diff options
Diffstat (limited to 'widget/windows')
132 files changed, 61210 insertions, 0 deletions
diff --git a/widget/windows/AudioSession.cpp b/widget/windows/AudioSession.cpp new file mode 100644 index 0000000000..11e5ba50cd --- /dev/null +++ b/widget/windows/AudioSession.cpp @@ -0,0 +1,453 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <windows.h> +#include <audiopolicy.h> +#include <mmdeviceapi.h> + +#include "mozilla/RefPtr.h" +#include "nsIStringBundle.h" +#include "nsIUUIDGenerator.h" +#include "nsIXULAppInfo.h" + +//#include "AudioSession.h" +#include "nsCOMPtr.h" +#include "nsServiceManagerUtils.h" +#include "nsString.h" +#include "nsThreadUtils.h" +#include "nsXULAppAPI.h" +#include "mozilla/Attributes.h" + +#include <objbase.h> + +namespace mozilla { +namespace widget { + +/* + * To take advantage of what Vista+ have to offer with respect to audio, + * we need to maintain an audio session. This class wraps IAudioSessionControl + * and implements IAudioSessionEvents (for callbacks from Windows) + */ +class AudioSession final : public IAudioSessionEvents { +private: + AudioSession(); + ~AudioSession(); +public: + static AudioSession* GetSingleton(); + + // COM IUnknown + STDMETHODIMP_(ULONG) AddRef(); + STDMETHODIMP QueryInterface(REFIID, void**); + STDMETHODIMP_(ULONG) Release(); + + // IAudioSessionEvents + STDMETHODIMP OnChannelVolumeChanged(DWORD aChannelCount, + float aChannelVolumeArray[], + DWORD aChangedChannel, + LPCGUID aContext); + STDMETHODIMP OnDisplayNameChanged(LPCWSTR aDisplayName, LPCGUID aContext); + STDMETHODIMP OnGroupingParamChanged(LPCGUID aGroupingParam, LPCGUID aContext); + STDMETHODIMP OnIconPathChanged(LPCWSTR aIconPath, LPCGUID aContext); + STDMETHODIMP OnSessionDisconnected(AudioSessionDisconnectReason aReason); +private: + nsresult OnSessionDisconnectedInternal(); +public: + STDMETHODIMP OnSimpleVolumeChanged(float aVolume, + BOOL aMute, + LPCGUID aContext); + STDMETHODIMP OnStateChanged(AudioSessionState aState); + + nsresult Start(); + nsresult Stop(); + void StopInternal(); + + nsresult GetSessionData(nsID& aID, + nsString& aSessionName, + nsString& aIconPath); + + nsresult SetSessionData(const nsID& aID, + const nsString& aSessionName, + const nsString& aIconPath); + + enum SessionState { + UNINITIALIZED, // Has not been initialized yet + STARTED, // Started + CLONED, // SetSessionInfoCalled, Start not called + FAILED, // The audio session failed to start + STOPPED, // Stop called + AUDIO_SESSION_DISCONNECTED // Audio session disconnected + }; +protected: + RefPtr<IAudioSessionControl> mAudioSessionControl; + nsString mDisplayName; + nsString mIconPath; + nsID mSessionGroupingParameter; + SessionState mState; + + ThreadSafeAutoRefCnt mRefCnt; + NS_DECL_OWNINGTHREAD + + static AudioSession* sService; +}; + +nsresult +StartAudioSession() +{ + return AudioSession::GetSingleton()->Start(); +} + +nsresult +StopAudioSession() +{ + return AudioSession::GetSingleton()->Stop(); +} + +nsresult +GetAudioSessionData(nsID& aID, + nsString& aSessionName, + nsString& aIconPath) +{ + return AudioSession::GetSingleton()->GetSessionData(aID, + aSessionName, + aIconPath); +} + +nsresult +RecvAudioSessionData(const nsID& aID, + const nsString& aSessionName, + const nsString& aIconPath) +{ + return AudioSession::GetSingleton()->SetSessionData(aID, + aSessionName, + aIconPath); +} + +AudioSession* AudioSession::sService = nullptr; + +AudioSession::AudioSession() +{ + mState = UNINITIALIZED; +} + +AudioSession::~AudioSession() +{ + +} + +AudioSession* +AudioSession::GetSingleton() +{ + if (!(AudioSession::sService)) { + RefPtr<AudioSession> service = new AudioSession(); + service.forget(&AudioSession::sService); + } + + // We don't refcount AudioSession on the Gecko side, we hold one single ref + // as long as the appshell is running. + return AudioSession::sService; +} + +// It appears Windows will use us on a background thread ... +NS_IMPL_ADDREF(AudioSession) +NS_IMPL_RELEASE(AudioSession) + +STDMETHODIMP +AudioSession::QueryInterface(REFIID iid, void **ppv) +{ + const IID IID_IAudioSessionEvents = __uuidof(IAudioSessionEvents); + if ((IID_IUnknown == iid) || + (IID_IAudioSessionEvents == iid)) { + *ppv = static_cast<IAudioSessionEvents*>(this); + AddRef(); + return S_OK; + } + + return E_NOINTERFACE; +} + +// Once we are started Windows will hold a reference to us through our +// IAudioSessionEvents interface that will keep us alive until the appshell +// calls Stop. +nsresult +AudioSession::Start() +{ + MOZ_ASSERT(mState == UNINITIALIZED || + mState == CLONED || + mState == AUDIO_SESSION_DISCONNECTED, + "State invariants violated"); + + const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator); + const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator); + const IID IID_IAudioSessionManager = __uuidof(IAudioSessionManager); + + HRESULT hr; + + // There's a matching CoUninit in Stop() for this tied to a state of + // UNINITIALIZED. + hr = CoInitialize(nullptr); + MOZ_ASSERT(SUCCEEDED(hr), "CoInitialize failure in audio session control, unexpected"); + + if (mState == UNINITIALIZED) { + mState = FAILED; + + // Content processes should be CLONED + if (XRE_IsContentProcess()) { + return NS_ERROR_FAILURE; + } + + MOZ_ASSERT(XRE_IsParentProcess(), + "Should only get here in a chrome process!"); + + nsCOMPtr<nsIStringBundleService> bundleService = + do_GetService(NS_STRINGBUNDLE_CONTRACTID); + NS_ENSURE_TRUE(bundleService, NS_ERROR_FAILURE); + nsCOMPtr<nsIStringBundle> bundle; + bundleService->CreateBundle("chrome://branding/locale/brand.properties", + getter_AddRefs(bundle)); + NS_ENSURE_TRUE(bundle, NS_ERROR_FAILURE); + + bundle->GetStringFromName(u"brandFullName", + getter_Copies(mDisplayName)); + + wchar_t *buffer; + mIconPath.GetMutableData(&buffer, MAX_PATH); + ::GetModuleFileNameW(nullptr, buffer, MAX_PATH); + + nsCOMPtr<nsIUUIDGenerator> uuidgen = + do_GetService("@mozilla.org/uuid-generator;1"); + NS_ENSURE_TRUE(uuidgen, NS_ERROR_FAILURE); + uuidgen->GenerateUUIDInPlace(&mSessionGroupingParameter); + } + + mState = FAILED; + + MOZ_ASSERT(!mDisplayName.IsEmpty() || !mIconPath.IsEmpty(), + "Should never happen ..."); + + RefPtr<IMMDeviceEnumerator> enumerator; + hr = ::CoCreateInstance(CLSID_MMDeviceEnumerator, + nullptr, + CLSCTX_ALL, + IID_IMMDeviceEnumerator, + getter_AddRefs(enumerator)); + if (FAILED(hr)) + return NS_ERROR_NOT_AVAILABLE; + + RefPtr<IMMDevice> device; + hr = enumerator->GetDefaultAudioEndpoint(EDataFlow::eRender, + ERole::eMultimedia, + getter_AddRefs(device)); + if (FAILED(hr)) { + if (hr == E_NOTFOUND) + return NS_ERROR_NOT_AVAILABLE; + return NS_ERROR_FAILURE; + } + + RefPtr<IAudioSessionManager> manager; + hr = device->Activate(IID_IAudioSessionManager, + CLSCTX_ALL, + nullptr, + getter_AddRefs(manager)); + if (FAILED(hr)) { + return NS_ERROR_FAILURE; + } + + hr = manager->GetAudioSessionControl(&GUID_NULL, + 0, + getter_AddRefs(mAudioSessionControl)); + + if (FAILED(hr)) { + return NS_ERROR_FAILURE; + } + + hr = mAudioSessionControl->SetGroupingParam((LPGUID)&mSessionGroupingParameter, + nullptr); + if (FAILED(hr)) { + StopInternal(); + return NS_ERROR_FAILURE; + } + + hr = mAudioSessionControl->SetDisplayName(mDisplayName.get(), nullptr); + if (FAILED(hr)) { + StopInternal(); + return NS_ERROR_FAILURE; + } + + hr = mAudioSessionControl->SetIconPath(mIconPath.get(), nullptr); + if (FAILED(hr)) { + StopInternal(); + return NS_ERROR_FAILURE; + } + + // Increments refcount of 'this'. + hr = mAudioSessionControl->RegisterAudioSessionNotification(this); + if (FAILED(hr)) { + StopInternal(); + return NS_ERROR_FAILURE; + } + + mState = STARTED; + + return NS_OK; +} + +void +AudioSession::StopInternal() +{ + if (mAudioSessionControl && + (mState == STARTED || mState == STOPPED)) { + // Decrement refcount of 'this' + mAudioSessionControl->UnregisterAudioSessionNotification(this); + } + mAudioSessionControl = nullptr; +} + +nsresult +AudioSession::Stop() +{ + MOZ_ASSERT(mState == STARTED || + mState == UNINITIALIZED || // XXXremove this + mState == FAILED, + "State invariants violated"); + SessionState state = mState; + mState = STOPPED; + + { + RefPtr<AudioSession> kungFuDeathGrip; + kungFuDeathGrip.swap(sService); + + StopInternal(); + } + + if (state != UNINITIALIZED) { + ::CoUninitialize(); + } + return NS_OK; +} + +void CopynsID(nsID& lhs, const nsID& rhs) +{ + lhs.m0 = rhs.m0; + lhs.m1 = rhs.m1; + lhs.m2 = rhs.m2; + for (int i = 0; i < 8; i++ ) { + lhs.m3[i] = rhs.m3[i]; + } +} + +nsresult +AudioSession::GetSessionData(nsID& aID, + nsString& aSessionName, + nsString& aIconPath) +{ + MOZ_ASSERT(mState == FAILED || + mState == STARTED || + mState == CLONED, + "State invariants violated"); + + CopynsID(aID, mSessionGroupingParameter); + aSessionName = mDisplayName; + aIconPath = mIconPath; + + if (mState == FAILED) + return NS_ERROR_FAILURE; + + return NS_OK; +} + +nsresult +AudioSession::SetSessionData(const nsID& aID, + const nsString& aSessionName, + const nsString& aIconPath) +{ + MOZ_ASSERT(mState == UNINITIALIZED, + "State invariants violated"); + MOZ_ASSERT(!XRE_IsParentProcess(), + "Should never get here in a chrome process!"); + mState = CLONED; + + CopynsID(mSessionGroupingParameter, aID); + mDisplayName = aSessionName; + mIconPath = aIconPath; + return NS_OK; +} + +STDMETHODIMP +AudioSession::OnChannelVolumeChanged(DWORD aChannelCount, + float aChannelVolumeArray[], + DWORD aChangedChannel, + LPCGUID aContext) +{ + return S_OK; // NOOP +} + +STDMETHODIMP +AudioSession::OnDisplayNameChanged(LPCWSTR aDisplayName, + LPCGUID aContext) +{ + return S_OK; // NOOP +} + +STDMETHODIMP +AudioSession::OnGroupingParamChanged(LPCGUID aGroupingParam, + LPCGUID aContext) +{ + return S_OK; // NOOP +} + +STDMETHODIMP +AudioSession::OnIconPathChanged(LPCWSTR aIconPath, + LPCGUID aContext) +{ + return S_OK; // NOOP +} + +STDMETHODIMP +AudioSession::OnSessionDisconnected(AudioSessionDisconnectReason aReason) +{ + // Run our code asynchronously. Per MSDN we can't do anything interesting + // in this callback. + nsCOMPtr<nsIRunnable> runnable = + NewRunnableMethod(this, &AudioSession::OnSessionDisconnectedInternal); + NS_DispatchToMainThread(runnable); + return S_OK; +} + +nsresult +AudioSession::OnSessionDisconnectedInternal() +{ + if (!mAudioSessionControl) + return NS_OK; + + // When successful, UnregisterAudioSessionNotification will decrement the + // refcount of 'this'. Start will re-increment it. In the interim, + // we'll need to reference ourselves. + RefPtr<AudioSession> kungFuDeathGrip(this); + mAudioSessionControl->UnregisterAudioSessionNotification(this); + mAudioSessionControl = nullptr; + + mState = AUDIO_SESSION_DISCONNECTED; + CoUninitialize(); + Start(); // If it fails there's not much we can do. + return NS_OK; +} + +STDMETHODIMP +AudioSession::OnSimpleVolumeChanged(float aVolume, + BOOL aMute, + LPCGUID aContext) +{ + return S_OK; // NOOP +} + +STDMETHODIMP +AudioSession::OnStateChanged(AudioSessionState aState) +{ + return S_OK; // NOOP +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/windows/AudioSession.h b/widget/windows/AudioSession.h new file mode 100644 index 0000000000..d7c1d3787a --- /dev/null +++ b/widget/windows/AudioSession.h @@ -0,0 +1,30 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsString.h" + +namespace mozilla { +namespace widget { + +// Start the audio session in the current process +nsresult StartAudioSession(); + +// Pass the information necessary to start an audio session in another process +nsresult GetAudioSessionData(nsID& aID, + nsString& aSessionName, + nsString& aIconPath); + +// Receive the information necessary to start an audio session in a non-chrome +// process +nsresult RecvAudioSessionData(const nsID& aID, + const nsString& aSessionName, + const nsString& aIconPath); + +// Stop the audio session in the current process +nsresult StopAudioSession(); + +} // namespace widget +} // namespace mozilla diff --git a/widget/windows/CompositorWidgetChild.cpp b/widget/windows/CompositorWidgetChild.cpp new file mode 100644 index 0000000000..55d71d21e0 --- /dev/null +++ b/widget/windows/CompositorWidgetChild.cpp @@ -0,0 +1,78 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "CompositorWidgetChild.h" +#include "mozilla/Unused.h" +#include "mozilla/widget/CompositorWidgetVsyncObserver.h" +#include "nsBaseWidget.h" +#include "VsyncDispatcher.h" + +namespace mozilla { +namespace widget { + +CompositorWidgetChild::CompositorWidgetChild(RefPtr<CompositorVsyncDispatcher> aVsyncDispatcher, + RefPtr<CompositorWidgetVsyncObserver> aVsyncObserver) + : mVsyncDispatcher(aVsyncDispatcher), + mVsyncObserver(aVsyncObserver) +{ + MOZ_ASSERT(XRE_IsParentProcess()); +} + +CompositorWidgetChild::~CompositorWidgetChild() +{ +} + +void +CompositorWidgetChild::EnterPresentLock() +{ + Unused << SendEnterPresentLock(); +} + +void +CompositorWidgetChild::LeavePresentLock() +{ + Unused << SendLeavePresentLock(); +} + +void +CompositorWidgetChild::OnDestroyWindow() +{ +} + +void +CompositorWidgetChild::UpdateTransparency(nsTransparencyMode aMode) +{ + Unused << SendUpdateTransparency(static_cast<int32_t>(aMode)); +} + +void +CompositorWidgetChild::ClearTransparentWindow() +{ + Unused << SendClearTransparentWindow(); +} + +HDC +CompositorWidgetChild::GetTransparentDC() const +{ + // Not supported in out-of-process mode. + return nullptr; +} + +bool +CompositorWidgetChild::RecvObserveVsync() +{ + mVsyncDispatcher->SetCompositorVsyncObserver(mVsyncObserver); + return true; +} + +bool +CompositorWidgetChild::RecvUnobserveVsync() +{ + mVsyncDispatcher->SetCompositorVsyncObserver(nullptr); + return true; +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/windows/CompositorWidgetChild.h b/widget/windows/CompositorWidgetChild.h new file mode 100644 index 0000000000..ee6e90ca9c --- /dev/null +++ b/widget/windows/CompositorWidgetChild.h @@ -0,0 +1,45 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef widget_windows_CompositorWidgetChild_h +#define widget_windows_CompositorWidgetChild_h + +#include "WinCompositorWidget.h" +#include "mozilla/widget/PCompositorWidgetChild.h" +#include "mozilla/widget/CompositorWidgetVsyncObserver.h" + +namespace mozilla { +class CompositorVsyncDispatcher; + +namespace widget { + +class CompositorWidgetChild final + : public PCompositorWidgetChild, + public CompositorWidgetDelegate +{ +public: + CompositorWidgetChild(RefPtr<CompositorVsyncDispatcher> aVsyncDispatcher, + RefPtr<CompositorWidgetVsyncObserver> aVsyncObserver); + ~CompositorWidgetChild() override; + + void EnterPresentLock() override; + void LeavePresentLock() override; + void OnDestroyWindow() override; + void UpdateTransparency(nsTransparencyMode aMode) override; + void ClearTransparentWindow() override; + HDC GetTransparentDC() const override; + + bool RecvObserveVsync() override; + bool RecvUnobserveVsync() override; + +private: + RefPtr<CompositorVsyncDispatcher> mVsyncDispatcher; + RefPtr<CompositorWidgetVsyncObserver> mVsyncObserver; +}; + +} // namespace widget +} // namespace mozilla + +#endif // widget_windows_CompositorWidgetChild_h diff --git a/widget/windows/CompositorWidgetParent.cpp b/widget/windows/CompositorWidgetParent.cpp new file mode 100644 index 0000000000..c784ff72ef --- /dev/null +++ b/widget/windows/CompositorWidgetParent.cpp @@ -0,0 +1,81 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "CompositorWidgetParent.h" + +#include "mozilla/Unused.h" + +namespace mozilla { +namespace widget { + +CompositorWidgetParent::CompositorWidgetParent(const CompositorWidgetInitData& aInitData) + : WinCompositorWidget(aInitData) +{ + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_GPU); +} + +CompositorWidgetParent::~CompositorWidgetParent() +{ +} + +bool +CompositorWidgetParent::RecvEnterPresentLock() +{ + EnterPresentLock(); + return true; +} + +bool +CompositorWidgetParent::RecvLeavePresentLock() +{ + LeavePresentLock(); + return true; +} + +bool +CompositorWidgetParent::RecvUpdateTransparency(const int32_t& aMode) +{ + UpdateTransparency(static_cast<nsTransparencyMode>(aMode)); + return true; +} + +bool +CompositorWidgetParent::RecvClearTransparentWindow() +{ + ClearTransparentWindow(); + return true; +} + +nsIWidget* +CompositorWidgetParent::RealWidget() +{ + return nullptr; +} + +void +CompositorWidgetParent::ObserveVsync(VsyncObserver* aObserver) +{ + if (aObserver) { + Unused << SendObserveVsync(); + } else { + Unused << SendUnobserveVsync(); + } + mVsyncObserver = aObserver; +} + +RefPtr<VsyncObserver> +CompositorWidgetParent::GetVsyncObserver() const +{ + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_GPU); + return mVsyncObserver; +} + +void +CompositorWidgetParent::ActorDestroy(ActorDestroyReason aWhy) +{ +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/windows/CompositorWidgetParent.h b/widget/windows/CompositorWidgetParent.h new file mode 100644 index 0000000000..a2f63bd72b --- /dev/null +++ b/widget/windows/CompositorWidgetParent.h @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _widget_windows_WinCompositorWidget_h__ +#define _widget_windows_WinCompositorWidget_h__ + +#include "WinCompositorWidget.h" +#include "mozilla/widget/PCompositorWidgetParent.h" + +namespace mozilla { +namespace widget { + +class CompositorWidgetParent final + : public PCompositorWidgetParent, + public WinCompositorWidget +{ +public: + CompositorWidgetParent(const CompositorWidgetInitData& aInitData); + ~CompositorWidgetParent() override; + + bool RecvEnterPresentLock() override; + bool RecvLeavePresentLock() override; + bool RecvUpdateTransparency(const int32_t& aMode) override; + bool RecvClearTransparentWindow() override; + void ActorDestroy(ActorDestroyReason aWhy) override; + + nsIWidget* RealWidget() override; + void ObserveVsync(VsyncObserver* aObserver) override; + RefPtr<VsyncObserver> GetVsyncObserver() const override; + +private: + RefPtr<VsyncObserver> mVsyncObserver; +}; + +} // namespace widget +} // namespace mozilla + +#endif // _widget_windows_WinCompositorWidget_h__ diff --git a/widget/windows/GfxInfo.cpp b/widget/windows/GfxInfo.cpp new file mode 100644 index 0000000000..0cbd323ded --- /dev/null +++ b/widget/windows/GfxInfo.cpp @@ -0,0 +1,1450 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/ArrayUtils.h" + +#include <windows.h> +#include <setupapi.h> +#include "gfxConfig.h" +#include "gfxWindowsPlatform.h" +#include "GfxInfo.h" +#include "GfxInfoWebGL.h" +#include "nsUnicharUtils.h" +#include "prenv.h" +#include "prprf.h" +#include "GfxDriverInfo.h" +#include "mozilla/Preferences.h" +#include "mozilla/gfx/DeviceManagerDx.h" +#include "mozilla/gfx/Logging.h" +#include "nsPrintfCString.h" +#include "jsapi.h" + +#if defined(MOZ_CRASHREPORTER) +#include "nsExceptionHandler.h" +#include "nsICrashReporter.h" +#define NS_CRASHREPORTER_CONTRACTID "@mozilla.org/toolkit/crash-reporter;1" +#endif + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::widget; + +#ifdef DEBUG +NS_IMPL_ISUPPORTS_INHERITED(GfxInfo, GfxInfoBase, nsIGfxInfoDebug) +#endif + +static const uint32_t allWindowsVersions = 0xffffffff; + +GfxInfo::GfxInfo() + : mWindowsVersion(0), + mHasDualGPU(false), + mIsGPU2Active(false) +{ +} + +/* GetD2DEnabled and GetDwriteEnabled shouldn't be called until after gfxPlatform initialization + * has occurred because they depend on it for information. (See bug 591561) */ +nsresult +GfxInfo::GetD2DEnabled(bool *aEnabled) +{ + // Telemetry queries this during XPCOM initialization, and there's no + // gfxPlatform by then. Just bail out if gfxPlatform isn't initialized. + if (!gfxPlatform::Initialized()) { + *aEnabled = false; + return NS_OK; + } + + // We check gfxConfig rather than the actual render mode, since the UI + // process does not use Direct2D if the GPU process is enabled. However, + // content processes can still use Direct2D. + *aEnabled = gfx::gfxConfig::IsEnabled(gfx::Feature::DIRECT2D); + return NS_OK; +} + +nsresult +GfxInfo::GetDWriteEnabled(bool *aEnabled) +{ + *aEnabled = gfxWindowsPlatform::GetPlatform()->DWriteEnabled(); + return NS_OK; +} + +NS_IMETHODIMP +GfxInfo::GetDWriteVersion(nsAString & aDwriteVersion) +{ + gfxWindowsPlatform::GetDLLVersion(L"dwrite.dll", aDwriteVersion); + return NS_OK; +} + +#define PIXEL_STRUCT_RGB 1 +#define PIXEL_STRUCT_BGR 2 + +NS_IMETHODIMP +GfxInfo::GetCleartypeParameters(nsAString & aCleartypeParams) +{ + nsTArray<ClearTypeParameterInfo> clearTypeParams; + + gfxWindowsPlatform::GetPlatform()->GetCleartypeParams(clearTypeParams); + uint32_t d, numDisplays = clearTypeParams.Length(); + bool displayNames = (numDisplays > 1); + bool foundData = false; + nsString outStr; + + for (d = 0; d < numDisplays; d++) { + ClearTypeParameterInfo& params = clearTypeParams[d]; + + if (displayNames) { + outStr.AppendPrintf("%S [ ", params.displayName.get()); + } + + if (params.gamma >= 0) { + foundData = true; + outStr.AppendPrintf("Gamma: %.4g ", params.gamma / 1000.0); + } + + if (params.pixelStructure >= 0) { + foundData = true; + if (params.pixelStructure == PIXEL_STRUCT_RGB || + params.pixelStructure == PIXEL_STRUCT_BGR) + { + outStr.AppendPrintf("Pixel Structure: %S ", + (params.pixelStructure == PIXEL_STRUCT_RGB + ? u"RGB" : u"BGR")); + } else { + outStr.AppendPrintf("Pixel Structure: %d ", params.pixelStructure); + } + } + + if (params.clearTypeLevel >= 0) { + foundData = true; + outStr.AppendPrintf("ClearType Level: %d ", params.clearTypeLevel); + } + + if (params.enhancedContrast >= 0) { + foundData = true; + outStr.AppendPrintf("Enhanced Contrast: %d ", params.enhancedContrast); + } + + if (displayNames) { + outStr.Append(u"] "); + } + } + + if (foundData) { + aCleartypeParams.Assign(outStr); + return NS_OK; + } + return NS_ERROR_FAILURE; +} + +static nsresult GetKeyValue(const WCHAR* keyLocation, const WCHAR* keyName, nsAString& destString, int type) +{ + HKEY key; + DWORD dwcbData; + DWORD dValue; + DWORD resultType; + LONG result; + nsresult retval = NS_OK; + + result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, keyLocation, 0, KEY_QUERY_VALUE, &key); + if (result != ERROR_SUCCESS) { + return NS_ERROR_FAILURE; + } + + switch (type) { + case REG_DWORD: { + // We only use this for vram size + dwcbData = sizeof(dValue); + result = RegQueryValueExW(key, keyName, nullptr, &resultType, + (LPBYTE)&dValue, &dwcbData); + if (result == ERROR_SUCCESS && resultType == REG_DWORD) { + dValue = dValue / 1024 / 1024; + destString.AppendInt(int32_t(dValue)); + } else { + retval = NS_ERROR_FAILURE; + } + break; + } + case REG_QWORD: { + // We only use this for vram size + LONGLONG qValue; + dwcbData = sizeof(qValue); + result = RegQueryValueExW(key, keyName, nullptr, &resultType, + (LPBYTE)&qValue, &dwcbData); + if (result == ERROR_SUCCESS && resultType == REG_QWORD) { + qValue = qValue / 1024 / 1024; + destString.AppendInt(int32_t(qValue)); + } else { + retval = NS_ERROR_FAILURE; + } + break; + } + case REG_MULTI_SZ: { + // A chain of null-separated strings; we convert the nulls to spaces + WCHAR wCharValue[1024]; + dwcbData = sizeof(wCharValue); + + result = RegQueryValueExW(key, keyName, nullptr, &resultType, + (LPBYTE)wCharValue, &dwcbData); + if (result == ERROR_SUCCESS && resultType == REG_MULTI_SZ) { + // This bit here could probably be cleaner. + bool isValid = false; + + DWORD strLen = dwcbData/sizeof(wCharValue[0]); + for (DWORD i = 0; i < strLen; i++) { + if (wCharValue[i] == '\0') { + if (i < strLen - 1 && wCharValue[i + 1] == '\0') { + isValid = true; + break; + } else { + wCharValue[i] = ' '; + } + } + } + + // ensure wCharValue is null terminated + wCharValue[strLen-1] = '\0'; + + if (isValid) + destString = wCharValue; + + } else { + retval = NS_ERROR_FAILURE; + } + + break; + } + } + RegCloseKey(key); + + return retval; +} + +// The device ID is a string like PCI\VEN_15AD&DEV_0405&SUBSYS_040515AD +// this function is used to extract the id's out of it +uint32_t +ParseIDFromDeviceID(const nsAString &key, const char *prefix, int length) +{ + nsAutoString id(key); + ToUpperCase(id); + int32_t start = id.Find(prefix); + if (start != -1) { + id.Cut(0, start + strlen(prefix)); + id.Truncate(length); + } + nsresult err; + return id.ToInteger(&err, 16); +} + +// OS version in 16.16 major/minor form +// based on http://msdn.microsoft.com/en-us/library/ms724834(VS.85).aspx +enum { + kWindowsUnknown = 0, + kWindowsXP = 0x50001, + kWindowsServer2003 = 0x50002, + kWindowsVista = 0x60000, + kWindows7 = 0x60001, + kWindows8 = 0x60002, + kWindows8_1 = 0x60003, + kWindows10 = 0xA0000 +}; + +static int32_t +WindowsOSVersion() +{ + static int32_t winVersion = UNINITIALIZED_VALUE; + + OSVERSIONINFO vinfo; + + if (winVersion == UNINITIALIZED_VALUE) { + vinfo.dwOSVersionInfoSize = sizeof (vinfo); +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4996) +#endif + if (!GetVersionEx(&vinfo)) { +#ifdef _MSC_VER +#pragma warning(pop) +#endif + winVersion = kWindowsUnknown; + } else { + winVersion = int32_t(vinfo.dwMajorVersion << 16) + vinfo.dwMinorVersion; + } + } + + return winVersion; +} + +/* Other interesting places for info: + * IDXGIAdapter::GetDesc() + * IDirectDraw7::GetAvailableVidMem() + * e->GetAvailableTextureMem() + * */ + +#define DEVICE_KEY_PREFIX L"\\Registry\\Machine\\" +nsresult +GfxInfo::Init() +{ + nsresult rv = GfxInfoBase::Init(); + + DISPLAY_DEVICEW displayDevice; + displayDevice.cb = sizeof(displayDevice); + int deviceIndex = 0; + + const char *spoofedWindowsVersion = PR_GetEnv("MOZ_GFX_SPOOF_WINDOWS_VERSION"); + if (spoofedWindowsVersion) { + PR_sscanf(spoofedWindowsVersion, "%x", &mWindowsVersion); + } else { + mWindowsVersion = WindowsOSVersion(); + } + + mDeviceKeyDebug = NS_LITERAL_STRING("PrimarySearch"); + + while (EnumDisplayDevicesW(nullptr, deviceIndex, &displayDevice, 0)) { + if (displayDevice.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE) { + mDeviceKeyDebug = NS_LITERAL_STRING("NullSearch"); + break; + } + deviceIndex++; + } + + // make sure the string is nullptr terminated + if (wcsnlen(displayDevice.DeviceKey, ArrayLength(displayDevice.DeviceKey)) + == ArrayLength(displayDevice.DeviceKey)) { + // we did not find a nullptr + return rv; + } + + mDeviceKeyDebug = displayDevice.DeviceKey; + + /* DeviceKey is "reserved" according to MSDN so we'll be careful with it */ + /* check that DeviceKey begins with DEVICE_KEY_PREFIX */ + /* some systems have a DeviceKey starting with \REGISTRY\Machine\ so we need to compare case insenstively */ + if (_wcsnicmp(displayDevice.DeviceKey, DEVICE_KEY_PREFIX, ArrayLength(DEVICE_KEY_PREFIX)-1) != 0) + return rv; + + // chop off DEVICE_KEY_PREFIX + mDeviceKey = displayDevice.DeviceKey + ArrayLength(DEVICE_KEY_PREFIX)-1; + + mDeviceID = displayDevice.DeviceID; + mDeviceString = displayDevice.DeviceString; + + // On Windows 8 and Server 2012 hosts, we want to not block RDP + // sessions from attempting hardware acceleration. RemoteFX + // provides features and functionaltiy that can give a good D3D10 + + // D2D + DirectWrite experience emulated via a software GPU. + // + // Unfortunately, the Device ID is nullptr, and we can't enumerate + // it using the setup infrastructure (SetupDiGetClassDevsW below + // will return INVALID_HANDLE_VALUE). + if (mWindowsVersion == kWindows8 && + mDeviceID.Length() == 0 && + mDeviceString.EqualsLiteral("RDPUDD Chained DD")) + { + WCHAR sysdir[255]; + UINT len = GetSystemDirectory(sysdir, sizeof(sysdir)); + if (len < sizeof(sysdir)) { + nsString rdpudd(sysdir); + rdpudd.AppendLiteral("\\rdpudd.dll"); + gfxWindowsPlatform::GetDLLVersion(rdpudd.BeginReading(), mDriverVersion); + mDriverDate.AssignLiteral("01-01-1970"); + + // 0x1414 is Microsoft; 0xfefe is an invented (and unused) code + mDeviceID.AssignLiteral("PCI\\VEN_1414&DEV_FEFE&SUBSYS_00000000"); + } + } + + /* create a device information set composed of the current display device */ + HDEVINFO devinfo = SetupDiGetClassDevsW(nullptr, mDeviceID.get(), nullptr, + DIGCF_PRESENT | DIGCF_PROFILE | DIGCF_ALLCLASSES); + + if (devinfo != INVALID_HANDLE_VALUE) { + HKEY key; + LONG result; + WCHAR value[255]; + DWORD dwcbData; + SP_DEVINFO_DATA devinfoData; + DWORD memberIndex = 0; + + devinfoData.cbSize = sizeof(devinfoData); + NS_NAMED_LITERAL_STRING(driverKeyPre, "System\\CurrentControlSet\\Control\\Class\\"); + /* enumerate device information elements in the device information set */ + while (SetupDiEnumDeviceInfo(devinfo, memberIndex++, &devinfoData)) { + /* get a string that identifies the device's driver key */ + if (SetupDiGetDeviceRegistryPropertyW(devinfo, + &devinfoData, + SPDRP_DRIVER, + nullptr, + (PBYTE)value, + sizeof(value), + nullptr)) { + nsAutoString driverKey(driverKeyPre); + driverKey += value; + result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, driverKey.get(), 0, KEY_QUERY_VALUE, &key); + if (result == ERROR_SUCCESS) { + /* we've found the driver we're looking for */ + dwcbData = sizeof(value); + result = RegQueryValueExW(key, L"DriverVersion", nullptr, nullptr, + (LPBYTE)value, &dwcbData); + if (result == ERROR_SUCCESS) { + mDriverVersion = value; + } else { + // If the entry wasn't found, assume the worst (0.0.0.0). + mDriverVersion.AssignLiteral("0.0.0.0"); + } + dwcbData = sizeof(value); + result = RegQueryValueExW(key, L"DriverDate", nullptr, nullptr, + (LPBYTE)value, &dwcbData); + if (result == ERROR_SUCCESS) { + mDriverDate = value; + } else { + // Again, assume the worst + mDriverDate.AssignLiteral("01-01-1970"); + } + RegCloseKey(key); + break; + } + } + } + + SetupDiDestroyDeviceInfoList(devinfo); + } + + mAdapterVendorID.AppendPrintf("0x%04x", ParseIDFromDeviceID(mDeviceID, "VEN_", 4)); + mAdapterDeviceID.AppendPrintf("0x%04x", ParseIDFromDeviceID(mDeviceID, "&DEV_", 4)); + mAdapterSubsysID.AppendPrintf("%08x", ParseIDFromDeviceID(mDeviceID, "&SUBSYS_", 8)); + + // We now check for second display adapter. + + // Device interface class for display adapters. + CLSID GUID_DISPLAY_DEVICE_ARRIVAL; + HRESULT hresult = CLSIDFromString(L"{1CA05180-A699-450A-9A0C-DE4FBE3DDD89}", + &GUID_DISPLAY_DEVICE_ARRIVAL); + if (hresult == NOERROR) { + devinfo = SetupDiGetClassDevsW(&GUID_DISPLAY_DEVICE_ARRIVAL, + nullptr, nullptr, + DIGCF_PRESENT | DIGCF_INTERFACEDEVICE); + + if (devinfo != INVALID_HANDLE_VALUE) { + HKEY key; + LONG result; + WCHAR value[255]; + DWORD dwcbData; + SP_DEVINFO_DATA devinfoData; + DWORD memberIndex = 0; + devinfoData.cbSize = sizeof(devinfoData); + + nsAutoString adapterDriver2; + nsAutoString deviceID2; + nsAutoString driverVersion2; + nsAutoString driverDate2; + uint32_t adapterVendorID2; + uint32_t adapterDeviceID2; + + NS_NAMED_LITERAL_STRING(driverKeyPre, "System\\CurrentControlSet\\Control\\Class\\"); + /* enumerate device information elements in the device information set */ + while (SetupDiEnumDeviceInfo(devinfo, memberIndex++, &devinfoData)) { + /* get a string that identifies the device's driver key */ + if (SetupDiGetDeviceRegistryPropertyW(devinfo, + &devinfoData, + SPDRP_DRIVER, + nullptr, + (PBYTE)value, + sizeof(value), + nullptr)) { + nsAutoString driverKey2(driverKeyPre); + driverKey2 += value; + result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, driverKey2.get(), 0, KEY_QUERY_VALUE, &key); + if (result == ERROR_SUCCESS) { + dwcbData = sizeof(value); + result = RegQueryValueExW(key, L"MatchingDeviceId", nullptr, + nullptr, (LPBYTE)value, &dwcbData); + if (result != ERROR_SUCCESS) { + continue; + } + deviceID2 = value; + nsAutoString adapterVendorID2String; + nsAutoString adapterDeviceID2String; + adapterVendorID2 = ParseIDFromDeviceID(deviceID2, "VEN_", 4); + adapterVendorID2String.AppendPrintf("0x%04x", adapterVendorID2); + adapterDeviceID2 = ParseIDFromDeviceID(deviceID2, "&DEV_", 4); + adapterDeviceID2String.AppendPrintf("0x%04x", adapterDeviceID2); + if (mAdapterVendorID == adapterVendorID2String && + mAdapterDeviceID == adapterDeviceID2String) { + RegCloseKey(key); + continue; + } + + // If this device is missing driver information, it is unlikely to + // be a real display adapter. + if (NS_FAILED(GetKeyValue(driverKey2.get(), L"InstalledDisplayDrivers", + adapterDriver2, REG_MULTI_SZ))) { + RegCloseKey(key); + continue; + } + dwcbData = sizeof(value); + result = RegQueryValueExW(key, L"DriverVersion", nullptr, nullptr, + (LPBYTE)value, &dwcbData); + if (result != ERROR_SUCCESS) { + RegCloseKey(key); + continue; + } + driverVersion2 = value; + dwcbData = sizeof(value); + result = RegQueryValueExW(key, L"DriverDate", nullptr, nullptr, + (LPBYTE)value, &dwcbData); + if (result != ERROR_SUCCESS) { + RegCloseKey(key); + continue; + } + driverDate2 = value; + dwcbData = sizeof(value); + result = RegQueryValueExW(key, L"Device Description", nullptr, + nullptr, (LPBYTE)value, &dwcbData); + if (result != ERROR_SUCCESS) { + dwcbData = sizeof(value); + result = RegQueryValueExW(key, L"DriverDesc", nullptr, nullptr, + (LPBYTE)value, &dwcbData); + } + RegCloseKey(key); + if (result == ERROR_SUCCESS) { + mHasDualGPU = true; + mDeviceString2 = value; + mDeviceID2 = deviceID2; + mDeviceKey2 = driverKey2; + mDriverVersion2 = driverVersion2; + mDriverDate2 = driverDate2; + mAdapterVendorID2.AppendPrintf("0x%04x", adapterVendorID2); + mAdapterDeviceID2.AppendPrintf("0x%04x", adapterDeviceID2); + mAdapterSubsysID2.AppendPrintf("%08x", ParseIDFromDeviceID(mDeviceID2, "&SUBSYS_", 8)); + break; + } + } + } + } + + SetupDiDestroyDeviceInfoList(devinfo); + } + } + + mHasDriverVersionMismatch = false; + if (mAdapterVendorID == GfxDriverInfo::GetDeviceVendor(VendorIntel)) { + // we've had big crashers (bugs 590373 and 595364) apparently correlated + // with bad Intel driver installations where the DriverVersion reported + // by the registry was not the version of the DLL. + bool is64bitApp = sizeof(void*) == 8; + const char16_t *dllFileName = is64bitApp + ? u"igd10umd64.dll" + : u"igd10umd32.dll", + *dllFileName2 = is64bitApp + ? u"igd10iumd64.dll" + : u"igd10iumd32.dll"; + nsString dllVersion, dllVersion2; + gfxWindowsPlatform::GetDLLVersion((char16_t*)dllFileName, dllVersion); + gfxWindowsPlatform::GetDLLVersion((char16_t*)dllFileName2, dllVersion2); + + uint64_t dllNumericVersion = 0, dllNumericVersion2 = 0, + driverNumericVersion = 0, knownSafeMismatchVersion = 0; + ParseDriverVersion(dllVersion, &dllNumericVersion); + ParseDriverVersion(dllVersion2, &dllNumericVersion2); + + ParseDriverVersion(mDriverVersion, &driverNumericVersion); + ParseDriverVersion(NS_LITERAL_STRING("9.17.10.0"), &knownSafeMismatchVersion); + + // If there's a driver version mismatch, consider this harmful only when + // the driver version is less than knownSafeMismatchVersion. See the + // above comment about crashes with old mismatches. If the GetDllVersion + // call fails, we are not calling it a mismatch. + if ((dllNumericVersion != 0 && dllNumericVersion != driverNumericVersion) || + (dllNumericVersion2 != 0 && dllNumericVersion2 != driverNumericVersion)) { + if (driverNumericVersion < knownSafeMismatchVersion || + std::max(dllNumericVersion, dllNumericVersion2) < knownSafeMismatchVersion) { + mHasDriverVersionMismatch = true; + gfxWarningOnce() << "Mismatched driver versions between the registry " << mDriverVersion.get() << " and DLL(s) " << NS_ConvertUTF16toUTF8(dllVersion).get() << ", " << NS_ConvertUTF16toUTF8(dllVersion2).get() << " reported."; + } + } else if (dllNumericVersion == 0 && dllNumericVersion2 == 0) { + // Leave it as an asserting error for now, to see if we can find + // a system that exhibits this kind of a problem internally. + gfxCriticalErrorOnce() << "Potential driver version mismatch ignored due to missing DLLs " << NS_ConvertUTF16toUTF8(dllVersion).get() << " and " << NS_ConvertUTF16toUTF8(dllVersion2).get(); + } + } + + const char *spoofedDriverVersionString = PR_GetEnv("MOZ_GFX_SPOOF_DRIVER_VERSION"); + if (spoofedDriverVersionString) { + mDriverVersion.AssignASCII(spoofedDriverVersionString); + } + + const char *spoofedVendor = PR_GetEnv("MOZ_GFX_SPOOF_VENDOR_ID"); + if (spoofedVendor) { + mAdapterVendorID.AssignASCII(spoofedVendor); + } + + const char *spoofedDevice = PR_GetEnv("MOZ_GFX_SPOOF_DEVICE_ID"); + if (spoofedDevice) { + mAdapterDeviceID.AssignASCII(spoofedDevice); + } + + AddCrashReportAnnotations(); + + return rv; +} + +NS_IMETHODIMP +GfxInfo::GetAdapterDescription(nsAString & aAdapterDescription) +{ + aAdapterDescription = mDeviceString; + return NS_OK; +} + +NS_IMETHODIMP +GfxInfo::GetAdapterDescription2(nsAString & aAdapterDescription) +{ + aAdapterDescription = mDeviceString2; + return NS_OK; +} + +NS_IMETHODIMP +GfxInfo::GetAdapterRAM(nsAString & aAdapterRAM) +{ + if (NS_FAILED(GetKeyValue(mDeviceKey.get(), L"HardwareInformation.qwMemorySize", aAdapterRAM, REG_QWORD)) || aAdapterRAM.Length() == 0) { + if (NS_FAILED(GetKeyValue(mDeviceKey.get(), L"HardwareInformation.MemorySize", aAdapterRAM, REG_DWORD))) { + aAdapterRAM = L"Unknown"; + } + } + return NS_OK; +} + +NS_IMETHODIMP +GfxInfo::GetAdapterRAM2(nsAString & aAdapterRAM) +{ + if (!mHasDualGPU) { + aAdapterRAM.Truncate(); + } else if (NS_FAILED(GetKeyValue(mDeviceKey2.get(), L"HardwareInformation.qwMemorySize", aAdapterRAM, REG_QWORD)) || aAdapterRAM.Length() == 0) { + if (NS_FAILED(GetKeyValue(mDeviceKey2.get(), L"HardwareInformation.MemorySize", aAdapterRAM, REG_DWORD))) { + aAdapterRAM = L"Unknown"; + } + } + return NS_OK; +} + +NS_IMETHODIMP +GfxInfo::GetAdapterDriver(nsAString & aAdapterDriver) +{ + if (NS_FAILED(GetKeyValue(mDeviceKey.get(), L"InstalledDisplayDrivers", aAdapterDriver, REG_MULTI_SZ))) + aAdapterDriver = L"Unknown"; + return NS_OK; +} + +NS_IMETHODIMP +GfxInfo::GetAdapterDriver2(nsAString & aAdapterDriver) +{ + if (!mHasDualGPU) { + aAdapterDriver.Truncate(); + } else if (NS_FAILED(GetKeyValue(mDeviceKey2.get(), L"InstalledDisplayDrivers", aAdapterDriver, REG_MULTI_SZ))) { + aAdapterDriver = L"Unknown"; + } + return NS_OK; +} + +NS_IMETHODIMP +GfxInfo::GetAdapterDriverVersion(nsAString & aAdapterDriverVersion) +{ + aAdapterDriverVersion = mDriverVersion; + return NS_OK; +} + +NS_IMETHODIMP +GfxInfo::GetAdapterDriverDate(nsAString & aAdapterDriverDate) +{ + aAdapterDriverDate = mDriverDate; + return NS_OK; +} + +NS_IMETHODIMP +GfxInfo::GetAdapterDriverVersion2(nsAString & aAdapterDriverVersion) +{ + aAdapterDriverVersion = mDriverVersion2; + return NS_OK; +} + +NS_IMETHODIMP +GfxInfo::GetAdapterDriverDate2(nsAString & aAdapterDriverDate) +{ + aAdapterDriverDate = mDriverDate2; + return NS_OK; +} + +NS_IMETHODIMP +GfxInfo::GetAdapterVendorID(nsAString & aAdapterVendorID) +{ + aAdapterVendorID = mAdapterVendorID; + return NS_OK; +} + +NS_IMETHODIMP +GfxInfo::GetAdapterVendorID2(nsAString & aAdapterVendorID) +{ + aAdapterVendorID = mAdapterVendorID2; + return NS_OK; +} + +NS_IMETHODIMP +GfxInfo::GetAdapterDeviceID(nsAString & aAdapterDeviceID) +{ + aAdapterDeviceID = mAdapterDeviceID; + return NS_OK; +} + +NS_IMETHODIMP +GfxInfo::GetAdapterDeviceID2(nsAString & aAdapterDeviceID) +{ + aAdapterDeviceID = mAdapterDeviceID2; + return NS_OK; +} + +NS_IMETHODIMP +GfxInfo::GetAdapterSubsysID(nsAString & aAdapterSubsysID) +{ + aAdapterSubsysID = mAdapterSubsysID; + return NS_OK; +} + +NS_IMETHODIMP +GfxInfo::GetAdapterSubsysID2(nsAString & aAdapterSubsysID) +{ + aAdapterSubsysID = mAdapterSubsysID2; + return NS_OK; +} + +NS_IMETHODIMP +GfxInfo::GetIsGPU2Active(bool* aIsGPU2Active) +{ + *aIsGPU2Active = mIsGPU2Active; + return NS_OK; +} + +#if defined(MOZ_CRASHREPORTER) +/* Cisco's VPN software can cause corruption of the floating point state. + * Make a note of this in our crash reports so that some weird crashes + * make more sense */ +static void +CheckForCiscoVPN() { + LONG result; + HKEY key; + /* This will give false positives, but hopefully no false negatives */ + result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"Software\\Cisco Systems\\VPN Client", 0, KEY_QUERY_VALUE, &key); + if (result == ERROR_SUCCESS) { + RegCloseKey(key); + CrashReporter::AppendAppNotesToCrashReport(NS_LITERAL_CSTRING("Cisco VPN\n")); + } +} +#endif + +void +GfxInfo::AddCrashReportAnnotations() +{ +#if defined(MOZ_CRASHREPORTER) + CheckForCiscoVPN(); + + if (mHasDriverVersionMismatch) { + CrashReporter::AppendAppNotesToCrashReport(NS_LITERAL_CSTRING("DriverVersionMismatch\n")); + } + + nsString deviceID, vendorID, driverVersion, subsysID; + nsCString narrowDeviceID, narrowVendorID, narrowDriverVersion, narrowSubsysID; + + GetAdapterDeviceID(deviceID); + CopyUTF16toUTF8(deviceID, narrowDeviceID); + GetAdapterVendorID(vendorID); + CopyUTF16toUTF8(vendorID, narrowVendorID); + GetAdapterDriverVersion(driverVersion); + CopyUTF16toUTF8(driverVersion, narrowDriverVersion); + GetAdapterSubsysID(subsysID); + CopyUTF16toUTF8(subsysID, narrowSubsysID); + + CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("AdapterVendorID"), + narrowVendorID); + CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("AdapterDeviceID"), + narrowDeviceID); + CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("AdapterDriverVersion"), + narrowDriverVersion); + CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("AdapterSubsysID"), + narrowSubsysID); + + /* Add an App Note for now so that we get the data immediately. These + * can go away after we store the above in the socorro db */ + nsAutoCString note; + /* AppendPrintf only supports 32 character strings, mrghh. */ + note.AppendLiteral("AdapterVendorID: "); + note.Append(narrowVendorID); + note.AppendLiteral(", AdapterDeviceID: "); + note.Append(narrowDeviceID); + note.AppendLiteral(", AdapterSubsysID: "); + note.Append(narrowSubsysID); + note.AppendLiteral(", AdapterDriverVersion: "); + note.Append(NS_LossyConvertUTF16toASCII(driverVersion)); + + if (vendorID == GfxDriverInfo::GetDeviceVendor(VendorAll)) { + /* if we didn't find a valid vendorID lets append the mDeviceID string to try to find out why */ + note.AppendLiteral(", "); + LossyAppendUTF16toASCII(mDeviceID, note); + note.AppendLiteral(", "); + LossyAppendUTF16toASCII(mDeviceKeyDebug, note); + LossyAppendUTF16toASCII(mDeviceKeyDebug, note); + } + note.Append("\n"); + + if (mHasDualGPU) { + nsString deviceID2, vendorID2, subsysID2; + nsAutoString adapterDriverVersionString2; + nsCString narrowDeviceID2, narrowVendorID2, narrowSubsysID2; + + note.AppendLiteral("Has dual GPUs. GPU #2: "); + GetAdapterDeviceID2(deviceID2); + CopyUTF16toUTF8(deviceID2, narrowDeviceID2); + GetAdapterVendorID2(vendorID2); + CopyUTF16toUTF8(vendorID2, narrowVendorID2); + GetAdapterDriverVersion2(adapterDriverVersionString2); + GetAdapterSubsysID(subsysID2); + CopyUTF16toUTF8(subsysID2, narrowSubsysID2); + note.AppendLiteral("AdapterVendorID2: "); + note.Append(narrowVendorID2); + note.AppendLiteral(", AdapterDeviceID2: "); + note.Append(narrowDeviceID2); + note.AppendLiteral(", AdapterSubsysID2: "); + note.Append(narrowSubsysID2); + note.AppendLiteral(", AdapterDriverVersion2: "); + note.Append(NS_LossyConvertUTF16toASCII(adapterDriverVersionString2)); + } + CrashReporter::AppendAppNotesToCrashReport(note); + +#endif +} + +static OperatingSystem +WindowsVersionToOperatingSystem(int32_t aWindowsVersion) +{ + switch(aWindowsVersion) { + case kWindowsXP: + return OperatingSystem::WindowsXP; + case kWindowsServer2003: + return OperatingSystem::WindowsServer2003; + case kWindowsVista: + return OperatingSystem::WindowsVista; + case kWindows7: + return OperatingSystem::Windows7; + case kWindows8: + return OperatingSystem::Windows8; + case kWindows8_1: + return OperatingSystem::Windows8_1; + case kWindows10: + return OperatingSystem::Windows10; + case kWindowsUnknown: + default: + return OperatingSystem::Unknown; + } +} + +const nsTArray<GfxDriverInfo>& +GfxInfo::GetGfxDriverInfo() +{ + if (!mDriverInfo->Length()) { + /* + * It should be noted here that more specialized rules on certain features + * should be inserted -before- more generalized restriction. As the first + * match for feature/OS/device found in the list will be used for the final + * blacklisting call. + */ + + /* + * NVIDIA entries + */ + APPEND_TO_DRIVER_BLOCKLIST(OperatingSystem::WindowsXP, + (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorNVIDIA), GfxDriverInfo::allDevices, + GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, + DRIVER_LESS_THAN_OR_EQUAL, V(6,14,11,8745), "FEATURE_FAILURE_NV_XP", "nVidia driver > 187.45" ); + + /* + * The last 5 digit of the NVIDIA driver version maps to the version that + * NVIDIA uses. The minor version (15, 16, 17) corresponds roughtly to the + * OS (Vista, Win7, Win7) but they show up in smaller numbers across all + * OS versions (perhaps due to OS upgrades). So we want to support + * October 2009+ drivers across all these minor versions. + * + * 187.45 (late October 2009) and earlier contain a bug which can cause us + * to crash on shutdown. + */ + APPEND_TO_DRIVER_BLOCKLIST(OperatingSystem::WindowsVista, + (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorNVIDIA), GfxDriverInfo::allDevices, + GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, + DRIVER_LESS_THAN_OR_EQUAL, V(8,15,11,8745), + "FEATURE_FAILURE_NV_VISTA_15", "nVidia driver > 187.45" ); + APPEND_TO_DRIVER_BLOCKLIST(OperatingSystem::Windows7, + (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorNVIDIA), GfxDriverInfo::allDevices, + GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, + DRIVER_LESS_THAN_OR_EQUAL, V(8,15,11,8745), + "FEATURE_FAILURE_NV_W7_15", "nVidia driver > 187.45" ); + APPEND_TO_DRIVER_BLOCKLIST_RANGE(OperatingSystem::WindowsVista, + (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorNVIDIA), GfxDriverInfo::allDevices, + GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, + DRIVER_BETWEEN_INCLUSIVE_START, V(8,16,10,0000), V(8,16,11,8745), + "FEATURE_FAILURE_NV_VISTA_16", "nVidia driver > 187.45" ); + APPEND_TO_DRIVER_BLOCKLIST_RANGE(OperatingSystem::Windows7, + (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorNVIDIA), GfxDriverInfo::allDevices, + GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, + DRIVER_BETWEEN_INCLUSIVE_START, V(8,16,10,0000), V(8,16,11,8745), + "FEATURE_FAILURE_NV_W7_16", "nVidia driver > 187.45" ); + // Telemetry doesn't show any driver in this range so it might not even be required. + APPEND_TO_DRIVER_BLOCKLIST_RANGE(OperatingSystem::WindowsVista, + (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorNVIDIA), GfxDriverInfo::allDevices, + GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, + DRIVER_BETWEEN_INCLUSIVE_START, V(8,17,10,0000), V(8,17,11,8745), + "FEATURE_FAILURE_NV_VISTA_17", "nVidia driver > 187.45" ); + // Telemetry doesn't show any driver in this range so it might not even be required. + APPEND_TO_DRIVER_BLOCKLIST_RANGE(OperatingSystem::Windows7, + (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorNVIDIA), GfxDriverInfo::allDevices, + GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, + DRIVER_BETWEEN_INCLUSIVE_START, V(8,17,10,0000), V(8,17,11,8745), + "FEATURE_FAILURE_NV_W7_17", "nVidia driver > 187.45" ); + + /* + * AMD/ATI entries. 8.56.1.15 is the driver that shipped with Windows 7 RTM + */ + APPEND_TO_DRIVER_BLOCKLIST(OperatingSystem::Windows, + (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorATI), GfxDriverInfo::allDevices, + GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, + DRIVER_LESS_THAN, V(8,56,1,15), "FEATURE_FAILURE_AMD1", "8.56.1.15" ); + APPEND_TO_DRIVER_BLOCKLIST(OperatingSystem::Windows, + (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorAMD), GfxDriverInfo::allDevices, + GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, + DRIVER_LESS_THAN, V(8,56,1,15), "FEATURE_FAILURE_AMD2", "8.56.1.15" ); + + // Bug 1099252 + APPEND_TO_DRIVER_BLOCKLIST2(OperatingSystem::Windows7, + (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorATI), GfxDriverInfo::allDevices, + GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, + DRIVER_EQUAL, V(8,832,0,0), "FEATURE_FAILURE_BUG_1099252"); + + // Bug 1118695 + APPEND_TO_DRIVER_BLOCKLIST2(OperatingSystem::Windows7, + (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorATI), GfxDriverInfo::allDevices, + GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, + DRIVER_EQUAL, V(8,783,2,2000), "FEATURE_FAILURE_BUG_1118695"); + + // Bug 1198815 + APPEND_TO_DRIVER_BLOCKLIST_RANGE(OperatingSystem::Windows, + (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorATI), GfxDriverInfo::allDevices, + nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, + DRIVER_BETWEEN_INCLUSIVE, V(15,200,0,0), V(15,200,1062,1004), + "FEATURE_FAILURE_BUG_1198815", "15.200.0.0-15.200.1062.1004"); + + // Bug 1267970 + APPEND_TO_DRIVER_BLOCKLIST_RANGE(OperatingSystem::Windows10, + (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorATI), GfxDriverInfo::allDevices, + nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, + DRIVER_BETWEEN_INCLUSIVE, V(15,200,0,0), V(15,301,2301,1002), + "FEATURE_FAILURE_BUG_1267970", "15.200.0.0-15.301.2301.1002"); + APPEND_TO_DRIVER_BLOCKLIST_RANGE(OperatingSystem::Windows10, + (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorATI), GfxDriverInfo::allDevices, + nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, + DRIVER_BETWEEN_INCLUSIVE, V(16,100,0,0), V(16,300,2311,0), + "FEATURE_FAILURE_BUG_1267970", "16.100.0.0-16.300.2311.0"); + + /* + * Bug 783517 - crashes in AMD driver on Windows 8 + */ + APPEND_TO_DRIVER_BLOCKLIST_RANGE(OperatingSystem::Windows8, + (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorATI), GfxDriverInfo::allDevices, + GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, + DRIVER_BETWEEN_INCLUSIVE_START, V(8,982,0,0), V(8,983,0,0), + "FEATURE_FAILURE_BUG_783517_AMD", "!= 8.982.*.*" ); + APPEND_TO_DRIVER_BLOCKLIST_RANGE(OperatingSystem::Windows8, + (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorAMD), GfxDriverInfo::allDevices, + GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, + DRIVER_BETWEEN_INCLUSIVE_START, V(8,982,0,0), V(8,983,0,0), + "FEATURE_FAILURE_BUG_783517_ATI", "!= 8.982.*.*" ); + + /* OpenGL on any ATI/AMD hardware is discouraged + * See: + * bug 619773 - WebGL: Crash with blue screen : "NMI: Parity Check / Memory Parity Error" + * bugs 584403, 584404, 620924 - crashes in atioglxx + * + many complaints about incorrect rendering + */ + APPEND_TO_DRIVER_BLOCKLIST2(OperatingSystem::Windows, + (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorATI), GfxDriverInfo::allDevices, + nsIGfxInfo::FEATURE_OPENGL_LAYERS, nsIGfxInfo::FEATURE_DISCOURAGED, + DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_OGL_ATI_DIS" ); + APPEND_TO_DRIVER_BLOCKLIST2(OperatingSystem::Windows, + (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorAMD), GfxDriverInfo::allDevices, + nsIGfxInfo::FEATURE_OPENGL_LAYERS, nsIGfxInfo::FEATURE_DISCOURAGED, + DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_OGL_AMD_DIS" ); + + /* + * Intel entries + */ + + /* The driver versions used here come from bug 594877. They might not + * be particularly relevant anymore. + */ + #define IMPLEMENT_INTEL_DRIVER_BLOCKLIST(winVer, devFamily, driverVer, ruleId) \ + APPEND_TO_DRIVER_BLOCKLIST2( winVer, \ + (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorIntel), (GfxDeviceFamily*) GfxDriverInfo::GetDeviceFamily(devFamily), \ + GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, \ + DRIVER_LESS_THAN, driverVer, ruleId ) + + #define IMPLEMENT_INTEL_DRIVER_BLOCKLIST_D2D(winVer, devFamily, driverVer, ruleId) \ + APPEND_TO_DRIVER_BLOCKLIST2( winVer, \ + (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorIntel), (GfxDeviceFamily*) GfxDriverInfo::GetDeviceFamily(devFamily), \ + nsIGfxInfo::FEATURE_DIRECT2D, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, \ + DRIVER_BUILD_ID_LESS_THAN, driverVer, ruleId ) + + IMPLEMENT_INTEL_DRIVER_BLOCKLIST_D2D(OperatingSystem::WindowsVista, IntelGMA500, 1006, "FEATURE_FAILURE_594877_1"); + IMPLEMENT_INTEL_DRIVER_BLOCKLIST_D2D(OperatingSystem::WindowsVista, IntelGMA900, GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_594877_2"); + IMPLEMENT_INTEL_DRIVER_BLOCKLIST_D2D(OperatingSystem::WindowsVista, IntelGMA950, 1504, "FEATURE_FAILURE_594877_3"); + IMPLEMENT_INTEL_DRIVER_BLOCKLIST_D2D(OperatingSystem::WindowsVista, IntelGMA3150, 2124, "FEATURE_FAILURE_594877_4"); + IMPLEMENT_INTEL_DRIVER_BLOCKLIST_D2D(OperatingSystem::WindowsVista, IntelGMAX3000, 1666, "FEATURE_FAILURE_594877_5"); + IMPLEMENT_INTEL_DRIVER_BLOCKLIST_D2D(OperatingSystem::WindowsVista, IntelHDGraphicsToSandyBridge, 2202, "FEATURE_FAILURE_594877_6"); + + IMPLEMENT_INTEL_DRIVER_BLOCKLIST_D2D(OperatingSystem::Windows7, IntelGMA500, 2026, "FEATURE_FAILURE_594877_7"); + IMPLEMENT_INTEL_DRIVER_BLOCKLIST_D2D(OperatingSystem::Windows7, IntelGMA900, GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_594877_8"); + IMPLEMENT_INTEL_DRIVER_BLOCKLIST_D2D(OperatingSystem::Windows7, IntelGMA950, 1930, "FEATURE_FAILURE_594877_9"); + IMPLEMENT_INTEL_DRIVER_BLOCKLIST_D2D(OperatingSystem::Windows7, IntelGMA3150, 2117, "FEATURE_FAILURE_594877_10"); + IMPLEMENT_INTEL_DRIVER_BLOCKLIST_D2D(OperatingSystem::Windows7, IntelGMAX3000, 1930, "FEATURE_FAILURE_594877_11"); + IMPLEMENT_INTEL_DRIVER_BLOCKLIST_D2D(OperatingSystem::Windows7, IntelHDGraphicsToSandyBridge, 2202, "FEATURE_FAILURE_594877_12"); + + /* Disable Direct2D on Intel GMAX4500 devices because of rendering corruption discovered + * in bug 1180379. These seems to affect even the most recent drivers. We're black listing + * all of the devices to be safe even though we've only confirmed the issue on the G45 + */ + APPEND_TO_DRIVER_BLOCKLIST2(OperatingSystem::Windows, + (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorIntel), (GfxDeviceFamily*) GfxDriverInfo::GetDeviceFamily(IntelGMAX4500HD), + nsIGfxInfo::FEATURE_DIRECT2D, nsIGfxInfo::FEATURE_BLOCKED_DEVICE, + DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_1180379"); + + IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::WindowsXP, IntelGMA500, V(3,0,20,3200), "FEATURE_FAILURE_INTEL_1"); + IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::WindowsXP, IntelGMA900, V(6,14,10,4764), "FEATURE_FAILURE_INTEL_2"); + IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::WindowsXP, IntelGMA950, V(6,14,10,4926), "FEATURE_FAILURE_INTEL_3"); + IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::WindowsXP, IntelGMA3150, V(6,14,10,5134), "FEATURE_FAILURE_INTEL_4"); + IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::WindowsXP, IntelGMAX3000, V(6,14,10,5218), "FEATURE_FAILURE_INTEL_5"); + IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::WindowsXP, IntelGMAX4500HD, V(6,14,10,4969), "FEATURE_FAILURE_INTEL_6"); + IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::WindowsXP, IntelHDGraphicsToSandyBridge, V(6,14,10,4969), "FEATURE_FAILURE_INTEL_7"); + + // StrechRect seems to suffer from precision issues which leads to artifacting + // during content drawing starting with at least version 6.14.10.5082 + // and going until 6.14.10.5218. See bug 919454 and bug 949275 for more info. + APPEND_TO_DRIVER_BLOCKLIST_RANGE(OperatingSystem::WindowsXP, + const_cast<nsAString&>(GfxDriverInfo::GetDeviceVendor(VendorIntel)), + const_cast<GfxDeviceFamily*>(GfxDriverInfo::GetDeviceFamily(IntelGMAX4500HD)), + GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, + DRIVER_BETWEEN_EXCLUSIVE, V(6,14,10,5076), V(6,14,10,5218), "FEATURE_FAILURE_INTEL_8", "6.14.10.5218"); + + IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::WindowsVista, IntelGMA500, V(3,0,20,3200), "FEATURE_FAILURE_INTEL_9"); + IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::WindowsVista, IntelGMA900, GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_INTEL_10"); + IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::WindowsVista, IntelGMA950, V(7,14,10,1504), "FEATURE_FAILURE_INTEL_11"); + IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::WindowsVista, IntelGMA3150, V(7,14,10,1910), "FEATURE_FAILURE_INTEL_12"); + IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::WindowsVista, IntelGMAX3000, V(7,15,10,1666), "FEATURE_FAILURE_INTEL_13"); + IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::WindowsVista, IntelGMAX4500HD, V(7,15,10,1666), "FEATURE_FAILURE_INTEL_14"); + IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::WindowsVista, IntelHDGraphicsToSandyBridge, V(7,15,10,1666), "FEATURE_FAILURE_INTEL_15"); + + IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::Windows7, IntelGMA500, V(5,0,0,2026), "FEATURE_FAILURE_INTEL_16"); + IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::Windows7, IntelGMA900, GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_INTEL_17"); + IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::Windows7, IntelGMA950, V(8,15,10,1930), "FEATURE_FAILURE_INTEL_18"); + IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::Windows7, IntelGMA3150, V(8,14,10,1972), "FEATURE_FAILURE_INTEL_19"); + IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::Windows7, IntelGMAX3000, V(7,15,10,1666), "FEATURE_FAILURE_INTEL_20"); + IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::Windows7, IntelGMAX4500HD, V(7,15,10,1666), "FEATURE_FAILURE_INTEL_21"); + IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::Windows7, IntelHDGraphicsToSandyBridge, V(7,15,10,1666), "FEATURE_FAILURE_INTEL_22"); + + // Bug 1074378 + APPEND_TO_DRIVER_BLOCKLIST(OperatingSystem::Windows7, + (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorIntel), + (GfxDeviceFamily*) GfxDriverInfo::GetDeviceFamily(IntelGMAX4500HD), + GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, + DRIVER_EQUAL, V(8,15,10,1749), "FEATURE_FAILURE_BUG_1074378_1", "8.15.10.2342"); + APPEND_TO_DRIVER_BLOCKLIST(OperatingSystem::Windows7, + (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorIntel), + (GfxDeviceFamily*) GfxDriverInfo::GetDeviceFamily(IntelHDGraphicsToSandyBridge), + GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, + DRIVER_EQUAL, V(8,15,10,1749), "FEATURE_FAILURE_BUG_1074378_2", "8.15.10.2342"); + + /* OpenGL on any Intel hardware is discouraged */ + APPEND_TO_DRIVER_BLOCKLIST2(OperatingSystem::Windows, + (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorIntel), GfxDriverInfo::allDevices, + nsIGfxInfo::FEATURE_OPENGL_LAYERS, nsIGfxInfo::FEATURE_DISCOURAGED, + DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_INTEL_OGL_DIS" ); + + /** + * Disable acceleration on Intel HD 3000 for graphics drivers <= 8.15.10.2321. + * See bug 1018278 and bug 1060736. + */ + APPEND_TO_DRIVER_BLOCKLIST(OperatingSystem::Windows, + (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorIntel), (GfxDeviceFamily*) GfxDriverInfo::GetDeviceFamily(IntelHD3000), + GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, + DRIVER_BUILD_ID_LESS_THAN_OR_EQUAL, 2321, "FEATURE_FAILURE_BUG_1018278", "X.X.X.2342"); + + /* Disable D2D on Win7 on Intel HD Graphics on driver <= 8.15.10.2302 + * See bug 806786 + */ + APPEND_TO_DRIVER_BLOCKLIST2( OperatingSystem::Windows7, + (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorIntel), (GfxDeviceFamily*) GfxDriverInfo::GetDeviceFamily(IntelMobileHDGraphics), + nsIGfxInfo::FEATURE_DIRECT2D, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, + DRIVER_LESS_THAN_OR_EQUAL, V(8,15,10,2302), "FEATURE_FAILURE_BUG_806786" ); + + /* Disable D2D on Win8 on Intel HD Graphics on driver <= 8.15.10.2302 + * See bug 804144 and 863683 + */ + APPEND_TO_DRIVER_BLOCKLIST2( OperatingSystem::Windows8, + (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorIntel), (GfxDeviceFamily*) GfxDriverInfo::GetDeviceFamily(IntelMobileHDGraphics), + nsIGfxInfo::FEATURE_DIRECT2D, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, + DRIVER_LESS_THAN_OR_EQUAL, V(8,15,10,2302), "FEATURE_FAILURE_BUG_804144" ); + + /* Disable D3D11 layers on Intel G41 express graphics and Intel GM965, Intel X3100, for causing device resets. + * See bug 1116812. + */ + APPEND_TO_DRIVER_BLOCKLIST2(OperatingSystem::Windows, + (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorIntel), (GfxDeviceFamily*) GfxDriverInfo::GetDeviceFamily(Bug1116812), + nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS, nsIGfxInfo::FEATURE_BLOCKED_DEVICE, + DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_BUG_1116812" ); + + /* Disable D3D11 layers on Intel GMA 3150 for failing to allocate a shared handle for textures. + * See bug 1207665. Additionally block D2D so we don't accidentally use WARP. + */ + APPEND_TO_DRIVER_BLOCKLIST2(OperatingSystem::Windows, + (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorIntel), (GfxDeviceFamily*) GfxDriverInfo::GetDeviceFamily(Bug1207665), + nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS, nsIGfxInfo::FEATURE_BLOCKED_DEVICE, + DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_BUG_1207665_1" ); + APPEND_TO_DRIVER_BLOCKLIST2(OperatingSystem::Windows, + (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorIntel), (GfxDeviceFamily*) GfxDriverInfo::GetDeviceFamily(Bug1207665), + nsIGfxInfo::FEATURE_DIRECT2D, nsIGfxInfo::FEATURE_BLOCKED_DEVICE, + DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_BUG_1207665_2" ); + + /* Disable D2D on AMD Catalyst 14.4 until 14.6 + * See bug 984488 + */ + APPEND_TO_DRIVER_BLOCKLIST_RANGE(OperatingSystem::Windows, + (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorATI), GfxDriverInfo::allDevices, + nsIGfxInfo::FEATURE_DIRECT2D, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, + DRIVER_BETWEEN_INCLUSIVE_START, V(14,1,0,0), V(14,2,0,0), "FEATURE_FAILURE_BUG_984488_1", "ATI Catalyst 14.6+"); + APPEND_TO_DRIVER_BLOCKLIST_RANGE(OperatingSystem::Windows, + (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorAMD), GfxDriverInfo::allDevices, + nsIGfxInfo::FEATURE_DIRECT2D, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, + DRIVER_BETWEEN_INCLUSIVE_START, V(14,1,0,0), V(14,2,0,0), "FEATURE_FAILURE_BUG_984488_2", "ATI Catalyst 14.6+"); + + /* Disable D3D9 layers on NVIDIA 6100/6150/6200 series due to glitches + * whilst scrolling. See bugs: 612007, 644787 & 645872. + */ + APPEND_TO_DRIVER_BLOCKLIST2(OperatingSystem::Windows, + (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorNVIDIA), (GfxDeviceFamily*) GfxDriverInfo::GetDeviceFamily(NvidiaBlockD3D9Layers), + nsIGfxInfo::FEATURE_DIRECT3D_9_LAYERS, nsIGfxInfo::FEATURE_BLOCKED_DEVICE, + DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_BUG_612007" ); + + /* Microsoft RemoteFX; blocked less than 6.2.0.0 */ + APPEND_TO_DRIVER_BLOCKLIST(OperatingSystem::Windows, + (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorMicrosoft), GfxDriverInfo::allDevices, + GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, + DRIVER_LESS_THAN, V(6,2,0,0), "< 6.2.0.0", "FEATURE_FAILURE_REMOTE_FX" ); + + /* Bug 1008759: Optimus (NVidia) crash. Disable D2D on NV 310M. */ + APPEND_TO_DRIVER_BLOCKLIST2(OperatingSystem::Windows, + (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorNVIDIA), (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(Nvidia310M), + nsIGfxInfo::FEATURE_DIRECT2D, nsIGfxInfo::FEATURE_BLOCKED_DEVICE, + DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_BUG_1008759"); + + /* Bug 1139503: DXVA crashes with ATI cards on windows 10. */ + APPEND_TO_DRIVER_BLOCKLIST2(OperatingSystem::Windows10, + (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorATI), GfxDriverInfo::allDevices, + nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, + DRIVER_EQUAL, V(15,200,1006,0), "FEATURE_FAILURE_BUG_1139503"); + + /* Bug 1213107: D3D9 crashes with ATI cards on Windows 7. */ + APPEND_TO_DRIVER_BLOCKLIST_RANGE(OperatingSystem::Windows7, + (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorATI), GfxDriverInfo::allDevices, + nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, + DRIVER_BETWEEN_INCLUSIVE, V(8,861,0,0), V(8,862,6,5000), "FEATURE_FAILURE_BUG_1213107_1", "Radeon driver > 8.862.6.5000"); + APPEND_TO_DRIVER_BLOCKLIST_RANGE(OperatingSystem::Windows7, + (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorATI), GfxDriverInfo::allDevices, + nsIGfxInfo::FEATURE_WEBGL_ANGLE, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, + DRIVER_BETWEEN_INCLUSIVE, V(8,861,0,0), V(8,862,6,5000), "FEATURE_FAILURE_BUG_1213107_2", "Radeon driver > 8.862.6.5000"); + + /* This may not be needed at all */ + APPEND_TO_DRIVER_BLOCKLIST2(OperatingSystem::Windows7, + (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorIntel), (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(Bug1155608), + nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, + DRIVER_LESS_THAN, V(8,15,10,2869), "FEATURE_FAILURE_INTEL_W7_HW_DECODING"); + + /* Bug 1203199/1092166: DXVA startup crashes on some intel drivers. */ + APPEND_TO_DRIVER_BLOCKLIST(OperatingSystem::Windows, + (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorIntel), GfxDriverInfo::allDevices, + nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, + DRIVER_BUILD_ID_LESS_THAN_OR_EQUAL, 2849, "FEATURE_FAILURE_BUG_1203199_1", "Intel driver > X.X.X.2849"); + + APPEND_TO_DRIVER_BLOCKLIST2(OperatingSystem::Windows, + (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorNVIDIA), (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(Nvidia8800GTS), + nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, + DRIVER_EQUAL, V(9,18,13,4052), "FEATURE_FAILURE_BUG_1203199_2"); + + /* Bug 1137716: XXX this should really check for the matching Intel piece as well. + * Unfortunately, we don't have the infrastructure to do that */ + APPEND_TO_DRIVER_BLOCKLIST_RANGE_GPU2(OperatingSystem::Windows7, + (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorNVIDIA), (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(Bug1137716), + GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, + DRIVER_BETWEEN_INCLUSIVE, V(8,17,12,5730), V(8,17,12,6901), "FEATURE_FAILURE_BUG_1137716", "Nvidia driver > 8.17.12.6901"); + + /* Bug 1153381: WebGL issues with D3D11 ANGLE on Intel. These may be fixed by an ANGLE update. */ + APPEND_TO_DRIVER_BLOCKLIST2(OperatingSystem::Windows, + (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorIntel), (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(IntelGMAX4500HD), + nsIGfxInfo::FEATURE_DIRECT3D_11_ANGLE, nsIGfxInfo::FEATURE_BLOCKED_DEVICE, + DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_BUG_1153381"); + + /* Bug 1336710: Crash in rx::Blit9::initialize. */ + APPEND_TO_DRIVER_BLOCKLIST2(OperatingSystem::WindowsXP, + (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorIntel), (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(IntelGMAX4500HD), + nsIGfxInfo::FEATURE_WEBGL_ANGLE, nsIGfxInfo::FEATURE_BLOCKED_DEVICE, + DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_BUG_1336710"); + + APPEND_TO_DRIVER_BLOCKLIST2(OperatingSystem::WindowsXP, + (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorIntel), (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(IntelHDGraphicsToSandyBridge), + nsIGfxInfo::FEATURE_WEBGL_ANGLE, nsIGfxInfo::FEATURE_BLOCKED_DEVICE, + DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_BUG_1336710"); + + /* Bug 1304360: Graphical artifacts with D3D9 on Windows 7. */ + APPEND_TO_DRIVER_BLOCKLIST2(OperatingSystem::Windows7, + (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorIntel), (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(IntelGMAX3000), + nsIGfxInfo::FEATURE_DIRECT3D_9_LAYERS, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, + DRIVER_BUILD_ID_LESS_THAN_OR_EQUAL, 1749, "FEATURE_FAILURE_INTEL_W7_D3D9_LAYERS"); + + //////////////////////////////////// + // WebGL + + // Older than 5-15-2016 + APPEND_TO_DRIVER_BLOCKLIST2(OperatingSystem::Windows, + (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorAMD), GfxDriverInfo::allDevices, + nsIGfxInfo::FEATURE_WEBGL_OPENGL, nsIGfxInfo::FEATURE_DISCOURAGED, DRIVER_LESS_THAN, + V(16,200,1010,1002), "WEBGL_NATIVE_GL_OLD_AMD"); + + // Older than 11-18-2015 + APPEND_TO_DRIVER_BLOCKLIST2(OperatingSystem::Windows, + (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorIntel), GfxDriverInfo::allDevices, + nsIGfxInfo::FEATURE_WEBGL_OPENGL, nsIGfxInfo::FEATURE_DISCOURAGED, DRIVER_BUILD_ID_LESS_THAN, + 4331, "WEBGL_NATIVE_GL_OLD_INTEL"); + + // Older than 2-23-2016 + APPEND_TO_DRIVER_BLOCKLIST2(OperatingSystem::Windows, + (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorNVIDIA), GfxDriverInfo::allDevices, + nsIGfxInfo::FEATURE_WEBGL_OPENGL, nsIGfxInfo::FEATURE_DISCOURAGED, DRIVER_LESS_THAN, + V(10,18,13,6200), "WEBGL_NATIVE_GL_OLD_NVIDIA"); + + //////////////////////////////////// + // FEATURE_DX_INTEROP2 + + // All AMD. + APPEND_TO_DRIVER_BLOCKLIST2(OperatingSystem::Windows, + (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorAMD), GfxDriverInfo::allDevices, + nsIGfxInfo::FEATURE_DX_INTEROP2, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, + DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions, "DX_INTEROP2_AMD_CRASH"); + } + return *mDriverInfo; +} + +nsresult +GfxInfo::GetFeatureStatusImpl(int32_t aFeature, + int32_t *aStatus, + nsAString & aSuggestedDriverVersion, + const nsTArray<GfxDriverInfo>& aDriverInfo, + nsACString& aFailureId, + OperatingSystem* aOS /* = nullptr */) +{ + NS_ENSURE_ARG_POINTER(aStatus); + aSuggestedDriverVersion.SetIsVoid(true); + OperatingSystem os = WindowsVersionToOperatingSystem(mWindowsVersion); + *aStatus = nsIGfxInfo::FEATURE_STATUS_UNKNOWN; + if (aOS) + *aOS = os; + + // Don't evaluate special cases if we're checking the downloaded blocklist. + if (!aDriverInfo.Length()) { + nsAutoString adapterVendorID; + nsAutoString adapterDeviceID; + nsAutoString adapterDriverVersionString; + if (NS_FAILED(GetAdapterVendorID(adapterVendorID)) || + NS_FAILED(GetAdapterDeviceID(adapterDeviceID)) || + NS_FAILED(GetAdapterDriverVersion(adapterDriverVersionString))) + { + aFailureId = "FEATURE_FAILURE_GET_ADAPTER"; + *aStatus = FEATURE_BLOCKED_DEVICE; + return NS_OK; + } + + if (!adapterVendorID.Equals(GfxDriverInfo::GetDeviceVendor(VendorIntel), nsCaseInsensitiveStringComparator()) && + !adapterVendorID.Equals(GfxDriverInfo::GetDeviceVendor(VendorNVIDIA), nsCaseInsensitiveStringComparator()) && + !adapterVendorID.Equals(GfxDriverInfo::GetDeviceVendor(VendorAMD), nsCaseInsensitiveStringComparator()) && + !adapterVendorID.Equals(GfxDriverInfo::GetDeviceVendor(VendorATI), nsCaseInsensitiveStringComparator()) && + !adapterVendorID.Equals(GfxDriverInfo::GetDeviceVendor(VendorMicrosoft), nsCaseInsensitiveStringComparator()) && + // FIXME - these special hex values are currently used in xpcshell tests introduced by + // bug 625160 patch 8/8. Maybe these tests need to be adjusted now that we're only whitelisting + // intel/ati/nvidia. + !adapterVendorID.LowerCaseEqualsLiteral("0xabcd") && + !adapterVendorID.LowerCaseEqualsLiteral("0xdcba") && + !adapterVendorID.LowerCaseEqualsLiteral("0xabab") && + !adapterVendorID.LowerCaseEqualsLiteral("0xdcdc")) + { + aFailureId = "FEATURE_FAILURE_UNKNOWN_DEVICE_VENDOR"; + *aStatus = FEATURE_BLOCKED_DEVICE; + return NS_OK; + } + + uint64_t driverVersion; + if (!ParseDriverVersion(adapterDriverVersionString, &driverVersion)) { + aFailureId = "FEATURE_FAILURE_PARSE_DRIVER"; + *aStatus = FEATURE_BLOCKED_DRIVER_VERSION; + return NS_OK; + } + + // special-case the WinXP test slaves: they have out-of-date drivers, but we still want to + // whitelist them, actually we do know that this combination of device and driver version + // works well. + if (mWindowsVersion == kWindowsXP && + adapterVendorID.Equals(GfxDriverInfo::GetDeviceVendor(VendorNVIDIA), nsCaseInsensitiveStringComparator()) && + adapterDeviceID.LowerCaseEqualsLiteral("0x0861") && // GeForce 9400 + driverVersion == V(6,14,11,7756)) + { + *aStatus = FEATURE_STATUS_OK; + return NS_OK; + } + + // Windows Server 2003 should be just like Windows XP for present purpose, but still has a different version number. + // OTOH Windows Server 2008 R1 and R2 already have the same version numbers as Vista and Seven respectively + if (os == OperatingSystem::WindowsServer2003) + os = OperatingSystem::WindowsXP; + + if (mHasDriverVersionMismatch) { + *aStatus = nsIGfxInfo::FEATURE_BLOCKED_MISMATCHED_VERSION; + return NS_OK; + } + } + + return GfxInfoBase::GetFeatureStatusImpl(aFeature, aStatus, aSuggestedDriverVersion, aDriverInfo, aFailureId, &os); +} + +nsresult +GfxInfo::FindMonitors(JSContext* aCx, JS::HandleObject aOutArray) +{ + int deviceCount = 0; + for (int deviceIndex = 0;; deviceIndex++) { + DISPLAY_DEVICEA device; + device.cb = sizeof(device); + if (!::EnumDisplayDevicesA(nullptr, deviceIndex, &device, 0)) { + break; + } + + if (!(device.StateFlags & DISPLAY_DEVICE_ACTIVE)) { + continue; + } + + DEVMODEA mode; + mode.dmSize = sizeof(mode); + mode.dmDriverExtra = 0; + if (!::EnumDisplaySettingsA(device.DeviceName, ENUM_CURRENT_SETTINGS, &mode)) { + continue; + } + + JS::Rooted<JSObject*> obj(aCx, JS_NewPlainObject(aCx)); + + JS::Rooted<JS::Value> screenWidth(aCx, JS::Int32Value(mode.dmPelsWidth)); + JS_SetProperty(aCx, obj, "screenWidth", screenWidth); + + JS::Rooted<JS::Value> screenHeight(aCx, JS::Int32Value(mode.dmPelsHeight)); + JS_SetProperty(aCx, obj, "screenHeight", screenHeight); + + JS::Rooted<JS::Value> refreshRate(aCx, JS::Int32Value(mode.dmDisplayFrequency)); + JS_SetProperty(aCx, obj, "refreshRate", refreshRate); + + JS::Rooted<JS::Value> pseudoDisplay(aCx, + JS::BooleanValue(!!(device.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER))); + JS_SetProperty(aCx, obj, "pseudoDisplay", pseudoDisplay); + + JS::Rooted<JS::Value> element(aCx, JS::ObjectValue(*obj)); + JS_SetElement(aCx, aOutArray, deviceCount++, element); + } + return NS_OK; +} + +void +GfxInfo::DescribeFeatures(JSContext* aCx, JS::Handle<JSObject*> aObj) +{ + // Add the platform neutral features + GfxInfoBase::DescribeFeatures(aCx, aObj); + + JS::Rooted<JSObject*> obj(aCx); + + gfx::FeatureStatus d3d11 = gfxConfig::GetValue(Feature::D3D11_COMPOSITING); + if (!InitFeatureObject(aCx, aObj, "d3d11", FEATURE_DIRECT3D_11_ANGLE, + Some(d3d11), &obj)) { + return; + } + if (d3d11 == gfx::FeatureStatus::Available) { + DeviceManagerDx* dm = DeviceManagerDx::Get(); + JS::Rooted<JS::Value> val(aCx, JS::Int32Value(dm->GetCompositorFeatureLevel())); + JS_SetProperty(aCx, obj, "version", val); + + val = JS::BooleanValue(dm->IsWARP()); + JS_SetProperty(aCx, obj, "warp", val); + + val = JS::BooleanValue(dm->TextureSharingWorks()); + JS_SetProperty(aCx, obj, "textureSharing", val); + + bool blacklisted = false; + if (nsCOMPtr<nsIGfxInfo> gfxInfo = services::GetGfxInfo()) { + int32_t status; + nsCString discardFailureId; + if (SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS, discardFailureId, &status))) { + blacklisted = (status != nsIGfxInfo::FEATURE_STATUS_OK); + } + } + + val = JS::BooleanValue(blacklisted); + JS_SetProperty(aCx, obj, "blacklisted", val); + } + + gfx::FeatureStatus d2d = gfxConfig::GetValue(Feature::DIRECT2D); + if (!InitFeatureObject(aCx, aObj, "d2d", nsIGfxInfo::FEATURE_DIRECT2D, + Some(d2d), &obj)) { + return; + } + { + const char* version = "1.1"; + JS::Rooted<JSString*> str(aCx, JS_NewStringCopyZ(aCx, version)); + JS::Rooted<JS::Value> val(aCx, JS::StringValue(str)); + JS_SetProperty(aCx, obj, "version", val); + } +} + +#ifdef DEBUG + +// Implement nsIGfxInfoDebug + +NS_IMETHODIMP GfxInfo::SpoofVendorID(const nsAString & aVendorID) +{ + mAdapterVendorID = aVendorID; + return NS_OK; +} + +NS_IMETHODIMP GfxInfo::SpoofDeviceID(const nsAString & aDeviceID) +{ + mAdapterDeviceID = aDeviceID; + return NS_OK; +} + +NS_IMETHODIMP GfxInfo::SpoofDriverVersion(const nsAString & aDriverVersion) +{ + mDriverVersion = aDriverVersion; + return NS_OK; +} + +NS_IMETHODIMP GfxInfo::SpoofOSVersion(uint32_t aVersion) +{ + mWindowsVersion = aVersion; + return NS_OK; +} + +#endif diff --git a/widget/windows/GfxInfo.h b/widget/windows/GfxInfo.h new file mode 100644 index 0000000000..c7aa859a6e --- /dev/null +++ b/widget/windows/GfxInfo.h @@ -0,0 +1,102 @@ +/* vim: se cin sw=2 ts=2 et : */ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __mozilla_widget_GfxInfo_h__ +#define __mozilla_widget_GfxInfo_h__ + +#include "GfxInfoBase.h" + +namespace mozilla { +namespace widget { + +class GfxInfo : public GfxInfoBase +{ + ~GfxInfo() {} +public: + GfxInfo(); + + // We only declare the subset of nsIGfxInfo that we actually implement. The + // rest is brought forward from GfxInfoBase. + NS_IMETHOD GetD2DEnabled(bool *aD2DEnabled) override; + NS_IMETHOD GetDWriteEnabled(bool *aDWriteEnabled) override; + NS_IMETHOD GetDWriteVersion(nsAString & aDwriteVersion) override; + NS_IMETHOD GetCleartypeParameters(nsAString & aCleartypeParams) override; + NS_IMETHOD GetAdapterDescription(nsAString & aAdapterDescription) override; + NS_IMETHOD GetAdapterDriver(nsAString & aAdapterDriver) override; + NS_IMETHOD GetAdapterVendorID(nsAString & aAdapterVendorID) override; + NS_IMETHOD GetAdapterDeviceID(nsAString & aAdapterDeviceID) override; + NS_IMETHOD GetAdapterSubsysID(nsAString & aAdapterSubsysID) override; + NS_IMETHOD GetAdapterRAM(nsAString & aAdapterRAM) override; + NS_IMETHOD GetAdapterDriverVersion(nsAString & aAdapterDriverVersion) override; + NS_IMETHOD GetAdapterDriverDate(nsAString & aAdapterDriverDate) override; + NS_IMETHOD GetAdapterDescription2(nsAString & aAdapterDescription) override; + NS_IMETHOD GetAdapterDriver2(nsAString & aAdapterDriver) override; + NS_IMETHOD GetAdapterVendorID2(nsAString & aAdapterVendorID) override; + NS_IMETHOD GetAdapterDeviceID2(nsAString & aAdapterDeviceID) override; + NS_IMETHOD GetAdapterSubsysID2(nsAString & aAdapterSubsysID) override; + NS_IMETHOD GetAdapterRAM2(nsAString & aAdapterRAM) override; + NS_IMETHOD GetAdapterDriverVersion2(nsAString & aAdapterDriverVersion) override; + NS_IMETHOD GetAdapterDriverDate2(nsAString & aAdapterDriverDate) override; + NS_IMETHOD GetIsGPU2Active(bool *aIsGPU2Active) override; + using GfxInfoBase::GetFeatureStatus; + using GfxInfoBase::GetFeatureSuggestedDriverVersion; + using GfxInfoBase::GetWebGLParameter; + + virtual nsresult Init() override; + + virtual uint32_t OperatingSystemVersion() override { return mWindowsVersion; } + + nsresult FindMonitors(JSContext* cx, JS::HandleObject array) override; + +#ifdef DEBUG + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIGFXINFODEBUG +#endif + +protected: + + virtual nsresult GetFeatureStatusImpl(int32_t aFeature, + int32_t *aStatus, + nsAString & aSuggestedDriverVersion, + const nsTArray<GfxDriverInfo>& aDriverInfo, + nsACString& aFailureId, + OperatingSystem* aOS = nullptr) override; + virtual const nsTArray<GfxDriverInfo>& GetGfxDriverInfo() override; + + void DescribeFeatures(JSContext* cx, JS::Handle<JSObject*> aOut) override; + +private: + + void AddCrashReportAnnotations(); + + nsString mDeviceString; + nsString mDeviceID; + nsString mDriverVersion; + nsString mDriverDate; + nsString mDeviceKey; + nsString mDeviceKeyDebug; + nsString mAdapterVendorID; + nsString mAdapterDeviceID; + nsString mAdapterSubsysID; + nsString mDeviceString2; + nsString mDriverVersion2; + nsString mDeviceID2; + nsString mDriverDate2; + nsString mDeviceKey2; + nsString mAdapterVendorID2; + nsString mAdapterDeviceID2; + nsString mAdapterSubsysID2; + uint32_t mWindowsVersion; + bool mHasDualGPU; + bool mIsGPU2Active; + bool mHasDriverVersionMismatch; +}; + +} // namespace widget +} // namespace mozilla + +#endif /* __mozilla_widget_GfxInfo_h__ */ diff --git a/widget/windows/IEnumFE.cpp b/widget/windows/IEnumFE.cpp new file mode 100644 index 0000000000..4fa76258ba --- /dev/null +++ b/widget/windows/IEnumFE.cpp @@ -0,0 +1,173 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "IEnumFE.h" +#include "nsAlgorithm.h" +#include <algorithm> + +CEnumFormatEtc::CEnumFormatEtc() : + mRefCnt(0), + mCurrentIdx(0) +{ +} + +// Constructor used by Clone() +CEnumFormatEtc::CEnumFormatEtc(nsTArray<FormatEtc>& aArray) : + mRefCnt(0), + mCurrentIdx(0) +{ + // a deep copy, calls FormatEtc's copy constructor on each + mFormatList.AppendElements(aArray); +} + +CEnumFormatEtc::~CEnumFormatEtc() +{ +} + +/* IUnknown impl. */ + +STDMETHODIMP +CEnumFormatEtc::QueryInterface(REFIID riid, LPVOID *ppv) +{ + *ppv = nullptr; + + if (IsEqualIID(riid, IID_IUnknown) || + IsEqualIID(riid, IID_IEnumFORMATETC)) + *ppv = (LPVOID)this; + + if (*ppv == nullptr) + return E_NOINTERFACE; + + // AddRef any interface we'll return. + ((LPUNKNOWN)*ppv)->AddRef(); + return S_OK; +} + +STDMETHODIMP_(ULONG) +CEnumFormatEtc::AddRef() +{ + ++mRefCnt; + NS_LOG_ADDREF(this, mRefCnt, "CEnumFormatEtc",sizeof(*this)); + return mRefCnt; +} + +STDMETHODIMP_(ULONG) +CEnumFormatEtc::Release() +{ + uint32_t refReturn; + + refReturn = --mRefCnt; + NS_LOG_RELEASE(this, mRefCnt, "CEnumFormatEtc"); + + if (mRefCnt == 0) + delete this; + + return refReturn; +} + +/* IEnumFORMATETC impl. */ + +STDMETHODIMP +CEnumFormatEtc::Next(ULONG aMaxToFetch, FORMATETC *aResult, ULONG *aNumFetched) +{ + // If the method retrieves the number of items requested, the return + // value is S_OK. Otherwise, it is S_FALSE. + + if (aNumFetched) + *aNumFetched = 0; + + // aNumFetched can be null if aMaxToFetch is 1 + if (!aNumFetched && aMaxToFetch > 1) + return S_FALSE; + + if (!aResult) + return S_FALSE; + + // We're done walking the list + if (mCurrentIdx >= mFormatList.Length()) + return S_FALSE; + + uint32_t left = mFormatList.Length() - mCurrentIdx; + + if (!aMaxToFetch) + return S_FALSE; + + uint32_t count = std::min(static_cast<uint32_t>(aMaxToFetch), left); + + uint32_t idx = 0; + while (count > 0) { + // Copy out to aResult + mFormatList[mCurrentIdx++].CopyOut(&aResult[idx++]); + count--; + } + + if (aNumFetched) + *aNumFetched = idx; + + return S_OK; +} + +STDMETHODIMP +CEnumFormatEtc::Skip(ULONG aSkipNum) +{ + // If the method skips the number of items requested, the return value is S_OK. + // Otherwise, it is S_FALSE. + + if ((mCurrentIdx + aSkipNum) >= mFormatList.Length()) + return S_FALSE; + + mCurrentIdx += aSkipNum; + + return S_OK; +} + +STDMETHODIMP +CEnumFormatEtc::Reset(void) +{ + mCurrentIdx = 0; + return S_OK; +} + +STDMETHODIMP +CEnumFormatEtc::Clone(LPENUMFORMATETC *aResult) +{ + // Must return a new IEnumFORMATETC interface with the same iterative state. + + if (!aResult) + return E_INVALIDARG; + + CEnumFormatEtc * pEnumObj = new CEnumFormatEtc(mFormatList); + + if (!pEnumObj) + return E_OUTOFMEMORY; + + pEnumObj->AddRef(); + pEnumObj->SetIndex(mCurrentIdx); + + *aResult = pEnumObj; + + return S_OK; +} + +/* utils */ + +void +CEnumFormatEtc::AddFormatEtc(LPFORMATETC aFormat) +{ + if (!aFormat) + return; + FormatEtc * etc = mFormatList.AppendElement(); + // Make a copy of aFormat + if (etc) + etc->CopyIn(aFormat); +} + +/* private */ + +void +CEnumFormatEtc::SetIndex(uint32_t aIdx) +{ + mCurrentIdx = aIdx; +} diff --git a/widget/windows/IEnumFE.h b/widget/windows/IEnumFE.h new file mode 100644 index 0000000000..d3875865a9 --- /dev/null +++ b/widget/windows/IEnumFE.h @@ -0,0 +1,90 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef IEnumeFE_h__ +#define IEnumeFE_h__ + +/* + * CEnumFormatEtc - implements IEnumFORMATETC + */ + +#include <ole2.h> + +#include "nsTArray.h" +#include "mozilla/Attributes.h" + +// FORMATETC container +class FormatEtc +{ +public: + FormatEtc() { memset(&mFormat, 0, sizeof(FORMATETC)); } + FormatEtc(const FormatEtc& copy) { CopyIn(©.mFormat); } + ~FormatEtc() { if (mFormat.ptd) CoTaskMemFree(mFormat.ptd); } + + void CopyIn(const FORMATETC *aSrc) { + if (!aSrc) { + memset(&mFormat, 0, sizeof(FORMATETC)); + return; + } + mFormat = *aSrc; + if (aSrc->ptd) { + mFormat.ptd = (DVTARGETDEVICE*)CoTaskMemAlloc(sizeof(DVTARGETDEVICE)); + *(mFormat.ptd) = *(aSrc->ptd); + } + } + + void CopyOut(LPFORMATETC aDest) { + if (!aDest) + return; + *aDest = mFormat; + if (mFormat.ptd) { + aDest->ptd = (DVTARGETDEVICE*)CoTaskMemAlloc(sizeof(DVTARGETDEVICE)); + *(aDest->ptd) = *(mFormat.ptd); + } + } + +private: + FORMATETC mFormat; +}; + +/* + * CEnumFormatEtc is created within IDataObject::EnumFormatEtc. This object lives + * on its own, that is, QueryInterface only knows IUnknown and IEnumFormatEtc, + * nothing more. We still use an outer unknown for reference counting, because as + * long as this enumerator lives, the data object should live, thereby keeping the + * application up. + */ + +class CEnumFormatEtc final : public IEnumFORMATETC +{ +public: + CEnumFormatEtc(nsTArray<FormatEtc>& aArray); + CEnumFormatEtc(); + ~CEnumFormatEtc(); + + // IUnknown impl. + STDMETHODIMP QueryInterface(REFIID riid, LPVOID *ppv); + STDMETHODIMP_(ULONG) AddRef(); + STDMETHODIMP_(ULONG) Release(); + + // IEnumFORMATETC impl. + STDMETHODIMP Next(ULONG aMaxToFetch, FORMATETC *aResult, ULONG *aNumFetched); + STDMETHODIMP Skip(ULONG aSkipNum); + STDMETHODIMP Reset(); + STDMETHODIMP Clone(LPENUMFORMATETC *aResult); // Addrefs + + // Utils + void AddFormatEtc(LPFORMATETC aFormat); + +private: + nsTArray<FormatEtc> mFormatList; // Formats + ULONG mRefCnt; // Object reference count + ULONG mCurrentIdx; // Current element + + void SetIndex(uint32_t aIdx); +}; + + +#endif //_IENUMFE_H_ diff --git a/widget/windows/IMMHandler.cpp b/widget/windows/IMMHandler.cpp new file mode 100644 index 0000000000..9bd7d2e7a9 --- /dev/null +++ b/widget/windows/IMMHandler.cpp @@ -0,0 +1,2910 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sts=2 sw=2 et cin: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Logging.h" + +#include "IMMHandler.h" +#include "nsWindow.h" +#include "nsWindowDefs.h" +#include "WinUtils.h" +#include "KeyboardLayout.h" +#include <algorithm> + +#include "mozilla/CheckedInt.h" +#include "mozilla/MiscEvents.h" +#include "mozilla/TextEvents.h" + +#ifndef IME_PROP_ACCEPT_WIDE_VKEY +#define IME_PROP_ACCEPT_WIDE_VKEY 0x20 +#endif + +//------------------------------------------------------------------------- +// +// from http://download.microsoft.com/download/6/0/9/60908e9e-d2c1-47db-98f6-216af76a235f/msime.h +// The document for this has been removed from MSDN... +// +//------------------------------------------------------------------------- + +#define RWM_MOUSE TEXT("MSIMEMouseOperation") + +#define IMEMOUSE_NONE 0x00 // no mouse button was pushed +#define IMEMOUSE_LDOWN 0x01 +#define IMEMOUSE_RDOWN 0x02 +#define IMEMOUSE_MDOWN 0x04 +#define IMEMOUSE_WUP 0x10 // wheel up +#define IMEMOUSE_WDOWN 0x20 // wheel down + +static const char* +GetBoolName(bool aBool) +{ + return aBool ? "true" : "false"; +} + +static void +HandleSeparator(nsACString& aDesc) +{ + if (!aDesc.IsEmpty()) { + aDesc.AppendLiteral(" | "); + } +} + +class GetIMEGeneralPropertyName : public nsAutoCString +{ +public: + GetIMEGeneralPropertyName(DWORD aFlags) + { + if (!aFlags) { + AppendLiteral("no flags"); + return; + } + if (aFlags & IME_PROP_AT_CARET) { + AppendLiteral("IME_PROP_AT_CARET"); + } + if (aFlags & IME_PROP_SPECIAL_UI) { + HandleSeparator(*this); + AppendLiteral("IME_PROP_SPECIAL_UI"); + } + if (aFlags & IME_PROP_CANDLIST_START_FROM_1) { + HandleSeparator(*this); + AppendLiteral("IME_PROP_CANDLIST_START_FROM_1"); + } + if (aFlags & IME_PROP_UNICODE) { + HandleSeparator(*this); + AppendLiteral("IME_PROP_UNICODE"); + } + if (aFlags & IME_PROP_COMPLETE_ON_UNSELECT) { + HandleSeparator(*this); + AppendLiteral("IME_PROP_COMPLETE_ON_UNSELECT"); + } + if (aFlags & IME_PROP_ACCEPT_WIDE_VKEY) { + HandleSeparator(*this); + AppendLiteral("IME_PROP_ACCEPT_WIDE_VKEY"); + } + } + virtual ~GetIMEGeneralPropertyName() {} +}; + +class GetIMEUIPropertyName : public nsAutoCString +{ +public: + GetIMEUIPropertyName(DWORD aFlags) + { + if (!aFlags) { + AppendLiteral("no flags"); + return; + } + if (aFlags & UI_CAP_2700) { + AppendLiteral("UI_CAP_2700"); + } + if (aFlags & UI_CAP_ROT90) { + HandleSeparator(*this); + AppendLiteral("UI_CAP_ROT90"); + } + if (aFlags & UI_CAP_ROTANY) { + HandleSeparator(*this); + AppendLiteral("UI_CAP_ROTANY"); + } + } + virtual ~GetIMEUIPropertyName() {} +}; + +class GetWritingModeName : public nsAutoCString +{ +public: + GetWritingModeName(const WritingMode& aWritingMode) + { + if (!aWritingMode.IsVertical()) { + Assign("Horizontal"); + return; + } + if (aWritingMode.IsVerticalLR()) { + Assign("Vertical (LR)"); + return; + } + Assign("Vertical (RL)"); + } + virtual ~GetWritingModeName() {} +}; + +class GetReconvertStringLog : public nsAutoCString +{ +public: + GetReconvertStringLog(RECONVERTSTRING* aReconv) + { + AssignLiteral("{ dwSize="); + AppendInt(static_cast<uint32_t>(aReconv->dwSize)); + AppendLiteral(", dwVersion="); + AppendInt(static_cast<uint32_t>(aReconv->dwVersion)); + AppendLiteral(", dwStrLen="); + AppendInt(static_cast<uint32_t>(aReconv->dwStrLen)); + AppendLiteral(", dwStrOffset="); + AppendInt(static_cast<uint32_t>(aReconv->dwStrOffset)); + AppendLiteral(", dwCompStrLen="); + AppendInt(static_cast<uint32_t>(aReconv->dwCompStrLen)); + AppendLiteral(", dwCompStrOffset="); + AppendInt(static_cast<uint32_t>(aReconv->dwCompStrOffset)); + AppendLiteral(", dwTargetStrLen="); + AppendInt(static_cast<uint32_t>(aReconv->dwTargetStrLen)); + AppendLiteral(", dwTargetStrOffset="); + AppendInt(static_cast<uint32_t>(aReconv->dwTargetStrOffset)); + AppendLiteral(", result str=\""); + if (aReconv->dwStrLen) { + char16_t* strStart = + reinterpret_cast<char16_t*>( + reinterpret_cast<char*>(aReconv) + aReconv->dwStrOffset); + nsDependentString str(strStart, aReconv->dwStrLen); + Append(NS_ConvertUTF16toUTF8(str)); + } + AppendLiteral("\" }"); + } + virtual ~GetReconvertStringLog() {} +}; + +namespace mozilla { +namespace widget { + +static IMMHandler* gIMMHandler = nullptr; + +LazyLogModule gIMMLog("nsIMM32HandlerWidgets"); + +/****************************************************************************** + * IMEContext + ******************************************************************************/ + +IMEContext::IMEContext(HWND aWnd) + : mWnd(aWnd) + , mIMC(::ImmGetContext(aWnd)) +{ +} + +IMEContext::IMEContext(nsWindowBase* aWindowBase) + : mWnd(aWindowBase->GetWindowHandle()) + , mIMC(::ImmGetContext(aWindowBase->GetWindowHandle())) +{ +} + +void +IMEContext::Init(HWND aWnd) +{ + Clear(); + mWnd = aWnd; + mIMC = ::ImmGetContext(mWnd); +} + +void +IMEContext::Init(nsWindowBase* aWindowBase) +{ + Init(aWindowBase->GetWindowHandle()); +} + +void +IMEContext::Clear() +{ + if (mWnd && mIMC) { + ::ImmReleaseContext(mWnd, mIMC); + } + mWnd = nullptr; + mIMC = nullptr; +} + +/****************************************************************************** + * IMMHandler + ******************************************************************************/ + +static UINT sWM_MSIME_MOUSE = 0; // mouse message for MSIME 98/2000 + +WritingMode IMMHandler::sWritingModeOfCompositionFont; +nsString IMMHandler::sIMEName; +UINT IMMHandler::sCodePage = 0; +DWORD IMMHandler::sIMEProperty = 0; +DWORD IMMHandler::sIMEUIProperty = 0; +bool IMMHandler::sAssumeVerticalWritingModeNotSupported = false; +bool IMMHandler::sHasFocus = false; +bool IMMHandler::sNativeCaretIsCreatedForPlugin = false; + +// static +void +IMMHandler::EnsureHandlerInstance() +{ + if (!gIMMHandler) { + gIMMHandler = new IMMHandler(); + } +} + +// static +void +IMMHandler::Initialize() +{ + if (!sWM_MSIME_MOUSE) { + sWM_MSIME_MOUSE = ::RegisterWindowMessage(RWM_MOUSE); + } + sAssumeVerticalWritingModeNotSupported = + Preferences::GetBool( + "intl.imm.vertical_writing.always_assume_not_supported", false); + InitKeyboardLayout(nullptr, ::GetKeyboardLayout(0)); +} + +// static +void +IMMHandler::Terminate() +{ + if (!gIMMHandler) + return; + delete gIMMHandler; + gIMMHandler = nullptr; +} + +// static +bool +IMMHandler::IsComposingOnOurEditor() +{ + return gIMMHandler && gIMMHandler->mIsComposing; +} + +// static +bool +IMMHandler::IsComposingOnPlugin() +{ + return gIMMHandler && gIMMHandler->mIsComposingOnPlugin; +} + +// static +bool +IMMHandler::IsComposingWindow(nsWindow* aWindow) +{ + return gIMMHandler && gIMMHandler->mComposingWindow == aWindow; +} + +// static +bool +IMMHandler::IsTopLevelWindowOfComposition(nsWindow* aWindow) +{ + if (!gIMMHandler || !gIMMHandler->mComposingWindow) { + return false; + } + HWND wnd = gIMMHandler->mComposingWindow->GetWindowHandle(); + return WinUtils::GetTopLevelHWND(wnd, true) == aWindow->GetWindowHandle(); +} + +// static +bool +IMMHandler::IsJapanist2003Active() +{ + return sIMEName.EqualsLiteral("Japanist 2003"); +} + +// static +bool +IMMHandler::IsGoogleJapaneseInputActive() +{ + // NOTE: Even on Windows for en-US, the name of Google Japanese Input is + // written in Japanese. + return sIMEName.Equals(L"Google \x65E5\x672C\x8A9E\x5165\x529B " + L"IMM32 \x30E2\x30B8\x30E5\x30FC\x30EB"); +} + +// static +bool +IMMHandler::ShouldDrawCompositionStringOurselves() +{ + // If current IME has special UI or its composition window should not + // positioned to caret position, we should now draw composition string + // ourselves. + return !(sIMEProperty & IME_PROP_SPECIAL_UI) && + (sIMEProperty & IME_PROP_AT_CARET); +} + +// static +bool +IMMHandler::IsVerticalWritingSupported() +{ + // Even if IME claims that they support vertical writing mode but it may not + // support vertical writing mode for its candidate window. + if (sAssumeVerticalWritingModeNotSupported) { + return false; + } + // Google Japanese Input doesn't support vertical writing mode. We should + // return false if it's active IME. + if (IsGoogleJapaneseInputActive()) { + return false; + } + return !!(sIMEUIProperty & (UI_CAP_2700 | UI_CAP_ROT90 | UI_CAP_ROTANY)); +} + +// static +void +IMMHandler::InitKeyboardLayout(nsWindow* aWindow, + HKL aKeyboardLayout) +{ + UINT IMENameLength = ::ImmGetDescriptionW(aKeyboardLayout, nullptr, 0); + if (IMENameLength) { + // Add room for the terminating null character + sIMEName.SetLength(++IMENameLength); + IMENameLength = + ::ImmGetDescriptionW(aKeyboardLayout, wwc(sIMEName.BeginWriting()), + IMENameLength); + // Adjust the length to ignore the terminating null character + sIMEName.SetLength(IMENameLength); + } else { + sIMEName.Truncate(); + } + + WORD langID = LOWORD(aKeyboardLayout); + ::GetLocaleInfoW(MAKELCID(langID, SORT_DEFAULT), + LOCALE_IDEFAULTANSICODEPAGE | LOCALE_RETURN_NUMBER, + (PWSTR)&sCodePage, sizeof(sCodePage) / sizeof(WCHAR)); + sIMEProperty = ::ImmGetProperty(aKeyboardLayout, IGP_PROPERTY); + sIMEUIProperty = ::ImmGetProperty(aKeyboardLayout, IGP_UI); + + // If active IME is a TIP of TSF, we cannot retrieve the name with IMM32 API. + // For hacking some bugs of some TIP, we should set an IME name from the + // pref. + if (sCodePage == 932 && sIMEName.IsEmpty()) { + sIMEName = + Preferences::GetString("intl.imm.japanese.assume_active_tip_name_as"); + } + + // Whether the IME supports vertical writing mode might be changed or + // some IMEs may need specific font for their UI. Therefore, we should + // update composition font forcibly here. + if (aWindow) { + MaybeAdjustCompositionFont(aWindow, sWritingModeOfCompositionFont, true); + } + + MOZ_LOG(gIMMLog, LogLevel::Info, + ("InitKeyboardLayout, aKeyboardLayout=%08x (\"%s\"), sCodePage=%lu, " + "sIMEProperty=%s, sIMEUIProperty=%s", + aKeyboardLayout, NS_ConvertUTF16toUTF8(sIMEName).get(), + sCodePage, GetIMEGeneralPropertyName(sIMEProperty).get(), + GetIMEUIPropertyName(sIMEUIProperty).get())); +} + +// static +UINT +IMMHandler::GetKeyboardCodePage() +{ + return sCodePage; +} + +// static +nsIMEUpdatePreference +IMMHandler::GetIMEUpdatePreference() +{ + return nsIMEUpdatePreference( + nsIMEUpdatePreference::NOTIFY_POSITION_CHANGE | + nsIMEUpdatePreference::NOTIFY_MOUSE_BUTTON_EVENT_ON_CHAR); +} + +// used for checking the lParam of WM_IME_COMPOSITION +#define IS_COMPOSING_LPARAM(lParam) \ + ((lParam) & (GCS_COMPSTR | GCS_COMPATTR | GCS_COMPCLAUSE | GCS_CURSORPOS)) +#define IS_COMMITTING_LPARAM(lParam) ((lParam) & GCS_RESULTSTR) +// Some IMEs (e.g., the standard IME for Korean) don't have caret position, +// then, we should not set caret position to compositionchange event. +#define NO_IME_CARET -1 + +IMMHandler::IMMHandler() + : mComposingWindow(nullptr) + , mCursorPosition(NO_IME_CARET) + , mCompositionStart(0) + , mIsComposing(false) + , mIsComposingOnPlugin(false) + , mNativeCaretIsCreated(false) +{ + MOZ_LOG(gIMMLog, LogLevel::Debug, ("IMMHandler is created")); +} + +IMMHandler::~IMMHandler() +{ + if (mIsComposing) { + MOZ_LOG(gIMMLog, LogLevel::Error, + ("~IMMHandler, ERROR, the instance is still composing")); + } + MOZ_LOG(gIMMLog, LogLevel::Debug, ("IMMHandler is destroyed")); +} + +nsresult +IMMHandler::EnsureClauseArray(int32_t aCount) +{ + NS_ENSURE_ARG_MIN(aCount, 0); + mClauseArray.SetCapacity(aCount + 32); + return NS_OK; +} + +nsresult +IMMHandler::EnsureAttributeArray(int32_t aCount) +{ + NS_ENSURE_ARG_MIN(aCount, 0); + mAttributeArray.SetCapacity(aCount + 64); + return NS_OK; +} + +// static +void +IMMHandler::CommitComposition(nsWindow* aWindow, bool aForce) +{ + MOZ_LOG(gIMMLog, LogLevel::Info, + ("CommitComposition, aForce=%s, aWindow=%p, hWnd=%08x, " + "mComposingWindow=%p%s", + GetBoolName(aForce), aWindow, aWindow->GetWindowHandle(), + gIMMHandler ? gIMMHandler->mComposingWindow : nullptr, + gIMMHandler && gIMMHandler->mComposingWindow ? + IsComposingOnOurEditor() ? " (composing on editor)" : + " (composing on plug-in)" : "")); + if (!aForce && !IsComposingWindow(aWindow)) { + return; + } + + IMEContext context(aWindow); + bool associated = context.AssociateDefaultContext(); + MOZ_LOG(gIMMLog, LogLevel::Info, + ("CommitComposition, associated=%s", + GetBoolName(associated))); + + if (context.IsValid()) { + ::ImmNotifyIME(context.get(), NI_COMPOSITIONSTR, CPS_COMPLETE, 0); + ::ImmNotifyIME(context.get(), NI_COMPOSITIONSTR, CPS_CANCEL, 0); + } + + if (associated) { + context.Disassociate(); + } +} + +// static +void +IMMHandler::CancelComposition(nsWindow* aWindow, bool aForce) +{ + MOZ_LOG(gIMMLog, LogLevel::Info, + ("CancelComposition, aForce=%s, aWindow=%p, hWnd=%08x, " + "mComposingWindow=%p%s", + GetBoolName(aForce), aWindow, aWindow->GetWindowHandle(), + gIMMHandler ? gIMMHandler->mComposingWindow : nullptr, + gIMMHandler && gIMMHandler->mComposingWindow ? + IsComposingOnOurEditor() ? " (composing on editor)" : + " (composing on plug-in)" : "")); + if (!aForce && !IsComposingWindow(aWindow)) { + return; + } + + IMEContext context(aWindow); + bool associated = context.AssociateDefaultContext(); + MOZ_LOG(gIMMLog, LogLevel::Info, + ("CancelComposition, associated=%s", + GetBoolName(associated))); + + if (context.IsValid()) { + ::ImmNotifyIME(context.get(), NI_COMPOSITIONSTR, CPS_CANCEL, 0); + } + + if (associated) { + context.Disassociate(); + } +} + +// static +void +IMMHandler::OnFocusChange(bool aFocus, nsWindow* aWindow) +{ + MOZ_LOG(gIMMLog, LogLevel::Info, + ("OnFocusChange(aFocus=%s, aWindow=%p), sHasFocus=%s, " + "IsComposingWindow(aWindow)=%s, aWindow->Destroyed()=%s, " + "sNativeCaretIsCreatedForPlugin=%s", + GetBoolName(aFocus), aWindow, GetBoolName(sHasFocus), + GetBoolName(IsComposingWindow(aWindow)), + GetBoolName(aWindow->Destroyed()), + GetBoolName(sNativeCaretIsCreatedForPlugin))); + + if (!aFocus) { + if (sNativeCaretIsCreatedForPlugin) { + ::DestroyCaret(); + sNativeCaretIsCreatedForPlugin = false; + } + if (IsComposingWindow(aWindow) && aWindow->Destroyed()) { + CancelComposition(aWindow); + } + } + if (gIMMHandler) { + gIMMHandler->mSelection.Clear(); + } + sHasFocus = aFocus; +} + +// static +void +IMMHandler::OnUpdateComposition(nsWindow* aWindow) +{ + if (!gIMMHandler) { + return; + } + + if (aWindow->PluginHasFocus()) { + return; + } + + IMEContext context(aWindow); + gIMMHandler->SetIMERelatedWindowsPos(aWindow, context); +} + +// static +void +IMMHandler::OnSelectionChange(nsWindow* aWindow, + const IMENotification& aIMENotification, + bool aIsIMMActive) +{ + if (!aIMENotification.mSelectionChangeData.mCausedByComposition && + aIsIMMActive) { + MaybeAdjustCompositionFont(aWindow, + aIMENotification.mSelectionChangeData.GetWritingMode()); + } + // MaybeAdjustCompositionFont() may create gIMMHandler. So, check it + // after a call of MaybeAdjustCompositionFont(). + if (gIMMHandler) { + gIMMHandler->mSelection.Update(aIMENotification); + } +} + +// static +void +IMMHandler::MaybeAdjustCompositionFont(nsWindow* aWindow, + const WritingMode& aWritingMode, + bool aForceUpdate) +{ + switch (sCodePage) { + case 932: // Japanese Shift-JIS + case 936: // Simlified Chinese GBK + case 949: // Korean + case 950: // Traditional Chinese Big5 + EnsureHandlerInstance(); + break; + default: + // If there is no instance of nsIMM32Hander, we shouldn't waste footprint. + if (!gIMMHandler) { + return; + } + } + + // Like Navi-Bar of ATOK, some IMEs may require proper composition font even + // before sending WM_IME_STARTCOMPOSITION. + IMEContext context(aWindow); + gIMMHandler->AdjustCompositionFont(aWindow, context, aWritingMode, + aForceUpdate); +} + +// static +bool +IMMHandler::ProcessInputLangChangeMessage(nsWindow* aWindow, + WPARAM wParam, + LPARAM lParam, + MSGResult& aResult) +{ + aResult.mResult = 0; + aResult.mConsumed = false; + // We don't need to create the instance of the handler here. + if (gIMMHandler) { + gIMMHandler->OnInputLangChange(aWindow, wParam, lParam, aResult); + } + InitKeyboardLayout(aWindow, reinterpret_cast<HKL>(lParam)); + // We can release the instance here, because the instance may be never + // used. E.g., the new keyboard layout may not use IME, or it may use TSF. + Terminate(); + // Don't return as "processed", the messages should be processed on nsWindow + // too. + return false; +} + +// static +bool +IMMHandler::ProcessMessage(nsWindow* aWindow, + UINT msg, + WPARAM& wParam, + LPARAM& lParam, + MSGResult& aResult) +{ + // XXX We store the composing window in mComposingWindow. If IME messages are + // sent to different window, we should commit the old transaction. And also + // if the new window handle is not focused, probably, we should not start + // the composition, however, such case should not be, it's just bad scenario. + + // When a plug-in has focus, we should dispatch the IME events to + // the plug-in at first. + if (aWindow->PluginHasFocus()) { + bool ret = false; + if (ProcessMessageForPlugin(aWindow, msg, wParam, lParam, ret, aResult)) { + return ret; + } + } + + aResult.mResult = 0; + switch (msg) { + case WM_INPUTLANGCHANGE: + return ProcessInputLangChangeMessage(aWindow, wParam, lParam, aResult); + case WM_IME_STARTCOMPOSITION: + EnsureHandlerInstance(); + return gIMMHandler->OnIMEStartComposition(aWindow, aResult); + case WM_IME_COMPOSITION: + EnsureHandlerInstance(); + return gIMMHandler->OnIMEComposition(aWindow, wParam, lParam, aResult); + case WM_IME_ENDCOMPOSITION: + EnsureHandlerInstance(); + return gIMMHandler->OnIMEEndComposition(aWindow, aResult); + case WM_IME_CHAR: + return OnIMEChar(aWindow, wParam, lParam, aResult); + case WM_IME_NOTIFY: + return OnIMENotify(aWindow, wParam, lParam, aResult); + case WM_IME_REQUEST: + EnsureHandlerInstance(); + return gIMMHandler->OnIMERequest(aWindow, wParam, lParam, aResult); + case WM_IME_SELECT: + return OnIMESelect(aWindow, wParam, lParam, aResult); + case WM_IME_SETCONTEXT: + return OnIMESetContext(aWindow, wParam, lParam, aResult); + case WM_KEYDOWN: + return OnKeyDownEvent(aWindow, wParam, lParam, aResult); + case WM_CHAR: + if (!gIMMHandler) { + return false; + } + return gIMMHandler->OnChar(aWindow, wParam, lParam, aResult); + default: + return false; + }; +} + +// static +bool +IMMHandler::ProcessMessageForPlugin(nsWindow* aWindow, + UINT msg, + WPARAM& wParam, + LPARAM& lParam, + bool& aRet, + MSGResult& aResult) +{ + aResult.mResult = 0; + aResult.mConsumed = false; + switch (msg) { + case WM_INPUTLANGCHANGEREQUEST: + case WM_INPUTLANGCHANGE: + aWindow->DispatchPluginEvent(msg, wParam, lParam, false); + aRet = ProcessInputLangChangeMessage(aWindow, wParam, lParam, aResult); + return true; + case WM_IME_CHAR: + EnsureHandlerInstance(); + aRet = gIMMHandler->OnIMECharOnPlugin(aWindow, wParam, lParam, aResult); + return true; + case WM_IME_SETCONTEXT: + aRet = OnIMESetContextOnPlugin(aWindow, wParam, lParam, aResult); + return true; + case WM_CHAR: + if (!gIMMHandler) { + return true; + } + aRet = gIMMHandler->OnCharOnPlugin(aWindow, wParam, lParam, aResult); + return true; + case WM_IME_COMPOSITIONFULL: + case WM_IME_CONTROL: + case WM_IME_KEYDOWN: + case WM_IME_KEYUP: + case WM_IME_SELECT: + aResult.mConsumed = + aWindow->DispatchPluginEvent(msg, wParam, lParam, false); + aRet = true; + return true; + case WM_IME_REQUEST: + // Our plugin implementation is alwasy OOP. So WM_IME_REQUEST doesn't + // allow that parameter is pointer and shouldn't handle into Gecko. + aRet = false; + return true; + } + return false; +} + +/**************************************************************************** + * message handlers + ****************************************************************************/ + +void +IMMHandler::OnInputLangChange(nsWindow* aWindow, + WPARAM wParam, + LPARAM lParam, + MSGResult& aResult) +{ + MOZ_LOG(gIMMLog, LogLevel::Info, + ("OnInputLangChange, hWnd=%08x, wParam=%08x, lParam=%08x", + aWindow->GetWindowHandle(), wParam, lParam)); + + aWindow->NotifyIME(REQUEST_TO_COMMIT_COMPOSITION); + NS_ASSERTION(!mIsComposing, "ResetInputState failed"); + + if (mIsComposing) { + HandleEndComposition(aWindow); + } + + aResult.mConsumed = false; +} + +bool +IMMHandler::OnIMEStartComposition(nsWindow* aWindow, + MSGResult& aResult) +{ + MOZ_LOG(gIMMLog, LogLevel::Info, + ("OnIMEStartComposition, hWnd=%08x, mIsComposing=%s", + aWindow->GetWindowHandle(), GetBoolName(mIsComposing))); + aResult.mConsumed = ShouldDrawCompositionStringOurselves(); + if (mIsComposing) { + NS_WARNING("Composition has been already started"); + return true; + } + + IMEContext context(aWindow); + HandleStartComposition(aWindow, context); + return true; +} + +bool +IMMHandler::OnIMEComposition(nsWindow* aWindow, + WPARAM wParam, + LPARAM lParam, + MSGResult& aResult) +{ + MOZ_LOG(gIMMLog, LogLevel::Info, + ("OnIMEComposition, hWnd=%08x, lParam=%08x, mIsComposing=%s, " + "GCS_RESULTSTR=%s, GCS_COMPSTR=%s, GCS_COMPATTR=%s, GCS_COMPCLAUSE=%s, " + "GCS_CURSORPOS=%s,", + aWindow->GetWindowHandle(), lParam, GetBoolName(mIsComposing), + GetBoolName(lParam & GCS_RESULTSTR), GetBoolName(lParam & GCS_COMPSTR), + GetBoolName(lParam & GCS_COMPATTR), GetBoolName(lParam & GCS_COMPCLAUSE), + GetBoolName(lParam & GCS_CURSORPOS))); + + IMEContext context(aWindow); + aResult.mConsumed = HandleComposition(aWindow, context, lParam); + return true; +} + +bool +IMMHandler::OnIMEEndComposition(nsWindow* aWindow, + MSGResult& aResult) +{ + MOZ_LOG(gIMMLog, LogLevel::Info, + ("OnIMEEndComposition, hWnd=%08x, mIsComposing=%s", + aWindow->GetWindowHandle(), GetBoolName(mIsComposing))); + + aResult.mConsumed = ShouldDrawCompositionStringOurselves(); + if (!mIsComposing) { + return true; + } + + // Korean IME posts WM_IME_ENDCOMPOSITION first when we hit space during + // composition. Then, we should ignore the message and commit the composition + // string at following WM_IME_COMPOSITION. + MSG compositionMsg; + if (WinUtils::PeekMessage(&compositionMsg, aWindow->GetWindowHandle(), + WM_IME_STARTCOMPOSITION, WM_IME_COMPOSITION, + PM_NOREMOVE) && + compositionMsg.message == WM_IME_COMPOSITION && + IS_COMMITTING_LPARAM(compositionMsg.lParam)) { + MOZ_LOG(gIMMLog, LogLevel::Info, + ("OnIMEEndComposition, WM_IME_ENDCOMPOSITION is followed by " + "WM_IME_COMPOSITION, ignoring the message...")); + return true; + } + + // Otherwise, e.g., ChangJie doesn't post WM_IME_COMPOSITION before + // WM_IME_ENDCOMPOSITION when composition string becomes empty. + // Then, we should dispatch a compositionupdate event, a compositionchange + // event and a compositionend event. + // XXX Shouldn't we dispatch the compositionchange event with actual or + // latest composition string? + MOZ_LOG(gIMMLog, LogLevel::Info, + ("OnIMEEndComposition, mCompositionString=\"%s\"%s", + NS_ConvertUTF16toUTF8(mCompositionString).get(), + mCompositionString.IsEmpty() ? "" : ", but canceling it...")); + + HandleEndComposition(aWindow, &EmptyString()); + + return true; +} + +// static +bool +IMMHandler::OnIMEChar(nsWindow* aWindow, + WPARAM wParam, + LPARAM lParam, + MSGResult& aResult) +{ + MOZ_LOG(gIMMLog, LogLevel::Info, + ("OnIMEChar, hWnd=%08x, char=%08x", + aWindow->GetWindowHandle(), wParam)); + + // We don't need to fire any compositionchange events from here. This method + // will be called when the composition string of the current IME is not drawn + // by us and some characters are committed. In that case, the committed + // string was processed in nsWindow::OnIMEComposition already. + + // We need to consume the message so that Windows don't send two WM_CHAR msgs + aResult.mConsumed = true; + return true; +} + +// static +bool +IMMHandler::OnIMECompositionFull(nsWindow* aWindow, + MSGResult& aResult) +{ + MOZ_LOG(gIMMLog, LogLevel::Info, + ("OnIMECompositionFull, hWnd=%08x", + aWindow->GetWindowHandle())); + + // not implement yet + aResult.mConsumed = false; + return true; +} + +// static +bool +IMMHandler::OnIMENotify(nsWindow* aWindow, + WPARAM wParam, + LPARAM lParam, + MSGResult& aResult) +{ + switch (wParam) { + case IMN_CHANGECANDIDATE: + MOZ_LOG(gIMMLog, LogLevel::Info, + ("OnIMENotify, hWnd=%08x, IMN_CHANGECANDIDATE, lParam=%08x", + aWindow->GetWindowHandle(), lParam)); + break; + case IMN_CLOSECANDIDATE: + MOZ_LOG(gIMMLog, LogLevel::Info, + ("OnIMENotify, hWnd=%08x, IMN_CLOSECANDIDATE, lParam=%08x", + aWindow->GetWindowHandle(), lParam)); + break; + case IMN_CLOSESTATUSWINDOW: + MOZ_LOG(gIMMLog, LogLevel::Info, + ("OnIMENotify, hWnd=%08x, IMN_CLOSESTATUSWINDOW", + aWindow->GetWindowHandle())); + break; + case IMN_GUIDELINE: + MOZ_LOG(gIMMLog, LogLevel::Info, + ("OnIMENotify, hWnd=%08x, IMN_GUIDELINE", + aWindow->GetWindowHandle())); + break; + case IMN_OPENCANDIDATE: + MOZ_LOG(gIMMLog, LogLevel::Info, + ("OnIMENotify, hWnd=%08x, IMN_OPENCANDIDATE, lParam=%08x", + aWindow->GetWindowHandle(), lParam)); + break; + case IMN_OPENSTATUSWINDOW: + MOZ_LOG(gIMMLog, LogLevel::Info, + ("OnIMENotify, hWnd=%08x, IMN_OPENSTATUSWINDOW", + aWindow->GetWindowHandle())); + break; + case IMN_SETCANDIDATEPOS: + MOZ_LOG(gIMMLog, LogLevel::Info, + ("OnIMENotify, hWnd=%08x, IMN_SETCANDIDATEPOS, lParam=%08x", + aWindow->GetWindowHandle(), lParam)); + break; + case IMN_SETCOMPOSITIONFONT: + MOZ_LOG(gIMMLog, LogLevel::Info, + ("OnIMENotify, hWnd=%08x, IMN_SETCOMPOSITIONFONT", + aWindow->GetWindowHandle())); + break; + case IMN_SETCOMPOSITIONWINDOW: + MOZ_LOG(gIMMLog, LogLevel::Info, + ("OnIMENotify, hWnd=%08x, IMN_SETCOMPOSITIONWINDOW", + aWindow->GetWindowHandle())); + break; + case IMN_SETCONVERSIONMODE: + MOZ_LOG(gIMMLog, LogLevel::Info, + ("OnIMENotify, hWnd=%08x, IMN_SETCONVERSIONMODE", + aWindow->GetWindowHandle())); + break; + case IMN_SETOPENSTATUS: + MOZ_LOG(gIMMLog, LogLevel::Info, + ("OnIMENotify, hWnd=%08x, IMN_SETOPENSTATUS", + aWindow->GetWindowHandle())); + break; + case IMN_SETSENTENCEMODE: + MOZ_LOG(gIMMLog, LogLevel::Info, + ("OnIMENotify, hWnd=%08x, IMN_SETSENTENCEMODE", + aWindow->GetWindowHandle())); + break; + case IMN_SETSTATUSWINDOWPOS: + MOZ_LOG(gIMMLog, LogLevel::Info, + ("OnIMENotify, hWnd=%08x, IMN_SETSTATUSWINDOWPOS", + aWindow->GetWindowHandle())); + break; + case IMN_PRIVATE: + MOZ_LOG(gIMMLog, LogLevel::Info, + ("OnIMENotify, hWnd=%08x, IMN_PRIVATE", + aWindow->GetWindowHandle())); + break; + } + + // not implement yet + aResult.mConsumed = false; + return true; +} + +bool +IMMHandler::OnIMERequest(nsWindow* aWindow, + WPARAM wParam, + LPARAM lParam, + MSGResult& aResult) +{ + switch (wParam) { + case IMR_RECONVERTSTRING: + MOZ_LOG(gIMMLog, LogLevel::Info, + ("OnIMERequest, hWnd=%08x, IMR_RECONVERTSTRING", + aWindow->GetWindowHandle())); + aResult.mConsumed = HandleReconvert(aWindow, lParam, &aResult.mResult); + return true; + case IMR_QUERYCHARPOSITION: + MOZ_LOG(gIMMLog, LogLevel::Info, + ("OnIMERequest, hWnd=%08x, IMR_QUERYCHARPOSITION", + aWindow->GetWindowHandle())); + aResult.mConsumed = + HandleQueryCharPosition(aWindow, lParam, &aResult.mResult); + return true; + case IMR_DOCUMENTFEED: + MOZ_LOG(gIMMLog, LogLevel::Info, + ("OnIMERequest, hWnd=%08x, IMR_DOCUMENTFEED", + aWindow->GetWindowHandle())); + aResult.mConsumed = HandleDocumentFeed(aWindow, lParam, &aResult.mResult); + return true; + default: + MOZ_LOG(gIMMLog, LogLevel::Info, + ("OnIMERequest, hWnd=%08x, wParam=%08x", + aWindow->GetWindowHandle(), wParam)); + aResult.mConsumed = false; + return true; + } +} + +// static +bool +IMMHandler::OnIMESelect(nsWindow* aWindow, + WPARAM wParam, + LPARAM lParam, + MSGResult& aResult) +{ + MOZ_LOG(gIMMLog, LogLevel::Info, + ("OnIMESelect, hWnd=%08x, wParam=%08x, lParam=%08x", + aWindow->GetWindowHandle(), wParam, lParam)); + + // not implement yet + aResult.mConsumed = false; + return true; +} + +// static +bool +IMMHandler::OnIMESetContext(nsWindow* aWindow, + WPARAM wParam, + LPARAM lParam, + MSGResult& aResult) +{ + MOZ_LOG(gIMMLog, LogLevel::Info, + ("OnIMESetContext, hWnd=%08x, %s, lParam=%08x", + aWindow->GetWindowHandle(), wParam ? "Active" : "Deactive", lParam)); + + aResult.mConsumed = false; + + // NOTE: If the aWindow is top level window of the composing window because + // when a window on deactive window gets focus, WM_IME_SETCONTEXT (wParam is + // TRUE) is sent to the top level window first. After that, + // WM_IME_SETCONTEXT (wParam is FALSE) is sent to the top level window. + // Finally, WM_IME_SETCONTEXT (wParam is TRUE) is sent to the focused window. + // The top level window never becomes composing window, so, we can ignore + // the WM_IME_SETCONTEXT on the top level window. + if (IsTopLevelWindowOfComposition(aWindow)) { + MOZ_LOG(gIMMLog, LogLevel::Info, + ("OnIMESetContext, hWnd=%08x is top level window")); + return true; + } + + // When IME context is activating on another window, + // we should commit the old composition on the old window. + bool cancelComposition = false; + if (wParam && gIMMHandler) { + cancelComposition = + gIMMHandler->CommitCompositionOnPreviousWindow(aWindow); + } + + if (wParam && (lParam & ISC_SHOWUICOMPOSITIONWINDOW) && + ShouldDrawCompositionStringOurselves()) { + MOZ_LOG(gIMMLog, LogLevel::Info, + ("OnIMESetContext, ISC_SHOWUICOMPOSITIONWINDOW is removed")); + lParam &= ~ISC_SHOWUICOMPOSITIONWINDOW; + } + + // We should sent WM_IME_SETCONTEXT to the DefWndProc here because the + // ancestor windows shouldn't receive this message. If they receive the + // message, we cannot know whether which window is the target of the message. + aResult.mResult = ::DefWindowProc(aWindow->GetWindowHandle(), + WM_IME_SETCONTEXT, wParam, lParam); + + // Cancel composition on the new window if we committed our composition on + // another window. + if (cancelComposition) { + CancelComposition(aWindow, true); + } + + aResult.mConsumed = true; + return true; +} + +bool +IMMHandler::OnChar(nsWindow* aWindow, + WPARAM wParam, + LPARAM lParam, + MSGResult& aResult) +{ + // The return value must be same as aResult.mConsumed because only when we + // consume the message, the caller shouldn't do anything anymore but + // otherwise, the caller should handle the message. + aResult.mConsumed = false; + if (IsIMECharRecordsEmpty()) { + return aResult.mConsumed; + } + WPARAM recWParam; + LPARAM recLParam; + DequeueIMECharRecords(recWParam, recLParam); + MOZ_LOG(gIMMLog, LogLevel::Info, + ("OnChar, aWindow=%p, wParam=%08x, lParam=%08x, " + "recorded: wParam=%08x, lParam=%08x", + aWindow->GetWindowHandle(), wParam, lParam, recWParam, recLParam)); + // If an unexpected char message comes, we should reset the records, + // of course, this shouldn't happen. + if (recWParam != wParam || recLParam != lParam) { + ResetIMECharRecords(); + return aResult.mConsumed; + } + // Eat the char message which is caused by WM_IME_CHAR because we should + // have processed the IME messages, so, this message could be come from + // a windowless plug-in. + aResult.mConsumed = true; + return aResult.mConsumed; +} + +/**************************************************************************** + * message handlers for plug-in + ****************************************************************************/ + +void +IMMHandler::OnIMEStartCompositionOnPlugin(nsWindow* aWindow, + WPARAM wParam, + LPARAM lParam) +{ + MOZ_LOG(gIMMLog, LogLevel::Info, + ("OnIMEStartCompositionOnPlugin, hWnd=%08x, mIsComposingOnPlugin=%s", + aWindow->GetWindowHandle(), GetBoolName(mIsComposingOnPlugin))); + mIsComposingOnPlugin = true; + mDispatcher = GetTextEventDispatcherFor(aWindow); + mComposingWindow = aWindow; + IMEContext context(aWindow); + SetIMERelatedWindowsPosOnPlugin(aWindow, context); + // On widnowless plugin, we should assume that the focused editor is always + // in horizontal writing mode. + AdjustCompositionFont(aWindow, context, WritingMode()); +} + +void +IMMHandler::OnIMECompositionOnPlugin(nsWindow* aWindow, + WPARAM wParam, + LPARAM lParam) +{ + MOZ_LOG(gIMMLog, LogLevel::Info, + ("OnIMECompositionOnPlugin, hWnd=%08x, lParam=%08x, " + "mIsComposingOnPlugin=%s, GCS_RESULTSTR=%s, GCS_COMPSTR=%s, " + "GCS_COMPATTR=%s, GCS_COMPCLAUSE=%s, GCS_CURSORPOS=%s", + aWindow->GetWindowHandle(), lParam, GetBoolName(mIsComposingOnPlugin), + GetBoolName(lParam & GCS_RESULTSTR), GetBoolName(lParam & GCS_COMPSTR), + GetBoolName(lParam & GCS_COMPATTR), GetBoolName(lParam & GCS_COMPCLAUSE), + GetBoolName(lParam & GCS_CURSORPOS))); + // We should end composition if there is a committed string. + if (IS_COMMITTING_LPARAM(lParam)) { + mIsComposingOnPlugin = false; + mComposingWindow = nullptr; + mDispatcher = nullptr; + return; + } + // Continue composition if there is still a string being composed. + if (IS_COMPOSING_LPARAM(lParam)) { + mIsComposingOnPlugin = true; + mDispatcher = GetTextEventDispatcherFor(aWindow); + mComposingWindow = aWindow; + IMEContext context(aWindow); + SetIMERelatedWindowsPosOnPlugin(aWindow, context); + } +} + +void +IMMHandler::OnIMEEndCompositionOnPlugin(nsWindow* aWindow, + WPARAM wParam, + LPARAM lParam) +{ + MOZ_LOG(gIMMLog, LogLevel::Info, + ("OnIMEEndCompositionOnPlugin, hWnd=%08x, mIsComposingOnPlugin=%s", + aWindow->GetWindowHandle(), GetBoolName(mIsComposingOnPlugin))); + + mIsComposingOnPlugin = false; + mComposingWindow = nullptr; + mDispatcher = nullptr; + + if (mNativeCaretIsCreated) { + ::DestroyCaret(); + mNativeCaretIsCreated = false; + } +} + +bool +IMMHandler::OnIMECharOnPlugin(nsWindow* aWindow, + WPARAM wParam, + LPARAM lParam, + MSGResult& aResult) +{ + MOZ_LOG(gIMMLog, LogLevel::Info, + ("OnIMECharOnPlugin, hWnd=%08x, char=%08x, scancode=%08x", + aWindow->GetWindowHandle(), wParam, lParam)); + + aResult.mConsumed = + aWindow->DispatchPluginEvent(WM_IME_CHAR, wParam, lParam, true); + + if (!aResult.mConsumed) { + // Record the WM_CHAR messages which are going to be coming. + EnsureHandlerInstance(); + EnqueueIMECharRecords(wParam, lParam); + } + return true; +} + +// static +bool +IMMHandler::OnIMESetContextOnPlugin(nsWindow* aWindow, + WPARAM wParam, + LPARAM lParam, + MSGResult& aResult) +{ + MOZ_LOG(gIMMLog, LogLevel::Info, + ("OnIMESetContextOnPlugin, hWnd=%08x, %s, lParam=%08x", + aWindow->GetWindowHandle(), wParam ? "Active" : "Deactive", lParam)); + + // If the IME context becomes active on a plug-in, we should commit + // our composition. And also we should cancel the composition on new + // window. Note that if IsTopLevelWindowOfComposition(aWindow) returns + // true, we should ignore the message here, see the comment in + // OnIMESetContext() for the detail. + if (wParam && gIMMHandler && !IsTopLevelWindowOfComposition(aWindow)) { + if (gIMMHandler->CommitCompositionOnPreviousWindow(aWindow)) { + CancelComposition(aWindow); + } + } + + // Dispatch message to the plug-in. + // XXX When a windowless plug-in gets focus, we should send + // WM_IME_SETCONTEXT + aWindow->DispatchPluginEvent(WM_IME_SETCONTEXT, wParam, lParam, false); + + // We should send WM_IME_SETCONTEXT to the DefWndProc here. It shouldn't + // be received on ancestor windows, see OnIMESetContext() for the detail. + aResult.mResult = ::DefWindowProc(aWindow->GetWindowHandle(), + WM_IME_SETCONTEXT, wParam, lParam); + + // Don't synchronously dispatch the pending events when we receive + // WM_IME_SETCONTEXT because we get it during plugin destruction. + // (bug 491848) + aResult.mConsumed = true; + return true; +} + +bool +IMMHandler::OnCharOnPlugin(nsWindow* aWindow, + WPARAM wParam, + LPARAM lParam, + MSGResult& aResult) +{ + NS_WARNING("OnCharOnPlugin"); + if (mIsComposing) { + aWindow->NotifyIME(REQUEST_TO_COMMIT_COMPOSITION); + return true; + } + + // We should never consume char message on windowless plugin. + aResult.mConsumed = false; + if (IsIMECharRecordsEmpty()) { + return false; + } + + WPARAM recWParam; + LPARAM recLParam; + DequeueIMECharRecords(recWParam, recLParam); + MOZ_LOG(gIMMLog, LogLevel::Info, + ("OnCharOnPlugin, aWindow=%p, wParam=%08x, lParam=%08x, " + "recorded: wParam=%08x, lParam=%08x", + aWindow->GetWindowHandle(), wParam, lParam, recWParam, recLParam)); + // If an unexpected char message comes, we should reset the records, + // of course, this shouldn't happen. + if (recWParam != wParam || recLParam != lParam) { + ResetIMECharRecords(); + } + // WM_CHAR on plug-in is always handled by nsWindow. + return false; +} + +/**************************************************************************** + * others + ****************************************************************************/ + +TextEventDispatcher* +IMMHandler::GetTextEventDispatcherFor(nsWindow* aWindow) +{ + return aWindow == mComposingWindow && mDispatcher ? + mDispatcher.get() : aWindow->GetTextEventDispatcher(); +} + +void +IMMHandler::HandleStartComposition(nsWindow* aWindow, + const IMEContext& aContext) +{ + NS_PRECONDITION(!mIsComposing, + "HandleStartComposition is called but mIsComposing is TRUE"); + + Selection& selection = GetSelection(); + if (!selection.EnsureValidSelection(aWindow)) { + MOZ_LOG(gIMMLog, LogLevel::Error, + ("HandleStartComposition, FAILED, due to " + "Selection::EnsureValidSelection() failure")); + return; + } + + AdjustCompositionFont(aWindow, aContext, selection.mWritingMode); + + mCompositionStart = selection.mOffset; + mCursorPosition = NO_IME_CARET; + + RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcherFor(aWindow); + nsresult rv = dispatcher->BeginNativeInputTransaction(); + if (NS_WARN_IF(NS_FAILED(rv))) { + MOZ_LOG(gIMMLog, LogLevel::Error, + ("HandleStartComposition, FAILED due to " + "TextEventDispatcher::BeginNativeInputTransaction() failure")); + return; + } + WidgetEventTime eventTime = aWindow->CurrentMessageWidgetEventTime(); + nsEventStatus status; + rv = dispatcher->StartComposition(status, &eventTime); + if (NS_WARN_IF(NS_FAILED(rv))) { + MOZ_LOG(gIMMLog, LogLevel::Error, + ("HandleStartComposition, FAILED, due to " + "TextEventDispatcher::StartComposition() failure")); + return; + } + + mIsComposing = true; + mComposingWindow = aWindow; + mDispatcher = dispatcher; + + MOZ_LOG(gIMMLog, LogLevel::Info, + ("HandleStartComposition, START composition, mCompositionStart=%ld", + mCompositionStart)); +} + +bool +IMMHandler::HandleComposition(nsWindow* aWindow, + const IMEContext& aContext, + LPARAM lParam) +{ + // for bug #60050 + // MS-IME 95/97/98/2000 may send WM_IME_COMPOSITION with non-conversion + // mode before it send WM_IME_STARTCOMPOSITION. + // However, ATOK sends a WM_IME_COMPOSITION before WM_IME_STARTCOMPOSITION, + // and if we access ATOK via some APIs, ATOK will sometimes fail to + // initialize its state. If WM_IME_STARTCOMPOSITION is already in the + // message queue, we should ignore the strange WM_IME_COMPOSITION message and + // skip to the next. So, we should look for next composition message + // (WM_IME_STARTCOMPOSITION or WM_IME_ENDCOMPOSITION or WM_IME_COMPOSITION), + // and if it's WM_IME_STARTCOMPOSITION, and one more next composition message + // is WM_IME_COMPOSITION, current IME is ATOK, probably. Otherwise, we + // should start composition forcibly. + if (!mIsComposing) { + MSG msg1, msg2; + HWND wnd = aWindow->GetWindowHandle(); + if (WinUtils::PeekMessage(&msg1, wnd, WM_IME_STARTCOMPOSITION, + WM_IME_COMPOSITION, PM_NOREMOVE) && + msg1.message == WM_IME_STARTCOMPOSITION && + WinUtils::PeekMessage(&msg2, wnd, WM_IME_ENDCOMPOSITION, + WM_IME_COMPOSITION, PM_NOREMOVE) && + msg2.message == WM_IME_COMPOSITION) { + MOZ_LOG(gIMMLog, LogLevel::Info, + ("HandleComposition, Ignores due to find a " + "WM_IME_STARTCOMPOSITION")); + return ShouldDrawCompositionStringOurselves(); + } + } + + bool startCompositionMessageHasBeenSent = mIsComposing; + + // + // This catches a fixed result + // + if (IS_COMMITTING_LPARAM(lParam)) { + if (!mIsComposing) { + HandleStartComposition(aWindow, aContext); + } + + GetCompositionString(aContext, GCS_RESULTSTR, mCompositionString); + + MOZ_LOG(gIMMLog, LogLevel::Info, + ("HandleComposition, GCS_RESULTSTR")); + + HandleEndComposition(aWindow, &mCompositionString); + + if (!IS_COMPOSING_LPARAM(lParam)) { + return ShouldDrawCompositionStringOurselves(); + } + } + + + // + // This provides us with a composition string + // + if (!mIsComposing) { + HandleStartComposition(aWindow, aContext); + } + + //-------------------------------------------------------- + // 1. Get GCS_COMPSTR + //-------------------------------------------------------- + MOZ_LOG(gIMMLog, LogLevel::Info, + ("HandleComposition, GCS_COMPSTR")); + + nsAutoString previousCompositionString(mCompositionString); + GetCompositionString(aContext, GCS_COMPSTR, mCompositionString); + + if (!IS_COMPOSING_LPARAM(lParam)) { + MOZ_LOG(gIMMLog, LogLevel::Info, + ("HandleComposition, lParam doesn't indicate composing, " + "mCompositionString=\"%s\", previousCompositionString=\"%s\"", + NS_ConvertUTF16toUTF8(mCompositionString).get(), + NS_ConvertUTF16toUTF8(previousCompositionString).get())); + + // If composition string isn't changed, we can trust the lParam. + // So, we need to do nothing. + if (previousCompositionString == mCompositionString) { + return ShouldDrawCompositionStringOurselves(); + } + + // IME may send WM_IME_COMPOSITION without composing lParam values + // when composition string becomes empty (e.g., using Backspace key). + // If composition string is empty, we should dispatch a compositionchange + // event with empty string and clear the clause information. + if (mCompositionString.IsEmpty()) { + mClauseArray.Clear(); + mAttributeArray.Clear(); + mCursorPosition = 0; + DispatchCompositionChangeEvent(aWindow, aContext); + return ShouldDrawCompositionStringOurselves(); + } + + // Otherwise, we cannot trust the lParam value. We might need to + // dispatch compositionchange event with the latest composition string + // information. + } + + // See https://bugzilla.mozilla.org/show_bug.cgi?id=296339 + if (mCompositionString.IsEmpty() && !startCompositionMessageHasBeenSent) { + // In this case, maybe, the sender is MSPinYin. That sends *only* + // WM_IME_COMPOSITION with GCS_COMP* and GCS_RESULT* when + // user inputted the Chinese full stop. So, that doesn't send + // WM_IME_STARTCOMPOSITION and WM_IME_ENDCOMPOSITION. + // If WM_IME_STARTCOMPOSITION was not sent and the composition + // string is null (it indicates the composition transaction ended), + // WM_IME_ENDCOMPOSITION may not be sent. If so, we cannot run + // HandleEndComposition() in other place. + MOZ_LOG(gIMMLog, LogLevel::Info, + ("HandleComposition, Aborting GCS_COMPSTR")); + HandleEndComposition(aWindow); + return IS_COMMITTING_LPARAM(lParam); + } + + //-------------------------------------------------------- + // 2. Get GCS_COMPCLAUSE + //-------------------------------------------------------- + long clauseArrayLength = + ::ImmGetCompositionStringW(aContext.get(), GCS_COMPCLAUSE, nullptr, 0); + clauseArrayLength /= sizeof(uint32_t); + + if (clauseArrayLength > 0) { + nsresult rv = EnsureClauseArray(clauseArrayLength); + NS_ENSURE_SUCCESS(rv, false); + + // Intelligent ABC IME (Simplified Chinese IME, the code page is 936) + // will crash in ImmGetCompositionStringW for GCS_COMPCLAUSE (bug 424663). + // See comment 35 of the bug for the detail. Therefore, we should use A + // API for it, however, we should not kill Unicode support on all IMEs. + bool useA_API = !(sIMEProperty & IME_PROP_UNICODE); + + MOZ_LOG(gIMMLog, LogLevel::Info, + ("HandleComposition, GCS_COMPCLAUSE, useA_API=%s", + useA_API ? "TRUE" : "FALSE")); + + long clauseArrayLength2 = + useA_API ? + ::ImmGetCompositionStringA(aContext.get(), GCS_COMPCLAUSE, + mClauseArray.Elements(), + mClauseArray.Capacity() * sizeof(uint32_t)) : + ::ImmGetCompositionStringW(aContext.get(), GCS_COMPCLAUSE, + mClauseArray.Elements(), + mClauseArray.Capacity() * sizeof(uint32_t)); + clauseArrayLength2 /= sizeof(uint32_t); + + if (clauseArrayLength != clauseArrayLength2) { + MOZ_LOG(gIMMLog, LogLevel::Info, + ("HandleComposition, GCS_COMPCLAUSE, clauseArrayLength=%ld but " + "clauseArrayLength2=%ld", + clauseArrayLength, clauseArrayLength2)); + if (clauseArrayLength > clauseArrayLength2) + clauseArrayLength = clauseArrayLength2; + } + + if (useA_API && clauseArrayLength > 0) { + // Convert each values of sIMECompClauseArray. The values mean offset of + // the clauses in ANSI string. But we need the values in Unicode string. + nsAutoCString compANSIStr; + if (ConvertToANSIString(mCompositionString, GetKeyboardCodePage(), + compANSIStr)) { + uint32_t maxlen = compANSIStr.Length(); + mClauseArray.SetLength(clauseArrayLength); + mClauseArray[0] = 0; // first value must be 0 + for (int32_t i = 1; i < clauseArrayLength; i++) { + uint32_t len = std::min(mClauseArray[i], maxlen); + mClauseArray[i] = ::MultiByteToWideChar(GetKeyboardCodePage(), + MB_PRECOMPOSED, + (LPCSTR)compANSIStr.get(), + len, nullptr, 0); + } + } + } + } + // compClauseArrayLength may be negative. I.e., ImmGetCompositionStringW + // may return an error code. + mClauseArray.SetLength(std::max<long>(0, clauseArrayLength)); + + MOZ_LOG(gIMMLog, LogLevel::Info, + ("HandleComposition, GCS_COMPCLAUSE, mClauseLength=%ld", + mClauseArray.Length())); + + //-------------------------------------------------------- + // 3. Get GCS_COMPATTR + //-------------------------------------------------------- + // This provides us with the attribute string necessary + // for doing hiliting + long attrArrayLength = + ::ImmGetCompositionStringW(aContext.get(), GCS_COMPATTR, nullptr, 0); + attrArrayLength /= sizeof(uint8_t); + + if (attrArrayLength > 0) { + nsresult rv = EnsureAttributeArray(attrArrayLength); + NS_ENSURE_SUCCESS(rv, false); + attrArrayLength = + ::ImmGetCompositionStringW(aContext.get(), GCS_COMPATTR, + mAttributeArray.Elements(), + mAttributeArray.Capacity() * sizeof(uint8_t)); + } + + // attrStrLen may be negative. I.e., ImmGetCompositionStringW may return an + // error code. + mAttributeArray.SetLength(std::max<long>(0, attrArrayLength)); + + MOZ_LOG(gIMMLog, LogLevel::Info, + ("HandleComposition, GCS_COMPATTR, mAttributeLength=%ld", + mAttributeArray.Length())); + + //-------------------------------------------------------- + // 4. Get GCS_CURSOPOS + //-------------------------------------------------------- + // Some IMEs (e.g., the standard IME for Korean) don't have caret position. + if (lParam & GCS_CURSORPOS) { + mCursorPosition = + ::ImmGetCompositionStringW(aContext.get(), GCS_CURSORPOS, nullptr, 0); + if (mCursorPosition < 0) { + mCursorPosition = NO_IME_CARET; // The result is error + } + } else { + mCursorPosition = NO_IME_CARET; + } + + NS_ASSERTION(mCursorPosition <= (long)mCompositionString.Length(), + "illegal pos"); + + MOZ_LOG(gIMMLog, LogLevel::Info, + ("HandleComposition, GCS_CURSORPOS, mCursorPosition=%d", + mCursorPosition)); + + //-------------------------------------------------------- + // 5. Send the compositionchange event + //-------------------------------------------------------- + DispatchCompositionChangeEvent(aWindow, aContext); + + return ShouldDrawCompositionStringOurselves(); +} + +void +IMMHandler::HandleEndComposition(nsWindow* aWindow, + const nsAString* aCommitString) +{ + MOZ_ASSERT(mIsComposing, + "HandleEndComposition is called but mIsComposing is FALSE"); + + MOZ_LOG(gIMMLog, LogLevel::Info, + ("HandleEndComposition(aWindow=0x%p, aCommitString=0x%p (\"%s\"))", + aWindow, aCommitString, + aCommitString ? NS_ConvertUTF16toUTF8(*aCommitString).get() : "")); + + if (mNativeCaretIsCreated) { + ::DestroyCaret(); + mNativeCaretIsCreated = false; + } + + RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcherFor(aWindow); + nsresult rv = dispatcher->BeginNativeInputTransaction(); + if (NS_WARN_IF(NS_FAILED(rv))) { + MOZ_LOG(gIMMLog, LogLevel::Error, + ("HandleEndComposition, FAILED due to " + "TextEventDispatcher::BeginNativeInputTransaction() failure")); + return; + } + WidgetEventTime eventTime = aWindow->CurrentMessageWidgetEventTime(); + nsEventStatus status; + rv = dispatcher->CommitComposition(status, aCommitString, &eventTime); + if (NS_WARN_IF(NS_FAILED(rv))) { + MOZ_LOG(gIMMLog, LogLevel::Error, + ("HandleStartComposition, FAILED, due to " + "TextEventDispatcher::CommitComposition() failure")); + return; + } + mIsComposing = false; + // XXX aWindow and mComposingWindow are always same?? + mComposingWindow = nullptr; + mDispatcher = nullptr; +} + +bool +IMMHandler::HandleReconvert(nsWindow* aWindow, + LPARAM lParam, + LRESULT* oResult) +{ + *oResult = 0; + RECONVERTSTRING* pReconv = reinterpret_cast<RECONVERTSTRING*>(lParam); + + Selection& selection = GetSelection(); + if (!selection.EnsureValidSelection(aWindow)) { + MOZ_LOG(gIMMLog, LogLevel::Error, + ("HandleReconvert, FAILED, due to " + "Selection::EnsureValidSelection() failure")); + return false; + } + + uint32_t len = selection.Length(); + uint32_t needSize = sizeof(RECONVERTSTRING) + len * sizeof(WCHAR); + + if (!pReconv) { + // Return need size to reconvert. + if (len == 0) { + MOZ_LOG(gIMMLog, LogLevel::Error, + ("HandleReconvert, There are not selected text")); + return false; + } + *oResult = needSize; + MOZ_LOG(gIMMLog, LogLevel::Info, + ("HandleReconvert, succeeded, result=%ld", + *oResult)); + return true; + } + + if (pReconv->dwSize < needSize) { + MOZ_LOG(gIMMLog, LogLevel::Info, + ("HandleReconvert, FAILED, pReconv->dwSize=%ld, needSize=%ld", + pReconv->dwSize, needSize)); + return false; + } + + *oResult = needSize; + + // Fill reconvert struct + pReconv->dwVersion = 0; + pReconv->dwStrLen = len; + pReconv->dwStrOffset = sizeof(RECONVERTSTRING); + pReconv->dwCompStrLen = len; + pReconv->dwCompStrOffset = 0; + pReconv->dwTargetStrLen = len; + pReconv->dwTargetStrOffset = 0; + + ::CopyMemory(reinterpret_cast<LPVOID>(lParam + sizeof(RECONVERTSTRING)), + selection.mString.get(), len * sizeof(WCHAR)); + + MOZ_LOG(gIMMLog, LogLevel::Info, + ("HandleReconvert, SUCCEEDED, pReconv=%s, result=%ld", + GetReconvertStringLog(pReconv).get(), *oResult)); + + return true; +} + +bool +IMMHandler::HandleQueryCharPosition(nsWindow* aWindow, + LPARAM lParam, + LRESULT* oResult) +{ + uint32_t len = mIsComposing ? mCompositionString.Length() : 0; + *oResult = false; + IMECHARPOSITION* pCharPosition = reinterpret_cast<IMECHARPOSITION*>(lParam); + if (!pCharPosition) { + MOZ_LOG(gIMMLog, LogLevel::Error, + ("HandleQueryCharPosition, FAILED, due to pCharPosition is null")); + return false; + } + if (pCharPosition->dwSize < sizeof(IMECHARPOSITION)) { + MOZ_LOG(gIMMLog, LogLevel::Error, + ("HandleReconvert, FAILED, pCharPosition->dwSize=%ld, " + "sizeof(IMECHARPOSITION)=%ld", + pCharPosition->dwSize, sizeof(IMECHARPOSITION))); + return false; + } + if (::GetFocus() != aWindow->GetWindowHandle()) { + MOZ_LOG(gIMMLog, LogLevel::Error, + ("HandleReconvert, FAILED, ::GetFocus()=%08x, OurWindowHandle=%08x", + ::GetFocus(), aWindow->GetWindowHandle())); + return false; + } + if (pCharPosition->dwCharPos > len) { + MOZ_LOG(gIMMLog, LogLevel::Error, + ("HandleQueryCharPosition, FAILED, pCharPosition->dwCharPos=%ld, " + "len=%ld", + pCharPosition->dwCharPos, len)); + return false; + } + + LayoutDeviceIntRect r; + bool ret = + GetCharacterRectOfSelectedTextAt(aWindow, pCharPosition->dwCharPos, r); + NS_ENSURE_TRUE(ret, false); + + LayoutDeviceIntRect screenRect; + // We always need top level window that is owner window of the popup window + // even if the content of the popup window has focus. + ResolveIMECaretPos(aWindow->GetTopLevelWindow(false), + r, nullptr, screenRect); + + // XXX This might need to check writing mode. However, MSDN doesn't explain + // how to set the values in vertical writing mode. Additionally, IME + // doesn't work well with top-left of the character (this is explicitly + // documented) and its horizontal width. So, it might be better to set + // top-right corner of the character and horizontal width, but we're not + // sure if it doesn't cause any problems with a lot of IMEs... + pCharPosition->pt.x = screenRect.x; + pCharPosition->pt.y = screenRect.y; + + pCharPosition->cLineHeight = r.height; + + WidgetQueryContentEvent editorRect(true, eQueryEditorRect, aWindow); + aWindow->InitEvent(editorRect); + DispatchEvent(aWindow, editorRect); + if (NS_WARN_IF(!editorRect.mSucceeded)) { + MOZ_LOG(gIMMLog, LogLevel::Error, + ("HandleQueryCharPosition, eQueryEditorRect failed")); + ::GetWindowRect(aWindow->GetWindowHandle(), &pCharPosition->rcDocument); + } else { + LayoutDeviceIntRect editorRectInWindow = editorRect.mReply.mRect; + nsWindow* window = editorRect.mReply.mFocusedWidget ? + static_cast<nsWindow*>(editorRect.mReply.mFocusedWidget) : aWindow; + LayoutDeviceIntRect editorRectInScreen; + ResolveIMECaretPos(window, editorRectInWindow, nullptr, editorRectInScreen); + ::SetRect(&pCharPosition->rcDocument, + editorRectInScreen.x, editorRectInScreen.y, + editorRectInScreen.XMost(), editorRectInScreen.YMost()); + } + + *oResult = TRUE; + + MOZ_LOG(gIMMLog, LogLevel::Info, + ("HandleQueryCharPosition, SUCCEEDED, pCharPosition={ pt={ x=%d, " + "y=%d }, cLineHeight=%d, rcDocument={ left=%d, top=%d, right=%d, " + "bottom=%d } }", + pCharPosition->pt.x, pCharPosition->pt.y, pCharPosition->cLineHeight, + pCharPosition->rcDocument.left, pCharPosition->rcDocument.top, + pCharPosition->rcDocument.right, pCharPosition->rcDocument.bottom)); + return true; +} + +bool +IMMHandler::HandleDocumentFeed(nsWindow* aWindow, + LPARAM lParam, + LRESULT* oResult) +{ + *oResult = 0; + RECONVERTSTRING* pReconv = reinterpret_cast<RECONVERTSTRING*>(lParam); + + LayoutDeviceIntPoint point(0, 0); + + bool hasCompositionString = + mIsComposing && ShouldDrawCompositionStringOurselves(); + + int32_t targetOffset, targetLength; + if (!hasCompositionString) { + Selection& selection = GetSelection(); + if (!selection.EnsureValidSelection(aWindow)) { + MOZ_LOG(gIMMLog, LogLevel::Error, + ("HandleDocumentFeed, FAILED, due to " + "Selection::EnsureValidSelection() failure")); + return false; + } + targetOffset = int32_t(selection.mOffset); + targetLength = int32_t(selection.Length()); + } else { + targetOffset = int32_t(mCompositionStart); + targetLength = int32_t(mCompositionString.Length()); + } + + // XXX nsString::Find and nsString::RFind take int32_t for offset, so, + // we cannot support this message when the current offset is larger than + // INT32_MAX. + if (targetOffset < 0 || targetLength < 0 || + targetOffset + targetLength < 0) { + MOZ_LOG(gIMMLog, LogLevel::Error, + ("HandleDocumentFeed, FAILED, due to the selection is out of " + "range")); + return false; + } + + // Get all contents of the focused editor. + WidgetQueryContentEvent textContent(true, eQueryTextContent, aWindow); + textContent.InitForQueryTextContent(0, UINT32_MAX); + aWindow->InitEvent(textContent, &point); + DispatchEvent(aWindow, textContent); + if (!textContent.mSucceeded) { + MOZ_LOG(gIMMLog, LogLevel::Error, + ("HandleDocumentFeed, FAILED, due to eQueryTextContent failure")); + return false; + } + + nsAutoString str(textContent.mReply.mString); + if (targetOffset > int32_t(str.Length())) { + MOZ_LOG(gIMMLog, LogLevel::Error, + ("HandleDocumentFeed, FAILED, due to the caret offset is invalid")); + return false; + } + + // Get the focused paragraph, we decide that it starts from the previous CRLF + // (or start of the editor) to the next one (or the end of the editor). + int32_t paragraphStart = str.RFind("\n", false, targetOffset, -1) + 1; + int32_t paragraphEnd = + str.Find("\r", false, targetOffset + targetLength, -1); + if (paragraphEnd < 0) { + paragraphEnd = str.Length(); + } + nsDependentSubstring paragraph(str, paragraphStart, + paragraphEnd - paragraphStart); + + uint32_t len = paragraph.Length(); + uint32_t needSize = sizeof(RECONVERTSTRING) + len * sizeof(WCHAR); + + if (!pReconv) { + *oResult = needSize; + MOZ_LOG(gIMMLog, LogLevel::Info, + ("HandleDocumentFeed, succeeded, result=%ld", + *oResult)); + return true; + } + + if (pReconv->dwSize < needSize) { + MOZ_LOG(gIMMLog, LogLevel::Error, + ("HandleDocumentFeed, FAILED, pReconv->dwSize=%ld, needSize=%ld", + pReconv->dwSize, needSize)); + return false; + } + + // Fill reconvert struct + pReconv->dwVersion = 0; + pReconv->dwStrLen = len; + pReconv->dwStrOffset = sizeof(RECONVERTSTRING); + if (hasCompositionString) { + pReconv->dwCompStrLen = targetLength; + pReconv->dwCompStrOffset = + (targetOffset - paragraphStart) * sizeof(WCHAR); + // Set composition target clause information + uint32_t offset, length; + if (!GetTargetClauseRange(&offset, &length)) { + MOZ_LOG(gIMMLog, LogLevel::Error, + ("HandleDocumentFeed, FAILED, due to GetTargetClauseRange() " + "failure")); + return false; + } + pReconv->dwTargetStrLen = length; + pReconv->dwTargetStrOffset = (offset - paragraphStart) * sizeof(WCHAR); + } else { + pReconv->dwTargetStrLen = targetLength; + pReconv->dwTargetStrOffset = + (targetOffset - paragraphStart) * sizeof(WCHAR); + // There is no composition string, so, the length is zero but we should + // set the cursor offset to the composition str offset. + pReconv->dwCompStrLen = 0; + pReconv->dwCompStrOffset = pReconv->dwTargetStrOffset; + } + + *oResult = needSize; + ::CopyMemory(reinterpret_cast<LPVOID>(lParam + sizeof(RECONVERTSTRING)), + paragraph.BeginReading(), len * sizeof(WCHAR)); + + MOZ_LOG(gIMMLog, LogLevel::Info, + ("HandleDocumentFeed, SUCCEEDED, pReconv=%s, result=%ld", + GetReconvertStringLog(pReconv).get(), *oResult)); + + return true; +} + +bool +IMMHandler::CommitCompositionOnPreviousWindow(nsWindow* aWindow) +{ + if (!mComposingWindow || mComposingWindow == aWindow) { + return false; + } + + MOZ_LOG(gIMMLog, LogLevel::Info, + ("CommitCompositionOnPreviousWindow, mIsComposing=%s", + GetBoolName(mIsComposing))); + + // If we have composition, we should dispatch composition events internally. + if (mIsComposing) { + IMEContext context(mComposingWindow); + NS_ASSERTION(context.IsValid(), "IME context must be valid"); + + HandleEndComposition(mComposingWindow); + return true; + } + + return false; +} + +static TextRangeType +PlatformToNSAttr(uint8_t aAttr) +{ + switch (aAttr) + { + case ATTR_INPUT_ERROR: + // case ATTR_FIXEDCONVERTED: + case ATTR_INPUT: + return TextRangeType::eRawClause; + case ATTR_CONVERTED: + return TextRangeType::eConvertedClause; + case ATTR_TARGET_NOTCONVERTED: + return TextRangeType::eSelectedRawClause; + case ATTR_TARGET_CONVERTED: + return TextRangeType::eSelectedClause; + default: + NS_ASSERTION(false, "unknown attribute"); + return TextRangeType::eCaret; + } +} + +// static +void +IMMHandler::DispatchEvent(nsWindow* aWindow, WidgetGUIEvent& aEvent) +{ + MOZ_LOG(gIMMLog, LogLevel::Info, + ("DispatchEvent(aWindow=0x%p, aEvent={ mMessage=%s }, " + "aWindow->Destroyed()=%s", + aWindow, ToChar(aEvent.mMessage), GetBoolName(aWindow->Destroyed()))); + + if (aWindow->Destroyed()) { + return; + } + + aWindow->DispatchWindowEvent(&aEvent); +} + +void +IMMHandler::DispatchCompositionChangeEvent(nsWindow* aWindow, + const IMEContext& aContext) +{ + NS_ASSERTION(mIsComposing, "conflict state"); + MOZ_LOG(gIMMLog, LogLevel::Info, + ("DispatchCompositionChangeEvent")); + + // If we don't need to draw composition string ourselves, we don't need to + // fire compositionchange event during composing. + if (!ShouldDrawCompositionStringOurselves()) { + // But we need to adjust composition window pos and native caret pos, here. + SetIMERelatedWindowsPos(aWindow, aContext); + return; + } + + RefPtr<nsWindow> kungFuDeathGrip(aWindow); + RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcherFor(aWindow); + nsresult rv = dispatcher->BeginNativeInputTransaction(); + if (NS_WARN_IF(NS_FAILED(rv))) { + MOZ_LOG(gIMMLog, LogLevel::Error, + ("DispatchCompositionChangeEvent, FAILED due to " + "TextEventDispatcher::BeginNativeInputTransaction() failure")); + return; + } + + // NOTE: Calling SetIMERelatedWindowsPos() from this method will be failure + // in e10s mode. compositionchange event will notify this of + // NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED, then + // SetIMERelatedWindowsPos() will be called. + + // XXX Sogou (Simplified Chinese IME) returns contradictory values: + // The cursor position is actual cursor position. However, other values + // (composition string and attributes) are empty. + + if (mCompositionString.IsEmpty()) { + // Don't append clause information if composition string is empty. + } else if (mClauseArray.IsEmpty()) { + // Some IMEs don't return clause array information, then, we assume that + // all characters in the composition string are in one clause. + MOZ_LOG(gIMMLog, LogLevel::Info, + ("DispatchCompositionChangeEvent, mClauseArray.Length()=0")); + rv =dispatcher->SetPendingComposition(mCompositionString, nullptr); + if (NS_WARN_IF(NS_FAILED(rv))) { + MOZ_LOG(gIMMLog, LogLevel::Error, + ("DispatchCompositionChangeEvent, FAILED due to" + "TextEventDispatcher::SetPendingComposition() failure")); + return; + } + } else { + // iterate over the attributes + rv = dispatcher->SetPendingCompositionString(mCompositionString); + if (NS_WARN_IF(NS_FAILED(rv))) { + MOZ_LOG(gIMMLog, LogLevel::Error, + ("DispatchCompositionChangeEvent, FAILED due to" + "TextEventDispatcher::SetPendingCompositionString() failure")); + return; + } + uint32_t lastOffset = 0; + for (uint32_t i = 0; i < mClauseArray.Length() - 1; i++) { + uint32_t current = mClauseArray[i + 1]; + if (current > mCompositionString.Length()) { + MOZ_LOG(gIMMLog, LogLevel::Info, + ("DispatchCompositionChangeEvent, mClauseArray[%ld]=%lu. " + "This is larger than mCompositionString.Length()=%lu", + i + 1, current, mCompositionString.Length())); + current = int32_t(mCompositionString.Length()); + } + + uint32_t length = current - lastOffset; + if (NS_WARN_IF(lastOffset >= mAttributeArray.Length())) { + MOZ_LOG(gIMMLog, LogLevel::Error, + ("DispatchCompositionChangeEvent, FAILED due to invalid data of " + "mClauseArray or mAttributeArray")); + return; + } + TextRangeType textRangeType = + PlatformToNSAttr(mAttributeArray[lastOffset]); + rv = dispatcher->AppendClauseToPendingComposition(length, textRangeType); + if (NS_WARN_IF(NS_FAILED(rv))) { + MOZ_LOG(gIMMLog, LogLevel::Error, + ("DispatchCompositionChangeEvent, FAILED due to" + "TextEventDispatcher::AppendClauseToPendingComposition() failure")); + return; + } + + lastOffset = current; + + MOZ_LOG(gIMMLog, LogLevel::Info, + ("DispatchCompositionChangeEvent, index=%ld, rangeType=%s, " + "range length=%lu", + i, ToChar(textRangeType), length)); + } + } + + if (mCursorPosition == NO_IME_CARET) { + MOZ_LOG(gIMMLog, LogLevel::Info, + ("DispatchCompositionChangeEvent, no caret")); + } else { + uint32_t cursor = static_cast<uint32_t>(mCursorPosition); + if (cursor > mCompositionString.Length()) { + MOZ_LOG(gIMMLog, LogLevel::Info, + ("CreateTextRangeArray, mCursorPosition=%ld. " + "This is larger than mCompositionString.Length()=%lu", + mCursorPosition, mCompositionString.Length())); + cursor = mCompositionString.Length(); + } + + // If caret is in the target clause, the target clause will be painted as + // normal selection range. Since caret shouldn't be in selection range on + // Windows, we shouldn't append caret range in such case. + const TextRangeArray* clauses = dispatcher->GetPendingCompositionClauses(); + const TextRange* targetClause = + clauses ? clauses->GetTargetClause() : nullptr; + if (targetClause && + cursor >= targetClause->mStartOffset && + cursor <= targetClause->mEndOffset) { + // Forget the caret position specified by IME since Gecko's caret position + // will be at the end of composition string. + mCursorPosition = NO_IME_CARET; + MOZ_LOG(gIMMLog, LogLevel::Info, + ("CreateTextRangeArray, no caret due to it's in the target " + "clause, now, mCursorPosition is NO_IME_CARET")); + } + + if (mCursorPosition != NO_IME_CARET) { + rv = dispatcher->SetCaretInPendingComposition(cursor, 0); + if (NS_WARN_IF(NS_FAILED(rv))) { + MOZ_LOG(gIMMLog, LogLevel::Error, + ("DispatchCompositionChangeEvent, FAILED due to" + "TextEventDispatcher::SetCaretInPendingComposition() failure")); + return; + } + } + } + + WidgetEventTime eventTime = aWindow->CurrentMessageWidgetEventTime(); + nsEventStatus status; + rv = dispatcher->FlushPendingComposition(status, &eventTime); + if (NS_WARN_IF(NS_FAILED(rv))) { + MOZ_LOG(gIMMLog, LogLevel::Error, + ("DispatchCompositionChangeEvent, FAILED due to" + "TextEventDispatcher::FlushPendingComposition() failure")); + return; + } +} + +void +IMMHandler::GetCompositionString(const IMEContext& aContext, + DWORD aIndex, + nsAString& aCompositionString) const +{ + aCompositionString.Truncate(); + + // Retrieve the size of the required output buffer. + long lRtn = ::ImmGetCompositionStringW(aContext.get(), aIndex, nullptr, 0); + if (lRtn < 0 || + !aCompositionString.SetLength((lRtn / sizeof(WCHAR)) + 1, + mozilla::fallible)) { + MOZ_LOG(gIMMLog, LogLevel::Error, + ("GetCompositionString, FAILED, due to OOM")); + return; // Error or out of memory. + } + + // Actually retrieve the composition string information. + lRtn = ::ImmGetCompositionStringW(aContext.get(), aIndex, + (LPVOID)aCompositionString.BeginWriting(), + lRtn + sizeof(WCHAR)); + aCompositionString.SetLength(lRtn / sizeof(WCHAR)); + + MOZ_LOG(gIMMLog, LogLevel::Info, + ("GetCompositionString, succeeded, aCompositionString=\"%s\"", + NS_ConvertUTF16toUTF8(aCompositionString).get())); +} + +bool +IMMHandler::GetTargetClauseRange(uint32_t* aOffset, + uint32_t* aLength) +{ + NS_ENSURE_TRUE(aOffset, false); + NS_ENSURE_TRUE(mIsComposing, false); + NS_ENSURE_TRUE(ShouldDrawCompositionStringOurselves(), false); + + bool found = false; + *aOffset = mCompositionStart; + for (uint32_t i = 0; i < mAttributeArray.Length(); i++) { + if (mAttributeArray[i] == ATTR_TARGET_NOTCONVERTED || + mAttributeArray[i] == ATTR_TARGET_CONVERTED) { + *aOffset = mCompositionStart + i; + found = true; + break; + } + } + + if (!aLength) { + return true; + } + + if (!found) { + // The all composition string is targetted when there is no ATTR_TARGET_* + // clause. E.g., there is only ATTR_INPUT + *aLength = mCompositionString.Length(); + return true; + } + + uint32_t offsetInComposition = *aOffset - mCompositionStart; + *aLength = mCompositionString.Length() - offsetInComposition; + for (uint32_t i = offsetInComposition; i < mAttributeArray.Length(); i++) { + if (mAttributeArray[i] != ATTR_TARGET_NOTCONVERTED && + mAttributeArray[i] != ATTR_TARGET_CONVERTED) { + *aLength = i - offsetInComposition; + break; + } + } + return true; +} + +bool +IMMHandler::ConvertToANSIString(const nsAFlatString& aStr, + UINT aCodePage, + nsACString& aANSIStr) +{ + int len = ::WideCharToMultiByte(aCodePage, 0, + (LPCWSTR)aStr.get(), aStr.Length(), + nullptr, 0, nullptr, nullptr); + NS_ENSURE_TRUE(len >= 0, false); + + if (!aANSIStr.SetLength(len, mozilla::fallible)) { + MOZ_LOG(gIMMLog, LogLevel::Error, + ("ConvertToANSIString, FAILED, due to OOM")); + return false; + } + ::WideCharToMultiByte(aCodePage, 0, (LPCWSTR)aStr.get(), aStr.Length(), + (LPSTR)aANSIStr.BeginWriting(), len, nullptr, nullptr); + return true; +} + +bool +IMMHandler::GetCharacterRectOfSelectedTextAt(nsWindow* aWindow, + uint32_t aOffset, + LayoutDeviceIntRect& aCharRect, + WritingMode* aWritingMode) +{ + LayoutDeviceIntPoint point(0, 0); + + Selection& selection = GetSelection(); + if (!selection.EnsureValidSelection(aWindow)) { + MOZ_LOG(gIMMLog, LogLevel::Error, + ("GetCharacterRectOfSelectedTextAt, FAILED, due to " + "Selection::EnsureValidSelection() failure")); + return false; + } + + // If the offset is larger than the end of composition string or selected + // string, we should return false since such case must be a bug of the caller + // or the active IME. If it's an IME's bug, we need to set targetLength to + // aOffset. + uint32_t targetLength = + mIsComposing ? mCompositionString.Length() : selection.Length(); + if (NS_WARN_IF(aOffset > targetLength)) { + MOZ_LOG(gIMMLog, LogLevel::Error, + ("GetCharacterRectOfSelectedTextAt, FAILED, due to " + "aOffset is too large (aOffset=%u, targetLength=%u, mIsComposing=%s)", + aOffset, targetLength, GetBoolName(mIsComposing))); + return false; + } + + // If there is caret, we might be able to use caret rect. + uint32_t caretOffset = UINT32_MAX; + // There is a caret only when the normal selection is collapsed. + if (selection.Collapsed()) { + if (mIsComposing) { + // If it's composing, mCursorPosition is the offset to caret in + // the composition string. + if (mCursorPosition != NO_IME_CARET) { + MOZ_ASSERT(mCursorPosition >= 0); + caretOffset = mCursorPosition; + } else if (!ShouldDrawCompositionStringOurselves() || + mCompositionString.IsEmpty()) { + // Otherwise, if there is no composition string, we should assume that + // there is a caret at the start of composition string. + caretOffset = 0; + } + } else { + // If there is no composition, the selection offset is the caret offset. + caretOffset = 0; + } + } + + // If there is a caret and retrieving offset is same as the caret offset, + // we should use the caret rect. + if (aOffset != caretOffset) { + WidgetQueryContentEvent charRect(true, eQueryTextRect, aWindow); + WidgetQueryContentEvent::Options options; + options.mRelativeToInsertionPoint = true; + charRect.InitForQueryTextRect(aOffset, 1, options); + aWindow->InitEvent(charRect, &point); + DispatchEvent(aWindow, charRect); + if (charRect.mSucceeded) { + aCharRect = charRect.mReply.mRect; + if (aWritingMode) { + *aWritingMode = charRect.GetWritingMode(); + } + MOZ_LOG(gIMMLog, LogLevel::Debug, + ("GetCharacterRectOfSelectedTextAt, Succeeded, aOffset=%u, " + "aCharRect={ x: %ld, y: %ld, width: %ld, height: %ld }, " + "charRect.GetWritingMode()=%s", + aOffset, aCharRect.x, aCharRect.y, aCharRect.width, aCharRect.height, + GetWritingModeName(charRect.GetWritingMode()).get())); + return true; + } + } + + return GetCaretRect(aWindow, aCharRect, aWritingMode); +} + +bool +IMMHandler::GetCaretRect(nsWindow* aWindow, + LayoutDeviceIntRect& aCaretRect, + WritingMode* aWritingMode) +{ + LayoutDeviceIntPoint point(0, 0); + + WidgetQueryContentEvent caretRect(true, eQueryCaretRect, aWindow); + WidgetQueryContentEvent::Options options; + options.mRelativeToInsertionPoint = true; + caretRect.InitForQueryCaretRect(0, options); + aWindow->InitEvent(caretRect, &point); + DispatchEvent(aWindow, caretRect); + if (!caretRect.mSucceeded) { + MOZ_LOG(gIMMLog, LogLevel::Info, + ("GetCaretRect, FAILED, due to eQueryCaretRect failure")); + return false; + } + aCaretRect = caretRect.mReply.mRect; + if (aWritingMode) { + *aWritingMode = caretRect.GetWritingMode(); + } + MOZ_LOG(gIMMLog, LogLevel::Info, + ("GetCaretRect, SUCCEEDED, " + "aCaretRect={ x: %ld, y: %ld, width: %ld, height: %ld }, " + "caretRect.GetWritingMode()=%s", + aCaretRect.x, aCaretRect.y, aCaretRect.width, aCaretRect.height, + GetWritingModeName(caretRect.GetWritingMode()).get())); + return true; +} + +bool +IMMHandler::SetIMERelatedWindowsPos(nsWindow* aWindow, + const IMEContext& aContext) +{ + LayoutDeviceIntRect r; + // Get first character rect of current a normal selected text or a composing + // string. + WritingMode writingMode; + bool ret = GetCharacterRectOfSelectedTextAt(aWindow, 0, r, &writingMode); + NS_ENSURE_TRUE(ret, false); + nsWindow* toplevelWindow = aWindow->GetTopLevelWindow(false); + LayoutDeviceIntRect firstSelectedCharRect; + ResolveIMECaretPos(toplevelWindow, r, aWindow, firstSelectedCharRect); + + // Set native caret size/position to our caret. Some IMEs honor it. E.g., + // "Intelligent ABC" (Simplified Chinese) and "MS PinYin 3.0" (Simplified + // Chinese) on XP. + LayoutDeviceIntRect caretRect(firstSelectedCharRect); + if (GetCaretRect(aWindow, r)) { + ResolveIMECaretPos(toplevelWindow, r, aWindow, caretRect); + } else { + NS_WARNING("failed to get caret rect"); + caretRect.width = 1; + } + if (!mNativeCaretIsCreated) { + mNativeCaretIsCreated = ::CreateCaret(aWindow->GetWindowHandle(), nullptr, + caretRect.width, caretRect.height); + MOZ_LOG(gIMMLog, LogLevel::Info, + ("SetIMERelatedWindowsPos, mNativeCaretIsCreated=%s, " + "width=%ld, height=%ld", + GetBoolName(mNativeCaretIsCreated), caretRect.width, caretRect.height)); + } + ::SetCaretPos(caretRect.x, caretRect.y); + + if (ShouldDrawCompositionStringOurselves()) { + MOZ_LOG(gIMMLog, LogLevel::Info, + ("SetIMERelatedWindowsPos, Set candidate window")); + + // Get a rect of first character in current target in composition string. + LayoutDeviceIntRect firstTargetCharRect, lastTargetCharRect; + if (mIsComposing && !mCompositionString.IsEmpty()) { + // If there are no targetted selection, we should use it's first character + // rect instead. + uint32_t offset, length; + if (!GetTargetClauseRange(&offset, &length)) { + MOZ_LOG(gIMMLog, LogLevel::Error, + ("SetIMERelatedWindowsPos, FAILED, due to " + "GetTargetClauseRange() failure")); + return false; + } + ret = GetCharacterRectOfSelectedTextAt(aWindow, + offset - mCompositionStart, + firstTargetCharRect, &writingMode); + NS_ENSURE_TRUE(ret, false); + if (length) { + ret = GetCharacterRectOfSelectedTextAt(aWindow, + offset + length - 1 - mCompositionStart, lastTargetCharRect); + NS_ENSURE_TRUE(ret, false); + } else { + lastTargetCharRect = firstTargetCharRect; + } + } else { + // If there are no composition string, we should use a first character + // rect. + ret = GetCharacterRectOfSelectedTextAt(aWindow, 0, + firstTargetCharRect, &writingMode); + NS_ENSURE_TRUE(ret, false); + lastTargetCharRect = firstTargetCharRect; + } + ResolveIMECaretPos(toplevelWindow, firstTargetCharRect, + aWindow, firstTargetCharRect); + ResolveIMECaretPos(toplevelWindow, lastTargetCharRect, + aWindow, lastTargetCharRect); + LayoutDeviceIntRect targetClauseRect; + targetClauseRect.UnionRect(firstTargetCharRect, lastTargetCharRect); + + // Move the candidate window to proper position from the target clause as + // far as possible. + CANDIDATEFORM candForm; + candForm.dwIndex = 0; + if (!writingMode.IsVertical() || IsVerticalWritingSupported()) { + candForm.dwStyle = CFS_EXCLUDE; + // Candidate window shouldn't overlap the target clause in any writing + // mode. + candForm.rcArea.left = targetClauseRect.x; + candForm.rcArea.right = targetClauseRect.XMost(); + candForm.rcArea.top = targetClauseRect.y; + candForm.rcArea.bottom = targetClauseRect.YMost(); + if (!writingMode.IsVertical()) { + // In horizontal layout, current point of interest should be top-left + // of the first character. + candForm.ptCurrentPos.x = firstTargetCharRect.x; + candForm.ptCurrentPos.y = firstTargetCharRect.y; + } else if (writingMode.IsVerticalRL()) { + // In vertical layout (RL), candidate window should be positioned right + // side of target clause. However, we don't set vertical writing font + // to the IME. Therefore, the candidate window may be positioned + // bottom-left of target clause rect with these information. + candForm.ptCurrentPos.x = targetClauseRect.x; + candForm.ptCurrentPos.y = targetClauseRect.y; + } else { + MOZ_ASSERT(writingMode.IsVerticalLR(), "Did we miss some causes?"); + // In vertical layout (LR), candidate window should be poisitioned left + // side of target clause. Although, we don't set vertical writing font + // to the IME, the candidate window may be positioned bottom-right of + // the target clause rect with these information. + candForm.ptCurrentPos.x = targetClauseRect.XMost(); + candForm.ptCurrentPos.y = targetClauseRect.y; + } + } else { + // If vertical writing is not supported by IME, let's set candidate + // window position to the bottom-left of the target clause because + // the position must be the safest position to prevent the candidate + // window to overlap with the target clause. + candForm.dwStyle = CFS_CANDIDATEPOS; + candForm.ptCurrentPos.x = targetClauseRect.x; + candForm.ptCurrentPos.y = targetClauseRect.YMost(); + } + MOZ_LOG(gIMMLog, LogLevel::Info, + ("SetIMERelatedWindowsPos, Calling ImmSetCandidateWindow()... " + "ptCurrentPos={ x=%d, y=%d }, " + "rcArea={ left=%d, top=%d, right=%d, bottom=%d }, " + "writingMode=%s", + candForm.ptCurrentPos.x, candForm.ptCurrentPos.y, + candForm.rcArea.left, candForm.rcArea.top, + candForm.rcArea.right, candForm.rcArea.bottom, + GetWritingModeName(writingMode).get())); + ::ImmSetCandidateWindow(aContext.get(), &candForm); + } else { + MOZ_LOG(gIMMLog, LogLevel::Info, + ("SetIMERelatedWindowsPos, Set composition window")); + + // Move the composition window to caret position (if selected some + // characters, we should use first character rect of them). + // And in this mode, IME adjusts the candidate window position + // automatically. So, we don't need to set it. + COMPOSITIONFORM compForm; + compForm.dwStyle = CFS_POINT; + compForm.ptCurrentPos.x = + !writingMode.IsVerticalLR() ? firstSelectedCharRect.x : + firstSelectedCharRect.XMost(); + compForm.ptCurrentPos.y = firstSelectedCharRect.y; + ::ImmSetCompositionWindow(aContext.get(), &compForm); + } + + return true; +} + +void +IMMHandler::SetIMERelatedWindowsPosOnPlugin(nsWindow* aWindow, + const IMEContext& aContext) +{ + WidgetQueryContentEvent editorRectEvent(true, eQueryEditorRect, aWindow); + aWindow->InitEvent(editorRectEvent); + DispatchEvent(aWindow, editorRectEvent); + if (!editorRectEvent.mSucceeded) { + MOZ_LOG(gIMMLog, LogLevel::Info, + ("SetIMERelatedWindowsPosOnPlugin, " + "FAILED, due to eQueryEditorRect failure")); + return; + } + + // Clip the plugin rect by the client rect of the window because composition + // window needs to be specified the position in the client area. + nsWindow* toplevelWindow = aWindow->GetTopLevelWindow(false); + LayoutDeviceIntRect pluginRectInScreen = + editorRectEvent.mReply.mRect + toplevelWindow->WidgetToScreenOffset(); + LayoutDeviceIntRect winRectInScreen = aWindow->GetClientBounds(); + // composition window cannot be positioned on the edge of client area. + winRectInScreen.width--; + winRectInScreen.height--; + LayoutDeviceIntRect clippedPluginRect; + clippedPluginRect.x = + std::min(std::max(pluginRectInScreen.x, winRectInScreen.x), + winRectInScreen.XMost()); + clippedPluginRect.y = + std::min(std::max(pluginRectInScreen.y, winRectInScreen.y), + winRectInScreen.YMost()); + int32_t xMost = std::min(pluginRectInScreen.XMost(), winRectInScreen.XMost()); + int32_t yMost = std::min(pluginRectInScreen.YMost(), winRectInScreen.YMost()); + clippedPluginRect.width = std::max(0, xMost - clippedPluginRect.x); + clippedPluginRect.height = std::max(0, yMost - clippedPluginRect.y); + clippedPluginRect -= aWindow->WidgetToScreenOffset(); + + // Cover the plugin with native caret. This prevents IME's window and plugin + // overlap. + if (mNativeCaretIsCreated) { + ::DestroyCaret(); + } + mNativeCaretIsCreated = + ::CreateCaret(aWindow->GetWindowHandle(), nullptr, + clippedPluginRect.width, clippedPluginRect.height); + ::SetCaretPos(clippedPluginRect.x, clippedPluginRect.y); + + // Set the composition window to bottom-left of the clipped plugin. + // As far as we know, there is no IME for RTL language. Therefore, this code + // must not need to take care of RTL environment. + COMPOSITIONFORM compForm; + compForm.dwStyle = CFS_POINT; + compForm.ptCurrentPos.x = clippedPluginRect.BottomLeft().x; + compForm.ptCurrentPos.y = clippedPluginRect.BottomLeft().y; + if (!::ImmSetCompositionWindow(aContext.get(), &compForm)) { + MOZ_LOG(gIMMLog, LogLevel::Info, + ("SetIMERelatedWindowsPosOnPlugin, " + "FAILED, due to ::ImmSetCompositionWindow() failure")); + return; + } +} + +void +IMMHandler::ResolveIMECaretPos(nsIWidget* aReferenceWidget, + LayoutDeviceIntRect& aCursorRect, + nsIWidget* aNewOriginWidget, + LayoutDeviceIntRect& aOutRect) +{ + aOutRect = aCursorRect; + + if (aReferenceWidget == aNewOriginWidget) + return; + + if (aReferenceWidget) + aOutRect.MoveBy(aReferenceWidget->WidgetToScreenOffset()); + + if (aNewOriginWidget) + aOutRect.MoveBy(-aNewOriginWidget->WidgetToScreenOffset()); +} + +static void +SetHorizontalFontToLogFont(const nsAString& aFontFace, + LOGFONTW& aLogFont) +{ + aLogFont.lfEscapement = aLogFont.lfOrientation = 0; + if (NS_WARN_IF(aFontFace.Length() > LF_FACESIZE - 1)) { + memcpy(aLogFont.lfFaceName, L"System", sizeof(L"System")); + return; + } + memcpy(aLogFont.lfFaceName, aFontFace.BeginReading(), + aFontFace.Length() * sizeof(wchar_t)); + aLogFont.lfFaceName[aFontFace.Length()] = 0; +} + +static void +SetVerticalFontToLogFont(const nsAString& aFontFace, + LOGFONTW& aLogFont) +{ + aLogFont.lfEscapement = aLogFont.lfOrientation = 2700; + if (NS_WARN_IF(aFontFace.Length() > LF_FACESIZE - 2)) { + memcpy(aLogFont.lfFaceName, L"@System", sizeof(L"@System")); + return; + } + aLogFont.lfFaceName[0] = '@'; + memcpy(&aLogFont.lfFaceName[1], aFontFace.BeginReading(), + aFontFace.Length() * sizeof(wchar_t)); + aLogFont.lfFaceName[aFontFace.Length() + 1] = 0; +} + +void +IMMHandler::AdjustCompositionFont(nsWindow* aWindow, + const IMEContext& aContext, + const WritingMode& aWritingMode, + bool aForceUpdate) +{ + // An instance of IMMHandler is destroyed when active IME is changed. + // Therefore, we need to store the information which are set to the IM + // context to static variables since IM context is never recreated. + static bool sCompositionFontsInitialized = false; + static nsString sCompositionFont = + Preferences::GetString("intl.imm.composition_font"); + + // If composition font is customized by pref, we need to modify the + // composition font of the IME context at first time even if the writing mode + // is horizontal. + bool setCompositionFontForcibly = aForceUpdate || + (!sCompositionFontsInitialized && !sCompositionFont.IsEmpty()); + + static WritingMode sCurrentWritingMode; + static nsString sCurrentIMEName; + if (!setCompositionFontForcibly && + sWritingModeOfCompositionFont == aWritingMode && + sCurrentIMEName == sIMEName) { + // Nothing to do if writing mode isn't being changed. + return; + } + + // Decide composition fonts for both horizontal writing mode and vertical + // writing mode. If the font isn't specified by the pref, use default + // font which is already set to the IM context. And also in vertical writing + // mode, insert '@' to the start of the font. + if (!sCompositionFontsInitialized) { + sCompositionFontsInitialized = true; + // sCompositionFontH must not start with '@' and its length is less than + // LF_FACESIZE since it needs to end with null terminating character. + if (sCompositionFont.IsEmpty() || + sCompositionFont.Length() > LF_FACESIZE - 1 || + sCompositionFont[0] == '@') { + LOGFONTW defaultLogFont; + if (NS_WARN_IF(!::ImmGetCompositionFont(aContext.get(), + &defaultLogFont))) { + MOZ_LOG(gIMMLog, LogLevel::Error, + ("AdjustCompositionFont, ::ImmGetCompositionFont() failed")); + sCompositionFont.AssignLiteral("System"); + } else { + // The font face is typically, "System". + sCompositionFont.Assign(defaultLogFont.lfFaceName); + } + } + + MOZ_LOG(gIMMLog, LogLevel::Info, + ("AdjustCompositionFont, sCompositionFont=\"%s\" is initialized", + NS_ConvertUTF16toUTF8(sCompositionFont).get())); + } + + static nsString sCompositionFontForJapanist2003; + if (IsJapanist2003Active() && sCompositionFontForJapanist2003.IsEmpty()) { + const char* kCompositionFontForJapanist2003 = + "intl.imm.composition_font.japanist_2003"; + sCompositionFontForJapanist2003 = + Preferences::GetString(kCompositionFontForJapanist2003); + // If the font name is not specified properly, let's use + // "MS PGothic" instead. + if (sCompositionFontForJapanist2003.IsEmpty() || + sCompositionFontForJapanist2003.Length() > LF_FACESIZE - 2 || + sCompositionFontForJapanist2003[0] == '@') { + sCompositionFontForJapanist2003.AssignLiteral("MS PGothic"); + } + } + + sWritingModeOfCompositionFont = aWritingMode; + sCurrentIMEName = sIMEName; + + LOGFONTW logFont; + memset(&logFont, 0, sizeof(logFont)); + if (!::ImmGetCompositionFont(aContext.get(), &logFont)) { + MOZ_LOG(gIMMLog, LogLevel::Error, + ("AdjustCompositionFont, ::ImmGetCompositionFont() failed")); + logFont.lfFaceName[0] = 0; + } + // Need to reset some information which should be recomputed with new font. + logFont.lfWidth = 0; + logFont.lfWeight = FW_DONTCARE; + logFont.lfOutPrecision = OUT_DEFAULT_PRECIS; + logFont.lfClipPrecision = CLIP_DEFAULT_PRECIS; + logFont.lfPitchAndFamily = DEFAULT_PITCH; + + if (!aWindow->PluginHasFocus() && + aWritingMode.IsVertical() && IsVerticalWritingSupported()) { + SetVerticalFontToLogFont( + IsJapanist2003Active() ? sCompositionFontForJapanist2003 : + sCompositionFont, logFont); + } else { + SetHorizontalFontToLogFont( + IsJapanist2003Active() ? sCompositionFontForJapanist2003 : + sCompositionFont, logFont); + } + MOZ_LOG(gIMMLog, LogLevel::Warning, + ("AdjustCompositionFont, calling ::ImmSetCompositionFont(\"%s\")", + NS_ConvertUTF16toUTF8(nsDependentString(logFont.lfFaceName)).get())); + ::ImmSetCompositionFontW(aContext.get(), &logFont); +} + +// static +nsresult +IMMHandler::OnMouseButtonEvent(nsWindow* aWindow, + const IMENotification& aIMENotification) +{ + // We don't need to create the instance of the handler here. + if (!gIMMHandler) { + return NS_OK; + } + + if (!sWM_MSIME_MOUSE || !IsComposingOnOurEditor() || + !ShouldDrawCompositionStringOurselves()) { + return NS_OK; + } + + // We need to handle only mousedown event. + if (aIMENotification.mMouseButtonEventData.mEventMessage != eMouseDown) { + return NS_OK; + } + + // If the character under the cursor is not in the composition string, + // we don't need to notify IME of it. + uint32_t compositionStart = gIMMHandler->mCompositionStart; + uint32_t compositionEnd = + compositionStart + gIMMHandler->mCompositionString.Length(); + if (aIMENotification.mMouseButtonEventData.mOffset < compositionStart || + aIMENotification.mMouseButtonEventData.mOffset >= compositionEnd) { + return NS_OK; + } + + BYTE button; + switch (aIMENotification.mMouseButtonEventData.mButton) { + case WidgetMouseEventBase::eLeftButton: + button = IMEMOUSE_LDOWN; + break; + case WidgetMouseEventBase::eMiddleButton: + button = IMEMOUSE_MDOWN; + break; + case WidgetMouseEventBase::eRightButton: + button = IMEMOUSE_RDOWN; + break; + default: + return NS_OK; + } + + // calcurate positioning and offset + // char : JCH1|JCH2|JCH3 + // offset: 0011 1122 2233 + // positioning: 2301 2301 2301 + nsIntPoint cursorPos = + aIMENotification.mMouseButtonEventData.mCursorPos.AsIntPoint(); + nsIntRect charRect = + aIMENotification.mMouseButtonEventData.mCharRect.AsIntRect(); + int32_t cursorXInChar = cursorPos.x - charRect.x; + // The event might hit to zero-width character, see bug 694913. + // The reason might be: + // * There are some zero-width characters are actually. + // * font-size is specified zero. + // But nobody reproduced this bug actually... + // We should assume that user clicked on right most of the zero-width + // character in such case. + int positioning = 1; + if (charRect.width > 0) { + positioning = cursorXInChar * 4 / charRect.width; + positioning = (positioning + 2) % 4; + } + + int offset = + aIMENotification.mMouseButtonEventData.mOffset - compositionStart; + if (positioning < 2) { + offset++; + } + + MOZ_LOG(gIMMLog, LogLevel::Info, + ("OnMouseButtonEvent, x,y=%ld,%ld, offset=%ld, positioning=%ld", + cursorPos.x, cursorPos.y, offset, positioning)); + + // send MS_MSIME_MOUSE message to default IME window. + HWND imeWnd = ::ImmGetDefaultIMEWnd(aWindow->GetWindowHandle()); + IMEContext context(aWindow); + if (::SendMessageW(imeWnd, sWM_MSIME_MOUSE, + MAKELONG(MAKEWORD(button, positioning), offset), + (LPARAM) context.get()) == 1) { + return NS_SUCCESS_EVENT_CONSUMED; + } + return NS_OK; +} + +// static +bool +IMMHandler::OnKeyDownEvent(nsWindow* aWindow, + WPARAM wParam, + LPARAM lParam, + MSGResult& aResult) +{ + MOZ_LOG(gIMMLog, LogLevel::Info, + ("OnKeyDownEvent, hWnd=%08x, wParam=%08x, lParam=%08x", + aWindow->GetWindowHandle(), wParam, lParam)); + aResult.mConsumed = false; + switch (wParam) { + case VK_TAB: + case VK_PRIOR: + case VK_NEXT: + case VK_END: + case VK_HOME: + case VK_LEFT: + case VK_UP: + case VK_RIGHT: + case VK_DOWN: + case VK_RETURN: + // If IME didn't process the key message (the virtual key code wasn't + // converted to VK_PROCESSKEY), and the virtual key code event causes + // moving caret or editing text with keeping composing state, we should + // cancel the composition here because we cannot support moving + // composition string with DOM events (IE also cancels the composition + // in same cases). Then, this event will be dispatched. + if (IsComposingOnOurEditor()) { + // NOTE: We don't need to cancel the composition on another window. + CancelComposition(aWindow, false); + } + return false; + default: + return false; + } +} + +// static +void +IMMHandler::SetCandidateWindow(nsWindow* aWindow, CANDIDATEFORM* aForm) +{ + // Hack for ATOK 2011 - 2016 (Japanese IME). They refer native caret + // position at deciding candidate window position. Note that we cannot + // check active IME since TIPs are wrapped and hidden by CUAS. + if (aWindow->PluginHasFocus()) { + // We cannot retrieve proper character height from plugin. Therefore, + // we should assume that the caret height is always 20px since if less than + // this height, candidate window may overlap with composition string when + // there is no enough space under composition string to show candidate + // window. + static const int32_t kCaretHeight = 20; + if (sNativeCaretIsCreatedForPlugin) { + ::DestroyCaret(); + } + sNativeCaretIsCreatedForPlugin = + ::CreateCaret(aWindow->GetWindowHandle(), nullptr, 0, kCaretHeight); + if (sNativeCaretIsCreatedForPlugin) { + LayoutDeviceIntPoint caretPosition(aForm->ptCurrentPos.x, + aForm->ptCurrentPos.y - kCaretHeight); + nsWindow* toplevelWindow = aWindow->GetTopLevelWindow(false); + if (toplevelWindow && toplevelWindow != aWindow) { + caretPosition += toplevelWindow->WidgetToScreenOffset(); + caretPosition -= aWindow->WidgetToScreenOffset(); + } + ::SetCaretPos(caretPosition.x, caretPosition.y); + } + } + IMEContext context(aWindow); + ::ImmSetCandidateWindow(context.get(), aForm); +} + +// staitc +void +IMMHandler::DefaultProcOfPluginEvent(nsWindow* aWindow, const NPEvent* aEvent) +{ + switch (aEvent->event) { + case WM_IME_STARTCOMPOSITION: + EnsureHandlerInstance(); + gIMMHandler->OnIMEStartCompositionOnPlugin(aWindow, aEvent->wParam, + aEvent->lParam); + break; + + case WM_IME_COMPOSITION: + if (gIMMHandler) { + gIMMHandler->OnIMECompositionOnPlugin(aWindow, aEvent->wParam, + aEvent->lParam); + } + break; + + case WM_IME_ENDCOMPOSITION: + if (gIMMHandler) { + gIMMHandler->OnIMEEndCompositionOnPlugin(aWindow, aEvent->wParam, + aEvent->lParam); + } + break; + } +} + +/****************************************************************************** + * IMMHandler::Selection + ******************************************************************************/ + +bool +IMMHandler::Selection::IsValid() const +{ + if (!mIsValid || NS_WARN_IF(mOffset == UINT32_MAX)) { + return false; + } + CheckedInt<uint32_t> endOffset = + CheckedInt<uint32_t>(mOffset) + Length(); + return endOffset.isValid(); +} + +bool +IMMHandler::Selection::Update(const IMENotification& aIMENotification) +{ + mOffset = aIMENotification.mSelectionChangeData.mOffset; + mString = aIMENotification.mSelectionChangeData.String(); + mWritingMode = aIMENotification.mSelectionChangeData.GetWritingMode(); + mIsValid = true; + + MOZ_LOG(gIMMLog, LogLevel::Info, + ("Selection::Update, aIMENotification={ mSelectionChangeData={ " + "mOffset=%u, mLength=%u, GetWritingMode()=%s } }", + mOffset, mString.Length(), GetWritingModeName(mWritingMode).get())); + + if (!IsValid()) { + MOZ_LOG(gIMMLog, LogLevel::Error, + ("Selection::Update, FAILED, due to invalid range")); + Clear(); + return false; + } + return true; +} + +bool +IMMHandler::Selection::Init(nsWindow* aWindow) +{ + Clear(); + + WidgetQueryContentEvent selection(true, eQuerySelectedText, aWindow); + LayoutDeviceIntPoint point(0, 0); + aWindow->InitEvent(selection, &point); + DispatchEvent(aWindow, selection); + if (NS_WARN_IF(!selection.mSucceeded)) { + MOZ_LOG(gIMMLog, LogLevel::Error, + ("Selection::Init, FAILED, due to eQuerySelectedText failure")); + return false; + } + // If the window is destroyed during querying selected text, we shouldn't + // do anymore. + if (aWindow->Destroyed()) { + MOZ_LOG(gIMMLog, LogLevel::Error, + ("Selection::Init, FAILED, due to the widget destroyed")); + return false; + } + + mOffset = selection.mReply.mOffset; + mString = selection.mReply.mString; + mWritingMode = selection.GetWritingMode(); + mIsValid = true; + + MOZ_LOG(gIMMLog, LogLevel::Info, + ("Selection::Init, selection={ mReply={ mOffset=%u, " + "mString.Length()=%u, mWritingMode=%s } }", + mOffset, mString.Length(), GetWritingModeName(mWritingMode).get())); + + if (!IsValid()) { + MOZ_LOG(gIMMLog, LogLevel::Error, + ("Selection::Init, FAILED, due to invalid range")); + Clear(); + return false; + } + return true; +} + +bool +IMMHandler::Selection::EnsureValidSelection(nsWindow* aWindow) +{ + if (IsValid()) { + return true; + } + return Init(aWindow); +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/windows/IMMHandler.h b/widget/windows/IMMHandler.h new file mode 100644 index 0000000000..f3120cfec7 --- /dev/null +++ b/widget/windows/IMMHandler.h @@ -0,0 +1,495 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef IMMHandler_h_ +#define IMMHandler_h_ + +#include "nscore.h" +#include <windows.h> +#include "nsCOMPtr.h" +#include "nsString.h" +#include "nsTArray.h" +#include "nsIWidget.h" +#include "mozilla/EventForwards.h" +#include "mozilla/TextEventDispatcher.h" +#include "nsRect.h" +#include "WritingModes.h" +#include "npapi.h" + +class nsWindow; +class nsWindowBase; + +namespace mozilla { +namespace widget { + +struct MSGResult; + +class IMEContext final +{ +public: + IMEContext() + : mWnd(nullptr) + , mIMC(nullptr) + { + } + + explicit IMEContext(HWND aWnd); + explicit IMEContext(nsWindowBase* aWindowBase); + + ~IMEContext() + { + Clear(); + } + + HIMC get() const + { + return mIMC; + } + + void Init(HWND aWnd); + void Init(nsWindowBase* aWindowBase); + void Clear(); + + bool IsValid() const + { + return !!mIMC; + } + + void SetOpenState(bool aOpen) const + { + if (!mIMC) { + return; + } + ::ImmSetOpenStatus(mIMC, aOpen); + } + + bool GetOpenState() const + { + if (!mIMC) { + return false; + } + return (::ImmGetOpenStatus(mIMC) != FALSE); + } + + bool AssociateDefaultContext() + { + // We assume that there is only default IMC, no new IMC has been created. + if (mIMC) { + return false; + } + if (!::ImmAssociateContextEx(mWnd, nullptr, IACE_DEFAULT)) { + return false; + } + mIMC = ::ImmGetContext(mWnd); + return (mIMC != nullptr); + } + + bool Disassociate() + { + if (!mIMC) { + return false; + } + if (!::ImmAssociateContextEx(mWnd, nullptr, 0)) { + return false; + } + ::ImmReleaseContext(mWnd, mIMC); + mIMC = nullptr; + return true; + } + +protected: + IMEContext(const IMEContext& aOther) + { + MOZ_CRASH("Don't copy IMEContext"); + } + + HWND mWnd; + HIMC mIMC; +}; + +class IMMHandler final +{ +public: + static void Initialize(); + static void Terminate(); + + // If Process*() returns true, the caller shouldn't do anything anymore. + static bool ProcessMessage(nsWindow* aWindow, UINT msg, + WPARAM& wParam, LPARAM& lParam, + MSGResult& aResult); + static bool IsComposing() + { + return IsComposingOnOurEditor(); + } + static bool IsComposingOn(nsWindow* aWindow) + { + return IsComposing() && IsComposingWindow(aWindow); + } + +#ifdef DEBUG + /** + * IsIMEAvailable() returns TRUE when current keyboard layout has IME. + * Otherwise, FALSE. + */ + static bool IsIMEAvailable() { return !!::ImmIsIME(::GetKeyboardLayout(0)); } +#endif + + // If aForce is TRUE, these methods doesn't check whether we have composition + // or not. If you don't set it to TRUE, these method doesn't commit/cancel + // the composition on uexpected window. + static void CommitComposition(nsWindow* aWindow, bool aForce = false); + static void CancelComposition(nsWindow* aWindow, bool aForce = false); + static void OnFocusChange(bool aFocus, nsWindow* aWindow); + static void OnUpdateComposition(nsWindow* aWindow); + static void OnSelectionChange(nsWindow* aWindow, + const IMENotification& aIMENotification, + bool aIsIMMActive); + + static nsIMEUpdatePreference GetIMEUpdatePreference(); + + // Returns NS_SUCCESS_EVENT_CONSUMED if the mouse button event is consumed by + // IME. Otherwise, NS_OK. + static nsresult OnMouseButtonEvent(nsWindow* aWindow, + const IMENotification& aIMENotification); + static void SetCandidateWindow(nsWindow* aWindow, CANDIDATEFORM* aForm); + static void DefaultProcOfPluginEvent(nsWindow* aWindow, + const NPEvent* aEvent); + +protected: + static void EnsureHandlerInstance(); + + static bool IsComposingOnOurEditor(); + static bool IsComposingOnPlugin(); + static bool IsComposingWindow(nsWindow* aWindow); + + static bool IsJapanist2003Active(); + static bool IsGoogleJapaneseInputActive(); + + static bool ShouldDrawCompositionStringOurselves(); + static bool IsVerticalWritingSupported(); + // aWindow can be nullptr if it's called without receiving WM_INPUTLANGCHANGE. + static void InitKeyboardLayout(nsWindow* aWindow, HKL aKeyboardLayout); + static UINT GetKeyboardCodePage(); + + /** + * Checks whether the window is top level window of the composing window. + * In this method, the top level window means in all windows, not only in all + * OUR windows. I.e., if the aWindow is embedded, this always returns FALSE. + */ + static bool IsTopLevelWindowOfComposition(nsWindow* aWindow); + + static bool ProcessInputLangChangeMessage(nsWindow* aWindow, + WPARAM wParam, + LPARAM lParam, + MSGResult& aResult); + static bool ProcessMessageForPlugin(nsWindow* aWindow, UINT msg, + WPARAM &wParam, LPARAM &lParam, + bool &aRet, MSGResult& aResult); + + IMMHandler(); + ~IMMHandler(); + + // On*() methods return true if the caller of message handler shouldn't do + // anything anymore. Otherwise, false. + static bool OnKeyDownEvent(nsWindow* aWindow, WPARAM wParam, LPARAM lParam, + MSGResult& aResult); + + bool OnIMEStartComposition(nsWindow* aWindow, MSGResult& aResult); + void OnIMEStartCompositionOnPlugin(nsWindow* aWindow, + WPARAM wParam, LPARAM lParam); + bool OnIMEComposition(nsWindow* aWindow, WPARAM wParam, LPARAM lParam, + MSGResult& aResult); + void OnIMECompositionOnPlugin(nsWindow* aWindow, WPARAM wParam, + LPARAM lParam); + bool OnIMEEndComposition(nsWindow* aWindow, MSGResult& aResult); + void OnIMEEndCompositionOnPlugin(nsWindow* aWindow, WPARAM wParam, + LPARAM lParam); + bool OnIMERequest(nsWindow* aWindow, WPARAM wParam, LPARAM lParam, + MSGResult& aResult); + bool OnIMECharOnPlugin(nsWindow* aWindow, WPARAM wParam, LPARAM lParam, + MSGResult& aResult); + bool OnChar(nsWindow* aWindow, WPARAM wParam, LPARAM lParam, + MSGResult& aResult); + bool OnCharOnPlugin(nsWindow* aWindow, WPARAM wParam, LPARAM lParam, + MSGResult& aResult); + void OnInputLangChange(nsWindow* aWindow, WPARAM wParam, LPARAM lParam, + MSGResult& aResult); + + // These message handlers don't use instance members, we should not create + // the instance by the messages. So, they should be static. + static bool OnIMEChar(nsWindow* aWindow, WPARAM wParam, LPARAM lParam, + MSGResult& aResult); + static bool OnIMESetContext(nsWindow* aWindow, WPARAM wParam, LPARAM lParam, + MSGResult& aResult); + static bool OnIMESetContextOnPlugin(nsWindow* aWindow, + WPARAM wParam, LPARAM lParam, + MSGResult& aResult); + static bool OnIMECompositionFull(nsWindow* aWindow, MSGResult& aResult); + static bool OnIMENotify(nsWindow* aWindow, WPARAM wParam, LPARAM lParam, + MSGResult& aResult); + static bool OnIMESelect(nsWindow* aWindow, WPARAM wParam, LPARAM lParam, + MSGResult& aResult); + + // The result of Handle* method mean "Processed" when it's TRUE. + void HandleStartComposition(nsWindow* aWindow, + const IMEContext& aContext); + bool HandleComposition(nsWindow* aWindow, + const IMEContext& aContext, + LPARAM lParam); + // If aCommitString is null, this commits composition with the latest + // dispatched data. Otherwise, commits composition with the value. + void HandleEndComposition(nsWindow* aWindow, + const nsAString* aCommitString = nullptr); + bool HandleReconvert(nsWindow* aWindow, LPARAM lParam, LRESULT *oResult); + bool HandleQueryCharPosition(nsWindow* aWindow, LPARAM lParam, + LRESULT *oResult); + bool HandleDocumentFeed(nsWindow* aWindow, LPARAM lParam, LRESULT *oResult); + + /** + * When a window's IME context is activating but we have composition on + * another window, we should commit our composition because IME context is + * shared by all our windows (including plug-ins). + * @param aWindow is a new activated window. + * If aWindow is our composing window, this method does nothing. + * Otherwise, this commits the composition on the previous window. + * If this method did commit a composition, this returns TRUE. + */ + bool CommitCompositionOnPreviousWindow(nsWindow* aWindow); + + /** + * ResolveIMECaretPos + * Convert the caret rect of a composition event to another widget's + * coordinate system. + * + * @param aReferenceWidget The origin widget of aCursorRect. + * Typically, this is mReferenceWidget of the + * composing events. If the aCursorRect is in screen + * coordinates, set nullptr. + * @param aCursorRect The cursor rect. + * @param aNewOriginWidget aOutRect will be in this widget's coordinates. If + * this is nullptr, aOutRect will be in screen + * coordinates. + * @param aOutRect The converted cursor rect. + */ + void ResolveIMECaretPos(nsIWidget* aReferenceWidget, + mozilla::LayoutDeviceIntRect& aCursorRect, + nsIWidget* aNewOriginWidget, + mozilla::LayoutDeviceIntRect& aOutRect); + + bool ConvertToANSIString(const nsAFlatString& aStr, + UINT aCodePage, + nsACString& aANSIStr); + + bool SetIMERelatedWindowsPos(nsWindow* aWindow, + const IMEContext& aContext); + void SetIMERelatedWindowsPosOnPlugin(nsWindow* aWindow, + const IMEContext& aContext); + /** + * GetCharacterRectOfSelectedTextAt() returns character rect of the offset + * from the selection start or the start of composition string if there is + * a composition. + * + * @param aWindow The window which has focus. + * @param aOffset Offset from the selection start or the start of + * composition string when there is a composition. + * This must be in the selection range or + * the composition string. + * @param aCharRect The result. + * @param aWritingMode The writing mode of current selection. When this + * is nullptr, this assumes that the selection is in + * horizontal writing mode. + * @return true if this succeeded to retrieve the rect. + * Otherwise, false. + */ + bool GetCharacterRectOfSelectedTextAt( + nsWindow* aWindow, + uint32_t aOffset, + mozilla::LayoutDeviceIntRect& aCharRect, + mozilla::WritingMode* aWritingMode = nullptr); + /** + * GetCaretRect() returns caret rect at current selection start. + * + * @param aWindow The window which has focus. + * @param aCaretRect The result. + * @param aWritingMode The writing mode of current selection. When this + * is nullptr, this assumes that the selection is in + * horizontal writing mode. + * @return true if this succeeded to retrieve the rect. + * Otherwise, false. + */ + bool GetCaretRect(nsWindow* aWindow, + mozilla::LayoutDeviceIntRect& aCaretRect, + mozilla::WritingMode* aWritingMode = nullptr); + void GetCompositionString(const IMEContext& aContext, + DWORD aIndex, + nsAString& aCompositionString) const; + + /** + * AdjustCompositionFont() makes IME vertical writing mode if it's supported. + * If aForceUpdate is true, it will update composition font even if writing + * mode isn't being changed. + */ + void AdjustCompositionFont(nsWindow* aWindow, + const IMEContext& aContext, + const mozilla::WritingMode& aWritingMode, + bool aForceUpdate = false); + + /** + * MaybeAdjustCompositionFont() calls AdjustCompositionFont() when the + * locale of active IME is CJK. Note that this creates an instance even + * when there is no composition but the locale is CJK. + */ + static void MaybeAdjustCompositionFont( + nsWindow* aWindow, + const mozilla::WritingMode& aWritingMode, + bool aForceUpdate = false); + + /** + * Get the current target clause of composition string. + * If there are one or more characters whose attribute is ATTR_TARGET_*, + * this returns the first character's offset and its length. + * Otherwise, e.g., the all characters are ATTR_INPUT, this returns + * the composition string range because the all is the current target. + * + * aLength can be null (default), but aOffset must not be null. + * + * The aOffset value is offset in the contents. So, when you need offset + * in the composition string, you need to subtract mCompositionStart from it. + */ + bool GetTargetClauseRange(uint32_t *aOffset, uint32_t *aLength = nullptr); + + /** + * DispatchEvent() dispatches aEvent if aWidget hasn't been destroyed yet. + */ + static void DispatchEvent(nsWindow* aWindow, WidgetGUIEvent& aEvent); + + /** + * DispatchCompositionChangeEvent() dispatches eCompositionChange event + * with clause information (it'll be retrieved by CreateTextRangeArray()). + * I.e., this should be called only during composing. If a composition is + * being committed, only HandleCompositionEnd() should be called. + * + * @param aWindow The window which has the composition. + * @param aContext Native IME context which has the composition. + */ + void DispatchCompositionChangeEvent(nsWindow* aWindow, + const IMEContext& aContext); + + nsresult EnsureClauseArray(int32_t aCount); + nsresult EnsureAttributeArray(int32_t aCount); + + /** + * When WM_IME_CHAR is received and passed to DefWindowProc, we need to + * record the messages. In other words, we should record the messages + * when we receive WM_IME_CHAR on windowless plug-in (if we have focus, + * we always eat them). When focus is moved from a windowless plug-in to + * our window during composition, WM_IME_CHAR messages were received when + * the plug-in has focus. However, WM_CHAR messages are received after the + * plug-in lost focus. So, we need to ignore the WM_CHAR messages because + * they make unexpected text input events on us. + */ + nsTArray<MSG> mPassedIMEChar; + + bool IsIMECharRecordsEmpty() + { + return mPassedIMEChar.IsEmpty(); + } + void ResetIMECharRecords() + { + mPassedIMEChar.Clear(); + } + void DequeueIMECharRecords(WPARAM &wParam, LPARAM &lParam) + { + MSG msg = mPassedIMEChar.ElementAt(0); + wParam = msg.wParam; + lParam = msg.lParam; + mPassedIMEChar.RemoveElementAt(0); + } + void EnqueueIMECharRecords(WPARAM wParam, LPARAM lParam) + { + MSG msg; + msg.wParam = wParam; + msg.lParam = lParam; + mPassedIMEChar.AppendElement(msg); + } + + TextEventDispatcher* GetTextEventDispatcherFor(nsWindow* aWindow); + + nsWindow* mComposingWindow; + RefPtr<TextEventDispatcher> mDispatcher; + nsString mCompositionString; + InfallibleTArray<uint32_t> mClauseArray; + InfallibleTArray<uint8_t> mAttributeArray; + + int32_t mCursorPosition; + uint32_t mCompositionStart; + + struct Selection + { + nsString mString; + uint32_t mOffset; + mozilla::WritingMode mWritingMode; + bool mIsValid; + + Selection() + : mOffset(UINT32_MAX) + , mIsValid(false) + { + } + + void Clear() + { + mOffset = UINT32_MAX; + mIsValid = false; + } + uint32_t Length() const { return mString.Length(); } + bool Collapsed() const { return !Length(); } + + bool IsValid() const; + bool Update(const IMENotification& aIMENotification); + bool Init(nsWindow* aWindow); + bool EnsureValidSelection(nsWindow* aWindow); + private: + Selection(const Selection& aOther) = delete; + void operator =(const Selection& aOther) = delete; + }; + // mSelection stores the latest selection data only when sHasFocus is true. + // Don't access mSelection directly. You should use GetSelection() for + // getting proper state. + Selection mSelection; + + Selection& GetSelection() + { + // When IME has focus, mSelection is automatically updated by + // NOTIFY_IME_OF_SELECTION_CHANGE. + if (sHasFocus) { + return mSelection; + } + // Otherwise, i.e., While IME doesn't have focus, we cannot observe + // selection changes. So, in such case, we need to query selection + // when it's necessary. + static Selection sTempSelection; + sTempSelection.Clear(); + return sTempSelection; + } + + bool mIsComposing; + bool mIsComposingOnPlugin; + bool mNativeCaretIsCreated; + + static mozilla::WritingMode sWritingModeOfCompositionFont; + static nsString sIMEName; + static UINT sCodePage; + static DWORD sIMEProperty; + static DWORD sIMEUIProperty; + static bool sAssumeVerticalWritingModeNotSupported; + static bool sHasFocus; + static bool sNativeCaretIsCreatedForPlugin; +}; + +} // namespace widget +} // namespace mozilla + +#endif // IMMHandler_h_ diff --git a/widget/windows/InProcessWinCompositorWidget.cpp b/widget/windows/InProcessWinCompositorWidget.cpp new file mode 100644 index 0000000000..685eaf5ca6 --- /dev/null +++ b/widget/windows/InProcessWinCompositorWidget.cpp @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "InProcessWinCompositorWidget.h" +#include "nsWindow.h" + +namespace mozilla { +namespace widget { + +/* static */ RefPtr<CompositorWidget> +CompositorWidget::CreateLocal(const CompositorWidgetInitData& aInitData, nsIWidget* aWidget) +{ + return new InProcessWinCompositorWidget(aInitData, static_cast<nsWindow*>(aWidget)); +} + +InProcessWinCompositorWidget::InProcessWinCompositorWidget(const CompositorWidgetInitData& aInitData, + nsWindow* aWindow) + : WinCompositorWidget(aInitData), + mWindow(aWindow) +{ + MOZ_ASSERT(mWindow); +} + +nsIWidget* +InProcessWinCompositorWidget::RealWidget() +{ + return mWindow; +} + +void +InProcessWinCompositorWidget::ObserveVsync(VsyncObserver* aObserver) +{ + if (RefPtr<CompositorVsyncDispatcher> cvd = mWindow->GetCompositorVsyncDispatcher()) { + cvd->SetCompositorVsyncObserver(aObserver); + } +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/windows/InProcessWinCompositorWidget.h b/widget/windows/InProcessWinCompositorWidget.h new file mode 100644 index 0000000000..2ce6ba0be1 --- /dev/null +++ b/widget/windows/InProcessWinCompositorWidget.h @@ -0,0 +1,35 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef widget_windows_InProcessCompositorWidgetParent_h +#define widget_windows_InProcessCompositorWidgetParent_h + +#include "WinCompositorWidget.h" + +class nsWindow; + +namespace mozilla { +namespace widget { + +// This is the Windows-specific implementation of CompositorWidget. For +// the most part it only requires an HWND, however it maintains extra state +// for transparent windows, as well as for synchronizing WM_SETTEXT messages +// with the compositor. +class InProcessWinCompositorWidget final : public WinCompositorWidget +{ +public: + InProcessWinCompositorWidget(const CompositorWidgetInitData& aInitData, nsWindow* aWindow); + + void ObserveVsync(VsyncObserver* aObserver) override; + nsIWidget* RealWidget() override; + +private: + nsWindow* mWindow; +}; + +} // namespace widget +} // namespace mozilla + +#endif // widget_windows_InProcessCompositorWidgetParent_h diff --git a/widget/windows/InkCollector.cpp b/widget/windows/InkCollector.cpp new file mode 100644 index 0000000000..5383dda7c1 --- /dev/null +++ b/widget/windows/InkCollector.cpp @@ -0,0 +1,253 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- +* vim: set ts=2 sw=2 et tw=78: +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#include "InkCollector.h" + +// Msinkaut_i.c and Msinkaut.h should both be included +// https://msdn.microsoft.com/en-us/library/windows/desktop/ms695519.aspx +#include <msinkaut_i.c> + +StaticAutoPtr<InkCollector> InkCollector::sInkCollector; + +InkCollector::~InkCollector() +{ + Shutdown(); + MOZ_ASSERT(!mCookie && !mEnabled && !mComInitialized + && !mMarshaller && !mInkCollector + && !mConnectionPoint && !mInkCollectorEvent); +} + +void InkCollector::Initialize() +{ + // Possibly, we can use mConnectionPoint for checking, + // But if errors exist (perhaps COM object is unavailable), + // Initialize() will be called more times. + static bool sInkCollectorCreated = false; + if (sInkCollectorCreated) { + return; + } + sInkCollectorCreated = true; + + // COM could get uninitialized due to previous initialization. + mComInitialized = SUCCEEDED(::CoInitialize(nullptr)); + + // Set up instance of InkCollectorEvent. + mInkCollectorEvent = new InkCollectorEvent(); + + // Set up a free threaded marshaler. + if (FAILED(::CoCreateFreeThreadedMarshaler(mInkCollectorEvent, getter_AddRefs(mMarshaller)))) { + return; + } + + // Create the ink collector. + if (FAILED(::CoCreateInstance(CLSID_InkCollector, NULL, CLSCTX_INPROC_SERVER, + IID_IInkCollector, getter_AddRefs(mInkCollector)))) { + return; + } + + // Set up connection between sink and InkCollector. + RefPtr<IConnectionPointContainer> connPointContainer; + + // Get the connection point container. + if (SUCCEEDED(mInkCollector->QueryInterface(IID_IConnectionPointContainer, + getter_AddRefs(connPointContainer)))) { + + // Find the connection point for Ink Collector events. + if (SUCCEEDED(connPointContainer->FindConnectionPoint(__uuidof(_IInkCollectorEvents), + getter_AddRefs(mConnectionPoint)))) { + + // Hook up sink to connection point. + if (SUCCEEDED(mConnectionPoint->Advise(mInkCollectorEvent, &mCookie))) { + OnInitialize(); + } + } + } +} + +void InkCollector::Shutdown() +{ + Enable(false); + if (mConnectionPoint) { + // Remove the connection of the sink to the Ink Collector. + mConnectionPoint->Unadvise(mCookie); + mCookie = 0; + mConnectionPoint = nullptr; + } + mInkCollector = nullptr; + mMarshaller = nullptr; + mInkCollectorEvent = nullptr; + + // Let uninitialization get handled in a place where it got inited. + if (mComInitialized) { + CoUninitialize(); + mComInitialized = false; + } +} + +void InkCollector::OnInitialize() +{ + // Suppress all events to do not allow performance decreasing. + // https://msdn.microsoft.com/en-us/library/ms820347.aspx + mInkCollector->SetEventInterest(InkCollectorEventInterest::ICEI_AllEvents, VARIANT_FALSE); + + // Sets a value that indicates whether an object or control has interest in a specified event. + mInkCollector->SetEventInterest(InkCollectorEventInterest::ICEI_CursorOutOfRange, VARIANT_TRUE); + + // If the MousePointer property is set to IMP_Custom and the MouseIcon property is NULL, + // Then the ink collector no longer handles mouse cursor settings. + // https://msdn.microsoft.com/en-us/library/windows/desktop/ms700686.aspx + mInkCollector->put_MouseIcon(nullptr); + mInkCollector->put_MousePointer(InkMousePointer::IMP_Custom); + + // This mode allows an ink collector to collect ink from any tablet attached to the Tablet PC. + // The Boolean value that indicates whether to use the mouse as an input device. + // If TRUE, the mouse is used for input. + // https://msdn.microsoft.com/en-us/library/ms820346.aspx + mInkCollector->SetAllTabletsMode(VARIANT_FALSE); + + // Sets the value that specifies whether ink is rendered as it is drawn. + // VARIANT_TRUE to render ink as it is drawn on the display. + // VARIANT_FALSE to not have the ink appear on the display as strokes are made. + // https://msdn.microsoft.com/en-us/library/windows/desktop/dd314598.aspx + mInkCollector->put_DynamicRendering(VARIANT_FALSE); +} + +// Sets a value that specifies whether the InkCollector object collects pen input. +// This property must be set to FALSE before setting or +// calling specific properties and methods of the object. +// https://msdn.microsoft.com/en-us/library/windows/desktop/ms701721.aspx +void InkCollector::Enable(bool aNewState) +{ + if (aNewState != mEnabled) { + if (mInkCollector) { + if (SUCCEEDED(mInkCollector->put_Enabled(aNewState ? VARIANT_TRUE : VARIANT_FALSE))) { + mEnabled = aNewState; + } else { + NS_WARNING("InkCollector did not change status successfully"); + } + } else { + NS_WARNING("InkCollector should be exist"); + } + } +} + +HWND InkCollector::GetTarget() +{ + return mTargetWindow; +} + +void InkCollector::SetTarget(HWND aTargetWindow) +{ + NS_ASSERTION(aTargetWindow, "aTargetWindow should be exist"); + if (aTargetWindow && (aTargetWindow != mTargetWindow)) { + Initialize(); + if (mInkCollector) { + Enable(false); + if (SUCCEEDED(mInkCollector->put_hWnd((LONG_PTR)aTargetWindow))) { + mTargetWindow = aTargetWindow; + } else { + NS_WARNING("InkCollector did not change window property successfully"); + } + Enable(true); + } + } +} + +void InkCollector::ClearTarget() +{ + if (mTargetWindow && mInkCollector) { + Enable(false); + if (SUCCEEDED(mInkCollector->put_hWnd(0))) { + mTargetWindow = 0; + } else { + NS_WARNING("InkCollector did not clear window property successfully"); + } + } +} + +uint16_t InkCollector::GetPointerId() +{ + return mPointerId; +} + +void InkCollector::SetPointerId(uint16_t aPointerId) +{ + mPointerId = aPointerId; +} + +void InkCollector::ClearPointerId() +{ + mPointerId = 0; +} + +// The display and the digitizer have quite different properties. +// The display has CursorMustTouch, the mouse pointer alway touches the display surface. +// The digitizer lists Integrated and HardProximity. +// When the stylus is in the proximity of the tablet its movements are also detected. +// An external tablet will only list HardProximity. +bool InkCollectorEvent::IsHardProximityTablet(IInkTablet* aTablet) const +{ + if (aTablet) { + TabletHardwareCapabilities caps; + if (SUCCEEDED(aTablet->get_HardwareCapabilities(&caps))) { + return (TabletHardwareCapabilities::THWC_HardProximity & caps); + } + } + return false; +} + +HRESULT __stdcall InkCollectorEvent::QueryInterface(REFIID aRiid, void **aObject) +{ + // Validate the input + if (!aObject) { + return E_POINTER; + } + HRESULT result = E_NOINTERFACE; + // This object supports IUnknown/IDispatch/IInkCollectorEvents + if ((IID_IUnknown == aRiid) || + (IID_IDispatch == aRiid) || + (DIID__IInkCollectorEvents == aRiid)) { + *aObject = this; + // AddRef should be called when we give info about interface + NS_ADDREF_THIS(); + result = S_OK; + } + return result; +} + +HRESULT InkCollectorEvent::Invoke(DISPID aDispIdMember, REFIID /*aRiid*/, + LCID /*aId*/, WORD /*wFlags*/, + DISPPARAMS* aDispParams, VARIANT* /*aVarResult*/, + EXCEPINFO* /*aExcepInfo*/, UINT* /*aArgErr*/) +{ + switch (aDispIdMember) { + case DISPID_ICECursorOutOfRange: { + if (aDispParams && aDispParams->cArgs) { + CursorOutOfRange(static_cast<IInkCursor*>(aDispParams->rgvarg[0].pdispVal)); + } + break; + } + } + return S_OK; +} + +void InkCollectorEvent::CursorOutOfRange(IInkCursor* aCursor) const +{ + IInkTablet* curTablet = nullptr; + if (FAILED(aCursor->get_Tablet(&curTablet))) { + return; + } + // All events should be suppressed except + // from tablets with hard proximity. + if (!IsHardProximityTablet(curTablet)) { + return; + } + // Notify current target window. + if (HWND targetWindow = InkCollector::sInkCollector->GetTarget()) { + ::SendMessage(targetWindow, MOZ_WM_PEN_LEAVES_HOVER_OF_DIGITIZER, 0, 0); + } +} diff --git a/widget/windows/InkCollector.h b/widget/windows/InkCollector.h new file mode 100644 index 0000000000..9570eea2a1 --- /dev/null +++ b/widget/windows/InkCollector.h @@ -0,0 +1,98 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- +* vim: set ts=2 sw=2 et tw=78: +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#ifndef InkCollector_h__ +#define InkCollector_h__ + +#include <msinkaut.h> +#include "mozilla/StaticPtr.h" + +#define MOZ_WM_PEN_LEAVES_HOVER_OF_DIGITIZER WM_USER + 0x83 + +class InkCollectorEvent : public _IInkCollectorEvents +{ +public: + // IUnknown + HRESULT __stdcall QueryInterface(REFIID aRiid, void **aObject); + virtual ULONG STDMETHODCALLTYPE AddRef() { return ++mRefCount; } + virtual ULONG STDMETHODCALLTYPE Release() + { + MOZ_ASSERT(mRefCount); + if (!--mRefCount) { + delete this; + return 0; + } + return mRefCount; + } + +protected: + // IDispatch + STDMETHOD(GetTypeInfoCount)(UINT* aInfo) { return E_NOTIMPL; } + STDMETHOD(GetTypeInfo)(UINT aInfo, LCID aId, ITypeInfo** aTInfo) { return E_NOTIMPL; } + STDMETHOD(GetIDsOfNames)(REFIID aRiid, LPOLESTR* aStrNames, UINT aNames, + LCID aId, DISPID* aDispId) { return E_NOTIMPL; } + STDMETHOD(Invoke)(DISPID aDispIdMember, REFIID aRiid, + LCID aId, WORD wFlags, + DISPPARAMS* aDispParams, VARIANT* aVarResult, + EXCEPINFO* aExcepInfo, UINT* aArgErr); + + // InkCollectorEvent + void CursorOutOfRange(IInkCursor* aCursor) const; + bool IsHardProximityTablet(IInkTablet* aTablet) const; + +private: + uint32_t mRefCount = 0; +}; + +class InkCollector +{ +public: + ~InkCollector(); + void Shutdown(); + + HWND GetTarget(); + void SetTarget(HWND aTargetWindow); + void ClearTarget(); + + uint16_t GetPointerId(); // 0 shows that there is no existing pen. + void SetPointerId(uint16_t aPointerId); + void ClearPointerId(); + + static StaticAutoPtr<InkCollector> sInkCollector; + +protected: + void Initialize(); + void OnInitialize(); + void Enable(bool aNewState); + +private: + RefPtr<IUnknown> mMarshaller; + RefPtr<IInkCollector> mInkCollector; + RefPtr<IConnectionPoint> mConnectionPoint; + RefPtr<InkCollectorEvent> mInkCollectorEvent; + + HWND mTargetWindow = 0; + DWORD mCookie = 0; + bool mComInitialized = false; + bool mEnabled = false; + + // This value holds the previous pointerId of the pen, and is used by the + // nsWindow when processing a MOZ_WM_PEN_LEAVES_HOVER_OF_DIGITIZER which + // indicates that a pen leaves the digitizer. + + // TODO: If we move our implementation to window pointer input messages, then + // we no longer need this value, since the pointerId can be retrieved from the + // window message, please refer to + // https://msdn.microsoft.com/en-us/library/windows/desktop/hh454916(v=vs.85).aspx + + // NOTE: The pointerId of a pen shouldn't be 0 on a Windows platform, since 0 + // is reserved of the mouse, please refer to + // https://msdn.microsoft.com/en-us/library/windows/desktop/ms703320(v=vs.85).aspx + uint16_t mPointerId = 0; +}; + +#endif // InkCollector_h__ diff --git a/widget/windows/JumpListBuilder.cpp b/widget/windows/JumpListBuilder.cpp new file mode 100644 index 0000000000..566c41d4ab --- /dev/null +++ b/widget/windows/JumpListBuilder.cpp @@ -0,0 +1,542 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "JumpListBuilder.h" + +#include "nsError.h" +#include "nsCOMPtr.h" +#include "nsServiceManagerUtils.h" +#include "nsString.h" +#include "nsArrayUtils.h" +#include "nsIMutableArray.h" +#include "nsWidgetsCID.h" +#include "WinTaskbar.h" +#include "nsDirectoryServiceUtils.h" +#include "nsISimpleEnumerator.h" +#include "mozilla/Preferences.h" +#include "nsStringStream.h" +#include "nsThreadUtils.h" +#include "mozilla/LazyIdleThread.h" +#include "nsIObserverService.h" + +#include "WinUtils.h" + +// The amount of time, in milliseconds, that our IO thread will stay alive after the last event it processes. +#define DEFAULT_THREAD_TIMEOUT_MS 30000 + +namespace mozilla { +namespace widget { + +static NS_DEFINE_CID(kJumpListItemCID, NS_WIN_JUMPLISTITEM_CID); +static NS_DEFINE_CID(kJumpListLinkCID, NS_WIN_JUMPLISTLINK_CID); +static NS_DEFINE_CID(kJumpListShortcutCID, NS_WIN_JUMPLISTSHORTCUT_CID); + +// defined in WinTaskbar.cpp +extern const wchar_t *gMozillaJumpListIDGeneric; + +bool JumpListBuilder::sBuildingList = false; +const char kPrefTaskbarEnabled[] = "browser.taskbar.lists.enabled"; + +NS_IMPL_ISUPPORTS(JumpListBuilder, nsIJumpListBuilder, nsIObserver) +#define TOPIC_PROFILE_BEFORE_CHANGE "profile-before-change" +#define TOPIC_CLEAR_PRIVATE_DATA "clear-private-data" + +JumpListBuilder::JumpListBuilder() : + mMaxItems(0), + mHasCommit(false) +{ + ::CoInitialize(nullptr); + + CoCreateInstance(CLSID_DestinationList, nullptr, CLSCTX_INPROC_SERVER, + IID_ICustomDestinationList, getter_AddRefs(mJumpListMgr)); + + // Make a lazy thread for any IO + mIOThread = new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS, + NS_LITERAL_CSTRING("Jump List"), + LazyIdleThread::ManualShutdown); + Preferences::AddStrongObserver(this, kPrefTaskbarEnabled); + + nsCOMPtr<nsIObserverService> observerService = + do_GetService("@mozilla.org/observer-service;1"); + if (observerService) { + observerService->AddObserver(this, TOPIC_PROFILE_BEFORE_CHANGE, false); + observerService->AddObserver(this, TOPIC_CLEAR_PRIVATE_DATA, false); + } +} + +JumpListBuilder::~JumpListBuilder() +{ + Preferences::RemoveObserver(this, kPrefTaskbarEnabled); + mJumpListMgr = nullptr; + ::CoUninitialize(); +} + +NS_IMETHODIMP JumpListBuilder::GetAvailable(int16_t *aAvailable) +{ + *aAvailable = false; + + if (mJumpListMgr) + *aAvailable = true; + + return NS_OK; +} + +NS_IMETHODIMP JumpListBuilder::GetIsListCommitted(bool *aCommit) +{ + *aCommit = mHasCommit; + + return NS_OK; +} + +NS_IMETHODIMP JumpListBuilder::GetMaxListItems(int16_t *aMaxItems) +{ + if (!mJumpListMgr) + return NS_ERROR_NOT_AVAILABLE; + + *aMaxItems = 0; + + if (sBuildingList) { + *aMaxItems = mMaxItems; + return NS_OK; + } + + IObjectArray *objArray; + if (SUCCEEDED(mJumpListMgr->BeginList(&mMaxItems, IID_PPV_ARGS(&objArray)))) { + *aMaxItems = mMaxItems; + + if (objArray) + objArray->Release(); + + mJumpListMgr->AbortList(); + } + + return NS_OK; +} + +NS_IMETHODIMP JumpListBuilder::InitListBuild(nsIMutableArray *removedItems, bool *_retval) +{ + NS_ENSURE_ARG_POINTER(removedItems); + + *_retval = false; + + if (!mJumpListMgr) + return NS_ERROR_NOT_AVAILABLE; + + if(sBuildingList) + AbortListBuild(); + + IObjectArray *objArray; + + // The returned objArray of removed items are for manually removed items. + // This does not return items which are removed because they were previously + // part of the jump list but are no longer part of the jump list. + if (SUCCEEDED(mJumpListMgr->BeginList(&mMaxItems, IID_PPV_ARGS(&objArray)))) { + if (objArray) { + TransferIObjectArrayToIMutableArray(objArray, removedItems); + objArray->Release(); + } + + RemoveIconCacheForItems(removedItems); + + sBuildingList = true; + *_retval = true; + return NS_OK; + } + + return NS_OK; +} + +// Ensures that we don't have old ICO files that aren't in our jump lists +// anymore left over in the cache. +nsresult JumpListBuilder::RemoveIconCacheForItems(nsIMutableArray *items) +{ + NS_ENSURE_ARG_POINTER(items); + + nsresult rv; + uint32_t length; + items->GetLength(&length); + for (uint32_t i = 0; i < length; ++i) { + + //Obtain an IJumpListItem and get the type + nsCOMPtr<nsIJumpListItem> item = do_QueryElementAt(items, i); + if (!item) { + continue; + } + int16_t type; + if (NS_FAILED(item->GetType(&type))) { + continue; + } + + // If the item is a shortcut, remove its associated icon if any + if (type == nsIJumpListItem::JUMPLIST_ITEM_SHORTCUT) { + nsCOMPtr<nsIJumpListShortcut> shortcut = do_QueryInterface(item); + if (shortcut) { + nsCOMPtr<nsIURI> uri; + rv = shortcut->GetFaviconPageUri(getter_AddRefs(uri)); + if (NS_SUCCEEDED(rv) && uri) { + + // The local file path is stored inside the nsIURI + // Get the nsIURI spec which stores the local path for the icon to remove + nsAutoCString spec; + nsresult rv = uri->GetSpec(spec); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIRunnable> event + = new mozilla::widget::AsyncDeleteIconFromDisk(NS_ConvertUTF8toUTF16(spec)); + mIOThread->Dispatch(event, NS_DISPATCH_NORMAL); + + // The shortcut was generated from an IShellLinkW so IShellLinkW can + // only tell us what the original icon is and not the URI. + // So this field was used only temporarily as the actual icon file + // path. It should be cleared. + shortcut->SetFaviconPageUri(nullptr); + } + } + } + + } // end for + + return NS_OK; +} + +// Ensures that we have no old ICO files left in the jump list cache +nsresult JumpListBuilder::RemoveIconCacheForAllItems() +{ + // Construct the path of our jump list cache + nsCOMPtr<nsIFile> jumpListCacheDir; + nsresult rv = NS_GetSpecialDirectory("ProfLDS", + getter_AddRefs(jumpListCacheDir)); + NS_ENSURE_SUCCESS(rv, rv); + rv = jumpListCacheDir->AppendNative(nsDependentCString( + mozilla::widget::FaviconHelper::kJumpListCacheDir)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsISimpleEnumerator> entries; + rv = jumpListCacheDir->GetDirectoryEntries(getter_AddRefs(entries)); + NS_ENSURE_SUCCESS(rv, rv); + + // Loop through each directory entry and remove all ICO files found + do { + bool hasMore = false; + if (NS_FAILED(entries->HasMoreElements(&hasMore)) || !hasMore) + break; + + nsCOMPtr<nsISupports> supp; + if (NS_FAILED(entries->GetNext(getter_AddRefs(supp)))) + break; + + nsCOMPtr<nsIFile> currFile(do_QueryInterface(supp)); + nsAutoString path; + if (NS_FAILED(currFile->GetPath(path))) + continue; + + if (StringTail(path, 4).LowerCaseEqualsASCII(".ico")) { + // Check if the cached ICO file exists + bool exists; + if (NS_FAILED(currFile->Exists(&exists)) || !exists) + continue; + + // We found an ICO file that exists, so we should remove it + currFile->Remove(false); + } + } while(true); + + return NS_OK; +} + +NS_IMETHODIMP JumpListBuilder::AddListToBuild(int16_t aCatType, nsIArray *items, const nsAString &catName, bool *_retval) +{ + nsresult rv; + + *_retval = false; + + if (!mJumpListMgr) + return NS_ERROR_NOT_AVAILABLE; + + switch(aCatType) { + case nsIJumpListBuilder::JUMPLIST_CATEGORY_TASKS: + { + NS_ENSURE_ARG_POINTER(items); + + HRESULT hr; + RefPtr<IObjectCollection> collection; + hr = CoCreateInstance(CLSID_EnumerableObjectCollection, nullptr, + CLSCTX_INPROC_SERVER, IID_IObjectCollection, + getter_AddRefs(collection)); + if (FAILED(hr)) + return NS_ERROR_UNEXPECTED; + + // Build the list + uint32_t length; + items->GetLength(&length); + for (uint32_t i = 0; i < length; ++i) { + nsCOMPtr<nsIJumpListItem> item = do_QueryElementAt(items, i); + if (!item) + continue; + // Check for separators + if (IsSeparator(item)) { + RefPtr<IShellLinkW> link; + rv = JumpListSeparator::GetSeparator(link); + if (NS_FAILED(rv)) + return rv; + collection->AddObject(link); + continue; + } + // These should all be ShellLinks + RefPtr<IShellLinkW> link; + rv = JumpListShortcut::GetShellLink(item, link, mIOThread); + if (NS_FAILED(rv)) + return rv; + collection->AddObject(link); + } + + // We need IObjectArray to submit + RefPtr<IObjectArray> pArray; + hr = collection->QueryInterface(IID_IObjectArray, getter_AddRefs(pArray)); + if (FAILED(hr)) + return NS_ERROR_UNEXPECTED; + + // Add the tasks + hr = mJumpListMgr->AddUserTasks(pArray); + if (SUCCEEDED(hr)) + *_retval = true; + return NS_OK; + } + break; + case nsIJumpListBuilder::JUMPLIST_CATEGORY_RECENT: + { + if (SUCCEEDED(mJumpListMgr->AppendKnownCategory(KDC_RECENT))) + *_retval = true; + return NS_OK; + } + break; + case nsIJumpListBuilder::JUMPLIST_CATEGORY_FREQUENT: + { + if (SUCCEEDED(mJumpListMgr->AppendKnownCategory(KDC_FREQUENT))) + *_retval = true; + return NS_OK; + } + break; + case nsIJumpListBuilder::JUMPLIST_CATEGORY_CUSTOMLIST: + { + NS_ENSURE_ARG_POINTER(items); + + if (catName.IsEmpty()) + return NS_ERROR_INVALID_ARG; + + HRESULT hr; + RefPtr<IObjectCollection> collection; + hr = CoCreateInstance(CLSID_EnumerableObjectCollection, nullptr, + CLSCTX_INPROC_SERVER, IID_IObjectCollection, + getter_AddRefs(collection)); + if (FAILED(hr)) + return NS_ERROR_UNEXPECTED; + + uint32_t length; + items->GetLength(&length); + for (uint32_t i = 0; i < length; ++i) { + nsCOMPtr<nsIJumpListItem> item = do_QueryElementAt(items, i); + if (!item) + continue; + int16_t type; + if (NS_FAILED(item->GetType(&type))) + continue; + switch(type) { + case nsIJumpListItem::JUMPLIST_ITEM_SEPARATOR: + { + RefPtr<IShellLinkW> shellItem; + rv = JumpListSeparator::GetSeparator(shellItem); + if (NS_FAILED(rv)) + return rv; + collection->AddObject(shellItem); + } + break; + case nsIJumpListItem::JUMPLIST_ITEM_LINK: + { + RefPtr<IShellItem2> shellItem; + rv = JumpListLink::GetShellItem(item, shellItem); + if (NS_FAILED(rv)) + return rv; + collection->AddObject(shellItem); + } + break; + case nsIJumpListItem::JUMPLIST_ITEM_SHORTCUT: + { + RefPtr<IShellLinkW> shellItem; + rv = JumpListShortcut::GetShellLink(item, shellItem, mIOThread); + if (NS_FAILED(rv)) + return rv; + collection->AddObject(shellItem); + } + break; + } + } + + // We need IObjectArray to submit + RefPtr<IObjectArray> pArray; + hr = collection->QueryInterface(IID_IObjectArray, (LPVOID*)&pArray); + if (FAILED(hr)) + return NS_ERROR_UNEXPECTED; + + // Add the tasks + hr = mJumpListMgr->AppendCategory(reinterpret_cast<const wchar_t*>(catName.BeginReading()), pArray); + if (SUCCEEDED(hr)) + *_retval = true; + + // Get rid of the old icons + nsCOMPtr<nsIRunnable> event = + new mozilla::widget::AsyncDeleteAllFaviconsFromDisk(true); + mIOThread->Dispatch(event, NS_DISPATCH_NORMAL); + + return NS_OK; + } + break; + } + return NS_OK; +} + +NS_IMETHODIMP JumpListBuilder::AbortListBuild() +{ + if (!mJumpListMgr) + return NS_ERROR_NOT_AVAILABLE; + + mJumpListMgr->AbortList(); + sBuildingList = false; + + return NS_OK; +} + +NS_IMETHODIMP JumpListBuilder::CommitListBuild(bool *_retval) +{ + *_retval = false; + + if (!mJumpListMgr) + return NS_ERROR_NOT_AVAILABLE; + + HRESULT hr = mJumpListMgr->CommitList(); + sBuildingList = false; + + // XXX We might want some specific error data here. + if (SUCCEEDED(hr)) { + *_retval = true; + mHasCommit = true; + } + + return NS_OK; +} + +NS_IMETHODIMP JumpListBuilder::DeleteActiveList(bool *_retval) +{ + *_retval = false; + + if (!mJumpListMgr) + return NS_ERROR_NOT_AVAILABLE; + + if(sBuildingList) + AbortListBuild(); + + nsAutoString uid; + if (!WinTaskbar::GetAppUserModelID(uid)) + return NS_OK; + + if (SUCCEEDED(mJumpListMgr->DeleteList(uid.get()))) + *_retval = true; + + return NS_OK; +} + +/* internal */ + +bool JumpListBuilder::IsSeparator(nsCOMPtr<nsIJumpListItem>& item) +{ + int16_t type; + item->GetType(&type); + if (NS_FAILED(item->GetType(&type))) + return false; + + if (type == nsIJumpListItem::JUMPLIST_ITEM_SEPARATOR) + return true; + return false; +} + +// TransferIObjectArrayToIMutableArray - used in converting removed items +// to our objects. +nsresult JumpListBuilder::TransferIObjectArrayToIMutableArray(IObjectArray *objArray, nsIMutableArray *removedItems) +{ + NS_ENSURE_ARG_POINTER(objArray); + NS_ENSURE_ARG_POINTER(removedItems); + + nsresult rv; + + uint32_t count = 0; + objArray->GetCount(&count); + + nsCOMPtr<nsIJumpListItem> item; + + for (uint32_t idx = 0; idx < count; idx++) { + IShellLinkW * pLink = nullptr; + IShellItem * pItem = nullptr; + + if (SUCCEEDED(objArray->GetAt(idx, IID_IShellLinkW, (LPVOID*)&pLink))) { + nsCOMPtr<nsIJumpListShortcut> shortcut = + do_CreateInstance(kJumpListShortcutCID, &rv); + if (NS_FAILED(rv)) + return NS_ERROR_UNEXPECTED; + rv = JumpListShortcut::GetJumpListShortcut(pLink, shortcut); + item = do_QueryInterface(shortcut); + } + else if (SUCCEEDED(objArray->GetAt(idx, IID_IShellItem, (LPVOID*)&pItem))) { + nsCOMPtr<nsIJumpListLink> link = + do_CreateInstance(kJumpListLinkCID, &rv); + if (NS_FAILED(rv)) + return NS_ERROR_UNEXPECTED; + rv = JumpListLink::GetJumpListLink(pItem, link); + item = do_QueryInterface(link); + } + + if (pLink) + pLink->Release(); + if (pItem) + pItem->Release(); + + if (NS_SUCCEEDED(rv)) { + removedItems->AppendElement(item, false); + } + } + return NS_OK; +} + +NS_IMETHODIMP JumpListBuilder::Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t* aData) +{ + NS_ENSURE_ARG_POINTER(aTopic); + if (strcmp(aTopic, TOPIC_PROFILE_BEFORE_CHANGE) == 0) { + nsCOMPtr<nsIObserverService> observerService = + do_GetService("@mozilla.org/observer-service;1"); + if (observerService) { + observerService->RemoveObserver(this, TOPIC_PROFILE_BEFORE_CHANGE); + } + mIOThread->Shutdown(); + } else if (strcmp(aTopic, "nsPref:changed") == 0 && + nsDependentString(aData).EqualsASCII(kPrefTaskbarEnabled)) { + bool enabled = Preferences::GetBool(kPrefTaskbarEnabled, true); + if (!enabled) { + + nsCOMPtr<nsIRunnable> event = + new mozilla::widget::AsyncDeleteAllFaviconsFromDisk(); + mIOThread->Dispatch(event, NS_DISPATCH_NORMAL); + } + } else if (strcmp(aTopic, TOPIC_CLEAR_PRIVATE_DATA) == 0) { + // Delete JumpListCache icons from Disk, if any. + nsCOMPtr<nsIRunnable> event = + new mozilla::widget::AsyncDeleteAllFaviconsFromDisk(false); + mIOThread->Dispatch(event, NS_DISPATCH_NORMAL); + } + return NS_OK; +} + +} // namespace widget +} // namespace mozilla + diff --git a/widget/windows/JumpListBuilder.h b/widget/windows/JumpListBuilder.h new file mode 100644 index 0000000000..553ff765d0 --- /dev/null +++ b/widget/windows/JumpListBuilder.h @@ -0,0 +1,62 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __JumpListBuilder_h__ +#define __JumpListBuilder_h__ + +#include <windows.h> + +#undef NTDDI_VERSION +#define NTDDI_VERSION NTDDI_WIN7 +// Needed for various com interfaces +#include <shobjidl.h> +#undef LogSeverity // SetupAPI.h #defines this as DWORD + +#include "nsString.h" +#include "nsIMutableArray.h" + +#include "nsIJumpListBuilder.h" +#include "nsIJumpListItem.h" +#include "JumpListItem.h" +#include "nsIObserver.h" +#include "mozilla/Attributes.h" + +namespace mozilla { +namespace widget { + +class JumpListBuilder : public nsIJumpListBuilder, + public nsIObserver +{ + virtual ~JumpListBuilder(); + +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIJUMPLISTBUILDER + NS_DECL_NSIOBSERVER + + JumpListBuilder(); + +protected: + static bool sBuildingList; + +private: + RefPtr<ICustomDestinationList> mJumpListMgr; + uint32_t mMaxItems; + bool mHasCommit; + nsCOMPtr<nsIThread> mIOThread; + + bool IsSeparator(nsCOMPtr<nsIJumpListItem>& item); + nsresult TransferIObjectArrayToIMutableArray(IObjectArray *objArray, nsIMutableArray *removedItems); + nsresult RemoveIconCacheForItems(nsIMutableArray *removedItems); + nsresult RemoveIconCacheForAllItems(); + + friend class WinTaskbar; +}; + +} // namespace widget +} // namespace mozilla + +#endif /* __JumpListBuilder_h__ */ + diff --git a/widget/windows/JumpListItem.cpp b/widget/windows/JumpListItem.cpp new file mode 100644 index 0000000000..57dff6466e --- /dev/null +++ b/widget/windows/JumpListItem.cpp @@ -0,0 +1,630 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "JumpListItem.h" + +#include <shellapi.h> +#include <propvarutil.h> +#include <propkey.h> + +#include "nsIFile.h" +#include "nsNetUtil.h" +#include "nsCRT.h" +#include "nsNetCID.h" +#include "nsCExternalHandlerService.h" +#include "nsCycleCollectionParticipant.h" +#include "mozilla/Preferences.h" +#include "JumpListBuilder.h" +#include "WinUtils.h" + +namespace mozilla { +namespace widget { + +// ISUPPORTS Impl's +NS_IMPL_ISUPPORTS(JumpListItem, + nsIJumpListItem) + +NS_IMPL_ISUPPORTS_INHERITED(JumpListSeparator, + JumpListItem, + nsIJumpListSeparator) + +NS_IMPL_ISUPPORTS_INHERITED(JumpListLink, + JumpListItem, + nsIJumpListLink) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(JumpListShortcut) + NS_INTERFACE_MAP_ENTRY(nsIJumpListShortcut) +NS_INTERFACE_MAP_END_INHERITING(JumpListItem) + +NS_IMPL_CYCLE_COLLECTION(JumpListShortcut, mHandlerApp) + +NS_IMPL_CYCLE_COLLECTING_ADDREF(JumpListShortcut) +NS_IMPL_CYCLE_COLLECTING_RELEASE(JumpListShortcut) + +NS_IMETHODIMP JumpListItem::GetType(int16_t *aType) +{ + NS_ENSURE_ARG_POINTER(aType); + + *aType = mItemType; + + return NS_OK; +} + +NS_IMETHODIMP JumpListItem::Equals(nsIJumpListItem *aItem, bool *aResult) +{ + NS_ENSURE_ARG_POINTER(aItem); + + *aResult = false; + + int16_t theType = nsIJumpListItem::JUMPLIST_ITEM_EMPTY; + if (NS_FAILED(aItem->GetType(&theType))) + return NS_OK; + + // Make sure the types match. + if (Type() != theType) + return NS_OK; + + *aResult = true; + + return NS_OK; +} + +/* link impl. */ + +NS_IMETHODIMP JumpListLink::GetUri(nsIURI **aURI) +{ + NS_IF_ADDREF(*aURI = mURI); + + return NS_OK; +} + +NS_IMETHODIMP JumpListLink::SetUri(nsIURI *aURI) +{ + mURI = aURI; + + return NS_OK; +} + +NS_IMETHODIMP JumpListLink::SetUriTitle(const nsAString &aUriTitle) +{ + mUriTitle.Assign(aUriTitle); + + return NS_OK; +} + +NS_IMETHODIMP JumpListLink::GetUriTitle(nsAString& aUriTitle) +{ + aUriTitle.Assign(mUriTitle); + + return NS_OK; +} + +NS_IMETHODIMP JumpListLink::GetUriHash(nsACString& aUriHash) +{ + if (!mURI) + return NS_ERROR_NOT_AVAILABLE; + + return mozilla::widget::FaviconHelper::HashURI(mCryptoHash, mURI, aUriHash); +} + +NS_IMETHODIMP JumpListLink::CompareHash(nsIURI *aUri, bool *aResult) +{ + nsresult rv; + + if (!mURI) { + *aResult = !aUri; + return NS_OK; + } + + NS_ENSURE_ARG_POINTER(aUri); + + nsAutoCString hash1, hash2; + + rv = mozilla::widget::FaviconHelper::HashURI(mCryptoHash, mURI, hash1); + NS_ENSURE_SUCCESS(rv, rv); + rv = mozilla::widget::FaviconHelper::HashURI(mCryptoHash, aUri, hash2); + NS_ENSURE_SUCCESS(rv, rv); + + *aResult = hash1.Equals(hash2); + + return NS_OK; +} + +NS_IMETHODIMP JumpListLink::Equals(nsIJumpListItem *aItem, bool *aResult) +{ + NS_ENSURE_ARG_POINTER(aItem); + + nsresult rv; + + *aResult = false; + + int16_t theType = nsIJumpListItem::JUMPLIST_ITEM_EMPTY; + if (NS_FAILED(aItem->GetType(&theType))) + return NS_OK; + + // Make sure the types match. + if (Type() != theType) + return NS_OK; + + nsCOMPtr<nsIJumpListLink> link = do_QueryInterface(aItem, &rv); + if (NS_FAILED(rv)) + return rv; + + // Check the titles + nsAutoString title; + link->GetUriTitle(title); + if (!mUriTitle.Equals(title)) + return NS_OK; + + // Call the internal object's equals() method to check. + nsCOMPtr<nsIURI> theUri; + bool equals = false; + if (NS_SUCCEEDED(link->GetUri(getter_AddRefs(theUri)))) { + if (!theUri) { + if (!mURI) + *aResult = true; + return NS_OK; + } + if (NS_SUCCEEDED(theUri->Equals(mURI, &equals)) && equals) { + *aResult = true; + } + } + + return NS_OK; +} + +/* shortcut impl. */ + +NS_IMETHODIMP JumpListShortcut::GetApp(nsILocalHandlerApp **aApp) +{ + NS_IF_ADDREF(*aApp = mHandlerApp); + + return NS_OK; +} + +NS_IMETHODIMP JumpListShortcut::SetApp(nsILocalHandlerApp *aApp) +{ + mHandlerApp = aApp; + + // Confirm the app is present on the system + if (!ExecutableExists(mHandlerApp)) + return NS_ERROR_FILE_NOT_FOUND; + + return NS_OK; +} + +NS_IMETHODIMP JumpListShortcut::GetIconIndex(int32_t *aIconIndex) +{ + NS_ENSURE_ARG_POINTER(aIconIndex); + + *aIconIndex = mIconIndex; + return NS_OK; +} + +NS_IMETHODIMP JumpListShortcut::SetIconIndex(int32_t aIconIndex) +{ + mIconIndex = aIconIndex; + return NS_OK; +} + +NS_IMETHODIMP JumpListShortcut::GetFaviconPageUri(nsIURI **aFaviconPageURI) +{ + NS_IF_ADDREF(*aFaviconPageURI = mFaviconPageURI); + + return NS_OK; +} + +NS_IMETHODIMP JumpListShortcut::SetFaviconPageUri(nsIURI *aFaviconPageURI) +{ + mFaviconPageURI = aFaviconPageURI; + return NS_OK; +} + +NS_IMETHODIMP JumpListShortcut::Equals(nsIJumpListItem *aItem, bool *aResult) +{ + NS_ENSURE_ARG_POINTER(aItem); + + nsresult rv; + + *aResult = false; + + int16_t theType = nsIJumpListItem::JUMPLIST_ITEM_EMPTY; + if (NS_FAILED(aItem->GetType(&theType))) + return NS_OK; + + // Make sure the types match. + if (Type() != theType) + return NS_OK; + + nsCOMPtr<nsIJumpListShortcut> shortcut = do_QueryInterface(aItem, &rv); + if (NS_FAILED(rv)) + return rv; + + // Check the icon index + //int32_t idx; + //shortcut->GetIconIndex(&idx); + //if (mIconIndex != idx) + // return NS_OK; + // No need to check the icon page URI either + + // Call the internal object's equals() method to check. + nsCOMPtr<nsILocalHandlerApp> theApp; + bool equals = false; + if (NS_SUCCEEDED(shortcut->GetApp(getter_AddRefs(theApp)))) { + if (!theApp) { + if (!mHandlerApp) + *aResult = true; + return NS_OK; + } + if (NS_SUCCEEDED(theApp->Equals(mHandlerApp, &equals)) && equals) { + *aResult = true; + } + } + + return NS_OK; +} + +/* internal helpers */ + +// (static) Creates a ShellLink that encapsulate a separator. +nsresult JumpListSeparator::GetSeparator(RefPtr<IShellLinkW>& aShellLink) +{ + HRESULT hr; + IShellLinkW* psl; + + // Create a IShellLink. + hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, + IID_IShellLinkW, (LPVOID*)&psl); + if (FAILED(hr)) + return NS_ERROR_UNEXPECTED; + + IPropertyStore* pPropStore = nullptr; + hr = psl->QueryInterface(IID_IPropertyStore, (LPVOID*)&pPropStore); + if (FAILED(hr)) + return NS_ERROR_UNEXPECTED; + + PROPVARIANT pv; + InitPropVariantFromBoolean(TRUE, &pv); + + pPropStore->SetValue(PKEY_AppUserModel_IsDestListSeparator, pv); + pPropStore->Commit(); + pPropStore->Release(); + + PropVariantClear(&pv); + + aShellLink = dont_AddRef(psl); + + return NS_OK; +} + +// (static) Creates a ShellLink that encapsulate a shortcut to local apps. +nsresult JumpListShortcut::GetShellLink(nsCOMPtr<nsIJumpListItem>& item, + RefPtr<IShellLinkW>& aShellLink, + nsCOMPtr<nsIThread> &aIOThread) +{ + HRESULT hr; + IShellLinkW* psl; + nsresult rv; + + // Shell links: + // http://msdn.microsoft.com/en-us/library/bb776891(VS.85).aspx + // http://msdn.microsoft.com/en-us/library/bb774950(VS.85).aspx + + int16_t type; + if (NS_FAILED(item->GetType(&type))) + return NS_ERROR_INVALID_ARG; + + if (type != nsIJumpListItem::JUMPLIST_ITEM_SHORTCUT) + return NS_ERROR_INVALID_ARG; + + nsCOMPtr<nsIJumpListShortcut> shortcut = do_QueryInterface(item, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsILocalHandlerApp> handlerApp; + rv = shortcut->GetApp(getter_AddRefs(handlerApp)); + NS_ENSURE_SUCCESS(rv, rv); + + // Create a IShellLink + hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, + IID_IShellLinkW, (LPVOID*)&psl); + if (FAILED(hr)) + return NS_ERROR_UNEXPECTED; + + // Retrieve the app path, title, description and optional command line args. + nsAutoString appPath, appTitle, appDescription, appArgs; + int32_t appIconIndex = 0; + + // Path + nsCOMPtr<nsIFile> executable; + handlerApp->GetExecutable(getter_AddRefs(executable)); + + rv = executable->GetPath(appPath); + NS_ENSURE_SUCCESS(rv, rv); + + // Command line parameters + uint32_t count = 0; + handlerApp->GetParameterCount(&count); + for (uint32_t idx = 0; idx < count; idx++) { + if (idx > 0) + appArgs.Append(' '); + nsAutoString param; + rv = handlerApp->GetParameter(idx, param); + if (NS_FAILED(rv)) + return rv; + appArgs.Append(param); + } + + handlerApp->GetName(appTitle); + handlerApp->GetDetailedDescription(appDescription); + + bool useUriIcon = false; // if we want to use the URI icon + bool usedUriIcon = false; // if we did use the URI icon + shortcut->GetIconIndex(&appIconIndex); + + nsCOMPtr<nsIURI> iconUri; + rv = shortcut->GetFaviconPageUri(getter_AddRefs(iconUri)); + if (NS_SUCCEEDED(rv) && iconUri) { + useUriIcon = true; + } + + // Store the title of the app + if (appTitle.Length() > 0) { + IPropertyStore* pPropStore = nullptr; + hr = psl->QueryInterface(IID_IPropertyStore, (LPVOID*)&pPropStore); + if (FAILED(hr)) + return NS_ERROR_UNEXPECTED; + + PROPVARIANT pv; + InitPropVariantFromString(appTitle.get(), &pv); + + pPropStore->SetValue(PKEY_Title, pv); + pPropStore->Commit(); + pPropStore->Release(); + + PropVariantClear(&pv); + } + + // Store the rest of the params + psl->SetPath(appPath.get()); + psl->SetDescription(appDescription.get()); + psl->SetArguments(appArgs.get()); + + if (useUriIcon) { + nsString icoFilePath; + rv = mozilla::widget::FaviconHelper::ObtainCachedIconFile(iconUri, + icoFilePath, + aIOThread, + false); + if (NS_SUCCEEDED(rv)) { + // Always use the first icon in the ICO file + // our encoded icon only has 1 resource + psl->SetIconLocation(icoFilePath.get(), 0); + usedUriIcon = true; + } + } + + // We didn't use an ICO via URI so fall back to the app icon + if (!usedUriIcon) { + psl->SetIconLocation(appPath.get(), appIconIndex); + } + + aShellLink = dont_AddRef(psl); + + return NS_OK; +} + +// If successful fills in the aSame parameter +// aSame will be true if the path is in our icon cache +static nsresult IsPathInOurIconCache(nsCOMPtr<nsIJumpListShortcut>& aShortcut, + wchar_t *aPath, bool *aSame) +{ + NS_ENSURE_ARG_POINTER(aPath); + NS_ENSURE_ARG_POINTER(aSame); + + *aSame = false; + + // Construct the path of our jump list cache + nsCOMPtr<nsIFile> jumpListCache; + nsresult rv = NS_GetSpecialDirectory("ProfLDS", getter_AddRefs(jumpListCache)); + NS_ENSURE_SUCCESS(rv, rv); + rv = jumpListCache->AppendNative(nsDependentCString(FaviconHelper::kJumpListCacheDir)); + NS_ENSURE_SUCCESS(rv, rv); + nsAutoString jumpListCachePath; + rv = jumpListCache->GetPath(jumpListCachePath); + NS_ENSURE_SUCCESS(rv, rv); + + // Construct the parent path of the passed in path + nsCOMPtr<nsIFile> passedInFile = do_CreateInstance("@mozilla.org/file/local;1"); + NS_ENSURE_TRUE(passedInFile, NS_ERROR_FAILURE); + nsAutoString passedInPath(aPath); + rv = passedInFile->InitWithPath(passedInPath); + nsCOMPtr<nsIFile> passedInParentFile; + passedInFile->GetParent(getter_AddRefs(passedInParentFile)); + nsAutoString passedInParentPath; + rv = jumpListCache->GetPath(passedInParentPath); + NS_ENSURE_SUCCESS(rv, rv); + + *aSame = jumpListCachePath.Equals(passedInParentPath); + return NS_OK; +} + +// (static) For a given IShellLink, create and return a populated nsIJumpListShortcut. +nsresult JumpListShortcut::GetJumpListShortcut(IShellLinkW *pLink, nsCOMPtr<nsIJumpListShortcut>& aShortcut) +{ + NS_ENSURE_ARG_POINTER(pLink); + + nsresult rv; + HRESULT hres; + + nsCOMPtr<nsILocalHandlerApp> handlerApp = + do_CreateInstance(NS_LOCALHANDLERAPP_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + wchar_t buf[MAX_PATH]; + + // Path + hres = pLink->GetPath(buf, MAX_PATH, nullptr, SLGP_UNCPRIORITY); + if (FAILED(hres)) + return NS_ERROR_INVALID_ARG; + + nsCOMPtr<nsIFile> file; + nsDependentString filepath(buf); + rv = NS_NewLocalFile(filepath, false, getter_AddRefs(file)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = handlerApp->SetExecutable(file); + NS_ENSURE_SUCCESS(rv, rv); + + // Parameters + hres = pLink->GetArguments(buf, MAX_PATH); + if (SUCCEEDED(hres)) { + LPWSTR *arglist; + int32_t numArgs; + int32_t idx; + + arglist = ::CommandLineToArgvW(buf, &numArgs); + if(arglist) { + for (idx = 0; idx < numArgs; idx++) { + // szArglist[i] is null terminated + nsDependentString arg(arglist[idx]); + handlerApp->AppendParameter(arg); + } + ::LocalFree(arglist); + } + } + + rv = aShortcut->SetApp(handlerApp); + NS_ENSURE_SUCCESS(rv, rv); + + // Icon index or file location + int iconIdx = 0; + hres = pLink->GetIconLocation(buf, MAX_PATH, &iconIdx); + if (SUCCEEDED(hres)) { + // XXX How do we handle converting local files to images here? Do we need to? + aShortcut->SetIconIndex(iconIdx); + + // Obtain the local profile directory and construct the output icon file path + // We only set the Icon Uri if we're sure it was from our icon cache. + bool isInOurCache; + if (NS_SUCCEEDED(IsPathInOurIconCache(aShortcut, buf, &isInOurCache)) && + isInOurCache) { + nsCOMPtr<nsIURI> iconUri; + nsAutoString path(buf); + rv = NS_NewURI(getter_AddRefs(iconUri), path); + if (NS_SUCCEEDED(rv)) { + aShortcut->SetFaviconPageUri(iconUri); + } + } + } + + // Do we need the title and description? Probably not since handler app doesn't compare + // these in equals. + + return NS_OK; +} + +// (static) ShellItems are used to encapsulate links to things. We currently only support URI links, +// but more support could be added, such as local file and directory links. +nsresult JumpListLink::GetShellItem(nsCOMPtr<nsIJumpListItem>& item, RefPtr<IShellItem2>& aShellItem) +{ + IShellItem2 *psi = nullptr; + nsresult rv; + + int16_t type; + if (NS_FAILED(item->GetType(&type))) + return NS_ERROR_INVALID_ARG; + + if (type != nsIJumpListItem::JUMPLIST_ITEM_LINK) + return NS_ERROR_INVALID_ARG; + + nsCOMPtr<nsIJumpListLink> link = do_QueryInterface(item, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIURI> uri; + rv = link->GetUri(getter_AddRefs(uri)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString spec; + rv = uri->GetSpec(spec); + NS_ENSURE_SUCCESS(rv, rv); + + // Create the IShellItem + if (FAILED(WinUtils::SHCreateItemFromParsingName( + NS_ConvertASCIItoUTF16(spec).get(), + nullptr, IID_PPV_ARGS(&psi)))) { + return NS_ERROR_INVALID_ARG; + } + + // Set the title + nsAutoString linkTitle; + link->GetUriTitle(linkTitle); + + IPropertyStore* pPropStore = nullptr; + HRESULT hres = psi->GetPropertyStore(GPS_DEFAULT, IID_IPropertyStore, (void**)&pPropStore); + if (FAILED(hres)) + return NS_ERROR_UNEXPECTED; + + PROPVARIANT pv; + InitPropVariantFromString(linkTitle.get(), &pv); + + // May fail due to shell item access permissions. + pPropStore->SetValue(PKEY_ItemName, pv); + pPropStore->Commit(); + pPropStore->Release(); + + PropVariantClear(&pv); + + aShellItem = dont_AddRef(psi); + + return NS_OK; +} + +// (static) For a given IShellItem, create and return a populated nsIJumpListLink. +nsresult JumpListLink::GetJumpListLink(IShellItem *pItem, nsCOMPtr<nsIJumpListLink>& aLink) +{ + NS_ENSURE_ARG_POINTER(pItem); + + // We assume for now these are URI links, but through properties we could + // query and create other types. + nsresult rv; + LPWSTR lpstrName = nullptr; + + if (SUCCEEDED(pItem->GetDisplayName(SIGDN_URL, &lpstrName))) { + nsCOMPtr<nsIURI> uri; + nsAutoString spec(lpstrName); + + rv = NS_NewURI(getter_AddRefs(uri), NS_ConvertUTF16toUTF8(spec)); + if (NS_FAILED(rv)) + return NS_ERROR_INVALID_ARG; + + aLink->SetUri(uri); + + ::CoTaskMemFree(lpstrName); + } + + return NS_OK; +} + +// Confirm the app is on the system +bool JumpListShortcut::ExecutableExists(nsCOMPtr<nsILocalHandlerApp>& handlerApp) +{ + nsresult rv; + + if (!handlerApp) + return false; + + nsCOMPtr<nsIFile> executable; + rv = handlerApp->GetExecutable(getter_AddRefs(executable)); + if (NS_SUCCEEDED(rv) && executable) { + bool exists; + executable->Exists(&exists); + return exists; + } + return false; +} + +} // namespace widget +} // namespace mozilla + diff --git a/widget/windows/JumpListItem.h b/widget/windows/JumpListItem.h new file mode 100644 index 0000000000..1e3152ae09 --- /dev/null +++ b/widget/windows/JumpListItem.h @@ -0,0 +1,132 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __JumpListItem_h__ +#define __JumpListItem_h__ + +#include <windows.h> +#include <shobjidl.h> +#undef LogSeverity // SetupAPI.h #defines this as DWORD + +#include "mozilla/RefPtr.h" +#include "nsIJumpListItem.h" // defines nsIJumpListItem +#include "nsIMIMEInfo.h" // defines nsILocalHandlerApp +#include "nsTArray.h" +#include "nsIMutableArray.h" +#include "nsCOMPtr.h" +#include "nsIURI.h" +#include "nsICryptoHash.h" +#include "nsString.h" +#include "nsCycleCollectionParticipant.h" + +class nsIThread; + +namespace mozilla { +namespace widget { + +class JumpListItem : public nsIJumpListItem +{ +public: + JumpListItem() : + mItemType(nsIJumpListItem::JUMPLIST_ITEM_EMPTY) + {} + + JumpListItem(int32_t type) : + mItemType(type) + {} + + NS_DECL_ISUPPORTS + NS_DECL_NSIJUMPLISTITEM + + static const char kJumpListCacheDir[]; + +protected: + virtual ~JumpListItem() + {} + + short Type() { return mItemType; } + short mItemType; + +}; + +class JumpListSeparator : public JumpListItem, public nsIJumpListSeparator +{ + ~JumpListSeparator() {} + +public: + JumpListSeparator() : + JumpListItem(nsIJumpListItem::JUMPLIST_ITEM_SEPARATOR) + {} + + NS_DECL_ISUPPORTS_INHERITED + NS_IMETHOD GetType(int16_t *aType) override { return JumpListItem::GetType(aType); } + NS_IMETHOD Equals(nsIJumpListItem *item, bool *_retval) override { return JumpListItem::Equals(item, _retval); } + + static nsresult GetSeparator(RefPtr<IShellLinkW>& aShellLink); +}; + +class JumpListLink : public JumpListItem, public nsIJumpListLink +{ + ~JumpListLink() {} + +public: + JumpListLink() : + JumpListItem(nsIJumpListItem::JUMPLIST_ITEM_LINK) + {} + + NS_DECL_ISUPPORTS_INHERITED + NS_IMETHOD GetType(int16_t *aType) override { return JumpListItem::GetType(aType); } + NS_IMETHOD Equals(nsIJumpListItem *item, bool *_retval) override; + NS_DECL_NSIJUMPLISTLINK + + static nsresult GetShellItem(nsCOMPtr<nsIJumpListItem>& item, RefPtr<IShellItem2>& aShellItem); + static nsresult GetJumpListLink(IShellItem *pItem, nsCOMPtr<nsIJumpListLink>& aLink); + +protected: + nsString mUriTitle; + nsCOMPtr<nsIURI> mURI; + nsCOMPtr<nsICryptoHash> mCryptoHash; +}; + +class JumpListShortcut : public JumpListItem, public nsIJumpListShortcut +{ + ~JumpListShortcut() {} + +public: + JumpListShortcut() : + JumpListItem(nsIJumpListItem::JUMPLIST_ITEM_SHORTCUT) + {} + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(JumpListShortcut, JumpListItem) + NS_IMETHOD GetType(int16_t *aType) override { return JumpListItem::GetType(aType); } + NS_IMETHOD Equals(nsIJumpListItem *item, bool *_retval) override; + NS_DECL_NSIJUMPLISTSHORTCUT + + static nsresult GetShellLink(nsCOMPtr<nsIJumpListItem>& item, + RefPtr<IShellLinkW>& aShellLink, + nsCOMPtr<nsIThread> &aIOThread); + static nsresult GetJumpListShortcut(IShellLinkW *pLink, nsCOMPtr<nsIJumpListShortcut>& aShortcut); + static nsresult GetOutputIconPath(nsCOMPtr<nsIURI> aFaviconPageURI, + nsCOMPtr<nsIFile> &aICOFile); + +protected: + int32_t mIconIndex; + nsCOMPtr<nsIURI> mFaviconPageURI; + nsCOMPtr<nsILocalHandlerApp> mHandlerApp; + + bool ExecutableExists(nsCOMPtr<nsILocalHandlerApp>& handlerApp); + static nsresult ObtainCachedIconFile(nsCOMPtr<nsIURI> aFaviconPageURI, + nsString &aICOFilePath, + nsCOMPtr<nsIThread> &aIOThread); + static nsresult CacheIconFileFromFaviconURIAsync(nsCOMPtr<nsIURI> aFaviconPageURI, + nsCOMPtr<nsIFile> aICOFile, + nsCOMPtr<nsIThread> &aIOThread); +}; + +} // namespace widget +} // namespace mozilla + +#endif /* __JumpListItem_h__ */ diff --git a/widget/windows/KeyboardLayout.cpp b/widget/windows/KeyboardLayout.cpp new file mode 100644 index 0000000000..11f6578747 --- /dev/null +++ b/widget/windows/KeyboardLayout.cpp @@ -0,0 +1,5232 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Logging.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/MouseEvents.h" +#include "mozilla/MiscEvents.h" +#include "mozilla/TextEvents.h" +#include "mozilla/WindowsVersion.h" + +#include "nsAlgorithm.h" +#ifdef MOZ_CRASHREPORTER +#include "nsExceptionHandler.h" +#endif +#include "nsGkAtoms.h" +#include "nsIDOMKeyEvent.h" +#include "nsIIdleServiceInternal.h" +#include "nsIWindowsRegKey.h" +#include "nsMemory.h" +#include "nsPrintfCString.h" +#include "nsQuickSort.h" +#include "nsServiceManagerUtils.h" +#include "nsToolkit.h" +#include "nsUnicharUtils.h" +#include "nsWindowDbg.h" + +#include "KeyboardLayout.h" +#include "WidgetUtils.h" +#include "WinUtils.h" + +#include "npapi.h" + +#include <windows.h> +#include <winuser.h> +#include <algorithm> + +#ifndef WINABLEAPI +#include <winable.h> +#endif + +// In WinUser.h, MAPVK_VK_TO_VSC_EX is defined only when WINVER >= 0x0600 +#ifndef MAPVK_VK_TO_VSC_EX +#define MAPVK_VK_TO_VSC_EX (4) +#endif + +namespace mozilla { +namespace widget { + +static const char* kVirtualKeyName[] = { + "NULL", "VK_LBUTTON", "VK_RBUTTON", "VK_CANCEL", + "VK_MBUTTON", "VK_XBUTTON1", "VK_XBUTTON2", "0x07", + "VK_BACK", "VK_TAB", "0x0A", "0x0B", + "VK_CLEAR", "VK_RETURN", "0x0E", "0x0F", + + "VK_SHIFT", "VK_CONTROL", "VK_MENU", "VK_PAUSE", + "VK_CAPITAL", "VK_KANA, VK_HANGUL", "0x16", "VK_JUNJA", + "VK_FINAL", "VK_HANJA, VK_KANJI", "0x1A", "VK_ESCAPE", + "VK_CONVERT", "VK_NONCONVERT", "VK_ACCEPT", "VK_MODECHANGE", + + "VK_SPACE", "VK_PRIOR", "VK_NEXT", "VK_END", + "VK_HOME", "VK_LEFT", "VK_UP", "VK_RIGHT", + "VK_DOWN", "VK_SELECT", "VK_PRINT", "VK_EXECUTE", + "VK_SNAPSHOT", "VK_INSERT", "VK_DELETE", "VK_HELP", + + "VK_0", "VK_1", "VK_2", "VK_3", + "VK_4", "VK_5", "VK_6", "VK_7", + "VK_8", "VK_9", "0x3A", "0x3B", + "0x3C", "0x3D", "0x3E", "0x3F", + + "0x40", "VK_A", "VK_B", "VK_C", + "VK_D", "VK_E", "VK_F", "VK_G", + "VK_H", "VK_I", "VK_J", "VK_K", + "VK_L", "VK_M", "VK_N", "VK_O", + + "VK_P", "VK_Q", "VK_R", "VK_S", + "VK_T", "VK_U", "VK_V", "VK_W", + "VK_X", "VK_Y", "VK_Z", "VK_LWIN", + "VK_RWIN", "VK_APPS", "0x5E", "VK_SLEEP", + + "VK_NUMPAD0", "VK_NUMPAD1", "VK_NUMPAD2", "VK_NUMPAD3", + "VK_NUMPAD4", "VK_NUMPAD5", "VK_NUMPAD6", "VK_NUMPAD7", + "VK_NUMPAD8", "VK_NUMPAD9", "VK_MULTIPLY", "VK_ADD", + "VK_SEPARATOR", "VK_SUBTRACT", "VK_DECIMAL", "VK_DIVIDE", + + "VK_F1", "VK_F2", "VK_F3", "VK_F4", + "VK_F5", "VK_F6", "VK_F7", "VK_F8", + "VK_F9", "VK_F10", "VK_F11", "VK_F12", + "VK_F13", "VK_F14", "VK_F15", "VK_F16", + + "VK_F17", "VK_F18", "VK_F19", "VK_F20", + "VK_F21", "VK_F22", "VK_F23", "VK_F24", + "0x88", "0x89", "0x8A", "0x8B", + "0x8C", "0x8D", "0x8E", "0x8F", + + "VK_NUMLOCK", "VK_SCROLL", "VK_OEM_NEC_EQUAL, VK_OEM_FJ_JISHO", + "VK_OEM_FJ_MASSHOU", + "VK_OEM_FJ_TOUROKU", "VK_OEM_FJ_LOYA", "VK_OEM_FJ_ROYA", "0x97", + "0x98", "0x99", "0x9A", "0x9B", + "0x9C", "0x9D", "0x9E", "0x9F", + + "VK_LSHIFT", "VK_RSHIFT", "VK_LCONTROL", "VK_RCONTROL", + "VK_LMENU", "VK_RMENU", "VK_BROWSER_BACK", "VK_BROWSER_FORWARD", + "VK_BROWSER_REFRESH", "VK_BROWSER_STOP", "VK_BROWSER_SEARCH", + "VK_BROWSER_FAVORITES", + "VK_BROWSER_HOME", "VK_VOLUME_MUTE", "VK_VOLUME_DOWN", "VK_VOLUME_UP", + + "VK_MEDIA_NEXT_TRACK", "VK_MEDIA_PREV_TRACK", "VK_MEDIA_STOP", + "VK_MEDIA_PLAY_PAUSE", + "VK_LAUNCH_MAIL", "VK_LAUNCH_MEDIA_SELECT", "VK_LAUNCH_APP1", + "VK_LAUNCH_APP2", + "0xB8", "0xB9", "VK_OEM_1", "VK_OEM_PLUS", + "VK_OEM_COMMA", "VK_OEM_MINUS", "VK_OEM_PERIOD", "VK_OEM_2", + + "VK_OEM_3", "VK_ABNT_C1", "VK_ABNT_C2", "0xC3", + "0xC4", "0xC5", "0xC6", "0xC7", + "0xC8", "0xC9", "0xCA", "0xCB", + "0xCC", "0xCD", "0xCE", "0xCF", + + "0xD0", "0xD1", "0xD2", "0xD3", + "0xD4", "0xD5", "0xD6", "0xD7", + "0xD8", "0xD9", "0xDA", "VK_OEM_4", + "VK_OEM_5", "VK_OEM_6", "VK_OEM_7", "VK_OEM_8", + + "0xE0", "VK_OEM_AX", "VK_OEM_102", "VK_ICO_HELP", + "VK_ICO_00", "VK_PROCESSKEY", "VK_ICO_CLEAR", "VK_PACKET", + "0xE8", "VK_OEM_RESET", "VK_OEM_JUMP", "VK_OEM_PA1", + "VK_OEM_PA2", "VK_OEM_PA3", "VK_OEM_WSCTRL", "VK_OEM_CUSEL", + + "VK_OEM_ATTN", "VK_OEM_FINISH", "VK_OEM_COPY", "VK_OEM_AUTO", + "VK_OEM_ENLW", "VK_OEM_BACKTAB", "VK_ATTN", "VK_CRSEL", + "VK_EXSEL", "VK_EREOF", "VK_PLAY", "VK_ZOOM", + "VK_NONAME", "VK_PA1", "VK_OEM_CLEAR", "0xFF" +}; + +static_assert(sizeof(kVirtualKeyName) / sizeof(const char*) == 0x100, + "The virtual key name must be defined just 256 keys"); + +static const char* +GetBoolName(bool aBool) +{ + return aBool ? "true" : "false"; +} + +static const nsCString +GetCharacterCodeName(WPARAM aCharCode) +{ + switch (aCharCode) { + case 0x0000: + return NS_LITERAL_CSTRING("NULL (0x0000)"); + case 0x0008: + return NS_LITERAL_CSTRING("BACKSPACE (0x0008)"); + case 0x0009: + return NS_LITERAL_CSTRING("CHARACTER TABULATION (0x0009)"); + case 0x000A: + return NS_LITERAL_CSTRING("LINE FEED (0x000A)"); + case 0x000B: + return NS_LITERAL_CSTRING("LINE TABULATION (0x000B)"); + case 0x000C: + return NS_LITERAL_CSTRING("FORM FEED (0x000C)"); + case 0x000D: + return NS_LITERAL_CSTRING("CARRIAGE RETURN (0x000D)"); + case 0x0018: + return NS_LITERAL_CSTRING("CANCEL (0x0018)"); + case 0x001B: + return NS_LITERAL_CSTRING("ESCAPE (0x001B)"); + case 0x0020: + return NS_LITERAL_CSTRING("SPACE (0x0020)"); + case 0x007F: + return NS_LITERAL_CSTRING("DELETE (0x007F)"); + case 0x00A0: + return NS_LITERAL_CSTRING("NO-BREAK SPACE (0x00A0)"); + case 0x00AD: + return NS_LITERAL_CSTRING("SOFT HYPHEN (0x00AD)"); + case 0x2000: + return NS_LITERAL_CSTRING("EN QUAD (0x2000)"); + case 0x2001: + return NS_LITERAL_CSTRING("EM QUAD (0x2001)"); + case 0x2002: + return NS_LITERAL_CSTRING("EN SPACE (0x2002)"); + case 0x2003: + return NS_LITERAL_CSTRING("EM SPACE (0x2003)"); + case 0x2004: + return NS_LITERAL_CSTRING("THREE-PER-EM SPACE (0x2004)"); + case 0x2005: + return NS_LITERAL_CSTRING("FOUR-PER-EM SPACE (0x2005)"); + case 0x2006: + return NS_LITERAL_CSTRING("SIX-PER-EM SPACE (0x2006)"); + case 0x2007: + return NS_LITERAL_CSTRING("FIGURE SPACE (0x2007)"); + case 0x2008: + return NS_LITERAL_CSTRING("PUNCTUATION SPACE (0x2008)"); + case 0x2009: + return NS_LITERAL_CSTRING("THIN SPACE (0x2009)"); + case 0x200A: + return NS_LITERAL_CSTRING("HAIR SPACE (0x200A)"); + case 0x200B: + return NS_LITERAL_CSTRING("ZERO WIDTH SPACE (0x200B)"); + case 0x200C: + return NS_LITERAL_CSTRING("ZERO WIDTH NON-JOINER (0x200C)"); + case 0x200D: + return NS_LITERAL_CSTRING("ZERO WIDTH JOINER (0x200D)"); + case 0x200E: + return NS_LITERAL_CSTRING("LEFT-TO-RIGHT MARK (0x200E)"); + case 0x200F: + return NS_LITERAL_CSTRING("RIGHT-TO-LEFT MARK (0x200F)"); + case 0x2029: + return NS_LITERAL_CSTRING("PARAGRAPH SEPARATOR (0x2029)"); + case 0x202A: + return NS_LITERAL_CSTRING("LEFT-TO-RIGHT EMBEDDING (0x202A)"); + case 0x202B: + return NS_LITERAL_CSTRING("RIGHT-TO-LEFT EMBEDDING (0x202B)"); + case 0x202D: + return NS_LITERAL_CSTRING("LEFT-TO-RIGHT OVERRIDE (0x202D)"); + case 0x202E: + return NS_LITERAL_CSTRING("RIGHT-TO-LEFT OVERRIDE (0x202E)"); + case 0x202F: + return NS_LITERAL_CSTRING("NARROW NO-BREAK SPACE (0x202F)"); + case 0x205F: + return NS_LITERAL_CSTRING("MEDIUM MATHEMATICAL SPACE (0x205F)"); + case 0x2060: + return NS_LITERAL_CSTRING("WORD JOINER (0x2060)"); + case 0x2066: + return NS_LITERAL_CSTRING("LEFT-TO-RIGHT ISOLATE (0x2066)"); + case 0x2067: + return NS_LITERAL_CSTRING("RIGHT-TO-LEFT ISOLATE (0x2067)"); + case 0x3000: + return NS_LITERAL_CSTRING("IDEOGRAPHIC SPACE (0x3000)"); + case 0xFEFF: + return NS_LITERAL_CSTRING("ZERO WIDTH NO-BREAK SPACE (0xFEFF)"); + default: { + if (aCharCode < ' ' || + (aCharCode >= 0x80 && aCharCode < 0xA0)) { + return nsPrintfCString("control (0x%04X)", aCharCode); + } + if (NS_IS_HIGH_SURROGATE(aCharCode)) { + return nsPrintfCString("high surrogate (0x%04X)", aCharCode); + } + if (NS_IS_LOW_SURROGATE(aCharCode)) { + return nsPrintfCString("low surrogate (0x%04X)", aCharCode); + } + return IS_IN_BMP(aCharCode) ? + nsPrintfCString("'%s' (0x%04X)", + NS_ConvertUTF16toUTF8(nsAutoString(aCharCode)).get(), aCharCode) : + nsPrintfCString("'%s' (0x%08X)", + NS_ConvertUTF16toUTF8(nsAutoString(aCharCode)).get(), aCharCode); + } + } +} + +static const nsCString +GetKeyLocationName(uint32_t aLocation) +{ + switch (aLocation) { + case nsIDOMKeyEvent::DOM_KEY_LOCATION_LEFT: + return NS_LITERAL_CSTRING("KEY_LOCATION_LEFT"); + case nsIDOMKeyEvent::DOM_KEY_LOCATION_RIGHT: + return NS_LITERAL_CSTRING("KEY_LOCATION_RIGHT"); + case nsIDOMKeyEvent::DOM_KEY_LOCATION_STANDARD: + return NS_LITERAL_CSTRING("KEY_LOCATION_STANDARD"); + case nsIDOMKeyEvent::DOM_KEY_LOCATION_NUMPAD: + return NS_LITERAL_CSTRING("KEY_LOCATION_NUMPAD"); + default: + return nsPrintfCString("Unknown (0x%04X)", aLocation); + } +} + +static const nsCString +GetCharacterCodeName(char16_t* aChars, uint32_t aLength) +{ + if (!aLength) { + return NS_LITERAL_CSTRING(""); + } + nsAutoCString result; + for (uint32_t i = 0; i < aLength; ++i) { + if (!result.IsEmpty()) { + result.AppendLiteral(", "); + } else { + result.AssignLiteral("\""); + } + result.Append(GetCharacterCodeName(aChars[i])); + } + result.AppendLiteral("\""); + return result; +} + +class MOZ_STACK_CLASS GetShiftStateName final : public nsAutoCString +{ +public: + explicit GetShiftStateName(VirtualKey::ShiftState aShiftState) + { + if (!aShiftState) { + AssignLiteral("none"); + return; + } + if (aShiftState & VirtualKey::STATE_SHIFT) { + AssignLiteral("Shift"); + aShiftState &= ~VirtualKey::STATE_SHIFT; + } + if (aShiftState & VirtualKey::STATE_CONTROL) { + MaybeAppendSeparator(); + AssignLiteral("Ctrl"); + aShiftState &= ~VirtualKey::STATE_CONTROL; + } + if (aShiftState & VirtualKey::STATE_ALT) { + MaybeAppendSeparator(); + AssignLiteral("Alt"); + aShiftState &= ~VirtualKey::STATE_ALT; + } + if (aShiftState & VirtualKey::STATE_CAPSLOCK) { + MaybeAppendSeparator(); + AssignLiteral("CapsLock"); + aShiftState &= ~VirtualKey::STATE_CAPSLOCK; + } + MOZ_ASSERT(!aShiftState); + } + +private: + void MaybeAppendSeparator() + { + if (!IsEmpty()) { + AppendLiteral(" | "); + } + } +}; + +static const nsCString +GetMessageName(UINT aMessage) +{ + switch (aMessage) { + case WM_NULL: + return NS_LITERAL_CSTRING("WM_NULL"); + case WM_KEYDOWN: + return NS_LITERAL_CSTRING("WM_KEYDOWN"); + case WM_KEYUP: + return NS_LITERAL_CSTRING("WM_KEYUP"); + case WM_SYSKEYDOWN: + return NS_LITERAL_CSTRING("WM_SYSKEYDOWN"); + case WM_SYSKEYUP: + return NS_LITERAL_CSTRING("WM_SYSKEYUP"); + case WM_CHAR: + return NS_LITERAL_CSTRING("WM_CHAR"); + case WM_UNICHAR: + return NS_LITERAL_CSTRING("WM_UNICHAR"); + case WM_SYSCHAR: + return NS_LITERAL_CSTRING("WM_SYSCHAR"); + case WM_DEADCHAR: + return NS_LITERAL_CSTRING("WM_DEADCHAR"); + case WM_SYSDEADCHAR: + return NS_LITERAL_CSTRING("WM_SYSDEADCHAR"); + case MOZ_WM_KEYDOWN: + return NS_LITERAL_CSTRING("MOZ_WM_KEYDOWN"); + case MOZ_WM_KEYUP: + return NS_LITERAL_CSTRING("MOZ_WM_KEYUP"); + case WM_APPCOMMAND: + return NS_LITERAL_CSTRING("WM_APPCOMMAND"); + case WM_QUIT: + return NS_LITERAL_CSTRING("WM_QUIT"); + default: + return nsPrintfCString("Unknown Message (0x%04X)", aMessage); + } +} + +static const nsCString +GetVirtualKeyCodeName(WPARAM aVK) +{ + if (aVK >= ArrayLength(kVirtualKeyName)) { + return nsPrintfCString("Invalid (0x%08X)", aVK); + } + return nsCString(kVirtualKeyName[aVK]); +} + +static const nsCString +GetAppCommandName(WPARAM aCommand) +{ + switch (aCommand) { + case APPCOMMAND_BASS_BOOST: + return NS_LITERAL_CSTRING("APPCOMMAND_BASS_BOOST"); + case APPCOMMAND_BASS_DOWN: + return NS_LITERAL_CSTRING("APPCOMMAND_BASS_DOWN"); + case APPCOMMAND_BASS_UP: + return NS_LITERAL_CSTRING("APPCOMMAND_BASS_UP"); + case APPCOMMAND_BROWSER_BACKWARD: + return NS_LITERAL_CSTRING("APPCOMMAND_BROWSER_BACKWARD"); + case APPCOMMAND_BROWSER_FAVORITES: + return NS_LITERAL_CSTRING("APPCOMMAND_BROWSER_FAVORITES"); + case APPCOMMAND_BROWSER_FORWARD: + return NS_LITERAL_CSTRING("APPCOMMAND_BROWSER_FORWARD"); + case APPCOMMAND_BROWSER_HOME: + return NS_LITERAL_CSTRING("APPCOMMAND_BROWSER_HOME"); + case APPCOMMAND_BROWSER_REFRESH: + return NS_LITERAL_CSTRING("APPCOMMAND_BROWSER_REFRESH"); + case APPCOMMAND_BROWSER_SEARCH: + return NS_LITERAL_CSTRING("APPCOMMAND_BROWSER_SEARCH"); + case APPCOMMAND_BROWSER_STOP: + return NS_LITERAL_CSTRING("APPCOMMAND_BROWSER_STOP"); + case APPCOMMAND_CLOSE: + return NS_LITERAL_CSTRING("APPCOMMAND_CLOSE"); + case APPCOMMAND_COPY: + return NS_LITERAL_CSTRING("APPCOMMAND_COPY"); + case APPCOMMAND_CORRECTION_LIST: + return NS_LITERAL_CSTRING("APPCOMMAND_CORRECTION_LIST"); + case APPCOMMAND_CUT: + return NS_LITERAL_CSTRING("APPCOMMAND_CUT"); + case APPCOMMAND_DICTATE_OR_COMMAND_CONTROL_TOGGLE: + return NS_LITERAL_CSTRING("APPCOMMAND_DICTATE_OR_COMMAND_CONTROL_TOGGLE"); + case APPCOMMAND_FIND: + return NS_LITERAL_CSTRING("APPCOMMAND_FIND"); + case APPCOMMAND_FORWARD_MAIL: + return NS_LITERAL_CSTRING("APPCOMMAND_FORWARD_MAIL"); + case APPCOMMAND_HELP: + return NS_LITERAL_CSTRING("APPCOMMAND_HELP"); + case APPCOMMAND_LAUNCH_APP1: + return NS_LITERAL_CSTRING("APPCOMMAND_LAUNCH_APP1"); + case APPCOMMAND_LAUNCH_APP2: + return NS_LITERAL_CSTRING("APPCOMMAND_LAUNCH_APP2"); + case APPCOMMAND_LAUNCH_MAIL: + return NS_LITERAL_CSTRING("APPCOMMAND_LAUNCH_MAIL"); + case APPCOMMAND_LAUNCH_MEDIA_SELECT: + return NS_LITERAL_CSTRING("APPCOMMAND_LAUNCH_MEDIA_SELECT"); + case APPCOMMAND_MEDIA_CHANNEL_DOWN: + return NS_LITERAL_CSTRING("APPCOMMAND_MEDIA_CHANNEL_DOWN"); + case APPCOMMAND_MEDIA_CHANNEL_UP: + return NS_LITERAL_CSTRING("APPCOMMAND_MEDIA_CHANNEL_UP"); + case APPCOMMAND_MEDIA_FAST_FORWARD: + return NS_LITERAL_CSTRING("APPCOMMAND_MEDIA_FAST_FORWARD"); + case APPCOMMAND_MEDIA_NEXTTRACK: + return NS_LITERAL_CSTRING("APPCOMMAND_MEDIA_NEXTTRACK"); + case APPCOMMAND_MEDIA_PAUSE: + return NS_LITERAL_CSTRING("APPCOMMAND_MEDIA_PAUSE"); + case APPCOMMAND_MEDIA_PLAY: + return NS_LITERAL_CSTRING("APPCOMMAND_MEDIA_PLAY"); + case APPCOMMAND_MEDIA_PLAY_PAUSE: + return NS_LITERAL_CSTRING("APPCOMMAND_MEDIA_PLAY_PAUSE"); + case APPCOMMAND_MEDIA_PREVIOUSTRACK: + return NS_LITERAL_CSTRING("APPCOMMAND_MEDIA_PREVIOUSTRACK"); + case APPCOMMAND_MEDIA_RECORD: + return NS_LITERAL_CSTRING("APPCOMMAND_MEDIA_RECORD"); + case APPCOMMAND_MEDIA_REWIND: + return NS_LITERAL_CSTRING("APPCOMMAND_MEDIA_REWIND"); + case APPCOMMAND_MEDIA_STOP: + return NS_LITERAL_CSTRING("APPCOMMAND_MEDIA_STOP"); + case APPCOMMAND_MIC_ON_OFF_TOGGLE: + return NS_LITERAL_CSTRING("APPCOMMAND_MIC_ON_OFF_TOGGLE"); + case APPCOMMAND_MICROPHONE_VOLUME_DOWN: + return NS_LITERAL_CSTRING("APPCOMMAND_MICROPHONE_VOLUME_DOWN"); + case APPCOMMAND_MICROPHONE_VOLUME_MUTE: + return NS_LITERAL_CSTRING("APPCOMMAND_MICROPHONE_VOLUME_MUTE"); + case APPCOMMAND_MICROPHONE_VOLUME_UP: + return NS_LITERAL_CSTRING("APPCOMMAND_MICROPHONE_VOLUME_UP"); + case APPCOMMAND_NEW: + return NS_LITERAL_CSTRING("APPCOMMAND_NEW"); + case APPCOMMAND_OPEN: + return NS_LITERAL_CSTRING("APPCOMMAND_OPEN"); + case APPCOMMAND_PASTE: + return NS_LITERAL_CSTRING("APPCOMMAND_PASTE"); + case APPCOMMAND_PRINT: + return NS_LITERAL_CSTRING("APPCOMMAND_PRINT"); + case APPCOMMAND_REDO: + return NS_LITERAL_CSTRING("APPCOMMAND_REDO"); + case APPCOMMAND_REPLY_TO_MAIL: + return NS_LITERAL_CSTRING("APPCOMMAND_REPLY_TO_MAIL"); + case APPCOMMAND_SAVE: + return NS_LITERAL_CSTRING("APPCOMMAND_SAVE"); + case APPCOMMAND_SEND_MAIL: + return NS_LITERAL_CSTRING("APPCOMMAND_SEND_MAIL"); + case APPCOMMAND_SPELL_CHECK: + return NS_LITERAL_CSTRING("APPCOMMAND_SPELL_CHECK"); + case APPCOMMAND_TREBLE_DOWN: + return NS_LITERAL_CSTRING("APPCOMMAND_TREBLE_DOWN"); + case APPCOMMAND_TREBLE_UP: + return NS_LITERAL_CSTRING("APPCOMMAND_TREBLE_UP"); + case APPCOMMAND_UNDO: + return NS_LITERAL_CSTRING("APPCOMMAND_UNDO"); + case APPCOMMAND_VOLUME_DOWN: + return NS_LITERAL_CSTRING("APPCOMMAND_VOLUME_DOWN"); + case APPCOMMAND_VOLUME_MUTE: + return NS_LITERAL_CSTRING("APPCOMMAND_VOLUME_MUTE"); + case APPCOMMAND_VOLUME_UP: + return NS_LITERAL_CSTRING("APPCOMMAND_VOLUME_UP"); + default: + return nsPrintfCString("Unknown app command (0x%08X)", aCommand); + } +} + +static const nsCString +GetAppCommandDeviceName(LPARAM aDevice) +{ + switch (aDevice) { + case FAPPCOMMAND_KEY: + return NS_LITERAL_CSTRING("FAPPCOMMAND_KEY"); + case FAPPCOMMAND_MOUSE: + return NS_LITERAL_CSTRING("FAPPCOMMAND_MOUSE"); + case FAPPCOMMAND_OEM: + return NS_LITERAL_CSTRING("FAPPCOMMAND_OEM"); + default: + return nsPrintfCString("Unknown app command device (0x%04X)", aDevice); + } +}; + +class MOZ_STACK_CLASS GetAppCommandKeysName final : public nsAutoCString +{ +public: + GetAppCommandKeysName(WPARAM aKeys) + { + if (aKeys & MK_CONTROL) { + AppendLiteral("MK_CONTROL"); + aKeys &= ~MK_CONTROL; + } + if (aKeys & MK_LBUTTON) { + MaybeAppendSeparator(); + AppendLiteral("MK_LBUTTON"); + aKeys &= ~MK_LBUTTON; + } + if (aKeys & MK_MBUTTON) { + MaybeAppendSeparator(); + AppendLiteral("MK_MBUTTON"); + aKeys &= ~MK_MBUTTON; + } + if (aKeys & MK_RBUTTON) { + MaybeAppendSeparator(); + AppendLiteral("MK_RBUTTON"); + aKeys &= ~MK_RBUTTON; + } + if (aKeys & MK_SHIFT) { + MaybeAppendSeparator(); + AppendLiteral("MK_SHIFT"); + aKeys &= ~MK_SHIFT; + } + if (aKeys & MK_XBUTTON1) { + MaybeAppendSeparator(); + AppendLiteral("MK_XBUTTON1"); + aKeys &= ~MK_XBUTTON1; + } + if (aKeys & MK_XBUTTON2) { + MaybeAppendSeparator(); + AppendLiteral("MK_XBUTTON2"); + aKeys &= ~MK_XBUTTON2; + } + if (aKeys) { + MaybeAppendSeparator(); + AppendPrintf("Unknown Flags (0x%04X)", aKeys); + } + if (IsEmpty()) { + AssignLiteral("none (0x0000)"); + } + } + +private: + void MaybeAppendSeparator() + { + if (!IsEmpty()) { + AppendLiteral(" | "); + } + } +}; + +static const nsCString +ToString(const MSG& aMSG) +{ + nsAutoCString result; + result.AssignLiteral("{ message="); + result.Append(GetMessageName(aMSG.message).get()); + result.AppendLiteral(", "); + switch (aMSG.message) { + case WM_KEYDOWN: + case WM_KEYUP: + case WM_SYSKEYDOWN: + case WM_SYSKEYUP: + case MOZ_WM_KEYDOWN: + case MOZ_WM_KEYUP: + result.AppendPrintf( + "virtual keycode=%s, repeat count=%d, " + "scancode=0x%02X, extended key=%s, " + "context code=%s, previous key state=%s, " + "transition state=%s", + GetVirtualKeyCodeName(aMSG.wParam).get(), + aMSG.lParam & 0xFFFF, + WinUtils::GetScanCode(aMSG.lParam), + GetBoolName(WinUtils::IsExtendedScanCode(aMSG.lParam)), + GetBoolName((aMSG.lParam & (1 << 29)) != 0), + GetBoolName((aMSG.lParam & (1 << 30)) != 0), + GetBoolName((aMSG.lParam & (1 << 31)) != 0)); + break; + case WM_CHAR: + case WM_DEADCHAR: + case WM_SYSCHAR: + case WM_SYSDEADCHAR: + result.AppendPrintf( + "character code=%s, repeat count=%d, " + "scancode=0x%02X, extended key=%s, " + "context code=%s, previous key state=%s, " + "transition state=%s", + GetCharacterCodeName(aMSG.wParam).get(), + aMSG.lParam & 0xFFFF, + WinUtils::GetScanCode(aMSG.lParam), + GetBoolName(WinUtils::IsExtendedScanCode(aMSG.lParam)), + GetBoolName((aMSG.lParam & (1 << 29)) != 0), + GetBoolName((aMSG.lParam & (1 << 30)) != 0), + GetBoolName((aMSG.lParam & (1 << 31)) != 0)); + break; + case WM_APPCOMMAND: + result.AppendPrintf( + "window handle=0x%p, app command=%s, device=%s, dwKeys=%s", + aMSG.wParam, + GetAppCommandName(GET_APPCOMMAND_LPARAM(aMSG.lParam)).get(), + GetAppCommandDeviceName(GET_DEVICE_LPARAM(aMSG.lParam)).get(), + GetAppCommandKeysName(GET_KEYSTATE_LPARAM(aMSG.lParam)).get()); + break; + default: + result.AppendPrintf("wParam=%u, lParam=%u", aMSG.wParam, aMSG.lParam); + break; + } + result.AppendPrintf(", hwnd=0x%p", aMSG.hwnd); + return result; +} + +static const nsCString +ToString(const UniCharsAndModifiers& aUniCharsAndModifiers) +{ + if (aUniCharsAndModifiers.IsEmpty()) { + return NS_LITERAL_CSTRING("{}"); + } + nsAutoCString result; + result.AssignLiteral("{ "); + result.Append(GetCharacterCodeName(aUniCharsAndModifiers.CharAt(0))); + for (size_t i = 1; i < aUniCharsAndModifiers.Length(); ++i) { + if (aUniCharsAndModifiers.ModifiersAt(i - 1) != + aUniCharsAndModifiers.ModifiersAt(i)) { + result.AppendLiteral(" ["); + result.Append(GetModifiersName(aUniCharsAndModifiers.ModifiersAt(0))); + result.AppendLiteral("]"); + } + result.AppendLiteral(", "); + result.Append(GetCharacterCodeName(aUniCharsAndModifiers.CharAt(i))); + } + result.AppendLiteral(" ["); + uint32_t lastIndex = aUniCharsAndModifiers.Length() - 1; + result.Append(GetModifiersName(aUniCharsAndModifiers.ModifiersAt(lastIndex))); + result.AppendLiteral("] }"); + return result; +} + +const nsCString +ToString(const ModifierKeyState& aModifierKeyState) +{ + nsAutoCString result; + result.AssignLiteral("{ "); + result.Append(GetModifiersName(aModifierKeyState.GetModifiers()).get()); + result.AppendLiteral(" }"); + return result; +} + +// Unique id counter associated with a keydown / keypress events. Used in +// identifing keypress events for removal from async event dispatch queue +// in metrofx after preventDefault is called on keydown events. +static uint32_t sUniqueKeyEventId = 0; + +struct DeadKeyEntry +{ + char16_t BaseChar; + char16_t CompositeChar; +}; + + +class DeadKeyTable +{ + friend class KeyboardLayout; + + uint16_t mEntries; + // KeyboardLayout::AddDeadKeyTable() will allocate as many entries as + // required. It is the only way to create new DeadKeyTable instances. + DeadKeyEntry mTable[1]; + + void Init(const DeadKeyEntry* aDeadKeyArray, uint32_t aEntries) + { + mEntries = aEntries; + memcpy(mTable, aDeadKeyArray, aEntries * sizeof(DeadKeyEntry)); + } + + static uint32_t SizeInBytes(uint32_t aEntries) + { + return offsetof(DeadKeyTable, mTable) + aEntries * sizeof(DeadKeyEntry); + } + +public: + uint32_t Entries() const + { + return mEntries; + } + + bool IsEqual(const DeadKeyEntry* aDeadKeyArray, uint32_t aEntries) const + { + return (mEntries == aEntries && + !memcmp(mTable, aDeadKeyArray, + aEntries * sizeof(DeadKeyEntry))); + } + + char16_t GetCompositeChar(char16_t aBaseChar) const; +}; + + +/***************************************************************************** + * mozilla::widget::ModifierKeyState + *****************************************************************************/ + +ModifierKeyState::ModifierKeyState() +{ + Update(); +} + +ModifierKeyState::ModifierKeyState(bool aIsShiftDown, + bool aIsControlDown, + bool aIsAltDown) +{ + Update(); + Unset(MODIFIER_SHIFT | MODIFIER_CONTROL | MODIFIER_ALT | MODIFIER_ALTGRAPH); + Modifiers modifiers = 0; + if (aIsShiftDown) { + modifiers |= MODIFIER_SHIFT; + } + if (aIsControlDown) { + modifiers |= MODIFIER_CONTROL; + } + if (aIsAltDown) { + modifiers |= MODIFIER_ALT; + } + if (modifiers) { + Set(modifiers); + } +} + +ModifierKeyState::ModifierKeyState(Modifiers aModifiers) : + mModifiers(aModifiers) +{ + EnsureAltGr(); +} + +void +ModifierKeyState::Update() +{ + mModifiers = 0; + if (IS_VK_DOWN(VK_SHIFT)) { + mModifiers |= MODIFIER_SHIFT; + } + if (IS_VK_DOWN(VK_CONTROL)) { + mModifiers |= MODIFIER_CONTROL; + } + if (IS_VK_DOWN(VK_MENU)) { + mModifiers |= MODIFIER_ALT; + } + if (IS_VK_DOWN(VK_LWIN) || IS_VK_DOWN(VK_RWIN)) { + mModifiers |= MODIFIER_OS; + } + if (::GetKeyState(VK_CAPITAL) & 1) { + mModifiers |= MODIFIER_CAPSLOCK; + } + if (::GetKeyState(VK_NUMLOCK) & 1) { + mModifiers |= MODIFIER_NUMLOCK; + } + if (::GetKeyState(VK_SCROLL) & 1) { + mModifiers |= MODIFIER_SCROLLLOCK; + } + + EnsureAltGr(); +} + +void +ModifierKeyState::Unset(Modifiers aRemovingModifiers) +{ + mModifiers &= ~aRemovingModifiers; + // Note that we don't need to unset AltGr flag here automatically. + // For EditorBase, we need to remove Alt and Control flags but AltGr isn't + // checked in EditorBase, so, it can be kept. +} + +void +ModifierKeyState::Set(Modifiers aAddingModifiers) +{ + mModifiers |= aAddingModifiers; + EnsureAltGr(); +} + +void +ModifierKeyState::InitInputEvent(WidgetInputEvent& aInputEvent) const +{ + aInputEvent.mModifiers = mModifiers; + + switch(aInputEvent.mClass) { + case eMouseEventClass: + case eMouseScrollEventClass: + case eWheelEventClass: + case eDragEventClass: + case eSimpleGestureEventClass: + InitMouseEvent(aInputEvent); + break; + default: + break; + } +} + +void +ModifierKeyState::InitMouseEvent(WidgetInputEvent& aMouseEvent) const +{ + NS_ASSERTION(aMouseEvent.mClass == eMouseEventClass || + aMouseEvent.mClass == eWheelEventClass || + aMouseEvent.mClass == eDragEventClass || + aMouseEvent.mClass == eSimpleGestureEventClass, + "called with non-mouse event"); + + WidgetMouseEventBase& mouseEvent = *aMouseEvent.AsMouseEventBase(); + mouseEvent.buttons = 0; + if (::GetKeyState(VK_LBUTTON) < 0) { + mouseEvent.buttons |= WidgetMouseEvent::eLeftButtonFlag; + } + if (::GetKeyState(VK_RBUTTON) < 0) { + mouseEvent.buttons |= WidgetMouseEvent::eRightButtonFlag; + } + if (::GetKeyState(VK_MBUTTON) < 0) { + mouseEvent.buttons |= WidgetMouseEvent::eMiddleButtonFlag; + } + if (::GetKeyState(VK_XBUTTON1) < 0) { + mouseEvent.buttons |= WidgetMouseEvent::e4thButtonFlag; + } + if (::GetKeyState(VK_XBUTTON2) < 0) { + mouseEvent.buttons |= WidgetMouseEvent::e5thButtonFlag; + } +} + +bool +ModifierKeyState::IsShift() const +{ + return (mModifiers & MODIFIER_SHIFT) != 0; +} + +bool +ModifierKeyState::IsControl() const +{ + return (mModifiers & MODIFIER_CONTROL) != 0; +} + +bool +ModifierKeyState::IsAlt() const +{ + return (mModifiers & MODIFIER_ALT) != 0; +} + +bool +ModifierKeyState::IsAltGr() const +{ + return IsControl() && IsAlt(); +} + +bool +ModifierKeyState::IsWin() const +{ + return (mModifiers & MODIFIER_OS) != 0; +} + +bool +ModifierKeyState::MaybeMatchShortcutKey() const +{ + // If Windows key is pressed, even if both Ctrl key and Alt key are pressed, + // it's possible to match a shortcut key. + if (IsWin()) { + return true; + } + // Otherwise, when both Ctrl key and Alt key are pressed, it shouldn't be + // a shortcut key for Windows since it means pressing AltGr key on + // some keyboard layouts. + if (IsControl() ^ IsAlt()) { + return true; + } + // If no modifier key is active except a lockable modifier nor Shift key, + // the key shouldn't match any shortcut keys (there are Space and + // Shift+Space, though, let's ignore these special case...). + return false; +} + +bool +ModifierKeyState::IsCapsLocked() const +{ + return (mModifiers & MODIFIER_CAPSLOCK) != 0; +} + +bool +ModifierKeyState::IsNumLocked() const +{ + return (mModifiers & MODIFIER_NUMLOCK) != 0; +} + +bool +ModifierKeyState::IsScrollLocked() const +{ + return (mModifiers & MODIFIER_SCROLLLOCK) != 0; +} + +void +ModifierKeyState::EnsureAltGr() +{ + // If both Control key and Alt key are pressed, it means AltGr is pressed. + // Ideally, we should check whether the current keyboard layout has AltGr + // or not. However, setting AltGr flags for keyboard which doesn't have + // AltGr must not be serious bug. So, it should be OK for now. + if (IsAltGr()) { + mModifiers |= MODIFIER_ALTGRAPH; + } +} + +/***************************************************************************** + * mozilla::widget::UniCharsAndModifiers + *****************************************************************************/ + +void +UniCharsAndModifiers::Append(char16_t aUniChar, Modifiers aModifiers) +{ + mChars.Append(aUniChar); + mModifiers.AppendElement(aModifiers); +} + +void +UniCharsAndModifiers::FillModifiers(Modifiers aModifiers) +{ + for (size_t i = 0; i < Length(); i++) { + mModifiers[i] = aModifiers; + } +} + +void +UniCharsAndModifiers::OverwriteModifiersIfBeginsWith( + const UniCharsAndModifiers& aOther) +{ + if (!BeginsWith(aOther)) { + return; + } + for (size_t i = 0; i < aOther.Length(); ++i) { + mModifiers[i] = aOther.mModifiers[i]; + } +} + +bool +UniCharsAndModifiers::UniCharsEqual(const UniCharsAndModifiers& aOther) const +{ + return mChars.Equals(aOther.mChars); +} + +bool +UniCharsAndModifiers::UniCharsCaseInsensitiveEqual( + const UniCharsAndModifiers& aOther) const +{ + nsCaseInsensitiveStringComparator comp; + return mChars.Equals(aOther.mChars, comp); +} + +bool +UniCharsAndModifiers::BeginsWith(const UniCharsAndModifiers& aOther) const +{ + return StringBeginsWith(mChars, aOther.mChars); +} + +UniCharsAndModifiers& +UniCharsAndModifiers::operator+=(const UniCharsAndModifiers& aOther) +{ + mChars.Append(aOther.mChars); + mModifiers.AppendElements(aOther.mModifiers); + return *this; +} + +UniCharsAndModifiers +UniCharsAndModifiers::operator+(const UniCharsAndModifiers& aOther) const +{ + UniCharsAndModifiers result(*this); + result += aOther; + return result; +} + +/***************************************************************************** + * mozilla::widget::VirtualKey + *****************************************************************************/ + +// static +VirtualKey::ShiftState +VirtualKey::ModifiersToShiftState(Modifiers aModifiers) +{ + ShiftState state = 0; + if (aModifiers & MODIFIER_SHIFT) { + state |= STATE_SHIFT; + } + if (aModifiers & MODIFIER_CONTROL) { + state |= STATE_CONTROL; + } + if (aModifiers & MODIFIER_ALT) { + state |= STATE_ALT; + } + if (aModifiers & MODIFIER_CAPSLOCK) { + state |= STATE_CAPSLOCK; + } + return state; +} + +// static +Modifiers +VirtualKey::ShiftStateToModifiers(ShiftState aShiftState) +{ + Modifiers modifiers = 0; + if (aShiftState & STATE_SHIFT) { + modifiers |= MODIFIER_SHIFT; + } + if (aShiftState & STATE_CONTROL) { + modifiers |= MODIFIER_CONTROL; + } + if (aShiftState & STATE_ALT) { + modifiers |= MODIFIER_ALT; + } + if (aShiftState & STATE_CAPSLOCK) { + modifiers |= MODIFIER_CAPSLOCK; + } + if ((modifiers & (MODIFIER_ALT | MODIFIER_CONTROL)) == + (MODIFIER_ALT | MODIFIER_CONTROL)) { + modifiers |= MODIFIER_ALTGRAPH; + } + return modifiers; +} + +inline char16_t +VirtualKey::GetCompositeChar(ShiftState aShiftState, char16_t aBaseChar) const +{ + return mShiftStates[aShiftState].DeadKey.Table->GetCompositeChar(aBaseChar); +} + +const DeadKeyTable* +VirtualKey::MatchingDeadKeyTable(const DeadKeyEntry* aDeadKeyArray, + uint32_t aEntries) const +{ + if (!mIsDeadKey) { + return nullptr; + } + + for (ShiftState shiftState = 0; shiftState < 16; shiftState++) { + if (!IsDeadKey(shiftState)) { + continue; + } + const DeadKeyTable* dkt = mShiftStates[shiftState].DeadKey.Table; + if (dkt && dkt->IsEqual(aDeadKeyArray, aEntries)) { + return dkt; + } + } + + return nullptr; +} + +void +VirtualKey::SetNormalChars(ShiftState aShiftState, + const char16_t* aChars, + uint32_t aNumOfChars) +{ + NS_ASSERTION(aShiftState < ArrayLength(mShiftStates), "invalid index"); + + SetDeadKey(aShiftState, false); + + for (uint32_t index = 0; index < aNumOfChars; index++) { + // Ignore legacy non-printable control characters + mShiftStates[aShiftState].Normal.Chars[index] = + (aChars[index] >= 0x20) ? aChars[index] : 0; + } + + uint32_t len = ArrayLength(mShiftStates[aShiftState].Normal.Chars); + for (uint32_t index = aNumOfChars; index < len; index++) { + mShiftStates[aShiftState].Normal.Chars[index] = 0; + } +} + +void +VirtualKey::SetDeadChar(ShiftState aShiftState, char16_t aDeadChar) +{ + NS_ASSERTION(aShiftState < ArrayLength(mShiftStates), "invalid index"); + + SetDeadKey(aShiftState, true); + + mShiftStates[aShiftState].DeadKey.DeadChar = aDeadChar; + mShiftStates[aShiftState].DeadKey.Table = nullptr; +} + +UniCharsAndModifiers +VirtualKey::GetUniChars(ShiftState aShiftState) const +{ + UniCharsAndModifiers result = GetNativeUniChars(aShiftState); + + const ShiftState STATE_ALT_CONTROL = (STATE_ALT | STATE_CONTROL); + if (!(aShiftState & STATE_ALT_CONTROL)) { + return result; + } + + if (result.IsEmpty()) { + result = GetNativeUniChars(aShiftState & ~STATE_ALT_CONTROL); + result.FillModifiers(ShiftStateToModifiers(aShiftState)); + return result; + } + + if ((aShiftState & STATE_ALT_CONTROL) == STATE_ALT_CONTROL) { + // Even if the shifted chars and the unshifted chars are same, we + // should consume the Alt key state and the Ctrl key state when + // AltGr key is pressed. Because if we don't consume them, the input + // events are ignored on EditorBase. (I.e., Users cannot input the + // characters with this key combination.) + Modifiers finalModifiers = ShiftStateToModifiers(aShiftState); + finalModifiers &= ~(MODIFIER_ALT | MODIFIER_CONTROL); + result.FillModifiers(finalModifiers); + return result; + } + + UniCharsAndModifiers unmodifiedReslt = + GetNativeUniChars(aShiftState & ~STATE_ALT_CONTROL); + if (!result.UniCharsEqual(unmodifiedReslt)) { + // Otherwise, we should consume the Alt key state and the Ctrl key state + // only when the shifted chars and unshifted chars are different. + Modifiers finalModifiers = ShiftStateToModifiers(aShiftState); + finalModifiers &= ~(MODIFIER_ALT | MODIFIER_CONTROL); + result.FillModifiers(finalModifiers); + } + return result; +} + + +UniCharsAndModifiers +VirtualKey::GetNativeUniChars(ShiftState aShiftState) const +{ +#ifdef DEBUG + if (aShiftState >= ArrayLength(mShiftStates)) { + nsPrintfCString warning("Shift state is out of range: " + "aShiftState=%d, ArrayLength(mShiftState)=%d", + aShiftState, ArrayLength(mShiftStates)); + NS_WARNING(warning.get()); + } +#endif + + UniCharsAndModifiers result; + Modifiers modifiers = ShiftStateToModifiers(aShiftState); + if (IsDeadKey(aShiftState)) { + result.Append(mShiftStates[aShiftState].DeadKey.DeadChar, modifiers); + return result; + } + + uint32_t index; + uint32_t len = ArrayLength(mShiftStates[aShiftState].Normal.Chars); + for (index = 0; + index < len && mShiftStates[aShiftState].Normal.Chars[index]; index++) { + result.Append(mShiftStates[aShiftState].Normal.Chars[index], modifiers); + } + return result; +} + +// static +void +VirtualKey::FillKbdState(PBYTE aKbdState, + const ShiftState aShiftState) +{ + NS_ASSERTION(aShiftState < 16, "aShiftState out of range"); + + if (aShiftState & STATE_SHIFT) { + aKbdState[VK_SHIFT] |= 0x80; + } else { + aKbdState[VK_SHIFT] &= ~0x80; + aKbdState[VK_LSHIFT] &= ~0x80; + aKbdState[VK_RSHIFT] &= ~0x80; + } + + if (aShiftState & STATE_CONTROL) { + aKbdState[VK_CONTROL] |= 0x80; + } else { + aKbdState[VK_CONTROL] &= ~0x80; + aKbdState[VK_LCONTROL] &= ~0x80; + aKbdState[VK_RCONTROL] &= ~0x80; + } + + if (aShiftState & STATE_ALT) { + aKbdState[VK_MENU] |= 0x80; + } else { + aKbdState[VK_MENU] &= ~0x80; + aKbdState[VK_LMENU] &= ~0x80; + aKbdState[VK_RMENU] &= ~0x80; + } + + if (aShiftState & STATE_CAPSLOCK) { + aKbdState[VK_CAPITAL] |= 0x01; + } else { + aKbdState[VK_CAPITAL] &= ~0x01; + } +} + +/***************************************************************************** + * mozilla::widget::NativeKey + *****************************************************************************/ + +uint8_t NativeKey::sDispatchedKeyOfAppCommand = 0; +NativeKey* NativeKey::sLatestInstance = nullptr; +const MSG NativeKey::sEmptyMSG = {}; + +LazyLogModule sNativeKeyLogger("NativeKeyWidgets"); + +NativeKey::NativeKey(nsWindowBase* aWidget, + const MSG& aMessage, + const ModifierKeyState& aModKeyState, + HKL aOverrideKeyboardLayout, + nsTArray<FakeCharMsg>* aFakeCharMsgs) + : mLastInstance(sLatestInstance) + , mRemovingMsg(sEmptyMSG) + , mReceivedMsg(sEmptyMSG) + , mWidget(aWidget) + , mDispatcher(aWidget->GetTextEventDispatcher()) + , mMsg(aMessage) + , mFocusedWndBeforeDispatch(::GetFocus()) + , mDOMKeyCode(0) + , mKeyNameIndex(KEY_NAME_INDEX_Unidentified) + , mCodeNameIndex(CODE_NAME_INDEX_UNKNOWN) + , mModKeyState(aModKeyState) + , mVirtualKeyCode(0) + , mOriginalVirtualKeyCode(0) + , mShiftedLatinChar(0) + , mUnshiftedLatinChar(0) + , mScanCode(0) + , mIsExtended(false) + , mIsDeadKey(false) + , mCharMessageHasGone(false) + , mCanIgnoreModifierStateAtKeyPress(true) + , mFakeCharMsgs(aFakeCharMsgs && aFakeCharMsgs->Length() ? + aFakeCharMsgs : nullptr) +{ + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::NativeKey(aWidget=0x%p { GetWindowHandle()=0x%p }, " + "aMessage=%s, aModKeyState=%s), sLatestInstance=0x%p", + this, aWidget, aWidget->GetWindowHandle(), ToString(aMessage).get(), + ToString(aModKeyState).get(), sLatestInstance)); + + MOZ_ASSERT(aWidget); + MOZ_ASSERT(mDispatcher); + sLatestInstance = this; + KeyboardLayout* keyboardLayout = KeyboardLayout::GetInstance(); + mKeyboardLayout = keyboardLayout->GetLayout(); + if (aOverrideKeyboardLayout && mKeyboardLayout != aOverrideKeyboardLayout) { + keyboardLayout->OverrideLayout(aOverrideKeyboardLayout); + mKeyboardLayout = keyboardLayout->GetLayout(); + MOZ_ASSERT(mKeyboardLayout == aOverrideKeyboardLayout); + mIsOverridingKeyboardLayout = true; + } else { + mIsOverridingKeyboardLayout = false; + } + + if (mMsg.message == WM_APPCOMMAND) { + InitWithAppCommand(); + } else { + InitWithKeyChar(); + } + + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::NativeKey(), mKeyboardLayout=0x%08X, " + "mFocusedWndBeforeDispatch=0x%p, mDOMKeyCode=0x%04X, " + "mKeyNameIndex=%s, mCodeNameIndex=%s, mModKeyState=%s, " + "mVirtualKeyCode=%s, mOriginalVirtualKeyCode=%s, " + "mCommittedCharsAndModifiers=%s, mInputtingStringAndModifiers=%s, " + "mShiftedString=%s, mUnshiftedString=%s, mShiftedLatinChar=%s, " + "mUnshiftedLatinChar=%s, mScanCode=0x%04X, mIsExtended=%s, " + "mIsDeadKey=%s, mIsPrintableKey=%s, mCharMessageHasGone=%s, " + "mIsOverridingKeyboardLayout=%s", + this, mKeyboardLayout, mFocusedWndBeforeDispatch, + GetDOMKeyCodeName(mDOMKeyCode).get(), ToString(mKeyNameIndex).get(), + ToString(mCodeNameIndex).get(), + ToString(mModKeyState).get(), + GetVirtualKeyCodeName(mVirtualKeyCode).get(), + GetVirtualKeyCodeName(mOriginalVirtualKeyCode).get(), + ToString(mCommittedCharsAndModifiers).get(), + ToString(mInputtingStringAndModifiers).get(), + ToString(mShiftedString).get(), ToString(mUnshiftedString).get(), + GetCharacterCodeName(mShiftedLatinChar).get(), + GetCharacterCodeName(mUnshiftedLatinChar).get(), + mScanCode, GetBoolName(mIsExtended), GetBoolName(mIsDeadKey), + GetBoolName(mIsPrintableKey), GetBoolName(mCharMessageHasGone), + GetBoolName(mIsOverridingKeyboardLayout))); +} + +void +NativeKey::InitWithKeyChar() +{ + mScanCode = WinUtils::GetScanCode(mMsg.lParam); + mIsExtended = WinUtils::IsExtendedScanCode(mMsg.lParam); + switch (mMsg.message) { + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + case WM_KEYUP: + case WM_SYSKEYUP: + case MOZ_WM_KEYDOWN: + case MOZ_WM_KEYUP: { + // First, resolve the IME converted virtual keycode to its original + // keycode. + if (mMsg.wParam == VK_PROCESSKEY) { + mOriginalVirtualKeyCode = + static_cast<uint8_t>(::ImmGetVirtualKey(mMsg.hwnd)); + } else { + mOriginalVirtualKeyCode = static_cast<uint8_t>(mMsg.wParam); + } + + // If the key message is sent from other application like a11y tools, the + // scancode value might not be set proper value. Then, probably the value + // is 0. + // NOTE: If the virtual keycode can be caused by both non-extended key + // and extended key, the API returns the non-extended key's + // scancode. E.g., VK_LEFT causes "4" key on numpad. + if (!mScanCode && mOriginalVirtualKeyCode != VK_PACKET) { + uint16_t scanCodeEx = ComputeScanCodeExFromVirtualKeyCode(mMsg.wParam); + if (scanCodeEx) { + mScanCode = static_cast<uint8_t>(scanCodeEx & 0xFF); + uint8_t extended = static_cast<uint8_t>((scanCodeEx & 0xFF00) >> 8); + mIsExtended = (extended == 0xE0) || (extended == 0xE1); + } + } + + // Most keys are not distinguished as left or right keys. + bool isLeftRightDistinguishedKey = false; + + // mOriginalVirtualKeyCode must not distinguish left or right of + // Shift, Control or Alt. + switch (mOriginalVirtualKeyCode) { + case VK_SHIFT: + case VK_CONTROL: + case VK_MENU: + isLeftRightDistinguishedKey = true; + break; + case VK_LSHIFT: + case VK_RSHIFT: + mVirtualKeyCode = mOriginalVirtualKeyCode; + mOriginalVirtualKeyCode = VK_SHIFT; + isLeftRightDistinguishedKey = true; + break; + case VK_LCONTROL: + case VK_RCONTROL: + mVirtualKeyCode = mOriginalVirtualKeyCode; + mOriginalVirtualKeyCode = VK_CONTROL; + isLeftRightDistinguishedKey = true; + break; + case VK_LMENU: + case VK_RMENU: + mVirtualKeyCode = mOriginalVirtualKeyCode; + mOriginalVirtualKeyCode = VK_MENU; + isLeftRightDistinguishedKey = true; + break; + } + + // If virtual keycode (left-right distinguished keycode) is already + // computed, we don't need to do anymore. + if (mVirtualKeyCode) { + break; + } + + // If the keycode doesn't have LR distinguished keycode, we just set + // mOriginalVirtualKeyCode to mVirtualKeyCode. Note that don't compute + // it from MapVirtualKeyEx() because the scan code might be wrong if + // the message is sent/posted by other application. Then, we will compute + // unexpected keycode from the scan code. + if (!isLeftRightDistinguishedKey) { + break; + } + + if (!CanComputeVirtualKeyCodeFromScanCode()) { + // The right control key and the right alt key are extended keys. + // Therefore, we never get VK_RCONTRL and VK_RMENU for the result of + // MapVirtualKeyEx() on WinXP or WinServer2003. + // + // If VK_SHIFT, VK_CONTROL or VK_MENU key message is caused by well + // known scan code, we should decide it as Right key. Otherwise, + // decide it as Left key. + switch (mOriginalVirtualKeyCode) { + case VK_CONTROL: + mVirtualKeyCode = + mIsExtended && mScanCode == 0x1D ? VK_RCONTROL : VK_LCONTROL; + break; + case VK_MENU: + mVirtualKeyCode = + mIsExtended && mScanCode == 0x38 ? VK_RMENU : VK_LMENU; + break; + case VK_SHIFT: + // Neither left shift nor right shift is an extended key, + // let's use VK_LSHIFT for unknown mapping. + mVirtualKeyCode = VK_LSHIFT; + break; + default: + MOZ_CRASH("Unsupported mOriginalVirtualKeyCode"); + } + break; + } + + NS_ASSERTION(!mVirtualKeyCode, + "mVirtualKeyCode has been computed already"); + + // Otherwise, compute the virtual keycode with MapVirtualKeyEx(). + mVirtualKeyCode = ComputeVirtualKeyCodeFromScanCodeEx(); + + // Following code shouldn't be used now because we compute scancode value + // if we detect that the sender doesn't set proper scancode. + // However, the detection might fail. Therefore, let's keep using this. + switch (mOriginalVirtualKeyCode) { + case VK_CONTROL: + if (mVirtualKeyCode != VK_LCONTROL && + mVirtualKeyCode != VK_RCONTROL) { + mVirtualKeyCode = mIsExtended ? VK_RCONTROL : VK_LCONTROL; + } + break; + case VK_MENU: + if (mVirtualKeyCode != VK_LMENU && mVirtualKeyCode != VK_RMENU) { + mVirtualKeyCode = mIsExtended ? VK_RMENU : VK_LMENU; + } + break; + case VK_SHIFT: + if (mVirtualKeyCode != VK_LSHIFT && mVirtualKeyCode != VK_RSHIFT) { + // Neither left shift nor right shift is an extended key, + // let's use VK_LSHIFT for unknown mapping. + mVirtualKeyCode = VK_LSHIFT; + } + break; + default: + MOZ_CRASH("Unsupported mOriginalVirtualKeyCode"); + } + break; + } + case WM_CHAR: + case WM_UNICHAR: + case WM_SYSCHAR: + // If there is another instance and it is trying to remove a char message + // from the queue, this message should be handled in the old instance. + if (IsAnotherInstanceRemovingCharMessage()) { + // XXX Do we need to make mReceivedMsg an array? + MOZ_ASSERT(IsEmptyMSG(mLastInstance->mReceivedMsg)); + MOZ_LOG(sNativeKeyLogger, LogLevel::Warning, + ("%p NativeKey::InitWithKeyChar(), WARNING, detecting another " + "instance is trying to remove a char message, so, this instance " + "should do nothing, mLastInstance=0x%p, mRemovingMsg=%s, " + "mReceivedMsg=%s", + this, mLastInstance, ToString(mLastInstance->mRemovingMsg).get(), + ToString(mLastInstance->mReceivedMsg).get())); + mLastInstance->mReceivedMsg = mMsg; + return; + } + + // NOTE: If other applications like a11y tools sends WM_*CHAR without + // scancode, we cannot compute virtual keycode. I.e., with such + // applications, we cannot generate proper KeyboardEvent.code value. + + // We cannot compute the virtual key code from WM_CHAR message on WinXP + // if it's caused by an extended key. + if (!CanComputeVirtualKeyCodeFromScanCode()) { + break; + } + mVirtualKeyCode = mOriginalVirtualKeyCode = + ComputeVirtualKeyCodeFromScanCodeEx(); + NS_ASSERTION(mVirtualKeyCode, "Failed to compute virtual keycode"); + break; + default: + MOZ_CRASH("Unsupported message"); + } + + if (!mVirtualKeyCode) { + mVirtualKeyCode = mOriginalVirtualKeyCode; + } + + KeyboardLayout* keyboardLayout = KeyboardLayout::GetInstance(); + mDOMKeyCode = + keyboardLayout->ConvertNativeKeyCodeToDOMKeyCode(mOriginalVirtualKeyCode); + // Be aware, keyboard utilities can change non-printable keys to printable + // keys. In such case, we should make the key value as a printable key. + // FYI: IsFollowedByPrintableCharMessage() returns true only when it's + // handling a keydown message. + mKeyNameIndex = IsFollowedByPrintableCharMessage() ? + KEY_NAME_INDEX_USE_STRING : + keyboardLayout->ConvertNativeKeyCodeToKeyNameIndex(mOriginalVirtualKeyCode); + mCodeNameIndex = + KeyboardLayout::ConvertScanCodeToCodeNameIndex( + GetScanCodeWithExtendedFlag()); + + // If next message of WM_(SYS)KEYDOWN is WM_*CHAR message and the key + // combination is not reserved by the system, let's consume it now. + // TODO: We cannot initialize mCommittedCharsAndModifiers for VK_PACKET + // if the message is WM_KEYUP because we don't have preceding + // WM_CHAR message. + // TODO: Like Edge, we shouldn't dispatch two sets of keyboard events + // for a Unicode character in non-BMP because its key value looks + // broken and not good thing for our editor if only one keydown or + // keypress event's default is prevented. I guess, we should store + // key message information globally and we should wait following + // WM_KEYDOWN if following WM_CHAR is a part of a Unicode character. + if ((mMsg.message == WM_KEYDOWN || mMsg.message == WM_SYSKEYDOWN) && + !IsReservedBySystem()) { + MSG charMsg; + while (GetFollowingCharMessage(charMsg)) { + // Although, got message shouldn't be WM_NULL in desktop apps, + // we should keep checking this. FYI: This was added for Metrofox. + if (charMsg.message == WM_NULL) { + continue; + } + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::InitWithKeyChar(), removed char message, %s", + this, ToString(charMsg).get())); + NS_WARN_IF(charMsg.hwnd != mMsg.hwnd); + mFollowingCharMsgs.AppendElement(charMsg); + } + } + + keyboardLayout->InitNativeKey(*this, mModKeyState); + + mIsDeadKey = + (IsFollowedByDeadCharMessage() || + keyboardLayout->IsDeadKey(mOriginalVirtualKeyCode, mModKeyState)); + mIsPrintableKey = + mKeyNameIndex == KEY_NAME_INDEX_USE_STRING || + KeyboardLayout::IsPrintableCharKey(mOriginalVirtualKeyCode); + + if (IsKeyDownMessage()) { + // Compute some strings which may be inputted by the key with various + // modifier state if this key event won't cause text input actually. + // They will be used for setting mAlternativeCharCodes in the callback + // method which will be called by TextEventDispatcher. + if (!IsFollowedByPrintableCharMessage()) { + ComputeInputtingStringWithKeyboardLayout(); + } + // Remove odd char messages if there are. + RemoveFollowingOddCharMessages(); + } +} + +void +NativeKey::InitCommittedCharsAndModifiersWithFollowingCharMessages( + const ModifierKeyState& aModKeyState) +{ + mCommittedCharsAndModifiers.Clear(); + // This should cause inputting text in focused editor. However, it + // ignores keypress events whose altKey or ctrlKey is true. + // Therefore, we need to remove these modifier state here. + Modifiers modifiers = aModKeyState.GetModifiers(); + if (IsFollowedByPrintableCharMessage()) { + modifiers &= ~(MODIFIER_ALT | MODIFIER_CONTROL); + } + // NOTE: This method assumes that WM_CHAR and WM_SYSCHAR are never retrieved + // at same time. + for (size_t i = 0; i < mFollowingCharMsgs.Length(); ++i) { + // Ignore non-printable char messages. + if (!IsPrintableCharOrSysCharMessage(mFollowingCharMsgs[i])) { + continue; + } + char16_t ch = static_cast<char16_t>(mFollowingCharMsgs[i].wParam); + mCommittedCharsAndModifiers.Append(ch, modifiers); + } +} + +NativeKey::~NativeKey() +{ + MOZ_LOG(sNativeKeyLogger, LogLevel::Debug, + ("%p NativeKey::~NativeKey(), destroyed", this)); + if (mIsOverridingKeyboardLayout) { + KeyboardLayout* keyboardLayout = KeyboardLayout::GetInstance(); + keyboardLayout->RestoreLayout(); + } + sLatestInstance = mLastInstance; +} + +void +NativeKey::InitWithAppCommand() +{ + if (GET_DEVICE_LPARAM(mMsg.lParam) != FAPPCOMMAND_KEY) { + return; + } + + uint32_t appCommand = GET_APPCOMMAND_LPARAM(mMsg.lParam); + switch (GET_APPCOMMAND_LPARAM(mMsg.lParam)) { + +#undef NS_APPCOMMAND_TO_DOM_KEY_NAME_INDEX +#define NS_APPCOMMAND_TO_DOM_KEY_NAME_INDEX(aAppCommand, aKeyNameIndex) \ + case aAppCommand: \ + mKeyNameIndex = aKeyNameIndex; \ + break; + +#include "NativeKeyToDOMKeyName.h" + +#undef NS_APPCOMMAND_TO_DOM_KEY_NAME_INDEX + + default: + mKeyNameIndex = KEY_NAME_INDEX_Unidentified; + } + + // Guess the virtual keycode which caused this message. + switch (appCommand) { + case APPCOMMAND_BROWSER_BACKWARD: + mVirtualKeyCode = mOriginalVirtualKeyCode = VK_BROWSER_BACK; + break; + case APPCOMMAND_BROWSER_FORWARD: + mVirtualKeyCode = mOriginalVirtualKeyCode = VK_BROWSER_FORWARD; + break; + case APPCOMMAND_BROWSER_REFRESH: + mVirtualKeyCode = mOriginalVirtualKeyCode = VK_BROWSER_REFRESH; + break; + case APPCOMMAND_BROWSER_STOP: + mVirtualKeyCode = mOriginalVirtualKeyCode = VK_BROWSER_STOP; + break; + case APPCOMMAND_BROWSER_SEARCH: + mVirtualKeyCode = mOriginalVirtualKeyCode = VK_BROWSER_SEARCH; + break; + case APPCOMMAND_BROWSER_FAVORITES: + mVirtualKeyCode = mOriginalVirtualKeyCode = VK_BROWSER_FAVORITES; + break; + case APPCOMMAND_BROWSER_HOME: + mVirtualKeyCode = mOriginalVirtualKeyCode = VK_BROWSER_HOME; + break; + case APPCOMMAND_VOLUME_MUTE: + mVirtualKeyCode = mOriginalVirtualKeyCode = VK_VOLUME_MUTE; + break; + case APPCOMMAND_VOLUME_DOWN: + mVirtualKeyCode = mOriginalVirtualKeyCode = VK_VOLUME_DOWN; + break; + case APPCOMMAND_VOLUME_UP: + mVirtualKeyCode = mOriginalVirtualKeyCode = VK_VOLUME_UP; + break; + case APPCOMMAND_MEDIA_NEXTTRACK: + mVirtualKeyCode = mOriginalVirtualKeyCode = VK_MEDIA_NEXT_TRACK; + break; + case APPCOMMAND_MEDIA_PREVIOUSTRACK: + mVirtualKeyCode = mOriginalVirtualKeyCode = VK_MEDIA_PREV_TRACK; + break; + case APPCOMMAND_MEDIA_STOP: + mVirtualKeyCode = mOriginalVirtualKeyCode = VK_MEDIA_STOP; + break; + case APPCOMMAND_MEDIA_PLAY_PAUSE: + mVirtualKeyCode = mOriginalVirtualKeyCode = VK_MEDIA_PLAY_PAUSE; + break; + case APPCOMMAND_LAUNCH_MAIL: + mVirtualKeyCode = mOriginalVirtualKeyCode = VK_LAUNCH_MAIL; + break; + case APPCOMMAND_LAUNCH_MEDIA_SELECT: + mVirtualKeyCode = mOriginalVirtualKeyCode = VK_LAUNCH_MEDIA_SELECT; + break; + case APPCOMMAND_LAUNCH_APP1: + mVirtualKeyCode = mOriginalVirtualKeyCode = VK_LAUNCH_APP1; + break; + case APPCOMMAND_LAUNCH_APP2: + mVirtualKeyCode = mOriginalVirtualKeyCode = VK_LAUNCH_APP2; + break; + default: + return; + } + + uint16_t scanCodeEx = ComputeScanCodeExFromVirtualKeyCode(mVirtualKeyCode); + mScanCode = static_cast<uint8_t>(scanCodeEx & 0xFF); + uint8_t extended = static_cast<uint8_t>((scanCodeEx & 0xFF00) >> 8); + mIsExtended = (extended == 0xE0) || (extended == 0xE1); + mDOMKeyCode = + KeyboardLayout::GetInstance()-> + ConvertNativeKeyCodeToDOMKeyCode(mOriginalVirtualKeyCode); + mCodeNameIndex = + KeyboardLayout::ConvertScanCodeToCodeNameIndex( + GetScanCodeWithExtendedFlag()); +} + +// static +bool +NativeKey::IsControlChar(char16_t aChar) +{ + static const char16_t U_SPACE = 0x20; + static const char16_t U_DELETE = 0x7F; + return aChar < U_SPACE || aChar == U_DELETE; +} + +bool +NativeKey::IsFollowedByDeadCharMessage() const +{ + if (mFollowingCharMsgs.IsEmpty()) { + return false; + } + return IsDeadCharMessage(mFollowingCharMsgs[0]); +} + +bool +NativeKey::IsFollowedByPrintableCharMessage() const +{ + for (size_t i = 0; i < mFollowingCharMsgs.Length(); ++i) { + if (IsPrintableCharMessage(mFollowingCharMsgs[i])) { + return true; + } + } + return false; +} + +bool +NativeKey::IsFollowedByPrintableCharOrSysCharMessage() const +{ + for (size_t i = 0; i < mFollowingCharMsgs.Length(); ++i) { + if (IsPrintableCharOrSysCharMessage(mFollowingCharMsgs[i])) { + return true; + } + } + return false; +} + +bool +NativeKey::IsReservedBySystem() const +{ + // Alt+Space key is handled by OS, we shouldn't touch it. + if (mModKeyState.IsAlt() && !mModKeyState.IsControl() && + mVirtualKeyCode == VK_SPACE) { + return true; + } + + // XXX How about Alt+F4? We receive WM_SYSKEYDOWN for F4 before closing the + // window. Although, we don't prevent to close the window but the key + // event shouldn't be exposed to the web. + + return false; +} + +bool +NativeKey::IsIMEDoingKakuteiUndo() const +{ + // Following message pattern is caused by "Kakutei-Undo" of ATOK or WXG: + // --------------------------------------------------------------------------- + // WM_KEYDOWN * n (wParam = VK_BACK, lParam = 0x1) + // WM_KEYUP * 1 (wParam = VK_BACK, lParam = 0xC0000001) # ATOK + // WM_IME_STARTCOMPOSITION * 1 (wParam = 0x0, lParam = 0x0) + // WM_IME_COMPOSITION * 1 (wParam = 0x0, lParam = 0x1BF) + // WM_CHAR * n (wParam = VK_BACK, lParam = 0x1) + // WM_KEYUP * 1 (wParam = VK_BACK, lParam = 0xC00E0001) + // --------------------------------------------------------------------------- + // This doesn't match usual key message pattern such as: + // WM_KEYDOWN -> WM_CHAR -> WM_KEYDOWN -> WM_CHAR -> ... -> WM_KEYUP + // See following bugs for the detail. + // https://bugzilla.mozilla.gr.jp/show_bug.cgi?id=2885 (written in Japanese) + // https://bugzilla.mozilla.org/show_bug.cgi?id=194559 (written in English) + MSG startCompositionMsg, compositionMsg, charMsg; + return WinUtils::PeekMessage(&startCompositionMsg, mMsg.hwnd, + WM_IME_STARTCOMPOSITION, WM_IME_STARTCOMPOSITION, + PM_NOREMOVE | PM_NOYIELD) && + WinUtils::PeekMessage(&compositionMsg, mMsg.hwnd, WM_IME_COMPOSITION, + WM_IME_COMPOSITION, PM_NOREMOVE | PM_NOYIELD) && + WinUtils::PeekMessage(&charMsg, mMsg.hwnd, WM_CHAR, WM_CHAR, + PM_NOREMOVE | PM_NOYIELD) && + startCompositionMsg.wParam == 0x0 && + startCompositionMsg.lParam == 0x0 && + compositionMsg.wParam == 0x0 && + compositionMsg.lParam == 0x1BF && + charMsg.wParam == VK_BACK && charMsg.lParam == 0x1 && + startCompositionMsg.time <= compositionMsg.time && + compositionMsg.time <= charMsg.time; +} + +void +NativeKey::RemoveFollowingOddCharMessages() +{ + MOZ_ASSERT(IsKeyDownMessage()); + + // If the keydown message is synthesized for automated tests, there is + // nothing to do here. + if (mFakeCharMsgs) { + return; + } + + // If there are some following char messages before another key message, + // there is nothing to do here. + if (!mFollowingCharMsgs.IsEmpty()) { + return; + } + + // If the handling key isn't Backspace, there is nothing to do here. + if (mOriginalVirtualKeyCode != VK_BACK) { + return; + } + + // If we don't see the odd message pattern, there is nothing to do here. + if (!IsIMEDoingKakuteiUndo()) { + return; + } + + // Otherwise, we need to remove odd WM_CHAR messages for ATOK or WXG (both + // of them are Japanese IME). + MSG msg; + while (WinUtils::PeekMessage(&msg, mMsg.hwnd, WM_CHAR, WM_CHAR, + PM_REMOVE | PM_NOYIELD)) { + if (msg.message != WM_CHAR) { + MOZ_RELEASE_ASSERT(msg.message == WM_NULL, + "Unexpected message was removed"); + continue; + } + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::RemoveFollowingOddCharMessages(), removed odd char " + "message, %s", + this, ToString(msg).get())); + mRemovedOddCharMsgs.AppendElement(msg); + } +} + +UINT +NativeKey::GetScanCodeWithExtendedFlag() const +{ + if (!mIsExtended) { + return mScanCode; + } + return (0xE000 | mScanCode); +} + +uint32_t +NativeKey::GetKeyLocation() const +{ + switch (mVirtualKeyCode) { + case VK_LSHIFT: + case VK_LCONTROL: + case VK_LMENU: + case VK_LWIN: + return nsIDOMKeyEvent::DOM_KEY_LOCATION_LEFT; + + case VK_RSHIFT: + case VK_RCONTROL: + case VK_RMENU: + case VK_RWIN: + return nsIDOMKeyEvent::DOM_KEY_LOCATION_RIGHT; + + case VK_RETURN: + // XXX This code assumes that all keyboard drivers use same mapping. + return !mIsExtended ? nsIDOMKeyEvent::DOM_KEY_LOCATION_STANDARD : + nsIDOMKeyEvent::DOM_KEY_LOCATION_NUMPAD; + + case VK_INSERT: + case VK_DELETE: + case VK_END: + case VK_DOWN: + case VK_NEXT: + case VK_LEFT: + case VK_CLEAR: + case VK_RIGHT: + case VK_HOME: + case VK_UP: + case VK_PRIOR: + // XXX This code assumes that all keyboard drivers use same mapping. + return mIsExtended ? nsIDOMKeyEvent::DOM_KEY_LOCATION_STANDARD : + nsIDOMKeyEvent::DOM_KEY_LOCATION_NUMPAD; + + // NumLock key isn't included due to IE9's behavior. + case VK_NUMPAD0: + case VK_NUMPAD1: + case VK_NUMPAD2: + case VK_NUMPAD3: + case VK_NUMPAD4: + case VK_NUMPAD5: + case VK_NUMPAD6: + case VK_NUMPAD7: + case VK_NUMPAD8: + case VK_NUMPAD9: + case VK_DECIMAL: + case VK_DIVIDE: + case VK_MULTIPLY: + case VK_SUBTRACT: + case VK_ADD: + // Separator key of Brazilian keyboard or JIS keyboard for Mac + case VK_ABNT_C2: + return nsIDOMKeyEvent::DOM_KEY_LOCATION_NUMPAD; + + case VK_SHIFT: + case VK_CONTROL: + case VK_MENU: + NS_WARNING("Failed to decide the key location?"); + + default: + return nsIDOMKeyEvent::DOM_KEY_LOCATION_STANDARD; + } +} + +bool +NativeKey::CanComputeVirtualKeyCodeFromScanCode() const +{ + // Vista or later supports ScanCodeEx. + if (IsVistaOrLater()) { + return true; + } + // Otherwise, MapVirtualKeyEx() can compute virtual keycode only with + // non-extended key. + return !mIsExtended; +} + +uint8_t +NativeKey::ComputeVirtualKeyCodeFromScanCode() const +{ + return static_cast<uint8_t>( + ::MapVirtualKeyEx(mScanCode, MAPVK_VSC_TO_VK, mKeyboardLayout)); +} + +uint8_t +NativeKey::ComputeVirtualKeyCodeFromScanCodeEx() const +{ + // MapVirtualKeyEx() has been improved for supporting extended keys since + // Vista. When we call it for mapping a scancode of an extended key and + // a virtual keycode, we need to add 0xE000 to the scancode. + // On the other hand, neither WinXP nor WinServer2003 doesn't support 0xE000. + // Therefore, we have no way to get virtual keycode from scan code of + // extended keys. + if (NS_WARN_IF(!CanComputeVirtualKeyCodeFromScanCode())) { + return 0; + } + return static_cast<uint8_t>( + ::MapVirtualKeyEx(GetScanCodeWithExtendedFlag(), MAPVK_VSC_TO_VK_EX, + mKeyboardLayout)); +} + +uint16_t +NativeKey::ComputeScanCodeExFromVirtualKeyCode(UINT aVirtualKeyCode) const +{ + return static_cast<uint16_t>( + ::MapVirtualKeyEx(aVirtualKeyCode, + IsVistaOrLater() ? MAPVK_VK_TO_VSC_EX : + MAPVK_VK_TO_VSC, + mKeyboardLayout)); +} + +char16_t +NativeKey::ComputeUnicharFromScanCode() const +{ + return static_cast<char16_t>( + ::MapVirtualKeyEx(ComputeVirtualKeyCodeFromScanCode(), + MAPVK_VK_TO_CHAR, mKeyboardLayout)); +} + +nsEventStatus +NativeKey::InitKeyEvent(WidgetKeyboardEvent& aKeyEvent, + const MSG* aMsgSentToPlugin) const +{ + return InitKeyEvent(aKeyEvent, mModKeyState, aMsgSentToPlugin); +} + +nsEventStatus +NativeKey::InitKeyEvent(WidgetKeyboardEvent& aKeyEvent, + const ModifierKeyState& aModKeyState, + const MSG* aMsgSentToPlugin) const +{ + if (mWidget->Destroyed()) { + MOZ_CRASH("NativeKey tries to dispatch a key event on destroyed widget"); + } + + LayoutDeviceIntPoint point(0, 0); + mWidget->InitEvent(aKeyEvent, &point); + + switch (aKeyEvent.mMessage) { + case eKeyDown: + // If it was followed by a char message but it was consumed by somebody, + // we should mark it as consumed because somebody must have handled it + // and we should prevent to do "double action" for the key operation. + if (mCharMessageHasGone) { + aKeyEvent.PreventDefaultBeforeDispatch(); + } + MOZ_FALLTHROUGH; + case eKeyDownOnPlugin: + aKeyEvent.mKeyCode = mDOMKeyCode; + // Unique id for this keydown event and its associated keypress. + sUniqueKeyEventId++; + aKeyEvent.mUniqueId = sUniqueKeyEventId; + break; + case eKeyUp: + case eKeyUpOnPlugin: + aKeyEvent.mKeyCode = mDOMKeyCode; + // Set defaultPrevented of the key event if the VK_MENU is not a system + // key release, so that the menu bar does not trigger. This helps avoid + // triggering the menu bar for ALT key accelerators used in assistive + // technologies such as Window-Eyes and ZoomText or for switching open + // state of IME. + if (mOriginalVirtualKeyCode == VK_MENU && mMsg.message != WM_SYSKEYUP) { + aKeyEvent.PreventDefaultBeforeDispatch(); + } + break; + case eKeyPress: + MOZ_ASSERT(!mCharMessageHasGone, + "If following char message was consumed by somebody, " + "keydown event should be consumed above"); + aKeyEvent.mUniqueId = sUniqueKeyEventId; + break; + default: + MOZ_CRASH("Invalid event message"); + } + + aKeyEvent.mIsRepeat = IsRepeat(); + aKeyEvent.mKeyNameIndex = mKeyNameIndex; + if (mKeyNameIndex == KEY_NAME_INDEX_USE_STRING) { + aKeyEvent.mKeyValue = mCommittedCharsAndModifiers.ToString(); + } + aKeyEvent.mCodeNameIndex = mCodeNameIndex; + MOZ_ASSERT(mCodeNameIndex != CODE_NAME_INDEX_USE_STRING); + aKeyEvent.mLocation = GetKeyLocation(); + aModKeyState.InitInputEvent(aKeyEvent); + + if (aMsgSentToPlugin) { + MaybeInitPluginEventOfKeyEvent(aKeyEvent, *aMsgSentToPlugin); + } + + KeyboardLayout::NotifyIdleServiceOfUserActivity(); + + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::InitKeyEvent(), initialized, aKeyEvent={ " + "mMessage=%s, mKeyNameIndex=%s, mKeyValue=\"%s\", mCodeNameIndex=%s, " + "mKeyCode=%s, mLocation=%s, mModifiers=%s, DefaultPrevented()=%s }", + this, ToChar(aKeyEvent.mMessage), + ToString(aKeyEvent.mKeyNameIndex).get(), + NS_ConvertUTF16toUTF8(aKeyEvent.mKeyValue).get(), + ToString(aKeyEvent.mCodeNameIndex).get(), + GetDOMKeyCodeName(aKeyEvent.mKeyCode).get(), + GetKeyLocationName(aKeyEvent.mLocation).get(), + GetModifiersName(aKeyEvent.mModifiers).get(), + GetBoolName(aKeyEvent.DefaultPrevented()))); + + return aKeyEvent.DefaultPrevented() ? nsEventStatus_eConsumeNoDefault : + nsEventStatus_eIgnore; +} + +void +NativeKey::MaybeInitPluginEventOfKeyEvent(WidgetKeyboardEvent& aKeyEvent, + const MSG& aMsgSentToPlugin) const +{ + if (mWidget->GetInputContext().mIMEState.mEnabled != IMEState::PLUGIN) { + return; + } + NPEvent pluginEvent; + pluginEvent.event = aMsgSentToPlugin.message; + pluginEvent.wParam = aMsgSentToPlugin.wParam; + pluginEvent.lParam = aMsgSentToPlugin.lParam; + aKeyEvent.mPluginEvent.Copy(pluginEvent); +} + +bool +NativeKey::DispatchCommandEvent(uint32_t aEventCommand) const +{ + nsCOMPtr<nsIAtom> command; + switch (aEventCommand) { + case APPCOMMAND_BROWSER_BACKWARD: + command = nsGkAtoms::Back; + break; + case APPCOMMAND_BROWSER_FORWARD: + command = nsGkAtoms::Forward; + break; + case APPCOMMAND_BROWSER_REFRESH: + command = nsGkAtoms::Reload; + break; + case APPCOMMAND_BROWSER_STOP: + command = nsGkAtoms::Stop; + break; + case APPCOMMAND_BROWSER_SEARCH: + command = nsGkAtoms::Search; + break; + case APPCOMMAND_BROWSER_FAVORITES: + command = nsGkAtoms::Bookmarks; + break; + case APPCOMMAND_BROWSER_HOME: + command = nsGkAtoms::Home; + break; + case APPCOMMAND_CLOSE: + command = nsGkAtoms::Close; + break; + case APPCOMMAND_FIND: + command = nsGkAtoms::Find; + break; + case APPCOMMAND_HELP: + command = nsGkAtoms::Help; + break; + case APPCOMMAND_NEW: + command = nsGkAtoms::New; + break; + case APPCOMMAND_OPEN: + command = nsGkAtoms::Open; + break; + case APPCOMMAND_PRINT: + command = nsGkAtoms::Print; + break; + case APPCOMMAND_SAVE: + command = nsGkAtoms::Save; + break; + case APPCOMMAND_FORWARD_MAIL: + command = nsGkAtoms::ForwardMail; + break; + case APPCOMMAND_REPLY_TO_MAIL: + command = nsGkAtoms::ReplyToMail; + break; + case APPCOMMAND_SEND_MAIL: + command = nsGkAtoms::SendMail; + break; + case APPCOMMAND_MEDIA_NEXTTRACK: + command = nsGkAtoms::NextTrack; + break; + case APPCOMMAND_MEDIA_PREVIOUSTRACK: + command = nsGkAtoms::PreviousTrack; + break; + case APPCOMMAND_MEDIA_STOP: + command = nsGkAtoms::MediaStop; + break; + case APPCOMMAND_MEDIA_PLAY_PAUSE: + command = nsGkAtoms::PlayPause; + break; + default: + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::DispatchCommandEvent(), doesn't dispatch command " + "event", this)); + return false; + } + WidgetCommandEvent commandEvent(true, nsGkAtoms::onAppCommand, + command, mWidget); + + mWidget->InitEvent(commandEvent); + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::DispatchCommandEvent(), dispatching %s command event...", + this, nsAtomCString(command).get())); + bool ok = mWidget->DispatchWindowEvent(&commandEvent) || mWidget->Destroyed(); + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::DispatchCommandEvent(), dispatched command event, " + "result=%s, mWidget->Destroyed()=%s", + this, GetBoolName(ok), GetBoolName(mWidget->Destroyed()))); + return ok; +} + +bool +NativeKey::HandleAppCommandMessage() const +{ + // If the widget has gone, we should do nothing. + if (mWidget->Destroyed()) { + MOZ_LOG(sNativeKeyLogger, LogLevel::Warning, + ("%p NativeKey::HandleAppCommandMessage(), WARNING, not handled due to " + "destroyed the widget", this)); + return false; + } + + // NOTE: Typical behavior of WM_APPCOMMAND caused by key is, WM_APPCOMMAND + // message is _sent_ first. Then, the DefaultWndProc will _post_ + // WM_KEYDOWN message and WM_KEYUP message if the keycode for the + // command is available (i.e., mVirtualKeyCode is not 0). + + // NOTE: IntelliType (Microsoft's keyboard utility software) always consumes + // WM_KEYDOWN and WM_KEYUP. + + // Let's dispatch keydown message before our chrome handles the command + // when the message is caused by a keypress. This behavior makes handling + // WM_APPCOMMAND be a default action of the keydown event. This means that + // web applications can handle multimedia keys and prevent our default action. + // This allow web applications to provide better UX for multimedia keyboard + // users. + bool dispatchKeyEvent = (GET_DEVICE_LPARAM(mMsg.lParam) == FAPPCOMMAND_KEY); + if (dispatchKeyEvent) { + // If a plug-in window has focus but it didn't consume the message, our + // window receive WM_APPCOMMAND message. In this case, we shouldn't + // dispatch KeyboardEvents because an event handler may access the + // plug-in process synchronously. + dispatchKeyEvent = + WinUtils::IsOurProcessWindow(reinterpret_cast<HWND>(mMsg.wParam)); + } + + bool consumed = false; + + if (dispatchKeyEvent) { + nsresult rv = mDispatcher->BeginNativeInputTransaction(); + if (NS_WARN_IF(NS_FAILED(rv))) { + MOZ_LOG(sNativeKeyLogger, LogLevel::Error, + ("%p NativeKey::HandleAppCommandMessage(), FAILED due to " + "BeginNativeInputTransaction() failure", this)); + return true; + } + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::HandleAppCommandMessage(), initializing keydown " + "event...", this)); + WidgetKeyboardEvent keydownEvent(true, eKeyDown, mWidget); + nsEventStatus status = InitKeyEvent(keydownEvent, mModKeyState, &mMsg); + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::HandleAppCommandMessage(), tries to dispatch " + "keydown event...", this)); + // NOTE: If the keydown event is consumed by web contents, we shouldn't + // continue to handle the command. + if (!mDispatcher->DispatchKeyboardEvent(eKeyDown, keydownEvent, status, + const_cast<NativeKey*>(this))) { + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::HandleAppCommandMessage(), keydown event isn't " + "dispatched", this)); + // If keyboard event wasn't fired, there must be composition. + // So, we don't need to dispatch a command event. + return true; + } + consumed = status == nsEventStatus_eConsumeNoDefault; + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::HandleAppCommandMessage(), keydown event was " + "dispatched, consumed=%s", + this, GetBoolName(consumed))); + sDispatchedKeyOfAppCommand = mVirtualKeyCode; + if (mWidget->Destroyed()) { + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::HandleAppCommandMessage(), keydown event caused " + "destroying the widget", this)); + return true; + } + } + + // Dispatch a command event or a content command event if the command is + // supported. + if (!consumed) { + uint32_t appCommand = GET_APPCOMMAND_LPARAM(mMsg.lParam); + EventMessage contentCommandMessage = eVoidEvent; + switch (appCommand) { + case APPCOMMAND_BROWSER_BACKWARD: + case APPCOMMAND_BROWSER_FORWARD: + case APPCOMMAND_BROWSER_REFRESH: + case APPCOMMAND_BROWSER_STOP: + case APPCOMMAND_BROWSER_SEARCH: + case APPCOMMAND_BROWSER_FAVORITES: + case APPCOMMAND_BROWSER_HOME: + case APPCOMMAND_CLOSE: + case APPCOMMAND_FIND: + case APPCOMMAND_HELP: + case APPCOMMAND_NEW: + case APPCOMMAND_OPEN: + case APPCOMMAND_PRINT: + case APPCOMMAND_SAVE: + case APPCOMMAND_FORWARD_MAIL: + case APPCOMMAND_REPLY_TO_MAIL: + case APPCOMMAND_SEND_MAIL: + case APPCOMMAND_MEDIA_NEXTTRACK: + case APPCOMMAND_MEDIA_PREVIOUSTRACK: + case APPCOMMAND_MEDIA_STOP: + case APPCOMMAND_MEDIA_PLAY_PAUSE: + // We shouldn't consume the message always because if we don't handle + // the message, the sender (typically, utility of keyboard or mouse) + // may send other key messages which indicate well known shortcut key. + consumed = DispatchCommandEvent(appCommand); + break; + + // Use content command for following commands: + case APPCOMMAND_COPY: + contentCommandMessage = eContentCommandCopy; + break; + case APPCOMMAND_CUT: + contentCommandMessage = eContentCommandCut; + break; + case APPCOMMAND_PASTE: + contentCommandMessage = eContentCommandPaste; + break; + case APPCOMMAND_REDO: + contentCommandMessage = eContentCommandRedo; + break; + case APPCOMMAND_UNDO: + contentCommandMessage = eContentCommandUndo; + break; + } + + if (contentCommandMessage) { + MOZ_ASSERT(!mWidget->Destroyed()); + WidgetContentCommandEvent contentCommandEvent(true, contentCommandMessage, + mWidget); + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::HandleAppCommandMessage(), dispatching %s event...", + this, ToChar(contentCommandMessage))); + mWidget->DispatchWindowEvent(&contentCommandEvent); + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::HandleAppCommandMessage(), dispatched %s event", + this, ToChar(contentCommandMessage))); + consumed = true; + + if (mWidget->Destroyed()) { + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::HandleAppCommandMessage(), %s event caused " + "destroying the widget", + this, ToChar(contentCommandMessage))); + return true; + } + } else { + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::HandleAppCommandMessage(), doesn't dispatch content " + "command event", this)); + } + } + + // Dispatch a keyup event if the command is caused by pressing a key and + // the key isn't mapped to a virtual keycode. + if (dispatchKeyEvent && !mVirtualKeyCode) { + MOZ_ASSERT(!mWidget->Destroyed()); + nsresult rv = mDispatcher->BeginNativeInputTransaction(); + if (NS_WARN_IF(NS_FAILED(rv))) { + MOZ_LOG(sNativeKeyLogger, LogLevel::Error, + ("%p NativeKey::HandleAppCommandMessage(), FAILED due to " + "BeginNativeInputTransaction() failure", this)); + return true; + } + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::HandleAppCommandMessage(), initializing keyup " + "event...", this)); + WidgetKeyboardEvent keyupEvent(true, eKeyUp, mWidget); + nsEventStatus status = InitKeyEvent(keyupEvent, mModKeyState, &mMsg); + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::HandleAppCommandMessage(), dispatching keyup event...", + this)); + // NOTE: Ignore if the keyup event is consumed because keyup event + // represents just a physical key event state change. + mDispatcher->DispatchKeyboardEvent(eKeyUp, keyupEvent, status, + const_cast<NativeKey*>(this)); + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::HandleAppCommandMessage(), dispatched keyup event", + this)); + if (mWidget->Destroyed()) { + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::HandleAppCommandMessage(), %s event caused " + "destroying the widget", this)); + return true; + } + } + + return consumed; +} + +bool +NativeKey::HandleKeyDownMessage(bool* aEventDispatched) const +{ + MOZ_ASSERT(IsKeyDownMessage()); + + if (aEventDispatched) { + *aEventDispatched = false; + } + + if (sDispatchedKeyOfAppCommand && + sDispatchedKeyOfAppCommand == mOriginalVirtualKeyCode) { + // The multimedia key event has already been dispatch from + // HandleAppCommandMessage(). + sDispatchedKeyOfAppCommand = 0; + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::HandleKeyDownMessage(), doesn't dispatch keydown " + "event due to already dispatched from HandleAppCommandMessage(), ", + this)); + if (RedirectedKeyDownMessageManager::IsRedirectedMessage(mMsg)) { + RedirectedKeyDownMessageManager::Forget(); + } + return true; + } + + if (IsReservedBySystem()) { + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::HandleKeyDownMessage(), doesn't dispatch keydown " + "event because the key combination is reserved by the system", this)); + if (RedirectedKeyDownMessageManager::IsRedirectedMessage(mMsg)) { + RedirectedKeyDownMessageManager::Forget(); + } + return false; + } + + // If the widget has gone, we should do nothing. + if (mWidget->Destroyed()) { + MOZ_LOG(sNativeKeyLogger, LogLevel::Warning, + ("%p NativeKey::HandleKeyDownMessage(), WARNING, not handled due to " + "destroyed the widget", this)); + if (RedirectedKeyDownMessageManager::IsRedirectedMessage(mMsg)) { + RedirectedKeyDownMessageManager::Forget(); + } + return false; + } + + bool defaultPrevented = false; + if (mFakeCharMsgs || IsKeyMessageOnPlugin() || + !RedirectedKeyDownMessageManager::IsRedirectedMessage(mMsg)) { + nsresult rv = mDispatcher->BeginNativeInputTransaction(); + if (NS_WARN_IF(NS_FAILED(rv))) { + MOZ_LOG(sNativeKeyLogger, LogLevel::Error, + ("%p NativeKey::HandleKeyDownMessage(), FAILED due to " + "BeginNativeInputTransaction() failure", this)); + return true; + } + + bool isIMEEnabled = WinUtils::IsIMEEnabled(mWidget->GetInputContext()); + + MOZ_LOG(sNativeKeyLogger, LogLevel::Debug, + ("%p NativeKey::HandleKeyDownMessage(), initializing keydown " + "event...", this)); + + EventMessage keyDownMessage = + IsKeyMessageOnPlugin() ? eKeyDownOnPlugin : eKeyDown; + WidgetKeyboardEvent keydownEvent(true, keyDownMessage, mWidget); + nsEventStatus status = InitKeyEvent(keydownEvent, mModKeyState, &mMsg); + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::HandleKeyDownMessage(), dispatching keydown event...", + this)); + bool dispatched = + mDispatcher->DispatchKeyboardEvent(keyDownMessage, keydownEvent, status, + const_cast<NativeKey*>(this)); + if (aEventDispatched) { + *aEventDispatched = dispatched; + } + if (!dispatched) { + // If the keydown event wasn't fired, there must be composition. + // we don't need to do anything anymore. + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::HandleKeyDownMessage(), doesn't dispatch keypress " + "event(s) because keydown event isn't dispatched actually", this)); + return false; + } + defaultPrevented = status == nsEventStatus_eConsumeNoDefault; + + // We don't need to handle key messages on plugin for eKeyPress since + // eKeyDownOnPlugin is handled as both eKeyDown and eKeyPress. + if (IsKeyMessageOnPlugin()) { + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::HandleKeyDownMessage(), doesn't dispatch keypress " + "event(s) because it's a keydown message on windowed plugin, " + "defaultPrevented=%s", + this, GetBoolName(defaultPrevented))); + return defaultPrevented; + } + + if (mWidget->Destroyed() || IsFocusedWindowChanged()) { + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::HandleKeyDownMessage(), keydown event caused " + "destroying the widget", this)); + return true; + } + + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::HandleKeyDownMessage(), dispatched keydown event, " + "dispatched=%s, defaultPrevented=%s", + this, GetBoolName(dispatched), GetBoolName(defaultPrevented))); + + // If IMC wasn't associated to the window but is associated it now (i.e., + // focus is moved from a non-editable editor to an editor by keydown + // event handler), WM_CHAR and WM_SYSCHAR shouldn't cause first character + // inputting if IME is opened. But then, we should redirect the native + // keydown message to IME. + // However, note that if focus has been already moved to another + // application, we shouldn't redirect the message to it because the keydown + // message is processed by us, so, nobody shouldn't process it. + HWND focusedWnd = ::GetFocus(); + if (!defaultPrevented && !mFakeCharMsgs && !IsKeyMessageOnPlugin() && + focusedWnd && !mWidget->PluginHasFocus() && !isIMEEnabled && + WinUtils::IsIMEEnabled(mWidget->GetInputContext())) { + RedirectedKeyDownMessageManager::RemoveNextCharMessage(focusedWnd); + + INPUT keyinput; + keyinput.type = INPUT_KEYBOARD; + keyinput.ki.wVk = mOriginalVirtualKeyCode; + keyinput.ki.wScan = mScanCode; + keyinput.ki.dwFlags = KEYEVENTF_SCANCODE; + if (mIsExtended) { + keyinput.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY; + } + keyinput.ki.time = 0; + keyinput.ki.dwExtraInfo = 0; + + RedirectedKeyDownMessageManager::WillRedirect(mMsg, defaultPrevented); + + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::HandleKeyDownMessage(), redirecting %s...", + this, ToString(mMsg).get())); + + ::SendInput(1, &keyinput, sizeof(keyinput)); + + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::HandleKeyDownMessage(), redirected %s", + this, ToString(mMsg).get())); + + // Return here. We shouldn't dispatch keypress event for this WM_KEYDOWN. + // If it's needed, it will be dispatched after next (redirected) + // WM_KEYDOWN. + return true; + } + } else { + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::HandleKeyDownMessage(), received a redirected %s", + this, ToString(mMsg).get())); + + defaultPrevented = RedirectedKeyDownMessageManager::DefaultPrevented(); + // If this is redirected keydown message, we have dispatched the keydown + // event already. + if (aEventDispatched) { + *aEventDispatched = true; + } + } + + RedirectedKeyDownMessageManager::Forget(); + + MOZ_ASSERT(!mWidget->Destroyed()); + + // If the key was processed by IME and didn't cause WM_(SYS)CHAR messages, we + // shouldn't dispatch keypress event. + if (mOriginalVirtualKeyCode == VK_PROCESSKEY && + !IsFollowedByPrintableCharOrSysCharMessage()) { + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::HandleKeyDownMessage(), not dispatching keypress " + "event because the key was already handled by IME, defaultPrevented=%s", + this, GetBoolName(defaultPrevented))); + return defaultPrevented; + } + + if (defaultPrevented) { + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::HandleKeyDownMessage(), not dispatching keypress " + "event because preceding keydown event was consumed", + this)); + MaybeDispatchPluginEventsForRemovedCharMessages(); + return true; + } + + MOZ_ASSERT(!mCharMessageHasGone, + "If following char message was consumed by somebody, " + "keydown event should have been consumed before dispatch"); + + // If mCommittedCharsAndModifiers was initialized with following char + // messages, we should dispatch keypress events with its information. + if (IsFollowedByPrintableCharOrSysCharMessage()) { + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::HandleKeyDownMessage(), tries to be dispatching " + "keypress events with retrieved char messages...", this)); + return DispatchKeyPressEventsWithRetrievedCharMessages(); + } + + // If we won't be getting a WM_CHAR, WM_SYSCHAR or WM_DEADCHAR, synthesize a + // keypress for almost all keys + if (NeedsToHandleWithoutFollowingCharMessages()) { + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::HandleKeyDownMessage(), tries to be dispatching " + "keypress events...", this)); + return (MaybeDispatchPluginEventsForRemovedCharMessages() || + DispatchKeyPressEventsWithoutCharMessage()); + } + + // If WM_KEYDOWN of VK_PACKET isn't followed by WM_CHAR, we don't need to + // dispatch keypress events. + if (mVirtualKeyCode == VK_PACKET) { + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::HandleKeyDownMessage(), not dispatching keypress event " + "because the key is VK_PACKET and there are no char messages", + this)); + return false; + } + + if (!mModKeyState.IsControl() && !mModKeyState.IsAlt() && + !mModKeyState.IsWin() && mIsPrintableKey) { + // If this is simple KeyDown event but next message is not WM_CHAR, + // this event may not input text, so we should ignore this event. + // See bug 314130. + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::HandleKeyDownMessage(), not dispatching keypress event " + "because the key event is simple printable key's event but not followed " + "by char messages", this)); + return false; + } + + if (mIsDeadKey) { + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::HandleKeyDownMessage(), not dispatching keypress event " + "because the key is a dead key and not followed by char messages", + this)); + return false; + } + + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::HandleKeyDownMessage(), tries to be dispatching " + "keypress events due to no following char messages...", this)); + return DispatchKeyPressEventsWithoutCharMessage(); +} + +bool +NativeKey::HandleCharMessage(bool* aEventDispatched) const +{ + MOZ_ASSERT(IsCharOrSysCharMessage(mMsg)); + return HandleCharMessage(mMsg, aEventDispatched); +} + +bool +NativeKey::HandleCharMessage(const MSG& aCharMsg, + bool* aEventDispatched) const +{ + MOZ_ASSERT(IsKeyDownMessage() || IsCharOrSysCharMessage(mMsg)); + MOZ_ASSERT(IsCharOrSysCharMessage(aCharMsg.message)); + + if (aEventDispatched) { + *aEventDispatched = false; + } + + if (IsCharOrSysCharMessage(mMsg) && IsAnotherInstanceRemovingCharMessage()) { + MOZ_LOG(sNativeKeyLogger, LogLevel::Warning, + ("%p NativeKey::HandleCharMessage(), WARNING, does nothing because " + "the message should be handled in another instance removing this " + "message", this)); + // Consume this for now because it will be handled by another instance. + return true; + } + + // If the key combinations is reserved by the system, we shouldn't dispatch + // eKeyPress event for it and passes the message to next wndproc. + if (IsReservedBySystem()) { + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::HandleCharMessage(), doesn't dispatch keypress " + "event because the key combination is reserved by the system", this)); + return false; + } + + // If the widget has gone, we should do nothing. + if (mWidget->Destroyed()) { + MOZ_LOG(sNativeKeyLogger, LogLevel::Warning, + ("%p NativeKey::HandleCharMessage(), WARNING, not handled due to " + "destroyed the widget", this)); + return false; + } + + // When a control key is inputted by a key, it should be handled without + // WM_*CHAR messages at receiving WM_*KEYDOWN message. So, when we receive + // WM_*CHAR message directly, we see a control character here. + if (IsControlCharMessage(aCharMsg)) { + // In this case, we don't need to dispatch eKeyPress event because: + // 1. We're the only browser which dispatches "keypress" event for + // non-printable characters (Although, both Chrome and Edge dispatch + // "keypress" event for some keys accidentally. For example, "IntlRo" + // key with Ctrl of Japanese keyboard layout). + // 2. Currently, we may handle shortcut keys with "keydown" event if + // it's reserved or something. So, we shouldn't dispatch "keypress" + // event without it. + // Note that this does NOT mean we stop dispatching eKeyPress event for + // key presses causes a control character when Ctrl is pressed. In such + // case, DispatchKeyPressEventsWithoutCharMessage() dispatches eKeyPress + // instead of this method. + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::HandleCharMessage(), doesn't dispatch keypress " + "event because received a control character input without WM_KEYDOWN", + this)); + return false; + } + + // XXXmnakano I think that if mMsg is WM_CHAR, i.e., it comes without + // preceding WM_KEYDOWN, we should should dispatch composition + // events instead of eKeyPress because they are not caused by + // actual keyboard operation. + + // First, handle normal text input or non-printable key case here. + WidgetKeyboardEvent keypressEvent(true, eKeyPress, mWidget); + keypressEvent.mCharCode = static_cast<uint32_t>(aCharMsg.wParam); + nsresult rv = mDispatcher->BeginNativeInputTransaction(); + if (NS_WARN_IF(NS_FAILED(rv))) { + MOZ_LOG(sNativeKeyLogger, LogLevel::Error, + ("%p NativeKey::HandleCharMessage(), FAILED due to " + "BeginNativeInputTransaction() failure", this)); + return true; + } + + MOZ_LOG(sNativeKeyLogger, LogLevel::Debug, + ("%p NativeKey::HandleCharMessage(), initializing keypress " + "event...", this)); + + ModifierKeyState modKeyState(mModKeyState); + // When AltGr is pressed, both Alt and Ctrl are active. However, when they + // are active, EditorBase won't treat the keypress event as inputting a + // character. Therefore, when AltGr is pressed and the key tries to input + // a character, let's set them to false. + if (modKeyState.IsAltGr() && IsPrintableCharMessage(aCharMsg)) { + modKeyState.Unset(MODIFIER_ALT | MODIFIER_CONTROL); + } + nsEventStatus status = InitKeyEvent(keypressEvent, modKeyState, &aCharMsg); + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::HandleCharMessage(), dispatching keypress event...", + this)); + bool dispatched = + mDispatcher->MaybeDispatchKeypressEvents(keypressEvent, status, + const_cast<NativeKey*>(this)); + if (aEventDispatched) { + *aEventDispatched = dispatched; + } + if (mWidget->Destroyed()) { + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::HandleCharMessage(), keypress event caused " + "destroying the widget", this)); + return true; + } + bool consumed = status == nsEventStatus_eConsumeNoDefault; + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::HandleCharMessage(), dispatched keypress event, " + "dispatched=%s, consumed=%s", + this, GetBoolName(dispatched), GetBoolName(consumed))); + return consumed; +} + +bool +NativeKey::HandleKeyUpMessage(bool* aEventDispatched) const +{ + MOZ_ASSERT(IsKeyUpMessage()); + + if (aEventDispatched) { + *aEventDispatched = false; + } + + // If the key combinations is reserved by the system, we shouldn't dispatch + // eKeyUp event for it and passes the message to next wndproc. + if (IsReservedBySystem()) { + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::HandleKeyUpMessage(), doesn't dispatch keyup " + "event because the key combination is reserved by the system", this)); + return false; + } + + // If the widget has gone, we should do nothing. + if (mWidget->Destroyed()) { + MOZ_LOG(sNativeKeyLogger, LogLevel::Warning, + ("%p NativeKey::HandleKeyUpMessage(), WARNING, not handled due to " + "destroyed the widget", this)); + return false; + } + + nsresult rv = mDispatcher->BeginNativeInputTransaction(); + if (NS_WARN_IF(NS_FAILED(rv))) { + MOZ_LOG(sNativeKeyLogger, LogLevel::Error, + ("%p NativeKey::HandleKeyUpMessage(), FAILED due to " + "BeginNativeInputTransaction() failure", this)); + return true; + } + + MOZ_LOG(sNativeKeyLogger, LogLevel::Debug, + ("%p NativeKey::HandleKeyUpMessage(), initializing keyup event...", + this)); + EventMessage keyUpMessage = IsKeyMessageOnPlugin() ? eKeyUpOnPlugin : eKeyUp; + WidgetKeyboardEvent keyupEvent(true, keyUpMessage, mWidget); + nsEventStatus status = InitKeyEvent(keyupEvent, mModKeyState, &mMsg); + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::HandleKeyUpMessage(), dispatching keyup event...", + this)); + bool dispatched = + mDispatcher->DispatchKeyboardEvent(keyUpMessage, keyupEvent, status, + const_cast<NativeKey*>(this)); + if (aEventDispatched) { + *aEventDispatched = dispatched; + } + if (mWidget->Destroyed()) { + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::HandleKeyUpMessage(), keyup event caused " + "destroying the widget", this)); + return true; + } + bool consumed = status == nsEventStatus_eConsumeNoDefault; + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::HandleKeyUpMessage(), dispatched keyup event, " + "dispatched=%s, consumed=%s", + this, GetBoolName(dispatched), GetBoolName(consumed))); + return consumed; +} + +bool +NativeKey::NeedsToHandleWithoutFollowingCharMessages() const +{ + MOZ_ASSERT(IsKeyDownMessage()); + + // We cannot know following char messages of key messages in a plugin + // process. So, let's compute the character to be inputted with every + // printable key should be computed with the keyboard layout. + if (IsKeyMessageOnPlugin()) { + return true; + } + + // If the key combination is reserved by the system, the caller shouldn't + // do anything with following WM_*CHAR messages. So, let's return true here. + if (IsReservedBySystem()) { + return true; + } + + // If the keydown message is generated for inputting some Unicode characters + // via SendInput() API, we need to handle it only with WM_*CHAR messages. + if (mVirtualKeyCode == VK_PACKET) { + return false; + } + + // If following char message is for a control character, it should be handled + // without WM_CHAR message. This is typically Ctrl + [a-z]. + if (mFollowingCharMsgs.Length() == 1 && + IsControlCharMessage(mFollowingCharMsgs[0])) { + return true; + } + + // If keydown message is followed by WM_CHAR or WM_SYSCHAR whose wParam isn't + // a control character, we should dispatch keypress event with the char + // message even with any modifier state. + if (IsFollowedByPrintableCharOrSysCharMessage()) { + return false; + } + + // If any modifier keys which may cause printable keys becoming non-printable + // are not pressed, we don't need special handling for the key. + if (!mModKeyState.IsControl() && !mModKeyState.IsAlt() && + !mModKeyState.IsWin()) { + return false; + } + + // If the key event causes dead key event, we don't need to dispatch keypress + // event. + if (mIsDeadKey && mCommittedCharsAndModifiers.IsEmpty()) { + return false; + } + + // Even if the key is a printable key, it might cause non-printable character + // input with modifier key(s). + return mIsPrintableKey; +} + +#ifdef MOZ_CRASHREPORTER + +static nsCString +GetResultOfInSendMessageEx() +{ + DWORD ret = ::InSendMessageEx(nullptr); + if (!ret) { + return NS_LITERAL_CSTRING("ISMEX_NOSEND"); + } + nsAutoCString result; + if (ret & ISMEX_CALLBACK) { + result = "ISMEX_CALLBACK"; + } + if (ret & ISMEX_NOTIFY) { + if (!result.IsEmpty()) { + result += " | "; + } + result += "ISMEX_NOTIFY"; + } + if (ret & ISMEX_REPLIED) { + if (!result.IsEmpty()) { + result += " | "; + } + result += "ISMEX_REPLIED"; + } + if (ret & ISMEX_SEND) { + if (!result.IsEmpty()) { + result += " | "; + } + result += "ISMEX_SEND"; + } + return result; +} + +#endif // #ifdef MOZ_CRASHREPORTER + +bool +NativeKey::MayBeSameCharMessage(const MSG& aCharMsg1, + const MSG& aCharMsg2) const +{ + // NOTE: Although, we don't know when this case occurs, the scan code value + // in lParam may be changed from 0 to something. The changed value + // is different from the scan code of handling keydown message. + static const LPARAM kScanCodeMask = 0x00FF0000; + return + aCharMsg1.message == aCharMsg2.message && + aCharMsg1.wParam == aCharMsg2.wParam && + (aCharMsg1.lParam & ~kScanCodeMask) == (aCharMsg2.lParam & ~kScanCodeMask); +} + +bool +NativeKey::IsSamePhysicalKeyMessage(const MSG& aKeyOrCharMsg1, + const MSG& aKeyOrCharMsg2) const +{ + if (NS_WARN_IF(aKeyOrCharMsg1.message < WM_KEYFIRST) || + NS_WARN_IF(aKeyOrCharMsg1.message > WM_KEYLAST) || + NS_WARN_IF(aKeyOrCharMsg2.message < WM_KEYFIRST) || + NS_WARN_IF(aKeyOrCharMsg2.message > WM_KEYLAST)) { + return false; + } + return WinUtils::GetScanCode(aKeyOrCharMsg1.lParam) == + WinUtils::GetScanCode(aKeyOrCharMsg2.lParam) && + WinUtils::IsExtendedScanCode(aKeyOrCharMsg1.lParam) == + WinUtils::IsExtendedScanCode(aKeyOrCharMsg2.lParam); +} + +bool +NativeKey::GetFollowingCharMessage(MSG& aCharMsg) +{ + MOZ_ASSERT(IsKeyDownMessage()); + MOZ_ASSERT(!IsKeyMessageOnPlugin()); + + aCharMsg.message = WM_NULL; + + if (mFakeCharMsgs) { + for (size_t i = 0; i < mFakeCharMsgs->Length(); i++) { + FakeCharMsg& fakeCharMsg = mFakeCharMsgs->ElementAt(i); + if (fakeCharMsg.mConsumed) { + continue; + } + MSG charMsg = fakeCharMsg.GetCharMsg(mMsg.hwnd); + fakeCharMsg.mConsumed = true; + if (!IsCharMessage(charMsg)) { + return false; + } + aCharMsg = charMsg; + return true; + } + return false; + } + + // If next key message is not char message, we should give up to find a + // related char message for the handling keydown event for now. + // Note that it's possible other applications may send other key message + // after we call TranslateMessage(). That may cause PeekMessage() failing + // to get char message for the handling keydown message. + MSG nextKeyMsg; + if (!WinUtils::PeekMessage(&nextKeyMsg, mMsg.hwnd, WM_KEYFIRST, WM_KEYLAST, + PM_NOREMOVE | PM_NOYIELD) || + !IsCharMessage(nextKeyMsg)) { + MOZ_LOG(sNativeKeyLogger, LogLevel::Verbose, + ("%p NativeKey::GetFollowingCharMessage(), there are no char messages", + this)); + return false; + } + const MSG kFoundCharMsg = nextKeyMsg; + + AutoRestore<MSG> saveLastRemovingMsg(mRemovingMsg); + mRemovingMsg = nextKeyMsg; + + mReceivedMsg = sEmptyMSG; + AutoRestore<MSG> ensureToClearRecivedMsg(mReceivedMsg); + + // On Metrofox, PeekMessage() sometimes returns WM_NULL even if we specify + // the message range. So, if it returns WM_NULL, we should retry to get + // the following char message it was found above. + for (uint32_t i = 0; i < 50; i++) { + MSG removedMsg, nextKeyMsgInAllWindows; + bool doCrash = false; + if (!WinUtils::PeekMessage(&removedMsg, mMsg.hwnd, + nextKeyMsg.message, nextKeyMsg.message, + PM_REMOVE | PM_NOYIELD)) { + // We meets unexpected case. We should collect the message queue state + // and crash for reporting the bug. + doCrash = true; + + // If another instance was created for the removing message during trying + // to remove a char message, the instance didn't handle it for preventing + // recursive handling. So, let's handle it in this instance. + if (!IsEmptyMSG(mReceivedMsg)) { + // If focus is moved to different window, we shouldn't handle it on + // the widget. Let's discard it for now. + if (mReceivedMsg.hwnd != nextKeyMsg.hwnd) { + MOZ_LOG(sNativeKeyLogger, LogLevel::Warning, + ("%p NativeKey::GetFollowingCharMessage(), WARNING, received a " + "char message during removing it from the queue, but it's for " + "different window, mReceivedMsg=%s, nextKeyMsg=%s, " + "kFoundCharMsg=%s", + this, ToString(mReceivedMsg).get(), ToString(nextKeyMsg).get(), + ToString(kFoundCharMsg).get())); + // There might still exist char messages, the loop of calling + // this method should be continued. + aCharMsg.message = WM_NULL; + return true; + } + // Even if the received message is different from what we tried to + // remove from the queue, let's take the received message as a part of + // the result of this key sequence. + if (mReceivedMsg.message != nextKeyMsg.message || + mReceivedMsg.wParam != nextKeyMsg.wParam || + mReceivedMsg.lParam != nextKeyMsg.lParam) { + MOZ_LOG(sNativeKeyLogger, LogLevel::Warning, + ("%p NativeKey::GetFollowingCharMessage(), WARNING, received a " + "char message during removing it from the queue, but it's " + "differnt from what trying to remove from the queue, " + "aCharMsg=%s, nextKeyMsg=%s, kFoundCharMsg=%s", + this, ToString(mReceivedMsg).get(), ToString(nextKeyMsg).get(), + ToString(kFoundCharMsg).get())); + } else { + MOZ_LOG(sNativeKeyLogger, LogLevel::Verbose, + ("%p NativeKey::GetFollowingCharMessage(), succeeded to retrieve " + "next char message via another instance, aCharMsg=%s, " + "kFoundCharMsg=%s", + this, ToString(mReceivedMsg).get(), + ToString(kFoundCharMsg).get())); + } + aCharMsg = mReceivedMsg; + return true; + } + + // The char message is redirected to different thread's window by focus + // move or something or just cancelled by external application. + if (!WinUtils::PeekMessage(&nextKeyMsgInAllWindows, 0, + WM_KEYFIRST, WM_KEYLAST, + PM_NOREMOVE | PM_NOYIELD)) { + MOZ_LOG(sNativeKeyLogger, LogLevel::Warning, + ("%p NativeKey::GetFollowingCharMessage(), WARNING, failed to " + "remove a char message, but it's already gone from all message " + "queues, nextKeyMsg=%s, kFoundCharMsg=%s", + this, ToString(nextKeyMsg).get(), ToString(kFoundCharMsg).get())); + return true; // XXX should return false in this case + } + // The next key message is redirected to different window created by our + // thread, we should do nothing because we must not have focus. + if (nextKeyMsgInAllWindows.hwnd != mMsg.hwnd) { + aCharMsg = nextKeyMsgInAllWindows; + MOZ_LOG(sNativeKeyLogger, LogLevel::Warning, + ("%p NativeKey::GetFollowingCharMessage(), WARNING, failed to " + "remove a char message, but found in another message queue, " + "nextKeyMsgInAllWindows=%s, nextKeyMsg=%s, kFoundCharMsg=%s", + this, ToString(nextKeyMsgInAllWindows).get(), + ToString(nextKeyMsg).get(), ToString(kFoundCharMsg).get())); + return true; + } + // If next key message becomes non-char message, this key operation + // may have already been consumed or canceled. + if (!IsCharMessage(nextKeyMsgInAllWindows)) { + MOZ_LOG(sNativeKeyLogger, LogLevel::Warning, + ("%p NativeKey::GetFollowingCharMessage(), WARNING, failed to " + "remove a char message and next key message becomes non-char " + "message, nextKeyMsgInAllWindows=%s, nextKeyMsg=%s, " + "kFoundCharMsg=%s", + this, ToString(nextKeyMsgInAllWindows).get(), + ToString(nextKeyMsg).get(), ToString(kFoundCharMsg).get())); + MOZ_ASSERT(!mCharMessageHasGone); + mFollowingCharMsgs.Clear(); + mCharMessageHasGone = true; + return false; + } + // If next key message is still a char message but different key message, + // we should treat current key operation is consumed or canceled and + // next char message should be handled as an orphan char message later. + if (!IsSamePhysicalKeyMessage(nextKeyMsgInAllWindows, kFoundCharMsg)) { + MOZ_LOG(sNativeKeyLogger, LogLevel::Warning, + ("%p NativeKey::GetFollowingCharMessage(), WARNING, failed to " + "remove a char message and next key message becomes differnt key's " + "char message, nextKeyMsgInAllWindows=%s, nextKeyMsg=%s, " + "kFoundCharMsg=%s", + this, ToString(nextKeyMsgInAllWindows).get(), + ToString(nextKeyMsg).get(), ToString(kFoundCharMsg).get())); + MOZ_ASSERT(!mCharMessageHasGone); + mFollowingCharMsgs.Clear(); + mCharMessageHasGone = true; + return false; + } + // If next key message is still a char message but the message is changed, + // we should retry to remove the new message with PeekMessage() again. + if (nextKeyMsgInAllWindows.message != nextKeyMsg.message) { + MOZ_LOG(sNativeKeyLogger, LogLevel::Warning, + ("%p NativeKey::GetFollowingCharMessage(), WARNING, failed to " + "remove a char message due to message change, let's retry to " + "remove the message with newly found char message, ", + "nextKeyMsgInAllWindows=%s, nextKeyMsg=%s, kFoundCharMsg=%s", + this, ToString(nextKeyMsgInAllWindows).get(), + ToString(nextKeyMsg).get(), ToString(kFoundCharMsg).get())); + nextKeyMsg = nextKeyMsgInAllWindows; + continue; + } + // If there is still existing a char message caused by same physical key + // in the queue but PeekMessage(PM_REMOVE) failed to remove it from the + // queue, it might be possible that the odd keyboard layout or utility + // hooks only PeekMessage(PM_NOREMOVE) and GetMessage(). So, let's try + // remove the char message with GetMessage() again. + // FYI: The wParam might be different from the found message, but it's + // okay because we assume that odd keyboard layouts return actual + // inputting character at removing the char message. + if (WinUtils::GetMessage(&removedMsg, mMsg.hwnd, + nextKeyMsg.message, nextKeyMsg.message)) { + MOZ_LOG(sNativeKeyLogger, LogLevel::Warning, + ("%p NativeKey::GetFollowingCharMessage(), WARNING, failed to " + "remove a char message, but succeeded with GetMessage(), " + "removedMsg=%s, kFoundCharMsg=%s", + this, ToString(removedMsg).get(), ToString(kFoundCharMsg).get())); + // Cancel to crash, but we need to check the removed message value. + doCrash = false; + } + // If we've already removed some WM_NULL messages from the queue and + // the found message has already gone from the queue, let's treat the key + // as inputting no characters and already consumed. + else if (i > 0) { + MOZ_LOG(sNativeKeyLogger, LogLevel::Warning, + ("%p NativeKey::GetFollowingCharMessage(), WARNING, failed to " + "remove a char message, but removed %d WM_NULL messages", + this, i)); + // If the key is a printable key or a control key but tried to input + // a character, mark mCharMessageHasGone true for handling the keydown + // event as inputting empty string. + MOZ_ASSERT(!mCharMessageHasGone); + mFollowingCharMsgs.Clear(); + mCharMessageHasGone = true; + return false; + } + MOZ_LOG(sNativeKeyLogger, LogLevel::Error, + ("%p NativeKey::GetFollowingCharMessage(), FAILED, lost target " + "message to remove, nextKeyMsg=%s", + this, ToString(nextKeyMsg).get())); + } + + if (doCrash) { +#ifdef MOZ_CRASHREPORTER + nsPrintfCString info("\nPeekMessage() failed to remove char message! " + "\nActive keyboard layout=0x%08X (%s), " + "\nHandling message: %s, InSendMessageEx()=%s, " + "\nFound message: %s, " + "\nWM_NULL has been removed: %d, " + "\nNext key message in all windows: %s, " + "time=%d, ", + KeyboardLayout::GetActiveLayout(), + KeyboardLayout::GetActiveLayoutName().get(), + ToString(mMsg).get(), + GetResultOfInSendMessageEx().get(), + ToString(kFoundCharMsg).get(), i, + ToString(nextKeyMsgInAllWindows).get(), + nextKeyMsgInAllWindows.time); + CrashReporter::AppendAppNotesToCrashReport(info); + MSG nextMsg; + if (WinUtils::PeekMessage(&nextMsg, 0, 0, 0, + PM_NOREMOVE | PM_NOYIELD)) { + nsPrintfCString info("\nNext message in all windows: %s, time=%d", + ToString(nextMsg).get(), nextMsg.time); + CrashReporter::AppendAppNotesToCrashReport(info); + } else { + CrashReporter::AppendAppNotesToCrashReport( + NS_LITERAL_CSTRING("\nThere is no message in any window")); + } +#endif // #ifdef MOZ_CRASHREPORTER + MOZ_CRASH("We lost the following char message"); + } + + // We're still not sure why ::PeekMessage() may return WM_NULL even with + // its first message and its last message are same message. However, + // at developing Metrofox, we met this case even with usual keyboard + // layouts. So, it might be possible in desktop application or it really + // occurs with some odd keyboard layouts which perhaps hook API. + if (removedMsg.message == WM_NULL) { + MOZ_LOG(sNativeKeyLogger, LogLevel::Warning, + ("%p NativeKey::GetFollowingCharMessage(), WARNING, failed to " + "remove a char message, instead, removed WM_NULL message, ", + "removedMsg=%s", + this, ToString(removedMsg).get())); + // Check if there is the message which we're trying to remove. + MSG newNextKeyMsg; + if (!WinUtils::PeekMessage(&newNextKeyMsg, mMsg.hwnd, + WM_KEYFIRST, WM_KEYLAST, + PM_NOREMOVE | PM_NOYIELD)) { + // If there is no key message, we should mark this keydown as consumed + // because the key operation may have already been handled or canceled. + MOZ_LOG(sNativeKeyLogger, LogLevel::Warning, + ("%p NativeKey::GetFollowingCharMessage(), WARNING, failed to " + "remove a char message because it's gone during removing it from " + "the queue, nextKeyMsg=%s, kFoundCharMsg=%s", + this, ToString(nextKeyMsg).get(), ToString(kFoundCharMsg).get())); + MOZ_ASSERT(!mCharMessageHasGone); + mFollowingCharMsgs.Clear(); + mCharMessageHasGone = true; + return false; + } + if (!IsCharMessage(newNextKeyMsg)) { + // If next key message becomes a non-char message, we should mark this + // keydown as consumed because the key operation may have already been + // handled or canceled. + MOZ_LOG(sNativeKeyLogger, LogLevel::Warning, + ("%p NativeKey::GetFollowingCharMessage(), WARNING, failed to " + "remove a char message because it's gone during removing it from " + "the queue, nextKeyMsg=%s, newNextKeyMsg=%s, kFoundCharMsg=%s", + this, ToString(nextKeyMsg).get(), ToString(newNextKeyMsg).get(), + ToString(kFoundCharMsg).get())); + MOZ_ASSERT(!mCharMessageHasGone); + mFollowingCharMsgs.Clear(); + mCharMessageHasGone = true; + return false; + } + MOZ_LOG(sNativeKeyLogger, LogLevel::Debug, + ("%p NativeKey::GetFollowingCharMessage(), there is the message " + "which is being tried to be removed from the queue, trying again...", + this)); + continue; + } + + // Typically, this case occurs with WM_DEADCHAR. If the removed message's + // wParam becomes 0, that means that the key event shouldn't cause text + // input. So, let's ignore the strange char message. + if (removedMsg.message == nextKeyMsg.message && !removedMsg.wParam) { + MOZ_LOG(sNativeKeyLogger, LogLevel::Warning, + ("%p NativeKey::GetFollowingCharMessage(), WARNING, succeeded to " + "remove a char message, but the removed message's wParam is 0, " + "removedMsg=%s", + this, ToString(removedMsg).get())); + return false; + } + + // This is normal case. + if (MayBeSameCharMessage(removedMsg, nextKeyMsg)) { + aCharMsg = removedMsg; + MOZ_LOG(sNativeKeyLogger, LogLevel::Verbose, + ("%p NativeKey::GetFollowingCharMessage(), succeeded to retrieve " + "next char message, aCharMsg=%s", + this, ToString(aCharMsg).get())); + return true; + } + + // Even if removed message is different char message from the found char + // message, when the scan code is same, we can assume that the message + // is overwritten by somebody who hooks API. See bug 1336028 comment 0 for + // the possible scenarios. + if (IsCharMessage(removedMsg) && + IsSamePhysicalKeyMessage(removedMsg, nextKeyMsg)) { + aCharMsg = removedMsg; + MOZ_LOG(sNativeKeyLogger, LogLevel::Warning, + ("%p NativeKey::GetFollowingCharMessage(), WARNING, succeeded to " + "remove a char message, but the removed message was changed from " + "the found message except their scancode, aCharMsg=%s, " + "nextKeyMsg=%s, kFoundCharMsg=%s", + this, ToString(aCharMsg).get(), ToString(nextKeyMsg).get(), + ToString(kFoundCharMsg).get())); + return true; + } + + // When found message's wParam is 0 and its scancode is 0xFF, we may remove + // usual char message actually. In such case, we should use the removed + // char message. + if (IsCharMessage(removedMsg) && !nextKeyMsg.wParam && + WinUtils::GetScanCode(nextKeyMsg.lParam) == 0xFF) { + aCharMsg = removedMsg; + MOZ_LOG(sNativeKeyLogger, LogLevel::Warning, + ("%p NativeKey::GetFollowingCharMessage(), WARNING, succeeded to " + "remove a char message, but the removed message was changed from " + "the found message but the found message was odd, so, ignoring the " + "odd found message and respecting the removed message, aCharMsg=%s, " + "nextKeyMsg=%s, kFoundCharMsg=%s", + this, ToString(aCharMsg).get(), ToString(nextKeyMsg).get(), + ToString(kFoundCharMsg).get())); + return true; + } + + // NOTE: Although, we don't know when this case occurs, the scan code value + // in lParam may be changed from 0 to something. The changed value + // is different from the scan code of handling keydown message. + MOZ_LOG(sNativeKeyLogger, LogLevel::Error, + ("%p NativeKey::GetFollowingCharMessage(), FAILED, removed message " + "is really different from what we have already found, removedMsg=%s, " + "nextKeyMsg=%s, kFoundCharMsg=%s", + this, ToString(removedMsg).get(), ToString(nextKeyMsg).get(), + ToString(kFoundCharMsg).get())); +#ifdef MOZ_CRASHREPORTER + nsPrintfCString info("\nPeekMessage() removed unexpcted char message! " + "\nActive keyboard layout=0x%08X (%s), " + "\nHandling message: %s, InSendMessageEx()=%s, " + "\nFound message: %s, " + "\nRemoved message: %s, ", + KeyboardLayout::GetActiveLayout(), + KeyboardLayout::GetActiveLayoutName().get(), + ToString(mMsg).get(), + GetResultOfInSendMessageEx().get(), + ToString(kFoundCharMsg).get(), + ToString(removedMsg).get()); + CrashReporter::AppendAppNotesToCrashReport(info); + // What's the next key message? + MSG nextKeyMsgAfter; + if (WinUtils::PeekMessage(&nextKeyMsgAfter, mMsg.hwnd, + WM_KEYFIRST, WM_KEYLAST, + PM_NOREMOVE | PM_NOYIELD)) { + nsPrintfCString info("\nNext key message after unexpected char message " + "removed: %s, ", + ToString(nextKeyMsgAfter).get()); + CrashReporter::AppendAppNotesToCrashReport(info); + } else { + CrashReporter::AppendAppNotesToCrashReport( + NS_LITERAL_CSTRING("\nThere is no key message after unexpected char " + "message removed, ")); + } + // Another window has a key message? + if (WinUtils::PeekMessage(&nextKeyMsgInAllWindows, 0, + WM_KEYFIRST, WM_KEYLAST, + PM_NOREMOVE | PM_NOYIELD)) { + nsPrintfCString info("\nNext key message in all windows: %s.", + ToString(nextKeyMsgInAllWindows).get()); + CrashReporter::AppendAppNotesToCrashReport(info); + } else { + CrashReporter::AppendAppNotesToCrashReport( + NS_LITERAL_CSTRING("\nThere is no key message in any windows.")); + } +#endif // #ifdef MOZ_CRASHREPORTER + MOZ_CRASH("PeekMessage() removed unexpected message"); + } + MOZ_LOG(sNativeKeyLogger, LogLevel::Error, + ("%p NativeKey::GetFollowingCharMessage(), FAILED, removed messages " + "are all WM_NULL, nextKeyMsg=%s", + this, ToString(nextKeyMsg).get())); +#ifdef MOZ_CRASHREPORTER + nsPrintfCString info("\nWe lost following char message! " + "\nActive keyboard layout=0x%08X (%s), " + "\nHandling message: %s, InSendMessageEx()=%s, \n" + "Found message: %s, removed a lot of WM_NULL", + KeyboardLayout::GetActiveLayout(), + KeyboardLayout::GetActiveLayoutName().get(), + ToString(mMsg).get(), + GetResultOfInSendMessageEx().get(), + ToString(kFoundCharMsg).get()); + CrashReporter::AppendAppNotesToCrashReport(info); +#endif // #ifdef MOZ_CRASHREPORTER + MOZ_CRASH("We lost the following char message"); + return false; +} + +bool +NativeKey::MaybeDispatchPluginEventsForRemovedCharMessages() const +{ + MOZ_ASSERT(IsKeyDownMessage()); + MOZ_ASSERT(!IsKeyMessageOnPlugin()); + + for (size_t i = 0; + i < mFollowingCharMsgs.Length() && mWidget->ShouldDispatchPluginEvent(); + ++i) { + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::MaybeDispatchPluginEventsForRemovedCharMessages(), " + "dispatching %uth plugin event for %s...", + this, i + 1, ToString(mFollowingCharMsgs[i]).get())); + MOZ_RELEASE_ASSERT(!mWidget->Destroyed(), + "NativeKey tries to dispatch a plugin event on destroyed widget"); + mWidget->DispatchPluginEvent(mFollowingCharMsgs[i]); + if (mWidget->Destroyed() || IsFocusedWindowChanged()) { + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::MaybeDispatchPluginEventsForRemovedCharMessages(), " + "%uth plugin event caused %s", + this, i + 1, mWidget->Destroyed() ? "destroying the widget" : + "focus change")); + return true; + } + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::MaybeDispatchPluginEventsForRemovedCharMessages(), " + "dispatched %uth plugin event", + this, i + 1)); + } + + // Dispatch odd char messages which are caused by ATOK or WXG (both of them + // are Japanese IME) and removed by RemoveFollowingOddCharMessages(). + for (size_t i = 0; + i < mRemovedOddCharMsgs.Length() && mWidget->ShouldDispatchPluginEvent(); + ++i) { + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::MaybeDispatchPluginEventsForRemovedCharMessages(), " + "dispatching %uth plugin event for odd char message, %s...", + this, i + 1, ToString(mFollowingCharMsgs[i]).get())); + MOZ_RELEASE_ASSERT(!mWidget->Destroyed(), + "NativeKey tries to dispatch a plugin event on destroyed widget"); + mWidget->DispatchPluginEvent(mRemovedOddCharMsgs[i]); + if (mWidget->Destroyed() || IsFocusedWindowChanged()) { + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::MaybeDispatchPluginEventsForRemovedCharMessages(), " + "%uth plugin event for odd char message caused %s", + this, i + 1, mWidget->Destroyed() ? "destroying the widget" : + "focus change")); + return true; + } + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::MaybeDispatchPluginEventsForRemovedCharMessages(), " + "dispatched %uth plugin event for odd char message", + this, i + 1)); + } + + return false; +} + +void +NativeKey::ComputeInputtingStringWithKeyboardLayout() +{ + KeyboardLayout* keyboardLayout = KeyboardLayout::GetInstance(); + + if (KeyboardLayout::IsPrintableCharKey(mVirtualKeyCode) || + mCharMessageHasGone) { + mInputtingStringAndModifiers = mCommittedCharsAndModifiers; + } else { + mInputtingStringAndModifiers.Clear(); + } + mShiftedString.Clear(); + mUnshiftedString.Clear(); + mShiftedLatinChar = mUnshiftedLatinChar = 0; + + // XXX How about when Win key is pressed? + if (mModKeyState.IsControl() == mModKeyState.IsAlt()) { + return; + } + + ModifierKeyState capsLockState( + mModKeyState.GetModifiers() & MODIFIER_CAPSLOCK); + + mUnshiftedString = + keyboardLayout->GetUniCharsAndModifiers(mVirtualKeyCode, capsLockState); + capsLockState.Set(MODIFIER_SHIFT); + mShiftedString = + keyboardLayout->GetUniCharsAndModifiers(mVirtualKeyCode, capsLockState); + + // The current keyboard cannot input alphabets or numerics, + // we should append them for Shortcut/Access keys. + // E.g., for Cyrillic keyboard layout. + capsLockState.Unset(MODIFIER_SHIFT); + WidgetUtils::GetLatinCharCodeForKeyCode(mDOMKeyCode, + capsLockState.GetModifiers(), + &mUnshiftedLatinChar, + &mShiftedLatinChar); + + // If the mShiftedLatinChar isn't 0, the key code is NS_VK_[A-Z]. + if (mShiftedLatinChar) { + // If the produced characters of the key on current keyboard layout + // are same as computed Latin characters, we shouldn't append the + // Latin characters to alternativeCharCode. + if (mUnshiftedLatinChar == mUnshiftedString.CharAt(0) && + mShiftedLatinChar == mShiftedString.CharAt(0)) { + mShiftedLatinChar = mUnshiftedLatinChar = 0; + } + } else if (mUnshiftedLatinChar) { + // If the mShiftedLatinChar is 0, the mKeyCode doesn't produce + // alphabet character. At that time, the character may be produced + // with Shift key. E.g., on French keyboard layout, NS_VK_PERCENT + // key produces LATIN SMALL LETTER U WITH GRAVE (U+00F9) without + // Shift key but with Shift key, it produces '%'. + // If the mUnshiftedLatinChar is produced by the key on current + // keyboard layout, we shouldn't append it to alternativeCharCode. + if (mUnshiftedLatinChar == mUnshiftedString.CharAt(0) || + mUnshiftedLatinChar == mShiftedString.CharAt(0)) { + mUnshiftedLatinChar = 0; + } + } + + if (!mModKeyState.IsControl()) { + return; + } + + // If the mCharCode is not ASCII character, we should replace the + // mCharCode with ASCII character only when Ctrl is pressed. + // But don't replace the mCharCode when the mCharCode is not same as + // unmodified characters. In such case, Ctrl is sometimes used for a + // part of character inputting key combination like Shift. + uint32_t ch = + mModKeyState.IsShift() ? mShiftedLatinChar : mUnshiftedLatinChar; + if (!ch) { + return; + } + if (mInputtingStringAndModifiers.IsEmpty() || + mInputtingStringAndModifiers.UniCharsCaseInsensitiveEqual( + mModKeyState.IsShift() ? mShiftedString : mUnshiftedString)) { + mInputtingStringAndModifiers.Clear(); + mInputtingStringAndModifiers.Append(ch, mModKeyState.GetModifiers()); + } +} + +bool +NativeKey::DispatchKeyPressEventsWithRetrievedCharMessages() const +{ + MOZ_ASSERT(IsKeyDownMessage()); + MOZ_ASSERT(IsFollowedByPrintableCharOrSysCharMessage()); + MOZ_ASSERT(!mWidget->Destroyed()); + + nsresult rv = mDispatcher->BeginNativeInputTransaction(); + if (NS_WARN_IF(NS_FAILED(rv))) { + MOZ_LOG(sNativeKeyLogger, LogLevel::Error, + ("%p NativeKey::DispatchKeyPressEventsWithRetrievedCharMessages(), " + "FAILED due to BeginNativeInputTransaction() failure", this)); + return true; + } + WidgetKeyboardEvent keypressEvent(true, eKeyPress, mWidget); + MOZ_LOG(sNativeKeyLogger, LogLevel::Debug, + ("%p NativeKey::DispatchKeyPressEventsWithRetrievedCharMessages(), " + "initializing keypress event...", this)); + ModifierKeyState modKeyState(mModKeyState); + if (mCanIgnoreModifierStateAtKeyPress && IsFollowedByPrintableCharMessage()) { + // If eKeyPress event should cause inputting text in focused editor, + // we need to remove Alt and Ctrl state. + modKeyState.Unset(MODIFIER_ALT | MODIFIER_CONTROL); + } + // We don't need to send char message here if there are two or more retrieved + // messages because we need to set each message to each eKeyPress event. + bool needsCallback = mFollowingCharMsgs.Length() > 1; + nsEventStatus status = + InitKeyEvent(keypressEvent, modKeyState, + !needsCallback ? &mFollowingCharMsgs[0] : nullptr); + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::DispatchKeyPressEventsWithRetrievedCharMessages(), " + "dispatching keypress event(s)...", this)); + bool dispatched = + mDispatcher->MaybeDispatchKeypressEvents(keypressEvent, status, + const_cast<NativeKey*>(this), + needsCallback); + if (mWidget->Destroyed()) { + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::DispatchKeyPressEventsWithRetrievedCharMessages(), " + "keypress event(s) caused destroying the widget", this)); + return true; + } + bool consumed = status == nsEventStatus_eConsumeNoDefault; + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::DispatchKeyPressEventsWithRetrievedCharMessages(), " + "dispatched keypress event(s), dispatched=%s, consumed=%s", + this, GetBoolName(dispatched), GetBoolName(consumed))); + return consumed; +} + +bool +NativeKey::DispatchKeyPressEventsWithoutCharMessage() const +{ + MOZ_ASSERT(IsKeyDownMessage()); + MOZ_ASSERT(!mIsDeadKey || !mCommittedCharsAndModifiers.IsEmpty()); + MOZ_ASSERT(!mWidget->Destroyed()); + + nsresult rv = mDispatcher->BeginNativeInputTransaction(); + if (NS_WARN_IF(NS_FAILED(rv))) { + MOZ_LOG(sNativeKeyLogger, LogLevel::Error, + ("%p NativeKey::DispatchKeyPressEventsWithoutCharMessage(), FAILED due " + "to BeginNativeInputTransaction() failure", this)); + return true; + } + + WidgetKeyboardEvent keypressEvent(true, eKeyPress, mWidget); + if (mInputtingStringAndModifiers.IsEmpty() && + mShiftedString.IsEmpty() && mUnshiftedString.IsEmpty()) { + keypressEvent.mKeyCode = mDOMKeyCode; + } + MOZ_LOG(sNativeKeyLogger, LogLevel::Debug, + ("%p NativeKey::DispatchKeyPressEventsWithoutCharMessage(), initializing " + "keypress event...", this)); + nsEventStatus status = InitKeyEvent(keypressEvent, mModKeyState); + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::DispatchKeyPressEventsWithoutCharMessage(), dispatching " + "keypress event(s)...", this)); + bool dispatched = + mDispatcher->MaybeDispatchKeypressEvents(keypressEvent, status, + const_cast<NativeKey*>(this)); + if (mWidget->Destroyed()) { + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::DispatchKeyPressEventsWithoutCharMessage(), " + "keypress event(s) caused destroying the widget", this)); + return true; + } + bool consumed = status == nsEventStatus_eConsumeNoDefault; + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::DispatchKeyPressEventsWithoutCharMessage(), dispatched " + "keypress event(s), dispatched=%s, consumed=%s", + this, GetBoolName(dispatched), GetBoolName(consumed))); + return consumed; +} + +void +NativeKey::WillDispatchKeyboardEvent(WidgetKeyboardEvent& aKeyboardEvent, + uint32_t aIndex) +{ + // If it's an eKeyPress event and it's generated from retrieved char message, + // we need to set raw message information for plugins. + if (aKeyboardEvent.mMessage == eKeyPress && + IsFollowedByPrintableCharOrSysCharMessage()) { + MOZ_RELEASE_ASSERT(aIndex < mCommittedCharsAndModifiers.Length()); + uint32_t foundPrintableCharMessages = 0; + for (size_t i = 0; i < mFollowingCharMsgs.Length(); ++i) { + if (!IsPrintableCharOrSysCharMessage(mFollowingCharMsgs[i])) { + // XXX Should we dispatch a plugin event for WM_*DEADCHAR messages and + // WM_CHAR with a control character here? But we're not sure + // how can we create such message queue (i.e., WM_CHAR or + // WM_SYSCHAR with a printable character and such message are + // generated by a keydown). So, let's ignore such case until + // we'd get some bug reports. + MOZ_LOG(sNativeKeyLogger, LogLevel::Warning, + ("%p NativeKey::WillDispatchKeyboardEvent(), WARNING, " + "ignoring %uth message due to non-printable char message, %s", + this, i + 1, ToString(mFollowingCharMsgs[i]).get())); + continue; + } + if (foundPrintableCharMessages++ == aIndex) { + // Found message which caused the eKeyPress event. Let's set the + // message for plugin if it's necessary. + MaybeInitPluginEventOfKeyEvent(aKeyboardEvent, mFollowingCharMsgs[i]); + break; + } + } + // Set modifier state from mCommittedCharsAndModifiers because some of them + // might be different. For example, Shift key was pressed at inputting + // dead char but Shift key was released before inputting next character. + if (mCanIgnoreModifierStateAtKeyPress) { + ModifierKeyState modKeyState(mModKeyState); + modKeyState.Unset(MODIFIER_SHIFT | MODIFIER_CONTROL | MODIFIER_ALT | + MODIFIER_ALTGRAPH | MODIFIER_CAPSLOCK); + modKeyState.Set(mCommittedCharsAndModifiers.ModifiersAt(aIndex)); + modKeyState.InitInputEvent(aKeyboardEvent); + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::WillDispatchKeyboardEvent(), " + "setting %uth modifier state to %s", + this, aIndex + 1, ToString(modKeyState).get())); + } + } + size_t longestLength = + std::max(mInputtingStringAndModifiers.Length(), + std::max(mShiftedString.Length(), mUnshiftedString.Length())); + size_t skipUniChars = longestLength - mInputtingStringAndModifiers.Length(); + size_t skipShiftedChars = longestLength - mShiftedString.Length(); + size_t skipUnshiftedChars = longestLength - mUnshiftedString.Length(); + if (aIndex >= longestLength) { + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::WillDispatchKeyboardEvent(), does nothing for %uth " + "%s event", + this, aIndex + 1, ToChar(aKeyboardEvent.mMessage))); + return; + } + + // Check if aKeyboardEvent is the last event for a key press. + // So, if it's not an eKeyPress event, it's always the last event. + // Otherwise, check if the index is the last character of + // mCommittedCharsAndModifiers. + bool isLastIndex = + aKeyboardEvent.mMessage != eKeyPress || + mCommittedCharsAndModifiers.IsEmpty() || + mCommittedCharsAndModifiers.Length() - 1 == aIndex; + + nsTArray<AlternativeCharCode>& altArray = + aKeyboardEvent.mAlternativeCharCodes; + + // Set charCode and adjust modifier state for every eKeyPress event. + // This is not necessary for the other keyboard events because the other + // keyboard events shouldn't have non-zero charCode value and should have + // current modifier state. + if (aKeyboardEvent.mMessage == eKeyPress && skipUniChars <= aIndex) { + // XXX Modifying modifier state of aKeyboardEvent is illegal, but no way + // to set different modifier state per keypress event except this + // hack. Note that ideally, dead key should cause composition events + // instead of keypress events, though. + if (aIndex - skipUniChars < mInputtingStringAndModifiers.Length()) { + ModifierKeyState modKeyState(mModKeyState); + // If key in combination with Alt and/or Ctrl produces a different + // character than without them then do not report these flags + // because it is separate keyboard layout shift state. If dead-key + // and base character does not produce a valid composite character + // then both produced dead-key character and following base + // character may have different modifier flags, too. + modKeyState.Unset(MODIFIER_SHIFT | MODIFIER_CONTROL | MODIFIER_ALT | + MODIFIER_ALTGRAPH | MODIFIER_CAPSLOCK); + modKeyState.Set( + mInputtingStringAndModifiers.ModifiersAt(aIndex - skipUniChars)); + modKeyState.InitInputEvent(aKeyboardEvent); + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::WillDispatchKeyboardEvent(), " + "setting %uth modifier state to %s", + this, aIndex + 1, ToString(modKeyState).get())); + } + uint16_t uniChar = + mInputtingStringAndModifiers.CharAt(aIndex - skipUniChars); + + // The mCharCode was set from mKeyValue. However, for example, when Ctrl key + // is pressed, its value should indicate an ASCII character for backward + // compatibility rather than inputting character without the modifiers. + // Therefore, we need to modify mCharCode value here. + aKeyboardEvent.SetCharCode(uniChar); + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::WillDispatchKeyboardEvent(), " + "setting %uth charCode to %s", + this, aIndex + 1, GetCharacterCodeName(uniChar).get())); + } + + // We need to append alterntaive charCode values: + // - if the event is eKeyPress, we need to append for the index because + // eKeyPress event is dispatched for every character inputted by a + // key press. + // - if the event is not eKeyPress, we need to append for all characters + // inputted by the key press because the other keyboard events (e.g., + // eKeyDown are eKeyUp) are fired only once for a key press. + size_t count; + if (aKeyboardEvent.mMessage == eKeyPress) { + // Basically, append alternative charCode values only for the index. + count = 1; + // However, if it's the last eKeyPress event but different shift state + // can input longer string, the last eKeyPress event should have all + // remaining alternative charCode values. + if (isLastIndex) { + count = longestLength - aIndex; + } + } else { + count = longestLength; + } + for (size_t i = 0; i < count; ++i) { + uint16_t shiftedChar = 0, unshiftedChar = 0; + if (skipShiftedChars <= aIndex + i) { + shiftedChar = mShiftedString.CharAt(aIndex + i - skipShiftedChars); + } + if (skipUnshiftedChars <= aIndex + i) { + unshiftedChar = mUnshiftedString.CharAt(aIndex + i - skipUnshiftedChars); + } + + if (shiftedChar || unshiftedChar) { + AlternativeCharCode chars(unshiftedChar, shiftedChar); + altArray.AppendElement(chars); + } + + if (!isLastIndex) { + continue; + } + + if (mUnshiftedLatinChar || mShiftedLatinChar) { + AlternativeCharCode chars(mUnshiftedLatinChar, mShiftedLatinChar); + altArray.AppendElement(chars); + } + + // Typically, following virtual keycodes are used for a key which can + // input the character. However, these keycodes are also used for + // other keys on some keyboard layout. E.g., in spite of Shift+'1' + // inputs '+' on Thai keyboard layout, a key which is at '=/+' + // key on ANSI keyboard layout is VK_OEM_PLUS. Native applications + // handle it as '+' key if Ctrl key is pressed. + char16_t charForOEMKeyCode = 0; + switch (mVirtualKeyCode) { + case VK_OEM_PLUS: charForOEMKeyCode = '+'; break; + case VK_OEM_COMMA: charForOEMKeyCode = ','; break; + case VK_OEM_MINUS: charForOEMKeyCode = '-'; break; + case VK_OEM_PERIOD: charForOEMKeyCode = '.'; break; + } + if (charForOEMKeyCode && + charForOEMKeyCode != mUnshiftedString.CharAt(0) && + charForOEMKeyCode != mShiftedString.CharAt(0) && + charForOEMKeyCode != mUnshiftedLatinChar && + charForOEMKeyCode != mShiftedLatinChar) { + AlternativeCharCode OEMChars(charForOEMKeyCode, charForOEMKeyCode); + altArray.AppendElement(OEMChars); + } + } +} + +/***************************************************************************** + * mozilla::widget::KeyboardLayout + *****************************************************************************/ + +KeyboardLayout* KeyboardLayout::sInstance = nullptr; +nsIIdleServiceInternal* KeyboardLayout::sIdleService = nullptr; + +// This log is very noisy if you don't want to retrieve the mapping table +// of specific keyboard layout. LogLevel::Debug and LogLevel::Verbose are +// used to log the layout mapping. If you need to log some behavior of +// KeyboardLayout class, you should use LogLevel::Info or lower level. +LazyLogModule sKeyboardLayoutLogger("KeyboardLayoutWidgets"); + +// static +KeyboardLayout* +KeyboardLayout::GetInstance() +{ + if (!sInstance) { + sInstance = new KeyboardLayout(); + nsCOMPtr<nsIIdleServiceInternal> idleService = + do_GetService("@mozilla.org/widget/idleservice;1"); + // The refcount will be decreased at shut down. + sIdleService = idleService.forget().take(); + } + return sInstance; +} + +// static +void +KeyboardLayout::Shutdown() +{ + delete sInstance; + sInstance = nullptr; + NS_IF_RELEASE(sIdleService); +} + +// static +void +KeyboardLayout::NotifyIdleServiceOfUserActivity() +{ + sIdleService->ResetIdleTimeOut(0); +} + +KeyboardLayout::KeyboardLayout() + : mKeyboardLayout(0) + , mIsOverridden(false) + , mIsPendingToRestoreKeyboardLayout(false) +{ + mDeadKeyTableListHead = nullptr; + // A dead key sequence should be made from up to 5 keys. Therefore, 4 is + // enough and makes sense because the item is uint8_t. + // (Although, even if it's possible to be 6 keys or more in a sequence, + // this array will be re-allocated). + mActiveDeadKeys.SetCapacity(4); + mDeadKeyShiftStates.SetCapacity(4); + + // NOTE: LoadLayout() should be called via OnLayoutChange(). +} + +KeyboardLayout::~KeyboardLayout() +{ + ReleaseDeadKeyTables(); +} + +bool +KeyboardLayout::IsPrintableCharKey(uint8_t aVirtualKey) +{ + return GetKeyIndex(aVirtualKey) >= 0; +} + +WORD +KeyboardLayout::ComputeScanCodeForVirtualKeyCode(uint8_t aVirtualKeyCode) const +{ + return static_cast<WORD>( + ::MapVirtualKeyEx(aVirtualKeyCode, MAPVK_VK_TO_VSC, GetLayout())); +} + +bool +KeyboardLayout::IsDeadKey(uint8_t aVirtualKey, + const ModifierKeyState& aModKeyState) const +{ + int32_t virtualKeyIndex = GetKeyIndex(aVirtualKey); + + // XXX KeyboardLayout class doesn't support unusual keyboard layout which + // maps some function keys as dead keys. + if (virtualKeyIndex < 0) { + return false; + } + + return mVirtualKeys[virtualKeyIndex].IsDeadKey( + VirtualKey::ModifiersToShiftState(aModKeyState.GetModifiers())); +} + +bool +KeyboardLayout::IsSysKey(uint8_t aVirtualKey, + const ModifierKeyState& aModKeyState) const +{ + // If Alt key is not pressed, it's never a system key combination. + // Additionally, if Ctrl key is pressed, it's never a system key combination + // too. + // FYI: Windows logo key state won't affect if it's a system key. + if (!aModKeyState.IsAlt() || aModKeyState.IsControl()) { + return false; + } + + int32_t virtualKeyIndex = GetKeyIndex(aVirtualKey); + if (virtualKeyIndex < 0) { + return true; + } + + UniCharsAndModifiers inputCharsAndModifiers = + GetUniCharsAndModifiers(aVirtualKey, aModKeyState); + if (inputCharsAndModifiers.IsEmpty()) { + return true; + } + + // If the Alt key state isn't consumed, that means that the key with Alt + // doesn't cause text input. So, the combination is a system key. + return !!(inputCharsAndModifiers.ModifiersAt(0) & MODIFIER_ALT); +} + +void +KeyboardLayout::InitNativeKey(NativeKey& aNativeKey, + const ModifierKeyState& aModKeyState) +{ + if (mIsPendingToRestoreKeyboardLayout) { + LoadLayout(::GetKeyboardLayout(0)); + } + + // If the aNativeKey is initialized with WM_CHAR, the key information + // should be discarded because mKeyValue should have the string to be + // inputted. + if (aNativeKey.mMsg.message == WM_CHAR) { + char16_t ch = static_cast<char16_t>(aNativeKey.mMsg.wParam); + // But don't set key value as printable key if the character is a control + // character such as 0x0D at pressing Enter key. + if (!NativeKey::IsControlChar(ch)) { + aNativeKey.mKeyNameIndex = KEY_NAME_INDEX_USE_STRING; + Modifiers modifiers = + aModKeyState.GetModifiers() & ~(MODIFIER_ALT | MODIFIER_CONTROL); + aNativeKey.mCommittedCharsAndModifiers.Append(ch, modifiers); + return; + } + } + + // When it's followed by non-dead char message(s) for printable character(s), + // aNativeKey should dispatch eKeyPress events for them rather than + // information from keyboard layout because respecting WM_(SYS)CHAR messages + // guarantees that we can always input characters which is expected by + // the user even if the user uses odd keyboard layout. + // Or, when it was followed by non-dead char message for a printable character + // but it's gone at removing the message from the queue, let's treat it + // as a key inputting empty string. + if (aNativeKey.IsFollowedByPrintableCharOrSysCharMessage() || + aNativeKey.mCharMessageHasGone) { + MOZ_ASSERT(!aNativeKey.IsCharMessage(aNativeKey.mMsg)); + if (aNativeKey.IsFollowedByPrintableCharOrSysCharMessage()) { + // Initialize mCommittedCharsAndModifiers with following char messages. + aNativeKey. + InitCommittedCharsAndModifiersWithFollowingCharMessages(aModKeyState); + MOZ_ASSERT(!aNativeKey.mCommittedCharsAndModifiers.IsEmpty()); + + // Currently, we are doing a ugly hack to keypress events to cause + // inputting character even if Ctrl or Alt key is pressed, that is, we + // remove Ctrl and Alt modifier state from keypress event. However, for + // example, Ctrl+Space which causes ' ' of WM_CHAR message never causes + // keypress event whose ctrlKey is true. For preventing this problem, + // we should mark as not removable if Ctrl or Alt key does not cause + // changing inputting character. + if (IsPrintableCharKey(aNativeKey.mOriginalVirtualKeyCode) && + !aModKeyState.IsAltGr() && + (aModKeyState.IsControl() || aModKeyState.IsAlt())) { + ModifierKeyState state = aModKeyState; + state.Unset(MODIFIER_ALT | MODIFIER_CONTROL); + UniCharsAndModifiers charsWithoutModifier = + GetUniCharsAndModifiers(aNativeKey.mOriginalVirtualKeyCode, state); + aNativeKey.mCanIgnoreModifierStateAtKeyPress = + !charsWithoutModifier.UniCharsEqual( + aNativeKey.mCommittedCharsAndModifiers); + } + } else { + aNativeKey.mCommittedCharsAndModifiers.Clear(); + } + aNativeKey.mKeyNameIndex = KEY_NAME_INDEX_USE_STRING; + + // If it's not in dead key sequence, we don't need to do anymore here. + if (!IsInDeadKeySequence()) { + return; + } + + // If it's in dead key sequence and dead char is inputted as is, we need to + // set the previous modifier state which is stored when preceding dead key + // is pressed. + UniCharsAndModifiers deadChars = GetDeadUniCharsAndModifiers(); + aNativeKey.mCommittedCharsAndModifiers. + OverwriteModifiersIfBeginsWith(deadChars); + // Finish the dead key sequence. + DeactivateDeadKeyState(); + return; + } + + // If it's a dead key, aNativeKey will be initialized by + // MaybeInitNativeKeyAsDeadKey(). + if (MaybeInitNativeKeyAsDeadKey(aNativeKey, aModKeyState)) { + return; + } + + // If the key is not a usual printable key, KeyboardLayout class assume that + // it's not cause dead char nor printable char. Therefore, there are nothing + // to do here fore such keys (e.g., function keys). + // However, this should keep dead key state even if non-printable key is + // pressed during a dead key sequence. + if (!IsPrintableCharKey(aNativeKey.mOriginalVirtualKeyCode)) { + return; + } + + MOZ_ASSERT(aNativeKey.mOriginalVirtualKeyCode != VK_PACKET, + "At handling VK_PACKET, we shouldn't refer keyboard layout"); + MOZ_ASSERT(aNativeKey.mKeyNameIndex == KEY_NAME_INDEX_USE_STRING, + "Printable key's key name index must be KEY_NAME_INDEX_USE_STRING"); + + // If it's in dead key handling and the pressed key causes a composite + // character, aNativeKey will be initialized by + // MaybeInitNativeKeyWithCompositeChar(). + if (MaybeInitNativeKeyWithCompositeChar(aNativeKey, aModKeyState)) { + return; + } + + UniCharsAndModifiers baseChars = + GetUniCharsAndModifiers(aNativeKey.mOriginalVirtualKeyCode, aModKeyState); + + // If the key press isn't related to any dead keys, initialize aNativeKey + // with the characters which should be caused by the key. + if (!IsInDeadKeySequence()) { + aNativeKey.mCommittedCharsAndModifiers = baseChars; + return; + } + + // If the key doesn't cause a composite character with preceding dead key, + // initialize aNativeKey with the dead-key character followed by current + // key's character. + UniCharsAndModifiers deadChars = GetDeadUniCharsAndModifiers(); + aNativeKey.mCommittedCharsAndModifiers = deadChars + baseChars; + if (aNativeKey.IsKeyDownMessage()) { + DeactivateDeadKeyState(); + } +} + +bool +KeyboardLayout::MaybeInitNativeKeyAsDeadKey( + NativeKey& aNativeKey, + const ModifierKeyState& aModKeyState) +{ + // Only when it's not in dead key sequence, we can trust IsDeadKey() result. + if (!IsInDeadKeySequence() && + !IsDeadKey(aNativeKey.mOriginalVirtualKeyCode, aModKeyState)) { + return false; + } + + // When keydown message is followed by a dead char message, it should be + // initialized as dead key. + bool isDeadKeyDownEvent = + aNativeKey.IsKeyDownMessage() && + aNativeKey.IsFollowedByDeadCharMessage(); + + // When keyup message is received, let's check if it's one of preceding + // dead keys because keydown message order and keyup message order may be + // different. + bool isDeadKeyUpEvent = + !aNativeKey.IsKeyDownMessage() && + mActiveDeadKeys.Contains(aNativeKey.mOriginalVirtualKeyCode); + + if (isDeadKeyDownEvent || isDeadKeyUpEvent) { + ActivateDeadKeyState(aNativeKey, aModKeyState); + // Any dead key events don't generate characters. So, a dead key should + // cause only keydown event and keyup event whose KeyboardEvent.key + // values are "Dead". + aNativeKey.mCommittedCharsAndModifiers.Clear(); + aNativeKey.mKeyNameIndex = KEY_NAME_INDEX_Dead; + return true; + } + + // At keydown message handling, we need to forget the first dead key + // because there is no guarantee coming WM_KEYUP for the second dead + // key before next WM_KEYDOWN. E.g., due to auto key repeat or pressing + // another dead key before releasing current key. Therefore, we can + // set only a character for current key for keyup event. + if (!IsInDeadKeySequence()) { + aNativeKey.mCommittedCharsAndModifiers = + GetUniCharsAndModifiers(aNativeKey.mOriginalVirtualKeyCode, aModKeyState); + return true; + } + + // When non-printable key event comes during a dead key sequence, that must + // be a modifier key event. So, such events shouldn't be handled as a part + // of the dead key sequence. + if (!IsDeadKey(aNativeKey.mOriginalVirtualKeyCode, aModKeyState)) { + return false; + } + + // FYI: Following code may run when the user doesn't input text actually + // but the key sequence is a dead key sequence. For example, + // ` -> Ctrl+` with Spanish keyboard layout. Let's keep using this + // complicated code for now because this runs really rarely. + + // Dead key followed by another dead key may cause a composed character + // (e.g., "Russian - Mnemonic" keyboard layout's 's' -> 'c'). + if (MaybeInitNativeKeyWithCompositeChar(aNativeKey, aModKeyState)) { + return true; + } + + // Otherwise, dead key followed by another dead key causes inputting both + // character. + UniCharsAndModifiers prevDeadChars = GetDeadUniCharsAndModifiers(); + UniCharsAndModifiers newChars = + GetUniCharsAndModifiers(aNativeKey.mOriginalVirtualKeyCode, aModKeyState); + // But keypress events should be fired for each committed character. + aNativeKey.mCommittedCharsAndModifiers = prevDeadChars + newChars; + if (aNativeKey.IsKeyDownMessage()) { + DeactivateDeadKeyState(); + } + return true; +} + +bool +KeyboardLayout::MaybeInitNativeKeyWithCompositeChar( + NativeKey& aNativeKey, + const ModifierKeyState& aModKeyState) +{ + if (!IsInDeadKeySequence()) { + return false; + } + + if (NS_WARN_IF(!IsPrintableCharKey(aNativeKey.mOriginalVirtualKeyCode))) { + return false; + } + + UniCharsAndModifiers baseChars = + GetUniCharsAndModifiers(aNativeKey.mOriginalVirtualKeyCode, aModKeyState); + if (baseChars.IsEmpty() || !baseChars.CharAt(0)) { + return false; + } + + char16_t compositeChar = GetCompositeChar(baseChars.CharAt(0)); + if (!compositeChar) { + return false; + } + + // Active dead-key and base character does produce exactly one composite + // character. + aNativeKey.mCommittedCharsAndModifiers.Append(compositeChar, + baseChars.ModifiersAt(0)); + if (aNativeKey.IsKeyDownMessage()) { + DeactivateDeadKeyState(); + } + return true; +} + +UniCharsAndModifiers +KeyboardLayout::GetUniCharsAndModifiers( + uint8_t aVirtualKey, + VirtualKey::ShiftState aShiftState) const +{ + UniCharsAndModifiers result; + int32_t key = GetKeyIndex(aVirtualKey); + if (key < 0) { + return result; + } + return mVirtualKeys[key].GetUniChars(aShiftState); +} + +UniCharsAndModifiers +KeyboardLayout::GetNativeUniCharsAndModifiers( + uint8_t aVirtualKey, + const ModifierKeyState& aModKeyState) const +{ + int32_t key = GetKeyIndex(aVirtualKey); + if (key < 0) { + return UniCharsAndModifiers(); + } + VirtualKey::ShiftState shiftState = + VirtualKey::ModifierKeyStateToShiftState(aModKeyState); + return mVirtualKeys[key].GetNativeUniChars(shiftState); +} + +UniCharsAndModifiers +KeyboardLayout::GetDeadUniCharsAndModifiers() const +{ + MOZ_RELEASE_ASSERT(mActiveDeadKeys.Length() == mDeadKeyShiftStates.Length()); + + if (NS_WARN_IF(mActiveDeadKeys.IsEmpty())) { + return UniCharsAndModifiers(); + } + + UniCharsAndModifiers result; + for (size_t i = 0; i < mActiveDeadKeys.Length(); ++i) { + result += + GetUniCharsAndModifiers(mActiveDeadKeys[i], mDeadKeyShiftStates[i]); + } + return result; +} + +char16_t +KeyboardLayout::GetCompositeChar(char16_t aBaseChar) const +{ + if (NS_WARN_IF(mActiveDeadKeys.IsEmpty())) { + return 0; + } + // XXX Currently, we don't support computing a composite character with + // two or more dead keys since it needs big table for supporting + // long chained dead keys. However, this should be a minor bug + // because this runs only when the latest keydown event does not cause + // WM_(SYS)CHAR messages. So, when user wants to input a character, + // this path never runs. + if (mActiveDeadKeys.Length() > 1) { + return 0; + } + int32_t key = GetKeyIndex(mActiveDeadKeys[0]); + if (key < 0) { + return 0; + } + return mVirtualKeys[key].GetCompositeChar(mDeadKeyShiftStates[0], aBaseChar); +} + +// static +HKL +KeyboardLayout::GetActiveLayout() +{ + return GetInstance()->mKeyboardLayout; +} + +// static +nsCString +KeyboardLayout::GetActiveLayoutName() +{ + return GetInstance()->GetLayoutName(GetActiveLayout()); +} + +static bool IsValidKeyboardLayoutsChild(const nsAString& aChildName) +{ + if (aChildName.Length() != 8) { + return false; + } + for (size_t i = 0; i < aChildName.Length(); i++) { + if ((aChildName[i] >= '0' && aChildName[i] <= '9') || + (aChildName[i] >= 'a' && aChildName[i] <= 'f') || + (aChildName[i] >= 'A' && aChildName[i] <= 'F')) { + continue; + } + return false; + } + return true; +} + +nsCString +KeyboardLayout::GetLayoutName(HKL aLayout) const +{ + const wchar_t kKeyboardLayouts[] = + L"SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts\\"; + uint16_t language = reinterpret_cast<uintptr_t>(aLayout) & 0xFFFF; + uint16_t layout = (reinterpret_cast<uintptr_t>(aLayout) >> 16) & 0xFFFF; + // If the layout is less than 0xA000XXXX (normal keyboard layout for the + // language) or 0xEYYYXXXX (IMM-IME), we can retrieve its name simply. + if (layout < 0xA000 || (layout & 0xF000) == 0xE000) { + nsAutoString key(kKeyboardLayouts); + key.AppendPrintf("%08X", layout < 0xA000 ? + layout : reinterpret_cast<uintptr_t>(aLayout)); + wchar_t buf[256]; + if (NS_WARN_IF(!WinUtils::GetRegistryKey(HKEY_LOCAL_MACHINE, + key.get(), L"Layout Text", + buf, sizeof(buf)))) { + return NS_LITERAL_CSTRING("No name or too long name"); + } + return NS_ConvertUTF16toUTF8(buf); + } + + if (NS_WARN_IF((layout & 0xF000) != 0xF000)) { + nsAutoCString result; + result.AppendPrintf("Odd HKL: 0x%08X", + reinterpret_cast<uintptr_t>(aLayout)); + return result; + } + + // Otherwise, we need to walk the registry under "Keyboard Layouts". + nsCOMPtr<nsIWindowsRegKey> regKey = + do_CreateInstance("@mozilla.org/windows-registry-key;1"); + if (NS_WARN_IF(!regKey)) { + return EmptyCString(); + } + nsresult rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE, + nsString(kKeyboardLayouts), + nsIWindowsRegKey::ACCESS_READ); + if (NS_WARN_IF(NS_FAILED(rv))) { + return EmptyCString(); + } + uint32_t childCount = 0; + if (NS_WARN_IF(NS_FAILED(regKey->GetChildCount(&childCount))) || + NS_WARN_IF(!childCount)) { + return EmptyCString(); + } + for (uint32_t i = 0; i < childCount; i++) { + nsAutoString childName; + if (NS_WARN_IF(NS_FAILED(regKey->GetChildName(i, childName))) || + !IsValidKeyboardLayoutsChild(childName)) { + continue; + } + uint32_t childNum = static_cast<uint32_t>(childName.ToInteger64(&rv, 16)); + if (NS_WARN_IF(NS_FAILED(rv))) { + continue; + } + // Ignore normal keyboard layouts for each language. + if (childNum <= 0xFFFF) { + continue; + } + // If it doesn't start with 'A' nor 'a', language should be matched. + if ((childNum & 0xFFFF) != language && + (childNum & 0xF0000000) != 0xA0000000) { + continue; + } + // Then, the child should have "Layout Id" which is "YYY" of 0xFYYYXXXX. + nsAutoString key(kKeyboardLayouts); + key += childName; + wchar_t buf[256]; + if (NS_WARN_IF(!WinUtils::GetRegistryKey(HKEY_LOCAL_MACHINE, + key.get(), L"Layout Id", + buf, sizeof(buf)))) { + continue; + } + uint16_t layoutId = wcstol(buf, nullptr, 16); + if (layoutId != (layout & 0x0FFF)) { + continue; + } + if (NS_WARN_IF(!WinUtils::GetRegistryKey(HKEY_LOCAL_MACHINE, + key.get(), L"Layout Text", + buf, sizeof(buf)))) { + continue; + } + return NS_ConvertUTF16toUTF8(buf); + } + return EmptyCString(); +} + +void +KeyboardLayout::LoadLayout(HKL aLayout) +{ + mIsPendingToRestoreKeyboardLayout = false; + + if (mKeyboardLayout == aLayout) { + return; + } + + mKeyboardLayout = aLayout; + + MOZ_LOG(sKeyboardLayoutLogger, LogLevel::Info, + ("KeyboardLayout::LoadLayout(aLayout=0x%08X (%s))", + aLayout, GetLayoutName(aLayout).get())); + + BYTE kbdState[256]; + memset(kbdState, 0, sizeof(kbdState)); + + BYTE originalKbdState[256]; + // Bitfield with all shift states that have at least one dead-key. + uint16_t shiftStatesWithDeadKeys = 0; + // Bitfield with all shift states that produce any possible dead-key base + // characters. + uint16_t shiftStatesWithBaseChars = 0; + + mActiveDeadKeys.Clear(); + mDeadKeyShiftStates.Clear(); + + ReleaseDeadKeyTables(); + + ::GetKeyboardState(originalKbdState); + + // For each shift state gather all printable characters that are produced + // for normal case when no any dead-key is active. + + for (VirtualKey::ShiftState shiftState = 0; shiftState < 16; shiftState++) { + VirtualKey::FillKbdState(kbdState, shiftState); + for (uint32_t virtualKey = 0; virtualKey < 256; virtualKey++) { + int32_t vki = GetKeyIndex(virtualKey); + if (vki < 0) { + continue; + } + NS_ASSERTION(uint32_t(vki) < ArrayLength(mVirtualKeys), "invalid index"); + char16_t uniChars[5]; + int32_t ret = + ::ToUnicodeEx(virtualKey, 0, kbdState, (LPWSTR)uniChars, + ArrayLength(uniChars), 0, mKeyboardLayout); + // dead-key + if (ret < 0) { + shiftStatesWithDeadKeys |= (1 << shiftState); + // Repeat dead-key to deactivate it and get its character + // representation. + char16_t deadChar[2]; + ret = ::ToUnicodeEx(virtualKey, 0, kbdState, (LPWSTR)deadChar, + ArrayLength(deadChar), 0, mKeyboardLayout); + NS_ASSERTION(ret == 2, "Expecting twice repeated dead-key character"); + mVirtualKeys[vki].SetDeadChar(shiftState, deadChar[0]); + + MOZ_LOG(sKeyboardLayoutLogger, LogLevel::Debug, + (" %s (%d): DeadChar(%s, %s) (ret=%d)", + kVirtualKeyName[virtualKey], vki, + GetShiftStateName(shiftState).get(), + GetCharacterCodeName(deadChar, 1).get(), ret)); + } else { + if (ret == 1) { + // dead-key can pair only with exactly one base character. + shiftStatesWithBaseChars |= (1 << shiftState); + } + mVirtualKeys[vki].SetNormalChars(shiftState, uniChars, ret); + MOZ_LOG(sKeyboardLayoutLogger, LogLevel::Verbose, + (" %s (%d): NormalChar(%s, %s) (ret=%d)", + kVirtualKeyName[virtualKey], vki, + GetShiftStateName(shiftState).get(), + GetCharacterCodeName(uniChars, ret).get(), ret)); + } + } + } + + // Now process each dead-key to find all its base characters and resulting + // composite characters. + for (VirtualKey::ShiftState shiftState = 0; shiftState < 16; shiftState++) { + if (!(shiftStatesWithDeadKeys & (1 << shiftState))) { + continue; + } + + VirtualKey::FillKbdState(kbdState, shiftState); + + for (uint32_t virtualKey = 0; virtualKey < 256; virtualKey++) { + int32_t vki = GetKeyIndex(virtualKey); + if (vki >= 0 && mVirtualKeys[vki].IsDeadKey(shiftState)) { + DeadKeyEntry deadKeyArray[256]; + int32_t n = GetDeadKeyCombinations(virtualKey, kbdState, + shiftStatesWithBaseChars, + deadKeyArray, + ArrayLength(deadKeyArray)); + const DeadKeyTable* dkt = + mVirtualKeys[vki].MatchingDeadKeyTable(deadKeyArray, n); + if (!dkt) { + dkt = AddDeadKeyTable(deadKeyArray, n); + } + mVirtualKeys[vki].AttachDeadKeyTable(shiftState, dkt); + } + } + } + + ::SetKeyboardState(originalKbdState); + + if (MOZ_LOG_TEST(sKeyboardLayoutLogger, LogLevel::Verbose)) { + static const UINT kExtendedScanCode[] = { 0x0000, 0xE000 }; + static const UINT kMapType = + IsVistaOrLater() ? MAPVK_VSC_TO_VK_EX : MAPVK_VSC_TO_VK; + MOZ_LOG(sKeyboardLayoutLogger, LogLevel::Verbose, + ("Logging virtual keycode values for scancode (0x%p)...", + mKeyboardLayout)); + for (uint32_t i = 0; i < ArrayLength(kExtendedScanCode); i++) { + for (uint32_t j = 1; j <= 0xFF; j++) { + UINT scanCode = kExtendedScanCode[i] + j; + UINT virtualKeyCode = + ::MapVirtualKeyEx(scanCode, kMapType, mKeyboardLayout); + MOZ_LOG(sKeyboardLayoutLogger, LogLevel::Verbose, + ("0x%04X, %s", scanCode, kVirtualKeyName[virtualKeyCode])); + } + // XP and Server 2003 don't support 0xE0 prefix of the scancode. + // Therefore, we don't need to continue on them. + if (!IsVistaOrLater()) { + break; + } + } + } +} + +inline int32_t +KeyboardLayout::GetKeyIndex(uint8_t aVirtualKey) +{ +// Currently these 68 (NS_NUM_OF_KEYS) virtual keys are assumed +// to produce visible representation: +// 0x20 - VK_SPACE ' ' +// 0x30..0x39 '0'..'9' +// 0x41..0x5A 'A'..'Z' +// 0x60..0x69 '0'..'9' on numpad +// 0x6A - VK_MULTIPLY '*' on numpad +// 0x6B - VK_ADD '+' on numpad +// 0x6D - VK_SUBTRACT '-' on numpad +// 0x6E - VK_DECIMAL '.' on numpad +// 0x6F - VK_DIVIDE '/' on numpad +// 0x6E - VK_DECIMAL '.' +// 0xBA - VK_OEM_1 ';:' for US +// 0xBB - VK_OEM_PLUS '+' any country +// 0xBC - VK_OEM_COMMA ',' any country +// 0xBD - VK_OEM_MINUS '-' any country +// 0xBE - VK_OEM_PERIOD '.' any country +// 0xBF - VK_OEM_2 '/?' for US +// 0xC0 - VK_OEM_3 '`~' for US +// 0xC1 - VK_ABNT_C1 '/?' for Brazilian +// 0xC2 - VK_ABNT_C2 separator key on numpad (Brazilian or JIS for Mac) +// 0xDB - VK_OEM_4 '[{' for US +// 0xDC - VK_OEM_5 '\|' for US +// 0xDD - VK_OEM_6 ']}' for US +// 0xDE - VK_OEM_7 ''"' for US +// 0xDF - VK_OEM_8 +// 0xE1 - no name +// 0xE2 - VK_OEM_102 '\_' for JIS +// 0xE3 - no name +// 0xE4 - no name + + static const int8_t xlat[256] = + { + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + //----------------------------------------------------------------------- + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 00 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 10 + 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 20 + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, -1, -1, -1, -1, -1, -1, // 30 + -1, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // 40 + 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, -1, -1, -1, -1, -1, // 50 + 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, -1, 49, 50, 51, // 60 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 70 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 80 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 90 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // A0 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 52, 53, 54, 55, 56, 57, // B0 + 58, 59, 60, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // C0 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 61, 62, 63, 64, 65, // D0 + -1, 66, 67, 68, 69, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // E0 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 // F0 + }; + + return xlat[aVirtualKey]; +} + +int +KeyboardLayout::CompareDeadKeyEntries(const void* aArg1, + const void* aArg2, + void*) +{ + const DeadKeyEntry* arg1 = static_cast<const DeadKeyEntry*>(aArg1); + const DeadKeyEntry* arg2 = static_cast<const DeadKeyEntry*>(aArg2); + + return arg1->BaseChar - arg2->BaseChar; +} + +const DeadKeyTable* +KeyboardLayout::AddDeadKeyTable(const DeadKeyEntry* aDeadKeyArray, + uint32_t aEntries) +{ + DeadKeyTableListEntry* next = mDeadKeyTableListHead; + + const size_t bytes = offsetof(DeadKeyTableListEntry, data) + + DeadKeyTable::SizeInBytes(aEntries); + uint8_t* p = new uint8_t[bytes]; + + mDeadKeyTableListHead = reinterpret_cast<DeadKeyTableListEntry*>(p); + mDeadKeyTableListHead->next = next; + + DeadKeyTable* dkt = + reinterpret_cast<DeadKeyTable*>(mDeadKeyTableListHead->data); + + dkt->Init(aDeadKeyArray, aEntries); + + return dkt; +} + +void +KeyboardLayout::ReleaseDeadKeyTables() +{ + while (mDeadKeyTableListHead) { + uint8_t* p = reinterpret_cast<uint8_t*>(mDeadKeyTableListHead); + mDeadKeyTableListHead = mDeadKeyTableListHead->next; + + delete [] p; + } +} + +bool +KeyboardLayout::EnsureDeadKeyActive(bool aIsActive, + uint8_t aDeadKey, + const PBYTE aDeadKeyKbdState) +{ + int32_t ret; + do { + char16_t dummyChars[5]; + ret = ::ToUnicodeEx(aDeadKey, 0, (PBYTE)aDeadKeyKbdState, + (LPWSTR)dummyChars, ArrayLength(dummyChars), 0, + mKeyboardLayout); + // returned values: + // <0 - Dead key state is active. The keyboard driver will wait for next + // character. + // 1 - Previous pressed key was a valid base character that produced + // exactly one composite character. + // >1 - Previous pressed key does not produce any composite characters. + // Return dead-key character followed by base character(s). + } while ((ret < 0) != aIsActive); + + return (ret < 0); +} + +void +KeyboardLayout::ActivateDeadKeyState(const NativeKey& aNativeKey, + const ModifierKeyState& aModKeyState) +{ + // Dead-key state should be activated at keydown. + if (!aNativeKey.IsKeyDownMessage()) { + return; + } + + mActiveDeadKeys.AppendElement(aNativeKey.mOriginalVirtualKeyCode); + mDeadKeyShiftStates.AppendElement( + VirtualKey::ModifierKeyStateToShiftState(aModKeyState)); +} + +void +KeyboardLayout::DeactivateDeadKeyState() +{ + if (mActiveDeadKeys.IsEmpty()) { + return; + } + + BYTE kbdState[256]; + memset(kbdState, 0, sizeof(kbdState)); + + // Assume that the last dead key can finish dead key sequence. + VirtualKey::FillKbdState(kbdState, mDeadKeyShiftStates.LastElement()); + EnsureDeadKeyActive(false, mActiveDeadKeys.LastElement(), kbdState); + mActiveDeadKeys.Clear(); + mDeadKeyShiftStates.Clear(); +} + +bool +KeyboardLayout::AddDeadKeyEntry(char16_t aBaseChar, + char16_t aCompositeChar, + DeadKeyEntry* aDeadKeyArray, + uint32_t aEntries) +{ + for (uint32_t index = 0; index < aEntries; index++) { + if (aDeadKeyArray[index].BaseChar == aBaseChar) { + return false; + } + } + + aDeadKeyArray[aEntries].BaseChar = aBaseChar; + aDeadKeyArray[aEntries].CompositeChar = aCompositeChar; + + return true; +} + +uint32_t +KeyboardLayout::GetDeadKeyCombinations(uint8_t aDeadKey, + const PBYTE aDeadKeyKbdState, + uint16_t aShiftStatesWithBaseChars, + DeadKeyEntry* aDeadKeyArray, + uint32_t aMaxEntries) +{ + bool deadKeyActive = false; + uint32_t entries = 0; + BYTE kbdState[256]; + memset(kbdState, 0, sizeof(kbdState)); + + for (uint32_t shiftState = 0; shiftState < 16; shiftState++) { + if (!(aShiftStatesWithBaseChars & (1 << shiftState))) { + continue; + } + + VirtualKey::FillKbdState(kbdState, shiftState); + + for (uint32_t virtualKey = 0; virtualKey < 256; virtualKey++) { + int32_t vki = GetKeyIndex(virtualKey); + // Dead-key can pair only with such key that produces exactly one base + // character. + if (vki >= 0 && + mVirtualKeys[vki].GetNativeUniChars(shiftState).Length() == 1) { + // Ensure dead-key is in active state, when it swallows entered + // character and waits for the next pressed key. + if (!deadKeyActive) { + deadKeyActive = EnsureDeadKeyActive(true, aDeadKey, + aDeadKeyKbdState); + } + + // Depending on the character the followed the dead-key, the keyboard + // driver can produce one composite character, or a dead-key character + // followed by a second character. + char16_t compositeChars[5]; + int32_t ret = + ::ToUnicodeEx(virtualKey, 0, kbdState, (LPWSTR)compositeChars, + ArrayLength(compositeChars), 0, mKeyboardLayout); + switch (ret) { + case 0: + // This key combination does not produce any characters. The + // dead-key is still in active state. + break; + case 1: { + char16_t baseChars[5]; + ret = ::ToUnicodeEx(virtualKey, 0, kbdState, (LPWSTR)baseChars, + ArrayLength(baseChars), 0, mKeyboardLayout); + if (entries < aMaxEntries) { + switch (ret) { + case 1: + // Exactly one composite character produced. Now, when + // dead-key is not active, repeat the last character one more + // time to determine the base character. + if (AddDeadKeyEntry(baseChars[0], compositeChars[0], + aDeadKeyArray, entries)) { + entries++; + } + deadKeyActive = false; + break; + case -1: { + // If pressing another dead-key produces different character, + // we should register the dead-key entry with first character + // produced by current key. + + // First inactivate the dead-key state completely. + deadKeyActive = + EnsureDeadKeyActive(false, aDeadKey, aDeadKeyKbdState); + if (NS_WARN_IF(deadKeyActive)) { + MOZ_LOG(sKeyboardLayoutLogger, LogLevel::Error, + (" failed to deactivating the dead-key state...")); + break; + } + for (int32_t i = 0; i < 5; ++i) { + ret = ::ToUnicodeEx(virtualKey, 0, kbdState, + (LPWSTR)baseChars, + ArrayLength(baseChars), + 0, mKeyboardLayout); + if (ret >= 0) { + break; + } + } + if (ret > 0 && + AddDeadKeyEntry(baseChars[0], compositeChars[0], + aDeadKeyArray, entries)) { + entries++; + } + // Inactivate dead-key state for current virtual keycode. + EnsureDeadKeyActive(false, virtualKey, kbdState); + break; + } + default: + NS_WARN_IF("File a bug for this dead-key handling!"); + deadKeyActive = false; + break; + } + } + MOZ_LOG(sKeyboardLayoutLogger, LogLevel::Debug, + (" %s -> %s (%d): DeadKeyEntry(%s, %s) (ret=%d)", + kVirtualKeyName[aDeadKey], kVirtualKeyName[virtualKey], vki, + GetCharacterCodeName(compositeChars, 1).get(), + ret <= 0 ? "''" : + GetCharacterCodeName(baseChars, std::min(ret, 5)).get(), ret)); + break; + } + default: + // 1. Unexpected dead-key. Dead-key chaining is not supported. + // 2. More than one character generated. This is not a valid + // dead-key and base character combination. + deadKeyActive = false; + MOZ_LOG(sKeyboardLayoutLogger, LogLevel::Verbose, + (" %s -> %s (%d): Unsupport dead key type(%s) (ret=%d)", + kVirtualKeyName[aDeadKey], kVirtualKeyName[virtualKey], vki, + ret <= 0 ? "''" : + GetCharacterCodeName(compositeChars, + std::min(ret, 5)).get(), ret)); + break; + } + } + } + } + + if (deadKeyActive) { + deadKeyActive = EnsureDeadKeyActive(false, aDeadKey, aDeadKeyKbdState); + } + + NS_QuickSort(aDeadKeyArray, entries, sizeof(DeadKeyEntry), + CompareDeadKeyEntries, nullptr); + return entries; +} + +uint32_t +KeyboardLayout::ConvertNativeKeyCodeToDOMKeyCode(UINT aNativeKeyCode) const +{ + // Alphabet or Numeric or Numpad or Function keys + if ((aNativeKeyCode >= 0x30 && aNativeKeyCode <= 0x39) || + (aNativeKeyCode >= 0x41 && aNativeKeyCode <= 0x5A) || + (aNativeKeyCode >= 0x60 && aNativeKeyCode <= 0x87)) { + return static_cast<uint32_t>(aNativeKeyCode); + } + switch (aNativeKeyCode) { + // Following keycodes are same as our DOM keycodes + case VK_CANCEL: + case VK_BACK: + case VK_TAB: + case VK_CLEAR: + case VK_RETURN: + case VK_SHIFT: + case VK_CONTROL: + case VK_MENU: // Alt + case VK_PAUSE: + case VK_CAPITAL: // CAPS LOCK + case VK_KANA: // same as VK_HANGUL + case VK_JUNJA: + case VK_FINAL: + case VK_HANJA: // same as VK_KANJI + case VK_ESCAPE: + case VK_CONVERT: + case VK_NONCONVERT: + case VK_ACCEPT: + case VK_MODECHANGE: + case VK_SPACE: + case VK_PRIOR: // PAGE UP + case VK_NEXT: // PAGE DOWN + case VK_END: + case VK_HOME: + case VK_LEFT: + case VK_UP: + case VK_RIGHT: + case VK_DOWN: + case VK_SELECT: + case VK_PRINT: + case VK_EXECUTE: + case VK_SNAPSHOT: + case VK_INSERT: + case VK_DELETE: + case VK_APPS: // Context Menu + case VK_SLEEP: + case VK_NUMLOCK: + case VK_SCROLL: // SCROLL LOCK + case VK_ATTN: // Attension key of IBM midrange computers, e.g., AS/400 + case VK_CRSEL: // Cursor Selection + case VK_EXSEL: // Extend Selection + case VK_EREOF: // Erase EOF key of IBM 3270 keyboard layout + case VK_PLAY: + case VK_ZOOM: + case VK_PA1: // PA1 key of IBM 3270 keyboard layout + return uint32_t(aNativeKeyCode); + + case VK_HELP: + return NS_VK_HELP; + + // Windows key should be mapped to a Win keycode + // They should be able to be distinguished by DOM3 KeyboardEvent.location + case VK_LWIN: + case VK_RWIN: + return NS_VK_WIN; + + case VK_VOLUME_MUTE: + return NS_VK_VOLUME_MUTE; + case VK_VOLUME_DOWN: + return NS_VK_VOLUME_DOWN; + case VK_VOLUME_UP: + return NS_VK_VOLUME_UP; + + // Following keycodes are not defined in our DOM keycodes. + case VK_BROWSER_BACK: + case VK_BROWSER_FORWARD: + case VK_BROWSER_REFRESH: + case VK_BROWSER_STOP: + case VK_BROWSER_SEARCH: + case VK_BROWSER_FAVORITES: + case VK_BROWSER_HOME: + case VK_MEDIA_NEXT_TRACK: + case VK_MEDIA_PREV_TRACK: + case VK_MEDIA_STOP: + case VK_MEDIA_PLAY_PAUSE: + case VK_LAUNCH_MAIL: + case VK_LAUNCH_MEDIA_SELECT: + case VK_LAUNCH_APP1: + case VK_LAUNCH_APP2: + return 0; + + // Following OEM specific virtual keycodes should pass through DOM keyCode + // for compatibility with the other browsers on Windows. + + // Following OEM specific virtual keycodes are defined for Fujitsu/OASYS. + case VK_OEM_FJ_JISHO: + case VK_OEM_FJ_MASSHOU: + case VK_OEM_FJ_TOUROKU: + case VK_OEM_FJ_LOYA: + case VK_OEM_FJ_ROYA: + // Not sure what means "ICO". + case VK_ICO_HELP: + case VK_ICO_00: + case VK_ICO_CLEAR: + // Following OEM specific virtual keycodes are defined for Nokia/Ericsson. + case VK_OEM_RESET: + case VK_OEM_JUMP: + case VK_OEM_PA1: + case VK_OEM_PA2: + case VK_OEM_PA3: + case VK_OEM_WSCTRL: + case VK_OEM_CUSEL: + case VK_OEM_ATTN: + case VK_OEM_FINISH: + case VK_OEM_COPY: + case VK_OEM_AUTO: + case VK_OEM_ENLW: + case VK_OEM_BACKTAB: + // VK_OEM_CLEAR is defined as not OEM specific, but let's pass though + // DOM keyCode like other OEM specific virtual keycodes. + case VK_OEM_CLEAR: + return uint32_t(aNativeKeyCode); + + // 0xE1 is an OEM specific virtual keycode. However, the value is already + // used in our DOM keyCode for AltGr on Linux. So, this virtual keycode + // cannot pass through DOM keyCode. + case 0xE1: + return 0; + + // Following keycodes are OEM keys which are keycodes for non-alphabet and + // non-numeric keys, we should compute each keycode of them from unshifted + // character which is inputted by each key. But if the unshifted character + // is not an ASCII character but shifted character is an ASCII character, + // we should refer it. + case VK_OEM_1: + case VK_OEM_PLUS: + case VK_OEM_COMMA: + case VK_OEM_MINUS: + case VK_OEM_PERIOD: + case VK_OEM_2: + case VK_OEM_3: + case VK_OEM_4: + case VK_OEM_5: + case VK_OEM_6: + case VK_OEM_7: + case VK_OEM_8: + case VK_OEM_102: + case VK_ABNT_C1: + { + NS_ASSERTION(IsPrintableCharKey(aNativeKeyCode), + "The key must be printable"); + ModifierKeyState modKeyState(0); + UniCharsAndModifiers uniChars = + GetUniCharsAndModifiers(aNativeKeyCode, modKeyState); + if (uniChars.Length() != 1 || + uniChars.CharAt(0) < ' ' || uniChars.CharAt(0) > 0x7F) { + modKeyState.Set(MODIFIER_SHIFT); + uniChars = GetUniCharsAndModifiers(aNativeKeyCode, modKeyState); + if (uniChars.Length() != 1 || + uniChars.CharAt(0) < ' ' || uniChars.CharAt(0) > 0x7F) { + return 0; + } + } + return WidgetUtils::ComputeKeyCodeFromChar(uniChars.CharAt(0)); + } + + // IE sets 0xC2 to the DOM keyCode for VK_ABNT_C2. However, we're already + // using NS_VK_SEPARATOR for the separator key on Mac and Linux. Therefore, + // We should keep consistency between Gecko on all platforms rather than + // with other browsers since a lot of keyCode values are already different + // between browsers. + case VK_ABNT_C2: + return NS_VK_SEPARATOR; + + // VK_PROCESSKEY means IME already consumed the key event. + case VK_PROCESSKEY: + return 0; + // VK_PACKET is generated by SendInput() API, we don't need to + // care this message as key event. + case VK_PACKET: + return 0; + // If a key is not mapped to a virtual keycode, 0xFF is used. + case 0xFF: + NS_WARNING("The key is failed to be converted to a virtual keycode"); + return 0; + } +#ifdef DEBUG + nsPrintfCString warning("Unknown virtual keycode (0x%08X), please check the " + "latest MSDN document, there may be some new " + "keycodes we've never known.", + aNativeKeyCode); + NS_WARNING(warning.get()); +#endif + return 0; +} + +KeyNameIndex +KeyboardLayout::ConvertNativeKeyCodeToKeyNameIndex(uint8_t aVirtualKey) const +{ + if (IsPrintableCharKey(aVirtualKey) || aVirtualKey == VK_PACKET) { + return KEY_NAME_INDEX_USE_STRING; + } + + switch (aVirtualKey) { + +#undef NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX +#define NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX(aNativeKey, aKeyNameIndex) \ + case aNativeKey: return aKeyNameIndex; + +#include "NativeKeyToDOMKeyName.h" + +#undef NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX + + default: + break; + } + + HKL layout = GetLayout(); + WORD langID = LOWORD(static_cast<HKL>(layout)); + WORD primaryLangID = PRIMARYLANGID(langID); + + if (primaryLangID == LANG_JAPANESE) { + switch (aVirtualKey) { + +#undef NS_JAPANESE_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX +#define NS_JAPANESE_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX(aNativeKey, aKeyNameIndex)\ + case aNativeKey: return aKeyNameIndex; + +#include "NativeKeyToDOMKeyName.h" + +#undef NS_JAPANESE_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX + + default: + break; + } + } else if (primaryLangID == LANG_KOREAN) { + switch (aVirtualKey) { + +#undef NS_KOREAN_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX +#define NS_KOREAN_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX(aNativeKey, aKeyNameIndex)\ + case aNativeKey: return aKeyNameIndex; + +#include "NativeKeyToDOMKeyName.h" + +#undef NS_KOREAN_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX + + default: + return KEY_NAME_INDEX_Unidentified; + } + } + + switch (aVirtualKey) { + +#undef NS_OTHER_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX +#define NS_OTHER_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX(aNativeKey, aKeyNameIndex)\ + case aNativeKey: return aKeyNameIndex; + +#include "NativeKeyToDOMKeyName.h" + +#undef NS_OTHER_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX + + default: + return KEY_NAME_INDEX_Unidentified; + } +} + +// static +CodeNameIndex +KeyboardLayout::ConvertScanCodeToCodeNameIndex(UINT aScanCode) +{ + switch (aScanCode) { + +#define NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX(aNativeKey, aCodeNameIndex) \ + case aNativeKey: return aCodeNameIndex; + +#include "NativeKeyToDOMCodeName.h" + +#undef NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX + + default: + return CODE_NAME_INDEX_UNKNOWN; + } +} + +nsresult +KeyboardLayout::SynthesizeNativeKeyEvent(nsWindowBase* aWidget, + int32_t aNativeKeyboardLayout, + int32_t aNativeKeyCode, + uint32_t aModifierFlags, + const nsAString& aCharacters, + const nsAString& aUnmodifiedCharacters) +{ + UINT keyboardLayoutListCount = ::GetKeyboardLayoutList(0, nullptr); + NS_ASSERTION(keyboardLayoutListCount > 0, + "One keyboard layout must be installed at least"); + HKL keyboardLayoutListBuff[50]; + HKL* keyboardLayoutList = + keyboardLayoutListCount < 50 ? keyboardLayoutListBuff : + new HKL[keyboardLayoutListCount]; + keyboardLayoutListCount = + ::GetKeyboardLayoutList(keyboardLayoutListCount, keyboardLayoutList); + NS_ASSERTION(keyboardLayoutListCount > 0, + "Failed to get all keyboard layouts installed on the system"); + + nsPrintfCString layoutName("%08x", aNativeKeyboardLayout); + HKL loadedLayout = LoadKeyboardLayoutA(layoutName.get(), KLF_NOTELLSHELL); + if (loadedLayout == nullptr) { + if (keyboardLayoutListBuff != keyboardLayoutList) { + delete [] keyboardLayoutList; + } + return NS_ERROR_NOT_AVAILABLE; + } + + // Setup clean key state and load desired layout + BYTE originalKbdState[256]; + ::GetKeyboardState(originalKbdState); + BYTE kbdState[256]; + memset(kbdState, 0, sizeof(kbdState)); + // This changes the state of the keyboard for the current thread only, + // and we'll restore it soon, so this should be OK. + ::SetKeyboardState(kbdState); + + OverrideLayout(loadedLayout); + + uint8_t argumentKeySpecific = 0; + switch (aNativeKeyCode & 0xFF) { + case VK_SHIFT: + aModifierFlags &= ~(nsIWidget::SHIFT_L | nsIWidget::SHIFT_R); + argumentKeySpecific = VK_LSHIFT; + break; + case VK_LSHIFT: + aModifierFlags &= ~nsIWidget::SHIFT_L; + argumentKeySpecific = aNativeKeyCode & 0xFF; + aNativeKeyCode = (aNativeKeyCode & 0xFFFF0000) | VK_SHIFT; + break; + case VK_RSHIFT: + aModifierFlags &= ~nsIWidget::SHIFT_R; + argumentKeySpecific = aNativeKeyCode & 0xFF; + aNativeKeyCode = (aNativeKeyCode & 0xFFFF0000) | VK_SHIFT; + break; + case VK_CONTROL: + aModifierFlags &= ~(nsIWidget::CTRL_L | nsIWidget::CTRL_R); + argumentKeySpecific = VK_LCONTROL; + break; + case VK_LCONTROL: + aModifierFlags &= ~nsIWidget::CTRL_L; + argumentKeySpecific = aNativeKeyCode & 0xFF; + aNativeKeyCode = (aNativeKeyCode & 0xFFFF0000) | VK_CONTROL; + break; + case VK_RCONTROL: + aModifierFlags &= ~nsIWidget::CTRL_R; + argumentKeySpecific = aNativeKeyCode & 0xFF; + aNativeKeyCode = (aNativeKeyCode & 0xFFFF0000) | VK_CONTROL; + break; + case VK_MENU: + aModifierFlags &= ~(nsIWidget::ALT_L | nsIWidget::ALT_R); + argumentKeySpecific = VK_LMENU; + break; + case VK_LMENU: + aModifierFlags &= ~nsIWidget::ALT_L; + argumentKeySpecific = aNativeKeyCode & 0xFF; + aNativeKeyCode = (aNativeKeyCode & 0xFFFF0000) | VK_MENU; + break; + case VK_RMENU: + aModifierFlags &= ~nsIWidget::ALT_R; + argumentKeySpecific = aNativeKeyCode & 0xFF; + aNativeKeyCode = (aNativeKeyCode & 0xFFFF0000) | VK_MENU; + break; + case VK_CAPITAL: + aModifierFlags &= ~nsIWidget::CAPS_LOCK; + argumentKeySpecific = VK_CAPITAL; + break; + case VK_NUMLOCK: + aModifierFlags &= ~nsIWidget::NUM_LOCK; + argumentKeySpecific = VK_NUMLOCK; + break; + } + + AutoTArray<KeyPair,10> keySequence; + WinUtils::SetupKeyModifiersSequence(&keySequence, aModifierFlags); + keySequence.AppendElement(KeyPair(aNativeKeyCode, argumentKeySpecific)); + + // Simulate the pressing of each modifier key and then the real key + // FYI: Each NativeKey instance here doesn't need to override keyboard layout + // since this method overrides and restores the keyboard layout. + for (uint32_t i = 0; i < keySequence.Length(); ++i) { + uint8_t key = keySequence[i].mGeneral; + uint8_t keySpecific = keySequence[i].mSpecific; + uint16_t scanCode = keySequence[i].mScanCode; + kbdState[key] = 0x81; // key is down and toggled on if appropriate + if (keySpecific) { + kbdState[keySpecific] = 0x81; + } + ::SetKeyboardState(kbdState); + ModifierKeyState modKeyState; + // If scan code isn't specified explicitly, let's compute it with current + // keyboard layout. + if (!scanCode) { + scanCode = + ComputeScanCodeForVirtualKeyCode(keySpecific ? keySpecific : key); + } + LPARAM lParam = static_cast<LPARAM>(scanCode << 16); + // If the scan code is for an extended key, set extended key flag. + if ((scanCode & 0xFF00) == 0xE000) { + lParam |= 0x1000000; + } + bool makeSysKeyMsg = IsSysKey(key, modKeyState); + MSG keyDownMsg = + WinUtils::InitMSG(makeSysKeyMsg ? WM_SYSKEYDOWN : WM_KEYDOWN, + key, lParam, aWidget->GetWindowHandle()); + if (i == keySequence.Length() - 1) { + bool makeDeadCharMsg = + (IsDeadKey(key, modKeyState) && aCharacters.IsEmpty()); + nsAutoString chars(aCharacters); + if (makeDeadCharMsg) { + UniCharsAndModifiers deadChars = + GetUniCharsAndModifiers(key, modKeyState); + chars = deadChars.ToString(); + NS_ASSERTION(chars.Length() == 1, + "Dead char must be only one character"); + } + if (chars.IsEmpty()) { + NativeKey nativeKey(aWidget, keyDownMsg, modKeyState); + nativeKey.HandleKeyDownMessage(); + } else { + AutoTArray<NativeKey::FakeCharMsg, 10> fakeCharMsgs; + for (uint32_t j = 0; j < chars.Length(); j++) { + NativeKey::FakeCharMsg* fakeCharMsg = fakeCharMsgs.AppendElement(); + fakeCharMsg->mCharCode = chars.CharAt(j); + fakeCharMsg->mScanCode = scanCode; + fakeCharMsg->mIsSysKey = makeSysKeyMsg; + fakeCharMsg->mIsDeadKey = makeDeadCharMsg; + } + NativeKey nativeKey(aWidget, keyDownMsg, modKeyState, 0, &fakeCharMsgs); + bool dispatched; + nativeKey.HandleKeyDownMessage(&dispatched); + // If some char messages are not consumed, let's emulate the widget + // receiving the message directly. + for (uint32_t j = 1; j < fakeCharMsgs.Length(); j++) { + if (fakeCharMsgs[j].mConsumed) { + continue; + } + MSG charMsg = fakeCharMsgs[j].GetCharMsg(aWidget->GetWindowHandle()); + NativeKey nativeKey(aWidget, charMsg, modKeyState); + nativeKey.HandleCharMessage(charMsg); + } + } + } else { + NativeKey nativeKey(aWidget, keyDownMsg, modKeyState); + nativeKey.HandleKeyDownMessage(); + } + } + for (uint32_t i = keySequence.Length(); i > 0; --i) { + uint8_t key = keySequence[i - 1].mGeneral; + uint8_t keySpecific = keySequence[i - 1].mSpecific; + uint16_t scanCode = keySequence[i - 1].mScanCode; + kbdState[key] = 0; // key is up and toggled off if appropriate + if (keySpecific) { + kbdState[keySpecific] = 0; + } + ::SetKeyboardState(kbdState); + ModifierKeyState modKeyState; + // If scan code isn't specified explicitly, let's compute it with current + // keyboard layout. + if (!scanCode) { + scanCode = + ComputeScanCodeForVirtualKeyCode(keySpecific ? keySpecific : key); + } + LPARAM lParam = static_cast<LPARAM>(scanCode << 16); + // If the scan code is for an extended key, set extended key flag. + if ((scanCode & 0xFF00) == 0xE000) { + lParam |= 0x1000000; + } + // Don't use WM_SYSKEYUP for Alt keyup. + bool makeSysKeyMsg = IsSysKey(key, modKeyState) && key != VK_MENU; + MSG keyUpMsg = WinUtils::InitMSG(makeSysKeyMsg ? WM_SYSKEYUP : WM_KEYUP, + key, lParam, + aWidget->GetWindowHandle()); + NativeKey nativeKey(aWidget, keyUpMsg, modKeyState); + nativeKey.HandleKeyUpMessage(); + } + + // Restore old key state and layout + ::SetKeyboardState(originalKbdState); + RestoreLayout(); + + // Don't unload the layout if it's installed actually. + for (uint32_t i = 0; i < keyboardLayoutListCount; i++) { + if (keyboardLayoutList[i] == loadedLayout) { + loadedLayout = 0; + break; + } + } + if (keyboardLayoutListBuff != keyboardLayoutList) { + delete [] keyboardLayoutList; + } + if (loadedLayout) { + ::UnloadKeyboardLayout(loadedLayout); + } + return NS_OK; +} + +/***************************************************************************** + * mozilla::widget::DeadKeyTable + *****************************************************************************/ + +char16_t +DeadKeyTable::GetCompositeChar(char16_t aBaseChar) const +{ + // Dead-key table is sorted by BaseChar in ascending order. + // Usually they are too small to use binary search. + + for (uint32_t index = 0; index < mEntries; index++) { + if (mTable[index].BaseChar == aBaseChar) { + return mTable[index].CompositeChar; + } + if (mTable[index].BaseChar > aBaseChar) { + break; + } + } + + return 0; +} + +/***************************************************************************** + * mozilla::widget::RedirectedKeyDownMessage + *****************************************************************************/ + +MSG RedirectedKeyDownMessageManager::sRedirectedKeyDownMsg; +bool RedirectedKeyDownMessageManager::sDefaultPreventedOfRedirectedMsg = false; + +// static +bool +RedirectedKeyDownMessageManager::IsRedirectedMessage(const MSG& aMsg) +{ + return (aMsg.message == WM_KEYDOWN || aMsg.message == WM_SYSKEYDOWN) && + (sRedirectedKeyDownMsg.message == aMsg.message && + WinUtils::GetScanCode(sRedirectedKeyDownMsg.lParam) == + WinUtils::GetScanCode(aMsg.lParam)); +} + +// static +void +RedirectedKeyDownMessageManager::RemoveNextCharMessage(HWND aWnd) +{ + MSG msg; + if (WinUtils::PeekMessage(&msg, aWnd, WM_KEYFIRST, WM_KEYLAST, + PM_NOREMOVE | PM_NOYIELD) && + (msg.message == WM_CHAR || msg.message == WM_SYSCHAR)) { + WinUtils::PeekMessage(&msg, aWnd, msg.message, msg.message, + PM_REMOVE | PM_NOYIELD); + } +} + +} // namespace widget +} // namespace mozilla + diff --git a/widget/windows/KeyboardLayout.h b/widget/windows/KeyboardLayout.h new file mode 100644 index 0000000000..dd2ac0bfce --- /dev/null +++ b/widget/windows/KeyboardLayout.h @@ -0,0 +1,1012 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef KeyboardLayout_h__ +#define KeyboardLayout_h__ + +#include "mozilla/RefPtr.h" +#include "nscore.h" +#include "nsString.h" +#include "nsWindowBase.h" +#include "nsWindowDefs.h" +#include "mozilla/Attributes.h" +#include "mozilla/EventForwards.h" +#include "mozilla/TextEventDispatcher.h" +#include "mozilla/widget/WinMessages.h" +#include "mozilla/widget/WinModifierKeyState.h" +#include <windows.h> + +#define NS_NUM_OF_KEYS 70 + +#define VK_OEM_1 0xBA // ';:' for US +#define VK_OEM_PLUS 0xBB // '+' any country +#define VK_OEM_COMMA 0xBC +#define VK_OEM_MINUS 0xBD // '-' any country +#define VK_OEM_PERIOD 0xBE +#define VK_OEM_2 0xBF +#define VK_OEM_3 0xC0 +// '/?' for Brazilian (ABNT) +#define VK_ABNT_C1 0xC1 +// Separator in Numpad for Brazilian (ABNT) or JIS keyboard for Mac. +#define VK_ABNT_C2 0xC2 +#define VK_OEM_4 0xDB +#define VK_OEM_5 0xDC +#define VK_OEM_6 0xDD +#define VK_OEM_7 0xDE +#define VK_OEM_8 0xDF +#define VK_OEM_102 0xE2 +#define VK_OEM_CLEAR 0xFE + +class nsIIdleServiceInternal; + +namespace mozilla { +namespace widget { + +static const uint32_t sModifierKeyMap[][3] = { + { nsIWidget::CAPS_LOCK, VK_CAPITAL, 0 }, + { nsIWidget::NUM_LOCK, VK_NUMLOCK, 0 }, + { nsIWidget::SHIFT_L, VK_SHIFT, VK_LSHIFT }, + { nsIWidget::SHIFT_R, VK_SHIFT, VK_RSHIFT }, + { nsIWidget::CTRL_L, VK_CONTROL, VK_LCONTROL }, + { nsIWidget::CTRL_R, VK_CONTROL, VK_RCONTROL }, + { nsIWidget::ALT_L, VK_MENU, VK_LMENU }, + { nsIWidget::ALT_R, VK_MENU, VK_RMENU } +}; + +class KeyboardLayout; + +class MOZ_STACK_CLASS UniCharsAndModifiers final +{ +public: + UniCharsAndModifiers() {} + UniCharsAndModifiers operator+(const UniCharsAndModifiers& aOther) const; + UniCharsAndModifiers& operator+=(const UniCharsAndModifiers& aOther); + + /** + * Append a pair of unicode character and the final modifier. + */ + void Append(char16_t aUniChar, Modifiers aModifiers); + void Clear() + { + mChars.Truncate(); + mModifiers.Clear(); + } + bool IsEmpty() const + { + MOZ_ASSERT(mChars.Length() == mModifiers.Length()); + return mChars.IsEmpty(); + } + + char16_t CharAt(size_t aIndex) const + { + MOZ_ASSERT(aIndex < Length()); + return mChars[aIndex]; + } + Modifiers ModifiersAt(size_t aIndex) const + { + MOZ_ASSERT(aIndex < Length()); + return mModifiers[aIndex]; + } + size_t Length() const + { + MOZ_ASSERT(mChars.Length() == mModifiers.Length()); + return mChars.Length(); + } + + void FillModifiers(Modifiers aModifiers); + /** + * OverwriteModifiersIfBeginsWith() assigns mModifiers with aOther between + * [0] and [aOther.mLength - 1] only when mChars begins with aOther.mChars. + */ + void OverwriteModifiersIfBeginsWith(const UniCharsAndModifiers& aOther); + + bool UniCharsEqual(const UniCharsAndModifiers& aOther) const; + bool UniCharsCaseInsensitiveEqual(const UniCharsAndModifiers& aOther) const; + bool BeginsWith(const UniCharsAndModifiers& aOther) const; + + const nsString& ToString() const { return mChars; } + +private: + nsAutoString mChars; + // 5 is enough number for normal keyboard layout handling. On Windows, + // a dead key sequence may cause inputting up to 5 characters per key press. + AutoTArray<Modifiers, 5> mModifiers; +}; + +struct DeadKeyEntry; +class DeadKeyTable; + + +class VirtualKey +{ +public: + // 0 - Normal + // 1 - Shift + // 2 - Control + // 3 - Control + Shift + // 4 - Alt + // 5 - Alt + Shift + // 6 - Alt + Control (AltGr) + // 7 - Alt + Control + Shift (AltGr + Shift) + // 8 - CapsLock + // 9 - CapsLock + Shift + // 10 - CapsLock + Control + // 11 - CapsLock + Control + Shift + // 12 - CapsLock + Alt + // 13 - CapsLock + Alt + Shift + // 14 - CapsLock + Alt + Control (CapsLock + AltGr) + // 15 - CapsLock + Alt + Control + Shift (CapsLock + AltGr + Shift) + + enum ShiftStateFlag + { + STATE_SHIFT = 0x01, + STATE_CONTROL = 0x02, + STATE_ALT = 0x04, + STATE_CAPSLOCK = 0x08 + }; + + typedef uint8_t ShiftState; + + static ShiftState ModifiersToShiftState(Modifiers aModifiers); + static ShiftState ModifierKeyStateToShiftState( + const ModifierKeyState& aModKeyState) + { + return ModifiersToShiftState(aModKeyState.GetModifiers()); + } + static Modifiers ShiftStateToModifiers(ShiftState aShiftState); + +private: + union KeyShiftState + { + struct + { + char16_t Chars[4]; + } Normal; + struct + { + const DeadKeyTable* Table; + char16_t DeadChar; + } DeadKey; + }; + + KeyShiftState mShiftStates[16]; + uint16_t mIsDeadKey; + + void SetDeadKey(ShiftState aShiftState, bool aIsDeadKey) + { + if (aIsDeadKey) { + mIsDeadKey |= 1 << aShiftState; + } else { + mIsDeadKey &= ~(1 << aShiftState); + } + } + +public: + static void FillKbdState(PBYTE aKbdState, const ShiftState aShiftState); + + bool IsDeadKey(ShiftState aShiftState) const + { + return (mIsDeadKey & (1 << aShiftState)) != 0; + } + + void AttachDeadKeyTable(ShiftState aShiftState, + const DeadKeyTable* aDeadKeyTable) + { + mShiftStates[aShiftState].DeadKey.Table = aDeadKeyTable; + } + + void SetNormalChars(ShiftState aShiftState, const char16_t* aChars, + uint32_t aNumOfChars); + void SetDeadChar(ShiftState aShiftState, char16_t aDeadChar); + const DeadKeyTable* MatchingDeadKeyTable(const DeadKeyEntry* aDeadKeyArray, + uint32_t aEntries) const; + inline char16_t GetCompositeChar(ShiftState aShiftState, + char16_t aBaseChar) const; + char16_t GetCompositeChar(const ModifierKeyState& aModKeyState, + char16_t aBaseChar) const + { + return GetCompositeChar(ModifierKeyStateToShiftState(aModKeyState), + aBaseChar); + } + UniCharsAndModifiers GetNativeUniChars(ShiftState aShiftState) const; + UniCharsAndModifiers GetNativeUniChars( + const ModifierKeyState& aModKeyState) const + { + return GetNativeUniChars(ModifierKeyStateToShiftState(aModKeyState)); + } + UniCharsAndModifiers GetUniChars(ShiftState aShiftState) const; + UniCharsAndModifiers GetUniChars(const ModifierKeyState& aModKeyState) const + { + return GetUniChars(ModifierKeyStateToShiftState(aModKeyState)); + } +}; + +class MOZ_STACK_CLASS NativeKey final +{ + friend class KeyboardLayout; + +public: + struct FakeCharMsg + { + UINT mCharCode; + UINT mScanCode; + bool mIsSysKey; + bool mIsDeadKey; + bool mConsumed; + + FakeCharMsg() + : mCharCode(0) + , mScanCode(0) + , mIsSysKey(false) + , mIsDeadKey(false) + , mConsumed(false) + { + } + + MSG GetCharMsg(HWND aWnd) const + { + MSG msg; + msg.hwnd = aWnd; + msg.message = mIsDeadKey && mIsSysKey ? WM_SYSDEADCHAR : + mIsDeadKey ? WM_DEADCHAR : + mIsSysKey ? WM_SYSCHAR : + WM_CHAR; + msg.wParam = static_cast<WPARAM>(mCharCode); + msg.lParam = static_cast<LPARAM>(mScanCode << 16); + msg.time = 0; + msg.pt.x = msg.pt.y = 0; + return msg; + } + }; + + NativeKey(nsWindowBase* aWidget, + const MSG& aMessage, + const ModifierKeyState& aModKeyState, + HKL aOverrideKeyboardLayout = 0, + nsTArray<FakeCharMsg>* aFakeCharMsgs = nullptr); + + ~NativeKey(); + + /** + * Handle WM_KEYDOWN message or WM_SYSKEYDOWN message. The instance must be + * initialized with WM_KEYDOWN or WM_SYSKEYDOWN. + * Returns true if dispatched keydown event or keypress event is consumed. + * Otherwise, false. + */ + bool HandleKeyDownMessage(bool* aEventDispatched = nullptr) const; + + /** + * Handles WM_CHAR message or WM_SYSCHAR message. The instance must be + * initialized with them. + * Returns true if dispatched keypress event is consumed. Otherwise, false. + */ + bool HandleCharMessage(bool* aEventDispatched = nullptr) const; + + /** + * Handles keyup message. Returns true if the event is consumed. + * Otherwise, false. + */ + bool HandleKeyUpMessage(bool* aEventDispatched = nullptr) const; + + /** + * Handles WM_APPCOMMAND message. Returns true if the event is consumed. + * Otherwise, false. + */ + bool HandleAppCommandMessage() const; + + /** + * Callback of TextEventDispatcherListener::WillDispatchKeyboardEvent(). + * This method sets alternative char codes of aKeyboardEvent. + */ + void WillDispatchKeyboardEvent(WidgetKeyboardEvent& aKeyboardEvent, + uint32_t aIndex); + + /** + * Returns true if aChar is a control character which shouldn't be inputted + * into focused text editor. + */ + static bool IsControlChar(char16_t aChar); + +private: + NativeKey* mLastInstance; + // mRemovingMsg is set at removing a char message from + // GetFollowingCharMessage(). + MSG mRemovingMsg; + // mReceivedMsg is set when another instance starts to handle the message + // unexpectedly. + MSG mReceivedMsg; + RefPtr<nsWindowBase> mWidget; + RefPtr<TextEventDispatcher> mDispatcher; + HKL mKeyboardLayout; + MSG mMsg; + // mFollowingCharMsgs stores WM_CHAR, WM_SYSCHAR, WM_DEADCHAR or + // WM_SYSDEADCHAR message which follows WM_KEYDOWN. + // Note that the stored messaged are already removed from the queue. + // FYI: 5 is enough number for usual keyboard layout handling. On Windows, + // a dead key sequence may cause inputting up to 5 characters per key press. + AutoTArray<MSG, 5> mFollowingCharMsgs; + // mRemovedOddCharMsgs stores WM_CHAR messages which are caused by ATOK or + // WXG (they are Japanese IME) when the user tries to do "Kakutei-undo" + // (it means "undo the last commit"). + nsTArray<MSG> mRemovedOddCharMsgs; + // If dispatching eKeyDown or eKeyPress event causes focus change, + // the instance shouldn't handle remaning char messages. For checking it, + // this should store first focused window. + HWND mFocusedWndBeforeDispatch; + + uint32_t mDOMKeyCode; + KeyNameIndex mKeyNameIndex; + CodeNameIndex mCodeNameIndex; + + ModifierKeyState mModKeyState; + + // mVirtualKeyCode distinguishes left key or right key of modifier key. + uint8_t mVirtualKeyCode; + // mOriginalVirtualKeyCode doesn't distinguish left key or right key of + // modifier key. However, if the given keycode is VK_PROCESS, it's resolved + // to a keycode before it's handled by IME. + uint8_t mOriginalVirtualKeyCode; + + // mCommittedChars indicates the inputted characters which is committed by + // the key. If dead key fail to composite a character, mCommittedChars + // indicates both the dead characters and the base characters. + UniCharsAndModifiers mCommittedCharsAndModifiers; + + // Following strings are computed by + // ComputeInputtingStringWithKeyboardLayout() which is typically called + // before dispatching keydown event. + // mInputtingStringAndModifiers's string is the string to be + // inputted into the focused editor and its modifier state is proper + // modifier state for inputting the string into the editor. + UniCharsAndModifiers mInputtingStringAndModifiers; + // mShiftedString is the string to be inputted into the editor with + // current modifier state with active shift state. + UniCharsAndModifiers mShiftedString; + // mUnshiftedString is the string to be inputted into the editor with + // current modifier state without shift state. + UniCharsAndModifiers mUnshiftedString; + // Following integers are computed by + // ComputeInputtingStringWithKeyboardLayout() which is typically called + // before dispatching keydown event. The meaning of these values is same + // as charCode. + uint32_t mShiftedLatinChar; + uint32_t mUnshiftedLatinChar; + + WORD mScanCode; + bool mIsExtended; + bool mIsDeadKey; + // mIsPrintableKey is true if the key may be a printable key without + // any modifier keys. Otherwise, false. + // Please note that the event may not cause any text input even if this + // is true. E.g., it might be dead key state or Ctrl key may be pressed. + bool mIsPrintableKey; + // mCharMessageHasGone is true if the message is a keydown message and + // it's followed by at least one char message but it's gone at removing + // from the queue. This could occur if PeekMessage() or something is + // hooked by odd tool. + bool mCharMessageHasGone; + // mIsOverridingKeyboardLayout is true if the instance temporarily overriding + // keyboard layout with specified by the constructor. + bool mIsOverridingKeyboardLayout; + // mCanIgnoreModifierStateAtKeyPress is true if it's allowed to remove + // Ctrl or Alt modifier state at dispatching eKeyPress. + bool mCanIgnoreModifierStateAtKeyPress; + + nsTArray<FakeCharMsg>* mFakeCharMsgs; + + // When a keydown event is dispatched at handling WM_APPCOMMAND, the computed + // virtual keycode is set to this. Even if we consume WM_APPCOMMAND message, + // Windows may send WM_KEYDOWN and WM_KEYUP message for them. + // At that time, we should not dispatch key events for them. + static uint8_t sDispatchedKeyOfAppCommand; + + NativeKey() + { + MOZ_CRASH("The default constructor of NativeKey isn't available"); + } + + void InitWithAppCommand(); + void InitWithKeyChar(); + + /** + * InitCommittedCharsAndModifiersWithFollowingCharMessages() initializes + * mCommittedCharsAndModifiers with mFollowingCharMsgs and aModKeyState. + * If mFollowingCharMsgs includes non-printable char messages, they are + * ignored (skipped). + */ + void InitCommittedCharsAndModifiersWithFollowingCharMessages( + const ModifierKeyState& aModKeyState); + + /** + * Returns true if the key event is caused by auto repeat. + */ + bool IsRepeat() const + { + switch (mMsg.message) { + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + case WM_CHAR: + case WM_SYSCHAR: + case WM_DEADCHAR: + case WM_SYSDEADCHAR: + case MOZ_WM_KEYDOWN: + return ((mMsg.lParam & (1 << 30)) != 0); + case WM_APPCOMMAND: + if (mVirtualKeyCode) { + // If we can map the WM_APPCOMMAND to a virtual keycode, we can trust + // the result of GetKeyboardState(). + BYTE kbdState[256]; + memset(kbdState, 0, sizeof(kbdState)); + ::GetKeyboardState(kbdState); + return !!kbdState[mVirtualKeyCode]; + } + // If there is no virtual keycode for the command, we dispatch both + // keydown and keyup events from WM_APPCOMMAND handler. Therefore, + // even if WM_APPCOMMAND is caused by auto key repeat, web apps receive + // a pair of DOM keydown and keyup events. I.e., KeyboardEvent.repeat + // should be never true of such keys. + return false; + default: + return false; + } + } + + UINT GetScanCodeWithExtendedFlag() const; + + // The result is one of nsIDOMKeyEvent::DOM_KEY_LOCATION_*. + uint32_t GetKeyLocation() const; + + /** + * RemoveFollowingOddCharMessages() removes odd WM_CHAR messages from the + * queue when IsIMEDoingKakuteiUndo() returns true. + */ + void RemoveFollowingOddCharMessages(); + + /** + * "Kakutei-Undo" of ATOK or WXG (both of them are Japanese IME) causes + * strange WM_KEYDOWN/WM_KEYUP/WM_CHAR message pattern. So, when this + * returns true, the caller needs to be careful for processing the messages. + */ + bool IsIMEDoingKakuteiUndo() const; + + bool IsKeyDownMessage() const + { + return (mMsg.message == WM_KEYDOWN || + mMsg.message == WM_SYSKEYDOWN || + mMsg.message == MOZ_WM_KEYDOWN); + } + bool IsKeyUpMessage() const + { + return (mMsg.message == WM_KEYUP || + mMsg.message == WM_SYSKEYUP || + mMsg.message == MOZ_WM_KEYUP); + } + bool IsCharOrSysCharMessage(const MSG& aMSG) const + { + return IsCharOrSysCharMessage(aMSG.message); + } + bool IsCharOrSysCharMessage(UINT aMessage) const + { + return (aMessage == WM_CHAR || aMessage == WM_SYSCHAR); + } + bool IsCharMessage(const MSG& aMSG) const + { + return IsCharMessage(aMSG.message); + } + bool IsCharMessage(UINT aMessage) const + { + return (IsCharOrSysCharMessage(aMessage) || IsDeadCharMessage(aMessage)); + } + bool IsDeadCharMessage(const MSG& aMSG) const + { + return IsDeadCharMessage(aMSG.message); + } + bool IsDeadCharMessage(UINT aMessage) const + { + return (aMessage == WM_DEADCHAR || aMessage == WM_SYSDEADCHAR); + } + bool IsSysCharMessage(const MSG& aMSG) const + { + return IsSysCharMessage(aMSG.message); + } + bool IsSysCharMessage(UINT aMessage) const + { + return (aMessage == WM_SYSCHAR || aMessage == WM_SYSDEADCHAR); + } + bool MayBeSameCharMessage(const MSG& aCharMsg1, const MSG& aCharMsg2) const; + bool IsSamePhysicalKeyMessage(const MSG& aKeyOrCharMsg1, + const MSG& aKeyOrCharMsg2) const; + bool IsFollowedByPrintableCharMessage() const; + bool IsFollowedByPrintableCharOrSysCharMessage() const; + bool IsFollowedByDeadCharMessage() const; + bool IsKeyMessageOnPlugin() const + { + return (mMsg.message == MOZ_WM_KEYDOWN || + mMsg.message == MOZ_WM_KEYUP); + } + bool IsPrintableCharMessage(const MSG& aMSG) const + { + return aMSG.message == WM_CHAR && + !IsControlChar(static_cast<char16_t>(aMSG.wParam)); + } + bool IsPrintableCharOrSysCharMessage(const MSG& aMSG) const + { + return IsCharOrSysCharMessage(aMSG) && + !IsControlChar(static_cast<char16_t>(aMSG.wParam)); + } + bool IsControlCharMessage(const MSG& aMSG) const + { + return IsCharMessage(aMSG.message) && + IsControlChar(static_cast<char16_t>(aMSG.wParam)); + } + + /** + * IsReservedBySystem() returns true if the key combination is reserved by + * the system. Even if it's consumed by web apps, the message should be + * sent to next wndproc. + */ + bool IsReservedBySystem() const; + + /** + * GetFollowingCharMessage() returns following char message of handling + * keydown event. If the message is found, this method returns true. + * Otherwise, returns false. + * + * WARNING: Even if this returns true, aCharMsg may be WM_NULL or its + * hwnd may be different window. + */ + bool GetFollowingCharMessage(MSG& aCharMsg); + + /** + * Whether the key event can compute virtual keycode from the scancode value. + */ + bool CanComputeVirtualKeyCodeFromScanCode() const; + + /** + * Wraps MapVirtualKeyEx() with MAPVK_VSC_TO_VK. + */ + uint8_t ComputeVirtualKeyCodeFromScanCode() const; + + /** + * Wraps MapVirtualKeyEx() with MAPVK_VSC_TO_VK_EX. + */ + uint8_t ComputeVirtualKeyCodeFromScanCodeEx() const; + + /** + * Wraps MapVirtualKeyEx() with MAPVK_VK_TO_VSC_EX or MAPVK_VK_TO_VSC. + */ + uint16_t ComputeScanCodeExFromVirtualKeyCode(UINT aVirtualKeyCode) const; + + /** + * Wraps MapVirtualKeyEx() with MAPVK_VSC_TO_VK and MAPVK_VK_TO_CHAR. + */ + char16_t ComputeUnicharFromScanCode() const; + + /** + * Initializes the aKeyEvent with the information stored in the instance. + */ + nsEventStatus InitKeyEvent(WidgetKeyboardEvent& aKeyEvent, + const ModifierKeyState& aModKeyState, + const MSG* aMsgSentToPlugin = nullptr) const; + nsEventStatus InitKeyEvent(WidgetKeyboardEvent& aKeyEvent, + const MSG* aMsgSentToPlugin = nullptr) const; + + /** + * MaybeInitPluginEventOfKeyEvent() may initialize aKeyEvent::mPluginEvent + * with aMsgSentToPlugin if it's necessary. + */ + void MaybeInitPluginEventOfKeyEvent(WidgetKeyboardEvent& aKeyEvent, + const MSG& aMsgSentToPlugin) const; + + /** + * Dispatches a command event for aEventCommand. + * Returns true if the event is consumed. Otherwise, false. + */ + bool DispatchCommandEvent(uint32_t aEventCommand) const; + + /** + * DispatchKeyPressEventsWithRetrievedCharMessages() dispatches keypress + * event(s) with retrieved char messages. + */ + bool DispatchKeyPressEventsWithRetrievedCharMessages() const; + + /** + * DispatchKeyPressEventsWithoutCharMessage() dispatches keypress event(s) + * without char messages. So, this should be used only when there are no + * following char messages. + */ + bool DispatchKeyPressEventsWithoutCharMessage() const; + + /** + * MaybeDispatchPluginEventsForRemovedCharMessages() dispatches plugin events + * for removed char messages when a windowless plugin has focus. + * Returns true if the widget is destroyed or blurred during dispatching a + * plugin event. + */ + bool MaybeDispatchPluginEventsForRemovedCharMessages() const; + + /** + * Checkes whether the key event down message is handled without following + * WM_CHAR messages. For example, if following WM_CHAR message indicates + * control character input, the WM_CHAR message is unclear whether it's + * caused by a printable key with Ctrl or just a function key such as Enter + * or Backspace. + */ + bool NeedsToHandleWithoutFollowingCharMessages() const; + + /** + * ComputeInputtingStringWithKeyboardLayout() computes string to be inputted + * with the key and the modifier state, without shift state and with shift + * state. + */ + void ComputeInputtingStringWithKeyboardLayout(); + + /** + * IsFocusedWindowChanged() returns true if focused window is changed + * after the instance is created. + */ + bool IsFocusedWindowChanged() const + { + return mFocusedWndBeforeDispatch != ::GetFocus(); + } + + /** + * Handles WM_CHAR message or WM_SYSCHAR message. The instance must be + * initialized with WM_KEYDOWN, WM_SYSKEYDOWN or them. + * Returns true if dispatched keypress event is consumed. Otherwise, false. + */ + bool HandleCharMessage(const MSG& aCharMsg, + bool* aEventDispatched = nullptr) const; + + // Calls of PeekMessage() from NativeKey might cause nested message handling + // due to (perhaps) odd API hook. NativeKey should do nothing if given + // message is tried to be retrieved by another instance. + + /** + * sLatestInstacne is a pointer to the newest instance of NativeKey which is + * handling a key or char message(s). + */ + static NativeKey* sLatestInstance; + + static const MSG sEmptyMSG; + + static bool IsEmptyMSG(const MSG& aMSG) + { + return !memcmp(&aMSG, &sEmptyMSG, sizeof(MSG)); + } + + bool IsAnotherInstanceRemovingCharMessage() const + { + return mLastInstance && !IsEmptyMSG(mLastInstance->mRemovingMsg); + } +}; + +class KeyboardLayout +{ +public: + static KeyboardLayout* GetInstance(); + static void Shutdown(); + static HKL GetActiveLayout(); + static nsCString GetActiveLayoutName(); + static void NotifyIdleServiceOfUserActivity(); + + static bool IsPrintableCharKey(uint8_t aVirtualKey); + + /** + * IsDeadKey() returns true if aVirtualKey is a dead key with aModKeyState. + * This method isn't stateful. + */ + bool IsDeadKey(uint8_t aVirtualKey, + const ModifierKeyState& aModKeyState) const; + + /** + * IsInDeadKeySequence() returns true when it's in a dead key sequence. + * It starts when a dead key is down and ends when another key down causes + * inactivating the dead key state. + */ + bool IsInDeadKeySequence() const { return !mActiveDeadKeys.IsEmpty(); } + + /** + * IsSysKey() returns true if aVirtualKey with aModKeyState causes WM_SYSKEY* + * or WM_SYS*CHAR messages. + */ + bool IsSysKey(uint8_t aVirtualKey, + const ModifierKeyState& aModKeyState) const; + + /** + * GetUniCharsAndModifiers() returns characters which are inputted by + * aVirtualKey with aModKeyState. This method isn't stateful. + * Note that if the combination causes text input, the result's Ctrl and + * Alt key state are never active. + */ + UniCharsAndModifiers GetUniCharsAndModifiers( + uint8_t aVirtualKey, + const ModifierKeyState& aModKeyState) const + { + VirtualKey::ShiftState shiftState = + VirtualKey::ModifierKeyStateToShiftState(aModKeyState); + return GetUniCharsAndModifiers(aVirtualKey, shiftState); + } + + /** + * GetNativeUniCharsAndModifiers() returns characters which are inputted by + * aVirtualKey with aModKeyState. The method isn't stateful. + * Note that different from GetUniCharsAndModifiers(), this returns + * actual modifier state of Ctrl and Alt. + */ + UniCharsAndModifiers GetNativeUniCharsAndModifiers( + uint8_t aVirtualKey, + const ModifierKeyState& aModKeyState) const; + + /** + * OnLayoutChange() must be called before the first keydown message is + * received. LoadLayout() changes the keyboard state, that causes breaking + * dead key state. Therefore, we need to load the layout before the first + * keydown message. + */ + void OnLayoutChange(HKL aKeyboardLayout) + { + MOZ_ASSERT(!mIsOverridden); + LoadLayout(aKeyboardLayout); + } + + /** + * OverrideLayout() loads the specified keyboard layout. + */ + void OverrideLayout(HKL aLayout) + { + mIsOverridden = true; + LoadLayout(aLayout); + } + + /** + * RestoreLayout() loads the current keyboard layout of the thread. + */ + void RestoreLayout() + { + mIsOverridden = false; + mIsPendingToRestoreKeyboardLayout = true; + } + + uint32_t ConvertNativeKeyCodeToDOMKeyCode(UINT aNativeKeyCode) const; + + /** + * ConvertNativeKeyCodeToKeyNameIndex() returns KeyNameIndex value for + * non-printable keys (except some special keys like space key). + */ + KeyNameIndex ConvertNativeKeyCodeToKeyNameIndex(uint8_t aVirtualKey) const; + + /** + * ConvertScanCodeToCodeNameIndex() returns CodeNameIndex value for + * the given scan code. aScanCode can be over 0xE000 since this method + * doesn't use Windows API. + */ + static CodeNameIndex ConvertScanCodeToCodeNameIndex(UINT aScanCode); + + HKL GetLayout() const + { + return mIsPendingToRestoreKeyboardLayout ? ::GetKeyboardLayout(0) : + mKeyboardLayout; + } + + /** + * This wraps MapVirtualKeyEx() API with MAPVK_VK_TO_VSC. + */ + WORD ComputeScanCodeForVirtualKeyCode(uint8_t aVirtualKeyCode) const; + + /** + * Implementation of nsIWidget::SynthesizeNativeKeyEvent(). + */ + nsresult SynthesizeNativeKeyEvent(nsWindowBase* aWidget, + int32_t aNativeKeyboardLayout, + int32_t aNativeKeyCode, + uint32_t aModifierFlags, + const nsAString& aCharacters, + const nsAString& aUnmodifiedCharacters); + +private: + KeyboardLayout(); + ~KeyboardLayout(); + + static KeyboardLayout* sInstance; + static nsIIdleServiceInternal* sIdleService; + + struct DeadKeyTableListEntry + { + DeadKeyTableListEntry* next; + uint8_t data[1]; + }; + + HKL mKeyboardLayout; + + VirtualKey mVirtualKeys[NS_NUM_OF_KEYS]; + DeadKeyTableListEntry* mDeadKeyTableListHead; + // When mActiveDeadKeys is empty, it's not in dead key sequence. + // Otherwise, it contains virtual keycodes which are pressed in current + // dead key sequence. + nsTArray<uint8_t> mActiveDeadKeys; + // mDeadKeyShiftStates is always same length as mActiveDeadKeys. + // This stores shift states at pressing each dead key stored in + // mActiveDeadKeys. + nsTArray<VirtualKey::ShiftState> mDeadKeyShiftStates; + + bool mIsOverridden; + bool mIsPendingToRestoreKeyboardLayout; + + static inline int32_t GetKeyIndex(uint8_t aVirtualKey); + static int CompareDeadKeyEntries(const void* aArg1, const void* aArg2, + void* aData); + static bool AddDeadKeyEntry(char16_t aBaseChar, char16_t aCompositeChar, + DeadKeyEntry* aDeadKeyArray, uint32_t aEntries); + bool EnsureDeadKeyActive(bool aIsActive, uint8_t aDeadKey, + const PBYTE aDeadKeyKbdState); + uint32_t GetDeadKeyCombinations(uint8_t aDeadKey, + const PBYTE aDeadKeyKbdState, + uint16_t aShiftStatesWithBaseChars, + DeadKeyEntry* aDeadKeyArray, + uint32_t aMaxEntries); + /** + * Activates or deactivates dead key state. + */ + void ActivateDeadKeyState(const NativeKey& aNativeKey, + const ModifierKeyState& aModKeyState); + void DeactivateDeadKeyState(); + + const DeadKeyTable* AddDeadKeyTable(const DeadKeyEntry* aDeadKeyArray, + uint32_t aEntries); + void ReleaseDeadKeyTables(); + + /** + * Loads the specified keyboard layout. This method always clear the dead key + * state. + */ + void LoadLayout(HKL aLayout); + + /** + * Gets the keyboard layout name of aLayout. Be careful, this may be too + * slow to call at handling user input. + */ + nsCString GetLayoutName(HKL aLayout) const; + + /** + * InitNativeKey() must be called when actually widget receives WM_KEYDOWN or + * WM_KEYUP. This method is stateful. This saves current dead key state at + * WM_KEYDOWN. Additionally, computes current inputted character(s) and set + * them to the aNativeKey. + */ + void InitNativeKey(NativeKey& aNativeKey, + const ModifierKeyState& aModKeyState); + + /** + * MaybeInitNativeKeyAsDeadKey() initializes aNativeKey only when aNativeKey + * is a dead key's event. + * When it's not in a dead key sequence, this activates the dead key state. + * When it's in a dead key sequence, this initializes aNativeKey with a + * composite character or a preceding dead char and a dead char which should + * be caused by aNativeKey. + * Returns true when this initializes aNativeKey. Otherwise, false. + */ + bool MaybeInitNativeKeyAsDeadKey(NativeKey& aNativeKey, + const ModifierKeyState& aModKeyState); + + /** + * MaybeInitNativeKeyWithCompositeChar() may initialize aNativeKey with + * proper composite character when dead key produces a composite character. + * Otherwise, just returns false. + */ + bool MaybeInitNativeKeyWithCompositeChar( + NativeKey& aNativeKey, + const ModifierKeyState& aModKeyState); + + /** + * See the comment of GetUniCharsAndModifiers() below. + */ + UniCharsAndModifiers GetUniCharsAndModifiers( + uint8_t aVirtualKey, + VirtualKey::ShiftState aShiftState) const; + + /** + * GetDeadUniCharsAndModifiers() returns dead chars which are stored in + * current dead key sequence. So, this is stateful. + */ + UniCharsAndModifiers GetDeadUniCharsAndModifiers() const; + + /** + * GetCompositeChar() returns a composite character with dead character + * caused by mActiveDeadKeys, mDeadKeyShiftStates and a base character + * (aBaseChar). + * If the combination of the dead character and the base character doesn't + * cause a composite character, this returns 0. + */ + char16_t GetCompositeChar(char16_t aBaseChar) const; + + // NativeKey class should access InitNativeKey() directly, but it shouldn't + // be available outside of NativeKey. So, let's make NativeKey a friend + // class of this. + friend class NativeKey; +}; + +class RedirectedKeyDownMessageManager +{ +public: + /* + * If a window receives WM_KEYDOWN message or WM_SYSKEYDOWM message which is + * a redirected message, NativeKey::DispatchKeyDownAndKeyPressEvent() + * prevents to dispatch eKeyDown event because it has been dispatched + * before the message was redirected. However, in some cases, WM_*KEYDOWN + * message handler may not handle actually. Then, the message handler needs + * to forget the redirected message and remove WM_CHAR message or WM_SYSCHAR + * message for the redirected keydown message. AutoFlusher class is a helper + * class for doing it. This must be created in the stack. + */ + class MOZ_STACK_CLASS AutoFlusher final + { + public: + AutoFlusher(nsWindowBase* aWidget, const MSG &aMsg) : + mCancel(!RedirectedKeyDownMessageManager::IsRedirectedMessage(aMsg)), + mWidget(aWidget), mMsg(aMsg) + { + } + + ~AutoFlusher() + { + if (mCancel) { + return; + } + // Prevent unnecessary keypress event + if (!mWidget->Destroyed()) { + RedirectedKeyDownMessageManager::RemoveNextCharMessage(mMsg.hwnd); + } + // Foreget the redirected message + RedirectedKeyDownMessageManager::Forget(); + } + + void Cancel() { mCancel = true; } + + private: + bool mCancel; + RefPtr<nsWindowBase> mWidget; + const MSG &mMsg; + }; + + static void WillRedirect(const MSG& aMsg, bool aDefualtPrevented) + { + sRedirectedKeyDownMsg = aMsg; + sDefaultPreventedOfRedirectedMsg = aDefualtPrevented; + } + + static void Forget() + { + sRedirectedKeyDownMsg.message = WM_NULL; + } + + static void PreventDefault() { sDefaultPreventedOfRedirectedMsg = true; } + static bool DefaultPrevented() { return sDefaultPreventedOfRedirectedMsg; } + + static bool IsRedirectedMessage(const MSG& aMsg); + + /** + * RemoveNextCharMessage() should be called by WM_KEYDOWN or WM_SYSKEYDOWM + * message handler. If there is no WM_(SYS)CHAR message for it, this + * method does nothing. + * NOTE: WM_(SYS)CHAR message is posted by TranslateMessage() API which is + * called in message loop. So, WM_(SYS)KEYDOWN message should have + * WM_(SYS)CHAR message in the queue if the keydown event causes character + * input. + */ + static void RemoveNextCharMessage(HWND aWnd); + +private: + // sRedirectedKeyDownMsg is WM_KEYDOWN message or WM_SYSKEYDOWN message which + // is reirected with SendInput() API by + // widget::NativeKey::DispatchKeyDownAndKeyPressEvent() + static MSG sRedirectedKeyDownMsg; + static bool sDefaultPreventedOfRedirectedMsg; +}; + +} // namespace widget +} // namespace mozilla + +#endif diff --git a/widget/windows/LSPAnnotator.cpp b/widget/windows/LSPAnnotator.cpp new file mode 100644 index 0000000000..de4a40d2ac --- /dev/null +++ b/widget/windows/LSPAnnotator.cpp @@ -0,0 +1,161 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * LSPs are evil little bits of code that are allowed to inject into our + * networking stack by Windows. Once they have wormed into our process + * they gnaw at our innards until we crash. Here we force the buggers + * into the light by recording them in our crash information. + * We do the enumeration on a thread because I'm concerned about startup perf + * on machines with several LSPs. + */ + +#if _WIN32_WINNT < 0x0600 +// Redefining _WIN32_WINNT for some Vista APIs that we call +#undef _WIN32_WINNT +#define _WIN32_WINNT 0x0600 +#endif +#include "nsICrashReporter.h" +#include "nsISupportsImpl.h" +#include "nsServiceManagerUtils.h" +#include "nsThreadUtils.h" +#include "nsQueryObject.h" +#include "nsWindowsHelpers.h" +#include <windows.h> +#include <rpc.h> +#include <ws2spi.h> + +namespace mozilla { +namespace crashreporter { + +class LSPAnnotationGatherer : public Runnable +{ + ~LSPAnnotationGatherer() {} + +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIRUNNABLE + + void Annotate(); + nsCString mString; + nsCOMPtr<nsIThread> mThread; +}; + +NS_IMPL_ISUPPORTS(LSPAnnotationGatherer, nsIRunnable) + +void +LSPAnnotationGatherer::Annotate() +{ + nsCOMPtr<nsICrashReporter> cr = + do_GetService("@mozilla.org/toolkit/crash-reporter;1"); + bool enabled; + if (cr && NS_SUCCEEDED(cr->GetEnabled(&enabled)) && enabled) { + cr->AnnotateCrashReport(NS_LITERAL_CSTRING("Winsock_LSP"), mString); + } + mThread->AsyncShutdown(); +} + +NS_IMETHODIMP +LSPAnnotationGatherer::Run() +{ + PR_SetCurrentThreadName("LSP Annotator"); + + mThread = NS_GetCurrentThread(); + + DWORD size = 0; + int err; + // Get the size of the buffer we need + if (SOCKET_ERROR != WSCEnumProtocols(nullptr, nullptr, &size, &err) || + err != WSAENOBUFS) { + // Er, what? + NS_NOTREACHED("WSCEnumProtocols suceeded when it should have failed ..."); + return NS_ERROR_FAILURE; + } + + auto byteArray = MakeUnique<char[]>(size); + WSAPROTOCOL_INFOW* providers = + reinterpret_cast<WSAPROTOCOL_INFOW*>(byteArray.get()); + + int n = WSCEnumProtocols(nullptr, providers, &size, &err); + if (n == SOCKET_ERROR) { + // Lame. We provided the right size buffer; we'll just give up now. + NS_WARNING("Could not get LSP list"); + return NS_ERROR_FAILURE; + } + + nsCString str; + for (int i = 0; i < n; i++) { + AppendUTF16toUTF8(nsDependentString(providers[i].szProtocol), str); + str.AppendLiteral(" : "); + str.AppendInt(providers[i].iVersion); + str.AppendLiteral(" : "); + str.AppendInt(providers[i].iAddressFamily); + str.AppendLiteral(" : "); + str.AppendInt(providers[i].iSocketType); + str.AppendLiteral(" : "); + str.AppendInt(providers[i].iProtocol); + str.AppendLiteral(" : "); + str.AppendPrintf("0x%x", providers[i].dwServiceFlags1); + str.AppendLiteral(" : "); + str.AppendPrintf("0x%x", providers[i].dwProviderFlags); + str.AppendLiteral(" : "); + + wchar_t path[MAX_PATH]; + int pathLen = MAX_PATH; + if (!WSCGetProviderPath(&providers[i].ProviderId, path, &pathLen, &err)) { + AppendUTF16toUTF8(nsDependentString(path), str); + } + + str.AppendLiteral(" : "); + // If WSCGetProviderInfo is available, we should call it to obtain the + // category flags for this provider. When present, these flags inform + // Windows as to which order to chain the providers. + nsModuleHandle ws2_32(LoadLibraryW(L"ws2_32.dll")); + if (ws2_32) { + decltype(WSCGetProviderInfo)* pWSCGetProviderInfo = + reinterpret_cast<decltype(WSCGetProviderInfo)*>( + GetProcAddress(ws2_32, "WSCGetProviderInfo")); + if (pWSCGetProviderInfo) { + DWORD categoryInfo; + size_t categoryInfoSize = sizeof(categoryInfo); + if (!pWSCGetProviderInfo(&providers[i].ProviderId, + ProviderInfoLspCategories, + (PBYTE)&categoryInfo, &categoryInfoSize, 0, + &err)) { + str.AppendPrintf("0x%x", categoryInfo); + } + } + } + + str.AppendLiteral(" : "); + if (providers[i].ProtocolChain.ChainLen <= BASE_PROTOCOL) { + // If we're dealing with a catalog entry that identifies an individual + // base or layer provider, log its provider GUID. + RPC_CSTR provIdStr = nullptr; + if (UuidToStringA(&providers[i].ProviderId, &provIdStr) == RPC_S_OK) { + str.Append(reinterpret_cast<char*>(provIdStr)); + RpcStringFreeA(&provIdStr); + } + } + + if (i + 1 != n) { + str.AppendLiteral(" \n "); + } + } + + mString = str; + NS_DispatchToMainThread(NewRunnableMethod(this, &LSPAnnotationGatherer::Annotate)); + return NS_OK; +} + +void LSPAnnotate() +{ + nsCOMPtr<nsIThread> thread; + nsCOMPtr<nsIRunnable> runnable = + do_QueryObject(new LSPAnnotationGatherer()); + NS_NewThread(getter_AddRefs(thread), runnable); +} + +} // namespace crashreporter +} // namespace mozilla diff --git a/widget/windows/PCompositorWidget.ipdl b/widget/windows/PCompositorWidget.ipdl new file mode 100644 index 0000000000..4805c95a41 --- /dev/null +++ b/widget/windows/PCompositorWidget.ipdl @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +include protocol PCompositorBridge; + +using mozilla::gfx::IntSize from "mozilla/gfx/Point.h"; + +namespace mozilla { +namespace widget { + +sync protocol PCompositorWidget +{ + manager PCompositorBridge; + +parent: + sync EnterPresentLock(); + sync LeavePresentLock(); + async UpdateTransparency(int32_t aMode); + sync ClearTransparentWindow(); + async __delete__(); + +child: + async ObserveVsync(); + async UnobserveVsync(); +}; + +} // namespace widget +} // namespace mozilla diff --git a/widget/windows/PlatformWidgetTypes.ipdlh b/widget/windows/PlatformWidgetTypes.ipdlh new file mode 100644 index 0000000000..aad9cf3959 --- /dev/null +++ b/widget/windows/PlatformWidgetTypes.ipdlh @@ -0,0 +1,23 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// This file is a stub, for platforms that do not yet support out-of-process +// compositing or do not need specialized types to do so. + +using mozilla::WindowsHandle from "ipc/IPCMessageUtils.h"; + +namespace mozilla { +namespace widget { + +struct CompositorWidgetInitData +{ + WindowsHandle hWnd; + uintptr_t widgetKey; + int32_t transparencyMode; +}; + +} // namespace widget +} // namespace mozilla diff --git a/widget/windows/TSFTextStore.cpp b/widget/windows/TSFTextStore.cpp new file mode 100644 index 0000000000..fb0505aa37 --- /dev/null +++ b/widget/windows/TSFTextStore.cpp @@ -0,0 +1,6423 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#define INPUTSCOPE_INIT_GUID +#define TEXTATTRS_INIT_GUID +#include "TSFTextStore.h" + +#include <olectl.h> +#include <algorithm> + +#include "nscore.h" +#include "nsWindow.h" +#include "nsPrintfCString.h" +#include "WinIMEHandler.h" +#include "WinUtils.h" +#include "mozilla/AutoRestore.h" +#include "mozilla/Logging.h" +#include "mozilla/Preferences.h" +#include "mozilla/TextEventDispatcher.h" +#include "mozilla/TextEvents.h" +#include "mozilla/WindowsVersion.h" +#include "nsIXULRuntime.h" + +namespace mozilla { +namespace widget { + +static const char* kPrefNameEnableTSF = "intl.tsf.enable"; + +/** + * TSF related code should log its behavior even on release build especially + * in the interface methods. + * + * In interface methods, use LogLevel::Info. + * In internal methods, use LogLevel::Debug for logging normal behavior. + * For logging error, use LogLevel::Error. + * + * When an instance method is called, start with following text: + * "0x%p TSFFoo::Bar(", the 0x%p should be the "this" of the nsFoo. + * after that, start with: + * "0x%p TSFFoo::Bar(" + * In an internal method, start with following text: + * "0x%p TSFFoo::Bar(" + * When a static method is called, start with following text: + * "TSFFoo::Bar(" + */ + +LazyLogModule sTextStoreLog("nsTextStoreWidgets"); + +static const char* +GetBoolName(bool aBool) +{ + return aBool ? "true" : "false"; +} + +static void +HandleSeparator(nsCString& aDesc) +{ + if (!aDesc.IsEmpty()) { + aDesc.AppendLiteral(" | "); + } +} + +static const nsCString +GetFindFlagName(DWORD aFindFlag) +{ + nsAutoCString description; + if (!aFindFlag) { + description.AppendLiteral("no flags (0)"); + return description; + } + if (aFindFlag & TS_ATTR_FIND_BACKWARDS) { + description.AppendLiteral("TS_ATTR_FIND_BACKWARDS"); + } + if (aFindFlag & TS_ATTR_FIND_WANT_OFFSET) { + HandleSeparator(description); + description.AppendLiteral("TS_ATTR_FIND_WANT_OFFSET"); + } + if (aFindFlag & TS_ATTR_FIND_UPDATESTART) { + HandleSeparator(description); + description.AppendLiteral("TS_ATTR_FIND_UPDATESTART"); + } + if (aFindFlag & TS_ATTR_FIND_WANT_VALUE) { + HandleSeparator(description); + description.AppendLiteral("TS_ATTR_FIND_WANT_VALUE"); + } + if (aFindFlag & TS_ATTR_FIND_WANT_END) { + HandleSeparator(description); + description.AppendLiteral("TS_ATTR_FIND_WANT_END"); + } + if (aFindFlag & TS_ATTR_FIND_HIDDEN) { + HandleSeparator(description); + description.AppendLiteral("TS_ATTR_FIND_HIDDEN"); + } + if (description.IsEmpty()) { + description.AppendLiteral("Unknown ("); + description.AppendInt(static_cast<uint32_t>(aFindFlag)); + description.Append(')'); + } + return description; +} + +class GetACPFromPointFlagName : public nsAutoCString +{ +public: + GetACPFromPointFlagName(DWORD aFlags) + { + if (!aFlags) { + AppendLiteral("no flags (0)"); + return; + } + if (aFlags & GXFPF_ROUND_NEAREST) { + AppendLiteral("GXFPF_ROUND_NEAREST"); + aFlags &= ~GXFPF_ROUND_NEAREST; + } + if (aFlags & GXFPF_NEAREST) { + HandleSeparator(*this); + AppendLiteral("GXFPF_NEAREST"); + aFlags &= ~GXFPF_NEAREST; + } + if (aFlags) { + HandleSeparator(*this); + AppendLiteral("Unknown("); + AppendInt(static_cast<uint32_t>(aFlags)); + Append(')'); + } + } + virtual ~GetACPFromPointFlagName() {} +}; + +static const char* +GetIMEEnabledName(IMEState::Enabled aIMEEnabled) +{ + switch (aIMEEnabled) { + case IMEState::DISABLED: + return "DISABLED"; + case IMEState::ENABLED: + return "ENABLED"; + case IMEState::PASSWORD: + return "PASSWORD"; + case IMEState::PLUGIN: + return "PLUGIN"; + default: + return "Invalid"; + } +} + +static const char* +GetFocusChangeName(InputContextAction::FocusChange aFocusChange) +{ + switch (aFocusChange) { + case InputContextAction::FOCUS_NOT_CHANGED: + return "FOCUS_NOT_CHANGED"; + case InputContextAction::GOT_FOCUS: + return "GOT_FOCUS"; + case InputContextAction::LOST_FOCUS: + return "LOST_FOCUS"; + case InputContextAction::MENU_GOT_PSEUDO_FOCUS: + return "MENU_GOT_PSEUDO_FOCUS"; + case InputContextAction::MENU_LOST_PSEUDO_FOCUS: + return "MENU_LOST_PSEUDO_FOCUS"; + default: + return "Unknown"; + } +} + +static nsCString +GetCLSIDNameStr(REFCLSID aCLSID) +{ + LPOLESTR str = nullptr; + HRESULT hr = ::StringFromCLSID(aCLSID, &str); + if (FAILED(hr) || !str || !str[0]) { + return EmptyCString(); + } + + nsAutoCString result; + result = NS_ConvertUTF16toUTF8(str); + ::CoTaskMemFree(str); + return result; +} + +static nsCString +GetGUIDNameStr(REFGUID aGUID) +{ + OLECHAR str[40]; + int len = ::StringFromGUID2(aGUID, str, ArrayLength(str)); + if (!len || !str[0]) { + return EmptyCString(); + } + + return NS_ConvertUTF16toUTF8(str); +} + +static nsCString +GetGUIDNameStrWithTable(REFGUID aGUID) +{ +#define RETURN_GUID_NAME(aNamedGUID) \ + if (IsEqualGUID(aGUID, aNamedGUID)) { \ + return NS_LITERAL_CSTRING(#aNamedGUID); \ + } + + RETURN_GUID_NAME(GUID_PROP_INPUTSCOPE) + RETURN_GUID_NAME(TSATTRID_OTHERS) + RETURN_GUID_NAME(TSATTRID_Font) + RETURN_GUID_NAME(TSATTRID_Font_FaceName) + RETURN_GUID_NAME(TSATTRID_Font_SizePts) + RETURN_GUID_NAME(TSATTRID_Font_Style) + RETURN_GUID_NAME(TSATTRID_Font_Style_Bold) + RETURN_GUID_NAME(TSATTRID_Font_Style_Italic) + RETURN_GUID_NAME(TSATTRID_Font_Style_SmallCaps) + RETURN_GUID_NAME(TSATTRID_Font_Style_Capitalize) + RETURN_GUID_NAME(TSATTRID_Font_Style_Uppercase) + RETURN_GUID_NAME(TSATTRID_Font_Style_Lowercase) + RETURN_GUID_NAME(TSATTRID_Font_Style_Animation) + RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_LasVegasLights) + RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_BlinkingBackground) + RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_SparkleText) + RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_MarchingBlackAnts) + RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_MarchingRedAnts) + RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_Shimmer) + RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_WipeDown) + RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_WipeRight) + RETURN_GUID_NAME(TSATTRID_Font_Style_Emboss) + RETURN_GUID_NAME(TSATTRID_Font_Style_Engrave) + RETURN_GUID_NAME(TSATTRID_Font_Style_Hidden) + RETURN_GUID_NAME(TSATTRID_Font_Style_Kerning) + RETURN_GUID_NAME(TSATTRID_Font_Style_Outlined) + RETURN_GUID_NAME(TSATTRID_Font_Style_Position) + RETURN_GUID_NAME(TSATTRID_Font_Style_Protected) + RETURN_GUID_NAME(TSATTRID_Font_Style_Shadow) + RETURN_GUID_NAME(TSATTRID_Font_Style_Spacing) + RETURN_GUID_NAME(TSATTRID_Font_Style_Weight) + RETURN_GUID_NAME(TSATTRID_Font_Style_Height) + RETURN_GUID_NAME(TSATTRID_Font_Style_Underline) + RETURN_GUID_NAME(TSATTRID_Font_Style_Underline_Single) + RETURN_GUID_NAME(TSATTRID_Font_Style_Underline_Double) + RETURN_GUID_NAME(TSATTRID_Font_Style_Strikethrough) + RETURN_GUID_NAME(TSATTRID_Font_Style_Strikethrough_Single) + RETURN_GUID_NAME(TSATTRID_Font_Style_Strikethrough_Double) + RETURN_GUID_NAME(TSATTRID_Font_Style_Overline) + RETURN_GUID_NAME(TSATTRID_Font_Style_Overline_Single) + RETURN_GUID_NAME(TSATTRID_Font_Style_Overline_Double) + RETURN_GUID_NAME(TSATTRID_Font_Style_Blink) + RETURN_GUID_NAME(TSATTRID_Font_Style_Subscript) + RETURN_GUID_NAME(TSATTRID_Font_Style_Superscript) + RETURN_GUID_NAME(TSATTRID_Font_Style_Color) + RETURN_GUID_NAME(TSATTRID_Font_Style_BackgroundColor) + RETURN_GUID_NAME(TSATTRID_Text) + RETURN_GUID_NAME(TSATTRID_Text_VerticalWriting) + RETURN_GUID_NAME(TSATTRID_Text_RightToLeft) + RETURN_GUID_NAME(TSATTRID_Text_Orientation) + RETURN_GUID_NAME(TSATTRID_Text_Language) + RETURN_GUID_NAME(TSATTRID_Text_ReadOnly) + RETURN_GUID_NAME(TSATTRID_Text_EmbeddedObject) + RETURN_GUID_NAME(TSATTRID_Text_Alignment) + RETURN_GUID_NAME(TSATTRID_Text_Alignment_Left) + RETURN_GUID_NAME(TSATTRID_Text_Alignment_Right) + RETURN_GUID_NAME(TSATTRID_Text_Alignment_Center) + RETURN_GUID_NAME(TSATTRID_Text_Alignment_Justify) + RETURN_GUID_NAME(TSATTRID_Text_Link) + RETURN_GUID_NAME(TSATTRID_Text_Hyphenation) + RETURN_GUID_NAME(TSATTRID_Text_Para) + RETURN_GUID_NAME(TSATTRID_Text_Para_FirstLineIndent) + RETURN_GUID_NAME(TSATTRID_Text_Para_LeftIndent) + RETURN_GUID_NAME(TSATTRID_Text_Para_RightIndent) + RETURN_GUID_NAME(TSATTRID_Text_Para_SpaceAfter) + RETURN_GUID_NAME(TSATTRID_Text_Para_SpaceBefore) + RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing) + RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing_Single) + RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing_OnePtFive) + RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing_Double) + RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing_AtLeast) + RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing_Exactly) + RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing_Multiple) + RETURN_GUID_NAME(TSATTRID_List) + RETURN_GUID_NAME(TSATTRID_List_LevelIndel) + RETURN_GUID_NAME(TSATTRID_List_Type) + RETURN_GUID_NAME(TSATTRID_List_Type_Bullet) + RETURN_GUID_NAME(TSATTRID_List_Type_Arabic) + RETURN_GUID_NAME(TSATTRID_List_Type_LowerLetter) + RETURN_GUID_NAME(TSATTRID_List_Type_UpperLetter) + RETURN_GUID_NAME(TSATTRID_List_Type_LowerRoman) + RETURN_GUID_NAME(TSATTRID_List_Type_UpperRoman) + RETURN_GUID_NAME(TSATTRID_App) + RETURN_GUID_NAME(TSATTRID_App_IncorrectSpelling) + RETURN_GUID_NAME(TSATTRID_App_IncorrectGrammar) + +#undef RETURN_GUID_NAME + + return GetGUIDNameStr(aGUID); +} + +static nsCString +GetRIIDNameStr(REFIID aRIID) +{ + LPOLESTR str = nullptr; + HRESULT hr = ::StringFromIID(aRIID, &str); + if (FAILED(hr) || !str || !str[0]) { + return EmptyCString(); + } + + nsAutoString key(L"Interface\\"); + key += str; + + nsAutoCString result; + wchar_t buf[256]; + if (WinUtils::GetRegistryKey(HKEY_CLASSES_ROOT, key.get(), nullptr, + buf, sizeof(buf))) { + result = NS_ConvertUTF16toUTF8(buf); + } else { + result = NS_ConvertUTF16toUTF8(str); + } + + ::CoTaskMemFree(str); + return result; +} + +static const char* +GetCommonReturnValueName(HRESULT aResult) +{ + switch (aResult) { + case S_OK: + return "S_OK"; + case E_ABORT: + return "E_ABORT"; + case E_ACCESSDENIED: + return "E_ACCESSDENIED"; + case E_FAIL: + return "E_FAIL"; + case E_HANDLE: + return "E_HANDLE"; + case E_INVALIDARG: + return "E_INVALIDARG"; + case E_NOINTERFACE: + return "E_NOINTERFACE"; + case E_NOTIMPL: + return "E_NOTIMPL"; + case E_OUTOFMEMORY: + return "E_OUTOFMEMORY"; + case E_POINTER: + return "E_POINTER"; + case E_UNEXPECTED: + return "E_UNEXPECTED"; + default: + return SUCCEEDED(aResult) ? "Succeeded" : "Failed"; + } +} + +static const char* +GetTextStoreReturnValueName(HRESULT aResult) +{ + switch (aResult) { + case TS_E_FORMAT: + return "TS_E_FORMAT"; + case TS_E_INVALIDPOINT: + return "TS_E_INVALIDPOINT"; + case TS_E_INVALIDPOS: + return "TS_E_INVALIDPOS"; + case TS_E_NOINTERFACE: + return "TS_E_NOINTERFACE"; + case TS_E_NOLAYOUT: + return "TS_E_NOLAYOUT"; + case TS_E_NOLOCK: + return "TS_E_NOLOCK"; + case TS_E_NOOBJECT: + return "TS_E_NOOBJECT"; + case TS_E_NOSELECTION: + return "TS_E_NOSELECTION"; + case TS_E_NOSERVICE: + return "TS_E_NOSERVICE"; + case TS_E_READONLY: + return "TS_E_READONLY"; + case TS_E_SYNCHRONOUS: + return "TS_E_SYNCHRONOUS"; + case TS_S_ASYNC: + return "TS_S_ASYNC"; + default: + return GetCommonReturnValueName(aResult); + } +} + +static const nsCString +GetSinkMaskNameStr(DWORD aSinkMask) +{ + nsAutoCString description; + if (aSinkMask & TS_AS_TEXT_CHANGE) { + description.AppendLiteral("TS_AS_TEXT_CHANGE"); + } + if (aSinkMask & TS_AS_SEL_CHANGE) { + HandleSeparator(description); + description.AppendLiteral("TS_AS_SEL_CHANGE"); + } + if (aSinkMask & TS_AS_LAYOUT_CHANGE) { + HandleSeparator(description); + description.AppendLiteral("TS_AS_LAYOUT_CHANGE"); + } + if (aSinkMask & TS_AS_ATTR_CHANGE) { + HandleSeparator(description); + description.AppendLiteral("TS_AS_ATTR_CHANGE"); + } + if (aSinkMask & TS_AS_STATUS_CHANGE) { + HandleSeparator(description); + description.AppendLiteral("TS_AS_STATUS_CHANGE"); + } + if (description.IsEmpty()) { + description.AppendLiteral("not-specified"); + } + return description; +} + +static const char* +GetActiveSelEndName(TsActiveSelEnd aSelEnd) +{ + return aSelEnd == TS_AE_NONE ? "TS_AE_NONE" : + aSelEnd == TS_AE_START ? "TS_AE_START" : + aSelEnd == TS_AE_END ? "TS_AE_END" : "Unknown"; +} + +static const nsCString +GetLockFlagNameStr(DWORD aLockFlags) +{ + nsAutoCString description; + if ((aLockFlags & TS_LF_READWRITE) == TS_LF_READWRITE) { + description.AppendLiteral("TS_LF_READWRITE"); + } else if (aLockFlags & TS_LF_READ) { + description.AppendLiteral("TS_LF_READ"); + } + if (aLockFlags & TS_LF_SYNC) { + if (!description.IsEmpty()) { + description.AppendLiteral(" | "); + } + description.AppendLiteral("TS_LF_SYNC"); + } + if (description.IsEmpty()) { + description.AppendLiteral("not-specified"); + } + return description; +} + +static const char* +GetTextRunTypeName(TsRunType aRunType) +{ + switch (aRunType) { + case TS_RT_PLAIN: + return "TS_RT_PLAIN"; + case TS_RT_HIDDEN: + return "TS_RT_HIDDEN"; + case TS_RT_OPAQUE: + return "TS_RT_OPAQUE"; + default: + return "Unknown"; + } +} + +static nsCString +GetColorName(const TF_DA_COLOR& aColor) +{ + switch (aColor.type) { + case TF_CT_NONE: + return NS_LITERAL_CSTRING("TF_CT_NONE"); + case TF_CT_SYSCOLOR: + return nsPrintfCString("TF_CT_SYSCOLOR, nIndex:0x%08X", + static_cast<int32_t>(aColor.nIndex)); + case TF_CT_COLORREF: + return nsPrintfCString("TF_CT_COLORREF, cr:0x%08X", + static_cast<int32_t>(aColor.cr)); + break; + default: + return nsPrintfCString("Unknown(%08X)", + static_cast<int32_t>(aColor.type)); + } +} + +static nsCString +GetLineStyleName(TF_DA_LINESTYLE aLineStyle) +{ + switch (aLineStyle) { + case TF_LS_NONE: + return NS_LITERAL_CSTRING("TF_LS_NONE"); + case TF_LS_SOLID: + return NS_LITERAL_CSTRING("TF_LS_SOLID"); + case TF_LS_DOT: + return NS_LITERAL_CSTRING("TF_LS_DOT"); + case TF_LS_DASH: + return NS_LITERAL_CSTRING("TF_LS_DASH"); + case TF_LS_SQUIGGLE: + return NS_LITERAL_CSTRING("TF_LS_SQUIGGLE"); + default: { + return nsPrintfCString("Unknown(%08X)", static_cast<int32_t>(aLineStyle)); + } + } +} + +static nsCString +GetClauseAttrName(TF_DA_ATTR_INFO aAttr) +{ + switch (aAttr) { + case TF_ATTR_INPUT: + return NS_LITERAL_CSTRING("TF_ATTR_INPUT"); + case TF_ATTR_TARGET_CONVERTED: + return NS_LITERAL_CSTRING("TF_ATTR_TARGET_CONVERTED"); + case TF_ATTR_CONVERTED: + return NS_LITERAL_CSTRING("TF_ATTR_CONVERTED"); + case TF_ATTR_TARGET_NOTCONVERTED: + return NS_LITERAL_CSTRING("TF_ATTR_TARGET_NOTCONVERTED"); + case TF_ATTR_INPUT_ERROR: + return NS_LITERAL_CSTRING("TF_ATTR_INPUT_ERROR"); + case TF_ATTR_FIXEDCONVERTED: + return NS_LITERAL_CSTRING("TF_ATTR_FIXEDCONVERTED"); + case TF_ATTR_OTHER: + return NS_LITERAL_CSTRING("TF_ATTR_OTHER"); + default: { + return nsPrintfCString("Unknown(%08X)", static_cast<int32_t>(aAttr)); + } + } +} + +static nsCString +GetDisplayAttrStr(const TF_DISPLAYATTRIBUTE& aDispAttr) +{ + nsAutoCString str; + str = "crText:{ "; + str += GetColorName(aDispAttr.crText); + str += " }, crBk:{ "; + str += GetColorName(aDispAttr.crBk); + str += " }, lsStyle: "; + str += GetLineStyleName(aDispAttr.lsStyle); + str += ", fBoldLine: "; + str += GetBoolName(aDispAttr.fBoldLine); + str += ", crLine:{ "; + str += GetColorName(aDispAttr.crLine); + str += " }, bAttr: "; + str += GetClauseAttrName(aDispAttr.bAttr); + return str; +} + +static const char* +GetMouseButtonName(int16_t aButton) +{ + switch (aButton) { + case WidgetMouseEventBase::eLeftButton: + return "LeftButton"; + case WidgetMouseEventBase::eMiddleButton: + return "MiddleButton"; + case WidgetMouseEventBase::eRightButton: + return "RightButton"; + default: + return "UnknownButton"; + } +} + +#define ADD_SEPARATOR_IF_NECESSARY(aStr) \ + if (!aStr.IsEmpty()) { \ + aStr.AppendLiteral(", "); \ + } + +static nsCString +GetMouseButtonsName(int16_t aButtons) +{ + if (!aButtons) { + return NS_LITERAL_CSTRING("no buttons"); + } + nsAutoCString names; + if (aButtons & WidgetMouseEventBase::eLeftButtonFlag) { + names = "LeftButton"; + } + if (aButtons & WidgetMouseEventBase::eRightButtonFlag) { + ADD_SEPARATOR_IF_NECESSARY(names); + names += "RightButton"; + } + if (aButtons & WidgetMouseEventBase::eMiddleButtonFlag) { + ADD_SEPARATOR_IF_NECESSARY(names); + names += "MiddleButton"; + } + if (aButtons & WidgetMouseEventBase::e4thButtonFlag) { + ADD_SEPARATOR_IF_NECESSARY(names); + names += "4thButton"; + } + if (aButtons & WidgetMouseEventBase::e5thButtonFlag) { + ADD_SEPARATOR_IF_NECESSARY(names); + names += "5thButton"; + } + return names; +} + +static nsCString +GetModifiersName(Modifiers aModifiers) +{ + if (aModifiers == MODIFIER_NONE) { + return NS_LITERAL_CSTRING("no modifiers"); + } + nsAutoCString names; + if (aModifiers & MODIFIER_ALT) { + names = NS_DOM_KEYNAME_ALT; + } + if (aModifiers & MODIFIER_ALTGRAPH) { + ADD_SEPARATOR_IF_NECESSARY(names); + names += NS_DOM_KEYNAME_ALTGRAPH; + } + if (aModifiers & MODIFIER_CAPSLOCK) { + ADD_SEPARATOR_IF_NECESSARY(names); + names += NS_DOM_KEYNAME_CAPSLOCK; + } + if (aModifiers & MODIFIER_CONTROL) { + ADD_SEPARATOR_IF_NECESSARY(names); + names += NS_DOM_KEYNAME_CONTROL; + } + if (aModifiers & MODIFIER_FN) { + ADD_SEPARATOR_IF_NECESSARY(names); + names += NS_DOM_KEYNAME_FN; + } + if (aModifiers & MODIFIER_FNLOCK) { + ADD_SEPARATOR_IF_NECESSARY(names); + names += NS_DOM_KEYNAME_FNLOCK; + } + if (aModifiers & MODIFIER_META) { + ADD_SEPARATOR_IF_NECESSARY(names); + names += NS_DOM_KEYNAME_META; + } + if (aModifiers & MODIFIER_NUMLOCK) { + ADD_SEPARATOR_IF_NECESSARY(names); + names += NS_DOM_KEYNAME_NUMLOCK; + } + if (aModifiers & MODIFIER_SCROLLLOCK) { + ADD_SEPARATOR_IF_NECESSARY(names); + names += NS_DOM_KEYNAME_SCROLLLOCK; + } + if (aModifiers & MODIFIER_SHIFT) { + ADD_SEPARATOR_IF_NECESSARY(names); + names += NS_DOM_KEYNAME_SHIFT; + } + if (aModifiers & MODIFIER_SYMBOL) { + ADD_SEPARATOR_IF_NECESSARY(names); + names += NS_DOM_KEYNAME_SYMBOL; + } + if (aModifiers & MODIFIER_SYMBOLLOCK) { + ADD_SEPARATOR_IF_NECESSARY(names); + names += NS_DOM_KEYNAME_SYMBOLLOCK; + } + if (aModifiers & MODIFIER_OS) { + ADD_SEPARATOR_IF_NECESSARY(names); + names += NS_DOM_KEYNAME_OS; + } + return names; +} + +class GetWritingModeName : public nsAutoCString +{ +public: + GetWritingModeName(const WritingMode& aWritingMode) + { + if (!aWritingMode.IsVertical()) { + AssignLiteral("Horizontal"); + return; + } + if (aWritingMode.IsVerticalLR()) { + AssignLiteral("Vertical (LR)"); + return; + } + AssignLiteral("Vertical (RL)"); + } + virtual ~GetWritingModeName() {} +}; + +class GetEscapedUTF8String final : public NS_ConvertUTF16toUTF8 +{ +public: + explicit GetEscapedUTF8String(const nsAString& aString) + : NS_ConvertUTF16toUTF8(aString) + { + Escape(); + } + explicit GetEscapedUTF8String(const char16ptr_t aString) + : NS_ConvertUTF16toUTF8(aString) + { + Escape(); + } + GetEscapedUTF8String(const char16ptr_t aString, uint32_t aLength) + : NS_ConvertUTF16toUTF8(aString, aLength) + { + Escape(); + } + +private: + void Escape() + { + ReplaceSubstring("\r", "\\r"); + ReplaceSubstring("\n", "\\n"); + ReplaceSubstring("\t", "\\t"); + } +}; + +/******************************************************************/ +/* InputScopeImpl */ +/******************************************************************/ + +class InputScopeImpl final : public ITfInputScope +{ + ~InputScopeImpl() {} + +public: + InputScopeImpl(const nsTArray<InputScope>& aList) + : mInputScopes(aList) + { + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p InputScopeImpl()", this)); + } + + NS_INLINE_DECL_IUNKNOWN_REFCOUNTING(InputScopeImpl) + + STDMETHODIMP QueryInterface(REFIID riid, void** ppv) + { + *ppv=nullptr; + if ( (IID_IUnknown == riid) || (IID_ITfInputScope == riid) ) { + *ppv = static_cast<ITfInputScope*>(this); + } + if (*ppv) { + AddRef(); + return S_OK; + } + return E_NOINTERFACE; + } + + STDMETHODIMP GetInputScopes(InputScope** pprgInputScopes, UINT* pcCount) + { + uint32_t count = (mInputScopes.IsEmpty() ? 1 : mInputScopes.Length()); + + InputScope* pScope = (InputScope*) CoTaskMemAlloc(sizeof(InputScope) * count); + NS_ENSURE_TRUE(pScope, E_OUTOFMEMORY); + + if (mInputScopes.IsEmpty()) { + *pScope = IS_DEFAULT; + *pcCount = 1; + *pprgInputScopes = pScope; + return S_OK; + } + + *pcCount = 0; + + for (uint32_t idx = 0; idx < count; idx++) { + *(pScope + idx) = mInputScopes[idx]; + (*pcCount)++; + } + + *pprgInputScopes = pScope; + return S_OK; + } + + STDMETHODIMP GetPhrase(BSTR **ppbstrPhrases, UINT* pcCount) + { + return E_NOTIMPL; + } + STDMETHODIMP GetRegularExpression(BSTR* pbstrRegExp) { return E_NOTIMPL; } + STDMETHODIMP GetSRGS(BSTR* pbstrSRGS) { return E_NOTIMPL; } + STDMETHODIMP GetXML(BSTR* pbstrXML) { return E_NOTIMPL; } + +private: + nsTArray<InputScope> mInputScopes; +}; + +/******************************************************************/ +/* TSFStaticSink */ +/******************************************************************/ + +class TSFStaticSink final : public ITfInputProcessorProfileActivationSink +{ +public: + static TSFStaticSink* GetInstance() + { + if (!sInstance) { + sInstance = new TSFStaticSink(); + } + return sInstance; + } + + static void Shutdown() + { + if (sInstance) { + sInstance->Destroy(); + sInstance = nullptr; + } + } + + bool Init(ITfThreadMgr* aThreadMgr, + ITfInputProcessorProfiles* aInputProcessorProfiles); + STDMETHODIMP QueryInterface(REFIID riid, void** ppv) + { + *ppv = nullptr; + if (IID_IUnknown == riid || + IID_ITfInputProcessorProfileActivationSink == riid) { + *ppv = static_cast<ITfInputProcessorProfileActivationSink*>(this); + } + if (*ppv) { + AddRef(); + return S_OK; + } + return E_NOINTERFACE; + } + + NS_INLINE_DECL_IUNKNOWN_REFCOUNTING(TSFStaticSink) + + const nsString& GetActiveTIPKeyboardDescription() const + { + return mActiveTIPKeyboardDescription; + } + + static bool IsIMM_IMEActive() + { + if (!sInstance || !sInstance->EnsureInitActiveTIPKeyboard()) { + return IsIMM_IME(::GetKeyboardLayout(0)); + } + return sInstance->mIsIMM_IME; + } + + static bool IsIMM_IME(HKL aHKL) + { + return (::ImmGetIMEFileNameW(aHKL, nullptr, 0) > 0); + } + + bool EnsureInitActiveTIPKeyboard(); + + /**************************************************************************** + * Japanese TIP + ****************************************************************************/ + + // Note that TIP name may depend on the language of the environment. + // For example, some TIP may use localized name for its target language + // environment but English name for the others. + + bool IsMSJapaneseIMEActive() const + { + // FYI: Name of MS-IME for Japanese is same as MS-IME for Korean. + // Therefore, we need to check the langid too. + return mLangID == 0x411 && + (mActiveTIPKeyboardDescription.EqualsLiteral("Microsoft IME") || + mActiveTIPKeyboardDescription.Equals( + NS_LITERAL_STRING(u"Microsoft \xC785\xB825\xAE30")) || + mActiveTIPKeyboardDescription.Equals( + NS_LITERAL_STRING(u"\x5FAE\x8F6F\x8F93\x5165\x6CD5")) || + mActiveTIPKeyboardDescription.Equals( + NS_LITERAL_STRING(u"\x5FAE\x8EDF\x8F38\x5165\x6CD5"))); + } + + bool IsMSOfficeJapaneseIME2010Active() const + { + // {54EDCC94-1524-4BB1-9FB7-7BABE4F4CA64} + static const GUID kGUID = { + 0x54EDCC94, 0x1524, 0x4BB1, + { 0x9F, 0xB7, 0x7B, 0xAB, 0xE4, 0xF4, 0xCA, 0x64 } + }; + return mActiveTIPGUID == kGUID; + } + + bool IsATOKActive() const + { + // FYI: Name of ATOK includes the release year like "ATOK 2015". + return StringBeginsWith(mActiveTIPKeyboardDescription, + NS_LITERAL_STRING("ATOK ")); + } + + bool IsATOK2011Active() const + { + // {F9C24A5C-8A53-499D-9572-93B2FF582115} + static const GUID kGUID = { + 0xF9C24A5C, 0x8A53, 0x499D, + { 0x95, 0x72, 0x93, 0xB2, 0xFF, 0x58, 0x21, 0x15 } + }; + return mActiveTIPGUID == kGUID; + } + + bool IsATOK2012Active() const + { + // {1DE01562-F445-401B-B6C3-E5B18DB79461} + static const GUID kGUID = { + 0x1DE01562, 0xF445, 0x401B, + { 0xB6, 0xC3, 0xE5, 0xB1, 0x8D, 0xB7, 0x94, 0x61 } + }; + return mActiveTIPGUID == kGUID; + } + + bool IsATOK2013Active() const + { + // {3C4DB511-189A-4168-B6EA-BFD0B4C85615} + static const GUID kGUID = { + 0x3C4DB511, 0x189A, 0x4168, + { 0xB6, 0xEA, 0xBF, 0xD0, 0xB4, 0xC8, 0x56, 0x15 } + }; + return mActiveTIPGUID == kGUID; + } + + bool IsATOK2014Active() const + { + // {4EF33B79-6AA9-4271-B4BF-9321C279381B} + static const GUID kGUID = { + 0x4EF33B79, 0x6AA9, 0x4271, + { 0xB4, 0xBF, 0x93, 0x21, 0xC2, 0x79, 0x38, 0x1B } + }; + return mActiveTIPGUID == kGUID; + } + + bool IsATOK2015Active() const + { + // {EAB4DC00-CE2E-483D-A86A-E6B99DA9599A} + static const GUID kGUID = { + 0xEAB4DC00, 0xCE2E, 0x483D, + { 0xA8, 0x6A, 0xE6, 0xB9, 0x9D, 0xA9, 0x59, 0x9A } + }; + return mActiveTIPGUID == kGUID; + } + + bool IsATOK2016Active() const + { + // {0B557B4C-5740-4110-A60A-1493FA10BF2B} + static const GUID kGUID = { + 0x0B557B4C, 0x5740, 0x4110, + { 0xA6, 0x0A, 0x14, 0x93, 0xFA, 0x10, 0xBF, 0x2B } + }; + return mActiveTIPGUID == kGUID; + } + + // Note that ATOK 2011 - 2016 refers native caret position for deciding its + // popup window position. + bool IsATOKReferringNativeCaretActive() const + { + return IsATOKActive() && + (IsATOK2011Active() || IsATOK2012Active() || IsATOK2013Active() || + IsATOK2014Active() || IsATOK2015Active() || IsATOK2016Active()); + } + + /**************************************************************************** + * Traditional Chinese TIP + ****************************************************************************/ + + bool IsMSChangJieActive() const + { + return mActiveTIPKeyboardDescription.EqualsLiteral("Microsoft ChangJie") || + mActiveTIPKeyboardDescription.Equals( + NS_LITERAL_STRING(u"\x5FAE\x8F6F\x4ED3\x9889")) || + mActiveTIPKeyboardDescription.Equals( + NS_LITERAL_STRING(u"\x5FAE\x8EDF\x5009\x9821")); + } + + bool IsMSQuickQuickActive() const + { + return mActiveTIPKeyboardDescription.EqualsLiteral("Microsoft Quick") || + mActiveTIPKeyboardDescription.Equals( + NS_LITERAL_STRING(u"\x5FAE\x8F6F\x901F\x6210")) || + mActiveTIPKeyboardDescription.Equals( + NS_LITERAL_STRING(u"\x5FAE\x8EDF\x901F\x6210")); + } + + bool IsFreeChangJieActive() const + { + // FYI: The TIP name is misspelled... + return mActiveTIPKeyboardDescription.EqualsLiteral("Free CangJie IME 10"); + } + + bool IsEasyChangjeiActive() const + { + return + mActiveTIPKeyboardDescription.Equals( + NS_LITERAL_STRING( + u"\x4E2D\x6587 (\x7E41\x9AD4) - \x6613\x9821\x8F38\x5165\x6CD5")); + } + + /**************************************************************************** + * Simplified Chinese TIP + ****************************************************************************/ + + bool IsMSPinyinActive() const + { + return mActiveTIPKeyboardDescription.EqualsLiteral("Microsoft Pinyin") || + mActiveTIPKeyboardDescription.Equals( + NS_LITERAL_STRING(u"\x5FAE\x8F6F\x62FC\x97F3")) || + mActiveTIPKeyboardDescription.Equals( + NS_LITERAL_STRING(u"\x5FAE\x8EDF\x62FC\x97F3")); + } + + bool IsMSWubiActive() const + { + return mActiveTIPKeyboardDescription.EqualsLiteral("Microsoft Wubi") || + mActiveTIPKeyboardDescription.Equals( + NS_LITERAL_STRING(u"\x5FAE\x8F6F\x4E94\x7B14")) || + mActiveTIPKeyboardDescription.Equals( + NS_LITERAL_STRING(u"\x5FAE\x8EDF\x4E94\x7B46")); + } + +public: // ITfInputProcessorProfileActivationSink + STDMETHODIMP OnActivated(DWORD, LANGID, REFCLSID, REFGUID, REFGUID, + HKL, DWORD); + +private: + TSFStaticSink(); + virtual ~TSFStaticSink() {} + + void Destroy(); + + void GetTIPDescription(REFCLSID aTextService, LANGID aLangID, + REFGUID aProfile, nsAString& aDescription); + bool IsTIPCategoryKeyboard(REFCLSID aTextService, LANGID aLangID, + REFGUID aProfile); + + // Cookie of installing ITfInputProcessorProfileActivationSink + DWORD mIPProfileCookie; + + LANGID mLangID; + + // True if current IME is implemented with IMM. + bool mIsIMM_IME; + // True if OnActivated() is already called + bool mOnActivatedCalled; + + RefPtr<ITfThreadMgr> mThreadMgr; + RefPtr<ITfInputProcessorProfiles> mInputProcessorProfiles; + + // Active TIP keyboard's description. If active language profile isn't TIP, + // i.e., IMM-IME or just a keyboard layout, this is empty. + nsString mActiveTIPKeyboardDescription; + + // Active TIP's GUID + GUID mActiveTIPGUID; + + static StaticRefPtr<TSFStaticSink> sInstance; +}; + +StaticRefPtr<TSFStaticSink> TSFStaticSink::sInstance; + +TSFStaticSink::TSFStaticSink() + : mIPProfileCookie(TF_INVALID_COOKIE) + , mLangID(0) + , mIsIMM_IME(false) + , mOnActivatedCalled(false) + , mActiveTIPGUID(GUID_NULL) +{ +} + +bool +TSFStaticSink::Init(ITfThreadMgr* aThreadMgr, + ITfInputProcessorProfiles* aInputProcessorProfiles) +{ + MOZ_ASSERT(!mThreadMgr && !mInputProcessorProfiles, + "TSFStaticSink::Init() must be called only once"); + + mThreadMgr = aThreadMgr; + mInputProcessorProfiles = aInputProcessorProfiles; + + RefPtr<ITfSource> source; + HRESULT hr = + mThreadMgr->QueryInterface(IID_ITfSource, getter_AddRefs(source)); + if (FAILED(hr)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFStaticSink::Init() FAILED to get ITfSource " + "instance (0x%08X)", this, hr)); + return false; + } + + // NOTE: On Vista or later, Windows let us know activate IME changed only + // with ITfInputProcessorProfileActivationSink. + hr = source->AdviseSink(IID_ITfInputProcessorProfileActivationSink, + static_cast<ITfInputProcessorProfileActivationSink*>(this), + &mIPProfileCookie); + if (FAILED(hr) || mIPProfileCookie == TF_INVALID_COOKIE) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFStaticSink::Init() FAILED to install " + "ITfInputProcessorProfileActivationSink (0x%08X)", this, hr)); + return false; + } + + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFStaticSink::Init(), " + "mIPProfileCookie=0x%08X", + this, mIPProfileCookie)); + return true; +} + +void +TSFStaticSink::Destroy() +{ + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFStaticSink::Shutdown() " + "mIPProfileCookie=0x%08X", + this, mIPProfileCookie)); + + if (mIPProfileCookie != TF_INVALID_COOKIE) { + RefPtr<ITfSource> source; + HRESULT hr = + mThreadMgr->QueryInterface(IID_ITfSource, getter_AddRefs(source)); + if (FAILED(hr)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFStaticSink::Shutdown() FAILED to get " + "ITfSource instance (0x%08X)", this, hr)); + } else { + hr = source->UnadviseSink(mIPProfileCookie); + if (FAILED(hr)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::Shutdown() FAILED to uninstall " + "ITfInputProcessorProfileActivationSink (0x%08X)", + this, hr)); + } + } + } + + mThreadMgr = nullptr; + mInputProcessorProfiles = nullptr; +} + +STDMETHODIMP +TSFStaticSink::OnActivated(DWORD dwProfileType, + LANGID langid, + REFCLSID rclsid, + REFGUID catid, + REFGUID guidProfile, + HKL hkl, + DWORD dwFlags) +{ + if ((dwFlags & TF_IPSINK_FLAG_ACTIVE) && + (dwProfileType == TF_PROFILETYPE_KEYBOARDLAYOUT || + catid == GUID_TFCAT_TIP_KEYBOARD)) { + mOnActivatedCalled = true; + mActiveTIPGUID = guidProfile; + mLangID = langid; + mIsIMM_IME = IsIMM_IME(hkl); + GetTIPDescription(rclsid, mLangID, guidProfile, + mActiveTIPKeyboardDescription); + // Notify IMEHandler of changing active keyboard layout. + IMEHandler::OnKeyboardLayoutChanged(); + } + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFStaticSink::OnActivated(dwProfileType=%s (0x%08X), " + "langid=0x%08X, rclsid=%s, catid=%s, guidProfile=%s, hkl=0x%08X, " + "dwFlags=0x%08X (TF_IPSINK_FLAG_ACTIVE: %s)), mIsIMM_IME=%s, " + "mActiveTIPDescription=\"%s\"", + this, dwProfileType == TF_PROFILETYPE_INPUTPROCESSOR ? + "TF_PROFILETYPE_INPUTPROCESSOR" : + dwProfileType == TF_PROFILETYPE_KEYBOARDLAYOUT ? + "TF_PROFILETYPE_KEYBOARDLAYOUT" : "Unknown", dwProfileType, + langid, GetCLSIDNameStr(rclsid).get(), GetGUIDNameStr(catid).get(), + GetGUIDNameStr(guidProfile).get(), hkl, dwFlags, + GetBoolName(dwFlags & TF_IPSINK_FLAG_ACTIVE), + GetBoolName(mIsIMM_IME), + NS_ConvertUTF16toUTF8(mActiveTIPKeyboardDescription).get())); + return S_OK; +} + +bool +TSFStaticSink::EnsureInitActiveTIPKeyboard() +{ + if (mOnActivatedCalled) { + return true; + } + + RefPtr<ITfInputProcessorProfileMgr> profileMgr; + HRESULT hr = + mInputProcessorProfiles->QueryInterface(IID_ITfInputProcessorProfileMgr, + getter_AddRefs(profileMgr)); + if (FAILED(hr) || !profileMgr) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFStaticSink::EnsureInitActiveLanguageProfile(), FAILED " + "to get input processor profile manager, hr=0x%08X", this, hr)); + return false; + } + + TF_INPUTPROCESSORPROFILE profile; + hr = profileMgr->GetActiveProfile(GUID_TFCAT_TIP_KEYBOARD, &profile); + if (hr == S_FALSE) { + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFStaticSink::EnsureInitActiveLanguageProfile(), FAILED " + "to get active keyboard layout profile due to no active profile, " + "hr=0x%08X", this, hr)); + // XXX Should we call OnActivated() with arguments like non-TIP in this + // case? + return false; + } + if (FAILED(hr)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFStaticSink::EnsureInitActiveLanguageProfile(), FAILED " + "to get active TIP keyboard, hr=0x%08X", this, hr)); + return false; + } + + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFStaticSink::EnsureInitActiveLanguageProfile(), " + "calling OnActivated() manually...", this)); + OnActivated(profile.dwProfileType, profile.langid, profile.clsid, + profile.catid, profile.guidProfile, ::GetKeyboardLayout(0), + TF_IPSINK_FLAG_ACTIVE); + return true; +} + +void +TSFStaticSink::GetTIPDescription(REFCLSID aTextService, LANGID aLangID, + REFGUID aProfile, nsAString& aDescription) +{ + aDescription.Truncate(); + + if (aTextService == CLSID_NULL || aProfile == GUID_NULL) { + return; + } + + BSTR description = nullptr; + HRESULT hr = + mInputProcessorProfiles->GetLanguageProfileDescription(aTextService, + aLangID, + aProfile, + &description); + if (FAILED(hr)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFStaticSink::InitActiveTIPDescription() FAILED " + "due to GetLanguageProfileDescription() failure, hr=0x%08X", + this, hr)); + return; + } + + if (description && description[0]) { + aDescription.Assign(description); + } + ::SysFreeString(description); +} + +bool +TSFStaticSink::IsTIPCategoryKeyboard(REFCLSID aTextService, LANGID aLangID, + REFGUID aProfile) +{ + if (aTextService == CLSID_NULL || aProfile == GUID_NULL) { + return false; + } + + RefPtr<IEnumTfLanguageProfiles> enumLangProfiles; + HRESULT hr = + mInputProcessorProfiles->EnumLanguageProfiles(aLangID, + getter_AddRefs(enumLangProfiles)); + if (FAILED(hr) || !enumLangProfiles) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFStaticSink::IsTIPCategoryKeyboard(), FAILED " + "to get language profiles enumerator, hr=0x%08X", this, hr)); + return false; + } + + TF_LANGUAGEPROFILE profile; + ULONG fetch = 0; + while (SUCCEEDED(enumLangProfiles->Next(1, &profile, &fetch)) && fetch) { + // XXX We're not sure a profile is registered with two or more categories. + if (profile.clsid == aTextService && + profile.guidProfile == aProfile && + profile.catid == GUID_TFCAT_TIP_KEYBOARD) { + return true; + } + } + return false; +} + +/******************************************************************/ +/* TSFTextStore */ +/******************************************************************/ + +StaticRefPtr<ITfThreadMgr> TSFTextStore::sThreadMgr; +StaticRefPtr<ITfMessagePump> TSFTextStore::sMessagePump; +StaticRefPtr<ITfKeystrokeMgr> TSFTextStore::sKeystrokeMgr; +StaticRefPtr<ITfDisplayAttributeMgr> TSFTextStore::sDisplayAttrMgr; +StaticRefPtr<ITfCategoryMgr> TSFTextStore::sCategoryMgr; +StaticRefPtr<ITfDocumentMgr> TSFTextStore::sDisabledDocumentMgr; +StaticRefPtr<ITfContext> TSFTextStore::sDisabledContext; +StaticRefPtr<ITfInputProcessorProfiles> TSFTextStore::sInputProcessorProfiles; +StaticRefPtr<TSFTextStore> TSFTextStore::sEnabledTextStore; +DWORD TSFTextStore::sClientId = 0; + +bool TSFTextStore::sCreateNativeCaretForLegacyATOK = false; +bool TSFTextStore::sDoNotReturnNoLayoutErrorToATOKOfCompositionString = false; +bool TSFTextStore::sDoNotReturnNoLayoutErrorToMSSimplifiedTIP = false; +bool TSFTextStore::sDoNotReturnNoLayoutErrorToMSTraditionalTIP = false; +bool TSFTextStore::sDoNotReturnNoLayoutErrorToFreeChangJie = false; +bool TSFTextStore::sDoNotReturnNoLayoutErrorToEasyChangjei = false; +bool TSFTextStore::sDoNotReturnNoLayoutErrorToMSJapaneseIMEAtFirstChar = false; +bool TSFTextStore::sDoNotReturnNoLayoutErrorToMSJapaneseIMEAtCaret = false; +bool TSFTextStore::sHackQueryInsertForMSSimplifiedTIP = false; +bool TSFTextStore::sHackQueryInsertForMSTraditionalTIP = false; + +#define TEXTSTORE_DEFAULT_VIEW (1) + +TSFTextStore::TSFTextStore() + : mEditCookie(0) + , mSinkMask(0) + , mLock(0) + , mLockQueued(0) + , mHandlingKeyMessage(0) + , mContentForTSF(mComposition, mSelectionForTSF) + , mRequestedAttrValues(false) + , mIsRecordingActionsWithoutLock(false) + , mHasReturnedNoLayoutError(false) + , mWaitingQueryLayout(false) + , mPendingDestroy(false) + , mDeferClearingContentForTSF(false) + , mNativeCaretIsCreated(false) + , mDeferNotifyingTSF(false) + , mDeferCommittingComposition(false) + , mDeferCancellingComposition(false) + , mDestroyed(false) + , mBeingDestroyed(false) +{ + for (int32_t i = 0; i < NUM_OF_SUPPORTED_ATTRS; i++) { + mRequestedAttrs[i] = false; + } + + // We hope that 5 or more actions don't occur at once. + mPendingActions.SetCapacity(5); + + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::TSFTextStore() SUCCEEDED", this)); +} + +TSFTextStore::~TSFTextStore() +{ + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore instance is destroyed", this)); +} + +bool +TSFTextStore::Init(nsWindowBase* aWidget, + const InputContext& aContext) +{ + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::Init(aWidget=0x%p)", + this, aWidget)); + + if (NS_WARN_IF(!aWidget) || NS_WARN_IF(aWidget->Destroyed())) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::Init() FAILED due to being initialized with " + "destroyed widget", + this)); + return false; + } + + TSFStaticSink::GetInstance()->EnsureInitActiveTIPKeyboard(); + + if (mDocumentMgr) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::Init() FAILED due to already initialized", + this)); + return false; + } + + mWidget = aWidget; + if (NS_WARN_IF(!mWidget)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::Init() FAILED " + "due to aWidget is nullptr ", this)); + return false; + } + mDispatcher = mWidget->GetTextEventDispatcher(); + if (NS_WARN_IF(!mDispatcher)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::Init() FAILED " + "due to aWidget->GetTextEventDispatcher() failure", this)); + return false; + } + + SetInputScope(aContext.mHTMLInputType, aContext.mHTMLInputInputmode); + + // Create document manager + RefPtr<ITfThreadMgr> threadMgr = sThreadMgr; + RefPtr<ITfDocumentMgr> documentMgr; + HRESULT hr = threadMgr->CreateDocumentMgr(getter_AddRefs(documentMgr)); + if (NS_WARN_IF(FAILED(hr))) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::Init() FAILED to create ITfDocumentMgr " + "(0x%08X)", this, hr)); + return false; + } + if (NS_WARN_IF(mDestroyed)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::Init() FAILED to create ITfDocumentMgr due to " + "TextStore being destroyed during calling " + "ITfThreadMgr::CreateDocumentMgr()", this)); + return false; + } + // Create context and add it to document manager + RefPtr<ITfContext> context; + hr = documentMgr->CreateContext(sClientId, 0, + static_cast<ITextStoreACP*>(this), + getter_AddRefs(context), &mEditCookie); + if (NS_WARN_IF(FAILED(hr))) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::Init() FAILED to create the context " + "(0x%08X)", this, hr)); + return false; + } + if (NS_WARN_IF(mDestroyed)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::Init() FAILED to create ITfContext due to " + "TextStore being destroyed during calling " + "ITfDocumentMgr::CreateContext()", this)); + return false; + } + + hr = documentMgr->Push(context); + if (NS_WARN_IF(FAILED(hr))) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::Init() FAILED to push the context (0x%08X)", + this, hr)); + return false; + } + if (NS_WARN_IF(mDestroyed)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::Init() FAILED to create ITfContext due to " + "TextStore being destroyed during calling ITfDocumentMgr::Push()", + this)); + documentMgr->Pop(TF_POPF_ALL); + return false; + } + + mDocumentMgr = documentMgr; + mContext = context; + + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::Init() succeeded: " + "mDocumentMgr=0x%p, mContext=0x%p, mEditCookie=0x%08X", + this, mDocumentMgr.get(), mContext.get(), mEditCookie)); + + return true; +} + +void +TSFTextStore::Destroy() +{ + if (mBeingDestroyed) { + return; + } + + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::Destroy(), mLock=%s, " + "mComposition.IsComposing()=%s, mHandlingKeyMessage=%u", + this, GetLockFlagNameStr(mLock).get(), + GetBoolName(mComposition.IsComposing()), + mHandlingKeyMessage)); + + mDestroyed = true; + + // Destroy native caret first because it's not directly related to TSF and + // there may be another textstore which gets focus. So, we should avoid + // to destroy caret after the new one recreates caret. + MaybeDestroyNativeCaret(); + + if (mLock) { + mPendingDestroy = true; + return; + } + + AutoRestore<bool> savedBeingDestroyed(mBeingDestroyed); + mBeingDestroyed = true; + + // If there is composition, TSF keeps the composition even after the text + // store destroyed. So, we should clear the composition here. + if (mComposition.IsComposing()) { + CommitCompositionInternal(false); + } + + if (mSink) { + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::Destroy(), calling " + "ITextStoreACPSink::OnLayoutChange(TS_LC_DESTROY)...", + this)); + RefPtr<ITextStoreACPSink> sink = mSink; + sink->OnLayoutChange(TS_LC_DESTROY, TEXTSTORE_DEFAULT_VIEW); + } + + // If this is called during handling a keydown or keyup message, we should + // put off to release TSF objects until it completely finishes since + // MS-IME for Japanese refers some objects without grabbing them. + if (!mHandlingKeyMessage) { + ReleaseTSFObjects(); + } + + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::Destroy() succeeded", this)); +} + +void +TSFTextStore::ReleaseTSFObjects() +{ + MOZ_ASSERT(!mHandlingKeyMessage); + + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::ReleaseTSFObjects()", this)); + + mContext = nullptr; + if (mDocumentMgr) { + RefPtr<ITfDocumentMgr> documentMgr = mDocumentMgr.forget(); + documentMgr->Pop(TF_POPF_ALL); + } + mSink = nullptr; + mWidget = nullptr; + mDispatcher = nullptr; + + if (!mMouseTrackers.IsEmpty()) { + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::ReleaseTSFObjects(), " + "removing a mouse tracker...", + this)); + mMouseTrackers.Clear(); + } + + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::ReleaseTSFObjects() completed", this)); +} + +STDMETHODIMP +TSFTextStore::QueryInterface(REFIID riid, + void** ppv) +{ + *ppv=nullptr; + if ( (IID_IUnknown == riid) || (IID_ITextStoreACP == riid) ) { + *ppv = static_cast<ITextStoreACP*>(this); + } else if (IID_ITfContextOwnerCompositionSink == riid) { + *ppv = static_cast<ITfContextOwnerCompositionSink*>(this); + } else if (IID_ITfMouseTrackerACP == riid) { + *ppv = static_cast<ITfMouseTrackerACP*>(this); + } + if (*ppv) { + AddRef(); + return S_OK; + } + + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::QueryInterface() FAILED, riid=%s", + this, GetRIIDNameStr(riid).get())); + return E_NOINTERFACE; +} + +STDMETHODIMP +TSFTextStore::AdviseSink(REFIID riid, + IUnknown* punk, + DWORD dwMask) +{ + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::AdviseSink(riid=%s, punk=0x%p, dwMask=%s), " + "mSink=0x%p, mSinkMask=%s", + this, GetRIIDNameStr(riid).get(), punk, GetSinkMaskNameStr(dwMask).get(), + mSink.get(), GetSinkMaskNameStr(mSinkMask).get())); + + if (!punk) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::AdviseSink() FAILED due to the null punk", + this)); + return E_UNEXPECTED; + } + + if (IID_ITextStoreACPSink != riid) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::AdviseSink() FAILED due to " + "unsupported interface", this)); + return E_INVALIDARG; // means unsupported interface. + } + + if (!mSink) { + // Install sink + punk->QueryInterface(IID_ITextStoreACPSink, getter_AddRefs(mSink)); + if (!mSink) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::AdviseSink() FAILED due to " + "punk not having the interface", this)); + return E_UNEXPECTED; + } + } else { + // If sink is already installed we check to see if they are the same + // Get IUnknown from both sides for comparison + RefPtr<IUnknown> comparison1, comparison2; + punk->QueryInterface(IID_IUnknown, getter_AddRefs(comparison1)); + mSink->QueryInterface(IID_IUnknown, getter_AddRefs(comparison2)); + if (comparison1 != comparison2) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::AdviseSink() FAILED due to " + "the sink being different from the stored sink", this)); + return CONNECT_E_ADVISELIMIT; + } + } + // Update mask either for a new sink or an existing sink + mSinkMask = dwMask; + return S_OK; +} + +STDMETHODIMP +TSFTextStore::UnadviseSink(IUnknown* punk) +{ + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::UnadviseSink(punk=0x%p), mSink=0x%p", + this, punk, mSink.get())); + + if (!punk) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::UnadviseSink() FAILED due to the null punk", + this)); + return E_INVALIDARG; + } + if (!mSink) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::UnadviseSink() FAILED due to " + "any sink not stored", this)); + return CONNECT_E_NOCONNECTION; + } + // Get IUnknown from both sides for comparison + RefPtr<IUnknown> comparison1, comparison2; + punk->QueryInterface(IID_IUnknown, getter_AddRefs(comparison1)); + mSink->QueryInterface(IID_IUnknown, getter_AddRefs(comparison2)); + // Unadvise only if sinks are the same + if (comparison1 != comparison2) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::UnadviseSink() FAILED due to " + "the sink being different from the stored sink", this)); + return CONNECT_E_NOCONNECTION; + } + mSink = nullptr; + mSinkMask = 0; + return S_OK; +} + +STDMETHODIMP +TSFTextStore::RequestLock(DWORD dwLockFlags, + HRESULT* phrSession) +{ + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::RequestLock(dwLockFlags=%s, phrSession=0x%p), " + "mLock=%s, mDestroyed=%s", this, GetLockFlagNameStr(dwLockFlags).get(), + phrSession, GetLockFlagNameStr(mLock).get(), GetBoolName(mDestroyed))); + + if (!mSink) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::RequestLock() FAILED due to " + "any sink not stored", this)); + return E_FAIL; + } + if (mDestroyed && + (!mContentForTSF.IsInitialized() || mSelectionForTSF.IsDirty())) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::RequestLock() FAILED due to " + "being destroyed and no information of the contents", this)); + return E_FAIL; + } + if (!phrSession) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::RequestLock() FAILED due to " + "null phrSession", this)); + return E_INVALIDARG; + } + + if (!mLock) { + // put on lock + mLock = dwLockFlags & (~TS_LF_SYNC); + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p Locking (%s) >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" + ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>", + this, GetLockFlagNameStr(mLock).get())); + // Don't release this instance during this lock because this is called by + // TSF but they don't grab us during this call. + RefPtr<TSFTextStore> kungFuDeathGrip(this); + RefPtr<ITextStoreACPSink> sink = mSink; + *phrSession = sink->OnLockGranted(mLock); + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p Unlocked (%s) <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" + "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<", + this, GetLockFlagNameStr(mLock).get())); + DidLockGranted(); + while (mLockQueued) { + mLock = mLockQueued; + mLockQueued = 0; + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p Locking for the request in the queue (%s) >>>>>>>>>>>>>>" + ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>", + this, GetLockFlagNameStr(mLock).get())); + sink->OnLockGranted(mLock); + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p Unlocked (%s) <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" + "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<", + this, GetLockFlagNameStr(mLock).get())); + DidLockGranted(); + } + + // The document is now completely unlocked. + mLock = 0; + + MaybeFlushPendingNotifications(); + + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::RequestLock() succeeded: *phrSession=%s", + this, GetTextStoreReturnValueName(*phrSession))); + return S_OK; + } + + // only time when reentrant lock is allowed is when caller holds a + // read-only lock and is requesting an async write lock + if (IsReadLocked() && !IsReadWriteLocked() && IsReadWriteLock(dwLockFlags) && + !(dwLockFlags & TS_LF_SYNC)) { + *phrSession = TS_S_ASYNC; + mLockQueued = dwLockFlags & (~TS_LF_SYNC); + + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::RequestLock() stores the request in the " + "queue, *phrSession=TS_S_ASYNC", this)); + return S_OK; + } + + // no more locks allowed + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::RequestLock() didn't allow to lock, " + "*phrSession=TS_E_SYNCHRONOUS", this)); + *phrSession = TS_E_SYNCHRONOUS; + return E_FAIL; +} + +void +TSFTextStore::DidLockGranted() +{ + if (IsReadWriteLocked()) { + // FreeCJ (TIP for Traditional Chinese) calls SetSelection() to set caret + // to the start of composition string and insert a full width space for + // a placeholder with a call of SetText(). After that, it calls + // OnUpdateComposition() without new range. Therefore, let's record the + // composition update information here. + CompleteLastActionIfStillIncomplete(); + + FlushPendingActions(); + } + + // If the widget has gone, we don't need to notify anything. + if (mDestroyed || !mWidget || mWidget->Destroyed()) { + mPendingSelectionChangeData.Clear(); + mHasReturnedNoLayoutError = false; + } +} + +void +TSFTextStore::DispatchEvent(WidgetGUIEvent& aEvent) +{ + if (NS_WARN_IF(!mWidget) || NS_WARN_IF(mWidget->Destroyed())) { + return; + } + // If the event isn't a query content event, the event may be handled + // asynchronously. So, we should put off to answer from GetTextExt() etc. + if (!aEvent.AsQueryContentEvent()) { + mDeferNotifyingTSF = true; + } + mWidget->DispatchWindowEvent(&aEvent); +} + +void +TSFTextStore::FlushPendingActions() +{ + if (!mWidget || mWidget->Destroyed()) { + // Note that don't clear mContentForTSF because TIP may try to commit + // composition with a document lock. In such case, TSFTextStore needs to + // behave as expected by TIP. + mPendingActions.Clear(); + mPendingSelectionChangeData.Clear(); + mHasReturnedNoLayoutError = false; + return; + } + + RefPtr<nsWindowBase> kungFuDeathGrip(mWidget); + nsresult rv = mDispatcher->BeginNativeInputTransaction(); + if (NS_WARN_IF(NS_FAILED(rv))) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::FlushPendingActions() " + "FAILED due to BeginNativeInputTransaction() failure", this)); + return; + } + for (uint32_t i = 0; i < mPendingActions.Length(); i++) { + PendingAction& action = mPendingActions[i]; + switch (action.mType) { + case PendingAction::COMPOSITION_START: { + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::FlushPendingActions() " + "flushing COMPOSITION_START={ mSelectionStart=%d, " + "mSelectionLength=%d }, mDestroyed=%s", + this, action.mSelectionStart, action.mSelectionLength, + GetBoolName(mDestroyed))); + + if (mDestroyed) { + MOZ_LOG(sTextStoreLog, LogLevel::Warning, + ("0x%p TSFTextStore::FlushPendingActions() " + "IGNORED pending compositionstart due to already destroyed", + this)); + break; + } + + if (action.mAdjustSelection) { + // Select composition range so the new composition replaces the range + WidgetSelectionEvent selectionSet(true, eSetSelection, mWidget); + mWidget->InitEvent(selectionSet); + selectionSet.mOffset = static_cast<uint32_t>(action.mSelectionStart); + selectionSet.mLength = static_cast<uint32_t>(action.mSelectionLength); + selectionSet.mReversed = false; + DispatchEvent(selectionSet); + if (!selectionSet.mSucceeded) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::FlushPendingActions() " + "FAILED due to eSetSelection failure", this)); + break; + } + } + + // eCompositionStart always causes + // NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED. Therefore, we should + // wait to clear mContentForTSF until it's notified. + mDeferClearingContentForTSF = true; + + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::FlushPendingActions() " + "dispatching compositionstart event...", this)); + WidgetEventTime eventTime = mWidget->CurrentMessageWidgetEventTime(); + nsEventStatus status; + rv = mDispatcher->StartComposition(status, &eventTime); + if (NS_WARN_IF(NS_FAILED(rv))) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::FlushPendingActions() " + "FAILED to dispatch compositionstart event, " + "IsComposingInContent()=%s", + this, GetBoolName(!IsComposingInContent()))); + mDeferClearingContentForTSF = !IsComposingInContent(); + } + if (!mWidget || mWidget->Destroyed()) { + break; + } + break; + } + case PendingAction::COMPOSITION_UPDATE: { + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::FlushPendingActions() " + "flushing COMPOSITION_UPDATE={ mData=\"%s\", " + "mRanges=0x%p, mRanges->Length()=%d }", + this, GetEscapedUTF8String(action.mData).get(), + action.mRanges.get(), + action.mRanges ? action.mRanges->Length() : 0)); + + // eCompositionChange causes a DOM text event, the IME will be notified + // of NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED. In this case, we + // should not clear mContentForTSF until we notify the IME of the + // composition update. + mDeferClearingContentForTSF = true; + + rv = mDispatcher->SetPendingComposition(action.mData, + action.mRanges); + if (NS_WARN_IF(NS_FAILED(rv))) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::FlushPendingActions() " + "FAILED to setting pending composition... " + "IsComposingInContent()=%s", + this, GetBoolName(IsComposingInContent()))); + mDeferClearingContentForTSF = !IsComposingInContent(); + } else { + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::FlushPendingActions() " + "dispatching compositionchange event...", this)); + WidgetEventTime eventTime = mWidget->CurrentMessageWidgetEventTime(); + nsEventStatus status; + rv = mDispatcher->FlushPendingComposition(status, &eventTime); + if (NS_WARN_IF(NS_FAILED(rv))) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::FlushPendingActions() " + "FAILED to dispatch compositionchange event, " + "IsComposingInContent()=%s", + this, GetBoolName(IsComposingInContent()))); + mDeferClearingContentForTSF = !IsComposingInContent(); + } + // Be aware, the mWidget might already have been destroyed. + } + break; + } + case PendingAction::COMPOSITION_END: { + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::FlushPendingActions() " + "flushing COMPOSITION_END={ mData=\"%s\" }", + this, GetEscapedUTF8String(action.mData).get())); + + // Dispatching eCompositionCommit causes a DOM text event, then, + // the IME will be notified of NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED. + // In this case, we should not clear mContentForTSFuntil we notify + // the IME of the composition update. + mDeferClearingContentForTSF = true; + + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::FlushPendingActions(), " + "dispatching compositioncommit event...", this)); + WidgetEventTime eventTime = mWidget->CurrentMessageWidgetEventTime(); + nsEventStatus status; + rv = mDispatcher->CommitComposition(status, &action.mData, &eventTime); + if (NS_WARN_IF(NS_FAILED(rv))) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::FlushPendingActions() " + "FAILED to dispatch compositioncommit event, " + "IsComposingInContent()=%s", + this, GetBoolName(IsComposingInContent()))); + mDeferClearingContentForTSF = !IsComposingInContent(); + } + break; + } + case PendingAction::SET_SELECTION: { + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::FlushPendingActions() " + "flushing SET_SELECTION={ mSelectionStart=%d, " + "mSelectionLength=%d, mSelectionReversed=%s }, " + "mDestroyed=%s", + this, action.mSelectionStart, action.mSelectionLength, + GetBoolName(action.mSelectionReversed), + GetBoolName(mDestroyed))); + + if (mDestroyed) { + MOZ_LOG(sTextStoreLog, LogLevel::Warning, + ("0x%p TSFTextStore::FlushPendingActions() " + "IGNORED pending selectionset due to already destroyed", + this)); + break; + } + + WidgetSelectionEvent selectionSet(true, eSetSelection, mWidget); + selectionSet.mOffset = + static_cast<uint32_t>(action.mSelectionStart); + selectionSet.mLength = + static_cast<uint32_t>(action.mSelectionLength); + selectionSet.mReversed = action.mSelectionReversed; + break; + } + default: + MOZ_CRASH("unexpected action type"); + } + + if (mWidget && !mWidget->Destroyed()) { + continue; + } + + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::FlushPendingActions(), " + "qutting since the mWidget has gone", this)); + break; + } + mPendingActions.Clear(); +} + +void +TSFTextStore::MaybeFlushPendingNotifications() +{ + if (IsReadLocked()) { + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), " + "putting off flushing pending notifications due to being the " + "document locked...", this)); + return; + } + + if (mDeferCommittingComposition) { + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), " + "calling TSFTextStore::CommitCompositionInternal(false)...", this)); + mDeferCommittingComposition = mDeferCancellingComposition = false; + CommitCompositionInternal(false); + } else if (mDeferCancellingComposition) { + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), " + "calling TSFTextStore::CommitCompositionInternal(true)...", this)); + mDeferCommittingComposition = mDeferCancellingComposition = false; + CommitCompositionInternal(true); + } + + if (mDeferNotifyingTSF) { + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), " + "putting off flushing pending notifications due to being " + "dispatching events...", this)); + return; + } + + if (mPendingDestroy) { + Destroy(); + return; + } + + if (mDestroyed) { + // If it's already been destroyed completely, this shouldn't notify TSF of + // anything anymore. + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), " + "does nothing because this has already destroyed completely...", this)); + return; + } + + if (!mDeferClearingContentForTSF && mContentForTSF.IsInitialized()) { + mContentForTSF.Clear(); + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), " + "mContentForTSF is cleared", this)); + } + + // When there is no cached content, we can sync actual contents and TSF/TIP + // expecting contents. + RefPtr<TSFTextStore> kungFuDeathGrip = this; + Unused << kungFuDeathGrip; + if (!mContentForTSF.IsInitialized()) { + if (mPendingTextChangeData.IsValid()) { + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), " + "calling TSFTextStore::NotifyTSFOfTextChange()...", this)); + NotifyTSFOfTextChange(); + } + if (mPendingSelectionChangeData.IsValid()) { + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), " + "calling TSFTextStore::NotifyTSFOfSelectionChange()...", this)); + NotifyTSFOfSelectionChange(); + } + } + + if (mHasReturnedNoLayoutError) { + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), " + "calling TSFTextStore::NotifyTSFOfLayoutChange()...", this)); + NotifyTSFOfLayoutChange(); + } +} + +STDMETHODIMP +TSFTextStore::GetStatus(TS_STATUS* pdcs) +{ + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::GetStatus(pdcs=0x%p)", this, pdcs)); + + if (!pdcs) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::GetStatus() FAILED due to null pdcs", this)); + return E_INVALIDARG; + } + pdcs->dwDynamicFlags = 0; + // we use a "flat" text model for TSF support so no hidden text + pdcs->dwStaticFlags = TS_SS_NOHIDDENTEXT; + return S_OK; +} + +STDMETHODIMP +TSFTextStore::QueryInsert(LONG acpTestStart, + LONG acpTestEnd, + ULONG cch, + LONG* pacpResultStart, + LONG* pacpResultEnd) +{ + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::QueryInsert(acpTestStart=%ld, " + "acpTestEnd=%ld, cch=%lu, pacpResultStart=0x%p, pacpResultEnd=0x%p)", + this, acpTestStart, acpTestEnd, cch, acpTestStart, acpTestEnd)); + + if (!pacpResultStart || !pacpResultEnd) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::QueryInsert() FAILED due to " + "the null argument", this)); + return E_INVALIDARG; + } + + if (acpTestStart < 0 || acpTestStart > acpTestEnd) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::QueryInsert() FAILED due to " + "wrong argument", this)); + return E_INVALIDARG; + } + + // XXX need to adjust to cluster boundary + // Assume we are given good offsets for now + const TSFStaticSink* kSink = TSFStaticSink::GetInstance(); + if (IsWin8OrLater() && !mComposition.IsComposing() && + ((sHackQueryInsertForMSTraditionalTIP && + (kSink->IsMSChangJieActive() || kSink->IsMSQuickQuickActive())) || + (sHackQueryInsertForMSSimplifiedTIP && + (kSink->IsMSPinyinActive() || kSink->IsMSWubiActive())))) { + MOZ_LOG(sTextStoreLog, LogLevel::Warning, + ("0x%p TSFTextStore::QueryInsert() WARNING using different " + "result for the TIP", this)); + // Chinese TIPs of Microsoft assume that QueryInsert() returns selected + // range which should be removed. + *pacpResultStart = acpTestStart; + *pacpResultEnd = acpTestEnd; + } else { + *pacpResultStart = acpTestStart; + *pacpResultEnd = acpTestStart + cch; + } + + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::QueryInsert() succeeded: " + "*pacpResultStart=%ld, *pacpResultEnd=%ld)", + this, *pacpResultStart, *pacpResultEnd)); + return S_OK; +} + +STDMETHODIMP +TSFTextStore::GetSelection(ULONG ulIndex, + ULONG ulCount, + TS_SELECTION_ACP* pSelection, + ULONG* pcFetched) +{ + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::GetSelection(ulIndex=%lu, ulCount=%lu, " + "pSelection=0x%p, pcFetched=0x%p)", + this, ulIndex, ulCount, pSelection, pcFetched)); + + if (!IsReadLocked()) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::GetSelection() FAILED due to not locked", + this)); + return TS_E_NOLOCK; + } + if (!ulCount || !pSelection || !pcFetched) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::GetSelection() FAILED due to " + "null argument", this)); + return E_INVALIDARG; + } + + *pcFetched = 0; + + if (ulIndex != static_cast<ULONG>(TS_DEFAULT_SELECTION) && + ulIndex != 0) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::GetSelection() FAILED due to " + "unsupported selection", this)); + return TS_E_NOSELECTION; + } + + Selection& selectionForTSF = SelectionForTSFRef(); + if (selectionForTSF.IsDirty()) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::GetSelection() FAILED due to " + "SelectionForTSFRef() failure", this)); + return E_FAIL; + } + *pSelection = selectionForTSF.ACP(); + *pcFetched = 1; + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::GetSelection() succeeded", this)); + return S_OK; +} + +bool +TSFTextStore::IsComposingInContent() const +{ + if (!mDispatcher) { + return false; + } + if (!mDispatcher->IsInNativeInputTransaction()) { + return false; + } + return mDispatcher->IsComposing(); +} + +TSFTextStore::Content& +TSFTextStore::ContentForTSFRef() +{ + // This should be called when the document is locked or the content hasn't + // been abandoned yet. + if (NS_WARN_IF(!IsReadLocked() && !mContentForTSF.IsInitialized())) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::ContentForTSFRef(), FAILED, due to " + "called wrong timing, IsReadLocked()=%s, " + "mContentForTSF.IsInitialized()=%s", + this, GetBoolName(IsReadLocked()), + GetBoolName(mContentForTSF.IsInitialized()))); + mContentForTSF.Clear(); + return mContentForTSF; + } + + Selection& selectionForTSF = SelectionForTSFRef(); + if (selectionForTSF.IsDirty()) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::ContentForTSFRef(), FAILED, due to " + "SelectionForTSFRef() failure", this)); + mContentForTSF.Clear(); + return mContentForTSF; + } + + if (!mContentForTSF.IsInitialized()) { + nsAutoString text; + if (NS_WARN_IF(!GetCurrentText(text))) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::ContentForTSFRef(), FAILED, due to " + "GetCurrentText() failure", this)); + mContentForTSF.Clear(); + return mContentForTSF; + } + + mContentForTSF.Init(text); + // Basically, the cached content which is expected by TSF/TIP should be + // cleared after active composition is committed or the document lock is + // unlocked. However, in e10s mode, content will be modified + // asynchronously. In such case, mDeferClearingContentForTSF may be + // true until whole dispatched events are handled by the focused editor. + mDeferClearingContentForTSF = false; + } + + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::ContentForTSFRef(): " + "mContentForTSF={ mText=\"%s\" (Length()=%u), " + "mLastCompositionString=\"%s\" (Length()=%u), " + "mMinTextModifiedOffset=%u }", + this, mContentForTSF.Text().Length() <= 40 ? + GetEscapedUTF8String(mContentForTSF.Text()).get() : "<omitted>", + mContentForTSF.Text().Length(), + GetEscapedUTF8String(mContentForTSF.LastCompositionString()).get(), + mContentForTSF.LastCompositionString().Length(), + mContentForTSF.MinTextModifiedOffset())); + + return mContentForTSF; +} + +bool +TSFTextStore::CanAccessActualContentDirectly() const +{ + if (!mContentForTSF.IsInitialized() || mSelectionForTSF.IsDirty()) { + return true; + } + + // If the cached content has been changed by something except composition, + // the content cache may be different from actual content. + if (mPendingTextChangeData.IsValid() && + !mPendingTextChangeData.mCausedOnlyByComposition) { + return false; + } + + // If the cached selection isn't changed, cached content and actual content + // should be same. + if (!mPendingSelectionChangeData.IsValid()) { + return true; + } + + return mSelectionForTSF.EqualsExceptDirection(mPendingSelectionChangeData); +} + +bool +TSFTextStore::GetCurrentText(nsAString& aTextContent) +{ + if (mContentForTSF.IsInitialized()) { + aTextContent = mContentForTSF.Text(); + return true; + } + + MOZ_ASSERT(!mDestroyed); + MOZ_ASSERT(mWidget && !mWidget->Destroyed()); + + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::GetCurrentText(): " + "retrieving text from the content...", this)); + + WidgetQueryContentEvent queryText(true, eQueryTextContent, mWidget); + queryText.InitForQueryTextContent(0, UINT32_MAX); + mWidget->InitEvent(queryText); + DispatchEvent(queryText); + if (NS_WARN_IF(!queryText.mSucceeded)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::GetCurrentText(), FAILED, due to " + "eQueryTextContent failure", this)); + aTextContent.Truncate(); + return false; + } + + aTextContent = queryText.mReply.mString; + return true; +} + +TSFTextStore::Selection& +TSFTextStore::SelectionForTSFRef() +{ + if (mSelectionForTSF.IsDirty()) { + MOZ_ASSERT(!mDestroyed); + // If the window has never been available, we should crash since working + // with broken values may make TIP confused. + if (!mWidget || mWidget->Destroyed()) { + MOZ_CRASH(); + } + + WidgetQueryContentEvent querySelection(true, eQuerySelectedText, mWidget); + mWidget->InitEvent(querySelection); + DispatchEvent(querySelection); + if (NS_WARN_IF(!querySelection.mSucceeded)) { + return mSelectionForTSF; + } + + mSelectionForTSF.SetSelection(querySelection.mReply.mOffset, + querySelection.mReply.mString.Length(), + querySelection.mReply.mReversed, + querySelection.GetWritingMode()); + } + + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::SelectionForTSFRef(): " + "acpStart=%d, acpEnd=%d (length=%d), reverted=%s", + this, mSelectionForTSF.StartOffset(), mSelectionForTSF.EndOffset(), + mSelectionForTSF.Length(), + GetBoolName(mSelectionForTSF.IsReversed()))); + + return mSelectionForTSF; +} + +static HRESULT +GetRangeExtent(ITfRange* aRange, LONG* aStart, LONG* aLength) +{ + RefPtr<ITfRangeACP> rangeACP; + aRange->QueryInterface(IID_ITfRangeACP, getter_AddRefs(rangeACP)); + NS_ENSURE_TRUE(rangeACP, E_FAIL); + return rangeACP->GetExtent(aStart, aLength); +} + +static TextRangeType +GetGeckoSelectionValue(TF_DISPLAYATTRIBUTE& aDisplayAttr) +{ + switch (aDisplayAttr.bAttr) { + case TF_ATTR_TARGET_CONVERTED: + return TextRangeType::eSelectedClause; + case TF_ATTR_CONVERTED: + return TextRangeType::eConvertedClause; + case TF_ATTR_TARGET_NOTCONVERTED: + return TextRangeType::eSelectedRawClause; + default: + return TextRangeType::eRawClause; + } +} + +HRESULT +TSFTextStore::GetDisplayAttribute(ITfProperty* aAttrProperty, + ITfRange* aRange, + TF_DISPLAYATTRIBUTE* aResult) +{ + NS_ENSURE_TRUE(aAttrProperty, E_FAIL); + NS_ENSURE_TRUE(aRange, E_FAIL); + NS_ENSURE_TRUE(aResult, E_FAIL); + + HRESULT hr; + + if (MOZ_LOG_TEST(sTextStoreLog, LogLevel::Debug)) { + LONG start = 0, length = 0; + hr = GetRangeExtent(aRange, &start, &length); + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::GetDisplayAttribute(): " + "GetDisplayAttribute range=%ld-%ld (hr=%s)", + this, start - mComposition.mStart, + start - mComposition.mStart + length, + GetCommonReturnValueName(hr))); + } + + VARIANT propValue; + ::VariantInit(&propValue); + hr = aAttrProperty->GetValue(TfEditCookie(mEditCookie), aRange, &propValue); + if (FAILED(hr)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::GetDisplayAttribute() FAILED due to " + "ITfProperty::GetValue() failed", this)); + return hr; + } + if (VT_I4 != propValue.vt) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::GetDisplayAttribute() FAILED due to " + "ITfProperty::GetValue() returns non-VT_I4 value", this)); + ::VariantClear(&propValue); + return E_FAIL; + } + + NS_ENSURE_TRUE(sCategoryMgr, E_FAIL); + GUID guid; + hr = sCategoryMgr->GetGUID(DWORD(propValue.lVal), &guid); + ::VariantClear(&propValue); + if (FAILED(hr)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::GetDisplayAttribute() FAILED due to " + "ITfCategoryMgr::GetGUID() failed", this)); + return hr; + } + + NS_ENSURE_TRUE(sDisplayAttrMgr, E_FAIL); + RefPtr<ITfDisplayAttributeInfo> info; + hr = sDisplayAttrMgr->GetDisplayAttributeInfo(guid, getter_AddRefs(info), + nullptr); + if (FAILED(hr) || !info) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::GetDisplayAttribute() FAILED due to " + "ITfDisplayAttributeMgr::GetDisplayAttributeInfo() failed", this)); + return hr; + } + + hr = info->GetAttributeInfo(aResult); + if (FAILED(hr)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::GetDisplayAttribute() FAILED due to " + "ITfDisplayAttributeInfo::GetAttributeInfo() failed", this)); + return hr; + } + + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::GetDisplayAttribute() succeeded: " + "Result={ %s }", this, GetDisplayAttrStr(*aResult).get())); + return S_OK; +} + +HRESULT +TSFTextStore::RestartCompositionIfNecessary(ITfRange* aRangeNew) +{ + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::RestartCompositionIfNecessary(" + "aRangeNew=0x%p), mComposition.mView=0x%p", + this, aRangeNew, mComposition.mView.get())); + + if (!mComposition.IsComposing()) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::RestartCompositionIfNecessary() FAILED " + "due to no composition view", this)); + return E_FAIL; + } + + HRESULT hr; + RefPtr<ITfCompositionView> pComposition(mComposition.mView); + RefPtr<ITfRange> composingRange(aRangeNew); + if (!composingRange) { + hr = pComposition->GetRange(getter_AddRefs(composingRange)); + if (FAILED(hr)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::RestartCompositionIfNecessary() " + "FAILED due to pComposition->GetRange() failure", this)); + return hr; + } + } + + // Get starting offset of the composition + LONG compStart = 0, compLength = 0; + hr = GetRangeExtent(composingRange, &compStart, &compLength); + if (FAILED(hr)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::RestartCompositionIfNecessary() FAILED " + "due to GetRangeExtent() failure", this)); + return hr; + } + + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::RestartCompositionIfNecessary(), " + "range=%ld-%ld, mComposition={ mStart=%ld, mString.Length()=%lu }", + this, compStart, compStart + compLength, mComposition.mStart, + mComposition.mString.Length())); + + if (mComposition.mStart != compStart || + mComposition.mString.Length() != (ULONG)compLength) { + // If the queried composition length is different from the length + // of our composition string, OnUpdateComposition is being called + // because a part of the original composition was committed. + hr = RestartComposition(pComposition, composingRange); + if (FAILED(hr)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::RestartCompositionIfNecessary() " + "FAILED due to RestartComposition() failure", this)); + return hr; + } + } + + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::RestartCompositionIfNecessary() succeeded", + this)); + return S_OK; +} + +HRESULT +TSFTextStore::RestartComposition(ITfCompositionView* aCompositionView, + ITfRange* aNewRange) +{ + Selection& selectionForTSF = SelectionForTSFRef(); + + LONG newStart, newLength; + HRESULT hr = GetRangeExtent(aNewRange, &newStart, &newLength); + LONG newEnd = newStart + newLength; + + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::RestartComposition(aCompositionView=0x%p, " + "aNewRange=0x%p { newStart=%d, newLength=%d }), " + "mComposition={ mStart=%d, mCompositionString.Length()=%d }, " + "selectionForTSF={ IsDirty()=%s, StartOffset()=%d, Length()=%d }", + this, aCompositionView, aNewRange, newStart, newLength, + mComposition.mStart, mComposition.mString.Length(), + GetBoolName(selectionForTSF.IsDirty()), + selectionForTSF.StartOffset(), selectionForTSF.Length())); + + if (selectionForTSF.IsDirty()) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::RestartComposition() FAILED " + "due to SelectionForTSFRef() failure", this)); + return E_FAIL; + } + + if (FAILED(hr)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::RestartComposition() FAILED " + "due to GetRangeExtent() failure", this)); + return hr; + } + + // If the new range has no overlap with the crrent range, we just commit + // the composition and restart new composition with the new range but + // current selection range should be preserved. + if (newStart >= mComposition.EndOffset() || newEnd <= mComposition.mStart) { + RecordCompositionEndAction(); + RecordCompositionStartAction(aCompositionView, newStart, newLength, true); + return S_OK; + } + + // If the new range has an overlap with the current one, we should not commit + // the whole current range to avoid creating an odd undo transaction. + // I.e., the overlapped range which is being composed should not appear in + // undo transaction. + + // Backup current composition data and selection data. + Composition oldComposition = mComposition; + Selection oldSelection = selectionForTSF; + + // Commit only the part of composition. + LONG keepComposingStartOffset = std::max(mComposition.mStart, newStart); + LONG keepComposingEndOffset = std::min(mComposition.EndOffset(), newEnd); + MOZ_ASSERT(keepComposingStartOffset <= keepComposingEndOffset, + "Why keepComposingEndOffset is smaller than keepComposingStartOffset?"); + LONG keepComposingLength = keepComposingEndOffset - keepComposingStartOffset; + // Remove the overlapped part from the commit string. + nsAutoString commitString(mComposition.mString); + commitString.Cut(keepComposingStartOffset - mComposition.mStart, + keepComposingLength); + // Update the composition string. + Content& contentForTSF = ContentForTSFRef(); + if (!contentForTSF.IsInitialized()) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::RestartComposition() FAILED " + "due to ContentForTSFRef() failure", this)); + return E_FAIL; + } + contentForTSF.ReplaceTextWith(mComposition.mStart, + mComposition.mString.Length(), + commitString); + // Record a compositionupdate action for commit the part of composing string. + PendingAction* action = LastOrNewPendingCompositionUpdate(); + action->mData = mComposition.mString; + action->mRanges->Clear(); + // Note that we shouldn't append ranges when composition string + // is empty because it may cause TextComposition confused. + if (!action->mData.IsEmpty()) { + TextRange caretRange; + caretRange.mStartOffset = caretRange.mEndOffset = + uint32_t(oldComposition.mStart + commitString.Length()); + caretRange.mRangeType = TextRangeType::eCaret; + action->mRanges->AppendElement(caretRange); + } + action->mIncomplete = false; + + // Record compositionend action. + RecordCompositionEndAction(); + + // Record compositionstart action only with the new start since this method + // hasn't restored composing string yet. + RecordCompositionStartAction(aCompositionView, newStart, 0, false); + + // Restore the latest text content and selection. + contentForTSF.ReplaceSelectedTextWith( + nsDependentSubstring(oldComposition.mString, + keepComposingStartOffset - oldComposition.mStart, + keepComposingLength)); + selectionForTSF = oldSelection; + + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::RestartComposition() succeeded, " + "mComposition={ mStart=%d, mCompositionString.Length()=%d }, " + "selectionForTSF={ IsDirty()=%s, StartOffset()=%d, Length()=%d }", + this, mComposition.mStart, mComposition.mString.Length(), + GetBoolName(selectionForTSF.IsDirty()), + selectionForTSF.StartOffset(), selectionForTSF.Length())); + + return S_OK; +} + +static bool +GetColor(const TF_DA_COLOR& aTSFColor, nscolor& aResult) +{ + switch (aTSFColor.type) { + case TF_CT_SYSCOLOR: { + DWORD sysColor = ::GetSysColor(aTSFColor.nIndex); + aResult = NS_RGB(GetRValue(sysColor), GetGValue(sysColor), + GetBValue(sysColor)); + return true; + } + case TF_CT_COLORREF: + aResult = NS_RGB(GetRValue(aTSFColor.cr), GetGValue(aTSFColor.cr), + GetBValue(aTSFColor.cr)); + return true; + case TF_CT_NONE: + default: + return false; + } +} + +static bool +GetLineStyle(TF_DA_LINESTYLE aTSFLineStyle, uint8_t& aTextRangeLineStyle) +{ + switch (aTSFLineStyle) { + case TF_LS_NONE: + aTextRangeLineStyle = TextRangeStyle::LINESTYLE_NONE; + return true; + case TF_LS_SOLID: + aTextRangeLineStyle = TextRangeStyle::LINESTYLE_SOLID; + return true; + case TF_LS_DOT: + aTextRangeLineStyle = TextRangeStyle::LINESTYLE_DOTTED; + return true; + case TF_LS_DASH: + aTextRangeLineStyle = TextRangeStyle::LINESTYLE_DASHED; + return true; + case TF_LS_SQUIGGLE: + aTextRangeLineStyle = TextRangeStyle::LINESTYLE_WAVY; + return true; + default: + return false; + } +} + +HRESULT +TSFTextStore::RecordCompositionUpdateAction() +{ + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::RecordCompositionUpdateAction(), " + "mComposition={ mView=0x%p, mStart=%d, mString=\"%s\" " + "(Length()=%d) }", + this, mComposition.mView.get(), mComposition.mStart, + GetEscapedUTF8String(mComposition.mString).get(), + mComposition.mString.Length())); + + if (!mComposition.IsComposing()) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::RecordCompositionUpdateAction() FAILED " + "due to no composition view", this)); + return E_FAIL; + } + + // Getting display attributes is *really* complicated! + // We first get the context and the property objects to query for + // attributes, but since a big range can have a variety of values for + // the attribute, we have to find out all the ranges that have distinct + // attribute values. Then we query for what the value represents through + // the display attribute manager and translate that to TextRange to be + // sent in eCompositionChange + + RefPtr<ITfProperty> attrPropetry; + HRESULT hr = mContext->GetProperty(GUID_PROP_ATTRIBUTE, + getter_AddRefs(attrPropetry)); + if (FAILED(hr) || !attrPropetry) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::RecordCompositionUpdateAction() FAILED " + "due to mContext->GetProperty() failure", this)); + return FAILED(hr) ? hr : E_FAIL; + } + + RefPtr<ITfRange> composingRange; + hr = mComposition.mView->GetRange(getter_AddRefs(composingRange)); + if (FAILED(hr)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::RecordCompositionUpdateAction() " + "FAILED due to mComposition.mView->GetRange() failure", this)); + return hr; + } + + RefPtr<IEnumTfRanges> enumRanges; + hr = attrPropetry->EnumRanges(TfEditCookie(mEditCookie), + getter_AddRefs(enumRanges), composingRange); + if (FAILED(hr) || !enumRanges) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::RecordCompositionUpdateAction() FAILED " + "due to attrPropetry->EnumRanges() failure", this)); + return FAILED(hr) ? hr : E_FAIL; + } + + // First, put the log of content and selection here. + Selection& selectionForTSF = SelectionForTSFRef(); + if (selectionForTSF.IsDirty()) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::RecordCompositionUpdateAction() FAILED " + "due to SelectionForTSFRef() failure", this)); + return E_FAIL; + } + + PendingAction* action = LastOrNewPendingCompositionUpdate(); + action->mData = mComposition.mString; + // The ranges might already have been initialized, however, if this is + // called again, that means we need to overwrite the ranges with current + // information. + action->mRanges->Clear(); + + // Note that we shouldn't append ranges when composition string + // is empty because it may cause TextComposition confused. + if (!action->mData.IsEmpty()) { + TextRange newRange; + // No matter if we have display attribute info or not, + // we always pass in at least one range to eCompositionChange + newRange.mStartOffset = 0; + newRange.mEndOffset = action->mData.Length(); + newRange.mRangeType = TextRangeType::eRawClause; + action->mRanges->AppendElement(newRange); + + RefPtr<ITfRange> range; + while (enumRanges->Next(1, getter_AddRefs(range), nullptr) == S_OK) { + if (NS_WARN_IF(!range)) { + break; + } + + LONG rangeStart = 0, rangeLength = 0; + if (FAILED(GetRangeExtent(range, &rangeStart, &rangeLength))) { + continue; + } + // The range may include out of composition string. We should ignore + // outside of the composition string. + LONG start = std::min(std::max(rangeStart, mComposition.mStart), + mComposition.EndOffset()); + LONG end = std::max(std::min(rangeStart + rangeLength, + mComposition.EndOffset()), + mComposition.mStart); + LONG length = end - start; + if (length < 0) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::RecordCompositionUpdateAction() " + "ignores invalid range (%d-%d)", + this, rangeStart - mComposition.mStart, + rangeStart - mComposition.mStart + rangeLength)); + continue; + } + if (!length) { + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::RecordCompositionUpdateAction() " + "ignores a range due to outside of the composition or empty " + "(%d-%d)", + this, rangeStart - mComposition.mStart, + rangeStart - mComposition.mStart + rangeLength)); + continue; + } + + TextRange newRange; + newRange.mStartOffset = uint32_t(start - mComposition.mStart); + // The end of the last range in the array is + // always kept at the end of composition + newRange.mEndOffset = mComposition.mString.Length(); + + TF_DISPLAYATTRIBUTE attr; + hr = GetDisplayAttribute(attrPropetry, range, &attr); + if (FAILED(hr)) { + newRange.mRangeType = TextRangeType::eRawClause; + } else { + newRange.mRangeType = GetGeckoSelectionValue(attr); + if (GetColor(attr.crText, newRange.mRangeStyle.mForegroundColor)) { + newRange.mRangeStyle.mDefinedStyles |= + TextRangeStyle::DEFINED_FOREGROUND_COLOR; + } + if (GetColor(attr.crBk, newRange.mRangeStyle.mBackgroundColor)) { + newRange.mRangeStyle.mDefinedStyles |= + TextRangeStyle::DEFINED_BACKGROUND_COLOR; + } + if (GetColor(attr.crLine, newRange.mRangeStyle.mUnderlineColor)) { + newRange.mRangeStyle.mDefinedStyles |= + TextRangeStyle::DEFINED_UNDERLINE_COLOR; + } + if (GetLineStyle(attr.lsStyle, newRange.mRangeStyle.mLineStyle)) { + newRange.mRangeStyle.mDefinedStyles |= + TextRangeStyle::DEFINED_LINESTYLE; + newRange.mRangeStyle.mIsBoldLine = attr.fBoldLine != 0; + } + } + + TextRange& lastRange = action->mRanges->LastElement(); + if (lastRange.mStartOffset == newRange.mStartOffset) { + // Replace range if last range is the same as this one + // So that ranges don't overlap and confuse the editor + lastRange = newRange; + } else { + lastRange.mEndOffset = newRange.mStartOffset; + action->mRanges->AppendElement(newRange); + } + } + + // We need to hack for Korean Input System which is Korean standard TIP. + // It sets no change style to IME selection (the selection is always only + // one). So, the composition string looks like normal (or committed) + // string. At this time, current selection range is same as the + // composition string range. Other applications set a wide caret which + // covers the composition string, however, Gecko doesn't support the wide + // caret drawing now (Gecko doesn't support XOR drawing), unfortunately. + // For now, we should change the range style to undefined. + if (!selectionForTSF.IsCollapsed() && action->mRanges->Length() == 1) { + TextRange& range = action->mRanges->ElementAt(0); + LONG start = selectionForTSF.MinOffset(); + LONG end = selectionForTSF.MaxOffset(); + if ((LONG)range.mStartOffset == start - mComposition.mStart && + (LONG)range.mEndOffset == end - mComposition.mStart && + range.mRangeStyle.IsNoChangeStyle()) { + range.mRangeStyle.Clear(); + // The looks of selected type is better than others. + range.mRangeType = TextRangeType::eSelectedRawClause; + } + } + + // The caret position has to be collapsed. + uint32_t caretPosition = + static_cast<uint32_t>(selectionForTSF.MaxOffset() - mComposition.mStart); + + // If caret is in the target clause and it doesn't have specific style, + // the target clause will be painted as normal selection range. Since + // caret shouldn't be in selection range on Windows, we shouldn't append + // caret range in such case. + const TextRange* targetClause = action->mRanges->GetTargetClause(); + if (!targetClause || targetClause->mRangeStyle.IsDefined() || + caretPosition < targetClause->mStartOffset || + caretPosition > targetClause->mEndOffset) { + TextRange caretRange; + caretRange.mStartOffset = caretRange.mEndOffset = caretPosition; + caretRange.mRangeType = TextRangeType::eCaret; + action->mRanges->AppendElement(caretRange); + } + } + + action->mIncomplete = false; + + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::RecordCompositionUpdateAction() " + "succeeded", this)); + + return S_OK; +} + +HRESULT +TSFTextStore::SetSelectionInternal(const TS_SELECTION_ACP* pSelection, + bool aDispatchCompositionChangeEvent) +{ + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::SetSelectionInternal(pSelection={ " + "acpStart=%ld, acpEnd=%ld, style={ ase=%s, fInterimChar=%s} }, " + "aDispatchCompositionChangeEvent=%s), mComposition.IsComposing()=%s", + this, pSelection->acpStart, pSelection->acpEnd, + GetActiveSelEndName(pSelection->style.ase), + GetBoolName(pSelection->style.fInterimChar), + GetBoolName(aDispatchCompositionChangeEvent), + GetBoolName(mComposition.IsComposing()))); + + MOZ_ASSERT(IsReadWriteLocked()); + + Selection& selectionForTSF = SelectionForTSFRef(); + if (selectionForTSF.IsDirty()) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to " + "SelectionForTSFRef() failure", this)); + return E_FAIL; + } + + // If actually the range is not changing, we should do nothing. + // Perhaps, we can ignore the difference change because it must not be + // important for following edit. + if (selectionForTSF.EqualsExceptDirection(*pSelection)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::SetSelectionInternal() Succeeded but " + "did nothing because the selection range isn't changing", this)); + selectionForTSF.SetSelection(*pSelection); + return S_OK; + } + + if (mComposition.IsComposing()) { + if (aDispatchCompositionChangeEvent) { + HRESULT hr = RestartCompositionIfNecessary(); + if (FAILED(hr)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to " + "RestartCompositionIfNecessary() failure", this)); + return hr; + } + } + if (pSelection->acpStart < mComposition.mStart || + pSelection->acpEnd > mComposition.EndOffset()) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to " + "the selection being out of the composition string", this)); + return TS_E_INVALIDPOS; + } + // Emulate selection during compositions + selectionForTSF.SetSelection(*pSelection); + if (aDispatchCompositionChangeEvent) { + HRESULT hr = RecordCompositionUpdateAction(); + if (FAILED(hr)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to " + "RecordCompositionUpdateAction() failure", this)); + return hr; + } + } + return S_OK; + } + + TS_SELECTION_ACP selectionInContent(*pSelection); + + // If mContentForTSF caches old contents which is now different from + // actual contents, we need some complicated hack here... + // Note that this hack assumes that this is used for reconversion. + if (mContentForTSF.IsInitialized() && + mPendingTextChangeData.IsValid() && + !mPendingTextChangeData.mCausedOnlyByComposition) { + uint32_t startOffset = static_cast<uint32_t>(selectionInContent.acpStart); + uint32_t endOffset = static_cast<uint32_t>(selectionInContent.acpEnd); + if (mPendingTextChangeData.mStartOffset >= endOffset) { + // Setting selection before any changed ranges is fine. + } else if (mPendingTextChangeData.mRemovedEndOffset <= startOffset) { + // Setting selection after removed range is fine with following + // adjustment. + selectionInContent.acpStart += mPendingTextChangeData.Difference(); + selectionInContent.acpEnd += mPendingTextChangeData.Difference(); + } else if (startOffset == endOffset) { + // Moving caret position may be fine in most cases even if the insertion + // point has already gone but in this case, composition will be inserted + // to unexpected position, though. + // It seems that moving caret into middle of the new text is odd. + // Perhaps, end of it is expected by users in most cases. + selectionInContent.acpStart = mPendingTextChangeData.mAddedEndOffset; + selectionInContent.acpEnd = selectionInContent.acpStart; + } else { + // Otherwise, i.e., setting range has already gone, we cannot set + // selection properly. + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to " + "there is unknown content change", this)); + return E_FAIL; + } + } + + CompleteLastActionIfStillIncomplete(); + PendingAction* action = mPendingActions.AppendElement(); + action->mType = PendingAction::SET_SELECTION; + action->mSelectionStart = selectionInContent.acpStart; + action->mSelectionLength = + selectionInContent.acpEnd - selectionInContent.acpStart; + action->mSelectionReversed = (selectionInContent.style.ase == TS_AE_START); + + // Use TSF specified selection for updating mSelectionForTSF. + selectionForTSF.SetSelection(*pSelection); + + return S_OK; +} + +STDMETHODIMP +TSFTextStore::SetSelection(ULONG ulCount, + const TS_SELECTION_ACP* pSelection) +{ + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::SetSelection(ulCount=%lu, pSelection=%p { " + "acpStart=%ld, acpEnd=%ld, style={ ase=%s, fInterimChar=%s } }), " + "mComposition.IsComposing()=%s", + this, ulCount, pSelection, + pSelection ? pSelection->acpStart : 0, + pSelection ? pSelection->acpEnd : 0, + pSelection ? GetActiveSelEndName(pSelection->style.ase) : "", + pSelection ? GetBoolName(pSelection->style.fInterimChar) : "", + GetBoolName(mComposition.IsComposing()))); + + if (!IsReadWriteLocked()) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::SetSelection() FAILED due to " + "not locked (read-write)", this)); + return TS_E_NOLOCK; + } + if (ulCount != 1) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::SetSelection() FAILED due to " + "trying setting multiple selection", this)); + return E_INVALIDARG; + } + if (!pSelection) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::SetSelection() FAILED due to " + "null argument", this)); + return E_INVALIDARG; + } + + HRESULT hr = SetSelectionInternal(pSelection, true); + if (FAILED(hr)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::SetSelection() FAILED due to " + "SetSelectionInternal() failure", this)); + } else { + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::SetSelection() succeeded", this)); + } + return hr; +} + +STDMETHODIMP +TSFTextStore::GetText(LONG acpStart, + LONG acpEnd, + WCHAR* pchPlain, + ULONG cchPlainReq, + ULONG* pcchPlainOut, + TS_RUNINFO* prgRunInfo, + ULONG ulRunInfoReq, + ULONG* pulRunInfoOut, + LONG* pacpNext) +{ + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::GetText(acpStart=%ld, acpEnd=%ld, pchPlain=0x%p, " + "cchPlainReq=%lu, pcchPlainOut=0x%p, prgRunInfo=0x%p, ulRunInfoReq=%lu, " + "pulRunInfoOut=0x%p, pacpNext=0x%p), mComposition={ mStart=%ld, " + "mString.Length()=%lu, IsComposing()=%s }", + this, acpStart, acpEnd, pchPlain, cchPlainReq, pcchPlainOut, + prgRunInfo, ulRunInfoReq, pulRunInfoOut, pacpNext, + mComposition.mStart, mComposition.mString.Length(), + GetBoolName(mComposition.IsComposing()))); + + if (!IsReadLocked()) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::GetText() FAILED due to " + "not locked (read)", this)); + return TS_E_NOLOCK; + } + + if (!pcchPlainOut || (!pchPlain && !prgRunInfo) || + !cchPlainReq != !pchPlain || !ulRunInfoReq != !prgRunInfo) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::GetText() FAILED due to " + "invalid argument", this)); + return E_INVALIDARG; + } + + if (acpStart < 0 || acpEnd < -1 || (acpEnd != -1 && acpStart > acpEnd)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::GetText() FAILED due to " + "invalid position", this)); + return TS_E_INVALIDPOS; + } + + // Making sure to null-terminate string just to be on the safe side + *pcchPlainOut = 0; + if (pchPlain && cchPlainReq) *pchPlain = 0; + if (pulRunInfoOut) *pulRunInfoOut = 0; + if (pacpNext) *pacpNext = acpStart; + if (prgRunInfo && ulRunInfoReq) { + prgRunInfo->uCount = 0; + prgRunInfo->type = TS_RT_PLAIN; + } + + Content& contentForTSF = ContentForTSFRef(); + if (!contentForTSF.IsInitialized()) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::GetText() FAILED due to " + "ContentForTSFRef() failure", this)); + return E_FAIL; + } + if (contentForTSF.Text().Length() < static_cast<uint32_t>(acpStart)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::GetText() FAILED due to " + "acpStart is larger offset than the actual text length", this)); + return TS_E_INVALIDPOS; + } + if (acpEnd != -1 && + contentForTSF.Text().Length() < static_cast<uint32_t>(acpEnd)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::GetText() FAILED due to " + "acpEnd is larger offset than the actual text length", this)); + return TS_E_INVALIDPOS; + } + uint32_t length = (acpEnd == -1) ? + contentForTSF.Text().Length() - static_cast<uint32_t>(acpStart) : + static_cast<uint32_t>(acpEnd - acpStart); + if (cchPlainReq && cchPlainReq - 1 < length) { + length = cchPlainReq - 1; + } + if (length) { + if (pchPlain && cchPlainReq) { + const char16_t* startChar = + contentForTSF.Text().BeginReading() + acpStart; + memcpy(pchPlain, startChar, length * sizeof(*pchPlain)); + pchPlain[length] = 0; + *pcchPlainOut = length; + } + if (prgRunInfo && ulRunInfoReq) { + prgRunInfo->uCount = length; + prgRunInfo->type = TS_RT_PLAIN; + if (pulRunInfoOut) *pulRunInfoOut = 1; + } + if (pacpNext) *pacpNext = acpStart + length; + } + + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::GetText() succeeded: pcchPlainOut=0x%p, " + "*prgRunInfo={ uCount=%lu, type=%s }, *pulRunInfoOut=%lu, " + "*pacpNext=%ld)", + this, pcchPlainOut, prgRunInfo ? prgRunInfo->uCount : 0, + prgRunInfo ? GetTextRunTypeName(prgRunInfo->type) : "N/A", + pulRunInfoOut ? *pulRunInfoOut : 0, pacpNext ? *pacpNext : 0)); + return S_OK; +} + +STDMETHODIMP +TSFTextStore::SetText(DWORD dwFlags, + LONG acpStart, + LONG acpEnd, + const WCHAR* pchText, + ULONG cch, + TS_TEXTCHANGE* pChange) +{ + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::SetText(dwFlags=%s, acpStart=%ld, " + "acpEnd=%ld, pchText=0x%p \"%s\", cch=%lu, pChange=0x%p), " + "mComposition.IsComposing()=%s", + this, dwFlags == TS_ST_CORRECTION ? "TS_ST_CORRECTION" : + "not-specified", + acpStart, acpEnd, pchText, + pchText && cch ? + GetEscapedUTF8String(pchText, cch).get() : "", + cch, pChange, GetBoolName(mComposition.IsComposing()))); + + // Per SDK documentation, and since we don't have better + // ways to do this, this method acts as a helper to + // call SetSelection followed by InsertTextAtSelection + if (!IsReadWriteLocked()) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::SetText() FAILED due to " + "not locked (read)", this)); + return TS_E_NOLOCK; + } + + TS_SELECTION_ACP selection; + selection.acpStart = acpStart; + selection.acpEnd = acpEnd; + selection.style.ase = TS_AE_END; + selection.style.fInterimChar = 0; + // Set selection to desired range + HRESULT hr = SetSelectionInternal(&selection); + if (FAILED(hr)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::SetText() FAILED due to " + "SetSelectionInternal() failure", this)); + return hr; + } + // Replace just selected text + if (!InsertTextAtSelectionInternal(nsDependentSubstring(pchText, cch), + pChange)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::SetText() FAILED due to " + "InsertTextAtSelectionInternal() failure", this)); + return E_FAIL; + } + + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::SetText() succeeded: pChange={ " + "acpStart=%ld, acpOldEnd=%ld, acpNewEnd=%ld }", + this, pChange ? pChange->acpStart : 0, + pChange ? pChange->acpOldEnd : 0, pChange ? pChange->acpNewEnd : 0)); + return S_OK; +} + +STDMETHODIMP +TSFTextStore::GetFormattedText(LONG acpStart, + LONG acpEnd, + IDataObject** ppDataObject) +{ + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::GetFormattedText() called " + "but not supported (E_NOTIMPL)", this)); + + // no support for formatted text + return E_NOTIMPL; +} + +STDMETHODIMP +TSFTextStore::GetEmbedded(LONG acpPos, + REFGUID rguidService, + REFIID riid, + IUnknown** ppunk) +{ + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::GetEmbedded() called " + "but not supported (E_NOTIMPL)", this)); + + // embedded objects are not supported + return E_NOTIMPL; +} + +STDMETHODIMP +TSFTextStore::QueryInsertEmbedded(const GUID* pguidService, + const FORMATETC* pFormatEtc, + BOOL* pfInsertable) +{ + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::QueryInsertEmbedded() called " + "but not supported, *pfInsertable=FALSE (S_OK)", this)); + + // embedded objects are not supported + *pfInsertable = FALSE; + return S_OK; +} + +STDMETHODIMP +TSFTextStore::InsertEmbedded(DWORD dwFlags, + LONG acpStart, + LONG acpEnd, + IDataObject* pDataObject, + TS_TEXTCHANGE* pChange) +{ + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::InsertEmbedded() called " + "but not supported (E_NOTIMPL)", this)); + + // embedded objects are not supported + return E_NOTIMPL; +} + +void +TSFTextStore::SetInputScope(const nsString& aHTMLInputType, + const nsString& aHTMLInputInputMode) +{ + mInputScopes.Clear(); + if (aHTMLInputType.IsEmpty() || aHTMLInputType.EqualsLiteral("text")) { + if (aHTMLInputInputMode.EqualsLiteral("url")) { + mInputScopes.AppendElement(IS_URL); + } else if (aHTMLInputInputMode.EqualsLiteral("email")) { + mInputScopes.AppendElement(IS_EMAIL_SMTPEMAILADDRESS); + } else if (aHTMLInputType.EqualsLiteral("tel")) { + mInputScopes.AppendElement(IS_TELEPHONE_FULLTELEPHONENUMBER); + mInputScopes.AppendElement(IS_TELEPHONE_LOCALNUMBER); + } else if (aHTMLInputType.EqualsLiteral("numeric")) { + mInputScopes.AppendElement(IS_NUMBER); + } + return; + } + + // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-input-element.html + if (aHTMLInputType.EqualsLiteral("url")) { + mInputScopes.AppendElement(IS_URL); + } else if (aHTMLInputType.EqualsLiteral("search")) { + mInputScopes.AppendElement(IS_SEARCH); + } else if (aHTMLInputType.EqualsLiteral("email")) { + mInputScopes.AppendElement(IS_EMAIL_SMTPEMAILADDRESS); + } else if (aHTMLInputType.EqualsLiteral("password")) { + mInputScopes.AppendElement(IS_PASSWORD); + } else if (aHTMLInputType.EqualsLiteral("datetime") || + aHTMLInputType.EqualsLiteral("datetime-local")) { + mInputScopes.AppendElement(IS_DATE_FULLDATE); + mInputScopes.AppendElement(IS_TIME_FULLTIME); + } else if (aHTMLInputType.EqualsLiteral("date") || + aHTMLInputType.EqualsLiteral("month") || + aHTMLInputType.EqualsLiteral("week")) { + mInputScopes.AppendElement(IS_DATE_FULLDATE); + } else if (aHTMLInputType.EqualsLiteral("time")) { + mInputScopes.AppendElement(IS_TIME_FULLTIME); + } else if (aHTMLInputType.EqualsLiteral("tel")) { + mInputScopes.AppendElement(IS_TELEPHONE_FULLTELEPHONENUMBER); + mInputScopes.AppendElement(IS_TELEPHONE_LOCALNUMBER); + } else if (aHTMLInputType.EqualsLiteral("number")) { + mInputScopes.AppendElement(IS_NUMBER); + } +} + +int32_t +TSFTextStore::GetRequestedAttrIndex(const TS_ATTRID& aAttrID) +{ + if (IsEqualGUID(aAttrID, GUID_PROP_INPUTSCOPE)) { + return eInputScope; + } + if (IsEqualGUID(aAttrID, TSATTRID_Text_VerticalWriting)) { + return eTextVerticalWriting; + } + if (IsEqualGUID(aAttrID, TSATTRID_Text_Orientation)) { + return eTextOrientation; + } + return eNotSupported; +} + +TS_ATTRID +TSFTextStore::GetAttrID(int32_t aIndex) +{ + switch (aIndex) { + case eInputScope: + return GUID_PROP_INPUTSCOPE; + case eTextVerticalWriting: + return TSATTRID_Text_VerticalWriting; + case eTextOrientation: + return TSATTRID_Text_Orientation; + default: + MOZ_CRASH("Invalid index? Or not implemented yet?"); + return GUID_NULL; + } +} + +HRESULT +TSFTextStore::HandleRequestAttrs(DWORD aFlags, + ULONG aFilterCount, + const TS_ATTRID* aFilterAttrs) +{ + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::HandleRequestAttrs(aFlags=%s, " + "aFilterCount=%u)", + this, GetFindFlagName(aFlags).get(), aFilterCount)); + + // This is a little weird! RequestSupportedAttrs gives us advanced notice + // of a support query via RetrieveRequestedAttrs for a specific attribute. + // RetrieveRequestedAttrs needs to return valid data for all attributes we + // support, but the text service will only want the input scope object + // returned in RetrieveRequestedAttrs if the dwFlags passed in here contains + // TS_ATTR_FIND_WANT_VALUE. + for (int32_t i = 0; i < NUM_OF_SUPPORTED_ATTRS; i++) { + mRequestedAttrs[i] = false; + } + mRequestedAttrValues = !!(aFlags & TS_ATTR_FIND_WANT_VALUE); + + for (uint32_t i = 0; i < aFilterCount; i++) { + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::HandleRequestAttrs(), " + "requested attr=%s", + this, GetGUIDNameStrWithTable(aFilterAttrs[i]).get())); + int32_t index = GetRequestedAttrIndex(aFilterAttrs[i]); + if (index != eNotSupported) { + mRequestedAttrs[index] = true; + } + } + return S_OK; +} + +STDMETHODIMP +TSFTextStore::RequestSupportedAttrs(DWORD dwFlags, + ULONG cFilterAttrs, + const TS_ATTRID* paFilterAttrs) +{ + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::RequestSupportedAttrs(dwFlags=%s, " + "cFilterAttrs=%lu)", + this, GetFindFlagName(dwFlags).get(), cFilterAttrs)); + + return HandleRequestAttrs(dwFlags, cFilterAttrs, paFilterAttrs); +} + +STDMETHODIMP +TSFTextStore::RequestAttrsAtPosition(LONG acpPos, + ULONG cFilterAttrs, + const TS_ATTRID* paFilterAttrs, + DWORD dwFlags) +{ + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::RequestAttrsAtPosition(acpPos=%ld, " + "cFilterAttrs=%lu, dwFlags=%s)", + this, acpPos, cFilterAttrs, GetFindFlagName(dwFlags).get())); + + return HandleRequestAttrs(dwFlags | TS_ATTR_FIND_WANT_VALUE, + cFilterAttrs, paFilterAttrs); +} + +STDMETHODIMP +TSFTextStore::RequestAttrsTransitioningAtPosition(LONG acpPos, + ULONG cFilterAttrs, + const TS_ATTRID* paFilterAttr, + DWORD dwFlags) +{ + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::RequestAttrsTransitioningAtPosition(" + "acpPos=%ld, cFilterAttrs=%lu, dwFlags=%s) called but not supported " + "(S_OK)", + this, acpPos, cFilterAttrs, GetFindFlagName(dwFlags).get())); + + // no per character attributes defined + return S_OK; +} + +STDMETHODIMP +TSFTextStore::FindNextAttrTransition(LONG acpStart, + LONG acpHalt, + ULONG cFilterAttrs, + const TS_ATTRID* paFilterAttrs, + DWORD dwFlags, + LONG* pacpNext, + BOOL* pfFound, + LONG* plFoundOffset) +{ + if (!pacpNext || !pfFound || !plFoundOffset) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + (" 0x%p TSFTextStore::FindNextAttrTransition() FAILED due to " + "null argument", this)); + return E_INVALIDARG; + } + + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::FindNextAttrTransition() called " + "but not supported (S_OK)", this)); + + // no per character attributes defined + *pacpNext = *plFoundOffset = acpHalt; + *pfFound = FALSE; + return S_OK; +} + +STDMETHODIMP +TSFTextStore::RetrieveRequestedAttrs(ULONG ulCount, + TS_ATTRVAL* paAttrVals, + ULONG* pcFetched) +{ + if (!pcFetched || !paAttrVals) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::RetrieveRequestedAttrs() FAILED due to " + "null argument", this)); + return E_INVALIDARG; + } + + ULONG expectedCount = 0; + for (int32_t i = 0; i < NUM_OF_SUPPORTED_ATTRS; i++) { + if (mRequestedAttrs[i]) { + expectedCount++; + } + } + if (ulCount < expectedCount) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::RetrieveRequestedAttrs() FAILED due to " + "not enough count ulCount=%u, expectedCount=%u", + this, ulCount, expectedCount)); + return E_INVALIDARG; + } + + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::RetrieveRequestedAttrs() called " + "ulCount=%d, mRequestedAttrValues=%s", + this, ulCount, GetBoolName(mRequestedAttrValues))); + + int32_t count = 0; + for (int32_t i = 0; i < NUM_OF_SUPPORTED_ATTRS; i++) { + if (!mRequestedAttrs[i]) { + continue; + } + mRequestedAttrs[i] = false; + + TS_ATTRID attrID = GetAttrID(i); + + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::RetrieveRequestedAttrs() for %s", + this, GetGUIDNameStrWithTable(attrID).get())); + + paAttrVals[count].idAttr = attrID; + paAttrVals[count].dwOverlapId = 0; + + if (!mRequestedAttrValues) { + paAttrVals[count].varValue.vt = VT_EMPTY; + } else { + switch (i) { + case eInputScope: { + paAttrVals[count].varValue.vt = VT_UNKNOWN; + RefPtr<IUnknown> inputScope = new InputScopeImpl(mInputScopes); + paAttrVals[count].varValue.punkVal = inputScope.forget().take(); + break; + } + case eTextVerticalWriting: { + Selection& selectionForTSF = SelectionForTSFRef(); + paAttrVals[count].varValue.vt = VT_BOOL; + paAttrVals[count].varValue.boolVal = + selectionForTSF.GetWritingMode().IsVertical() ? VARIANT_TRUE : + VARIANT_FALSE; + break; + } + case eTextOrientation: { + Selection& selectionForTSF = SelectionForTSFRef(); + paAttrVals[count].varValue.vt = VT_I4; + paAttrVals[count].varValue.lVal = + selectionForTSF.GetWritingMode().IsVertical() ? 2700 : 0; + break; + } + default: + MOZ_CRASH("Invalid index? Or not implemented yet?"); + break; + } + } + count++; + } + + mRequestedAttrValues = false; + + if (count) { + *pcFetched = count; + return S_OK; + } + + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::RetrieveRequestedAttrs() called " + "for unknown TS_ATTRVAL, *pcFetched=0 (S_OK)", this)); + + paAttrVals->dwOverlapId = 0; + paAttrVals->varValue.vt = VT_EMPTY; + *pcFetched = 0; + return S_OK; +} + +STDMETHODIMP +TSFTextStore::GetEndACP(LONG* pacp) +{ + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::GetEndACP(pacp=0x%p)", this, pacp)); + + if (!IsReadLocked()) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::GetEndACP() FAILED due to " + "not locked (read)", this)); + return TS_E_NOLOCK; + } + + if (!pacp) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::GetEndACP() FAILED due to " + "null argument", this)); + return E_INVALIDARG; + } + + Content& contentForTSF = ContentForTSFRef(); + if (!contentForTSF.IsInitialized()) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::GetEndACP() FAILED due to " + "ContentForTSFRef() failure", this)); + return E_FAIL; + } + *pacp = static_cast<LONG>(contentForTSF.Text().Length()); + return S_OK; +} + +STDMETHODIMP +TSFTextStore::GetActiveView(TsViewCookie* pvcView) +{ + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::GetActiveView(pvcView=0x%p)", + this, pvcView)); + + if (!pvcView) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::GetActiveView() FAILED due to " + "null argument", this)); + return E_INVALIDARG; + } + + *pvcView = TEXTSTORE_DEFAULT_VIEW; + + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::GetActiveView() succeeded: *pvcView=%ld", + this, *pvcView)); + return S_OK; +} + +STDMETHODIMP +TSFTextStore::GetACPFromPoint(TsViewCookie vcView, + const POINT* pt, + DWORD dwFlags, + LONG* pacp) +{ + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::GetACPFromPoint(pvcView=%d, pt=%p (x=%d, " + "y=%d), dwFlags=%s, pacp=%p, mDeferNotifyingTSF=%s, " + "mWaitingQueryLayout=%s", + this, vcView, pt, pt ? pt->x : 0, pt ? pt->y : 0, + GetACPFromPointFlagName(dwFlags).get(), pacp, + GetBoolName(mDeferNotifyingTSF), GetBoolName(mWaitingQueryLayout))); + + if (!IsReadLocked()) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to " + "not locked (read)", this)); + return TS_E_NOLOCK; + } + + if (vcView != TEXTSTORE_DEFAULT_VIEW) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to " + "called with invalid view", this)); + return E_INVALIDARG; + } + + if (!pt) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to " + "null pt", this)); + return E_INVALIDARG; + } + + if (!pacp) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to " + "null pacp", this)); + return E_INVALIDARG; + } + + mWaitingQueryLayout = false; + + if (mDestroyed || mContentForTSF.IsLayoutChanged()) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::GetACPFromPoint() returned " + "TS_E_NOLAYOUT", this)); + mHasReturnedNoLayoutError = true; + return TS_E_NOLAYOUT; + } + + LayoutDeviceIntPoint ourPt(pt->x, pt->y); + // Convert to widget relative coordinates from screen's. + ourPt -= mWidget->WidgetToScreenOffset(); + + // NOTE: Don't check if the point is in the widget since the point can be + // outside of the widget if focused editor is in a XUL <panel>. + + WidgetQueryContentEvent charAtPt(true, eQueryCharacterAtPoint, mWidget); + mWidget->InitEvent(charAtPt, &ourPt); + + // FYI: WidgetQueryContentEvent may cause flushing pending layout and it + // may cause focus change or something. + RefPtr<TSFTextStore> kungFuDeathGrip(this); + DispatchEvent(charAtPt); + if (!mWidget || mWidget->Destroyed()) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to " + "mWidget was destroyed during eQueryCharacterAtPoint", this)); + return E_FAIL; + } + + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::GetACPFromPoint(), charAtPt={ " + "mSucceeded=%s, mReply={ mOffset=%u, mTentativeCaretOffset=%u }}", + this, GetBoolName(charAtPt.mSucceeded), charAtPt.mReply.mOffset, + charAtPt.mReply.mTentativeCaretOffset)); + + if (NS_WARN_IF(!charAtPt.mSucceeded)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to " + "eQueryCharacterAtPoint failure", this)); + return E_FAIL; + } + + // If dwFlags isn't set and the point isn't in any character's bounding box, + // we should return TS_E_INVALIDPOINT. + if (!(dwFlags & GXFPF_NEAREST) && + charAtPt.mReply.mOffset == WidgetQueryContentEvent::NOT_FOUND) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to the " + "point contained by no bounding box", this)); + return TS_E_INVALIDPOINT; + } + + // Although, we're not sure if mTentativeCaretOffset becomes NOT_FOUND, + // let's assume that there is no content in such case. + if (NS_WARN_IF(charAtPt.mReply.mTentativeCaretOffset == + WidgetQueryContentEvent::NOT_FOUND)) { + charAtPt.mReply.mTentativeCaretOffset = 0; + } + + uint32_t offset; + + // If dwFlags includes GXFPF_ROUND_NEAREST, we should return tentative + // caret offset (MSDN calls it "range position"). + if (dwFlags & GXFPF_ROUND_NEAREST) { + offset = charAtPt.mReply.mTentativeCaretOffset; + } else if (charAtPt.mReply.mOffset != WidgetQueryContentEvent::NOT_FOUND) { + // Otherwise, we should return character offset whose bounding box contains + // the point. + offset = charAtPt.mReply.mOffset; + } else { + // If the point isn't in any character's bounding box but we need to return + // the nearest character from the point, we should *guess* the character + // offset since there is no inexpensive API to check it strictly. + // XXX If we retrieve 2 bounding boxes, one is before the offset and + // the other is after the offset, we could resolve the offset. + // However, dispatching 2 eQueryTextRect may be expensive. + + // So, use tentative offset for now. + offset = charAtPt.mReply.mTentativeCaretOffset; + + // However, if it's after the last character, we need to decrement the + // offset. + Content& contentForTSF = ContentForTSFRef(); + if (!contentForTSF.IsInitialized()) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to " + "ContentForTSFRef() failure", this)); + return E_FAIL; + } + if (contentForTSF.Text().Length() <= offset) { + // If the tentative caret is after the last character, let's return + // the last character's offset. + offset = contentForTSF.Text().Length() - 1; + } + } + + if (NS_WARN_IF(offset > LONG_MAX)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to out of " + "range of the result", this)); + return TS_E_INVALIDPOINT; + } + + *pacp = static_cast<LONG>(offset); + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::GetACPFromPoint() succeeded: *pacp=%d", + this, *pacp)); + return S_OK; +} + +STDMETHODIMP +TSFTextStore::GetTextExt(TsViewCookie vcView, + LONG acpStart, + LONG acpEnd, + RECT* prc, + BOOL* pfClipped) +{ + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::GetTextExt(vcView=%ld, " + "acpStart=%ld, acpEnd=%ld, prc=0x%p, pfClipped=0x%p), " + "mDeferNotifyingTSF=%s, mWaitingQueryLayout=%s", + this, vcView, acpStart, acpEnd, prc, pfClipped, + GetBoolName(mDeferNotifyingTSF), GetBoolName(mWaitingQueryLayout))); + + if (!IsReadLocked()) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::GetTextExt() FAILED due to " + "not locked (read)", this)); + return TS_E_NOLOCK; + } + + if (vcView != TEXTSTORE_DEFAULT_VIEW) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::GetTextExt() FAILED due to " + "called with invalid view", this)); + return E_INVALIDARG; + } + + if (!prc || !pfClipped) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::GetTextExt() FAILED due to " + "null argument", this)); + return E_INVALIDARG; + } + + if (acpStart < 0 || acpEnd < acpStart) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::GetTextExt() FAILED due to " + "invalid position", this)); + return TS_E_INVALIDPOS; + } + + mWaitingQueryLayout = false; + + // NOTE: TSF (at least on Win 8.1) doesn't return TS_E_NOLAYOUT to the + // caller even if we return it. It's converted to just E_FAIL. + // However, this is fixed on Win 10. + + bool dontReturnNoLayoutError = false; + + const TSFStaticSink* kSink = TSFStaticSink::GetInstance(); + if (mComposition.IsComposing() && mComposition.mStart < acpEnd && + mContentForTSF.IsLayoutChangedAt(acpEnd)) { + const Selection& selectionForTSF = SelectionForTSFRef(); + // The bug of Microsoft Office IME 2010 for Japanese is similar to + // MS-IME for Win 8.1 and Win 10. Newer version of MS Office IME is not + // released yet. So, we can hack it without prefs because there must be + // no developers who want to disable this hack for tests. + const bool kIsMSOfficeJapaneseIME2010 = + kSink->IsMSOfficeJapaneseIME2010Active(); + // MS IME for Japanese doesn't support asynchronous handling at deciding + // its suggest list window position. The feature was implemented + // starting from Windows 8. And also we may meet same trouble in e10s + // mode on Win7. So, we should never return TS_E_NOLAYOUT to MS IME for + // Japanese. + if (kIsMSOfficeJapaneseIME2010 || + ((sDoNotReturnNoLayoutErrorToMSJapaneseIMEAtFirstChar || + sDoNotReturnNoLayoutErrorToMSJapaneseIMEAtCaret) && + kSink->IsMSJapaneseIMEActive())) { + // Basically, MS-IME tries to retrieve whole composition string rect + // at deciding suggest window immediately after unlocking the document. + // However, in e10s mode, the content hasn't updated yet in most cases. + // Therefore, if the first character at the retrieving range rect is + // available, we should use it as the result. + if ((kIsMSOfficeJapaneseIME2010 || + sDoNotReturnNoLayoutErrorToMSJapaneseIMEAtFirstChar) && + acpStart < acpEnd) { + acpEnd = acpStart; + dontReturnNoLayoutError = true; + } + // Although, the condition is not clear, MS-IME sometimes retrieves the + // caret rect immediately after modifying the composition string but + // before unlocking the document. In such case, we should return the + // nearest character rect. + else if ((kIsMSOfficeJapaneseIME2010 || + sDoNotReturnNoLayoutErrorToMSJapaneseIMEAtCaret) && + acpStart == acpEnd && + selectionForTSF.IsCollapsed() && + selectionForTSF.EndOffset() == acpEnd) { + if (mContentForTSF.MinOffsetOfLayoutChanged() > LONG_MAX) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::GetTextExt(), FAILED due to the text " + "is too big for TSF (cannot treat modified offset as LONG), " + "mContentForTSF.MinOffsetOfLayoutChanged()=%u", + this, mContentForTSF.MinOffsetOfLayoutChanged())); + return E_FAIL; + } + int32_t minOffsetOfLayoutChanged = + static_cast<int32_t>(mContentForTSF.MinOffsetOfLayoutChanged()); + acpEnd = acpStart = std::max(minOffsetOfLayoutChanged - 1, 0); + dontReturnNoLayoutError = true; + } + } + // ATOK fails to handle TS_E_NOLAYOUT only when it decides the position of + // suggest window. In such case, ATOK tries to query rect of whole + // composition string. + // XXX For testing with legacy ATOK, we should hack it even if current ATOK + // refers native caret rect on windows whose window class is one of + // Mozilla window classes and we stop creating native caret for ATOK + // because creating native caret causes ATOK refers caret position + // when GetTextExt() returns TS_E_NOLAYOUT. + else if (sDoNotReturnNoLayoutErrorToATOKOfCompositionString && + kSink->IsATOKActive() && + (!kSink->IsATOKReferringNativeCaretActive() || + !sCreateNativeCaretForLegacyATOK) && + mComposition.mStart == acpStart && + mComposition.EndOffset() == acpEnd) { + dontReturnNoLayoutError = true; + } + // Free ChangJie 2010 and Easy Changjei 1.0.12.0 doesn't handle + // ITfContextView::GetTextExt() properly. Prehaps, it's due to the bug of + // TSF. We need to check if this is necessary on Windows 10 before + // disabling this on Windows 10. + else if ((sDoNotReturnNoLayoutErrorToFreeChangJie && + kSink->IsFreeChangJieActive()) || + (sDoNotReturnNoLayoutErrorToEasyChangjei && + kSink->IsEasyChangjeiActive())) { + acpEnd = mComposition.mStart; + acpStart = std::min(acpStart, acpEnd); + dontReturnNoLayoutError = true; + } + // Some Chinese TIPs of Microsoft doesn't show candidate window in e10s + // mode on Win8 or later. + else if (IsWin8OrLater() && + ((sDoNotReturnNoLayoutErrorToMSTraditionalTIP && + (kSink->IsMSChangJieActive() || + kSink->IsMSQuickQuickActive())) || + (sDoNotReturnNoLayoutErrorToMSSimplifiedTIP && + (kSink->IsMSPinyinActive() || + kSink->IsMSWubiActive())))) { + acpEnd = mComposition.mStart; + acpStart = std::min(acpStart, acpEnd); + dontReturnNoLayoutError = true; + } + + // If we hack the queried range for active TIP, that means we should not + // return TS_E_NOLAYOUT even if hacked offset is still modified. So, as + // far as possible, we should adjust the offset. + if (dontReturnNoLayoutError) { + MOZ_ASSERT(mContentForTSF.IsLayoutChanged()); + if (mContentForTSF.MinOffsetOfLayoutChanged() > LONG_MAX) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::GetTextExt(), FAILED due to the text " + "is too big for TSF (cannot treat modified offset as LONG), " + "mContentForTSF.MinOffsetOfLayoutChanged()=%u", + this, mContentForTSF.MinOffsetOfLayoutChanged())); + return E_FAIL; + } + // Note that even if all characters in the editor or the composition + // string was modified, 0 or start offset of the composition string is + // useful because it may return caret rect or old character's rect which + // the user still see. That must be useful information for TIP. + int32_t firstModifiedOffset = + static_cast<int32_t>(mContentForTSF.MinOffsetOfLayoutChanged()); + LONG lastUnmodifiedOffset = std::max(firstModifiedOffset - 1, 0); + if (mContentForTSF.IsLayoutChangedAt(acpStart)) { + // If TSF queries text rect in composition string, we should return + // rect at start of the composition even if its layout is changed. + if (acpStart >= mComposition.mStart) { + acpStart = mComposition.mStart; + } + // Otherwise, use first character's rect. Even if there is no + // characters, the query event will return caret rect instead. + else { + acpStart = lastUnmodifiedOffset; + } + MOZ_ASSERT(acpStart <= acpEnd); + } + if (mContentForTSF.IsLayoutChangedAt(acpEnd)) { + // Use max larger offset of last unmodified offset or acpStart which + // may be the first character offset of the composition string. + acpEnd = std::max(acpStart, lastUnmodifiedOffset); + } + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::GetTextExt() hacked the queried range " + "for not returning TS_E_NOLAYOUT, new values are: " + "acpStart=%d, acpEnd=%d", this, acpStart, acpEnd)); + } + } + + if (!dontReturnNoLayoutError && mContentForTSF.IsLayoutChangedAt(acpEnd)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::GetTextExt() returned TS_E_NOLAYOUT " + "(acpEnd=%d)", this, acpEnd)); + mHasReturnedNoLayoutError = true; + return TS_E_NOLAYOUT; + } + + if (mDestroyed) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::GetTextExt() returned TS_E_NOLAYOUT " + "(acpEnd=%d) because this has already been destroyed", + this, acpEnd)); + mHasReturnedNoLayoutError = true; + return TS_E_NOLAYOUT; + } + + // use eQueryTextRect to get rect in system, screen coordinates + WidgetQueryContentEvent event(true, eQueryTextRect, mWidget); + mWidget->InitEvent(event); + + WidgetQueryContentEvent::Options options; + int64_t startOffset = acpStart; + if (mComposition.IsComposing()) { + // If there is a composition, TSF must want character rects related to + // the composition. Therefore, we should use insertion point relative + // query because the composition might be at different position from + // the position where TSFTextStore believes it at. + options.mRelativeToInsertionPoint = true; + startOffset -= mComposition.mStart; + } else if (!CanAccessActualContentDirectly()) { + // If TSF/TIP cannot access actual content directly, there may be pending + // text and/or selection changes which have not been notified TSF yet. + // Therefore, we should use relative to insertion point query since + // TSF/TIP computes the offset from the cached selection. + options.mRelativeToInsertionPoint = true; + startOffset -= mSelectionForTSF.StartOffset(); + } + // ContentEventHandler and ContentCache return actual caret rect when + // the queried range is collapsed and selection is collapsed at the + // queried range. Then, its height (in horizontal layout, width in vertical + // layout) may be different from actual font height of the line. In such + // case, users see "dancing" of candidate or suggest window of TIP. + // For preventing it, we should query text rect with at least 1 length. + uint32_t length = std::max(static_cast<int32_t>(acpEnd - acpStart), 1); + event.InitForQueryTextRect(startOffset, length, options); + + DispatchEvent(event); + if (NS_WARN_IF(!event.mSucceeded)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::GetTextExt() FAILED due to " + "eQueryTextRect failure", this)); + return TS_E_INVALIDPOS; // but unexpected failure, maybe. + } + + // IMEs don't like empty rects, fix here + if (event.mReply.mRect.width <= 0) + event.mReply.mRect.width = 1; + if (event.mReply.mRect.height <= 0) + event.mReply.mRect.height = 1; + + // convert to unclipped screen rect + nsWindow* refWindow = static_cast<nsWindow*>( + event.mReply.mFocusedWidget ? event.mReply.mFocusedWidget : mWidget); + // Result rect is in top level widget coordinates + refWindow = refWindow->GetTopLevelWindow(false); + if (!refWindow) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::GetTextExt() FAILED due to " + "no top level window", this)); + return E_FAIL; + } + + event.mReply.mRect.MoveBy(refWindow->WidgetToScreenOffset()); + + // get bounding screen rect to test for clipping + if (!GetScreenExtInternal(*prc)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::GetTextExt() FAILED due to " + "GetScreenExtInternal() failure", this)); + return E_FAIL; + } + + // clip text rect to bounding rect + RECT textRect; + ::SetRect(&textRect, event.mReply.mRect.x, event.mReply.mRect.y, + event.mReply.mRect.XMost(), event.mReply.mRect.YMost()); + if (!::IntersectRect(prc, prc, &textRect)) + // Text is not visible + ::SetRectEmpty(prc); + + // not equal if text rect was clipped + *pfClipped = !::EqualRect(prc, &textRect); + + // ATOK 2011 - 2016 refers native caret position and size on windows whose + // class name is one of Mozilla's windows for deciding candidate window + // position. Therefore, we need to create native caret only when ATOK 2011 - + // 2016 is active. + if (sCreateNativeCaretForLegacyATOK && + kSink->IsATOKReferringNativeCaretActive() && + mComposition.IsComposing() && + mComposition.mStart <= acpStart && mComposition.EndOffset() >= acpStart && + mComposition.mStart <= acpEnd && mComposition.EndOffset() >= acpEnd) { + CreateNativeCaret(); + } + + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::GetTextExt() succeeded: " + "*prc={ left=%ld, top=%ld, right=%ld, bottom=%ld }, *pfClipped=%s", + this, prc->left, prc->top, prc->right, prc->bottom, + GetBoolName(*pfClipped))); + + return S_OK; +} + +STDMETHODIMP +TSFTextStore::GetScreenExt(TsViewCookie vcView, + RECT* prc) +{ + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::GetScreenExt(vcView=%ld, prc=0x%p)", + this, vcView, prc)); + + if (vcView != TEXTSTORE_DEFAULT_VIEW) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::GetScreenExt() FAILED due to " + "called with invalid view", this)); + return E_INVALIDARG; + } + + if (!prc) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::GetScreenExt() FAILED due to " + "null argument", this)); + return E_INVALIDARG; + } + + if (mDestroyed) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::GetScreenExt() returns empty rect " + "due to already destroyed", this)); + prc->left = prc->top = prc->right = prc->left = 0; + return S_OK; + } + + if (!GetScreenExtInternal(*prc)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::GetScreenExt() FAILED due to " + "GetScreenExtInternal() failure", this)); + return E_FAIL; + } + + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::GetScreenExt() succeeded: " + "*prc={ left=%ld, top=%ld, right=%ld, bottom=%ld }", + this, prc->left, prc->top, prc->right, prc->bottom)); + return S_OK; +} + +bool +TSFTextStore::GetScreenExtInternal(RECT& aScreenExt) +{ + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::GetScreenExtInternal()", this)); + + MOZ_ASSERT(!mDestroyed); + + // use NS_QUERY_EDITOR_RECT to get rect in system, screen coordinates + WidgetQueryContentEvent event(true, eQueryEditorRect, mWidget); + mWidget->InitEvent(event); + DispatchEvent(event); + if (!event.mSucceeded) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::GetScreenExtInternal() FAILED due to " + "eQueryEditorRect failure", this)); + return false; + } + + nsWindow* refWindow = static_cast<nsWindow*>( + event.mReply.mFocusedWidget ? + event.mReply.mFocusedWidget : mWidget); + // Result rect is in top level widget coordinates + refWindow = refWindow->GetTopLevelWindow(false); + if (!refWindow) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::GetScreenExtInternal() FAILED due to " + "no top level window", this)); + return false; + } + + LayoutDeviceIntRect boundRect = refWindow->GetClientBounds(); + boundRect.MoveTo(0, 0); + + // Clip frame rect to window rect + boundRect.IntersectRect(event.mReply.mRect, boundRect); + if (!boundRect.IsEmpty()) { + boundRect.MoveBy(refWindow->WidgetToScreenOffset()); + ::SetRect(&aScreenExt, boundRect.x, boundRect.y, + boundRect.XMost(), boundRect.YMost()); + } else { + ::SetRectEmpty(&aScreenExt); + } + + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::GetScreenExtInternal() succeeded: " + "aScreenExt={ left=%ld, top=%ld, right=%ld, bottom=%ld }", + this, aScreenExt.left, aScreenExt.top, + aScreenExt.right, aScreenExt.bottom)); + return true; +} + +STDMETHODIMP +TSFTextStore::GetWnd(TsViewCookie vcView, + HWND* phwnd) +{ + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::GetWnd(vcView=%ld, phwnd=0x%p), " + "mWidget=0x%p", + this, vcView, phwnd, mWidget.get())); + + if (vcView != TEXTSTORE_DEFAULT_VIEW) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::GetWnd() FAILED due to " + "called with invalid view", this)); + return E_INVALIDARG; + } + + if (!phwnd) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::GetScreenExt() FAILED due to " + "null argument", this)); + return E_INVALIDARG; + } + + *phwnd = mWidget ? mWidget->GetWindowHandle() : nullptr; + + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::GetWnd() succeeded: *phwnd=0x%p", + this, static_cast<void*>(*phwnd))); + return S_OK; +} + +STDMETHODIMP +TSFTextStore::InsertTextAtSelection(DWORD dwFlags, + const WCHAR* pchText, + ULONG cch, + LONG* pacpStart, + LONG* pacpEnd, + TS_TEXTCHANGE* pChange) +{ + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::InsertTextAtSelection(dwFlags=%s, " + "pchText=0x%p \"%s\", cch=%lu, pacpStart=0x%p, pacpEnd=0x%p, " + "pChange=0x%p), IsComposing()=%s", + this, dwFlags == 0 ? "0" : + dwFlags == TF_IAS_NOQUERY ? "TF_IAS_NOQUERY" : + dwFlags == TF_IAS_QUERYONLY ? "TF_IAS_QUERYONLY" : "Unknown", + pchText, + pchText && cch ? GetEscapedUTF8String(pchText, cch).get() : "", + cch, pacpStart, pacpEnd, pChange, + GetBoolName(mComposition.IsComposing()))); + + if (cch && !pchText) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to " + "null pchText", this)); + return E_INVALIDARG; + } + + if (TS_IAS_QUERYONLY == dwFlags) { + if (!IsReadLocked()) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to " + "not locked (read)", this)); + return TS_E_NOLOCK; + } + + if (!pacpStart || !pacpEnd) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to " + "null argument", this)); + return E_INVALIDARG; + } + + // Get selection first + Selection& selectionForTSF = SelectionForTSFRef(); + if (selectionForTSF.IsDirty()) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to " + "SelectionForTSFRef() failure", this)); + return E_FAIL; + } + + // Simulate text insertion + *pacpStart = selectionForTSF.StartOffset(); + *pacpEnd = selectionForTSF.EndOffset(); + if (pChange) { + pChange->acpStart = selectionForTSF.StartOffset(); + pChange->acpOldEnd = selectionForTSF.EndOffset(); + pChange->acpNewEnd = + selectionForTSF.StartOffset() + static_cast<LONG>(cch); + } + } else { + if (!IsReadWriteLocked()) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to " + "not locked (read-write)", this)); + return TS_E_NOLOCK; + } + + if (!pChange) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to " + "null pChange", this)); + return E_INVALIDARG; + } + + if (TS_IAS_NOQUERY != dwFlags && (!pacpStart || !pacpEnd)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to " + "null argument", this)); + return E_INVALIDARG; + } + + if (!InsertTextAtSelectionInternal(nsDependentSubstring(pchText, cch), + pChange)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to " + "InsertTextAtSelectionInternal() failure", this)); + return E_FAIL; + } + + if (TS_IAS_NOQUERY != dwFlags) { + *pacpStart = pChange->acpStart; + *pacpEnd = pChange->acpNewEnd; + } + } + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::InsertTextAtSelection() succeeded: " + "*pacpStart=%ld, *pacpEnd=%ld, " + "*pChange={ acpStart=%ld, acpOldEnd=%ld, acpNewEnd=%ld })", + this, pacpStart ? *pacpStart : 0, pacpEnd ? *pacpEnd : 0, + pChange ? pChange->acpStart: 0, pChange ? pChange->acpOldEnd : 0, + pChange ? pChange->acpNewEnd : 0)); + return S_OK; +} + +bool +TSFTextStore::InsertTextAtSelectionInternal(const nsAString& aInsertStr, + TS_TEXTCHANGE* aTextChange) +{ + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::InsertTextAtSelectionInternal(" + "aInsertStr=\"%s\", aTextChange=0x%p), IsComposing=%s", + this, GetEscapedUTF8String(aInsertStr).get(), aTextChange, + GetBoolName(mComposition.IsComposing()))); + + Content& contentForTSF = ContentForTSFRef(); + if (!contentForTSF.IsInitialized()) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::InsertTextAtSelectionInternal() failed " + "due to ContentForTSFRef() failure()", this)); + return false; + } + + TS_SELECTION_ACP oldSelection = contentForTSF.Selection().ACP(); + if (!mComposition.IsComposing()) { + // Use a temporary composition to contain the text + PendingAction* compositionStart = mPendingActions.AppendElement(); + compositionStart->mType = PendingAction::COMPOSITION_START; + compositionStart->mSelectionStart = oldSelection.acpStart; + compositionStart->mSelectionLength = + oldSelection.acpEnd - oldSelection.acpStart; + compositionStart->mAdjustSelection = false; + + PendingAction* compositionEnd = mPendingActions.AppendElement(); + compositionEnd->mType = PendingAction::COMPOSITION_END; + compositionEnd->mData = aInsertStr; + + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::InsertTextAtSelectionInternal() " + "appending pending compositionstart and compositionend... " + "PendingCompositionStart={ mSelectionStart=%d, " + "mSelectionLength=%d }, PendingCompositionEnd={ mData=\"%s\" " + "(Length()=%u) }", + this, compositionStart->mSelectionStart, + compositionStart->mSelectionLength, + GetEscapedUTF8String(compositionEnd->mData).get(), + compositionEnd->mData.Length())); + } + + contentForTSF.ReplaceSelectedTextWith(aInsertStr); + + if (aTextChange) { + aTextChange->acpStart = oldSelection.acpStart; + aTextChange->acpOldEnd = oldSelection.acpEnd; + aTextChange->acpNewEnd = contentForTSF.Selection().EndOffset(); + } + + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::InsertTextAtSelectionInternal() " + "succeeded: mWidget=0x%p, mWidget->Destroyed()=%s, aTextChange={ " + "acpStart=%ld, acpOldEnd=%ld, acpNewEnd=%ld }", + this, mWidget.get(), + GetBoolName(mWidget ? mWidget->Destroyed() : true), + aTextChange ? aTextChange->acpStart : 0, + aTextChange ? aTextChange->acpOldEnd : 0, + aTextChange ? aTextChange->acpNewEnd : 0)); + return true; +} + +STDMETHODIMP +TSFTextStore::InsertEmbeddedAtSelection(DWORD dwFlags, + IDataObject* pDataObject, + LONG* pacpStart, + LONG* pacpEnd, + TS_TEXTCHANGE* pChange) +{ + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::InsertEmbeddedAtSelection() called " + "but not supported (E_NOTIMPL)", this)); + + // embedded objects are not supported + return E_NOTIMPL; +} + +HRESULT +TSFTextStore::RecordCompositionStartAction(ITfCompositionView* aComposition, + ITfRange* aRange, + bool aPreserveSelection) +{ + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::RecordCompositionStartAction(" + "aComposition=0x%p, aRange=0x%p, aPreserveSelection=%s), " + "mComposition.mView=0x%p", + this, aComposition, aRange, GetBoolName(aPreserveSelection), + mComposition.mView.get())); + + LONG start = 0, length = 0; + HRESULT hr = GetRangeExtent(aRange, &start, &length); + if (FAILED(hr)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::RecordCompositionStartAction() FAILED " + "due to GetRangeExtent() failure", this)); + return hr; + } + + return RecordCompositionStartAction(aComposition, start, length, + aPreserveSelection); +} + +HRESULT +TSFTextStore::RecordCompositionStartAction(ITfCompositionView* aComposition, + LONG aStart, + LONG aLength, + bool aPreserveSelection) +{ + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::RecordCompositionStartAction(" + "aComposition=0x%p, aStart=%d, aLength=%d, aPreserveSelection=%s), " + "mComposition.mView=0x%p", + this, aComposition, aStart, aLength, GetBoolName(aPreserveSelection), + mComposition.mView.get())); + + Content& contentForTSF = ContentForTSFRef(); + if (!contentForTSF.IsInitialized()) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::RecordCompositionStartAction() FAILED " + "due to ContentForTSFRef() failure", this)); + return E_FAIL; + } + + CompleteLastActionIfStillIncomplete(); + + // TIP may have inserted text at selection before calling + // OnStartComposition(). In this case, we've already created a pair of + // pending compositionstart and pending compositionend. If the pending + // compositionstart occurred same range as this composition, it was the + // start of this composition. In such case, we should cancel the pending + // compositionend and start composition normally. + if (!aPreserveSelection && + WasTextInsertedWithoutCompositionAt(aStart, aLength)) { + const PendingAction& pendingCompositionEnd = mPendingActions.LastElement(); + const PendingAction& pendingCompositionStart = + mPendingActions[mPendingActions.Length() - 2]; + contentForTSF.RestoreCommittedComposition( + aComposition, pendingCompositionStart, pendingCompositionEnd); + mPendingActions.RemoveElementAt(mPendingActions.Length() - 1); + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::RecordCompositionStartAction() " + "succeeded: restoring the committed string as composing string, " + "mComposition={ mStart=%ld, mString.Length()=%ld, " + "mSelectionForTSF={ acpStart=%ld, acpEnd=%ld, style.ase=%s, " + "style.fInterimChar=%s } }", + this, mComposition.mStart, mComposition.mString.Length(), + mSelectionForTSF.StartOffset(), mSelectionForTSF.EndOffset(), + GetActiveSelEndName(mSelectionForTSF.ActiveSelEnd()), + GetBoolName(mSelectionForTSF.IsInterimChar()))); + return S_OK; + } + + PendingAction* action = mPendingActions.AppendElement(); + action->mType = PendingAction::COMPOSITION_START; + action->mSelectionStart = aStart; + action->mSelectionLength = aLength; + + Selection& selectionForTSF = SelectionForTSFRef(); + if (selectionForTSF.IsDirty()) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::RecordCompositionStartAction() FAILED " + "due to SelectionForTSFRef() failure", this)); + action->mAdjustSelection = true; + } else if (selectionForTSF.MinOffset() != aStart || + selectionForTSF.MaxOffset() != aStart + aLength) { + // If new composition range is different from current selection range, + // we need to set selection before dispatching compositionstart event. + action->mAdjustSelection = true; + } else { + // We shouldn't dispatch selection set event before dispatching + // compositionstart event because it may cause put caret different + // position in HTML editor since generated flat text content and offset in + // it are lossy data of HTML contents. + action->mAdjustSelection = false; + } + + contentForTSF.StartComposition(aComposition, *action, aPreserveSelection); + action->mData = mComposition.mString; + + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::RecordCompositionStartAction() succeeded: " + "mComposition={ mStart=%ld, mString.Length()=%ld, " + "mSelectionForTSF={ acpStart=%ld, acpEnd=%ld, style.ase=%s, " + "style.fInterimChar=%s } }", + this, mComposition.mStart, mComposition.mString.Length(), + mSelectionForTSF.StartOffset(), mSelectionForTSF.EndOffset(), + GetActiveSelEndName(mSelectionForTSF.ActiveSelEnd()), + GetBoolName(mSelectionForTSF.IsInterimChar()))); + return S_OK; +} + +HRESULT +TSFTextStore::RecordCompositionEndAction() +{ + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::RecordCompositionEndAction(), " + "mComposition={ mView=0x%p, mString=\"%s\" }", + this, mComposition.mView.get(), + GetEscapedUTF8String(mComposition.mString).get())); + + MOZ_ASSERT(mComposition.IsComposing()); + + CompleteLastActionIfStillIncomplete(); + PendingAction* action = mPendingActions.AppendElement(); + action->mType = PendingAction::COMPOSITION_END; + action->mData = mComposition.mString; + + Content& contentForTSF = ContentForTSFRef(); + if (!contentForTSF.IsInitialized()) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::RecordCompositionEndAction() FAILED due " + "to ContentForTSFRef() failure", this)); + return E_FAIL; + } + contentForTSF.EndComposition(*action); + + // If this composition was restart but the composition doesn't modify + // anything, we should remove the pending composition for preventing to + // dispatch redundant composition events. + for (size_t i = mPendingActions.Length(), j = 1; i > 0; --i, ++j) { + PendingAction& pendingAction = mPendingActions[i - 1]; + if (pendingAction.mType == PendingAction::COMPOSITION_START) { + if (pendingAction.mData != action->mData) { + break; + } + // When only setting selection is necessary, we should append it. + if (pendingAction.mAdjustSelection) { + PendingAction* setSelection = mPendingActions.AppendElement(); + setSelection->mType = PendingAction::SET_SELECTION; + setSelection->mSelectionStart = pendingAction.mSelectionStart; + setSelection->mSelectionLength = pendingAction.mSelectionLength; + setSelection->mSelectionReversed = false; + } + // Remove the redundant pending composition. + mPendingActions.RemoveElementsAt(i - 1, j); + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::RecordCompositionEndAction(), " + "succeeded, but the composition was canceled due to redundant", + this)); + return S_OK; + } + } + + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::RecordCompositionEndAction(), succeeded", + this)); + return S_OK; +} + +STDMETHODIMP +TSFTextStore::OnStartComposition(ITfCompositionView* pComposition, + BOOL* pfOk) +{ + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::OnStartComposition(pComposition=0x%p, " + "pfOk=0x%p), mComposition.mView=0x%p", + this, pComposition, pfOk, mComposition.mView.get())); + + AutoPendingActionAndContentFlusher flusher(this); + + *pfOk = FALSE; + + // Only one composition at a time + if (mComposition.IsComposing()) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::OnStartComposition() FAILED due to " + "there is another composition already (but returns S_OK)", this)); + return S_OK; + } + + RefPtr<ITfRange> range; + HRESULT hr = pComposition->GetRange(getter_AddRefs(range)); + if (FAILED(hr)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::OnStartComposition() FAILED due to " + "pComposition->GetRange() failure", this)); + return hr; + } + hr = RecordCompositionStartAction(pComposition, range, false); + if (FAILED(hr)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::OnStartComposition() FAILED due to " + "RecordCompositionStartAction() failure", this)); + return hr; + } + + *pfOk = TRUE; + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::OnStartComposition() succeeded", this)); + return S_OK; +} + +STDMETHODIMP +TSFTextStore::OnUpdateComposition(ITfCompositionView* pComposition, + ITfRange* pRangeNew) +{ + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::OnUpdateComposition(pComposition=0x%p, " + "pRangeNew=0x%p), mComposition.mView=0x%p", + this, pComposition, pRangeNew, mComposition.mView.get())); + + AutoPendingActionAndContentFlusher flusher(this); + + if (!mDocumentMgr || !mContext) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to " + "not ready for the composition", this)); + return E_UNEXPECTED; + } + if (!mComposition.IsComposing()) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to " + "no active composition", this)); + return E_UNEXPECTED; + } + if (mComposition.mView != pComposition) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to " + "different composition view specified", this)); + return E_UNEXPECTED; + } + + // pRangeNew is null when the update is not complete + if (!pRangeNew) { + PendingAction* action = LastOrNewPendingCompositionUpdate(); + action->mIncomplete = true; + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::OnUpdateComposition() succeeded but " + "not complete", this)); + return S_OK; + } + + HRESULT hr = RestartCompositionIfNecessary(pRangeNew); + if (FAILED(hr)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to " + "RestartCompositionIfNecessary() failure", this)); + return hr; + } + + hr = RecordCompositionUpdateAction(); + if (FAILED(hr)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to " + "RecordCompositionUpdateAction() failure", this)); + return hr; + } + + if (MOZ_LOG_TEST(sTextStoreLog, LogLevel::Info)) { + Selection& selectionForTSF = SelectionForTSFRef(); + if (selectionForTSF.IsDirty()) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to " + "SelectionForTSFRef() failure", this)); + return E_FAIL; + } + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::OnUpdateComposition() succeeded: " + "mComposition={ mStart=%ld, mString=\"%s\" }, " + "SelectionForTSFRef()={ acpStart=%ld, acpEnd=%ld, style.ase=%s }", + this, mComposition.mStart, + GetEscapedUTF8String(mComposition.mString).get(), + selectionForTSF.StartOffset(), selectionForTSF.EndOffset(), + GetActiveSelEndName(selectionForTSF.ActiveSelEnd()))); + } + return S_OK; +} + +STDMETHODIMP +TSFTextStore::OnEndComposition(ITfCompositionView* pComposition) +{ + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::OnEndComposition(pComposition=0x%p), " + "mComposition={ mView=0x%p, mString=\"%s\" }", + this, pComposition, mComposition.mView.get(), + GetEscapedUTF8String(mComposition.mString).get())); + + AutoPendingActionAndContentFlusher flusher(this); + + if (!mComposition.IsComposing()) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::OnEndComposition() FAILED due to " + "no active composition", this)); + return E_UNEXPECTED; + } + + if (mComposition.mView != pComposition) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::OnEndComposition() FAILED due to " + "different composition view specified", this)); + return E_UNEXPECTED; + } + + HRESULT hr = RecordCompositionEndAction(); + if (FAILED(hr)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::OnEndComposition() FAILED due to " + "RecordCompositionEndAction() failure", this)); + return hr; + } + + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::OnEndComposition(), succeeded", this)); + return S_OK; +} + +STDMETHODIMP +TSFTextStore::AdviseMouseSink(ITfRangeACP* range, + ITfMouseSink* pSink, + DWORD* pdwCookie) +{ + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::AdviseMouseSink(range=0x%p, pSink=0x%p, " + "pdwCookie=0x%p)", this, range, pSink, pdwCookie)); + + if (!pdwCookie) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::AdviseMouseSink() FAILED due to the " + "pdwCookie is null", this)); + return E_INVALIDARG; + } + // Initialize the result with invalid cookie for safety. + *pdwCookie = MouseTracker::kInvalidCookie; + + if (!range) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::AdviseMouseSink() FAILED due to the " + "range is null", this)); + return E_INVALIDARG; + } + if (!pSink) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::AdviseMouseSink() FAILED due to the " + "pSink is null", this)); + return E_INVALIDARG; + } + + // Looking for an unusing tracker. + MouseTracker* tracker = nullptr; + for (size_t i = 0; i < mMouseTrackers.Length(); i++) { + if (mMouseTrackers[i].IsUsing()) { + continue; + } + tracker = &mMouseTrackers[i]; + } + // If there is no unusing tracker, create new one. + // XXX Should we make limitation of the number of installs? + if (!tracker) { + tracker = mMouseTrackers.AppendElement(); + HRESULT hr = tracker->Init(this); + if (FAILED(hr)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::AdviseMouseSink() FAILED due to " + "failure of MouseTracker::Init()", this)); + return hr; + } + } + HRESULT hr = tracker->AdviseSink(this, range, pSink); + if (FAILED(hr)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::AdviseMouseSink() FAILED due to failure " + "of MouseTracker::Init()", this)); + return hr; + } + *pdwCookie = tracker->Cookie(); + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::AdviseMouseSink(), succeeded, " + "*pdwCookie=%d", this, *pdwCookie)); + return S_OK; +} + +STDMETHODIMP +TSFTextStore::UnadviseMouseSink(DWORD dwCookie) +{ + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::UnadviseMouseSink(dwCookie=%d)", + this, dwCookie)); + if (dwCookie == MouseTracker::kInvalidCookie) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::UnadviseMouseSink() FAILED due to " + "the cookie is invalid value", this)); + return E_INVALIDARG; + } + // The cookie value must be an index of mMouseTrackers. + // We can use this shortcut for now. + if (static_cast<size_t>(dwCookie) >= mMouseTrackers.Length()) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::UnadviseMouseSink() FAILED due to " + "the cookie is too large value", this)); + return E_INVALIDARG; + } + MouseTracker& tracker = mMouseTrackers[dwCookie]; + if (!tracker.IsUsing()) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::UnadviseMouseSink() FAILED due to " + "the found tracker uninstalled already", this)); + return E_INVALIDARG; + } + tracker.UnadviseSink(); + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::UnadviseMouseSink(), succeeded", this)); + return S_OK; +} + +// static +nsresult +TSFTextStore::OnFocusChange(bool aGotFocus, + nsWindowBase* aFocusedWidget, + const InputContext& aContext) +{ + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + (" TSFTextStore::OnFocusChange(aGotFocus=%s, " + "aFocusedWidget=0x%p, aContext={ mIMEState={ mEnabled=%s }, " + "mHTMLInputType=\"%s\" }), " + "sThreadMgr=0x%p, sEnabledTextStore=0x%p", + GetBoolName(aGotFocus), aFocusedWidget, + GetIMEEnabledName(aContext.mIMEState.mEnabled), + NS_ConvertUTF16toUTF8(aContext.mHTMLInputType).get(), + sThreadMgr.get(), sEnabledTextStore.get())); + + if (NS_WARN_IF(!IsInTSFMode())) { + return NS_ERROR_NOT_AVAILABLE; + } + + RefPtr<ITfDocumentMgr> prevFocusedDocumentMgr; + RefPtr<TSFTextStore> oldTextStore = sEnabledTextStore.forget(); + + // If currently sEnableTextStore has focus, notifies TSF of losing focus. + if (ThinksHavingFocus()) { + RefPtr<ITfThreadMgr> threadMgr = sThreadMgr; + DebugOnly<HRESULT> hr = + threadMgr->AssociateFocus( + oldTextStore->mWidget->GetWindowHandle(), + nullptr, getter_AddRefs(prevFocusedDocumentMgr)); + NS_ASSERTION(SUCCEEDED(hr), "Disassociating focus failed"); + NS_ASSERTION(prevFocusedDocumentMgr == oldTextStore->mDocumentMgr, + "different documentMgr has been associated with the window"); + } + + // If there is sEnabledTextStore, we don't use it in the new focused editor. + // Release it now. + if (oldTextStore) { + oldTextStore->Destroy(); + } + + if (NS_WARN_IF(!sThreadMgr)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + (" TSFTextStore::OnFocusChange() FAILED, due to " + "sThreadMgr being destroyed during calling " + "ITfThreadMgr::AssociateFocus()")); + return NS_ERROR_FAILURE; + } + if (NS_WARN_IF(sEnabledTextStore)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + (" TSFTextStore::OnFocusChange() FAILED, due to " + "nested event handling has created another focused TextStore during " + "calling ITfThreadMgr::AssociateFocus()")); + return NS_ERROR_FAILURE; + } + + // If this is a notification of blur, move focus to the dummy document + // manager. + if (!aGotFocus || !aContext.mIMEState.IsEditable()) { + RefPtr<ITfThreadMgr> threadMgr = sThreadMgr; + RefPtr<ITfDocumentMgr> disabledDocumentMgr = sDisabledDocumentMgr; + HRESULT hr = threadMgr->SetFocus(disabledDocumentMgr); + if (NS_WARN_IF(FAILED(hr))) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + (" TSFTextStore::OnFocusChange() FAILED due to " + "ITfThreadMgr::SetFocus() failure")); + return NS_ERROR_FAILURE; + } + return NS_OK; + } + + // If an editor is getting focus, create new TextStore and set focus. + if (NS_WARN_IF(!CreateAndSetFocus(aFocusedWidget, aContext))) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + (" TSFTextStore::OnFocusChange() FAILED due to " + "ITfThreadMgr::CreateAndSetFocus() failure")); + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +// static +void +TSFTextStore::EnsureToDestroyAndReleaseEnabledTextStoreIf( + RefPtr<TSFTextStore>& aTextStore) +{ + aTextStore->Destroy(); + if (sEnabledTextStore == aTextStore) { + sEnabledTextStore = nullptr; + } + aTextStore = nullptr; +} + +// static +bool +TSFTextStore::CreateAndSetFocus(nsWindowBase* aFocusedWidget, + const InputContext& aContext) +{ + // TSF might do something which causes that we need to access static methods + // of TSFTextStore. At that time, sEnabledTextStore may be necessary. + // So, we should set sEnabledTextStore directly. + RefPtr<TSFTextStore> textStore = new TSFTextStore(); + sEnabledTextStore = textStore; + if (NS_WARN_IF(!textStore->Init(aFocusedWidget, aContext))) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + (" TSFTextStore::CreateAndSetFocus() FAILED due to " + "TSFTextStore::Init() failure")); + EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore); + return false; + } + RefPtr<ITfDocumentMgr> newDocMgr = textStore->mDocumentMgr; + if (NS_WARN_IF(!newDocMgr)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + (" TSFTextStore::CreateAndSetFocus() FAILED due to " + "invalid TSFTextStore::mDocumentMgr")); + EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore); + return false; + } + if (aContext.mIMEState.mEnabled == IMEState::PASSWORD) { + MarkContextAsKeyboardDisabled(textStore->mContext); + RefPtr<ITfContext> topContext; + newDocMgr->GetTop(getter_AddRefs(topContext)); + if (topContext && topContext != textStore->mContext) { + MarkContextAsKeyboardDisabled(topContext); + } + } + + HRESULT hr; + RefPtr<ITfThreadMgr> threadMgr = sThreadMgr; + { + // Windows 10's softwware keyboard requires that SetSelection must be + // always successful into SetFocus. If returning error, it might crash + // into TextInputFramework.dll. + AutoSetTemporarySelection setSelection(textStore->SelectionForTSFRef()); + + hr = threadMgr->SetFocus(newDocMgr); + } + + if (NS_WARN_IF(FAILED(hr))) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + (" TSFTextStore::CreateAndSetFocus() FAILED due to " + "ITfTheadMgr::SetFocus() failure")); + EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore); + return false; + } + if (NS_WARN_IF(!sThreadMgr)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + (" TSFTextStore::CreateAndSetFocus() FAILED due to " + "sThreadMgr being destroyed during calling " + "ITfTheadMgr::SetFocus()")); + EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore); + return false; + } + if (NS_WARN_IF(sEnabledTextStore != textStore)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + (" TSFTextStore::CreateAndSetFocus() FAILED due to " + "creating TextStore has lost focus during calling " + "ITfThreadMgr::SetFocus()")); + EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore); + return false; + } + + // Use AssociateFocus() for ensuring that any native focus event + // never steal focus from our documentMgr. + RefPtr<ITfDocumentMgr> prevFocusedDocumentMgr; + hr = threadMgr->AssociateFocus(aFocusedWidget->GetWindowHandle(), newDocMgr, + getter_AddRefs(prevFocusedDocumentMgr)); + if (NS_WARN_IF(FAILED(hr))) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + (" TSFTextStore::CreateAndSetFocus() FAILED due to " + "ITfTheadMgr::AssociateFocus() failure")); + EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore); + return false; + } + if (NS_WARN_IF(!sThreadMgr)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + (" TSFTextStore::CreateAndSetFocus() FAILED due to " + "sThreadMgr being destroyed during calling " + "ITfTheadMgr::AssociateFocus()")); + EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore); + return false; + } + if (NS_WARN_IF(sEnabledTextStore != textStore)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + (" TSFTextStore::CreateAndSetFocus() FAILED due to " + "creating TextStore has lost focus during calling " + "ITfTheadMgr::AssociateFocus()")); + EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore); + return false; + } + + if (textStore->mSink) { + MOZ_LOG(sTextStoreLog, LogLevel::Info, + (" TSFTextStore::CreateAndSetFocus(), calling " + "ITextStoreACPSink::OnLayoutChange(TS_LC_CREATE) for 0x%p...", + textStore.get())); + RefPtr<ITextStoreACPSink> sink = textStore->mSink; + sink->OnLayoutChange(TS_LC_CREATE, TEXTSTORE_DEFAULT_VIEW); + if (NS_WARN_IF(sEnabledTextStore != textStore)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + (" TSFTextStore::CreateAndSetFocus() FAILED due to " + "creating TextStore has lost focus during calling " + "ITextStoreACPSink::OnLayoutChange(TS_LC_CREATE)")); + EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore); + return false; + } + } + return true; +} + +// static +nsIMEUpdatePreference +TSFTextStore::GetIMEUpdatePreference() +{ + if (sThreadMgr && sEnabledTextStore && sEnabledTextStore->mDocumentMgr) { + RefPtr<ITfDocumentMgr> docMgr; + sThreadMgr->GetFocus(getter_AddRefs(docMgr)); + if (docMgr == sEnabledTextStore->mDocumentMgr) { + return nsIMEUpdatePreference( + nsIMEUpdatePreference::NOTIFY_TEXT_CHANGE | + nsIMEUpdatePreference::NOTIFY_POSITION_CHANGE | + nsIMEUpdatePreference::NOTIFY_MOUSE_BUTTON_EVENT_ON_CHAR | + nsIMEUpdatePreference::NOTIFY_DURING_DEACTIVE); + } + } + return nsIMEUpdatePreference(); +} + +nsresult +TSFTextStore::OnTextChangeInternal(const IMENotification& aIMENotification) +{ + const TextChangeDataBase& textChangeData = aIMENotification.mTextChangeData; + + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::OnTextChangeInternal(aIMENotification={ " + "mMessage=0x%08X, mTextChangeData={ mStartOffset=%lu, " + "mRemovedEndOffset=%lu, mAddedEndOffset=%lu, " + "mCausedOnlyByComposition=%s, " + "mIncludingChangesDuringComposition=%s, " + "mIncludingChangesWithoutComposition=%s }), " + "mDestroyed=%s, mSink=0x%p, mSinkMask=%s, " + "mComposition.IsComposing()=%s", + this, aIMENotification.mMessage, + textChangeData.mStartOffset, + textChangeData.mRemovedEndOffset, + textChangeData.mAddedEndOffset, + GetBoolName(textChangeData.mCausedOnlyByComposition), + GetBoolName(textChangeData.mIncludingChangesDuringComposition), + GetBoolName(textChangeData.mIncludingChangesWithoutComposition), + GetBoolName(mDestroyed), + mSink.get(), + GetSinkMaskNameStr(mSinkMask).get(), + GetBoolName(mComposition.IsComposing()))); + + if (mDestroyed) { + // If this instance is already destroyed, we shouldn't notify TSF of any + // changes. + return NS_OK; + } + + mDeferNotifyingTSF = false; + + // Different from selection change, we don't modify anything with text + // change data. Therefore, if neither TSF not TIP wants text change + // notifications, we don't need to store the changes. + if (!mSink || !(mSinkMask & TS_AS_TEXT_CHANGE)) { + return NS_OK; + } + + // Merge any text change data even if it's caused by composition. + mPendingTextChangeData.MergeWith(textChangeData); + + MaybeFlushPendingNotifications(); + + return NS_OK; +} + +void +TSFTextStore::NotifyTSFOfTextChange() +{ + MOZ_ASSERT(!mDestroyed); + MOZ_ASSERT(!IsReadLocked()); + MOZ_ASSERT(!mComposition.IsComposing()); + MOZ_ASSERT(mPendingTextChangeData.IsValid()); + + // If the text changes are caused only by composition, we don't need to + // notify TSF of the text changes. + if (mPendingTextChangeData.mCausedOnlyByComposition) { + mPendingTextChangeData.Clear(); + return; + } + + // First, forget cached selection. + mSelectionForTSF.MarkDirty(); + + // For making it safer, we should check if there is a valid sink to receive + // text change notification. + if (NS_WARN_IF(!mSink) || NS_WARN_IF(!(mSinkMask & TS_AS_TEXT_CHANGE))) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::NotifyTSFOfTextChange() FAILED due to " + "mSink is not ready to call ITextStoreACPSink::OnTextChange()...", + this)); + mPendingTextChangeData.Clear(); + return; + } + + if (NS_WARN_IF(!mPendingTextChangeData.IsInInt32Range())) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::NotifyTSFOfTextChange() FAILED due to " + "offset is too big for calling " + "ITextStoreACPSink::OnTextChange()...", + this)); + mPendingTextChangeData.Clear(); + return; + } + + TS_TEXTCHANGE textChange; + textChange.acpStart = + static_cast<LONG>(mPendingTextChangeData.mStartOffset); + textChange.acpOldEnd = + static_cast<LONG>(mPendingTextChangeData.mRemovedEndOffset); + textChange.acpNewEnd = + static_cast<LONG>(mPendingTextChangeData.mAddedEndOffset); + mPendingTextChangeData.Clear(); + + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::NotifyTSFOfTextChange(), calling " + "ITextStoreACPSink::OnTextChange(0, { acpStart=%ld, acpOldEnd=%ld, " + "acpNewEnd=%ld })...", this, textChange.acpStart, + textChange.acpOldEnd, textChange.acpNewEnd)); + RefPtr<ITextStoreACPSink> sink = mSink; + sink->OnTextChange(0, &textChange); +} + +nsresult +TSFTextStore::OnSelectionChangeInternal(const IMENotification& aIMENotification) +{ + const SelectionChangeDataBase& selectionChangeData = + aIMENotification.mSelectionChangeData; + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::OnSelectionChangeInternal(" + "aIMENotification={ mSelectionChangeData={ mOffset=%lu, " + "Length()=%lu, mReversed=%s, mWritingMode=%s, " + "mCausedByComposition=%s, mCausedBySelectionEvent=%s, " + "mOccurredDuringComposition=%s } }), mDestroyed=%s, " + "mSink=0x%p, mSinkMask=%s, mIsRecordingActionsWithoutLock=%s, " + "mComposition.IsComposing()=%s", + this, selectionChangeData.mOffset, selectionChangeData.Length(), + GetBoolName(selectionChangeData.mReversed), + GetWritingModeName(selectionChangeData.GetWritingMode()).get(), + GetBoolName(selectionChangeData.mCausedByComposition), + GetBoolName(selectionChangeData.mCausedBySelectionEvent), + GetBoolName(selectionChangeData.mOccurredDuringComposition), + GetBoolName(mDestroyed), + mSink.get(), GetSinkMaskNameStr(mSinkMask).get(), + GetBoolName(mIsRecordingActionsWithoutLock), + GetBoolName(mComposition.IsComposing()))); + + if (mDestroyed) { + // If this instance is already destroyed, we shouldn't notify TSF of any + // changes. + return NS_OK; + } + + mDeferNotifyingTSF = false; + + // Assign the new selection change data to the pending selection change data + // because only the latest selection data is necessary. + // Note that this is necessary to update mSelectionForTSF. Therefore, even if + // neither TSF nor TIP wants selection change notifications, we need to + // store the selection information. + mPendingSelectionChangeData.Assign(selectionChangeData); + + // Flush remaining pending notifications here if it's possible. + MaybeFlushPendingNotifications(); + + return NS_OK; +} + +void +TSFTextStore::NotifyTSFOfSelectionChange() +{ + MOZ_ASSERT(!mDestroyed); + MOZ_ASSERT(!IsReadLocked()); + MOZ_ASSERT(!mComposition.IsComposing()); + MOZ_ASSERT(mPendingSelectionChangeData.IsValid()); + + // If selection range isn't actually changed, we don't need to notify TSF + // of this selection change. + if (!mSelectionForTSF.SetSelection( + mPendingSelectionChangeData.mOffset, + mPendingSelectionChangeData.Length(), + mPendingSelectionChangeData.mReversed, + mPendingSelectionChangeData.GetWritingMode())) { + mPendingSelectionChangeData.Clear(); + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::NotifyTSFOfSelectionChange(), " + "selection isn't actually changed.", this)); + return; + } + + mPendingSelectionChangeData.Clear(); + + if (!mSink || !(mSinkMask & TS_AS_SEL_CHANGE)) { + return; + } + + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::NotifyTSFOfSelectionChange(), calling " + "ITextStoreACPSink::OnSelectionChange()...", this)); + RefPtr<ITextStoreACPSink> sink = mSink; + sink->OnSelectionChange(); +} + +nsresult +TSFTextStore::OnLayoutChangeInternal() +{ + if (mDestroyed) { + // If this instance is already destroyed, we shouldn't notify TSF of any + // changes. + return NS_OK; + } + + NS_ENSURE_TRUE(mContext, NS_ERROR_FAILURE); + NS_ENSURE_TRUE(mSink, NS_ERROR_FAILURE); + + mDeferNotifyingTSF = false; + + nsresult rv = NS_OK; + + // We need to notify TSF of layout change even if the document is locked. + // So, don't use MaybeFlushPendingNotifications() for flushing pending + // layout change. + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::OnLayoutChangeInternal(), calling " + "NotifyTSFOfLayoutChange()...", this)); + if (NS_WARN_IF(!NotifyTSFOfLayoutChange())) { + rv = NS_ERROR_FAILURE; + } + + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::OnLayoutChangeInternal(), calling " + "MaybeFlushPendingNotifications()...", this)); + MaybeFlushPendingNotifications(); + + return rv; +} + +bool +TSFTextStore::NotifyTSFOfLayoutChange() +{ + MOZ_ASSERT(!mDestroyed); + + // If we're waiting a query of layout information from TIP, it means that + // we've returned TS_E_NOLAYOUT error. + bool returnedNoLayoutError = + mHasReturnedNoLayoutError || mWaitingQueryLayout; + + // If we returned TS_E_NOLAYOUT, TIP should query the computed layout again. + mWaitingQueryLayout = returnedNoLayoutError; + + // For avoiding to call this method again at unlocking the document during + // calls of OnLayoutChange(), reset mHasReturnedNoLayoutError. + mHasReturnedNoLayoutError = false; + + // Now, layout has been computed. We should notify mContentForTSF for + // making GetTextExt() and GetACPFromPoint() not return TS_E_NOLAYOUT. + if (mContentForTSF.IsInitialized()) { + mContentForTSF.OnLayoutChanged(); + } + + // Now, the caret position is different from ours. Destroy the native caret + // if there is. + MaybeDestroyNativeCaret(); + + // This method should return true if either way succeeds. + bool ret = true; + + if (mSink) { + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), " + "calling ITextStoreACPSink::OnLayoutChange()...", + this)); + RefPtr<ITextStoreACPSink> sink = mSink; + HRESULT hr = sink->OnLayoutChange(TS_LC_CHANGE, TEXTSTORE_DEFAULT_VIEW); + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), " + "called ITextStoreACPSink::OnLayoutChange()", + this)); + ret = SUCCEEDED(hr); + } + + // The layout change caused by composition string change should cause + // calling ITfContextOwnerServices::OnLayoutChange() too. + if (returnedNoLayoutError && mContext) { + RefPtr<ITfContextOwnerServices> service; + mContext->QueryInterface(IID_ITfContextOwnerServices, + getter_AddRefs(service)); + if (service) { + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), " + "calling ITfContextOwnerServices::OnLayoutChange()...", + this)); + HRESULT hr = service->OnLayoutChange(); + ret = ret && SUCCEEDED(hr); + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), " + "called ITfContextOwnerServices::OnLayoutChange()", + this)); + } + } + + if (!mWidget || mWidget->Destroyed()) { + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), " + "the widget is destroyed during calling OnLayoutChange()", + this)); + return ret; + } + + if (mDestroyed) { + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), " + "the TSFTextStore instance is destroyed during calling " + "OnLayoutChange()", + this)); + return ret; + } + + // If we returned TS_E_NOLAYOUT again, we need another call of + // OnLayoutChange() later. So, let's wait a query from TIP. + if (mHasReturnedNoLayoutError) { + mWaitingQueryLayout = true; + } + + if (!mWaitingQueryLayout) { + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), " + "succeeded notifying TIP of our layout change", + this)); + return ret; + } + + // If we believe that TIP needs to retry to retrieve our layout information + // later, we should call it with ::PostMessage() hack. + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), " + "posing MOZ_WM_NOTIY_TSF_OF_LAYOUT_CHANGE for calling " + "OnLayoutChange() again...", this)); + ::PostMessage(mWidget->GetWindowHandle(), + MOZ_WM_NOTIY_TSF_OF_LAYOUT_CHANGE, + reinterpret_cast<WPARAM>(this), 0); + + return true; +} + +void +TSFTextStore::NotifyTSFOfLayoutChangeAgain() +{ + // Don't notify TSF of layout change after destroyed. + if (mDestroyed) { + mWaitingQueryLayout = false; + return; + } + + // Before preforming this method, TIP has accessed our layout information by + // itself. In such case, we don't need to call OnLayoutChange() anymore. + if (!mWaitingQueryLayout) { + return; + } + + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::NotifyTSFOfLayoutChangeAgain(), " + "calling NotifyTSFOfLayoutChange()...", this)); + NotifyTSFOfLayoutChange(); + + // If TIP didn't retrieved our layout information during a call of + // NotifyTSFOfLayoutChange(), it means that the TIP already gave up to + // retry to retrieve layout information or doesn't necessary it anymore. + // But don't forget that the call may have caused returning TS_E_NOLAYOUT + // error again. In such case we still need to call OnLayoutChange() later. + if (!mHasReturnedNoLayoutError && mWaitingQueryLayout) { + mWaitingQueryLayout = false; + MOZ_LOG(sTextStoreLog, LogLevel::Warning, + ("0x%p TSFTextStore::NotifyTSFOfLayoutChangeAgain(), " + "called NotifyTSFOfLayoutChange() but TIP didn't retry to " + "retrieve the layout information", this)); + } else { + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::NotifyTSFOfLayoutChangeAgain(), " + "called NotifyTSFOfLayoutChange()", this)); + } +} + +nsresult +TSFTextStore::OnUpdateCompositionInternal() +{ + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::OnUpdateCompositionInternal(), " + "mDestroyed=%s, mDeferNotifyingTSF=%s", + this, GetBoolName(mDestroyed), GetBoolName(mDeferNotifyingTSF))); + + // There are nothing to do after destroyed. + if (mDestroyed) { + return NS_OK; + } + + // If composition is completely finished both in TSF/TIP and the focused + // editor which may be in a remote process, we can clear the cache until + // starting next composition. + if (!mComposition.IsComposing() && !IsComposingInContent()) { + mDeferClearingContentForTSF = false; + } + mDeferNotifyingTSF = false; + MaybeFlushPendingNotifications(); + return NS_OK; +} + +nsresult +TSFTextStore::OnMouseButtonEventInternal( + const IMENotification& aIMENotification) +{ + if (mDestroyed) { + // If this instance is already destroyed, we shouldn't notify TSF of any + // events. + return NS_OK; + } + + if (mMouseTrackers.IsEmpty()) { + return NS_OK; + } + + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::OnMouseButtonEventInternal(" + "aIMENotification={ mEventMessage=%s, mOffset=%u, mCursorPos={ " + "mX=%d, mY=%d }, mCharRect={ mX=%d, mY=%d, mWidth=%d, mHeight=%d }, " + "mButton=%s, mButtons=%s, mModifiers=%s })", + this, ToChar(aIMENotification.mMouseButtonEventData.mEventMessage), + aIMENotification.mMouseButtonEventData.mOffset, + aIMENotification.mMouseButtonEventData.mCursorPos.mX, + aIMENotification.mMouseButtonEventData.mCursorPos.mY, + aIMENotification.mMouseButtonEventData.mCharRect.mX, + aIMENotification.mMouseButtonEventData.mCharRect.mY, + aIMENotification.mMouseButtonEventData.mCharRect.mWidth, + aIMENotification.mMouseButtonEventData.mCharRect.mHeight, + GetMouseButtonName(aIMENotification.mMouseButtonEventData.mButton), + GetMouseButtonsName( + aIMENotification.mMouseButtonEventData.mButtons).get(), + GetModifiersName( + aIMENotification.mMouseButtonEventData.mModifiers).get())); + + uint32_t offset = aIMENotification.mMouseButtonEventData.mOffset; + nsIntRect charRect = + aIMENotification.mMouseButtonEventData.mCharRect.AsIntRect(); + nsIntPoint cursorPos = + aIMENotification.mMouseButtonEventData.mCursorPos.AsIntPoint(); + ULONG quadrant = 1; + if (charRect.width > 0) { + int32_t cursorXInChar = cursorPos.x - charRect.x; + quadrant = cursorXInChar * 4 / charRect.width; + quadrant = (quadrant + 2) % 4; + } + ULONG edge = quadrant < 2 ? offset + 1 : offset; + DWORD buttonStatus = 0; + bool isMouseUp = + aIMENotification.mMouseButtonEventData.mEventMessage == eMouseUp; + if (!isMouseUp) { + switch (aIMENotification.mMouseButtonEventData.mButton) { + case WidgetMouseEventBase::eLeftButton: + buttonStatus = MK_LBUTTON; + break; + case WidgetMouseEventBase::eMiddleButton: + buttonStatus = MK_MBUTTON; + break; + case WidgetMouseEventBase::eRightButton: + buttonStatus = MK_RBUTTON; + break; + } + } + if (aIMENotification.mMouseButtonEventData.mModifiers & MODIFIER_CONTROL) { + buttonStatus |= MK_CONTROL; + } + if (aIMENotification.mMouseButtonEventData.mModifiers & MODIFIER_SHIFT) { + buttonStatus |= MK_SHIFT; + } + for (size_t i = 0; i < mMouseTrackers.Length(); i++) { + MouseTracker& tracker = mMouseTrackers[i]; + if (!tracker.IsUsing() || !tracker.InRange(offset)) { + continue; + } + if (tracker.OnMouseButtonEvent(edge - tracker.RangeStart(), + quadrant, buttonStatus)) { + return NS_SUCCESS_EVENT_CONSUMED; + } + } + return NS_OK; +} + +void +TSFTextStore::CreateNativeCaret() +{ + MaybeDestroyNativeCaret(); + + // Don't create native caret after destroyed. + if (mDestroyed) { + return; + } + + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::CreateNativeCaret(), " + "mComposition.IsComposing()=%s", + this, GetBoolName(mComposition.IsComposing()))); + + Selection& selectionForTSF = SelectionForTSFRef(); + if (selectionForTSF.IsDirty()) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::CreateNativeCaret() FAILED due to " + "SelectionForTSFRef() failure", this)); + return; + } + + WidgetQueryContentEvent queryCaretRect(true, eQueryCaretRect, mWidget); + mWidget->InitEvent(queryCaretRect); + + WidgetQueryContentEvent::Options options; + // XXX If this is called without composition and the selection isn't + // collapsed, is it OK? + int64_t caretOffset = selectionForTSF.MaxOffset(); + if (mComposition.IsComposing()) { + // If there is a composition, use insertion point relative query for + // deciding caret position because composition might be at different + // position where TSFTextStore believes it at. + options.mRelativeToInsertionPoint = true; + caretOffset -= mComposition.mStart; + } else if (!CanAccessActualContentDirectly()) { + // If TSF/TIP cannot access actual content directly, there may be pending + // text and/or selection changes which have not been notified TSF yet. + // Therefore, we should use relative to insertion point query since + // TSF/TIP computes the offset from the cached selection. + options.mRelativeToInsertionPoint = true; + caretOffset -= mSelectionForTSF.StartOffset(); + } + queryCaretRect.InitForQueryCaretRect(caretOffset, options); + + DispatchEvent(queryCaretRect); + if (NS_WARN_IF(!queryCaretRect.mSucceeded)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::CreateNativeCaret() FAILED due to " + "eQueryCaretRect failure (offset=%d)", this, caretOffset)); + return; + } + + LayoutDeviceIntRect& caretRect = queryCaretRect.mReply.mRect; + mNativeCaretIsCreated = ::CreateCaret(mWidget->GetWindowHandle(), nullptr, + caretRect.width, caretRect.height); + if (!mNativeCaretIsCreated) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::CreateNativeCaret() FAILED due to " + "CreateCaret() failure", this)); + return; + } + + nsWindow* window = static_cast<nsWindow*>(mWidget.get()); + nsWindow* toplevelWindow = window->GetTopLevelWindow(false); + if (!toplevelWindow) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::CreateNativeCaret() FAILED due to " + "no top level window", this)); + return; + } + + if (toplevelWindow != window) { + caretRect.MoveBy(toplevelWindow->WidgetToScreenOffset()); + caretRect.MoveBy(-window->WidgetToScreenOffset()); + } + + ::SetCaretPos(caretRect.x, caretRect.y); +} + +void +TSFTextStore::MaybeDestroyNativeCaret() +{ + if (!mNativeCaretIsCreated) { + return; + } + + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::MaybeDestroyNativeCaret(), " + "destroying native caret", this)); + + ::DestroyCaret(); + mNativeCaretIsCreated = false; +} + +void +TSFTextStore::CommitCompositionInternal(bool aDiscard) +{ + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::CommitCompositionInternal(aDiscard=%s), " + "mSink=0x%p, mContext=0x%p, mComposition.mView=0x%p, " + "mComposition.mString=\"%s\"", + this, GetBoolName(aDiscard), mSink.get(), mContext.get(), + mComposition.mView.get(), + GetEscapedUTF8String(mComposition.mString).get())); + + // If the document is locked, TSF will fail to commit composition since + // TSF needs another document lock. So, let's put off the request. + // Note that TextComposition will commit composition in the focused editor + // with the latest composition string for web apps and waits asynchronous + // committing messages. Therefore, we can and need to perform this + // asynchronously. + if (IsReadLocked()) { + if (mDeferCommittingComposition || mDeferCancellingComposition) { + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::CommitCompositionInternal(), " + "does nothing because already called and waiting unlock...", this)); + return; + } + if (aDiscard) { + mDeferCancellingComposition = true; + } else { + mDeferCommittingComposition = true; + } + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::CommitCompositionInternal(), " + "putting off to request to %s composition after unlocking the document", + this, aDiscard ? "cancel" : "commit")); + return; + } + + if (mComposition.IsComposing() && aDiscard) { + LONG endOffset = mComposition.EndOffset(); + mComposition.mString.Truncate(0); + // Note that don't notify TSF of text change after this is destroyed. + if (mSink && !mDestroyed) { + TS_TEXTCHANGE textChange; + textChange.acpStart = mComposition.mStart; + textChange.acpOldEnd = endOffset; + textChange.acpNewEnd = mComposition.mStart; + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::CommitCompositionInternal(), calling" + "mSink->OnTextChange(0, { acpStart=%ld, acpOldEnd=%ld, " + "acpNewEnd=%ld })...", this, textChange.acpStart, + textChange.acpOldEnd, textChange.acpNewEnd)); + RefPtr<ITextStoreACPSink> sink = mSink; + sink->OnTextChange(0, &textChange); + } + } + // Terminate two contexts, the base context (mContext) and the top + // if the top context is not the same as the base context + RefPtr<ITfContext> context = mContext; + do { + if (context) { + RefPtr<ITfContextOwnerCompositionServices> services; + context->QueryInterface(IID_ITfContextOwnerCompositionServices, + getter_AddRefs(services)); + if (services) { + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::CommitCompositionInternal(), " + "requesting TerminateComposition() for the context 0x%p...", + this, context.get())); + services->TerminateComposition(nullptr); + } + } + if (context != mContext) + break; + if (mDocumentMgr) + mDocumentMgr->GetTop(getter_AddRefs(context)); + } while (context != mContext); +} + +static +bool +GetCompartment(IUnknown* pUnk, + const GUID& aID, + ITfCompartment** aCompartment) +{ + if (!pUnk) return false; + + RefPtr<ITfCompartmentMgr> compMgr; + pUnk->QueryInterface(IID_ITfCompartmentMgr, getter_AddRefs(compMgr)); + if (!compMgr) return false; + + return SUCCEEDED(compMgr->GetCompartment(aID, aCompartment)) && + (*aCompartment) != nullptr; +} + +// static +void +TSFTextStore::SetIMEOpenState(bool aState) +{ + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("TSFTextStore::SetIMEOpenState(aState=%s)", + GetBoolName(aState))); + + RefPtr<ITfCompartment> comp; + if (!GetCompartment(sThreadMgr, + GUID_COMPARTMENT_KEYBOARD_OPENCLOSE, + getter_AddRefs(comp))) { + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + (" TSFTextStore::SetIMEOpenState() FAILED due to" + "no compartment available")); + return; + } + + VARIANT variant; + variant.vt = VT_I4; + variant.lVal = aState; + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + (" TSFTextStore::SetIMEOpenState(), setting " + "0x%04X to GUID_COMPARTMENT_KEYBOARD_OPENCLOSE...", + variant.lVal)); + comp->SetValue(sClientId, &variant); +} + +// static +bool +TSFTextStore::GetIMEOpenState() +{ + RefPtr<ITfCompartment> comp; + if (!GetCompartment(sThreadMgr, + GUID_COMPARTMENT_KEYBOARD_OPENCLOSE, + getter_AddRefs(comp))) + return false; + + VARIANT variant; + ::VariantInit(&variant); + if (SUCCEEDED(comp->GetValue(&variant)) && variant.vt == VT_I4) + return variant.lVal != 0; + + ::VariantClear(&variant); // clear up in case variant.vt != VT_I4 + return false; +} + +// static +void +TSFTextStore::SetInputContext(nsWindowBase* aWidget, + const InputContext& aContext, + const InputContextAction& aAction) +{ + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("TSFTextStore::SetInputContext(aWidget=%p, " + "aContext.mIMEState.mEnabled=%s, aAction.mFocusChange=%s), " + "sEnabledTextStore=0x%p, ThinksHavingFocus()=%s", + aWidget, GetIMEEnabledName(aContext.mIMEState.mEnabled), + GetFocusChangeName(aAction.mFocusChange), sEnabledTextStore.get(), + GetBoolName(ThinksHavingFocus()))); + + NS_ENSURE_TRUE_VOID(IsInTSFMode()); + + if (aAction.mFocusChange != InputContextAction::FOCUS_NOT_CHANGED) { + if (sEnabledTextStore) { + RefPtr<TSFTextStore> textStore(sEnabledTextStore); + textStore->SetInputScope(aContext.mHTMLInputType, + aContext.mHTMLInputInputmode); + } + return; + } + + // If focus isn't actually changed but the enabled state is changed, + // emulate the focus move. + if (!ThinksHavingFocus() && aContext.mIMEState.IsEditable()) { + OnFocusChange(true, aWidget, aContext); + } else if (ThinksHavingFocus() && !aContext.mIMEState.IsEditable()) { + OnFocusChange(false, aWidget, aContext); + } +} + +// static +void +TSFTextStore::MarkContextAsKeyboardDisabled(ITfContext* aContext) +{ + VARIANT variant_int4_value1; + variant_int4_value1.vt = VT_I4; + variant_int4_value1.lVal = 1; + + RefPtr<ITfCompartment> comp; + if (!GetCompartment(aContext, + GUID_COMPARTMENT_KEYBOARD_DISABLED, + getter_AddRefs(comp))) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("TSFTextStore::MarkContextAsKeyboardDisabled() failed" + "aContext=0x%p...", aContext)); + return; + } + + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("TSFTextStore::MarkContextAsKeyboardDisabled(), setting " + "to disable context 0x%p...", + aContext)); + comp->SetValue(sClientId, &variant_int4_value1); +} + +// static +void +TSFTextStore::MarkContextAsEmpty(ITfContext* aContext) +{ + VARIANT variant_int4_value1; + variant_int4_value1.vt = VT_I4; + variant_int4_value1.lVal = 1; + + RefPtr<ITfCompartment> comp; + if (!GetCompartment(aContext, + GUID_COMPARTMENT_EMPTYCONTEXT, + getter_AddRefs(comp))) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("TSFTextStore::MarkContextAsEmpty() failed" + "aContext=0x%p...", aContext)); + return; + } + + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("TSFTextStore::MarkContextAsEmpty(), setting " + "to mark empty context 0x%p...", aContext)); + comp->SetValue(sClientId, &variant_int4_value1); +} + +// static +void +TSFTextStore::Initialize() +{ + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("TSFTextStore::Initialize() is called...")); + + if (sThreadMgr) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + (" TSFTextStore::Initialize() FAILED due to already initialized")); + return; + } + + bool enableTsf = + IsVistaOrLater() && Preferences::GetBool(kPrefNameEnableTSF, false); + MOZ_LOG(sTextStoreLog, LogLevel::Info, + (" TSFTextStore::Initialize(), TSF is %s", + enableTsf ? "enabled" : "disabled")); + if (!enableTsf) { + return; + } + + // XXX MSDN documents that ITfInputProcessorProfiles is available only on + // desktop apps. However, there is no known way to obtain + // ITfInputProcessorProfileMgr instance without ITfInputProcessorProfiles + // instance. + RefPtr<ITfInputProcessorProfiles> inputProcessorProfiles; + HRESULT hr = + ::CoCreateInstance(CLSID_TF_InputProcessorProfiles, nullptr, + CLSCTX_INPROC_SERVER, + IID_ITfInputProcessorProfiles, + getter_AddRefs(inputProcessorProfiles)); + if (FAILED(hr) || !inputProcessorProfiles) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + (" TSFTextStore::Initialize() FAILED to create input processor " + "profiles, hr=0x%08X", hr)); + return; + } + + RefPtr<ITfThreadMgr> threadMgr; + hr = ::CoCreateInstance(CLSID_TF_ThreadMgr, nullptr, + CLSCTX_INPROC_SERVER, IID_ITfThreadMgr, + getter_AddRefs(threadMgr)); + if (FAILED(hr) || !threadMgr) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + (" TSFTextStore::Initialize() FAILED to " + "create the thread manager, hr=0x%08X", hr)); + return; + } + + RefPtr<ITfMessagePump> messagePump; + hr = threadMgr->QueryInterface(IID_ITfMessagePump, + getter_AddRefs(messagePump)); + if (FAILED(hr) || !messagePump) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + (" TSFTextStore::Initialize() FAILED to " + "QI message pump from the thread manager, hr=0x%08X", hr)); + return; + } + + RefPtr<ITfKeystrokeMgr> keystrokeMgr; + hr = threadMgr->QueryInterface(IID_ITfKeystrokeMgr, + getter_AddRefs(keystrokeMgr)); + if (FAILED(hr) || !keystrokeMgr) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + (" TSFTextStore::Initialize() FAILED to " + "QI keystroke manager from the thread manager, hr=0x%08X", hr)); + return; + } + + hr = threadMgr->Activate(&sClientId); + if (FAILED(hr)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + (" TSFTextStore::Initialize() FAILED to activate, hr=0x%08X", hr)); + return; + } + + RefPtr<ITfDisplayAttributeMgr> displayAttributeMgr; + hr = ::CoCreateInstance(CLSID_TF_DisplayAttributeMgr, nullptr, + CLSCTX_INPROC_SERVER, IID_ITfDisplayAttributeMgr, + getter_AddRefs(displayAttributeMgr)); + if (FAILED(hr) || !displayAttributeMgr) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + (" TSFTextStore::Initialize() FAILED to create " + "a display attribute manager instance, hr=0x%08X", hr)); + return; + } + + RefPtr<ITfCategoryMgr> categoryMgr; + hr = ::CoCreateInstance(CLSID_TF_CategoryMgr, nullptr, + CLSCTX_INPROC_SERVER, IID_ITfCategoryMgr, + getter_AddRefs(categoryMgr)); + if (FAILED(hr) || !categoryMgr) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + (" TSFTextStore::Initialize() FAILED to create " + "a category manager instance, hr=0x%08X", hr)); + return; + } + + RefPtr<ITfDocumentMgr> disabledDocumentMgr; + hr = threadMgr->CreateDocumentMgr(getter_AddRefs(disabledDocumentMgr)); + if (FAILED(hr) || !disabledDocumentMgr) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + (" TSFTextStore::Initialize() FAILED to create " + "a document manager for disabled mode, hr=0x%08X", hr)); + return; + } + + RefPtr<ITfContext> disabledContext; + DWORD editCookie = 0; + hr = disabledDocumentMgr->CreateContext(sClientId, 0, nullptr, + getter_AddRefs(disabledContext), + &editCookie); + if (FAILED(hr) || !disabledContext) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + (" TSFTextStore::Initialize() FAILED to create " + "a context for disabled mode, hr=0x%08X", hr)); + return; + } + + MarkContextAsKeyboardDisabled(disabledContext); + MarkContextAsEmpty(disabledContext); + + MOZ_LOG(sTextStoreLog, LogLevel::Info, + (" TSFTextStore::Initialize() is creating " + "a TSFStaticSink instance...")); + TSFStaticSink* staticSink = TSFStaticSink::GetInstance(); + if (!staticSink->Init(threadMgr, inputProcessorProfiles)) { + TSFStaticSink::Shutdown(); + MOZ_LOG(sTextStoreLog, LogLevel::Error, + (" TSFTextStore::Initialize() FAILED to initialize TSFStaticSink " + "instance")); + return; + } + + sInputProcessorProfiles = inputProcessorProfiles; + sThreadMgr = threadMgr; + sMessagePump = messagePump; + sKeystrokeMgr = keystrokeMgr; + sDisplayAttrMgr = displayAttributeMgr; + sCategoryMgr = categoryMgr; + sDisabledDocumentMgr = disabledDocumentMgr; + sDisabledContext = disabledContext; + + sCreateNativeCaretForLegacyATOK = + Preferences::GetBool("intl.tsf.hack.atok.create_native_caret", true); + sDoNotReturnNoLayoutErrorToATOKOfCompositionString = + Preferences::GetBool( + "intl.tsf.hack.atok.do_not_return_no_layout_error_of_composition_string", + true); + sDoNotReturnNoLayoutErrorToMSSimplifiedTIP = + Preferences::GetBool( + "intl.tsf.hack.ms_simplified_chinese.do_not_return_no_layout_error", + true); + sDoNotReturnNoLayoutErrorToMSTraditionalTIP = + Preferences::GetBool( + "intl.tsf.hack.ms_traditional_chinese.do_not_return_no_layout_error", + true); + sDoNotReturnNoLayoutErrorToFreeChangJie = + Preferences::GetBool( + "intl.tsf.hack.free_chang_jie.do_not_return_no_layout_error", true); + sDoNotReturnNoLayoutErrorToEasyChangjei = + Preferences::GetBool( + "intl.tsf.hack.easy_changjei.do_not_return_no_layout_error", true); + sDoNotReturnNoLayoutErrorToMSJapaneseIMEAtFirstChar = + Preferences::GetBool( + "intl.tsf.hack.ms_japanese_ime." + "do_not_return_no_layout_error_at_first_char", true); + sDoNotReturnNoLayoutErrorToMSJapaneseIMEAtCaret = + Preferences::GetBool( + "intl.tsf.hack.ms_japanese_ime.do_not_return_no_layout_error_at_caret", + true); + sHackQueryInsertForMSSimplifiedTIP = + Preferences::GetBool( + "intl.tsf.hack.ms_simplified_chinese.query_insert_result", true); + sHackQueryInsertForMSTraditionalTIP = + Preferences::GetBool( + "intl.tsf.hack.ms_traditional_chinese.query_insert_result", true); + + MOZ_LOG(sTextStoreLog, LogLevel::Info, + (" TSFTextStore::Initialize(), sThreadMgr=0x%p, " + "sClientId=0x%08X, sDisplayAttrMgr=0x%p, " + "sCategoryMgr=0x%p, sDisabledDocumentMgr=0x%p, sDisabledContext=%p, " + "sCreateNativeCaretForLegacyATOK=%s, " + "sDoNotReturnNoLayoutErrorToATOKOfCompositionString=%s, " + "sDoNotReturnNoLayoutErrorToFreeChangJie=%s, " + "sDoNotReturnNoLayoutErrorToEasyChangjei=%s, " + "sDoNotReturnNoLayoutErrorToMSJapaneseIMEAtFirstChar=%s, " + "sDoNotReturnNoLayoutErrorToMSJapaneseIMEAtCaret=%s", + sThreadMgr.get(), sClientId, sDisplayAttrMgr.get(), + sCategoryMgr.get(), sDisabledDocumentMgr.get(), sDisabledContext.get(), + GetBoolName(sCreateNativeCaretForLegacyATOK), + GetBoolName(sDoNotReturnNoLayoutErrorToATOKOfCompositionString), + GetBoolName(sDoNotReturnNoLayoutErrorToFreeChangJie), + GetBoolName(sDoNotReturnNoLayoutErrorToEasyChangjei), + GetBoolName(sDoNotReturnNoLayoutErrorToMSJapaneseIMEAtFirstChar), + GetBoolName(sDoNotReturnNoLayoutErrorToMSJapaneseIMEAtCaret))); +} + +// static +void +TSFTextStore::Terminate() +{ + MOZ_LOG(sTextStoreLog, LogLevel::Info, ("TSFTextStore::Terminate()")); + + TSFStaticSink::Shutdown(); + + sDisplayAttrMgr = nullptr; + sCategoryMgr = nullptr; + sEnabledTextStore = nullptr; + sDisabledDocumentMgr = nullptr; + sDisabledContext = nullptr; + sInputProcessorProfiles = nullptr; + sClientId = 0; + if (sThreadMgr) { + sThreadMgr->Deactivate(); + sThreadMgr = nullptr; + sMessagePump = nullptr; + sKeystrokeMgr = nullptr; + } +} + +// static +bool +TSFTextStore::ProcessRawKeyMessage(const MSG& aMsg) +{ + if (!sKeystrokeMgr) { + return false; // not in TSF mode + } + + if (aMsg.message == WM_KEYDOWN) { + BOOL eaten; + RefPtr<ITfKeystrokeMgr> keystrokeMgr = sKeystrokeMgr; + HRESULT hr = keystrokeMgr->TestKeyDown(aMsg.wParam, aMsg.lParam, &eaten); + if (FAILED(hr) || !sKeystrokeMgr || !eaten) { + return false; + } + RefPtr<TSFTextStore> textStore(sEnabledTextStore); + if (textStore) { + textStore->OnStartToHandleKeyMessage(); + } + hr = keystrokeMgr->KeyDown(aMsg.wParam, aMsg.lParam, &eaten); + if (textStore) { + textStore->OnEndHandlingKeyMessage(); + } + return SUCCEEDED(hr) && (eaten || !sKeystrokeMgr); + } + if (aMsg.message == WM_KEYUP) { + BOOL eaten; + RefPtr<ITfKeystrokeMgr> keystrokeMgr = sKeystrokeMgr; + HRESULT hr = keystrokeMgr->TestKeyUp(aMsg.wParam, aMsg.lParam, &eaten); + if (FAILED(hr) || !sKeystrokeMgr || !eaten) { + return false; + } + RefPtr<TSFTextStore> textStore(sEnabledTextStore); + if (textStore) { + textStore->OnStartToHandleKeyMessage(); + } + hr = keystrokeMgr->KeyUp(aMsg.wParam, aMsg.lParam, &eaten); + if (textStore) { + textStore->OnEndHandlingKeyMessage(); + } + return SUCCEEDED(hr) && (eaten || !sKeystrokeMgr); + } + return false; +} + +// static +void +TSFTextStore::ProcessMessage(nsWindowBase* aWindow, + UINT aMessage, + WPARAM& aWParam, + LPARAM& aLParam, + MSGResult& aResult) +{ + switch (aMessage) { + case WM_IME_SETCONTEXT: + // If a windowless plugin had focus and IME was handled on it, composition + // window was set the position. After that, even in TSF mode, WinXP keeps + // to use composition window at the position if the active IME is not + // aware TSF. For avoiding this issue, we need to hide the composition + // window here. + if (aWParam) { + aLParam &= ~ISC_SHOWUICOMPOSITIONWINDOW; + } + break; + case WM_ENTERIDLE: + // When an modal dialog such as a file picker is open, composition + // should be committed because IME might be used on it. + if (!IsComposingOn(aWindow)) { + break; + } + CommitComposition(false); + break; + case MOZ_WM_NOTIY_TSF_OF_LAYOUT_CHANGE: { + TSFTextStore* maybeTextStore = reinterpret_cast<TSFTextStore*>(aWParam); + if (maybeTextStore == sEnabledTextStore) { + RefPtr<TSFTextStore> textStore(maybeTextStore); + textStore->NotifyTSFOfLayoutChangeAgain(); + } + break; + } + } +} + +// static +bool +TSFTextStore::IsIMM_IMEActive() +{ + return TSFStaticSink::IsIMM_IMEActive(); +} + +// static +bool +TSFTextStore::IsMSJapaneseIMEActive() +{ + return TSFStaticSink::GetInstance()->IsMSJapaneseIMEActive(); +} + +/******************************************************************/ +/* TSFTextStore::Composition */ +/******************************************************************/ + +void +TSFTextStore::Composition::Start(ITfCompositionView* aCompositionView, + LONG aCompositionStartOffset, + const nsAString& aCompositionString) +{ + mView = aCompositionView; + mString = aCompositionString; + mStart = aCompositionStartOffset; +} + +void +TSFTextStore::Composition::End() +{ + mView = nullptr; + mString.Truncate(); +} + +/****************************************************************************** + * TSFTextStore::Content + *****************************************************************************/ + +const nsDependentSubstring +TSFTextStore::Content::GetSelectedText() const +{ + MOZ_ASSERT(mInitialized); + return GetSubstring(static_cast<uint32_t>(mSelection.StartOffset()), + static_cast<uint32_t>(mSelection.Length())); +} + +const nsDependentSubstring +TSFTextStore::Content::GetSubstring(uint32_t aStart, uint32_t aLength) const +{ + MOZ_ASSERT(mInitialized); + return nsDependentSubstring(mText, aStart, aLength); +} + +void +TSFTextStore::Content::ReplaceSelectedTextWith(const nsAString& aString) +{ + MOZ_ASSERT(mInitialized); + ReplaceTextWith(mSelection.StartOffset(), mSelection.Length(), aString); +} + +inline uint32_t +FirstDifferentCharOffset(const nsAString& aStr1, const nsAString& aStr2) +{ + MOZ_ASSERT(aStr1 != aStr2); + uint32_t i = 0; + uint32_t minLength = std::min(aStr1.Length(), aStr2.Length()); + for (; i < minLength && aStr1[i] == aStr2[i]; i++) { + /* nothing to do */ + } + return i; +} + +void +TSFTextStore::Content::ReplaceTextWith(LONG aStart, + LONG aLength, + const nsAString& aReplaceString) +{ + MOZ_ASSERT(mInitialized); + const nsDependentSubstring replacedString = + GetSubstring(static_cast<uint32_t>(aStart), + static_cast<uint32_t>(aLength)); + if (aReplaceString != replacedString) { + uint32_t firstDifferentOffset = mMinTextModifiedOffset; + if (mComposition.IsComposing()) { + // Emulate text insertion during compositions, because during a + // composition, editor expects the whole composition string to + // be sent in eCompositionChange, not just the inserted part. + // The actual eCompositionChange will be sent in SetSelection + // or OnUpdateComposition. + MOZ_ASSERT(aStart >= mComposition.mStart); + MOZ_ASSERT(aStart + aLength <= mComposition.EndOffset()); + mComposition.mString.Replace( + static_cast<uint32_t>(aStart - mComposition.mStart), + static_cast<uint32_t>(aLength), aReplaceString); + // TIP may set composition string twice or more times during a document + // lock. Therefore, we should compute the first difference offset with + // mLastCompositionString. + if (mComposition.mString != mLastCompositionString) { + firstDifferentOffset = + mComposition.mStart + + FirstDifferentCharOffset(mComposition.mString, + mLastCompositionString); + // The previous change to the composition string is canceled. + if (mMinTextModifiedOffset >= + static_cast<uint32_t>(mComposition.mStart) && + mMinTextModifiedOffset < firstDifferentOffset) { + mMinTextModifiedOffset = firstDifferentOffset; + } + } else if (mMinTextModifiedOffset >= + static_cast<uint32_t>(mComposition.mStart) && + mMinTextModifiedOffset < + static_cast<uint32_t>(mComposition.EndOffset())) { + // The previous change to the composition string is canceled. + mMinTextModifiedOffset = firstDifferentOffset = + mComposition.EndOffset(); + } + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::Content::ReplaceTextWith(aStart=%d, " + "aLength=%d, aReplaceString=\"%s\"), mComposition={ mStart=%d, " + "mString=\"%s\" }, mLastCompositionString=\"%s\", " + "mMinTextModifiedOffset=%u, firstDifferentOffset=%u", + this, aStart, aLength, GetEscapedUTF8String(aReplaceString).get(), + mComposition.mStart, GetEscapedUTF8String(mComposition.mString).get(), + GetEscapedUTF8String(mLastCompositionString).get(), + mMinTextModifiedOffset, firstDifferentOffset)); + } else { + firstDifferentOffset = + static_cast<uint32_t>(aStart) + + FirstDifferentCharOffset(aReplaceString, replacedString); + } + mMinTextModifiedOffset = + std::min(mMinTextModifiedOffset, firstDifferentOffset); + mText.Replace(static_cast<uint32_t>(aStart), + static_cast<uint32_t>(aLength), aReplaceString); + } + // Selection should be collapsed at the end of the inserted string. + mSelection.CollapseAt( + static_cast<uint32_t>(aStart) + aReplaceString.Length()); +} + +void +TSFTextStore::Content::StartComposition(ITfCompositionView* aCompositionView, + const PendingAction& aCompStart, + bool aPreserveSelection) +{ + MOZ_ASSERT(mInitialized); + MOZ_ASSERT(aCompositionView); + MOZ_ASSERT(!mComposition.mView); + MOZ_ASSERT(aCompStart.mType == PendingAction::COMPOSITION_START); + + mComposition.Start(aCompositionView, aCompStart.mSelectionStart, + GetSubstring(static_cast<uint32_t>(aCompStart.mSelectionStart), + static_cast<uint32_t>(aCompStart.mSelectionLength))); + if (!aPreserveSelection) { + // XXX Do we need to set a new writing-mode here when setting a new + // selection? Currently, we just preserve the existing value. + mSelection.SetSelection(mComposition.mStart, mComposition.mString.Length(), + false, mSelection.GetWritingMode()); + } +} + +void +TSFTextStore::Content::RestoreCommittedComposition( + ITfCompositionView* aCompositionView, + const PendingAction& aPendingCompositionStart, + const PendingAction& aCanceledCompositionEnd) +{ + MOZ_ASSERT(mInitialized); + MOZ_ASSERT(aCompositionView); + MOZ_ASSERT(!mComposition.mView); + MOZ_ASSERT(aPendingCompositionStart.mType == + PendingAction::COMPOSITION_START); + MOZ_ASSERT(aCanceledCompositionEnd.mType == + PendingAction::COMPOSITION_END); + MOZ_ASSERT(GetSubstring( + static_cast<uint32_t>(aPendingCompositionStart.mSelectionStart), + static_cast<uint32_t>(aCanceledCompositionEnd.mData.Length())) == + aCanceledCompositionEnd.mData); + + // Restore the committed string as composing string. + mComposition.Start(aCompositionView, + aPendingCompositionStart.mSelectionStart, + aCanceledCompositionEnd.mData); +} + +void +TSFTextStore::Content::EndComposition(const PendingAction& aCompEnd) +{ + MOZ_ASSERT(mInitialized); + MOZ_ASSERT(mComposition.mView); + MOZ_ASSERT(aCompEnd.mType == PendingAction::COMPOSITION_END); + + mSelection.CollapseAt(mComposition.mStart + aCompEnd.mData.Length()); + mComposition.End(); +} + +/****************************************************************************** + * TSFTextStore::MouseTracker + *****************************************************************************/ + +TSFTextStore::MouseTracker::MouseTracker() + : mStart(-1) + , mLength(-1) + , mCookie(kInvalidCookie) +{ +} + +HRESULT +TSFTextStore::MouseTracker::Init(TSFTextStore* aTextStore) +{ + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::MouseTracker::Init(aTextStore=0x%p), " + "aTextStore->mMouseTrackers.Length()=%d", + this, aTextStore->mMouseTrackers.Length())); + + if (&aTextStore->mMouseTrackers.LastElement() != this) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::MouseTracker::Init() FAILED due to " + "this is not the last element of mMouseTrackers", this)); + return E_FAIL; + } + if (aTextStore->mMouseTrackers.Length() > kInvalidCookie) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::MouseTracker::Init() FAILED due to " + "no new cookie available", this)); + return E_FAIL; + } + MOZ_ASSERT(!aTextStore->mMouseTrackers.IsEmpty(), + "This instance must be in TSFTextStore::mMouseTrackers"); + mCookie = static_cast<DWORD>(aTextStore->mMouseTrackers.Length() - 1); + return S_OK; +} + +HRESULT +TSFTextStore::MouseTracker::AdviseSink(TSFTextStore* aTextStore, + ITfRangeACP* aTextRange, + ITfMouseSink* aMouseSink) +{ + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::MouseTracker::AdviseSink(aTextStore=0x%p, " + "aTextRange=0x%p, aMouseSink=0x%p), mCookie=%d, mSink=0x%p", + this, aTextStore, aTextRange, aMouseSink, mCookie, mSink.get())); + MOZ_ASSERT(mCookie != kInvalidCookie, "This hasn't been initalized?"); + + if (mSink) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink() FAILED " + "due to already being used", this)); + return E_FAIL; + } + + HRESULT hr = aTextRange->GetExtent(&mStart, &mLength); + if (FAILED(hr)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink() FAILED " + "due to failure of ITfRangeACP::GetExtent()", this)); + return hr; + } + + if (mStart < 0 || mLength <= 0) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink() FAILED " + "due to odd result of ITfRangeACP::GetExtent(), " + "mStart=%d, mLength=%d", this, mStart, mLength)); + return E_INVALIDARG; + } + + nsAutoString textContent; + if (NS_WARN_IF(!aTextStore->GetCurrentText(textContent))) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink() FAILED " + "due to failure of TSFTextStore::GetCurrentText()", this)); + return E_FAIL; + } + + if (textContent.Length() <= static_cast<uint32_t>(mStart) || + textContent.Length() < static_cast<uint32_t>(mStart + mLength)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink() FAILED " + "due to out of range, mStart=%d, mLength=%d, " + "textContent.Length()=%d", + this, mStart, mLength, textContent.Length())); + return E_INVALIDARG; + } + + mSink = aMouseSink; + + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink(), " + "succeeded, mStart=%d, mLength=%d, textContent.Length()=%d", + this, mStart, mLength, textContent.Length())); + return S_OK; +} + +void +TSFTextStore::MouseTracker::UnadviseSink() +{ + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::MouseTracker::UnadviseSink(), " + "mCookie=%d, mSink=0x%p, mStart=%d, mLength=%d", + this, mCookie, mSink.get(), mStart, mLength)); + mSink = nullptr; + mStart = mLength = -1; +} + +bool +TSFTextStore::MouseTracker::OnMouseButtonEvent(ULONG aEdge, + ULONG aQuadrant, + DWORD aButtonStatus) +{ + MOZ_ASSERT(IsUsing(), "The caller must check before calling OnMouseEvent()"); + + BOOL eaten = FALSE; + RefPtr<ITfMouseSink> sink = mSink; + HRESULT hr = sink->OnMouseEvent(aEdge, aQuadrant, aButtonStatus, &eaten); + + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::MouseTracker::OnMouseEvent(aEdge=%d, " + "aQuadrant=%d, aButtonStatus=0x%08X), hr=0x%08X, eaten=%s", + this, aEdge, aQuadrant, aButtonStatus, hr, GetBoolName(!!eaten))); + + return SUCCEEDED(hr) && eaten; +} + +#ifdef DEBUG +// static +bool +TSFTextStore::CurrentKeyboardLayoutHasIME() +{ + if (!sInputProcessorProfiles) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("TSFTextStore::CurrentKeyboardLayoutHasIME() FAILED due to " + "there is no input processor profiles instance")); + return false; + } + RefPtr<ITfInputProcessorProfileMgr> profileMgr; + HRESULT hr = + sInputProcessorProfiles->QueryInterface(IID_ITfInputProcessorProfileMgr, + getter_AddRefs(profileMgr)); + if (FAILED(hr) || !profileMgr) { + // On Windows Vista or later, ImmIsIME() API always returns true. + // If we failed to obtain the profile manager, we cannot know if current + // keyboard layout has IME. + MOZ_LOG(sTextStoreLog, LogLevel::Error, + (" TSFTextStore::CurrentKeyboardLayoutHasIME() FAILED to query " + "ITfInputProcessorProfileMgr")); + return false; + } + + TF_INPUTPROCESSORPROFILE profile; + hr = profileMgr->GetActiveProfile(GUID_TFCAT_TIP_KEYBOARD, &profile); + if (hr == S_FALSE) { + return false; // not found or not active + } + if (FAILED(hr)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + (" TSFTextStore::CurrentKeyboardLayoutHasIME() FAILED to retreive " + "active profile")); + return false; + } + return (profile.dwProfileType == TF_PROFILETYPE_INPUTPROCESSOR); +} +#endif // #ifdef DEBUG + +} // name widget +} // name mozilla diff --git a/widget/windows/TSFTextStore.h b/widget/windows/TSFTextStore.h new file mode 100644 index 0000000000..9596510d51 --- /dev/null +++ b/widget/windows/TSFTextStore.h @@ -0,0 +1,1045 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef TSFTextStore_h_ +#define TSFTextStore_h_ + +#include "nsCOMPtr.h" +#include "nsIWidget.h" +#include "nsString.h" +#include "nsWindowBase.h" + +#include "WinUtils.h" +#include "WritingModes.h" + +#include "mozilla/Attributes.h" +#include "mozilla/RefPtr.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/TextEventDispatcher.h" +#include "mozilla/TextRange.h" +#include "mozilla/WindowsVersion.h" + +#include <msctf.h> +#include <textstor.h> + +// GUID_PROP_INPUTSCOPE is declared in inputscope.h using INIT_GUID. +// With initguid.h, we get its instance instead of extern declaration. +#ifdef INPUTSCOPE_INIT_GUID +#include <initguid.h> +#endif +#ifdef TEXTATTRS_INIT_GUID +#include <tsattrs.h> +#endif +#include <inputscope.h> + +// TSF InputScope, for earlier SDK 8 +#define IS_SEARCH static_cast<InputScope>(50) + +struct ITfThreadMgr; +struct ITfDocumentMgr; +struct ITfDisplayAttributeMgr; +struct ITfCategoryMgr; +class nsWindow; + +namespace mozilla { +namespace widget { + +struct MSGResult; + +/* + * Text Services Framework text store + */ + +class TSFTextStore final : public ITextStoreACP + , public ITfContextOwnerCompositionSink + , public ITfMouseTrackerACP +{ +private: + typedef IMENotification::SelectionChangeDataBase SelectionChangeDataBase; + typedef IMENotification::SelectionChangeData SelectionChangeData; + typedef IMENotification::TextChangeDataBase TextChangeDataBase; + typedef IMENotification::TextChangeData TextChangeData; + +public: /*IUnknown*/ + STDMETHODIMP QueryInterface(REFIID, void**); + + NS_INLINE_DECL_IUNKNOWN_REFCOUNTING(TSFTextStore) + +public: /*ITextStoreACP*/ + STDMETHODIMP AdviseSink(REFIID, IUnknown*, DWORD); + STDMETHODIMP UnadviseSink(IUnknown*); + STDMETHODIMP RequestLock(DWORD, HRESULT*); + STDMETHODIMP GetStatus(TS_STATUS*); + STDMETHODIMP QueryInsert(LONG, LONG, ULONG, LONG*, LONG*); + STDMETHODIMP GetSelection(ULONG, ULONG, TS_SELECTION_ACP*, ULONG*); + STDMETHODIMP SetSelection(ULONG, const TS_SELECTION_ACP*); + STDMETHODIMP GetText(LONG, LONG, WCHAR*, ULONG, ULONG*, TS_RUNINFO*, ULONG, + ULONG*, LONG*); + STDMETHODIMP SetText(DWORD, LONG, LONG, const WCHAR*, ULONG, TS_TEXTCHANGE*); + STDMETHODIMP GetFormattedText(LONG, LONG, IDataObject**); + STDMETHODIMP GetEmbedded(LONG, REFGUID, REFIID, IUnknown**); + STDMETHODIMP QueryInsertEmbedded(const GUID*, const FORMATETC*, BOOL*); + STDMETHODIMP InsertEmbedded(DWORD, LONG, LONG, IDataObject*, TS_TEXTCHANGE*); + STDMETHODIMP RequestSupportedAttrs(DWORD, ULONG, const TS_ATTRID*); + STDMETHODIMP RequestAttrsAtPosition(LONG, ULONG, const TS_ATTRID*, DWORD); + STDMETHODIMP RequestAttrsTransitioningAtPosition(LONG, ULONG, + const TS_ATTRID*, DWORD); + STDMETHODIMP FindNextAttrTransition(LONG, LONG, ULONG, const TS_ATTRID*, + DWORD, LONG*, BOOL*, LONG*); + STDMETHODIMP RetrieveRequestedAttrs(ULONG, TS_ATTRVAL*, ULONG*); + STDMETHODIMP GetEndACP(LONG*); + STDMETHODIMP GetActiveView(TsViewCookie*); + STDMETHODIMP GetACPFromPoint(TsViewCookie, const POINT*, DWORD, LONG*); + STDMETHODIMP GetTextExt(TsViewCookie, LONG, LONG, RECT*, BOOL*); + STDMETHODIMP GetScreenExt(TsViewCookie, RECT*); + STDMETHODIMP GetWnd(TsViewCookie, HWND*); + STDMETHODIMP InsertTextAtSelection(DWORD, const WCHAR*, ULONG, LONG*, LONG*, + TS_TEXTCHANGE*); + STDMETHODIMP InsertEmbeddedAtSelection(DWORD, IDataObject*, LONG*, LONG*, + TS_TEXTCHANGE*); + +public: /*ITfContextOwnerCompositionSink*/ + STDMETHODIMP OnStartComposition(ITfCompositionView*, BOOL*); + STDMETHODIMP OnUpdateComposition(ITfCompositionView*, ITfRange*); + STDMETHODIMP OnEndComposition(ITfCompositionView*); + +public: /*ITfMouseTrackerACP*/ + STDMETHODIMP AdviseMouseSink(ITfRangeACP*, ITfMouseSink*, DWORD*); + STDMETHODIMP UnadviseMouseSink(DWORD); + +public: + static void Initialize(void); + static void Terminate(void); + + static bool ProcessRawKeyMessage(const MSG& aMsg); + static void ProcessMessage(nsWindowBase* aWindow, UINT aMessage, + WPARAM& aWParam, LPARAM& aLParam, + MSGResult& aResult); + + static void SetIMEOpenState(bool); + static bool GetIMEOpenState(void); + + static void CommitComposition(bool aDiscard) + { + NS_ASSERTION(IsInTSFMode(), "Not in TSF mode, shouldn't be called"); + if (!sEnabledTextStore) { + return; + } + RefPtr<TSFTextStore> textStore(sEnabledTextStore); + textStore->CommitCompositionInternal(aDiscard); + } + + static void SetInputContext(nsWindowBase* aWidget, + const InputContext& aContext, + const InputContextAction& aAction); + + static nsresult OnFocusChange(bool aGotFocus, + nsWindowBase* aFocusedWidget, + const InputContext& aContext); + static nsresult OnTextChange(const IMENotification& aIMENotification) + { + NS_ASSERTION(IsInTSFMode(), "Not in TSF mode, shouldn't be called"); + if (!sEnabledTextStore) { + return NS_OK; + } + RefPtr<TSFTextStore> textStore(sEnabledTextStore); + return textStore->OnTextChangeInternal(aIMENotification); + } + + static nsresult OnSelectionChange(const IMENotification& aIMENotification) + { + NS_ASSERTION(IsInTSFMode(), "Not in TSF mode, shouldn't be called"); + if (!sEnabledTextStore) { + return NS_OK; + } + RefPtr<TSFTextStore> textStore(sEnabledTextStore); + return textStore->OnSelectionChangeInternal(aIMENotification); + } + + static nsresult OnLayoutChange() + { + NS_ASSERTION(IsInTSFMode(), "Not in TSF mode, shouldn't be called"); + if (!sEnabledTextStore) { + return NS_OK; + } + RefPtr<TSFTextStore> textStore(sEnabledTextStore); + return textStore->OnLayoutChangeInternal(); + } + + static nsresult OnUpdateComposition() + { + NS_ASSERTION(IsInTSFMode(), "Not in TSF mode, shouldn't be called"); + if (!sEnabledTextStore) { + return NS_OK; + } + RefPtr<TSFTextStore> textStore(sEnabledTextStore); + return textStore->OnUpdateCompositionInternal(); + } + + static nsresult OnMouseButtonEvent(const IMENotification& aIMENotification) + { + NS_ASSERTION(IsInTSFMode(), "Not in TSF mode, shouldn't be called"); + if (!sEnabledTextStore) { + return NS_OK; + } + RefPtr<TSFTextStore> textStore(sEnabledTextStore); + return textStore->OnMouseButtonEventInternal(aIMENotification); + } + + static nsIMEUpdatePreference GetIMEUpdatePreference(); + + // Returns the address of the pointer so that the TSF automatic test can + // replace the system object with a custom implementation for testing. + // XXX TSF doesn't work now. Should we remove it? + static void* GetNativeData(uint32_t aDataType) + { + switch (aDataType) { + case NS_NATIVE_TSF_THREAD_MGR: + Initialize(); // Apply any previous changes + return static_cast<void*>(&sThreadMgr); + case NS_NATIVE_TSF_CATEGORY_MGR: + return static_cast<void*>(&sCategoryMgr); + case NS_NATIVE_TSF_DISPLAY_ATTR_MGR: + return static_cast<void*>(&sDisplayAttrMgr); + default: + return nullptr; + } + } + + static ITfMessagePump* GetMessagePump() + { + return sMessagePump; + } + + static void* GetThreadManager() + { + return static_cast<void*>(sThreadMgr); + } + + static bool ThinksHavingFocus() + { + return (sEnabledTextStore && sEnabledTextStore->mContext); + } + + static bool IsInTSFMode() + { + return sThreadMgr != nullptr; + } + + static bool IsComposing() + { + return (sEnabledTextStore && sEnabledTextStore->mComposition.IsComposing()); + } + + static bool IsComposingOn(nsWindowBase* aWidget) + { + return (IsComposing() && sEnabledTextStore->mWidget == aWidget); + } + + static nsWindowBase* GetEnabledWindowBase() + { + return sEnabledTextStore ? sEnabledTextStore->mWidget.get() : nullptr; + } + + /** + * Returns true if active keyboard layout is a legacy IMM-IME. + */ + static bool IsIMM_IMEActive(); + + /** + * Returns true if active TIP is MS-IME for Japanese. + */ + static bool IsMSJapaneseIMEActive(); + +#ifdef DEBUG + // Returns true when keyboard layout has IME (TIP). + static bool CurrentKeyboardLayoutHasIME(); +#endif // #ifdef DEBUG + +protected: + TSFTextStore(); + ~TSFTextStore(); + + static bool CreateAndSetFocus(nsWindowBase* aFocusedWidget, + const InputContext& aContext); + static void EnsureToDestroyAndReleaseEnabledTextStoreIf( + RefPtr<TSFTextStore>& aTextStore); + static void MarkContextAsKeyboardDisabled(ITfContext* aContext); + static void MarkContextAsEmpty(ITfContext* aContext); + + bool Init(nsWindowBase* aWidget, const InputContext& aContext); + void Destroy(); + void ReleaseTSFObjects(); + + bool IsReadLock(DWORD aLock) const + { + return (TS_LF_READ == (aLock & TS_LF_READ)); + } + bool IsReadWriteLock(DWORD aLock) const + { + return (TS_LF_READWRITE == (aLock & TS_LF_READWRITE)); + } + bool IsReadLocked() const { return IsReadLock(mLock); } + bool IsReadWriteLocked() const { return IsReadWriteLock(mLock); } + + // This is called immediately after a call of OnLockGranted() of mSink. + // Note that mLock isn't cleared yet when this is called. + void DidLockGranted(); + + bool GetScreenExtInternal(RECT& aScreenExt); + // If aDispatchCompositionChangeEvent is true, this method will dispatch + // compositionchange event if this is called during IME composing. + // aDispatchCompositionChangeEvent should be true only when this is called + // from SetSelection. Because otherwise, the compositionchange event should + // not be sent from here. + HRESULT SetSelectionInternal(const TS_SELECTION_ACP*, + bool aDispatchCompositionChangeEvent = false); + bool InsertTextAtSelectionInternal(const nsAString& aInsertStr, + TS_TEXTCHANGE* aTextChange); + void CommitCompositionInternal(bool); + HRESULT GetDisplayAttribute(ITfProperty* aProperty, + ITfRange* aRange, + TF_DISPLAYATTRIBUTE* aResult); + HRESULT RestartCompositionIfNecessary(ITfRange* pRangeNew = nullptr); + HRESULT RestartComposition(ITfCompositionView* aCompositionView, + ITfRange* aNewRange); + + // Following methods record composing action(s) to mPendingActions. + // They will be flushed FlushPendingActions(). + HRESULT RecordCompositionStartAction(ITfCompositionView* aCompositionView, + ITfRange* aRange, + bool aPreserveSelection); + HRESULT RecordCompositionStartAction(ITfCompositionView* aComposition, + LONG aStart, + LONG aLength, + bool aPreserveSelection); + HRESULT RecordCompositionUpdateAction(); + HRESULT RecordCompositionEndAction(); + + // DispatchEvent() dispatches the event and if it may not be handled + // synchronously, this makes the instance not notify TSF of pending + // notifications until next notification from content. + void DispatchEvent(WidgetGUIEvent& aEvent); + void OnLayoutInformationAvaliable(); + + // FlushPendingActions() performs pending actions recorded in mPendingActions + // and clear it. + void FlushPendingActions(); + // MaybeFlushPendingNotifications() performs pending notifications to TSF. + void MaybeFlushPendingNotifications(); + + nsresult OnTextChangeInternal(const IMENotification& aIMENotification); + nsresult OnSelectionChangeInternal(const IMENotification& aIMENotification); + nsresult OnMouseButtonEventInternal(const IMENotification& aIMENotification); + nsresult OnLayoutChangeInternal(); + nsresult OnUpdateCompositionInternal(); + + // mPendingSelectionChangeData stores selection change data until notifying + // TSF of selection change. If two or more selection changes occur, this + // stores the latest selection change data because only it is necessary. + SelectionChangeData mPendingSelectionChangeData; + + // mPendingTextChangeData stores one or more text change data until notifying + // TSF of text change. If two or more text changes occur, this merges + // every text change data. + TextChangeData mPendingTextChangeData; + + void NotifyTSFOfTextChange(); + void NotifyTSFOfSelectionChange(); + bool NotifyTSFOfLayoutChange(); + void NotifyTSFOfLayoutChangeAgain(); + + HRESULT HandleRequestAttrs(DWORD aFlags, + ULONG aFilterCount, + const TS_ATTRID* aFilterAttrs); + void SetInputScope(const nsString& aHTMLInputType, + const nsString& aHTMLInputInputmode); + + // Creates native caret over our caret. This method only works on desktop + // application. Otherwise, this does nothing. + void CreateNativeCaret(); + // Destroys native caret if there is. + void MaybeDestroyNativeCaret(); + + // Holds the pointer to our current win32 widget + RefPtr<nsWindowBase> mWidget; + // mDispatcher is a helper class to dispatch composition events. + RefPtr<TextEventDispatcher> mDispatcher; + // Document manager for the currently focused editor + RefPtr<ITfDocumentMgr> mDocumentMgr; + // Edit cookie associated with the current editing context + DWORD mEditCookie; + // Editing context at the bottom of mDocumentMgr's context stack + RefPtr<ITfContext> mContext; + // Currently installed notification sink + RefPtr<ITextStoreACPSink> mSink; + // TS_AS_* mask of what events to notify + DWORD mSinkMask; + // 0 if not locked, otherwise TS_LF_* indicating the current lock + DWORD mLock; + // 0 if no lock is queued, otherwise TS_LF_* indicating the queue lock + DWORD mLockQueued; + + uint32_t mHandlingKeyMessage; + void OnStartToHandleKeyMessage() { ++mHandlingKeyMessage; } + void OnEndHandlingKeyMessage() + { + MOZ_ASSERT(mHandlingKeyMessage); + if (--mHandlingKeyMessage) { + return; + } + // If TSFTextStore instance is destroyed during handling key message(s), + // release all TSF objects when all nested key messages have been handled. + if (mDestroyed) { + ReleaseTSFObjects(); + } + } + + class Composition final + { + public: + // nullptr if no composition is active, otherwise the current composition + RefPtr<ITfCompositionView> mView; + + // Current copy of the active composition string. Only mString is + // changed during a InsertTextAtSelection call if we have a composition. + // mString acts as a buffer until OnUpdateComposition is called + // and mString is flushed to editor through eCompositionChange. + // This way all changes are updated in batches to avoid + // inconsistencies/artifacts. + nsString mString; + + // The start of the current active composition, in ACP offsets + LONG mStart; + + bool IsComposing() const + { + return (mView != nullptr); + } + + LONG EndOffset() const + { + return mStart + static_cast<LONG>(mString.Length()); + } + + // Start() and End() updates the members for emulating the latest state. + // Unless flush the pending actions, this data never matches the actual + // content. + void Start(ITfCompositionView* aCompositionView, + LONG aCompositionStartOffset, + const nsAString& aCompositionString); + void End(); + }; + // While the document is locked, we cannot dispatch any events which cause + // DOM events since the DOM events' handlers may modify the locked document. + // However, even while the document is locked, TSF may queries us. + // For that, TSFTextStore modifies mComposition even while the document is + // locked. With mComposition, query methods can returns the text content + // information. + Composition mComposition; + + /** + * IsComposingInContent() returns true if there is a composition in the + * focused editor and it's caused by native IME (either TIP of TSF or IME of + * IMM). I.e., returns true between eCompositionStart and + * eCompositionCommit(AsIs). + */ + bool IsComposingInContent() const; + + class Selection + { + public: + Selection() : mDirty(true) {} + + bool IsDirty() const { return mDirty; }; + void MarkDirty() { mDirty = true; } + + TS_SELECTION_ACP& ACP() + { + MOZ_ASSERT(!mDirty); + return mACP; + } + + void SetSelection(const TS_SELECTION_ACP& aSelection) + { + mDirty = false; + mACP = aSelection; + // Selection end must be active in our editor. + if (mACP.style.ase != TS_AE_START) { + mACP.style.ase = TS_AE_END; + } + // We're not support interim char selection for now. + // XXX Probably, this is necessary for supporting South Asian languages. + mACP.style.fInterimChar = FALSE; + } + + bool SetSelection(uint32_t aStart, + uint32_t aLength, + bool aReversed, + WritingMode aWritingMode) + { + bool changed = mDirty || + mACP.acpStart != static_cast<LONG>(aStart) || + mACP.acpEnd != static_cast<LONG>(aStart + aLength); + + mDirty = false; + mACP.acpStart = static_cast<LONG>(aStart); + mACP.acpEnd = static_cast<LONG>(aStart + aLength); + mACP.style.ase = aReversed ? TS_AE_START : TS_AE_END; + mACP.style.fInterimChar = FALSE; + mWritingMode = aWritingMode; + + return changed; + } + + bool IsCollapsed() const + { + MOZ_ASSERT(!mDirty); + return (mACP.acpStart == mACP.acpEnd); + } + + void CollapseAt(uint32_t aOffset) + { + // XXX This does not update the selection's mWritingMode. + // If it is ever used to "collapse" to an entirely new location, + // we may need to fix that. + mDirty = false; + mACP.acpStart = mACP.acpEnd = static_cast<LONG>(aOffset); + mACP.style.ase = TS_AE_END; + mACP.style.fInterimChar = FALSE; + } + + LONG MinOffset() const + { + MOZ_ASSERT(!mDirty); + LONG min = std::min(mACP.acpStart, mACP.acpEnd); + MOZ_ASSERT(min >= 0); + return min; + } + + LONG MaxOffset() const + { + MOZ_ASSERT(!mDirty); + LONG max = std::max(mACP.acpStart, mACP.acpEnd); + MOZ_ASSERT(max >= 0); + return max; + } + + LONG StartOffset() const + { + MOZ_ASSERT(!mDirty); + MOZ_ASSERT(mACP.acpStart >= 0); + return mACP.acpStart; + } + + LONG EndOffset() const + { + MOZ_ASSERT(!mDirty); + MOZ_ASSERT(mACP.acpEnd >= 0); + return mACP.acpEnd; + } + + LONG Length() const + { + MOZ_ASSERT(!mDirty); + MOZ_ASSERT(mACP.acpEnd >= mACP.acpStart); + return std::abs(mACP.acpEnd - mACP.acpStart); + } + + bool IsReversed() const + { + MOZ_ASSERT(!mDirty); + return (mACP.style.ase == TS_AE_START); + } + + TsActiveSelEnd ActiveSelEnd() const + { + MOZ_ASSERT(!mDirty); + return mACP.style.ase; + } + + bool IsInterimChar() const + { + MOZ_ASSERT(!mDirty); + return (mACP.style.fInterimChar != FALSE); + } + + WritingMode GetWritingMode() const + { + MOZ_ASSERT(!mDirty); + return mWritingMode; + } + + bool EqualsExceptDirection(const TS_SELECTION_ACP& aACP) const + { + if (mACP.style.ase == aACP.style.ase) { + return mACP.acpStart == aACP.acpStart && + mACP.acpEnd == aACP.acpEnd; + } + return mACP.acpStart == aACP.acpEnd && + mACP.acpEnd == aACP.acpStart; + } + + bool EqualsExceptDirection( + const SelectionChangeDataBase& aChangedSelection) const + { + MOZ_ASSERT(!mDirty); + MOZ_ASSERT(aChangedSelection.IsValid()); + return aChangedSelection.Length() == static_cast<uint32_t>(Length()) && + aChangedSelection.mOffset == static_cast<uint32_t>(StartOffset()); + } + + private: + TS_SELECTION_ACP mACP; + WritingMode mWritingMode; + bool mDirty; + }; + // Don't access mSelection directly except at calling MarkDirty(). + // Use SelectionForTSFRef() instead. This is modified immediately when + // TSF requests to set selection and not updated by selection change in + // content until mContentForTSF is cleared. + Selection mSelectionForTSF; + + /** + * Get the selection expected by TSF. If mSelectionForTSF is already valid, + * this just return the reference to it. Otherwise, this initializes it + * with eQuerySelectedText. Please check if the result is valid before + * actually using it. + * Note that this is also called by ContentForTSFRef(). + */ + Selection& SelectionForTSFRef(); + + class MOZ_STACK_CLASS AutoSetTemporarySelection final + { + public: + AutoSetTemporarySelection(Selection& aSelection) + : mSelection(aSelection) + { + mDirty = mSelection.IsDirty(); + if (mDirty) { + mSelection.CollapseAt(0); + } + } + + ~AutoSetTemporarySelection() + { + if (mDirty) { + mSelection.MarkDirty(); + } + } + + private: + Selection& mSelection; + bool mDirty; + }; + + struct PendingAction final + { + enum ActionType : uint8_t + { + COMPOSITION_START, + COMPOSITION_UPDATE, + COMPOSITION_END, + SET_SELECTION + }; + ActionType mType; + // For compositionstart and selectionset + LONG mSelectionStart; + LONG mSelectionLength; + // For compositionstart, compositionupdate and compositionend + nsString mData; + // For compositionupdate + RefPtr<TextRangeArray> mRanges; + // For selectionset + bool mSelectionReversed; + // For compositionupdate + bool mIncomplete; + // For compositionstart + bool mAdjustSelection; + }; + // Items of mPendingActions are appended when TSF tells us to need to dispatch + // DOM composition events. However, we cannot dispatch while the document is + // locked because it can cause modifying the locked document. So, the pending + // actions should be performed when document lock is unlocked. + nsTArray<PendingAction> mPendingActions; + + PendingAction* LastOrNewPendingCompositionUpdate() + { + if (!mPendingActions.IsEmpty()) { + PendingAction& lastAction = mPendingActions.LastElement(); + if (lastAction.mType == PendingAction::COMPOSITION_UPDATE) { + return &lastAction; + } + } + PendingAction* newAction = mPendingActions.AppendElement(); + newAction->mType = PendingAction::COMPOSITION_UPDATE; + newAction->mRanges = new TextRangeArray(); + newAction->mIncomplete = true; + return newAction; + } + + /** + * WasTextInsertedWithoutCompositionAt() checks if text was inserted without + * composition immediately before (e.g., see InsertTextAtSelectionInternal()). + * + * @param aStart The inserted offset you expected. + * @param aLength The inserted text length you expected. + * @return true if the last pending actions are + * COMPOSITION_START and COMPOSITION_END and + * aStart and aLength match their information. + */ + bool WasTextInsertedWithoutCompositionAt(LONG aStart, LONG aLength) const + { + if (mPendingActions.Length() < 2) { + return false; + } + const PendingAction& pendingLastAction = mPendingActions.LastElement(); + if (pendingLastAction.mType != PendingAction::COMPOSITION_END || + pendingLastAction.mData.Length() != aLength) { + return false; + } + const PendingAction& pendingPreLastAction = + mPendingActions[mPendingActions.Length() - 2]; + return pendingPreLastAction.mType == PendingAction::COMPOSITION_START && + pendingPreLastAction.mSelectionStart == aStart; + } + + bool IsPendingCompositionUpdateIncomplete() const + { + if (mPendingActions.IsEmpty()) { + return false; + } + const PendingAction& lastAction = mPendingActions.LastElement(); + return lastAction.mType == PendingAction::COMPOSITION_UPDATE && + lastAction.mIncomplete; + } + + void CompleteLastActionIfStillIncomplete() + { + if (!IsPendingCompositionUpdateIncomplete()) { + return; + } + RecordCompositionUpdateAction(); + } + + // When On*Composition() is called without document lock, we need to flush + // the recorded actions at quitting the method. + // AutoPendingActionAndContentFlusher class is usedful for it. + class MOZ_STACK_CLASS AutoPendingActionAndContentFlusher final + { + public: + AutoPendingActionAndContentFlusher(TSFTextStore* aTextStore) + : mTextStore(aTextStore) + { + MOZ_ASSERT(!mTextStore->mIsRecordingActionsWithoutLock); + if (!mTextStore->IsReadWriteLocked()) { + mTextStore->mIsRecordingActionsWithoutLock = true; + } + } + + ~AutoPendingActionAndContentFlusher() + { + if (!mTextStore->mIsRecordingActionsWithoutLock) { + return; + } + mTextStore->FlushPendingActions(); + mTextStore->mIsRecordingActionsWithoutLock = false; + } + + private: + AutoPendingActionAndContentFlusher() {} + + RefPtr<TSFTextStore> mTextStore; + }; + + class Content final + { + public: + Content(TSFTextStore::Composition& aComposition, + TSFTextStore::Selection& aSelection) : + mComposition(aComposition), mSelection(aSelection) + { + Clear(); + } + + void Clear() + { + mText.Truncate(); + mLastCompositionString.Truncate(); + mInitialized = false; + } + + bool IsInitialized() const { return mInitialized; } + + void Init(const nsAString& aText) + { + mText = aText; + if (mComposition.IsComposing()) { + mLastCompositionString = mComposition.mString; + } else { + mLastCompositionString.Truncate(); + } + mMinTextModifiedOffset = NOT_MODIFIED; + mInitialized = true; + } + + void OnLayoutChanged() + { + mMinTextModifiedOffset = NOT_MODIFIED; + } + + const nsDependentSubstring GetSelectedText() const; + const nsDependentSubstring GetSubstring(uint32_t aStart, + uint32_t aLength) const; + void ReplaceSelectedTextWith(const nsAString& aString); + void ReplaceTextWith(LONG aStart, LONG aLength, const nsAString& aString); + + void StartComposition(ITfCompositionView* aCompositionView, + const PendingAction& aCompStart, + bool aPreserveSelection); + /** + * RestoreCommittedComposition() restores the committed string as + * composing string. If InsertTextAtSelection() or something is called + * before a call of OnStartComposition(), there is a pending + * compositionstart and a pending compositionend. In this case, we + * need to cancel the pending compositionend and continue the composition. + * + * @param aCompositionView The composition view. + * @param aPendingCompositionStart The pending compositionstart which + * started the committed composition. + * @param aCanceledCompositionEnd The pending compositionend which is + * canceled for restarting the composition. + */ + void RestoreCommittedComposition( + ITfCompositionView* aCompositionView, + const PendingAction& aPendingCompositionStart, + const PendingAction& aCanceledCompositionEnd); + void EndComposition(const PendingAction& aCompEnd); + + const nsString& Text() const + { + MOZ_ASSERT(mInitialized); + return mText; + } + const nsString& LastCompositionString() const + { + MOZ_ASSERT(mInitialized); + return mLastCompositionString; + } + uint32_t MinTextModifiedOffset() const + { + MOZ_ASSERT(mInitialized); + return mMinTextModifiedOffset; + } + + // Returns true if layout of the character at the aOffset has not been + // calculated. + bool IsLayoutChangedAt(uint32_t aOffset) const + { + return IsLayoutChanged() && (mMinTextModifiedOffset <= aOffset); + } + // Returns true if layout of the content has been changed, i.e., the new + // layout has not been calculated. + bool IsLayoutChanged() const + { + return mInitialized && (mMinTextModifiedOffset != NOT_MODIFIED); + } + // Returns minimum offset of modified text range. + uint32_t MinOffsetOfLayoutChanged() const + { + return mInitialized ? mMinTextModifiedOffset : NOT_MODIFIED; + } + + TSFTextStore::Composition& Composition() { return mComposition; } + TSFTextStore::Selection& Selection() { return mSelection; } + + private: + nsString mText; + // mLastCompositionString stores the composition string when the document + // is locked. This is necessary to compute mMinTextModifiedOffset. + nsString mLastCompositionString; + TSFTextStore::Composition& mComposition; + TSFTextStore::Selection& mSelection; + + // The minimum offset of modified part of the text. + enum : uint32_t + { + NOT_MODIFIED = UINT32_MAX + }; + uint32_t mMinTextModifiedOffset; + + bool mInitialized; + }; + // mContentForTSF is cache of content. The information is expected by TSF + // and TIP. Therefore, this is useful for answering the query from TSF or + // TIP. + // This is initialized by ContentForTSFRef() automatically (therefore, don't + // access this member directly except at calling Clear(), IsInitialized(), + // IsLayoutChangeAfter() or IsLayoutChanged()). + // This is cleared when: + // - When there is no composition, the document is unlocked. + // - When there is a composition, all dispatched events are handled by + // the focused editor which may be in a remote process. + // So, if two compositions are created very quickly, this cache may not be + // cleared between eCompositionCommit(AsIs) and eCompositionStart. + Content mContentForTSF; + + Content& ContentForTSFRef(); + + // CanAccessActualContentDirectly() returns true when TSF/TIP can access + // actual content directly. In other words, mContentForTSF and/or + // mSelectionForTSF doesn't cache content or they matches with actual + // contents due to no pending text/selection change notifications. + bool CanAccessActualContentDirectly() const; + + // While mContentForTSF is valid, this returns the text stored by it. + // Otherwise, return the current text content retrieved by eQueryTextContent. + bool GetCurrentText(nsAString& aTextContent); + + class MouseTracker final + { + public: + static const DWORD kInvalidCookie = static_cast<DWORD>(-1); + + MouseTracker(); + + HRESULT Init(TSFTextStore* aTextStore); + HRESULT AdviseSink(TSFTextStore* aTextStore, + ITfRangeACP* aTextRange, ITfMouseSink* aMouseSink); + void UnadviseSink(); + + bool IsUsing() const { return mSink != nullptr; } + bool InRange(uint32_t aOffset) const + { + if (NS_WARN_IF(mStart < 0) || + NS_WARN_IF(mLength <= 0)) { + return false; + } + return aOffset >= static_cast<uint32_t>(mStart) && + aOffset < static_cast<uint32_t>(mStart + mLength); + } + DWORD Cookie() const { return mCookie; } + bool OnMouseButtonEvent(ULONG aEdge, ULONG aQuadrant, DWORD aButtonStatus); + LONG RangeStart() const { return mStart; } + + private: + RefPtr<ITfMouseSink> mSink; + LONG mStart; + LONG mLength; + DWORD mCookie; + }; + // mMouseTrackers is an array to store each information of installed + // ITfMouseSink instance. + nsTArray<MouseTracker> mMouseTrackers; + + // The input scopes for this context, defaults to IS_DEFAULT. + nsTArray<InputScope> mInputScopes; + + // Support retrieving attributes. + // TODO: We should support RightToLeft, perhaps. + enum + { + // Used for result of GetRequestedAttrIndex() + eNotSupported = -1, + + // Supported attributes + eInputScope = 0, + eTextVerticalWriting, + eTextOrientation, + + // Count of the supported attributes + NUM_OF_SUPPORTED_ATTRS + }; + bool mRequestedAttrs[NUM_OF_SUPPORTED_ATTRS]; + + int32_t GetRequestedAttrIndex(const TS_ATTRID& aAttrID); + TS_ATTRID GetAttrID(int32_t aIndex); + + bool mRequestedAttrValues; + + // If edit actions are being recorded without document lock, this is true. + // Otherwise, false. + bool mIsRecordingActionsWithoutLock; + // If GetTextExt() or GetACPFromPoint() is called and the layout hasn't been + // calculated yet, these methods return TS_E_NOLAYOUT. At that time, + // mHasReturnedNoLayoutError is set to true. + bool mHasReturnedNoLayoutError; + // Before calling ITextStoreACPSink::OnLayoutChange() and + // ITfContextOwnerServices::OnLayoutChange(), mWaitingQueryLayout is set to + // true. This is set to false when GetTextExt() or GetACPFromPoint() is + // called. + bool mWaitingQueryLayout; + // During the documet is locked, we shouldn't destroy the instance. + // If this is true, the instance will be destroyed after unlocked. + bool mPendingDestroy; + // If this is false, MaybeFlushPendingNotifications() will clear the + // mContentForTSF. + bool mDeferClearingContentForTSF; + // While there is native caret, this is true. Otherwise, false. + bool mNativeCaretIsCreated; + // While the instance is dispatching events, the event may not be handled + // synchronously in e10s mode. So, in such case, in strictly speaking, + // we shouldn't query layout information. However, TS_E_NOLAYOUT bugs of + // ITextStoreAPC::GetTextExt() blocks us to behave ideally. + // For preventing it to be called, we should put off notifying TSF of + // anything until layout information becomes available. + bool mDeferNotifyingTSF; + // While the document is locked, committing composition always fails since + // TSF needs another document lock for modifying the composition, selection + // and etc. So, committing composition should be performed after the + // document is unlocked. + bool mDeferCommittingComposition; + bool mDeferCancellingComposition; + // Immediately after a call of Destroy(), mDestroyed becomes true. If this + // is true, the instance shouldn't grant any requests from the TIP anymore. + bool mDestroyed; + // While the instance is being destroyed, this is set to true for avoiding + // recursive Destroy() calls. + bool mBeingDestroyed; + + + // TSF thread manager object for the current application + static StaticRefPtr<ITfThreadMgr> sThreadMgr; + // sMessagePump is QI'ed from sThreadMgr + static StaticRefPtr<ITfMessagePump> sMessagePump; + // sKeystrokeMgr is QI'ed from sThreadMgr + static StaticRefPtr<ITfKeystrokeMgr> sKeystrokeMgr; + // TSF display attribute manager + static StaticRefPtr<ITfDisplayAttributeMgr> sDisplayAttrMgr; + // TSF category manager + static StaticRefPtr<ITfCategoryMgr> sCategoryMgr; + + // Current text store which is managing a keyboard enabled editor (i.e., + // editable editor). Currently only ONE TSFTextStore instance is ever used, + // although Create is called when an editor is focused and Destroy called + // when the focused editor is blurred. + static StaticRefPtr<TSFTextStore> sEnabledTextStore; + + // For IME (keyboard) disabled state: + static StaticRefPtr<ITfDocumentMgr> sDisabledDocumentMgr; + static StaticRefPtr<ITfContext> sDisabledContext; + + static StaticRefPtr<ITfInputProcessorProfiles> sInputProcessorProfiles; + + // TSF client ID for the current application + static DWORD sClientId; + + // Enables/Disables hack for specific TIP. + static bool sCreateNativeCaretForLegacyATOK; + static bool sDoNotReturnNoLayoutErrorToATOKOfCompositionString; + static bool sDoNotReturnNoLayoutErrorToMSSimplifiedTIP; + static bool sDoNotReturnNoLayoutErrorToMSTraditionalTIP; + static bool sDoNotReturnNoLayoutErrorToFreeChangJie; + static bool sDoNotReturnNoLayoutErrorToEasyChangjei; + static bool sDoNotReturnNoLayoutErrorToMSJapaneseIMEAtFirstChar; + static bool sDoNotReturnNoLayoutErrorToMSJapaneseIMEAtCaret; + static bool sHackQueryInsertForMSSimplifiedTIP; + static bool sHackQueryInsertForMSTraditionalTIP; +}; + +} // namespace widget +} // namespace mozilla + +#endif // #ifndef TSFTextStore_h_ diff --git a/widget/windows/TaskbarPreview.cpp b/widget/windows/TaskbarPreview.cpp new file mode 100644 index 0000000000..c897af0212 --- /dev/null +++ b/widget/windows/TaskbarPreview.cpp @@ -0,0 +1,431 @@ +/* vim: se cin sw=2 ts=2 et : */ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "TaskbarPreview.h" +#include <nsITaskbarPreviewController.h> +#include <windows.h> + +#include <nsError.h> +#include <nsCOMPtr.h> +#include <nsIWidget.h> +#include <nsIBaseWindow.h> +#include <nsIObserverService.h> +#include <nsServiceManagerUtils.h> + +#include "nsUXThemeData.h" +#include "nsWindow.h" +#include "nsAppShell.h" +#include "TaskbarPreviewButton.h" +#include "WinUtils.h" + +#include "mozilla/dom/HTMLCanvasElement.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/DataSurfaceHelpers.h" +#include "mozilla/Telemetry.h" + +// Defined in dwmapi in a header that needs a higher numbered _WINNT #define +#define DWM_SIT_DISPLAYFRAME 0x1 + +namespace mozilla { +namespace widget { + +/////////////////////////////////////////////////////////////////////////////// +// TaskbarPreview + +TaskbarPreview::TaskbarPreview(ITaskbarList4 *aTaskbar, nsITaskbarPreviewController *aController, HWND aHWND, nsIDocShell *aShell) + : mTaskbar(aTaskbar), + mController(aController), + mWnd(aHWND), + mVisible(false), + mDocShell(do_GetWeakReference(aShell)) +{ + // TaskbarPreview may outlive the WinTaskbar that created it + ::CoInitialize(nullptr); + + WindowHook &hook = GetWindowHook(); + hook.AddMonitor(WM_DESTROY, MainWindowHook, this); +} + +TaskbarPreview::~TaskbarPreview() { + // Avoid dangling pointer + if (sActivePreview == this) + sActivePreview = nullptr; + + // Our subclass should have invoked DetachFromNSWindow already. + NS_ASSERTION(!mWnd, "TaskbarPreview::DetachFromNSWindow was not called before destruction"); + + // Make sure to release before potentially uninitializing COM + mTaskbar = nullptr; + + ::CoUninitialize(); +} + +NS_IMETHODIMP +TaskbarPreview::SetController(nsITaskbarPreviewController *aController) { + NS_ENSURE_ARG(aController); + + mController = aController; + return NS_OK; +} + +NS_IMETHODIMP +TaskbarPreview::GetController(nsITaskbarPreviewController **aController) { + NS_ADDREF(*aController = mController); + return NS_OK; +} + +NS_IMETHODIMP +TaskbarPreview::GetTooltip(nsAString &aTooltip) { + aTooltip = mTooltip; + return NS_OK; +} + +NS_IMETHODIMP +TaskbarPreview::SetTooltip(const nsAString &aTooltip) { + mTooltip = aTooltip; + return CanMakeTaskbarCalls() ? UpdateTooltip() : NS_OK; +} + +NS_IMETHODIMP +TaskbarPreview::SetVisible(bool visible) { + if (mVisible == visible) return NS_OK; + mVisible = visible; + + // If the nsWindow has already been destroyed but the caller is still trying + // to use it then just pretend that everything succeeded. The caller doesn't + // actually have a way to detect this since it's the same case as when we + // CanMakeTaskbarCalls returns false. + if (!mWnd) + return NS_OK; + + return visible ? Enable() : Disable(); +} + +NS_IMETHODIMP +TaskbarPreview::GetVisible(bool *visible) { + *visible = mVisible; + return NS_OK; +} + +NS_IMETHODIMP +TaskbarPreview::SetActive(bool active) { + if (active) + sActivePreview = this; + else if (sActivePreview == this) + sActivePreview = nullptr; + + return CanMakeTaskbarCalls() ? ShowActive(active) : NS_OK; +} + +NS_IMETHODIMP +TaskbarPreview::GetActive(bool *active) { + *active = sActivePreview == this; + return NS_OK; +} + +NS_IMETHODIMP +TaskbarPreview::Invalidate() { + if (!mVisible) + return NS_OK; + + // DWM Composition is required for previews + if (!nsUXThemeData::CheckForCompositor()) + return NS_OK; + + HWND previewWindow = PreviewWindow(); + return FAILED(WinUtils::dwmInvalidateIconicBitmapsPtr(previewWindow)) + ? NS_ERROR_FAILURE + : NS_OK; +} + +nsresult +TaskbarPreview::UpdateTaskbarProperties() { + nsresult rv = UpdateTooltip(); + + // If we are the active preview and our window is the active window, restore + // our active state - otherwise some other non-preview window is now active + // and should be displayed as so. + if (sActivePreview == this) { + if (mWnd == ::GetActiveWindow()) { + nsresult rvActive = ShowActive(true); + if (NS_FAILED(rvActive)) + rv = rvActive; + } else { + sActivePreview = nullptr; + } + } + return rv; +} + +nsresult +TaskbarPreview::Enable() { + nsresult rv = NS_OK; + if (CanMakeTaskbarCalls()) { + rv = UpdateTaskbarProperties(); + } else { + WindowHook &hook = GetWindowHook(); + hook.AddMonitor(nsAppShell::GetTaskbarButtonCreatedMessage(), MainWindowHook, this); + } + return rv; +} + +nsresult +TaskbarPreview::Disable() { + if (!IsWindowAvailable()) { + // Window is already destroyed + return NS_OK; + } + + WindowHook &hook = GetWindowHook(); + (void) hook.RemoveMonitor(nsAppShell::GetTaskbarButtonCreatedMessage(), MainWindowHook, this); + + return NS_OK; +} + +bool +TaskbarPreview::IsWindowAvailable() const { + if (mWnd) { + nsWindow* win = WinUtils::GetNSWindowPtr(mWnd); + if(win && !win->Destroyed()) { + return true; + } + } + return false; +} + +void +TaskbarPreview::DetachFromNSWindow() { + WindowHook &hook = GetWindowHook(); + hook.RemoveMonitor(WM_DESTROY, MainWindowHook, this); + mWnd = nullptr; +} + +LRESULT +TaskbarPreview::WndProc(UINT nMsg, WPARAM wParam, LPARAM lParam) { + switch (nMsg) { + case WM_DWMSENDICONICTHUMBNAIL: + { + uint32_t width = HIWORD(lParam); + uint32_t height = LOWORD(lParam); + float aspectRatio = width/float(height); + + nsresult rv; + float preferredAspectRatio; + rv = mController->GetThumbnailAspectRatio(&preferredAspectRatio); + if (NS_FAILED(rv)) + break; + + uint32_t thumbnailWidth = width; + uint32_t thumbnailHeight = height; + + if (aspectRatio > preferredAspectRatio) { + thumbnailWidth = uint32_t(thumbnailHeight * preferredAspectRatio); + } else { + thumbnailHeight = uint32_t(thumbnailWidth / preferredAspectRatio); + } + + DrawBitmap(thumbnailWidth, thumbnailHeight, false); + } + break; + case WM_DWMSENDICONICLIVEPREVIEWBITMAP: + { + uint32_t width, height; + nsresult rv; + rv = mController->GetWidth(&width); + if (NS_FAILED(rv)) + break; + rv = mController->GetHeight(&height); + if (NS_FAILED(rv)) + break; + + double scale = nsIWidget::DefaultScaleOverride(); + if (scale <= 0.0) { + scale = WinUtils::LogToPhysFactor(PreviewWindow()); + } + DrawBitmap(NSToIntRound(scale * width), NSToIntRound(scale * height), true); + } + break; + } + return ::DefWindowProcW(PreviewWindow(), nMsg, wParam, lParam); +} + +bool +TaskbarPreview::CanMakeTaskbarCalls() { + // If the nsWindow has already been destroyed and we know it but our caller + // clearly doesn't so we can't make any calls. + if (!mWnd) + return false; + // Certain functions like SetTabOrder seem to require a visible window. During + // window close, the window seems to be hidden before being destroyed. + if (!::IsWindowVisible(mWnd)) + return false; + if (mVisible) { + nsWindow *window = WinUtils::GetNSWindowPtr(mWnd); + NS_ASSERTION(window, "Could not get nsWindow from HWND"); + return window->HasTaskbarIconBeenCreated(); + } + return false; +} + +WindowHook& +TaskbarPreview::GetWindowHook() { + nsWindow *window = WinUtils::GetNSWindowPtr(mWnd); + NS_ASSERTION(window, "Cannot use taskbar previews in an embedded context!"); + + return window->GetWindowHook(); +} + +void +TaskbarPreview::EnableCustomDrawing(HWND aHWND, bool aEnable) { + BOOL enabled = aEnable; + WinUtils::dwmSetWindowAttributePtr( + aHWND, + DWMWA_FORCE_ICONIC_REPRESENTATION, + &enabled, + sizeof(enabled)); + + WinUtils::dwmSetWindowAttributePtr( + aHWND, + DWMWA_HAS_ICONIC_BITMAP, + &enabled, + sizeof(enabled)); +} + + +nsresult +TaskbarPreview::UpdateTooltip() { + NS_ASSERTION(CanMakeTaskbarCalls() && mVisible, "UpdateTooltip called on invisible tab preview"); + + if (FAILED(mTaskbar->SetThumbnailTooltip(PreviewWindow(), mTooltip.get()))) + return NS_ERROR_FAILURE; + return NS_OK; +} + +void +TaskbarPreview::DrawBitmap(uint32_t width, uint32_t height, bool isPreview) { + nsresult rv; + nsCOMPtr<nsITaskbarPreviewCallback> callback = + do_CreateInstance("@mozilla.org/widget/taskbar-preview-callback;1", &rv); + if (NS_FAILED(rv)) { + return; + } + + ((TaskbarPreviewCallback*)callback.get())->SetPreview(this); + + if (isPreview) { + ((TaskbarPreviewCallback*)callback.get())->SetIsPreview(); + mController->RequestPreview(callback); + } else { + mController->RequestThumbnail(callback, width, height); + } +} + +/////////////////////////////////////////////////////////////////////////////// +// TaskbarPreviewCallback + +NS_IMPL_ISUPPORTS(TaskbarPreviewCallback, nsITaskbarPreviewCallback) + +/* void done (in nsISupports aCanvas, in boolean aDrawBorder); */ +NS_IMETHODIMP +TaskbarPreviewCallback::Done(nsISupports *aCanvas, bool aDrawBorder) { + // We create and destroy TaskbarTabPreviews from front end code in response + // to TabOpen and TabClose events. Each TaskbarTabPreview creates and owns a + // proxy HWND which it hands to Windows as a tab identifier. When a tab + // closes, TaskbarTabPreview Disable() method is called by front end, which + // destroys the proxy window and clears mProxyWindow which is the HWND + // returned from PreviewWindow(). So, since this is async, we should check to + // be sure the tab is still alive before doing all this gfx work and making + // dwm calls. To accomplish this we check the result of PreviewWindow(). + if (!aCanvas || !mPreview || !mPreview->PreviewWindow() || + !mPreview->IsWindowAvailable()) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIDOMHTMLCanvasElement> domcanvas(do_QueryInterface(aCanvas)); + dom::HTMLCanvasElement * canvas = ((dom::HTMLCanvasElement*)domcanvas.get()); + if (!canvas) { + return NS_ERROR_FAILURE; + } + + RefPtr<gfx::SourceSurface> source = canvas->GetSurfaceSnapshot(); + if (!source) { + return NS_ERROR_FAILURE; + } + RefPtr<gfxWindowsSurface> target = new gfxWindowsSurface(source->GetSize(), + gfx::SurfaceFormat::A8R8G8B8_UINT32); + if (!target) { + return NS_ERROR_FAILURE; + } + + RefPtr<gfx::DataSourceSurface> srcSurface = source->GetDataSurface(); + RefPtr<gfxImageSurface> imageSurface = target->GetAsImageSurface(); + if (!srcSurface || !imageSurface) { + return NS_ERROR_FAILURE; + } + + gfx::DataSourceSurface::MappedSurface sourceMap; + srcSurface->Map(gfx::DataSourceSurface::READ, &sourceMap); + mozilla::gfx::CopySurfaceDataToPackedArray(sourceMap.mData, + imageSurface->Data(), + srcSurface->GetSize(), + sourceMap.mStride, + BytesPerPixel(srcSurface->GetFormat())); + srcSurface->Unmap(); + + HDC hDC = target->GetDC(); + HBITMAP hBitmap = (HBITMAP)GetCurrentObject(hDC, OBJ_BITMAP); + + DWORD flags = aDrawBorder ? DWM_SIT_DISPLAYFRAME : 0; + POINT pptClient = { 0, 0 }; + HRESULT hr; + if (!mIsThumbnail) { + hr = WinUtils::dwmSetIconicLivePreviewBitmapPtr(mPreview->PreviewWindow(), + hBitmap, &pptClient, flags); + } else { + hr = WinUtils::dwmSetIconicThumbnailPtr(mPreview->PreviewWindow(), + hBitmap, flags); + } + MOZ_ASSERT(SUCCEEDED(hr)); + return NS_OK; +} + +/* static */ +bool +TaskbarPreview::MainWindowHook(void *aContext, + HWND hWnd, UINT nMsg, + WPARAM wParam, LPARAM lParam, + LRESULT *aResult) +{ + NS_ASSERTION(nMsg == nsAppShell::GetTaskbarButtonCreatedMessage() || + nMsg == WM_DESTROY, + "Window hook proc called with wrong message"); + NS_ASSERTION(aContext, "Null context in MainWindowHook"); + if (!aContext) + return false; + TaskbarPreview *preview = reinterpret_cast<TaskbarPreview*>(aContext); + if (nMsg == WM_DESTROY) { + // nsWindow is being destroyed + // We can't really do anything at this point including removing hooks + return false; + } else { + nsWindow *window = WinUtils::GetNSWindowPtr(preview->mWnd); + if (window) { + window->SetHasTaskbarIconBeenCreated(); + + if (preview->mVisible) + preview->UpdateTaskbarProperties(); + } + } + return false; +} + +TaskbarPreview * +TaskbarPreview::sActivePreview = nullptr; + +} // namespace widget +} // namespace mozilla + diff --git a/widget/windows/TaskbarPreview.h b/widget/windows/TaskbarPreview.h new file mode 100644 index 0000000000..d5f6b86f19 --- /dev/null +++ b/widget/windows/TaskbarPreview.h @@ -0,0 +1,140 @@ +/* vim: se cin sw=2 ts=2 et : */ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __mozilla_widget_TaskbarPreview_h__ +#define __mozilla_widget_TaskbarPreview_h__ + +#include <windows.h> +#include <shobjidl.h> +#undef LogSeverity // SetupAPI.h #defines this as DWORD + +#include "mozilla/RefPtr.h" +#include <nsITaskbarPreview.h> +#include <nsITaskbarPreviewController.h> +#include <nsString.h> +#include <nsWeakPtr.h> +#include <nsIDocShell.h> +#include "WindowHook.h" + +namespace mozilla { +namespace widget { + +class TaskbarPreviewCallback; + +class TaskbarPreview : public nsITaskbarPreview +{ +public: + TaskbarPreview(ITaskbarList4 *aTaskbar, nsITaskbarPreviewController *aController, HWND aHWND, nsIDocShell *aShell); + + friend class TaskbarPreviewCallback; + + NS_DECL_NSITASKBARPREVIEW + +protected: + virtual ~TaskbarPreview(); + + // Called to update ITaskbarList4 dependent properties + virtual nsresult UpdateTaskbarProperties(); + + // Invoked when the preview is made visible + virtual nsresult Enable(); + // Invoked when the preview is made invisible + virtual nsresult Disable(); + + // Detaches this preview from the nsWindow instance it's tied to + virtual void DetachFromNSWindow(); + + // Determines if the window is available and a destroy has not yet started + bool IsWindowAvailable() const; + + // Marks this preview as being active + virtual nsresult ShowActive(bool active) = 0; + // Gets a reference to the window used to handle the preview messages + virtual HWND& PreviewWindow() = 0; + + // Window procedure for the PreviewWindow (hooked for window previews) + virtual LRESULT WndProc(UINT nMsg, WPARAM wParam, LPARAM lParam); + + // Returns whether or not the taskbar icon has been created for mWnd The + // ITaskbarList4 API requires that we wait until the icon has been created + // before we can call its methods. + bool CanMakeTaskbarCalls(); + + // Gets the WindowHook for the nsWindow + WindowHook &GetWindowHook(); + + // Enables/disables custom drawing for the given window + static void EnableCustomDrawing(HWND aHWND, bool aEnable); + + // MSCOM Taskbar interface + RefPtr<ITaskbarList4> mTaskbar; + // Controller for this preview + nsCOMPtr<nsITaskbarPreviewController> mController; + // The HWND to the nsWindow that this object previews + HWND mWnd; + // Whether or not this preview is visible + bool mVisible; + +private: + // Called when the tooltip should be updated + nsresult UpdateTooltip(); + + // Requests the controller to draw into a canvas of the given width and + // height. The resulting bitmap is sent to the DWM to display. + void DrawBitmap(uint32_t width, uint32_t height, bool isPreview); + + // WindowHook procedure for hooking mWnd + static bool MainWindowHook(void *aContext, + HWND hWnd, UINT nMsg, + WPARAM wParam, LPARAM lParam, + LRESULT *aResult); + + // Docshell corresponding to the <window> the nsWindow contains + nsWeakPtr mDocShell; + nsString mTooltip; + + // The preview currently marked as active in the taskbar. nullptr if no + // preview is active (some other window is). + static TaskbarPreview *sActivePreview; +}; + +/* + * Callback object TaskbarPreview hands to preview controllers when we + * request async thumbnail or live preview images. Controllers invoke + * this interface once they have aquired the requested image. + */ +class TaskbarPreviewCallback : public nsITaskbarPreviewCallback +{ +public: + TaskbarPreviewCallback() : + mIsThumbnail(true) { + } + + NS_DECL_ISUPPORTS + NS_DECL_NSITASKBARPREVIEWCALLBACK + + void SetPreview(TaskbarPreview* aPreview) { + mPreview = aPreview; + } + + void SetIsPreview() { + mIsThumbnail = false; + } + +protected: + virtual ~TaskbarPreviewCallback() {} + +private: + RefPtr<TaskbarPreview> mPreview; + bool mIsThumbnail; +}; + +} // namespace widget +} // namespace mozilla + +#endif /* __mozilla_widget_TaskbarPreview_h__ */ + diff --git a/widget/windows/TaskbarPreviewButton.cpp b/widget/windows/TaskbarPreviewButton.cpp new file mode 100644 index 0000000000..69cea38092 --- /dev/null +++ b/widget/windows/TaskbarPreviewButton.cpp @@ -0,0 +1,145 @@ +/* vim: se cin sw=2 ts=2 et : */ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <windows.h> +#include <strsafe.h> + +#include "TaskbarWindowPreview.h" +#include "TaskbarPreviewButton.h" +#include "nsWindowGfx.h" +#include <imgIContainer.h> + +namespace mozilla { +namespace widget { + +NS_IMPL_ISUPPORTS(TaskbarPreviewButton, nsITaskbarPreviewButton, nsISupportsWeakReference) + +TaskbarPreviewButton::TaskbarPreviewButton(TaskbarWindowPreview* preview, uint32_t index) + : mPreview(preview), mIndex(index) +{ +} + +TaskbarPreviewButton::~TaskbarPreviewButton() { + SetVisible(false); +} + +NS_IMETHODIMP +TaskbarPreviewButton::GetTooltip(nsAString &aTooltip) { + aTooltip = mTooltip; + return NS_OK; +} + +NS_IMETHODIMP +TaskbarPreviewButton::SetTooltip(const nsAString &aTooltip) { + mTooltip = aTooltip; + size_t destLength = sizeof Button().szTip / (sizeof Button().szTip[0]); + wchar_t *tooltip = &(Button().szTip[0]); + StringCchCopyNW(tooltip, + destLength, + mTooltip.get(), + mTooltip.Length()); + return Update(); +} + +NS_IMETHODIMP +TaskbarPreviewButton::GetDismissOnClick(bool *dismiss) { + *dismiss = (Button().dwFlags & THBF_DISMISSONCLICK) == THBF_DISMISSONCLICK; + return NS_OK; +} + +NS_IMETHODIMP +TaskbarPreviewButton::SetDismissOnClick(bool dismiss) { + if (dismiss) + Button().dwFlags |= THBF_DISMISSONCLICK; + else + Button().dwFlags &= ~THBF_DISMISSONCLICK; + return Update(); +} + +NS_IMETHODIMP +TaskbarPreviewButton::GetHasBorder(bool *hasBorder) { + *hasBorder = (Button().dwFlags & THBF_NOBACKGROUND) != THBF_NOBACKGROUND; + return NS_OK; +} + +NS_IMETHODIMP +TaskbarPreviewButton::SetHasBorder(bool hasBorder) { + if (hasBorder) + Button().dwFlags &= ~THBF_NOBACKGROUND; + else + Button().dwFlags |= THBF_NOBACKGROUND; + return Update(); +} + +NS_IMETHODIMP +TaskbarPreviewButton::GetDisabled(bool *disabled) { + *disabled = (Button().dwFlags & THBF_DISABLED) == THBF_DISABLED; + return NS_OK; +} + +NS_IMETHODIMP +TaskbarPreviewButton::SetDisabled(bool disabled) { + if (disabled) + Button().dwFlags |= THBF_DISABLED; + else + Button().dwFlags &= ~THBF_DISABLED; + return Update(); +} + +NS_IMETHODIMP +TaskbarPreviewButton::GetImage(imgIContainer **img) { + if (mImage) + NS_ADDREF(*img = mImage); + else + *img = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +TaskbarPreviewButton::SetImage(imgIContainer *img) { + if (Button().hIcon) + ::DestroyIcon(Button().hIcon); + if (img) { + nsresult rv; + rv = nsWindowGfx::CreateIcon(img, false, 0, 0, + nsWindowGfx::GetIconMetrics(nsWindowGfx::kRegularIcon), + &Button().hIcon); + NS_ENSURE_SUCCESS(rv, rv); + } else { + Button().hIcon = nullptr; + } + return Update(); +} + +NS_IMETHODIMP +TaskbarPreviewButton::GetVisible(bool *visible) { + *visible = (Button().dwFlags & THBF_HIDDEN) != THBF_HIDDEN; + return NS_OK; +} + +NS_IMETHODIMP +TaskbarPreviewButton::SetVisible(bool visible) { + if (visible) + Button().dwFlags &= ~THBF_HIDDEN; + else + Button().dwFlags |= THBF_HIDDEN; + return Update(); +} + +THUMBBUTTON& +TaskbarPreviewButton::Button() { + return mPreview->mThumbButtons[mIndex]; +} + +nsresult +TaskbarPreviewButton::Update() { + return mPreview->UpdateButton(mIndex); +} + +} // namespace widget +} // namespace mozilla + diff --git a/widget/windows/TaskbarPreviewButton.h b/widget/windows/TaskbarPreviewButton.h new file mode 100644 index 0000000000..2b4342d157 --- /dev/null +++ b/widget/windows/TaskbarPreviewButton.h @@ -0,0 +1,48 @@ +/* vim: se cin sw=2 ts=2 et : */ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __mozilla_widget_TaskbarPreviewButton_h__ +#define __mozilla_widget_TaskbarPreviewButton_h__ + +#include <windows.h> +#include <shobjidl.h> +#undef LogSeverity // SetupAPI.h #defines this as DWORD + +#include "mozilla/RefPtr.h" +#include <nsITaskbarPreviewButton.h> +#include <nsString.h> +#include <nsWeakReference.h> + +namespace mozilla { +namespace widget { + +class TaskbarWindowPreview; +class TaskbarPreviewButton : public nsITaskbarPreviewButton, public nsSupportsWeakReference +{ + virtual ~TaskbarPreviewButton(); + +public: + TaskbarPreviewButton(TaskbarWindowPreview* preview, uint32_t index); + + NS_DECL_ISUPPORTS + NS_DECL_NSITASKBARPREVIEWBUTTON + +private: + THUMBBUTTON& Button(); + nsresult Update(); + + RefPtr<TaskbarWindowPreview> mPreview; + uint32_t mIndex; + nsString mTooltip; + nsCOMPtr<imgIContainer> mImage; +}; + +} // namespace widget +} // namespace mozilla + +#endif /* __mozilla_widget_TaskbarPreviewButton_h__ */ + diff --git a/widget/windows/TaskbarTabPreview.cpp b/widget/windows/TaskbarTabPreview.cpp new file mode 100644 index 0000000000..c89fecb32e --- /dev/null +++ b/widget/windows/TaskbarTabPreview.cpp @@ -0,0 +1,357 @@ +/* vim: se cin sw=2 ts=2 et : */ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "TaskbarTabPreview.h" +#include "nsWindowGfx.h" +#include "nsUXThemeData.h" +#include "WinUtils.h" +#include <nsITaskbarPreviewController.h> + +#define TASKBARPREVIEW_HWNDID L"TaskbarTabPreviewHwnd" + +namespace mozilla { +namespace widget { + +NS_IMPL_ISUPPORTS(TaskbarTabPreview, nsITaskbarTabPreview) + +const wchar_t *const kWindowClass = L"MozillaTaskbarPreviewClass"; + +TaskbarTabPreview::TaskbarTabPreview(ITaskbarList4 *aTaskbar, nsITaskbarPreviewController *aController, HWND aHWND, nsIDocShell *aShell) + : TaskbarPreview(aTaskbar, aController, aHWND, aShell), + mProxyWindow(nullptr), + mIcon(nullptr), + mRegistered(false) +{ + WindowHook &hook = GetWindowHook(); + hook.AddMonitor(WM_WINDOWPOSCHANGED, MainWindowHook, this); +} + +TaskbarTabPreview::~TaskbarTabPreview() { + if (mIcon) { + ::DestroyIcon(mIcon); + mIcon = nullptr; + } + + // We need to ensure that proxy window disappears or else Bad Things happen. + if (mProxyWindow) + Disable(); + + NS_ASSERTION(!mProxyWindow, "Taskbar proxy window was not destroyed!"); + + if (IsWindowAvailable()) { + DetachFromNSWindow(); + } else { + mWnd = nullptr; + } +} + +nsresult +TaskbarTabPreview::ShowActive(bool active) { + NS_ASSERTION(mVisible && CanMakeTaskbarCalls(), "ShowActive called on invisible window or before taskbar calls can be made for this window"); + return FAILED(mTaskbar->SetTabActive(active ? mProxyWindow : nullptr, + mWnd, 0)) ? NS_ERROR_FAILURE : NS_OK; +} + +HWND & +TaskbarTabPreview::PreviewWindow() { + return mProxyWindow; +} + +nativeWindow +TaskbarTabPreview::GetHWND() { + return mProxyWindow; +} + +void +TaskbarTabPreview::EnsureRegistration() { + NS_ASSERTION(mVisible && CanMakeTaskbarCalls(), "EnsureRegistration called when it is not safe to do so"); + + (void) UpdateTaskbarProperties(); +} + +NS_IMETHODIMP +TaskbarTabPreview::GetTitle(nsAString &aTitle) { + aTitle = mTitle; + return NS_OK; +} + +NS_IMETHODIMP +TaskbarTabPreview::SetTitle(const nsAString &aTitle) { + mTitle = aTitle; + return mVisible ? UpdateTitle() : NS_OK; +} + +NS_IMETHODIMP +TaskbarTabPreview::SetIcon(imgIContainer *icon) { + HICON hIcon = nullptr; + if (icon) { + nsresult rv; + rv = nsWindowGfx::CreateIcon(icon, false, 0, 0, + nsWindowGfx::GetIconMetrics(nsWindowGfx::kSmallIcon), + &hIcon); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (mIcon) + ::DestroyIcon(mIcon); + mIcon = hIcon; + mIconImage = icon; + return mVisible ? UpdateIcon() : NS_OK; +} + +NS_IMETHODIMP +TaskbarTabPreview::GetIcon(imgIContainer **icon) { + NS_IF_ADDREF(*icon = mIconImage); + return NS_OK; +} + +NS_IMETHODIMP +TaskbarTabPreview::Move(nsITaskbarTabPreview *aNext) { + if (aNext == this) + return NS_ERROR_INVALID_ARG; + mNext = aNext; + return CanMakeTaskbarCalls() ? UpdateNext() : NS_OK; +} + +nsresult +TaskbarTabPreview::UpdateTaskbarProperties() { + if (mRegistered) + return NS_OK; + + if (FAILED(mTaskbar->RegisterTab(mProxyWindow, mWnd))) + return NS_ERROR_FAILURE; + + nsresult rv = UpdateNext(); + NS_ENSURE_SUCCESS(rv, rv); + rv = TaskbarPreview::UpdateTaskbarProperties(); + mRegistered = true; + return rv; +} + +LRESULT +TaskbarTabPreview::WndProc(UINT nMsg, WPARAM wParam, LPARAM lParam) { + RefPtr<TaskbarTabPreview> kungFuDeathGrip(this); + switch (nMsg) { + case WM_CREATE: + TaskbarPreview::EnableCustomDrawing(mProxyWindow, true); + return 0; + case WM_CLOSE: + mController->OnClose(); + return 0; + case WM_ACTIVATE: + if (LOWORD(wParam) == WA_ACTIVE) { + // Activate the tab the user selected then restore the main window, + // keeping normal/max window state intact. + bool activateWindow; + nsresult rv = mController->OnActivate(&activateWindow); + if (NS_SUCCEEDED(rv) && activateWindow) { + nsWindow* win = WinUtils::GetNSWindowPtr(mWnd); + if (win) { + nsWindow * parent = win->GetTopLevelWindow(true); + if (parent) { + parent->Show(true); + } + } + } + } + return 0; + case WM_GETICON: + return (LRESULT)mIcon; + case WM_SYSCOMMAND: + // Send activation events to the top level window and select the proper + // tab through the controller. + if (wParam == SC_RESTORE || wParam == SC_MAXIMIZE) { + bool activateWindow; + nsresult rv = mController->OnActivate(&activateWindow); + if (NS_SUCCEEDED(rv) && activateWindow) { + // Note, restoring an iconic, maximized window here will only + // activate the maximized window. This is not a bug, it's default + // windows behavior. + ::SendMessageW(mWnd, WM_SYSCOMMAND, wParam, lParam); + } + return 0; + } + // Forward everything else to the top level window. Do not forward + // close since that's intended for the tab. When the preview proxy + // closes, we'll close the tab above. + return wParam == SC_CLOSE + ? ::DefWindowProcW(mProxyWindow, WM_SYSCOMMAND, wParam, lParam) + : ::SendMessageW(mWnd, WM_SYSCOMMAND, wParam, lParam); + return 0; + } + return TaskbarPreview::WndProc(nMsg, wParam, lParam); +} + +/* static */ +LRESULT CALLBACK +TaskbarTabPreview::GlobalWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam) { + TaskbarTabPreview *preview(nullptr); + if (nMsg == WM_CREATE) { + CREATESTRUCT *cs = reinterpret_cast<CREATESTRUCT*>(lParam); + preview = reinterpret_cast<TaskbarTabPreview*>(cs->lpCreateParams); + if (!::SetPropW(hWnd, TASKBARPREVIEW_HWNDID, preview)) + NS_ERROR("Could not associate native window with tab preview"); + preview->mProxyWindow = hWnd; + } else { + preview = reinterpret_cast<TaskbarTabPreview*>(::GetPropW(hWnd, TASKBARPREVIEW_HWNDID)); + if (nMsg == WM_DESTROY) + ::RemovePropW(hWnd, TASKBARPREVIEW_HWNDID); + } + + if (preview) + return preview->WndProc(nMsg, wParam, lParam); + return ::DefWindowProcW(hWnd, nMsg, wParam, lParam); +} + +nsresult +TaskbarTabPreview::Enable() { + WNDCLASSW wc; + HINSTANCE module = GetModuleHandle(nullptr); + + if (!GetClassInfoW(module, kWindowClass, &wc)) { + wc.style = 0; + wc.lpfnWndProc = GlobalWndProc; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hInstance = module; + wc.hIcon = nullptr; + wc.hCursor = nullptr; + wc.hbrBackground = (HBRUSH) nullptr; + wc.lpszMenuName = (LPCWSTR) nullptr; + wc.lpszClassName = kWindowClass; + RegisterClassW(&wc); + } + ::CreateWindowW(kWindowClass, L"TaskbarPreviewWindow", + WS_CAPTION | WS_SYSMENU, 0, 0, 200, 60, + nullptr, nullptr, module, this); + // GlobalWndProc will set mProxyWindow so that WM_CREATE can have a valid HWND + if (!mProxyWindow) + return NS_ERROR_INVALID_ARG; + + UpdateProxyWindowStyle(); + + nsresult rv = TaskbarPreview::Enable(); + nsresult rvUpdate; + rvUpdate = UpdateTitle(); + if (NS_FAILED(rvUpdate)) + rv = rvUpdate; + + rvUpdate = UpdateIcon(); + if (NS_FAILED(rvUpdate)) + rv = rvUpdate; + + return rv; +} + +nsresult +TaskbarTabPreview::Disable() { + // TaskbarPreview::Disable assumes that mWnd is valid but this method can be + // called when it is null iff the nsWindow has already been destroyed and we + // are still visible for some reason during object destruction. + if (mWnd) + TaskbarPreview::Disable(); + + if (FAILED(mTaskbar->UnregisterTab(mProxyWindow))) + return NS_ERROR_FAILURE; + mRegistered = false; + + // TaskbarPreview::WndProc will set mProxyWindow to null + if (!DestroyWindow(mProxyWindow)) + return NS_ERROR_FAILURE; + mProxyWindow = nullptr; + return NS_OK; +} + +void +TaskbarTabPreview::DetachFromNSWindow() { + (void) SetVisible(false); + WindowHook &hook = GetWindowHook(); + hook.RemoveMonitor(WM_WINDOWPOSCHANGED, MainWindowHook, this); + + TaskbarPreview::DetachFromNSWindow(); +} + +/* static */ +bool +TaskbarTabPreview::MainWindowHook(void *aContext, + HWND hWnd, UINT nMsg, + WPARAM wParam, LPARAM lParam, + LRESULT *aResult) { + if (nMsg == WM_WINDOWPOSCHANGED) { + TaskbarTabPreview *preview = reinterpret_cast<TaskbarTabPreview*>(aContext); + WINDOWPOS *pos = reinterpret_cast<WINDOWPOS*>(lParam); + if (SWP_FRAMECHANGED == (pos->flags & SWP_FRAMECHANGED)) + preview->UpdateProxyWindowStyle(); + } else { + NS_NOTREACHED("Style changed hook fired on non-style changed message"); + } + return false; +} + +void +TaskbarTabPreview::UpdateProxyWindowStyle() { + if (!mProxyWindow) + return; + + DWORD minMaxMask = WS_MINIMIZE | WS_MAXIMIZE; + DWORD windowStyle = GetWindowLongW(mWnd, GWL_STYLE); + + DWORD proxyStyle = GetWindowLongW(mProxyWindow, GWL_STYLE); + proxyStyle &= ~minMaxMask; + proxyStyle |= windowStyle & minMaxMask; + SetWindowLongW(mProxyWindow, GWL_STYLE, proxyStyle); + + DWORD exStyle = (WS_MAXIMIZE == (windowStyle & WS_MAXIMIZE)) ? WS_EX_TOOLWINDOW : 0; + SetWindowLongW(mProxyWindow, GWL_EXSTYLE, exStyle); +} + +nsresult +TaskbarTabPreview::UpdateTitle() { + NS_ASSERTION(mVisible, "UpdateTitle called on invisible preview"); + + if (!::SetWindowTextW(mProxyWindow, mTitle.get())) + return NS_ERROR_FAILURE; + return NS_OK; +} + +nsresult +TaskbarTabPreview::UpdateIcon() { + NS_ASSERTION(mVisible, "UpdateIcon called on invisible preview"); + + ::SendMessageW(mProxyWindow, WM_SETICON, ICON_SMALL, (LPARAM)mIcon); + + return NS_OK; +} + +nsresult +TaskbarTabPreview::UpdateNext() { + NS_ASSERTION(CanMakeTaskbarCalls() && mVisible, "UpdateNext called on invisible tab preview"); + HWND hNext = nullptr; + if (mNext) { + bool visible; + nsresult rv = mNext->GetVisible(&visible); + + NS_ENSURE_SUCCESS(rv, rv); + + // Can only move next to enabled previews + if (!visible) + return NS_ERROR_FAILURE; + + hNext = (HWND)mNext->GetHWND(); + + // hNext must be registered with the taskbar if the call is to succeed + mNext->EnsureRegistration(); + } + if (FAILED(mTaskbar->SetTabOrder(mProxyWindow, hNext))) + return NS_ERROR_FAILURE; + return NS_OK; +} + + +} // namespace widget +} // namespace mozilla + diff --git a/widget/windows/TaskbarTabPreview.h b/widget/windows/TaskbarTabPreview.h new file mode 100644 index 0000000000..3d043a1414 --- /dev/null +++ b/widget/windows/TaskbarTabPreview.h @@ -0,0 +1,70 @@ +/* vim: se cin sw=2 ts=2 et : */ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __mozilla_widget_TaskbarTabPreview_h__ +#define __mozilla_widget_TaskbarTabPreview_h__ + +#include "nsITaskbarTabPreview.h" +#include "TaskbarPreview.h" + +namespace mozilla { +namespace widget { + +class TaskbarTabPreview : public nsITaskbarTabPreview, + public TaskbarPreview +{ + virtual ~TaskbarTabPreview(); + +public: + TaskbarTabPreview(ITaskbarList4 *aTaskbar, nsITaskbarPreviewController *aController, HWND aHWND, nsIDocShell *aShell); + + NS_DECL_ISUPPORTS + NS_DECL_NSITASKBARTABPREVIEW + NS_FORWARD_NSITASKBARPREVIEW(TaskbarPreview::) + +private: + virtual nsresult ShowActive(bool active); + virtual HWND &PreviewWindow(); + virtual LRESULT WndProc(UINT nMsg, WPARAM wParam, LPARAM lParam); + static LRESULT CALLBACK GlobalWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam); + + virtual nsresult UpdateTaskbarProperties(); + virtual nsresult Enable(); + virtual nsresult Disable(); + virtual void DetachFromNSWindow(); + + // WindowHook procedure for hooking mWnd + static bool MainWindowHook(void *aContext, + HWND hWnd, UINT nMsg, + WPARAM wParam, LPARAM lParam, + LRESULT *aResult); + + // Bug 520807 - we need to update the proxy window style based on the main + // window's style to workaround a bug with the way the DWM displays the + // previews. + void UpdateProxyWindowStyle(); + + nsresult UpdateTitle(); + nsresult UpdateIcon(); + nsresult UpdateNext(); + + // Handle to the toplevel proxy window + HWND mProxyWindow; + nsString mTitle; + nsCOMPtr<imgIContainer> mIconImage; + // Cached Windows icon of mIconImage + HICON mIcon; + // Preview that follows this preview in the taskbar (left-to-right order) + nsCOMPtr<nsITaskbarTabPreview> mNext; + // True if this preview has been registered with the taskbar + bool mRegistered; +}; + +} // namespace widget +} // namespace mozilla + +#endif /* __mozilla_widget_TaskbarTabPreview_h__ */ diff --git a/widget/windows/TaskbarWindowPreview.cpp b/widget/windows/TaskbarWindowPreview.cpp new file mode 100644 index 0000000000..c3d005252d --- /dev/null +++ b/widget/windows/TaskbarWindowPreview.cpp @@ -0,0 +1,326 @@ +/* vim: se cin sw=2 ts=2 et : */ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/ArrayUtils.h" + +#include <nsITaskbarPreviewController.h> +#include "TaskbarWindowPreview.h" +#include "WindowHook.h" +#include "nsUXThemeData.h" +#include "TaskbarPreviewButton.h" +#include "nsWindow.h" +#include "nsWindowGfx.h" + +namespace mozilla { +namespace widget { + +namespace { +bool WindowHookProc(void *aContext, HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam, LRESULT *aResult) +{ + TaskbarWindowPreview *preview = reinterpret_cast<TaskbarWindowPreview*>(aContext); + *aResult = preview->WndProc(nMsg, wParam, lParam); + return true; +} +} + +NS_IMPL_ISUPPORTS(TaskbarWindowPreview, nsITaskbarWindowPreview, + nsITaskbarProgress, nsITaskbarOverlayIconController, + nsISupportsWeakReference) + +/** + * These correspond directly to the states defined in nsITaskbarProgress.idl, so + * they should be kept in sync. + */ +static TBPFLAG sNativeStates[] = +{ + TBPF_NOPROGRESS, + TBPF_INDETERMINATE, + TBPF_NORMAL, + TBPF_ERROR, + TBPF_PAUSED +}; + +TaskbarWindowPreview::TaskbarWindowPreview(ITaskbarList4 *aTaskbar, nsITaskbarPreviewController *aController, HWND aHWND, nsIDocShell *aShell) + : TaskbarPreview(aTaskbar, aController, aHWND, aShell), + mCustomDrawing(false), + mHaveButtons(false), + mState(TBPF_NOPROGRESS), + mCurrentValue(0), + mMaxValue(0), + mOverlayIcon(nullptr) +{ + // Window previews are visible by default + (void) SetVisible(true); + + memset(mThumbButtons, 0, sizeof mThumbButtons); + for (int32_t i = 0; i < nsITaskbarWindowPreview::NUM_TOOLBAR_BUTTONS; i++) { + mThumbButtons[i].dwMask = THB_FLAGS | THB_ICON | THB_TOOLTIP; + mThumbButtons[i].iId = i; + mThumbButtons[i].dwFlags = THBF_HIDDEN; + } + + WindowHook &hook = GetWindowHook(); + if (!CanMakeTaskbarCalls()) + hook.AddMonitor(nsAppShell::GetTaskbarButtonCreatedMessage(), + TaskbarWindowHook, this); +} + +TaskbarWindowPreview::~TaskbarWindowPreview() { + if (mOverlayIcon) { + ::DestroyIcon(mOverlayIcon); + mOverlayIcon = nullptr; + } + + if (IsWindowAvailable()) { + DetachFromNSWindow(); + } else { + mWnd = nullptr; + } +} + +nsresult +TaskbarWindowPreview::ShowActive(bool active) { + return FAILED(mTaskbar->ActivateTab(active ? mWnd : nullptr)) + ? NS_ERROR_FAILURE + : NS_OK; + +} + +HWND & +TaskbarWindowPreview::PreviewWindow() { + return mWnd; +} + +nsresult +TaskbarWindowPreview::GetButton(uint32_t index, nsITaskbarPreviewButton **_retVal) { + if (index >= nsITaskbarWindowPreview::NUM_TOOLBAR_BUTTONS) + return NS_ERROR_INVALID_ARG; + + nsCOMPtr<nsITaskbarPreviewButton> button(do_QueryReferent(mWeakButtons[index])); + + if (!button) { + // Lost reference + button = new TaskbarPreviewButton(this, index); + if (!button) { + return NS_ERROR_OUT_OF_MEMORY; + } + mWeakButtons[index] = do_GetWeakReference(button); + } + + if (!mHaveButtons) { + mHaveButtons = true; + + WindowHook &hook = GetWindowHook(); + (void) hook.AddHook(WM_COMMAND, WindowHookProc, this); + + if (mVisible && FAILED(mTaskbar->ThumbBarAddButtons(mWnd, nsITaskbarWindowPreview::NUM_TOOLBAR_BUTTONS, mThumbButtons))) { + return NS_ERROR_FAILURE; + } + } + button.forget(_retVal); + return NS_OK; +} + +NS_IMETHODIMP +TaskbarWindowPreview::SetEnableCustomDrawing(bool aEnable) { + if (aEnable == mCustomDrawing) + return NS_OK; + mCustomDrawing = aEnable; + TaskbarPreview::EnableCustomDrawing(mWnd, aEnable); + + WindowHook &hook = GetWindowHook(); + if (aEnable) { + (void) hook.AddHook(WM_DWMSENDICONICTHUMBNAIL, WindowHookProc, this); + (void) hook.AddHook(WM_DWMSENDICONICLIVEPREVIEWBITMAP, WindowHookProc, this); + } else { + (void) hook.RemoveHook(WM_DWMSENDICONICLIVEPREVIEWBITMAP, WindowHookProc, this); + (void) hook.RemoveHook(WM_DWMSENDICONICTHUMBNAIL, WindowHookProc, this); + } + return NS_OK; +} + +NS_IMETHODIMP +TaskbarWindowPreview::GetEnableCustomDrawing(bool *aEnable) { + *aEnable = mCustomDrawing; + return NS_OK; +} + +NS_IMETHODIMP +TaskbarWindowPreview::SetProgressState(nsTaskbarProgressState aState, + uint64_t aCurrentValue, + uint64_t aMaxValue) +{ + NS_ENSURE_ARG_RANGE(aState, + nsTaskbarProgressState(0), + nsTaskbarProgressState(ArrayLength(sNativeStates) - 1)); + + TBPFLAG nativeState = sNativeStates[aState]; + if (nativeState == TBPF_NOPROGRESS || nativeState == TBPF_INDETERMINATE) { + NS_ENSURE_TRUE(aCurrentValue == 0, NS_ERROR_INVALID_ARG); + NS_ENSURE_TRUE(aMaxValue == 0, NS_ERROR_INVALID_ARG); + } + + if (aCurrentValue > aMaxValue) + return NS_ERROR_ILLEGAL_VALUE; + + mState = nativeState; + mCurrentValue = aCurrentValue; + mMaxValue = aMaxValue; + + // Only update if we can + return CanMakeTaskbarCalls() ? UpdateTaskbarProgress() : NS_OK; +} + +NS_IMETHODIMP +TaskbarWindowPreview::SetOverlayIcon(imgIContainer* aStatusIcon, + const nsAString& aStatusDescription) { + nsresult rv; + if (aStatusIcon) { + // The image shouldn't be animated + bool isAnimated; + rv = aStatusIcon->GetAnimated(&isAnimated); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_FALSE(isAnimated, NS_ERROR_INVALID_ARG); + } + + HICON hIcon = nullptr; + if (aStatusIcon) { + rv = nsWindowGfx::CreateIcon(aStatusIcon, false, 0, 0, + nsWindowGfx::GetIconMetrics(nsWindowGfx::kSmallIcon), + &hIcon); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (mOverlayIcon) + ::DestroyIcon(mOverlayIcon); + mOverlayIcon = hIcon; + mIconDescription = aStatusDescription; + + // Only update if we can + return CanMakeTaskbarCalls() ? UpdateOverlayIcon() : NS_OK; +} + +nsresult +TaskbarWindowPreview::UpdateTaskbarProperties() { + if (mHaveButtons) { + if (FAILED(mTaskbar->ThumbBarAddButtons(mWnd, nsITaskbarWindowPreview::NUM_TOOLBAR_BUTTONS, mThumbButtons))) + return NS_ERROR_FAILURE; + } + nsresult rv = UpdateTaskbarProgress(); + NS_ENSURE_SUCCESS(rv, rv); + rv = UpdateOverlayIcon(); + NS_ENSURE_SUCCESS(rv, rv); + return TaskbarPreview::UpdateTaskbarProperties(); +} + +nsresult +TaskbarWindowPreview::UpdateTaskbarProgress() { + HRESULT hr = mTaskbar->SetProgressState(mWnd, mState); + if (SUCCEEDED(hr) && mState != TBPF_NOPROGRESS && + mState != TBPF_INDETERMINATE) + hr = mTaskbar->SetProgressValue(mWnd, mCurrentValue, mMaxValue); + + return SUCCEEDED(hr) ? NS_OK : NS_ERROR_FAILURE; +} + +nsresult +TaskbarWindowPreview::UpdateOverlayIcon() { + HRESULT hr = mTaskbar->SetOverlayIcon(mWnd, mOverlayIcon, + mIconDescription.get()); + return SUCCEEDED(hr) ? NS_OK : NS_ERROR_FAILURE; +} + +LRESULT +TaskbarWindowPreview::WndProc(UINT nMsg, WPARAM wParam, LPARAM lParam) { + RefPtr<TaskbarWindowPreview> kungFuDeathGrip(this); + switch (nMsg) { + case WM_COMMAND: + { + uint32_t id = LOWORD(wParam); + uint32_t index = id; + nsCOMPtr<nsITaskbarPreviewButton> button; + nsresult rv = GetButton(index, getter_AddRefs(button)); + if (NS_SUCCEEDED(rv)) + mController->OnClick(button); + } + return 0; + } + return TaskbarPreview::WndProc(nMsg, wParam, lParam); +} + +/* static */ +bool +TaskbarWindowPreview::TaskbarWindowHook(void *aContext, + HWND hWnd, UINT nMsg, + WPARAM wParam, LPARAM lParam, + LRESULT *aResult) +{ + NS_ASSERTION(nMsg == nsAppShell::GetTaskbarButtonCreatedMessage(), + "Window hook proc called with wrong message"); + TaskbarWindowPreview *preview = + reinterpret_cast<TaskbarWindowPreview*>(aContext); + // Now we can make all the calls to mTaskbar + preview->UpdateTaskbarProperties(); + return false; +} + +nsresult +TaskbarWindowPreview::Enable() { + nsresult rv = TaskbarPreview::Enable(); + NS_ENSURE_SUCCESS(rv, rv); + + return FAILED(mTaskbar->AddTab(mWnd)) + ? NS_ERROR_FAILURE + : NS_OK; +} + +nsresult +TaskbarWindowPreview::Disable() { + nsresult rv = TaskbarPreview::Disable(); + NS_ENSURE_SUCCESS(rv, rv); + + return FAILED(mTaskbar->DeleteTab(mWnd)) + ? NS_ERROR_FAILURE + : NS_OK; +} + +void +TaskbarWindowPreview::DetachFromNSWindow() { + // Remove the hooks we have for drawing + SetEnableCustomDrawing(false); + + WindowHook &hook = GetWindowHook(); + (void) hook.RemoveHook(WM_COMMAND, WindowHookProc, this); + (void) hook.RemoveMonitor(nsAppShell::GetTaskbarButtonCreatedMessage(), + TaskbarWindowHook, this); + + TaskbarPreview::DetachFromNSWindow(); +} + +nsresult +TaskbarWindowPreview::UpdateButtons() { + NS_ASSERTION(mVisible, "UpdateButtons called on invisible preview"); + + if (FAILED(mTaskbar->ThumbBarUpdateButtons(mWnd, nsITaskbarWindowPreview::NUM_TOOLBAR_BUTTONS, mThumbButtons))) + return NS_ERROR_FAILURE; + return NS_OK; +} + +nsresult +TaskbarWindowPreview::UpdateButton(uint32_t index) { + if (index >= nsITaskbarWindowPreview::NUM_TOOLBAR_BUTTONS) + return NS_ERROR_INVALID_ARG; + if (mVisible) { + if (FAILED(mTaskbar->ThumbBarUpdateButtons(mWnd, 1, &mThumbButtons[index]))) + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +} // namespace widget +} // namespace mozilla + diff --git a/widget/windows/TaskbarWindowPreview.h b/widget/windows/TaskbarWindowPreview.h new file mode 100644 index 0000000000..d0719f739a --- /dev/null +++ b/widget/windows/TaskbarWindowPreview.h @@ -0,0 +1,85 @@ +/* vim: se cin sw=2 ts=2 et : */ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __mozilla_widget_TaskbarWindowPreview_h__ +#define __mozilla_widget_TaskbarWindowPreview_h__ + +#include "nsITaskbarWindowPreview.h" +#include "nsITaskbarProgress.h" +#include "nsITaskbarOverlayIconController.h" +#include "TaskbarPreview.h" +#include <nsWeakReference.h> + +namespace mozilla { +namespace widget { + +class TaskbarPreviewButton; +class TaskbarWindowPreview : public TaskbarPreview, + public nsITaskbarWindowPreview, + public nsITaskbarProgress, + public nsITaskbarOverlayIconController, + public nsSupportsWeakReference +{ + virtual ~TaskbarWindowPreview(); + +public: + TaskbarWindowPreview(ITaskbarList4 *aTaskbar, nsITaskbarPreviewController *aController, HWND aHWND, nsIDocShell *aShell); + + NS_DECL_ISUPPORTS + NS_DECL_NSITASKBARWINDOWPREVIEW + NS_DECL_NSITASKBARPROGRESS + NS_DECL_NSITASKBAROVERLAYICONCONTROLLER + NS_FORWARD_NSITASKBARPREVIEW(TaskbarPreview::) + + virtual LRESULT WndProc(UINT nMsg, WPARAM wParam, LPARAM lParam) override; +private: + virtual nsresult ShowActive(bool active) override; + virtual HWND &PreviewWindow() override; + + virtual nsresult UpdateTaskbarProperties() override; + virtual nsresult Enable() override; + virtual nsresult Disable() override; + virtual void DetachFromNSWindow() override; + nsresult UpdateButton(uint32_t index); + nsresult UpdateButtons(); + + // Is custom drawing enabled? + bool mCustomDrawing; + // Have we made any buttons? + bool mHaveButtons; + // Windows button format + THUMBBUTTON mThumbButtons[nsITaskbarWindowPreview::NUM_TOOLBAR_BUTTONS]; + // Pointers to our button class (cached instances) + nsWeakPtr mWeakButtons[nsITaskbarWindowPreview::NUM_TOOLBAR_BUTTONS]; + + // Called to update ITaskbarList4 dependent properties + nsresult UpdateTaskbarProgress(); + nsresult UpdateOverlayIcon(); + + // The taskbar progress + TBPFLAG mState; + ULONGLONG mCurrentValue; + ULONGLONG mMaxValue; + + // Taskbar overlay icon + HICON mOverlayIcon; + nsString mIconDescription; + + // WindowHook procedure for hooking mWnd for taskbar progress and icon stuff + static bool TaskbarWindowHook(void *aContext, + HWND hWnd, UINT nMsg, + WPARAM wParam, LPARAM lParam, + LRESULT *aResult); + + friend class TaskbarPreviewButton; +}; + +} // namespace widget +} // namespace mozilla + +#endif /* __mozilla_widget_TaskbarWindowPreview_h__ */ + diff --git a/widget/windows/WidgetTraceEvent.cpp b/widget/windows/WidgetTraceEvent.cpp new file mode 100644 index 0000000000..589449af8b --- /dev/null +++ b/widget/windows/WidgetTraceEvent.cpp @@ -0,0 +1,132 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Windows widget support for event loop instrumentation. + * See toolkit/xre/EventTracer.cpp for more details. + */ + +#include <stdio.h> +#include <windows.h> + +#include "mozilla/RefPtr.h" +#include "mozilla/WidgetTraceEvent.h" +#include "nsAppShellCID.h" +#include "nsComponentManagerUtils.h" +#include "nsCOMPtr.h" +#include "nsIAppShellService.h" +#include "nsIBaseWindow.h" +#include "nsIDocShell.h" +#include "nsISupportsImpl.h" +#include "nsIWidget.h" +#include "nsIXULWindow.h" +#include "nsServiceManagerUtils.h" +#include "nsThreadUtils.h" +#include "nsWindowDefs.h" + +namespace { + +// Used for signaling the background thread from the main thread. +HANDLE sEventHandle = nullptr; + +// We need a runnable in order to find the hidden window on the main +// thread. +class HWNDGetter : public mozilla::Runnable { +public: + HWNDGetter() : hidden_window_hwnd(nullptr) { + MOZ_COUNT_CTOR(HWNDGetter); + } + ~HWNDGetter() { + MOZ_COUNT_DTOR(HWNDGetter); + } + + HWND hidden_window_hwnd; + + NS_IMETHOD Run() override { + // Jump through some hoops to locate the hidden window. + nsCOMPtr<nsIAppShellService> appShell(do_GetService(NS_APPSHELLSERVICE_CONTRACTID)); + nsCOMPtr<nsIXULWindow> hiddenWindow; + + nsresult rv = appShell->GetHiddenWindow(getter_AddRefs(hiddenWindow)); + if (NS_FAILED(rv)) { + return rv; + } + + nsCOMPtr<nsIDocShell> docShell; + rv = hiddenWindow->GetDocShell(getter_AddRefs(docShell)); + if (NS_FAILED(rv) || !docShell) { + return rv; + } + + nsCOMPtr<nsIBaseWindow> baseWindow(do_QueryInterface(docShell)); + + if (!baseWindow) + return NS_ERROR_FAILURE; + + nsCOMPtr<nsIWidget> widget; + baseWindow->GetMainWidget(getter_AddRefs(widget)); + + if (!widget) + return NS_ERROR_FAILURE; + + hidden_window_hwnd = (HWND)widget->GetNativeData(NS_NATIVE_WINDOW); + + return NS_OK; + } +}; + +HWND GetHiddenWindowHWND() +{ + // Need to dispatch this to the main thread because plenty of + // the things it wants to access are main-thread-only. + RefPtr<HWNDGetter> getter = new HWNDGetter(); + NS_DispatchToMainThread(getter, NS_DISPATCH_SYNC); + return getter->hidden_window_hwnd; +} + +} // namespace + +namespace mozilla { + +bool InitWidgetTracing() +{ + sEventHandle = CreateEventW(nullptr, FALSE, FALSE, nullptr); + return sEventHandle != nullptr; +} + +void CleanUpWidgetTracing() +{ + CloseHandle(sEventHandle); + sEventHandle = nullptr; +} + +// This function is called from the main (UI) thread. +void SignalTracerThread() +{ + if (sEventHandle != nullptr) + SetEvent(sEventHandle); +} + +// This function is called from the background tracer thread. +bool FireAndWaitForTracerEvent() +{ + MOZ_ASSERT(sEventHandle, "Tracing not initialized!"); + + // First, try to find the hidden window. + static HWND hidden_window = nullptr; + if (hidden_window == nullptr) { + hidden_window = GetHiddenWindowHWND(); + } + + if (hidden_window == nullptr) + return false; + + // Post the tracer message into the hidden window's message queue, + // and then block until it's processed. + PostMessage(hidden_window, MOZ_WM_TRACE, 0, 0); + WaitForSingleObject(sEventHandle, INFINITE); + return true; +} + +} // namespace mozilla diff --git a/widget/windows/WinCompositorWidget.cpp b/widget/windows/WinCompositorWidget.cpp new file mode 100644 index 0000000000..f660bd0192 --- /dev/null +++ b/widget/windows/WinCompositorWidget.cpp @@ -0,0 +1,329 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "WinCompositorWidget.h" +#include "gfxPrefs.h" +#include "mozilla/gfx/DeviceManagerDx.h" +#include "mozilla/gfx/Point.h" +#include "mozilla/layers/Compositor.h" +#include "mozilla/widget/PlatformWidgetTypes.h" +#include "nsWindow.h" +#include "VsyncDispatcher.h" + +#include <ddraw.h> + +namespace mozilla { +namespace widget { + +using namespace mozilla::gfx; + +WinCompositorWidget::WinCompositorWidget(const CompositorWidgetInitData& aInitData) + : mWidgetKey(aInitData.widgetKey()), + mWnd(reinterpret_cast<HWND>(aInitData.hWnd())), + mTransparencyMode(static_cast<nsTransparencyMode>(aInitData.transparencyMode())), + mMemoryDC(nullptr), + mCompositeDC(nullptr), + mLockedBackBufferData(nullptr) +{ + MOZ_ASSERT(mWnd && ::IsWindow(mWnd)); + + // mNotDeferEndRemoteDrawing is set on the main thread during init, + // but is only accessed after on the compositor thread. + mNotDeferEndRemoteDrawing = gfxPrefs::LayersCompositionFrameRate() == 0 || + gfxPlatform::IsInLayoutAsapMode() || + gfxPlatform::ForceSoftwareVsync(); +} + +void +WinCompositorWidget::OnDestroyWindow() +{ + mTransparentSurface = nullptr; + mMemoryDC = nullptr; +} + +bool +WinCompositorWidget::PreRender(WidgetRenderingContext* aContext) +{ + // This can block waiting for WM_SETTEXT to finish + // Using PreRender is unnecessarily pessimistic because + // we technically only need to block during the present call + // not all of compositor rendering + mPresentLock.Enter(); + return true; +} + +void +WinCompositorWidget::PostRender(WidgetRenderingContext* aContext) +{ + mPresentLock.Leave(); +} + +LayoutDeviceIntSize +WinCompositorWidget::GetClientSize() +{ + RECT r; + if (!::GetClientRect(mWnd, &r)) { + return LayoutDeviceIntSize(); + } + return LayoutDeviceIntSize( + r.right - r.left, + r.bottom - r.top); +} + +already_AddRefed<gfx::DrawTarget> +WinCompositorWidget::StartRemoteDrawing() +{ + MOZ_ASSERT(!mCompositeDC); + + RefPtr<gfxASurface> surf; + if (mTransparencyMode == eTransparencyTransparent) { + surf = EnsureTransparentSurface(); + } + + // Must call this after EnsureTransparentSurface(), since it could update + // the DC. + HDC dc = GetWindowSurface(); + if (!surf) { + if (!dc) { + return nullptr; + } + uint32_t flags = (mTransparencyMode == eTransparencyOpaque) ? 0 : + gfxWindowsSurface::FLAG_IS_TRANSPARENT; + surf = new gfxWindowsSurface(dc, flags); + } + + IntSize size = surf->GetSize(); + if (size.width <= 0 || size.height <= 0) { + if (dc) { + FreeWindowSurface(dc); + } + return nullptr; + } + + MOZ_ASSERT(!mCompositeDC); + mCompositeDC = dc; + + return mozilla::gfx::Factory::CreateDrawTargetForCairoSurface(surf->CairoSurface(), size); +} + +void +WinCompositorWidget::EndRemoteDrawing() +{ + MOZ_ASSERT(!mLockedBackBufferData); + + if (mTransparencyMode == eTransparencyTransparent) { + MOZ_ASSERT(mTransparentSurface); + RedrawTransparentWindow(); + } + if (mCompositeDC) { + FreeWindowSurface(mCompositeDC); + } + mCompositeDC = nullptr; +} + +bool +WinCompositorWidget::NeedsToDeferEndRemoteDrawing() +{ + if(mNotDeferEndRemoteDrawing) { + return false; + } + + IDirectDraw7* ddraw = DeviceManagerDx::Get()->GetDirectDraw(); + if (!ddraw) { + return false; + } + + DWORD scanLine = 0; + int height = ::GetSystemMetrics(SM_CYSCREEN); + HRESULT ret = ddraw->GetScanLine(&scanLine); + if (ret == DDERR_VERTICALBLANKINPROGRESS) { + scanLine = 0; + } else if (ret != DD_OK) { + return false; + } + + // Check if there is a risk of tearing with GDI. + if (static_cast<int>(scanLine) > height / 2) { + // No need to defer. + return false; + } + + return true; +} + +already_AddRefed<gfx::DrawTarget> +WinCompositorWidget::GetBackBufferDrawTarget(gfx::DrawTarget* aScreenTarget, + const LayoutDeviceIntRect& aRect, + const LayoutDeviceIntRect& aClearRect) +{ + MOZ_ASSERT(!mLockedBackBufferData); + + RefPtr<gfx::DrawTarget> target = + CompositorWidget::GetBackBufferDrawTarget(aScreenTarget, aRect, aClearRect); + if (!target) { + return nullptr; + } + + MOZ_ASSERT(target->GetBackendType() == BackendType::CAIRO); + + uint8_t* destData; + IntSize destSize; + int32_t destStride; + SurfaceFormat destFormat; + if (!target->LockBits(&destData, &destSize, &destStride, &destFormat)) { + // LockBits is not supported. Use original DrawTarget. + return target.forget(); + } + + RefPtr<gfx::DrawTarget> dataTarget = + Factory::CreateDrawTargetForData(BackendType::CAIRO, + destData, + destSize, + destStride, + destFormat); + mLockedBackBufferData = destData; + + return dataTarget.forget(); +} + +already_AddRefed<gfx::SourceSurface> +WinCompositorWidget::EndBackBufferDrawing() +{ + if (mLockedBackBufferData) { + MOZ_ASSERT(mLastBackBuffer); + mLastBackBuffer->ReleaseBits(mLockedBackBufferData); + mLockedBackBufferData = nullptr; + } + return CompositorWidget::EndBackBufferDrawing(); +} + +bool +WinCompositorWidget::InitCompositor(layers::Compositor* aCompositor) +{ + if (aCompositor->GetBackendType() == layers::LayersBackend::LAYERS_BASIC) { + DeviceManagerDx::Get()->InitializeDirectDraw(); + } + return true; +} + +uintptr_t +WinCompositorWidget::GetWidgetKey() +{ + return mWidgetKey; +} + +void +WinCompositorWidget::EnterPresentLock() +{ + mPresentLock.Enter(); +} + +void +WinCompositorWidget::LeavePresentLock() +{ + mPresentLock.Leave(); +} + +RefPtr<gfxASurface> +WinCompositorWidget::EnsureTransparentSurface() +{ + MOZ_ASSERT(mTransparencyMode == eTransparencyTransparent); + + IntSize size = GetClientSize().ToUnknownSize(); + if (!mTransparentSurface || mTransparentSurface->GetSize() != size) { + mTransparentSurface = nullptr; + mMemoryDC = nullptr; + CreateTransparentSurface(size); + } + + RefPtr<gfxASurface> surface = mTransparentSurface; + return surface.forget(); +} + +void +WinCompositorWidget::CreateTransparentSurface(const gfx::IntSize& aSize) +{ + MOZ_ASSERT(!mTransparentSurface && !mMemoryDC); + RefPtr<gfxWindowsSurface> surface = new gfxWindowsSurface(aSize, SurfaceFormat::A8R8G8B8_UINT32); + mTransparentSurface = surface; + mMemoryDC = surface->GetDC(); +} + +void +WinCompositorWidget::UpdateTransparency(nsTransparencyMode aMode) +{ + if (mTransparencyMode == aMode) { + return; + } + + mTransparencyMode = aMode; + mTransparentSurface = nullptr; + mMemoryDC = nullptr; + + if (mTransparencyMode == eTransparencyTransparent) { + EnsureTransparentSurface(); + } +} + +void +WinCompositorWidget::ClearTransparentWindow() +{ + if (!mTransparentSurface) { + return; + } + + EnsureTransparentSurface(); + + IntSize size = mTransparentSurface->GetSize(); + if (!size.IsEmpty()) { + RefPtr<DrawTarget> drawTarget = + gfxPlatform::CreateDrawTargetForSurface(mTransparentSurface, size); + if (!drawTarget) { + return; + } + drawTarget->ClearRect(Rect(0, 0, size.width, size.height)); + RedrawTransparentWindow(); + } +} + +bool +WinCompositorWidget::RedrawTransparentWindow() +{ + MOZ_ASSERT(mTransparencyMode == eTransparencyTransparent); + + LayoutDeviceIntSize size = GetClientSize(); + + ::GdiFlush(); + + BLENDFUNCTION bf = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA }; + SIZE winSize = { size.width, size.height }; + POINT srcPos = { 0, 0 }; + HWND hWnd = WinUtils::GetTopLevelHWND(mWnd, true); + RECT winRect; + ::GetWindowRect(hWnd, &winRect); + + // perform the alpha blend + return !!::UpdateLayeredWindow( + hWnd, nullptr, (POINT*)&winRect, &winSize, mMemoryDC, + &srcPos, 0, &bf, ULW_ALPHA); +} + +HDC +WinCompositorWidget::GetWindowSurface() +{ + return eTransparencyTransparent == mTransparencyMode + ? mMemoryDC + : ::GetDC(mWnd); +} + +void +WinCompositorWidget::FreeWindowSurface(HDC dc) +{ + if (eTransparencyTransparent != mTransparencyMode) + ::ReleaseDC(mWnd, dc); +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/windows/WinCompositorWidget.h b/widget/windows/WinCompositorWidget.h new file mode 100644 index 0000000000..9661cab458 --- /dev/null +++ b/widget/windows/WinCompositorWidget.h @@ -0,0 +1,112 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef widget_windows_CompositorWidgetParent_h +#define widget_windows_CompositorWidgetParent_h + +#include "CompositorWidget.h" +#include "gfxASurface.h" +#include "mozilla/gfx/CriticalSection.h" +#include "mozilla/gfx/Point.h" +#include "nsIWidget.h" + +class nsWindow; + +namespace mozilla { +namespace widget { + +class CompositorWidgetDelegate +{ +public: + // Callbacks for nsWindow. + virtual void EnterPresentLock() = 0; + virtual void LeavePresentLock() = 0; + virtual void OnDestroyWindow() = 0; + + // Transparency handling. + virtual void UpdateTransparency(nsTransparencyMode aMode) = 0; + virtual void ClearTransparentWindow() = 0; + + // If in-process and using software rendering, return the backing transparent + // DC. + virtual HDC GetTransparentDC() const = 0; +}; + +// This is the Windows-specific implementation of CompositorWidget. For +// the most part it only requires an HWND, however it maintains extra state +// for transparent windows, as well as for synchronizing WM_SETTEXT messages +// with the compositor. +class WinCompositorWidget + : public CompositorWidget, + public CompositorWidgetDelegate +{ +public: + WinCompositorWidget(const CompositorWidgetInitData& aInitData); + + bool PreRender(WidgetRenderingContext*) override; + void PostRender(WidgetRenderingContext*) override; + already_AddRefed<gfx::DrawTarget> StartRemoteDrawing() override; + void EndRemoteDrawing() override; + bool NeedsToDeferEndRemoteDrawing() override; + LayoutDeviceIntSize GetClientSize() override; + already_AddRefed<gfx::DrawTarget> GetBackBufferDrawTarget(gfx::DrawTarget* aScreenTarget, + const LayoutDeviceIntRect& aRect, + const LayoutDeviceIntRect& aClearRect) override; + already_AddRefed<gfx::SourceSurface> EndBackBufferDrawing() override; + bool InitCompositor(layers::Compositor* aCompositor) override; + uintptr_t GetWidgetKey() override; + WinCompositorWidget* AsWindows() override { + return this; + } + CompositorWidgetDelegate* AsDelegate() override { + return this; + } + + // CompositorWidgetDelegate overrides. + void EnterPresentLock() override; + void LeavePresentLock() override; + void OnDestroyWindow() override; + void UpdateTransparency(nsTransparencyMode aMode) override; + void ClearTransparentWindow() override; + + bool RedrawTransparentWindow(); + + // Ensure that a transparent surface exists, then return it. + RefPtr<gfxASurface> EnsureTransparentSurface(); + + HDC GetTransparentDC() const override { + return mMemoryDC; + } + HWND GetHwnd() const { + return mWnd; + } + +private: + HDC GetWindowSurface(); + void FreeWindowSurface(HDC dc); + + void CreateTransparentSurface(const gfx::IntSize& aSize); + +private: + uintptr_t mWidgetKey; + HWND mWnd; + gfx::CriticalSection mPresentLock; + + // Transparency handling. + nsTransparencyMode mTransparencyMode; + RefPtr<gfxASurface> mTransparentSurface; + HDC mMemoryDC; + HDC mCompositeDC; + + // Locked back buffer of BasicCompositor + uint8_t* mLockedBackBufferData; + + bool mNotDeferEndRemoteDrawing; +}; + +} // namespace widget +} // namespace mozilla + +#endif // widget_windows_CompositorWidgetParent_h diff --git a/widget/windows/WinIMEHandler.cpp b/widget/windows/WinIMEHandler.cpp new file mode 100644 index 0000000000..d44f729c48 --- /dev/null +++ b/widget/windows/WinIMEHandler.cpp @@ -0,0 +1,1055 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "WinIMEHandler.h" + +#include "IMMHandler.h" +#include "mozilla/Preferences.h" +#include "mozilla/WindowsVersion.h" +#include "nsWindowDefs.h" +#include "WinTextEventDispatcherListener.h" + +#ifdef NS_ENABLE_TSF +#include "TSFTextStore.h" +#endif // #ifdef NS_ENABLE_TSF + +#include "nsLookAndFeel.h" +#include "nsWindow.h" +#include "WinUtils.h" +#include "nsIWindowsRegKey.h" +#include "nsIWindowsUIUtils.h" + +#include "shellapi.h" +#include "shlobj.h" +#include "powrprof.h" +#include "setupapi.h" +#include "cfgmgr32.h" + +const char* kOskPathPrefName = "ui.osk.on_screen_keyboard_path"; +const char* kOskEnabled = "ui.osk.enabled"; +const char* kOskDetectPhysicalKeyboard = "ui.osk.detect_physical_keyboard"; +const char* kOskRequireWin10 = "ui.osk.require_win10"; +const char* kOskDebugReason = "ui.osk.debug.keyboardDisplayReason"; + +namespace mozilla { +namespace widget { + +/****************************************************************************** + * IMEHandler + ******************************************************************************/ + +nsWindow* IMEHandler::sFocusedWindow = nullptr; +InputContextAction::Cause IMEHandler::sLastContextActionCause = + InputContextAction::CAUSE_UNKNOWN; +bool IMEHandler::sPluginHasFocus = false; + +#ifdef NS_ENABLE_TSF +bool IMEHandler::sIsInTSFMode = false; +bool IMEHandler::sIsIMMEnabled = true; +bool IMEHandler::sAssociateIMCOnlyWhenIMM_IMEActive = false; +decltype(SetInputScopes)* IMEHandler::sSetInputScopes = nullptr; +#endif // #ifdef NS_ENABLE_TSF + +static POWER_PLATFORM_ROLE sPowerPlatformRole = PlatformRoleUnspecified; +static bool sDeterminedPowerPlatformRole = false; + +// static +void +IMEHandler::Initialize() +{ +#ifdef NS_ENABLE_TSF + TSFTextStore::Initialize(); + sIsInTSFMode = TSFTextStore::IsInTSFMode(); + sIsIMMEnabled = + !sIsInTSFMode || Preferences::GetBool("intl.tsf.support_imm", true); + sAssociateIMCOnlyWhenIMM_IMEActive = + sIsIMMEnabled && + Preferences::GetBool("intl.tsf.associate_imc_only_when_imm_ime_is_active", + false); + if (!sIsInTSFMode) { + // When full TSFTextStore is not available, try to use SetInputScopes API + // to enable at least InputScope. Use GET_MODULE_HANDLE_EX_FLAG_PIN to + // ensure that msctf.dll will not be unloaded. + HMODULE module = nullptr; + if (GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_PIN, L"msctf.dll", + &module)) { + sSetInputScopes = reinterpret_cast<decltype(SetInputScopes)*>( + GetProcAddress(module, "SetInputScopes")); + } + } +#endif // #ifdef NS_ENABLE_TSF + + IMMHandler::Initialize(); +} + +// static +void +IMEHandler::Terminate() +{ +#ifdef NS_ENABLE_TSF + if (sIsInTSFMode) { + TSFTextStore::Terminate(); + sIsInTSFMode = false; + } +#endif // #ifdef NS_ENABLE_TSF + + IMMHandler::Terminate(); + WinTextEventDispatcherListener::Shutdown(); +} + +// static +void* +IMEHandler::GetNativeData(nsWindow* aWindow, uint32_t aDataType) +{ + if (aDataType == NS_RAW_NATIVE_IME_CONTEXT) { +#ifdef NS_ENABLE_TSF + if (IsTSFAvailable()) { + return TSFTextStore::GetThreadManager(); + } +#endif // #ifdef NS_ENABLE_TSF + IMEContext context(aWindow); + if (context.IsValid()) { + return context.get(); + } + // If IMC isn't associated with the window, IME is disabled on the window + // now. In such case, we should return default IMC instead. + const IMEContext& defaultIMC = aWindow->DefaultIMC(); + if (defaultIMC.IsValid()) { + return defaultIMC.get(); + } + // If there is no default IMC, we should return the pointer to the window + // since if we return nullptr, IMEStateManager cannot manage composition + // with TextComposition instance. This is possible if no IME is installed, + // but composition may occur with dead key sequence. + return aWindow; + } + +#ifdef NS_ENABLE_TSF + void* result = TSFTextStore::GetNativeData(aDataType); + if (!result || !(*(static_cast<void**>(result)))) { + return nullptr; + } + // XXX During the TSF module test, sIsInTSFMode must be true. After that, + // the value should be restored but currently, there is no way for that. + // When the TSF test is enabled again, we need to fix this. Perhaps, + // sending a message can fix this. + sIsInTSFMode = true; + return result; +#else // #ifdef NS_ENABLE_TSF + return nullptr; +#endif // #ifdef NS_ENABLE_TSF #else +} + +// static +bool +IMEHandler::ProcessRawKeyMessage(const MSG& aMsg) +{ +#ifdef NS_ENABLE_TSF + if (IsTSFAvailable()) { + return TSFTextStore::ProcessRawKeyMessage(aMsg); + } +#endif // #ifdef NS_ENABLE_TSF + return false; // noting to do in IMM mode. +} + +// static +bool +IMEHandler::ProcessMessage(nsWindow* aWindow, UINT aMessage, + WPARAM& aWParam, LPARAM& aLParam, + MSGResult& aResult) +{ + if (aMessage == MOZ_WM_DISMISS_ONSCREEN_KEYBOARD) { + if (!sFocusedWindow) { + DismissOnScreenKeyboard(); + } + return true; + } + +#ifdef NS_ENABLE_TSF + if (IsTSFAvailable()) { + TSFTextStore::ProcessMessage(aWindow, aMessage, aWParam, aLParam, aResult); + if (aResult.mConsumed) { + return true; + } + // If we don't support IMM in TSF mode, we don't use IMMHandler. + if (!sIsIMMEnabled) { + return false; + } + // IME isn't implemented with IMM, IMMHandler shouldn't handle any + // messages. + if (!TSFTextStore::IsIMM_IMEActive()) { + return false; + } + } +#endif // #ifdef NS_ENABLE_TSF + + return IMMHandler::ProcessMessage(aWindow, aMessage, aWParam, aLParam, + aResult); +} + +#ifdef NS_ENABLE_TSF +// static +bool +IMEHandler::IsIMMActive() +{ + return TSFTextStore::IsIMM_IMEActive(); +} + +#endif // #ifdef NS_ENABLE_TSF + +// static +bool +IMEHandler::IsComposing() +{ +#ifdef NS_ENABLE_TSF + if (IsTSFAvailable()) { + return TSFTextStore::IsComposing() || IMMHandler::IsComposing(); + } +#endif // #ifdef NS_ENABLE_TSF + + return IMMHandler::IsComposing(); +} + +// static +bool +IMEHandler::IsComposingOn(nsWindow* aWindow) +{ +#ifdef NS_ENABLE_TSF + if (IsTSFAvailable()) { + return TSFTextStore::IsComposingOn(aWindow) || + IMMHandler::IsComposingOn(aWindow); + } +#endif // #ifdef NS_ENABLE_TSF + + return IMMHandler::IsComposingOn(aWindow); +} + +// static +nsresult +IMEHandler::NotifyIME(nsWindow* aWindow, + const IMENotification& aIMENotification) +{ +#ifdef NS_ENABLE_TSF + if (IsTSFAvailable()) { + switch (aIMENotification.mMessage) { + case NOTIFY_IME_OF_SELECTION_CHANGE: { + nsresult rv = TSFTextStore::OnSelectionChange(aIMENotification); + // If IMM IME is active, we need to notify IMMHandler of updating + // composition change. It will adjust candidate window position or + // composition window position. + bool isIMMActive = IsIMMActive(); + if (isIMMActive) { + IMMHandler::OnUpdateComposition(aWindow); + } + IMMHandler::OnSelectionChange(aWindow, aIMENotification, isIMMActive); + return rv; + } + case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED: + // If IMM IME is active, we need to notify IMMHandler of updating + // composition change. It will adjust candidate window position or + // composition window position. + if (IsIMMActive()) { + IMMHandler::OnUpdateComposition(aWindow); + } else { + TSFTextStore::OnUpdateComposition(); + } + return NS_OK; + case NOTIFY_IME_OF_TEXT_CHANGE: + return TSFTextStore::OnTextChange(aIMENotification); + case NOTIFY_IME_OF_FOCUS: { + sFocusedWindow = aWindow; + IMMHandler::OnFocusChange(true, aWindow); + nsresult rv = + TSFTextStore::OnFocusChange(true, aWindow, + aWindow->GetInputContext()); + IMEHandler::MaybeShowOnScreenKeyboard(); + return rv; + } + case NOTIFY_IME_OF_BLUR: + sFocusedWindow = nullptr; + IMEHandler::MaybeDismissOnScreenKeyboard(aWindow); + IMMHandler::OnFocusChange(false, aWindow); + return TSFTextStore::OnFocusChange(false, aWindow, + aWindow->GetInputContext()); + case NOTIFY_IME_OF_MOUSE_BUTTON_EVENT: + // If IMM IME is active, we should send a mouse button event via IMM. + if (IsIMMActive()) { + return IMMHandler::OnMouseButtonEvent(aWindow, aIMENotification); + } + return TSFTextStore::OnMouseButtonEvent(aIMENotification); + case REQUEST_TO_COMMIT_COMPOSITION: + if (TSFTextStore::IsComposingOn(aWindow)) { + TSFTextStore::CommitComposition(false); + } else if (IsIMMActive()) { + IMMHandler::CommitComposition(aWindow); + } + return NS_OK; + case REQUEST_TO_CANCEL_COMPOSITION: + if (TSFTextStore::IsComposingOn(aWindow)) { + TSFTextStore::CommitComposition(true); + } else if (IsIMMActive()) { + IMMHandler::CancelComposition(aWindow); + } + return NS_OK; + case NOTIFY_IME_OF_POSITION_CHANGE: + return TSFTextStore::OnLayoutChange(); + default: + return NS_ERROR_NOT_IMPLEMENTED; + } + } +#endif //NS_ENABLE_TSF + + switch (aIMENotification.mMessage) { + case REQUEST_TO_COMMIT_COMPOSITION: + IMMHandler::CommitComposition(aWindow); + return NS_OK; + case REQUEST_TO_CANCEL_COMPOSITION: + IMMHandler::CancelComposition(aWindow); + return NS_OK; + case NOTIFY_IME_OF_POSITION_CHANGE: + case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED: + IMMHandler::OnUpdateComposition(aWindow); + return NS_OK; + case NOTIFY_IME_OF_SELECTION_CHANGE: + IMMHandler::OnSelectionChange(aWindow, aIMENotification, true); + return NS_OK; + case NOTIFY_IME_OF_MOUSE_BUTTON_EVENT: + return IMMHandler::OnMouseButtonEvent(aWindow, aIMENotification); + case NOTIFY_IME_OF_FOCUS: + sFocusedWindow = aWindow; + IMMHandler::OnFocusChange(true, aWindow); + IMEHandler::MaybeShowOnScreenKeyboard(); + return NS_OK; + case NOTIFY_IME_OF_BLUR: + sFocusedWindow = nullptr; + IMEHandler::MaybeDismissOnScreenKeyboard(aWindow); + IMMHandler::OnFocusChange(false, aWindow); +#ifdef NS_ENABLE_TSF + // If a plugin gets focus while TSF has focus, we need to notify TSF of + // the blur. + if (TSFTextStore::ThinksHavingFocus()) { + return TSFTextStore::OnFocusChange(false, aWindow, + aWindow->GetInputContext()); + } +#endif //NS_ENABLE_TSF + return NS_OK; + default: + return NS_ERROR_NOT_IMPLEMENTED; + } +} + +// static +nsIMEUpdatePreference +IMEHandler::GetUpdatePreference() +{ + // While a plugin has focus, neither TSFTextStore nor IMMHandler needs + // notifications. + if (sPluginHasFocus) { + return nsIMEUpdatePreference(); + } + +#ifdef NS_ENABLE_TSF + if (IsTSFAvailable()) { + if (!sIsIMMEnabled) { + return TSFTextStore::GetIMEUpdatePreference(); + } + // Even if TSF is available, the active IME may be an IMM-IME. + // Unfortunately, changing the result of GetUpdatePreference() while an + // editor has focus isn't supported by IMEContentObserver nor + // ContentCacheInParent. Therefore, we need to request whole notifications + // which are necessary either IMMHandler or TSFTextStore. + return IMMHandler::GetIMEUpdatePreference() | + TSFTextStore::GetIMEUpdatePreference(); + } +#endif //NS_ENABLE_TSF + + return IMMHandler::GetIMEUpdatePreference(); +} + +// static +TextEventDispatcherListener* +IMEHandler::GetNativeTextEventDispatcherListener() +{ + return WinTextEventDispatcherListener::GetInstance(); +} + +// static +bool +IMEHandler::GetOpenState(nsWindow* aWindow) +{ +#ifdef NS_ENABLE_TSF + if (IsTSFAvailable() && !IsIMMActive()) { + return TSFTextStore::GetIMEOpenState(); + } +#endif //NS_ENABLE_TSF + + IMEContext context(aWindow); + return context.GetOpenState(); +} + +// static +void +IMEHandler::OnDestroyWindow(nsWindow* aWindow) +{ + // When focus is in remote process, but the window is being destroyed, we + // need to clean up TSFTextStore here since NOTIFY_IME_OF_BLUR won't reach + // here because TabParent already lost the reference to the nsWindow when + // it receives from the remote process. + if (sFocusedWindow == aWindow) { + NS_ASSERTION(aWindow->GetInputContext().IsOriginContentProcess(), + "input context of focused widget should be set from a remote process"); + NotifyIME(aWindow, IMENotification(NOTIFY_IME_OF_BLUR)); + } + +#ifdef NS_ENABLE_TSF + // We need to do nothing here for TSF. Just restore the default context + // if it's been disassociated. + if (!sIsInTSFMode) { + // MSDN says we need to set IS_DEFAULT to avoid memory leak when we use + // SetInputScopes API. Use an empty string to do this. + SetInputScopeForIMM32(aWindow, EmptyString(), EmptyString()); + } +#endif // #ifdef NS_ENABLE_TSF + AssociateIMEContext(aWindow, true); +} + +#ifdef NS_ENABLE_TSF +// static +bool +IMEHandler::NeedsToAssociateIMC() +{ + if (sAssociateIMCOnlyWhenIMM_IMEActive) { + return TSFTextStore::IsIMM_IMEActive(); + } + + // Even if IMC should be associated with focused widget with non-IMM-IME, + // we need to avoid crash bug of MS-IME for Japanese on Win10. It crashes + // while we're associating default IME to a window when it's active. + static const bool sDoNotAssociateIMCWhenMSJapaneseIMEActiveOnWin10 = + IsWin10OrLater() && + Preferences::GetBool( + "intl.tsf.hack.ms_japanese_ime.do_not_associate_imc_on_win10", true); + return !sDoNotAssociateIMCWhenMSJapaneseIMEActiveOnWin10 || + !TSFTextStore::IsMSJapaneseIMEActive(); +} +#endif // #ifdef NS_ENABLE_TSF + +// static +void +IMEHandler::SetInputContext(nsWindow* aWindow, + InputContext& aInputContext, + const InputContextAction& aAction) +{ + sLastContextActionCause = aAction.mCause; + // FYI: If there is no composition, this call will do nothing. + NotifyIME(aWindow, IMENotification(REQUEST_TO_COMMIT_COMPOSITION)); + + const InputContext& oldInputContext = aWindow->GetInputContext(); + + // Assume that SetInputContext() is called only when aWindow has focus. + sPluginHasFocus = (aInputContext.mIMEState.mEnabled == IMEState::PLUGIN); + + if (aAction.UserMightRequestOpenVKB()) { + IMEHandler::MaybeShowOnScreenKeyboard(); + } + + bool enable = WinUtils::IsIMEEnabled(aInputContext); + bool adjustOpenState = (enable && + aInputContext.mIMEState.mOpen != IMEState::DONT_CHANGE_OPEN_STATE); + bool open = (adjustOpenState && + aInputContext.mIMEState.mOpen == IMEState::OPEN); + +#ifdef NS_ENABLE_TSF + // Note that even while a plugin has focus, we need to notify TSF of that. + if (sIsInTSFMode) { + TSFTextStore::SetInputContext(aWindow, aInputContext, aAction); + if (IsTSFAvailable()) { + if (sIsIMMEnabled) { + // Associate IMC with aWindow only when it's necessary. + AssociateIMEContext(aWindow, enable && NeedsToAssociateIMC()); + } else if (oldInputContext.mIMEState.mEnabled == IMEState::PLUGIN) { + // Disassociate the IME context from the window when plugin loses focus + // in pure TSF mode. + AssociateIMEContext(aWindow, false); + } + if (adjustOpenState) { + TSFTextStore::SetIMEOpenState(open); + } + return; + } + } else { + // Set at least InputScope even when TextStore is not available. + SetInputScopeForIMM32(aWindow, aInputContext.mHTMLInputType, + aInputContext.mHTMLInputInputmode); + } +#endif // #ifdef NS_ENABLE_TSF + + AssociateIMEContext(aWindow, enable); + + IMEContext context(aWindow); + if (adjustOpenState) { + context.SetOpenState(open); + } +} + +// static +void +IMEHandler::AssociateIMEContext(nsWindowBase* aWindowBase, bool aEnable) +{ + IMEContext context(aWindowBase); + if (aEnable) { + context.AssociateDefaultContext(); + return; + } + // Don't disassociate the context after the window is destroyed. + if (aWindowBase->Destroyed()) { + return; + } + context.Disassociate(); +} + +// static +void +IMEHandler::InitInputContext(nsWindow* aWindow, InputContext& aInputContext) +{ + // For a11y, the default enabled state should be 'enabled'. + aInputContext.mIMEState.mEnabled = IMEState::ENABLED; + +#ifdef NS_ENABLE_TSF + if (sIsInTSFMode) { + TSFTextStore::SetInputContext(aWindow, aInputContext, + InputContextAction(InputContextAction::CAUSE_UNKNOWN, + InputContextAction::GOT_FOCUS)); + // IME context isn't necessary in pure TSF mode. + if (!sIsIMMEnabled) { + AssociateIMEContext(aWindow, false); + } + return; + } +#endif // #ifdef NS_ENABLE_TSF + +#ifdef DEBUG + // NOTE: IMC may be null if IMM module isn't installed. + IMEContext context(aWindow); + MOZ_ASSERT(context.IsValid() || !CurrentKeyboardLayoutHasIME()); +#endif // #ifdef DEBUG +} + +#ifdef DEBUG +// static +bool +IMEHandler::CurrentKeyboardLayoutHasIME() +{ +#ifdef NS_ENABLE_TSF + if (sIsInTSFMode) { + return TSFTextStore::CurrentKeyboardLayoutHasIME(); + } +#endif // #ifdef NS_ENABLE_TSF + + return IMMHandler::IsIMEAvailable(); +} +#endif // #ifdef DEBUG + +// static +void +IMEHandler::OnKeyboardLayoutChanged() +{ + if (!sIsIMMEnabled || !IsTSFAvailable()) { + return; + } + + // If there is no TSFTextStore which has focus, i.e., no editor has focus, + // nothing to do here. + nsWindowBase* windowBase = TSFTextStore::GetEnabledWindowBase(); + if (!windowBase) { + return; + } + + // If IME isn't available, nothing to do here. + InputContext inputContext = windowBase->GetInputContext(); + if (!WinUtils::IsIMEEnabled(inputContext)) { + return; + } + + // Associate or Disassociate IMC if it's necessary. + // Note that this does nothing if the window has already associated with or + // disassociated from the window. + AssociateIMEContext(windowBase, NeedsToAssociateIMC()); +} + +// static +void +IMEHandler::SetInputScopeForIMM32(nsWindow* aWindow, + const nsAString& aHTMLInputType, + const nsAString& aHTMLInputInputmode) +{ + if (sIsInTSFMode || !sSetInputScopes || aWindow->Destroyed()) { + return; + } + UINT arraySize = 0; + const InputScope* scopes = nullptr; + // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-input-element.html + if (aHTMLInputType.IsEmpty() || aHTMLInputType.EqualsLiteral("text")) { + if (aHTMLInputInputmode.EqualsLiteral("url")) { + static const InputScope inputScopes[] = { IS_URL }; + scopes = &inputScopes[0]; + arraySize = ArrayLength(inputScopes); + } else if (aHTMLInputInputmode.EqualsLiteral("email")) { + static const InputScope inputScopes[] = { IS_EMAIL_SMTPEMAILADDRESS }; + scopes = &inputScopes[0]; + arraySize = ArrayLength(inputScopes); + } else if (aHTMLInputInputmode.EqualsLiteral("tel")) { + static const InputScope inputScopes[] = + {IS_TELEPHONE_LOCALNUMBER, IS_TELEPHONE_FULLTELEPHONENUMBER}; + scopes = &inputScopes[0]; + arraySize = ArrayLength(inputScopes); + } else if (aHTMLInputInputmode.EqualsLiteral("numeric")) { + static const InputScope inputScopes[] = { IS_NUMBER }; + scopes = &inputScopes[0]; + arraySize = ArrayLength(inputScopes); + } else { + static const InputScope inputScopes[] = { IS_DEFAULT }; + scopes = &inputScopes[0]; + arraySize = ArrayLength(inputScopes); + } + } else if (aHTMLInputType.EqualsLiteral("url")) { + static const InputScope inputScopes[] = { IS_URL }; + scopes = &inputScopes[0]; + arraySize = ArrayLength(inputScopes); + } else if (aHTMLInputType.EqualsLiteral("search")) { + static const InputScope inputScopes[] = { IS_SEARCH }; + scopes = &inputScopes[0]; + arraySize = ArrayLength(inputScopes); + } else if (aHTMLInputType.EqualsLiteral("email")) { + static const InputScope inputScopes[] = { IS_EMAIL_SMTPEMAILADDRESS }; + scopes = &inputScopes[0]; + arraySize = ArrayLength(inputScopes); + } else if (aHTMLInputType.EqualsLiteral("password")) { + static const InputScope inputScopes[] = { IS_PASSWORD }; + scopes = &inputScopes[0]; + arraySize = ArrayLength(inputScopes); + } else if (aHTMLInputType.EqualsLiteral("datetime") || + aHTMLInputType.EqualsLiteral("datetime-local")) { + static const InputScope inputScopes[] = { + IS_DATE_FULLDATE, IS_TIME_FULLTIME }; + scopes = &inputScopes[0]; + arraySize = ArrayLength(inputScopes); + } else if (aHTMLInputType.EqualsLiteral("date") || + aHTMLInputType.EqualsLiteral("month") || + aHTMLInputType.EqualsLiteral("week")) { + static const InputScope inputScopes[] = { IS_DATE_FULLDATE }; + scopes = &inputScopes[0]; + arraySize = ArrayLength(inputScopes); + } else if (aHTMLInputType.EqualsLiteral("time")) { + static const InputScope inputScopes[] = { IS_TIME_FULLTIME }; + scopes = &inputScopes[0]; + arraySize = ArrayLength(inputScopes); + } else if (aHTMLInputType.EqualsLiteral("tel")) { + static const InputScope inputScopes[] = { + IS_TELEPHONE_FULLTELEPHONENUMBER, IS_TELEPHONE_LOCALNUMBER }; + scopes = &inputScopes[0]; + arraySize = ArrayLength(inputScopes); + } else if (aHTMLInputType.EqualsLiteral("number")) { + static const InputScope inputScopes[] = { IS_NUMBER }; + scopes = &inputScopes[0]; + arraySize = ArrayLength(inputScopes); + } + if (scopes && arraySize > 0) { + sSetInputScopes(aWindow->GetWindowHandle(), scopes, arraySize, nullptr, 0, + nullptr, nullptr); + } +} + +// static +void +IMEHandler::MaybeShowOnScreenKeyboard() +{ + if (sPluginHasFocus || + !IsWin8OrLater() || + !Preferences::GetBool(kOskEnabled, true) || + GetOnScreenKeyboardWindow() || + !IMEHandler::NeedOnScreenKeyboard()) { + return; + } + + // On Windows 10 we require tablet mode, unless the user has set the relevant + // Windows setting to enable the on-screen keyboard in desktop mode. + // We might be disabled specifically on Win8(.1), so we check that afterwards. + if (IsWin10OrLater()) { + if (!IsInTabletMode() && !AutoInvokeOnScreenKeyboardInDesktopMode()) { + return; + } + } + else if (Preferences::GetBool(kOskRequireWin10, true)) { + return; + } + + IMEHandler::ShowOnScreenKeyboard(); +} + +// static +void +IMEHandler::MaybeDismissOnScreenKeyboard(nsWindow* aWindow) +{ + if (sPluginHasFocus || + !IsWin8OrLater()) { + return; + } + + ::PostMessage(aWindow->GetWindowHandle(), MOZ_WM_DISMISS_ONSCREEN_KEYBOARD, + 0, 0); +} + +// static +bool +IMEHandler::WStringStartsWithCaseInsensitive(const std::wstring& aHaystack, + const std::wstring& aNeedle) +{ + std::wstring lowerCaseHaystack(aHaystack); + std::wstring lowerCaseNeedle(aNeedle); + std::transform(lowerCaseHaystack.begin(), lowerCaseHaystack.end(), + lowerCaseHaystack.begin(), ::tolower); + std::transform(lowerCaseNeedle.begin(), lowerCaseNeedle.end(), + lowerCaseNeedle.begin(), ::tolower); + return wcsstr(lowerCaseHaystack.c_str(), + lowerCaseNeedle.c_str()) == lowerCaseHaystack.c_str(); +} + +// Returns false if a physical keyboard is detected on Windows 8 and up, +// or there is some other reason why an onscreen keyboard is not necessary. +// Returns true if no keyboard is found and this device looks like it needs +// an on-screen keyboard for text input. +// static +bool +IMEHandler::NeedOnScreenKeyboard() +{ + // This function is only supported for Windows 8 and up. + if (!IsWin8OrLater()) { + Preferences::SetString(kOskDebugReason, L"IKPOS: Requires Win8+."); + return false; + } + + if (!Preferences::GetBool(kOskDetectPhysicalKeyboard, true)) { + Preferences::SetString(kOskDebugReason, L"IKPOS: Detection disabled."); + return true; + } + + // If the last focus cause was not user-initiated (ie a result of code + // setting focus to an element) then don't auto-show a keyboard. This + // avoids cases where the keyboard would pop up "just" because e.g. a + // web page chooses to focus a search field on the page, even when that + // really isn't what the user is trying to do at that moment. + if (!InputContextAction::IsUserAction(sLastContextActionCause)) { + return false; + } + + // This function should be only invoked for machines with touch screens. + if ((::GetSystemMetrics(SM_DIGITIZER) & NID_INTEGRATED_TOUCH) + != NID_INTEGRATED_TOUCH) { + Preferences::SetString(kOskDebugReason, + L"IKPOS: Touch screen not found."); + return false; + } + + // If the device is docked, the user is treating the device as a PC. + if (::GetSystemMetrics(SM_SYSTEMDOCKED) != 0) { + Preferences::SetString(kOskDebugReason, L"IKPOS: System docked."); + return false; + } + + // To determine whether a keyboard is present on the device, we do the + // following:- + // 1. If the platform role is that of a mobile or slate device, check the + // system metric SM_CONVERTIBLESLATEMODE to see if it is being used + // in slate mode. If it is, also check that the last input was a touch. + // If all of this is true, then we should show the on-screen keyboard. + + // 2. If step 1 didn't determine we should show the keyboard, we check if + // this device has keyboards attached to it. + + // Check if the device is being used as a laptop or a tablet. This can be + // checked by first checking the role of the device and then the + // corresponding system metric (SM_CONVERTIBLESLATEMODE). If it is being + // used as a tablet then we want the OSK to show up. + typedef POWER_PLATFORM_ROLE (WINAPI* PowerDeterminePlatformRoleEx)(ULONG Version); + if (!sDeterminedPowerPlatformRole) { + sDeterminedPowerPlatformRole = true; + PowerDeterminePlatformRoleEx power_determine_platform_role = + reinterpret_cast<PowerDeterminePlatformRoleEx>(::GetProcAddress( + ::LoadLibraryW(L"PowrProf.dll"), "PowerDeterminePlatformRoleEx")); + if (power_determine_platform_role) { + sPowerPlatformRole = power_determine_platform_role(POWER_PLATFORM_ROLE_V2); + } else { + sPowerPlatformRole = PlatformRoleUnspecified; + } + } + + // If this a mobile or slate (tablet) device, check if it is in slate mode. + // If the last input was touch, ignore whether or not a keyboard is present. + if ((sPowerPlatformRole == PlatformRoleMobile || + sPowerPlatformRole == PlatformRoleSlate) && + ::GetSystemMetrics(SM_CONVERTIBLESLATEMODE) == 0 && + sLastContextActionCause == InputContextAction::CAUSE_TOUCH) { + Preferences::SetString(kOskDebugReason, L"IKPOS: Mobile/Slate Platform role, in slate mode with touch event."); + return true; + } + + return !IMEHandler::IsKeyboardPresentOnSlate(); +} + +// Uses the Setup APIs to enumerate the attached keyboards and returns true +// if the keyboard count is 1 or more. While this will work in most cases +// it won't work if there are devices which expose keyboard interfaces which +// are attached to the machine. +// Based on IsKeyboardPresentOnSlate() in Chromium's base/win/win_util.cc. +// static +bool +IMEHandler::IsKeyboardPresentOnSlate() +{ + const GUID KEYBOARD_CLASS_GUID = + { 0x4D36E96B, 0xE325, 0x11CE, + { 0xBF, 0xC1, 0x08, 0x00, 0x2B, 0xE1, 0x03, 0x18 } }; + + // Query for all the keyboard devices. + HDEVINFO device_info = + ::SetupDiGetClassDevs(&KEYBOARD_CLASS_GUID, nullptr, + nullptr, DIGCF_PRESENT); + if (device_info == INVALID_HANDLE_VALUE) { + Preferences::SetString(kOskDebugReason, L"IKPOS: No keyboard info."); + return false; + } + + // Enumerate all keyboards and look for ACPI\PNP and HID\VID devices. If + // the count is more than 1 we assume that a keyboard is present. This is + // under the assumption that there will always be one keyboard device. + for (DWORD i = 0;; ++i) { + SP_DEVINFO_DATA device_info_data = { 0 }; + device_info_data.cbSize = sizeof(device_info_data); + if (!::SetupDiEnumDeviceInfo(device_info, i, &device_info_data)) { + break; + } + + // Get the device ID. + wchar_t device_id[MAX_DEVICE_ID_LEN]; + CONFIGRET status = ::CM_Get_Device_ID(device_info_data.DevInst, + device_id, + MAX_DEVICE_ID_LEN, + 0); + if (status == CR_SUCCESS) { + static const std::wstring BT_HID_DEVICE = L"HID\\{00001124"; + static const std::wstring BT_HOGP_DEVICE = L"HID\\{00001812"; + // To reduce the scope of the hack we only look for ACPI and HID\\VID + // prefixes in the keyboard device ids. + if (IMEHandler::WStringStartsWithCaseInsensitive(device_id, + L"ACPI") || + IMEHandler::WStringStartsWithCaseInsensitive(device_id, + L"HID\\VID") || + IMEHandler::WStringStartsWithCaseInsensitive(device_id, + BT_HID_DEVICE) || + IMEHandler::WStringStartsWithCaseInsensitive(device_id, + BT_HOGP_DEVICE)) { + // The heuristic we are using is to check the count of keyboards and + // return true if the API's report one or more keyboards. Please note + // that this will break for non keyboard devices which expose a + // keyboard PDO. + Preferences::SetString(kOskDebugReason, + L"IKPOS: Keyboard presence confirmed."); + return true; + } + } + } + Preferences::SetString(kOskDebugReason, + L"IKPOS: Lack of keyboard confirmed."); + return false; +} + +// static +bool +IMEHandler::IsInTabletMode() +{ + nsCOMPtr<nsIWindowsUIUtils> + uiUtils(do_GetService("@mozilla.org/windows-ui-utils;1")); + if (NS_WARN_IF(!uiUtils)) { + Preferences::SetString(kOskDebugReason, + L"IITM: nsIWindowsUIUtils not available."); + return false; + } + bool isInTabletMode = false; + uiUtils->GetInTabletMode(&isInTabletMode); + if (isInTabletMode) { + Preferences::SetString(kOskDebugReason, L"IITM: GetInTabletMode=true."); + } else { + Preferences::SetString(kOskDebugReason, L"IITM: GetInTabletMode=false."); + } + return isInTabletMode; +} + +// static +bool +IMEHandler::AutoInvokeOnScreenKeyboardInDesktopMode() +{ + nsresult rv; + nsCOMPtr<nsIWindowsRegKey> regKey + (do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv)); + if (NS_WARN_IF(NS_FAILED(rv))) { + Preferences::SetString(kOskDebugReason, L"AIOSKIDM: " + L"nsIWindowsRegKey not available"); + return false; + } + rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER, + NS_LITERAL_STRING("SOFTWARE\\Microsoft\\TabletTip\\1.7"), + nsIWindowsRegKey::ACCESS_QUERY_VALUE); + if (NS_FAILED(rv)) { + Preferences::SetString(kOskDebugReason, + L"AIOSKIDM: failed opening regkey."); + return false; + } + // EnableDesktopModeAutoInvoke is an opt-in option from the Windows + // Settings to "Automatically show the touch keyboard in windowed apps + // when there's no keyboard attached to your device." If the user has + // opted-in to this behavior, the tablet-mode requirement is skipped. + uint32_t value; + rv = regKey->ReadIntValue(NS_LITERAL_STRING("EnableDesktopModeAutoInvoke"), + &value); + if (NS_FAILED(rv)) { + Preferences::SetString(kOskDebugReason, + L"AIOSKIDM: failed reading value of regkey."); + return false; + } + if (!!value) { + Preferences::SetString(kOskDebugReason, L"AIOSKIDM: regkey value=true."); + } else { + Preferences::SetString(kOskDebugReason, L"AIOSKIDM: regkey value=false."); + } + return !!value; +} + +// Based on DisplayVirtualKeyboard() in Chromium's base/win/win_util.cc. +// static +void +IMEHandler::ShowOnScreenKeyboard() +{ + nsAutoString cachedPath; + nsresult result = Preferences::GetString(kOskPathPrefName, &cachedPath); + if (NS_FAILED(result) || cachedPath.IsEmpty()) { + wchar_t path[MAX_PATH]; + // The path to TabTip.exe is defined at the following registry key. + // This is pulled out of the 64-bit registry hive directly. + const wchar_t kRegKeyName[] = + L"Software\\Classes\\CLSID\\" + L"{054AAE20-4BEA-4347-8A35-64A533254A9D}\\LocalServer32"; + if (!WinUtils::GetRegistryKey(HKEY_LOCAL_MACHINE, + kRegKeyName, + nullptr, + path, + sizeof path)) { + return; + } + + std::wstring wstrpath(path); + // The path provided by the registry will often contain + // %CommonProgramFiles%, which will need to be replaced if it is present. + size_t commonProgramFilesOffset = wstrpath.find(L"%CommonProgramFiles%"); + if (commonProgramFilesOffset != std::wstring::npos) { + // The path read from the registry contains the %CommonProgramFiles% + // environment variable prefix. On 64 bit Windows the + // SHGetKnownFolderPath function returns the common program files path + // with the X86 suffix for the FOLDERID_ProgramFilesCommon value. + // To get the correct path to TabTip.exe we first read the environment + // variable CommonProgramW6432 which points to the desired common + // files path. Failing that we fallback to the SHGetKnownFolderPath API. + + // We then replace the %CommonProgramFiles% value with the actual common + // files path found in the process. + std::wstring commonProgramFilesPath; + std::vector<wchar_t> commonProgramFilesPathW6432; + DWORD bufferSize = ::GetEnvironmentVariableW(L"CommonProgramW6432", + nullptr, 0); + if (bufferSize) { + commonProgramFilesPathW6432.resize(bufferSize); + ::GetEnvironmentVariableW(L"CommonProgramW6432", + commonProgramFilesPathW6432.data(), + bufferSize); + commonProgramFilesPath = + std::wstring(commonProgramFilesPathW6432.data()); + } else { + PWSTR path = nullptr; + HRESULT hres = + WinUtils::SHGetKnownFolderPath(FOLDERID_ProgramFilesCommon, 0, + nullptr, &path); + if (FAILED(hres) || !path) { + return; + } + commonProgramFilesPath = + static_cast<const wchar_t*>(nsDependentString(path).get()); + ::CoTaskMemFree(path); + } + wstrpath.replace(commonProgramFilesOffset, + wcslen(L"%CommonProgramFiles%"), + commonProgramFilesPath); + } + + cachedPath.Assign(wstrpath.data()); + Preferences::SetString(kOskPathPrefName, cachedPath); + } + + const char16_t *cachedPathPtr; + cachedPath.GetData(&cachedPathPtr); + ShellExecuteW(nullptr, + L"", + char16ptr_t(cachedPathPtr), + nullptr, + nullptr, + SW_SHOW); +} + +// Based on DismissVirtualKeyboard() in Chromium's base/win/win_util.cc. +// static +void +IMEHandler::DismissOnScreenKeyboard() +{ + // Dismiss the virtual keyboard if it's open + HWND osk = GetOnScreenKeyboardWindow(); + if (osk) { + ::PostMessage(osk, WM_SYSCOMMAND, SC_CLOSE, 0); + } +} + +// static +HWND +IMEHandler::GetOnScreenKeyboardWindow() +{ + const wchar_t kOSKClassName[] = L"IPTip_Main_Window"; + HWND osk = ::FindWindowW(kOSKClassName, nullptr); + if (::IsWindow(osk) && ::IsWindowEnabled(osk) && ::IsWindowVisible(osk)) { + return osk; + } + return nullptr; +} + +// static +void +IMEHandler::SetCandidateWindow(nsWindow* aWindow, CANDIDATEFORM* aForm) +{ + if (!sPluginHasFocus) { + return; + } + + IMMHandler::SetCandidateWindow(aWindow, aForm); +} + +// static +void +IMEHandler::DefaultProcOfPluginEvent(nsWindow* aWindow, + const NPEvent* aPluginEvent) +{ + if (!sPluginHasFocus) { + return; + } + IMMHandler::DefaultProcOfPluginEvent(aWindow, aPluginEvent); +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/windows/WinIMEHandler.h b/widget/windows/WinIMEHandler.h new file mode 100644 index 0000000000..c18a4437e8 --- /dev/null +++ b/widget/windows/WinIMEHandler.h @@ -0,0 +1,190 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef WinIMEHandler_h_ +#define WinIMEHandler_h_ + +#include "nscore.h" +#include "nsWindowBase.h" +#include "npapi.h" +#include <windows.h> +#include <inputscope.h> + +#define NS_WM_IMEFIRST WM_IME_SETCONTEXT +#define NS_WM_IMELAST WM_IME_KEYUP + +class nsWindow; + +namespace mozilla { +namespace widget { + +struct MSGResult; + +/** + * IMEHandler class is a mediator class. On Windows, there are two IME API + * sets: One is IMM which is legacy API set. The other is TSF which is modern + * API set. By using this class, non-IME handler classes don't need to worry + * that we're in which mode. + */ +class IMEHandler final +{ +public: + static void Initialize(); + static void Terminate(); + + /** + * Returns TSF related native data or native IME context. + */ + static void* GetNativeData(nsWindow* aWindow, uint32_t aDataType); + + /** + * ProcessRawKeyMessage() message is called before calling TranslateMessage() + * and DispatchMessage(). If this returns true, the message is consumed. + * Then, caller must not perform TranslateMessage() nor DispatchMessage(). + */ + static bool ProcessRawKeyMessage(const MSG& aMsg); + + /** + * When the message is not needed to handle anymore by the caller, this + * returns true. Otherwise, false. + */ + static bool ProcessMessage(nsWindow* aWindow, UINT aMessage, + WPARAM& aWParam, LPARAM& aLParam, + MSGResult& aResult); + + /** + * When there is a composition, returns true. Otherwise, false. + */ + static bool IsComposing(); + + /** + * When there is a composition and it's in the window, returns true. + * Otherwise, false. + */ + static bool IsComposingOn(nsWindow* aWindow); + + /** + * Notifies IME of the notification (a request or an event). + */ + static nsresult NotifyIME(nsWindow* aWindow, + const IMENotification& aIMENotification); + + /** + * Returns update preferences. + */ + static nsIMEUpdatePreference GetUpdatePreference(); + + /** + * Returns native text event dispatcher listener. + */ + static TextEventDispatcherListener* GetNativeTextEventDispatcherListener(); + + /** + * Returns IME open state on the window. + */ + static bool GetOpenState(nsWindow* aWindow); + + /** + * Called when the window is destroying. + */ + static void OnDestroyWindow(nsWindow* aWindow); + + /** + * Called when nsIWidget::SetInputContext() is called before the window's + * InputContext is modified actually. + */ + static void SetInputContext(nsWindow* aWindow, + InputContext& aInputContext, + const InputContextAction& aAction); + + /** + * Associate or disassociate IME context to/from the aWindowBase. + */ + static void AssociateIMEContext(nsWindowBase* aWindowBase, bool aEnable); + + /** + * Called when the window is created. + */ + static void InitInputContext(nsWindow* aWindow, InputContext& aInputContext); + + /* + * For windowless plugin helper. + */ + static void SetCandidateWindow(nsWindow* aWindow, CANDIDATEFORM* aForm); + + /* + * For WM_IME_*COMPOSITION messages and e10s with windowless plugin + */ + static void DefaultProcOfPluginEvent(nsWindow* aWindow, + const NPEvent* aPluginEvent); + +#ifdef NS_ENABLE_TSF + /** + * This is called by TSFStaticSink when active IME is changed. + */ + static void OnKeyboardLayoutChanged(); +#endif // #ifdef NS_ENABLE_TSF + +#ifdef DEBUG + /** + * Returns true when current keyboard layout has IME. Otherwise, false. + */ + static bool CurrentKeyboardLayoutHasIME(); +#endif // #ifdef DEBUG + +private: + static nsWindow* sFocusedWindow; + static InputContextAction::Cause sLastContextActionCause; + + static bool sPluginHasFocus; + +#ifdef NS_ENABLE_TSF + static decltype(SetInputScopes)* sSetInputScopes; + static void SetInputScopeForIMM32(nsWindow* aWindow, + const nsAString& aHTMLInputType, + const nsAString& aHTMLInputInputmode); + static bool sIsInTSFMode; + // If sIMMEnabled is false, any IME messages are not handled in TSF mode. + // Additionally, IME context is always disassociated from focused window. + static bool sIsIMMEnabled; + static bool sAssociateIMCOnlyWhenIMM_IMEActive; + + static bool IsTSFAvailable() { return (sIsInTSFMode && !sPluginHasFocus); } + static bool IsIMMActive(); + + static void MaybeShowOnScreenKeyboard(); + static void MaybeDismissOnScreenKeyboard(nsWindow* aWindow); + static bool WStringStartsWithCaseInsensitive(const std::wstring& aHaystack, + const std::wstring& aNeedle); + static bool NeedOnScreenKeyboard(); + static bool IsKeyboardPresentOnSlate(); + static bool IsInTabletMode(); + static bool AutoInvokeOnScreenKeyboardInDesktopMode(); + static bool NeedsToAssociateIMC(); + + /** + * Show the Windows on-screen keyboard. Only allowed for + * chrome documents and Windows 8 and higher. + */ + static void ShowOnScreenKeyboard(); + + /** + * Dismiss the Windows on-screen keyboard. Only allowed for + * Windows 8 and higher. + */ + static void DismissOnScreenKeyboard(); + + /** + * Get the HWND for the on-screen keyboard, if it's up. Only + * allowed for Windows 8 and higher. + */ + static HWND GetOnScreenKeyboardWindow(); +#endif // #ifdef NS_ENABLE_TSF +}; + +} // namespace widget +} // namespace mozilla + +#endif // #ifndef WinIMEHandler_h_ diff --git a/widget/windows/WinMessages.h b/widget/windows/WinMessages.h new file mode 100644 index 0000000000..e722a38360 --- /dev/null +++ b/widget/windows/WinMessages.h @@ -0,0 +1,187 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_widget_WinMessages_h_ +#define mozilla_widget_WinMessages_h_ + +/***************************************************************************** + * MOZ_WM_* messages + ****************************************************************************/ + +// A magic APP message that can be sent to quit, sort of like a +// QUERYENDSESSION/ENDSESSION, but without the query. +#define MOZ_WM_APP_QUIT (WM_APP+0x0300) +// Used as a "tracer" event to probe event loop latency. +#define MOZ_WM_TRACE (WM_APP+0x0301) +// Our internal message for WM_MOUSEWHEEL, WM_MOUSEHWHEEL, WM_VSCROLL and +// WM_HSCROLL +#define MOZ_WM_MOUSEVWHEEL (WM_APP+0x0310) +#define MOZ_WM_MOUSEHWHEEL (WM_APP+0x0311) +#define MOZ_WM_VSCROLL (WM_APP+0x0312) +#define MOZ_WM_HSCROLL (WM_APP+0x0313) +#define MOZ_WM_MOUSEWHEEL_FIRST MOZ_WM_MOUSEVWHEEL +#define MOZ_WM_MOUSEWHEEL_LAST MOZ_WM_HSCROLL +// If a popup window is being activated, we try to reactivate the previous +// window with this message. +#define MOZ_WM_REACTIVATE (WM_APP+0x0314) +// If TSFTextStore needs to notify TSF/TIP of layout change later, this +// message is posted. +#define MOZ_WM_NOTIY_TSF_OF_LAYOUT_CHANGE (WM_APP+0x0315) +// Internal message used in correcting backwards clock skew +#define MOZ_WM_SKEWFIX (WM_APP+0x0316) +// Internal message used for hiding the on-screen keyboard +#define MOZ_WM_DISMISS_ONSCREEN_KEYBOARD (WM_APP+0x0317) + +// Following MOZ_WM_*KEY* messages are used by PluginInstanceChild and +// NativeKey internally. (never posted to the queue) +#define MOZ_WM_KEYDOWN (WM_APP+0x0318) +#define MOZ_WM_KEYUP (WM_APP+0x0319) +#define MOZ_WM_SYSKEYDOWN (WM_APP+0x031A) +#define MOZ_WM_SYSKEYUP (WM_APP+0x031B) +#define MOZ_WM_CHAR (WM_APP+0x031C) +#define MOZ_WM_SYSCHAR (WM_APP+0x031D) +#define MOZ_WM_DEADCHAR (WM_APP+0x031E) +#define MOZ_WM_SYSDEADCHAR (WM_APP+0x031F) + +// Internal message for ensuring the file picker is visible on multi monitor +// systems, and when the screen resolution changes. +#define MOZ_WM_ENSUREVISIBLE (WM_APP+0x374F) + +// XXX Should rename them to MOZ_WM_* and use safer values! +// Messages for fullscreen transition window +#define WM_FULLSCREEN_TRANSITION_BEFORE (WM_USER + 0) +#define WM_FULLSCREEN_TRANSITION_AFTER (WM_USER + 1) + +/***************************************************************************** + * WM_* messages and related constants which may not be defined by + * old Windows SDK + ****************************************************************************/ + +#ifndef SM_CXPADDEDBORDER +#define SM_CXPADDEDBORDER 92 +#endif + +// require WINVER >= 0x601 +#ifndef SM_MAXIMUMTOUCHES +#define SM_MAXIMUMTOUCHES 95 +#endif + +#ifndef WM_THEMECHANGED +#define WM_THEMECHANGED 0x031A +#endif + +#ifndef WM_GETOBJECT +#define WM_GETOBJECT 0x03d +#endif + +#ifndef PBT_APMRESUMEAUTOMATIC +#define PBT_APMRESUMEAUTOMATIC 0x0012 +#endif + +#ifndef WM_MOUSEHWHEEL +#define WM_MOUSEHWHEEL 0x020E +#endif + +#ifndef MOUSEEVENTF_HWHEEL +#define MOUSEEVENTF_HWHEEL 0x01000 +#endif + +#ifndef WM_MOUSELEAVE +#define WM_MOUSELEAVE 0x02A3 +#endif + +#ifndef SPI_GETWHEELSCROLLCHARS +#define SPI_GETWHEELSCROLLCHARS 0x006C +#endif + +#ifndef SPI_SETWHEELSCROLLCHARS +#define SPI_SETWHEELSCROLLCHARS 0x006D +#endif + +#ifndef MAPVK_VSC_TO_VK +#define MAPVK_VK_TO_VSC 0 +#define MAPVK_VSC_TO_VK 1 +#define MAPVK_VK_TO_CHAR 2 +#define MAPVK_VSC_TO_VK_EX 3 +#define MAPVK_VK_TO_VSC_EX 4 +#endif + +#ifndef WM_DWMCOMPOSITIONCHANGED +#define WM_DWMCOMPOSITIONCHANGED 0x031E +#endif +#ifndef WM_DWMNCRENDERINGCHANGED +#define WM_DWMNCRENDERINGCHANGED 0x031F +#endif +#ifndef WM_DWMCOLORIZATIONCOLORCHANGED +#define WM_DWMCOLORIZATIONCOLORCHANGED 0x0320 +#endif +#ifndef WM_DWMWINDOWMAXIMIZEDCHANGE +#define WM_DWMWINDOWMAXIMIZEDCHANGE 0x0321 +#endif + +// Drop shadow window style +#define CS_XP_DROPSHADOW 0x00020000 + +// App Command messages for IntelliMouse and Natural Keyboard Pro +// These messages are not included in Visual C++ 6.0, but are in 7.0+ +#ifndef WM_APPCOMMAND +#define WM_APPCOMMAND 0x0319 +#endif + +#define FAPPCOMMAND_MASK 0xF000 + +#ifndef WM_GETTITLEBARINFOEX +#define WM_GETTITLEBARINFOEX 0x033F +#endif + +#ifndef CCHILDREN_TITLEBAR +#define CCHILDREN_TITLEBAR 5 +#endif + +#ifndef APPCOMMAND_BROWSER_BACKWARD + #define APPCOMMAND_BROWSER_BACKWARD 1 + #define APPCOMMAND_BROWSER_FORWARD 2 + #define APPCOMMAND_BROWSER_REFRESH 3 + #define APPCOMMAND_BROWSER_STOP 4 + #define APPCOMMAND_BROWSER_SEARCH 5 + #define APPCOMMAND_BROWSER_FAVORITES 6 + #define APPCOMMAND_BROWSER_HOME 7 + + #define APPCOMMAND_MEDIA_NEXTTRACK 11 + #define APPCOMMAND_MEDIA_PREVIOUSTRACK 12 + #define APPCOMMAND_MEDIA_STOP 13 + #define APPCOMMAND_MEDIA_PLAY_PAUSE 14 + + /* + * Additional commands currently not in use. + * + *#define APPCOMMAND_VOLUME_MUTE 8 + *#define APPCOMMAND_VOLUME_DOWN 9 + *#define APPCOMMAND_VOLUME_UP 10 + *#define APPCOMMAND_LAUNCH_MAIL 15 + *#define APPCOMMAND_LAUNCH_MEDIA_SELECT 16 + *#define APPCOMMAND_LAUNCH_APP1 17 + *#define APPCOMMAND_LAUNCH_APP2 18 + *#define APPCOMMAND_BASS_DOWN 19 + *#define APPCOMMAND_BASS_BOOST 20 + *#define APPCOMMAND_BASS_UP 21 + *#define APPCOMMAND_TREBLE_DOWN 22 + *#define APPCOMMAND_TREBLE_UP 23 + *#define FAPPCOMMAND_MOUSE 0x8000 + *#define FAPPCOMMAND_KEY 0 + *#define FAPPCOMMAND_OEM 0x1000 + */ + + #define GET_APPCOMMAND_LPARAM(lParam) ((short)(HIWORD(lParam) & ~FAPPCOMMAND_MASK)) + + /* + *#define GET_DEVICE_LPARAM(lParam) ((WORD)(HIWORD(lParam) & FAPPCOMMAND_MASK)) + *#define GET_MOUSEORKEY_LPARAM GET_DEVICE_LPARAM + *#define GET_FLAGS_LPARAM(lParam) (LOWORD(lParam)) + *#define GET_KEYSTATE_LPARAM(lParam) GET_FLAGS_LPARAM(lParam) + */ +#endif // #ifndef APPCOMMAND_BROWSER_BACKWARD + +#endif // #ifndef mozilla_widget_WinMessages_h_ diff --git a/widget/windows/WinModifierKeyState.h b/widget/windows/WinModifierKeyState.h new file mode 100644 index 0000000000..f2c1704b96 --- /dev/null +++ b/widget/windows/WinModifierKeyState.h @@ -0,0 +1,62 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_widget_WinModifierKeyState_h_ +#define mozilla_widget_WinModifierKeyState_h_ + +#include "mozilla/RefPtr.h" +#include "mozilla/EventForwards.h" +#include <windows.h> + +class nsCString; + +namespace mozilla { +namespace widget { + +class MOZ_STACK_CLASS ModifierKeyState final +{ +public: + ModifierKeyState(); + ModifierKeyState(bool aIsShiftDown, bool aIsControlDown, bool aIsAltDown); + ModifierKeyState(Modifiers aModifiers); + + void Update(); + + void Unset(Modifiers aRemovingModifiers); + void Set(Modifiers aAddingModifiers); + + void InitInputEvent(WidgetInputEvent& aInputEvent) const; + + bool IsShift() const; + bool IsControl() const; + bool IsAlt() const; + bool IsAltGr() const; + bool IsWin() const; + + bool MaybeMatchShortcutKey() const; + + bool IsCapsLocked() const; + bool IsNumLocked() const; + bool IsScrollLocked() const; + + MOZ_ALWAYS_INLINE Modifiers GetModifiers() const + { + return mModifiers; + } + +private: + Modifiers mModifiers; + + MOZ_ALWAYS_INLINE void EnsureAltGr(); + + void InitMouseEvent(WidgetInputEvent& aMouseEvent) const; +}; + +const nsCString ToString(const ModifierKeyState& aModifierKeyState); + +} // namespace widget +} // namespace mozilla + +#endif // #ifndef mozilla_widget_WinModifierKeyState_h_ diff --git a/widget/windows/WinMouseScrollHandler.cpp b/widget/windows/WinMouseScrollHandler.cpp new file mode 100644 index 0000000000..10937ba519 --- /dev/null +++ b/widget/windows/WinMouseScrollHandler.cpp @@ -0,0 +1,1799 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/DebugOnly.h" + +#include "mozilla/Logging.h" + +#include "WinMouseScrollHandler.h" +#include "nsWindow.h" +#include "nsWindowDefs.h" +#include "KeyboardLayout.h" +#include "WinUtils.h" +#include "nsGkAtoms.h" +#include "nsIDOMWindowUtils.h" +#include "nsIDOMWheelEvent.h" + +#include "mozilla/MiscEvents.h" +#include "mozilla/MouseEvents.h" +#include "mozilla/Preferences.h" +#include "mozilla/WindowsVersion.h" + +#include <psapi.h> + +namespace mozilla { +namespace widget { + +LazyLogModule gMouseScrollLog("MouseScrollHandlerWidgets"); + +static const char* GetBoolName(bool aBool) +{ + return aBool ? "TRUE" : "FALSE"; +} + +MouseScrollHandler* MouseScrollHandler::sInstance = nullptr; + +bool MouseScrollHandler::Device::sFakeScrollableWindowNeeded = false; + +bool MouseScrollHandler::Device::SynTP::sInitialized = false; +int32_t MouseScrollHandler::Device::SynTP::sMajorVersion = 0; +int32_t MouseScrollHandler::Device::SynTP::sMinorVersion = -1; + +bool MouseScrollHandler::Device::Elantech::sUseSwipeHack = false; +bool MouseScrollHandler::Device::Elantech::sUsePinchHack = false; +DWORD MouseScrollHandler::Device::Elantech::sZoomUntil = 0; + +bool MouseScrollHandler::Device::Apoint::sInitialized = false; +int32_t MouseScrollHandler::Device::Apoint::sMajorVersion = 0; +int32_t MouseScrollHandler::Device::Apoint::sMinorVersion = -1; + +bool MouseScrollHandler::Device::SetPoint::sMightBeUsing = false; + +// The duration until timeout of events transaction. The value is 1.5 sec, +// it's just a magic number, it was suggested by Logitech's engineer, see +// bug 605648 comment 90. +#define DEFAULT_TIMEOUT_DURATION 1500 + +/****************************************************************************** + * + * MouseScrollHandler + * + ******************************************************************************/ + +/* static */ +POINTS +MouseScrollHandler::GetCurrentMessagePos() +{ + if (SynthesizingEvent::IsSynthesizing()) { + return sInstance->mSynthesizingEvent->GetCursorPoint(); + } + DWORD pos = ::GetMessagePos(); + return MAKEPOINTS(pos); +} + +// Get rid of the GetMessagePos() API. +#define GetMessagePos() + +/* static */ +void +MouseScrollHandler::Initialize() +{ + Device::Init(); +} + +/* static */ +void +MouseScrollHandler::Shutdown() +{ + delete sInstance; + sInstance = nullptr; +} + +/* static */ +MouseScrollHandler* +MouseScrollHandler::GetInstance() +{ + if (!sInstance) { + sInstance = new MouseScrollHandler(); + } + return sInstance; +} + +MouseScrollHandler::MouseScrollHandler() : + mIsWaitingInternalMessage(false), + mSynthesizingEvent(nullptr) +{ + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll: Creating an instance, this=%p, sInstance=%p", + this, sInstance)); +} + +MouseScrollHandler::~MouseScrollHandler() +{ + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll: Destroying an instance, this=%p, sInstance=%p", + this, sInstance)); + + delete mSynthesizingEvent; +} + +/* static */ +void +MouseScrollHandler::MaybeLogKeyState() +{ + if (!MOZ_LOG_TEST(gMouseScrollLog, LogLevel::Debug)) { + return; + } + BYTE keyboardState[256]; + if (::GetKeyboardState(keyboardState)) { + for (size_t i = 0; i < ArrayLength(keyboardState); i++) { + if (keyboardState[i]) { + MOZ_LOG(gMouseScrollLog, LogLevel::Debug, + (" Current key state: keyboardState[0x%02X]=0x%02X (%s)", + i, keyboardState[i], + ((keyboardState[i] & 0x81) == 0x81) ? "Pressed and Toggled" : + (keyboardState[i] & 0x80) ? "Pressed" : + (keyboardState[i] & 0x01) ? "Toggled" : "Unknown")); + } + } + } else { + MOZ_LOG(gMouseScrollLog, LogLevel::Debug, + ("MouseScroll::MaybeLogKeyState(): Failed to print current keyboard " + "state")); + } +} + +/* static */ +bool +MouseScrollHandler::NeedsMessage(UINT aMsg) +{ + switch (aMsg) { + case WM_SETTINGCHANGE: + case WM_MOUSEWHEEL: + case WM_MOUSEHWHEEL: + case WM_HSCROLL: + case WM_VSCROLL: + case MOZ_WM_MOUSEVWHEEL: + case MOZ_WM_MOUSEHWHEEL: + case MOZ_WM_HSCROLL: + case MOZ_WM_VSCROLL: + case WM_KEYDOWN: + case WM_KEYUP: + return true; + } + return false; +} + +/* static */ +bool +MouseScrollHandler::ProcessMessage(nsWindowBase* aWidget, UINT msg, + WPARAM wParam, LPARAM lParam, + MSGResult& aResult) +{ + Device::Elantech::UpdateZoomUntil(); + + switch (msg) { + case WM_SETTINGCHANGE: + if (!sInstance) { + return false; + } + if (wParam == SPI_SETWHEELSCROLLLINES || + wParam == SPI_SETWHEELSCROLLCHARS) { + sInstance->mSystemSettings.MarkDirty(); + } + return false; + + case WM_MOUSEWHEEL: + case WM_MOUSEHWHEEL: + GetInstance()-> + ProcessNativeMouseWheelMessage(aWidget, msg, wParam, lParam); + sInstance->mSynthesizingEvent->NotifyNativeMessageHandlingFinished(); + // We don't need to call next wndproc for WM_MOUSEWHEEL and + // WM_MOUSEHWHEEL. We should consume them always. If the messages + // would be handled by our window again, it caused making infinite + // message loop. + aResult.mConsumed = true; + aResult.mResult = (msg != WM_MOUSEHWHEEL); + return true; + + case WM_HSCROLL: + case WM_VSCROLL: + aResult.mConsumed = + GetInstance()->ProcessNativeScrollMessage(aWidget, msg, wParam, lParam); + sInstance->mSynthesizingEvent->NotifyNativeMessageHandlingFinished(); + aResult.mResult = 0; + return true; + + case MOZ_WM_MOUSEVWHEEL: + case MOZ_WM_MOUSEHWHEEL: + GetInstance()->HandleMouseWheelMessage(aWidget, msg, wParam, lParam); + sInstance->mSynthesizingEvent->NotifyInternalMessageHandlingFinished(); + // Doesn't need to call next wndproc for internal wheel message. + aResult.mConsumed = true; + return true; + + case MOZ_WM_HSCROLL: + case MOZ_WM_VSCROLL: + GetInstance()-> + HandleScrollMessageAsMouseWheelMessage(aWidget, msg, wParam, lParam); + sInstance->mSynthesizingEvent->NotifyInternalMessageHandlingFinished(); + // Doesn't need to call next wndproc for internal scroll message. + aResult.mConsumed = true; + return true; + + case WM_KEYDOWN: + case WM_KEYUP: + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::ProcessMessage(): aWidget=%p, " + "msg=%s(0x%04X), wParam=0x%02X, ::GetMessageTime()=%d", + aWidget, msg == WM_KEYDOWN ? "WM_KEYDOWN" : + msg == WM_KEYUP ? "WM_KEYUP" : "Unknown", msg, wParam, + ::GetMessageTime())); + MaybeLogKeyState(); + if (Device::Elantech::HandleKeyMessage(aWidget, msg, wParam, lParam)) { + aResult.mResult = 0; + aResult.mConsumed = true; + return true; + } + return false; + + default: + return false; + } +} + +/* static */ +nsresult +MouseScrollHandler::SynthesizeNativeMouseScrollEvent(nsWindowBase* aWidget, + const LayoutDeviceIntPoint& aPoint, + uint32_t aNativeMessage, + int32_t aDelta, + uint32_t aModifierFlags, + uint32_t aAdditionalFlags) +{ + bool useFocusedWindow = + !(aAdditionalFlags & nsIDOMWindowUtils::MOUSESCROLL_PREFER_WIDGET_AT_POINT); + + POINT pt; + pt.x = aPoint.x; + pt.y = aPoint.y; + + HWND target = useFocusedWindow ? ::WindowFromPoint(pt) : ::GetFocus(); + NS_ENSURE_TRUE(target, NS_ERROR_FAILURE); + + WPARAM wParam = 0; + LPARAM lParam = 0; + switch (aNativeMessage) { + case WM_MOUSEWHEEL: + case WM_MOUSEHWHEEL: { + lParam = MAKELPARAM(pt.x, pt.y); + WORD mod = 0; + if (aModifierFlags & (nsIWidget::CTRL_L | nsIWidget::CTRL_R)) { + mod |= MK_CONTROL; + } + if (aModifierFlags & (nsIWidget::SHIFT_L | nsIWidget::SHIFT_R)) { + mod |= MK_SHIFT; + } + wParam = MAKEWPARAM(mod, aDelta); + break; + } + case WM_VSCROLL: + case WM_HSCROLL: + lParam = (aAdditionalFlags & + nsIDOMWindowUtils::MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL) ? + reinterpret_cast<LPARAM>(target) : 0; + wParam = aDelta; + break; + default: + return NS_ERROR_INVALID_ARG; + } + + // Ensure to make the instance. + GetInstance(); + + BYTE kbdState[256]; + memset(kbdState, 0, sizeof(kbdState)); + + AutoTArray<KeyPair,10> keySequence; + WinUtils::SetupKeyModifiersSequence(&keySequence, aModifierFlags); + + for (uint32_t i = 0; i < keySequence.Length(); ++i) { + uint8_t key = keySequence[i].mGeneral; + uint8_t keySpecific = keySequence[i].mSpecific; + kbdState[key] = 0x81; // key is down and toggled on if appropriate + if (keySpecific) { + kbdState[keySpecific] = 0x81; + } + } + + if (!sInstance->mSynthesizingEvent) { + sInstance->mSynthesizingEvent = new SynthesizingEvent(); + } + + POINTS pts; + pts.x = static_cast<SHORT>(pt.x); + pts.y = static_cast<SHORT>(pt.y); + return sInstance->mSynthesizingEvent-> + Synthesize(pts, target, aNativeMessage, wParam, lParam, kbdState); +} + +/* static */ +void +MouseScrollHandler::InitEvent(nsWindowBase* aWidget, + WidgetGUIEvent& aEvent, + LayoutDeviceIntPoint* aPoint) +{ + NS_ENSURE_TRUE_VOID(aWidget); + LayoutDeviceIntPoint point; + if (aPoint) { + point = *aPoint; + } else { + POINTS pts = GetCurrentMessagePos(); + POINT pt; + pt.x = pts.x; + pt.y = pts.y; + ::ScreenToClient(aWidget->GetWindowHandle(), &pt); + point.x = pt.x; + point.y = pt.y; + } + aWidget->InitEvent(aEvent, &point); +} + +/* static */ +ModifierKeyState +MouseScrollHandler::GetModifierKeyState(UINT aMessage) +{ + ModifierKeyState result; + // Assume the Control key is down if the Elantech touchpad has sent the + // mis-ordered WM_KEYDOWN/WM_MOUSEWHEEL messages. (See the comment in + // MouseScrollHandler::Device::Elantech::HandleKeyMessage().) + if ((aMessage == MOZ_WM_MOUSEVWHEEL || aMessage == WM_MOUSEWHEEL) && + !result.IsControl() && Device::Elantech::IsZooming()) { + result.Set(MODIFIER_CONTROL); + } + return result; +} + +POINT +MouseScrollHandler::ComputeMessagePos(UINT aMessage, + WPARAM aWParam, + LPARAM aLParam) +{ + POINT point; + if (Device::SetPoint::IsGetMessagePosResponseValid(aMessage, + aWParam, aLParam)) { + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::ComputeMessagePos: Using ::GetCursorPos()")); + ::GetCursorPos(&point); + } else { + POINTS pts = GetCurrentMessagePos(); + point.x = pts.x; + point.y = pts.y; + } + return point; +} + +void +MouseScrollHandler::ProcessNativeMouseWheelMessage(nsWindowBase* aWidget, + UINT aMessage, + WPARAM aWParam, + LPARAM aLParam) +{ + if (SynthesizingEvent::IsSynthesizing()) { + mSynthesizingEvent->NativeMessageReceived(aWidget, aMessage, + aWParam, aLParam); + } + + POINT point = ComputeMessagePos(aMessage, aWParam, aLParam); + + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::ProcessNativeMouseWheelMessage: aWidget=%p, " + "aMessage=%s, wParam=0x%08X, lParam=0x%08X, point: { x=%d, y=%d }", + aWidget, aMessage == WM_MOUSEWHEEL ? "WM_MOUSEWHEEL" : + aMessage == WM_MOUSEHWHEEL ? "WM_MOUSEHWHEEL" : + aMessage == WM_VSCROLL ? "WM_VSCROLL" : "WM_HSCROLL", + aWParam, aLParam, point.x, point.y)); + MaybeLogKeyState(); + + HWND underCursorWnd = ::WindowFromPoint(point); + if (!underCursorWnd) { + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::ProcessNativeMouseWheelMessage: " + "No window is not found under the cursor")); + return; + } + + if (Device::Elantech::IsPinchHackNeeded() && + Device::Elantech::IsHelperWindow(underCursorWnd)) { + // The Elantech driver places a window right underneath the cursor + // when sending a WM_MOUSEWHEEL event to us as part of a pinch-to-zoom + // gesture. We detect that here, and search for our window that would + // be beneath the cursor if that window wasn't there. + underCursorWnd = WinUtils::FindOurWindowAtPoint(point); + if (!underCursorWnd) { + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::ProcessNativeMouseWheelMessage: " + "Our window is not found under the Elantech helper window")); + return; + } + } + + // Handle most cases first. If the window under mouse cursor is our window + // except plugin window (MozillaWindowClass), we should handle the message + // on the window. + if (WinUtils::IsOurProcessWindow(underCursorWnd)) { + nsWindowBase* destWindow = WinUtils::GetNSWindowBasePtr(underCursorWnd); + if (!destWindow) { + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::ProcessNativeMouseWheelMessage: " + "Found window under the cursor isn't managed by nsWindow...")); + HWND wnd = ::GetParent(underCursorWnd); + for (; wnd; wnd = ::GetParent(wnd)) { + destWindow = WinUtils::GetNSWindowBasePtr(wnd); + if (destWindow) { + break; + } + } + if (!wnd) { + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::ProcessNativeMouseWheelMessage: Our window which is " + "managed by nsWindow is not found under the cursor")); + return; + } + } + + MOZ_ASSERT(destWindow, "destWindow must not be NULL"); + + // If the found window is our plugin window, it means that the message + // has been handled by the plugin but not consumed. We should handle the + // message on its parent window. However, note that the DOM event may + // cause accessing the plugin. Therefore, we should unlock the plugin + // process by using PostMessage(). + if (destWindow->IsPlugin()) { + destWindow = destWindow->GetParentWindowBase(false); + if (!destWindow) { + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::ProcessNativeMouseWheelMessage: " + "Our window which is a parent of a plugin window is not found")); + return; + } + } + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::ProcessNativeMouseWheelMessage: Succeeded, " + "Posting internal message to an nsWindow (%p)...", + destWindow)); + mIsWaitingInternalMessage = true; + UINT internalMessage = WinUtils::GetInternalMessage(aMessage); + ::PostMessage(destWindow->GetWindowHandle(), internalMessage, + aWParam, aLParam); + return; + } + + // If the window under cursor is not in our process, it means: + // 1. The window may be a plugin window (GeckoPluginWindow or its descendant). + // 2. The window may be another application's window. + HWND pluginWnd = WinUtils::FindOurProcessWindow(underCursorWnd); + if (!pluginWnd) { + // If there is no plugin window in ancestors of the window under cursor, + // the window is for another applications (case 2). + // We don't need to handle this message. + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::ProcessNativeMouseWheelMessage: " + "Our window is not found under the cursor")); + return; + } + + // If we're a plugin window (MozillaWindowClass) and cursor in this window, + // the message shouldn't go to plugin's wndproc again. So, we should handle + // it on parent window. However, note that the DOM event may cause accessing + // the plugin. Therefore, we should unlock the plugin process by using + // PostMessage(). + if (aWidget->IsPlugin() && + aWidget->GetWindowHandle() == pluginWnd) { + nsWindowBase* destWindow = aWidget->GetParentWindowBase(false); + if (!destWindow) { + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::ProcessNativeMouseWheelMessage: Our normal window which " + "is a parent of this plugin window is not found")); + return; + } + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::ProcessNativeMouseWheelMessage: Succeeded, " + "Posting internal message to an nsWindow (%p) which is parent of this " + "plugin window...", + destWindow)); + mIsWaitingInternalMessage = true; + UINT internalMessage = WinUtils::GetInternalMessage(aMessage); + ::PostMessage(destWindow->GetWindowHandle(), internalMessage, + aWParam, aLParam); + return; + } + + // If the window is a part of plugin, we should post the message to it. + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::ProcessNativeMouseWheelMessage: Succeeded, " + "Redirecting the message to a window which is a plugin child window")); + ::PostMessage(underCursorWnd, aMessage, aWParam, aLParam); +} + +bool +MouseScrollHandler::ProcessNativeScrollMessage(nsWindowBase* aWidget, + UINT aMessage, + WPARAM aWParam, + LPARAM aLParam) +{ + if (aLParam || mUserPrefs.IsScrollMessageHandledAsWheelMessage()) { + // Scroll message generated by Thinkpad Trackpoint Driver or similar + // Treat as a mousewheel message and scroll appropriately + ProcessNativeMouseWheelMessage(aWidget, aMessage, aWParam, aLParam); + // Always consume the scroll message if we try to emulate mouse wheel + // action. + return true; + } + + if (SynthesizingEvent::IsSynthesizing()) { + mSynthesizingEvent->NativeMessageReceived(aWidget, aMessage, + aWParam, aLParam); + } + + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::ProcessNativeScrollMessage: aWidget=%p, " + "aMessage=%s, wParam=0x%08X, lParam=0x%08X", + aWidget, aMessage == WM_VSCROLL ? "WM_VSCROLL" : "WM_HSCROLL", + aWParam, aLParam)); + + // Scroll message generated by external application + WidgetContentCommandEvent commandEvent(true, eContentCommandScroll, aWidget); + commandEvent.mScroll.mIsHorizontal = (aMessage == WM_HSCROLL); + + switch (LOWORD(aWParam)) { + case SB_LINEUP: // SB_LINELEFT + commandEvent.mScroll.mUnit = + WidgetContentCommandEvent::eCmdScrollUnit_Line; + commandEvent.mScroll.mAmount = -1; + break; + case SB_LINEDOWN: // SB_LINERIGHT + commandEvent.mScroll.mUnit = + WidgetContentCommandEvent::eCmdScrollUnit_Line; + commandEvent.mScroll.mAmount = 1; + break; + case SB_PAGEUP: // SB_PAGELEFT + commandEvent.mScroll.mUnit = + WidgetContentCommandEvent::eCmdScrollUnit_Page; + commandEvent.mScroll.mAmount = -1; + break; + case SB_PAGEDOWN: // SB_PAGERIGHT + commandEvent.mScroll.mUnit = + WidgetContentCommandEvent::eCmdScrollUnit_Page; + commandEvent.mScroll.mAmount = 1; + break; + case SB_TOP: // SB_LEFT + commandEvent.mScroll.mUnit = + WidgetContentCommandEvent::eCmdScrollUnit_Whole; + commandEvent.mScroll.mAmount = -1; + break; + case SB_BOTTOM: // SB_RIGHT + commandEvent.mScroll.mUnit = + WidgetContentCommandEvent::eCmdScrollUnit_Whole; + commandEvent.mScroll.mAmount = 1; + break; + default: + return false; + } + // XXX If this is a plugin window, we should dispatch the event from + // parent window. + aWidget->DispatchContentCommandEvent(&commandEvent); + return true; +} + +void +MouseScrollHandler::HandleMouseWheelMessage(nsWindowBase* aWidget, + UINT aMessage, + WPARAM aWParam, + LPARAM aLParam) +{ + MOZ_ASSERT( + (aMessage == MOZ_WM_MOUSEVWHEEL || aMessage == MOZ_WM_MOUSEHWHEEL), + "HandleMouseWheelMessage must be called with " + "MOZ_WM_MOUSEVWHEEL or MOZ_WM_MOUSEHWHEEL"); + + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::HandleMouseWheelMessage: aWidget=%p, " + "aMessage=MOZ_WM_MOUSE%sWHEEL, aWParam=0x%08X, aLParam=0x%08X", + aWidget, aMessage == MOZ_WM_MOUSEVWHEEL ? "V" : "H", + aWParam, aLParam)); + + mIsWaitingInternalMessage = false; + + // If it's not allowed to cache system settings, we need to reset the cache + // before handling the mouse wheel message. + mSystemSettings.TrustedScrollSettingsDriver(); + + EventInfo eventInfo(aWidget, WinUtils::GetNativeMessage(aMessage), + aWParam, aLParam); + if (!eventInfo.CanDispatchWheelEvent()) { + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::HandleMouseWheelMessage: Cannot dispatch the events")); + mLastEventInfo.ResetTransaction(); + return; + } + + // Discard the remaining delta if current wheel message and last one are + // received by different window or to scroll different direction or + // different unit scroll. Furthermore, if the last event was too old. + if (!mLastEventInfo.CanContinueTransaction(eventInfo)) { + mLastEventInfo.ResetTransaction(); + } + + mLastEventInfo.RecordEvent(eventInfo); + + ModifierKeyState modKeyState = GetModifierKeyState(aMessage); + + // Grab the widget, it might be destroyed by a DOM event handler. + RefPtr<nsWindowBase> kungFuDethGrip(aWidget); + + WidgetWheelEvent wheelEvent(true, eWheel, aWidget); + if (mLastEventInfo.InitWheelEvent(aWidget, wheelEvent, modKeyState)) { + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::HandleMouseWheelMessage: dispatching " + "eWheel event")); + aWidget->DispatchWheelEvent(&wheelEvent); + if (aWidget->Destroyed()) { + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::HandleMouseWheelMessage: The window was destroyed " + "by eWheel event")); + mLastEventInfo.ResetTransaction(); + return; + } + } + else { + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::HandleMouseWheelMessage: eWheel event is not " + "dispatched")); + } +} + +void +MouseScrollHandler::HandleScrollMessageAsMouseWheelMessage(nsWindowBase* aWidget, + UINT aMessage, + WPARAM aWParam, + LPARAM aLParam) +{ + MOZ_ASSERT( + (aMessage == MOZ_WM_VSCROLL || aMessage == MOZ_WM_HSCROLL), + "HandleScrollMessageAsMouseWheelMessage must be called with " + "MOZ_WM_VSCROLL or MOZ_WM_HSCROLL"); + + mIsWaitingInternalMessage = false; + + ModifierKeyState modKeyState = GetModifierKeyState(aMessage); + + WidgetWheelEvent wheelEvent(true, eWheel, aWidget); + double& delta = + (aMessage == MOZ_WM_VSCROLL) ? wheelEvent.mDeltaY : wheelEvent.mDeltaX; + int32_t& lineOrPageDelta = + (aMessage == MOZ_WM_VSCROLL) ? wheelEvent.mLineOrPageDeltaY : + wheelEvent.mLineOrPageDeltaX; + + delta = 1.0; + lineOrPageDelta = 1; + + switch (LOWORD(aWParam)) { + case SB_PAGEUP: + delta = -1.0; + lineOrPageDelta = -1; + case SB_PAGEDOWN: + wheelEvent.mDeltaMode = nsIDOMWheelEvent::DOM_DELTA_PAGE; + break; + + case SB_LINEUP: + delta = -1.0; + lineOrPageDelta = -1; + case SB_LINEDOWN: + wheelEvent.mDeltaMode = nsIDOMWheelEvent::DOM_DELTA_LINE; + break; + + default: + return; + } + modKeyState.InitInputEvent(wheelEvent); + // XXX Current mouse position may not be same as when the original message + // is received. We need to know the actual mouse cursor position when + // the original message was received. + InitEvent(aWidget, wheelEvent); + + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::HandleScrollMessageAsMouseWheelMessage: aWidget=%p, " + "aMessage=MOZ_WM_%sSCROLL, aWParam=0x%08X, aLParam=0x%08X, " + "wheelEvent { mRefPoint: { x: %d, y: %d }, mDeltaX: %f, mDeltaY: %f, " + "mLineOrPageDeltaX: %d, mLineOrPageDeltaY: %d, " + "isShift: %s, isControl: %s, isAlt: %s, isMeta: %s }", + aWidget, (aMessage == MOZ_WM_VSCROLL) ? "V" : "H", aWParam, aLParam, + wheelEvent.mRefPoint.x, wheelEvent.mRefPoint.y, + wheelEvent.mDeltaX, wheelEvent.mDeltaY, + wheelEvent.mLineOrPageDeltaX, wheelEvent.mLineOrPageDeltaY, + GetBoolName(wheelEvent.IsShift()), + GetBoolName(wheelEvent.IsControl()), + GetBoolName(wheelEvent.IsAlt()), + GetBoolName(wheelEvent.IsMeta()))); + + aWidget->DispatchWheelEvent(&wheelEvent); +} + +/****************************************************************************** + * + * EventInfo + * + ******************************************************************************/ + +MouseScrollHandler::EventInfo::EventInfo(nsWindowBase* aWidget, + UINT aMessage, + WPARAM aWParam, LPARAM aLParam) +{ + MOZ_ASSERT(aMessage == WM_MOUSEWHEEL || aMessage == WM_MOUSEHWHEEL, + "EventInfo must be initialized with WM_MOUSEWHEEL or WM_MOUSEHWHEEL"); + + MouseScrollHandler::GetInstance()->mSystemSettings.Init(); + + mIsVertical = (aMessage == WM_MOUSEWHEEL); + mIsPage = MouseScrollHandler::sInstance-> + mSystemSettings.IsPageScroll(mIsVertical); + mDelta = (short)HIWORD(aWParam); + mWnd = aWidget->GetWindowHandle(); + mTimeStamp = TimeStamp::Now(); +} + +bool +MouseScrollHandler::EventInfo::CanDispatchWheelEvent() const +{ + if (!GetScrollAmount()) { + // XXX I think that we should dispatch mouse wheel events even if the + // operation will not scroll because the wheel operation really happened + // and web application may want to handle the event for non-scroll action. + return false; + } + + return (mDelta != 0); +} + +int32_t +MouseScrollHandler::EventInfo::GetScrollAmount() const +{ + if (mIsPage) { + return 1; + } + return MouseScrollHandler::sInstance-> + mSystemSettings.GetScrollAmount(mIsVertical); +} + +/****************************************************************************** + * + * LastEventInfo + * + ******************************************************************************/ + +bool +MouseScrollHandler::LastEventInfo::CanContinueTransaction( + const EventInfo& aNewEvent) +{ + int32_t timeout = MouseScrollHandler::sInstance-> + mUserPrefs.GetMouseScrollTransactionTimeout(); + return !mWnd || + (mWnd == aNewEvent.GetWindowHandle() && + IsPositive() == aNewEvent.IsPositive() && + mIsVertical == aNewEvent.IsVertical() && + mIsPage == aNewEvent.IsPage() && + (timeout < 0 || + TimeStamp::Now() - mTimeStamp <= + TimeDuration::FromMilliseconds(timeout))); +} + +void +MouseScrollHandler::LastEventInfo::ResetTransaction() +{ + if (!mWnd) { + return; + } + + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::LastEventInfo::ResetTransaction()")); + + mWnd = nullptr; + mAccumulatedDelta = 0; +} + +void +MouseScrollHandler::LastEventInfo::RecordEvent(const EventInfo& aEvent) +{ + mWnd = aEvent.GetWindowHandle(); + mDelta = aEvent.GetNativeDelta(); + mIsVertical = aEvent.IsVertical(); + mIsPage = aEvent.IsPage(); + mTimeStamp = TimeStamp::Now(); +} + +/* static */ +int32_t +MouseScrollHandler::LastEventInfo::RoundDelta(double aDelta) +{ + return (aDelta >= 0) ? (int32_t)floor(aDelta) : (int32_t)ceil(aDelta); +} + +bool +MouseScrollHandler::LastEventInfo::InitWheelEvent( + nsWindowBase* aWidget, + WidgetWheelEvent& aWheelEvent, + const ModifierKeyState& aModKeyState) +{ + MOZ_ASSERT(aWheelEvent.mMessage == eWheel); + + // XXX Why don't we use lParam value? We should use lParam value because + // our internal message is always posted by original message handler. + // So, GetMessagePos() may return different cursor position. + InitEvent(aWidget, aWheelEvent); + + aModKeyState.InitInputEvent(aWheelEvent); + + // Our positive delta value means to bottom or right. + // But positive native delta value means to top or right. + // Use orienter for computing our delta value with native delta value. + int32_t orienter = mIsVertical ? -1 : 1; + + aWheelEvent.mDeltaMode = mIsPage ? nsIDOMWheelEvent::DOM_DELTA_PAGE : + nsIDOMWheelEvent::DOM_DELTA_LINE; + + double& delta = mIsVertical ? aWheelEvent.mDeltaY : aWheelEvent.mDeltaX; + int32_t& lineOrPageDelta = mIsVertical ? aWheelEvent.mLineOrPageDeltaY : + aWheelEvent.mLineOrPageDeltaX; + + double nativeDeltaPerUnit = + mIsPage ? static_cast<double>(WHEEL_DELTA) : + static_cast<double>(WHEEL_DELTA) / GetScrollAmount(); + + delta = static_cast<double>(mDelta) * orienter / nativeDeltaPerUnit; + mAccumulatedDelta += mDelta; + lineOrPageDelta = + mAccumulatedDelta * orienter / RoundDelta(nativeDeltaPerUnit); + mAccumulatedDelta -= + lineOrPageDelta * orienter * RoundDelta(nativeDeltaPerUnit); + + if (aWheelEvent.mDeltaMode != nsIDOMWheelEvent::DOM_DELTA_LINE) { + // If the scroll delta mode isn't per line scroll, we shouldn't allow to + // override the system scroll speed setting. + aWheelEvent.mAllowToOverrideSystemScrollSpeed = false; + } else if (!MouseScrollHandler::sInstance-> + mSystemSettings.IsOverridingSystemScrollSpeedAllowed()) { + // If the system settings are customized by either the user or + // the mouse utility, we shouldn't allow to override the system scroll + // speed setting. + aWheelEvent.mAllowToOverrideSystemScrollSpeed = false; + } else { + // For suppressing too fast scroll, we should ensure that the maximum + // overridden delta value should be less than overridden scroll speed + // with default scroll amount. + double defaultScrollAmount = + mIsVertical ? SystemSettings::DefaultScrollLines() : + SystemSettings::DefaultScrollChars(); + double maxDelta = + WidgetWheelEvent::ComputeOverriddenDelta(defaultScrollAmount, + mIsVertical); + if (maxDelta != defaultScrollAmount) { + double overriddenDelta = + WidgetWheelEvent::ComputeOverriddenDelta(Abs(delta), mIsVertical); + if (overriddenDelta > maxDelta) { + // Suppress to fast scroll since overriding system scroll speed with + // current delta value causes too big delta value. + aWheelEvent.mAllowToOverrideSystemScrollSpeed = false; + } + } + } + + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::LastEventInfo::InitWheelEvent: aWidget=%p, " + "aWheelEvent { mRefPoint: { x: %d, y: %d }, mDeltaX: %f, mDeltaY: %f, " + "mLineOrPageDeltaX: %d, mLineOrPageDeltaY: %d, " + "isShift: %s, isControl: %s, isAlt: %s, isMeta: %s, " + "mAllowToOverrideSystemScrollSpeed: %s }, " + "mAccumulatedDelta: %d", + aWidget, aWheelEvent.mRefPoint.x, aWheelEvent.mRefPoint.y, + aWheelEvent.mDeltaX, aWheelEvent.mDeltaY, + aWheelEvent.mLineOrPageDeltaX, aWheelEvent.mLineOrPageDeltaY, + GetBoolName(aWheelEvent.IsShift()), + GetBoolName(aWheelEvent.IsControl()), + GetBoolName(aWheelEvent.IsAlt()), + GetBoolName(aWheelEvent.IsMeta()), + GetBoolName(aWheelEvent.mAllowToOverrideSystemScrollSpeed), + mAccumulatedDelta)); + + return (delta != 0); +} + +/****************************************************************************** + * + * SystemSettings + * + ******************************************************************************/ + +void +MouseScrollHandler::SystemSettings::Init() +{ + if (mInitialized) { + return; + } + + InitScrollLines(); + InitScrollChars(); + + mInitialized = true; + + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::SystemSettings::Init(): initialized, " + "mScrollLines=%d, mScrollChars=%d", + mScrollLines, mScrollChars)); +} + +bool +MouseScrollHandler::SystemSettings::InitScrollLines() +{ + int32_t oldValue = mInitialized ? mScrollLines : 0; + mIsReliableScrollLines = false; + mScrollLines = MouseScrollHandler::sInstance-> + mUserPrefs.GetOverriddenVerticalScrollAmout(); + if (mScrollLines >= 0) { + // overridden by the pref. + mIsReliableScrollLines = true; + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::SystemSettings::InitScrollLines(): mScrollLines is " + "overridden by the pref: %d", + mScrollLines)); + } else if (!::SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, + &mScrollLines, 0)) { + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::SystemSettings::InitScrollLines(): ::SystemParametersInfo(" + "SPI_GETWHEELSCROLLLINES) failed")); + mScrollLines = DefaultScrollLines(); + } + + if (mScrollLines > WHEEL_DELTA) { + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::SystemSettings::InitScrollLines(): the result of " + "::SystemParametersInfo(SPI_GETWHEELSCROLLLINES) is too large: %d", + mScrollLines)); + // sScrollLines usually equals 3 or 0 (for no scrolling) + // However, if sScrollLines > WHEEL_DELTA, we assume that + // the mouse driver wants a page scroll. The docs state that + // sScrollLines should explicitly equal WHEEL_PAGESCROLL, but + // since some mouse drivers use an arbitrary large number instead, + // we have to handle that as well. + mScrollLines = WHEEL_PAGESCROLL; + } + + return oldValue != mScrollLines; +} + +bool +MouseScrollHandler::SystemSettings::InitScrollChars() +{ + int32_t oldValue = mInitialized ? mScrollChars : 0; + mIsReliableScrollChars = false; + mScrollChars = MouseScrollHandler::sInstance-> + mUserPrefs.GetOverriddenHorizontalScrollAmout(); + if (mScrollChars >= 0) { + // overridden by the pref. + mIsReliableScrollChars = true; + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::SystemSettings::InitScrollChars(): mScrollChars is " + "overridden by the pref: %d", + mScrollChars)); + } else if (!::SystemParametersInfo(SPI_GETWHEELSCROLLCHARS, 0, + &mScrollChars, 0)) { + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::SystemSettings::InitScrollChars(): ::SystemParametersInfo(" + "SPI_GETWHEELSCROLLCHARS) failed, %s", + IsVistaOrLater() ? + "this is unexpected on Vista or later" : + "but on XP or earlier, this is not a problem")); + // XXX Should we use DefaultScrollChars()? + mScrollChars = 1; + } + + if (mScrollChars > WHEEL_DELTA) { + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::SystemSettings::InitScrollChars(): the result of " + "::SystemParametersInfo(SPI_GETWHEELSCROLLCHARS) is too large: %d", + mScrollChars)); + // See the comments for the case mScrollLines > WHEEL_DELTA. + mScrollChars = WHEEL_PAGESCROLL; + } + + return oldValue != mScrollChars; +} + +void +MouseScrollHandler::SystemSettings::MarkDirty() +{ + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScrollHandler::SystemSettings::MarkDirty(): " + "Marking SystemSettings dirty")); + mInitialized = false; + // When system settings are changed, we should reset current transaction. + MOZ_ASSERT(sInstance, + "Must not be called at initializing MouseScrollHandler"); + MouseScrollHandler::sInstance->mLastEventInfo.ResetTransaction(); +} + +void +MouseScrollHandler::SystemSettings::RefreshCache() +{ + bool isChanged = InitScrollLines(); + isChanged = InitScrollChars() || isChanged; + if (!isChanged) { + return; + } + // If the scroll amount is changed, we should reset current transaction. + MOZ_ASSERT(sInstance, + "Must not be called at initializing MouseScrollHandler"); + MouseScrollHandler::sInstance->mLastEventInfo.ResetTransaction(); +} + +void +MouseScrollHandler::SystemSettings::TrustedScrollSettingsDriver() +{ + if (!mInitialized) { + return; + } + + // if the cache is initialized with prefs, we don't need to refresh it. + if (mIsReliableScrollLines && mIsReliableScrollChars) { + return; + } + + MouseScrollHandler::UserPrefs& userPrefs = + MouseScrollHandler::sInstance->mUserPrefs; + + // If system settings cache is disabled, we should always refresh them. + if (!userPrefs.IsSystemSettingCacheEnabled()) { + RefreshCache(); + return; + } + + // If pref is set to as "always trust the cache", we shouldn't refresh them + // in any environments. + if (userPrefs.IsSystemSettingCacheForciblyEnabled()) { + return; + } + + // If SynTP of Synaptics or Apoint of Alps is installed, it may hook + // ::SystemParametersInfo() and returns different value from system settings. + if (Device::SynTP::IsDriverInstalled() || + Device::Apoint::IsDriverInstalled()) { + RefreshCache(); + return; + } + + // XXX We're not sure about other touchpad drivers... +} + +bool +MouseScrollHandler::SystemSettings::IsOverridingSystemScrollSpeedAllowed() +{ + return mScrollLines == DefaultScrollLines() && + (!IsVistaOrLater() || mScrollChars == DefaultScrollChars()); +} + +/****************************************************************************** + * + * UserPrefs + * + ******************************************************************************/ + +MouseScrollHandler::UserPrefs::UserPrefs() : + mInitialized(false) +{ + // We need to reset mouse wheel transaction when all of mousewheel related + // prefs are changed. + DebugOnly<nsresult> rv = + Preferences::RegisterCallback(OnChange, "mousewheel.", this); + MOZ_ASSERT(NS_SUCCEEDED(rv), + "Failed to register callback for mousewheel."); +} + +MouseScrollHandler::UserPrefs::~UserPrefs() +{ + DebugOnly<nsresult> rv = + Preferences::UnregisterCallback(OnChange, "mousewheel.", this); + MOZ_ASSERT(NS_SUCCEEDED(rv), + "Failed to unregister callback for mousewheel."); +} + +void +MouseScrollHandler::UserPrefs::Init() +{ + if (mInitialized) { + return; + } + + mInitialized = true; + + mScrollMessageHandledAsWheelMessage = + Preferences::GetBool("mousewheel.emulate_at_wm_scroll", false); + mEnableSystemSettingCache = + Preferences::GetBool("mousewheel.system_settings_cache.enabled", true); + mForceEnableSystemSettingCache = + Preferences::GetBool("mousewheel.system_settings_cache.force_enabled", + false); + mOverriddenVerticalScrollAmount = + Preferences::GetInt("mousewheel.windows.vertical_amount_override", -1); + mOverriddenHorizontalScrollAmount = + Preferences::GetInt("mousewheel.windows.horizontal_amount_override", -1); + mMouseScrollTransactionTimeout = + Preferences::GetInt("mousewheel.windows.transaction.timeout", + DEFAULT_TIMEOUT_DURATION); + + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::UserPrefs::Init(): initialized, " + "mScrollMessageHandledAsWheelMessage=%s, " + "mEnableSystemSettingCache=%s, " + "mForceEnableSystemSettingCache=%s, " + "mOverriddenVerticalScrollAmount=%d, " + "mOverriddenHorizontalScrollAmount=%d, " + "mMouseScrollTransactionTimeout=%d", + GetBoolName(mScrollMessageHandledAsWheelMessage), + GetBoolName(mEnableSystemSettingCache), + GetBoolName(mForceEnableSystemSettingCache), + mOverriddenVerticalScrollAmount, mOverriddenHorizontalScrollAmount, + mMouseScrollTransactionTimeout)); +} + +void +MouseScrollHandler::UserPrefs::MarkDirty() +{ + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScrollHandler::UserPrefs::MarkDirty(): Marking UserPrefs dirty")); + mInitialized = false; + // Some prefs might override system settings, so, we should mark them dirty. + MouseScrollHandler::sInstance->mSystemSettings.MarkDirty(); + // When user prefs for mousewheel are changed, we should reset current + // transaction. + MOZ_ASSERT(sInstance, + "Must not be called at initializing MouseScrollHandler"); + MouseScrollHandler::sInstance->mLastEventInfo.ResetTransaction(); +} + +/****************************************************************************** + * + * Device + * + ******************************************************************************/ + +/* static */ +bool +MouseScrollHandler::Device::GetWorkaroundPref(const char* aPrefName, + bool aValueIfAutomatic) +{ + if (!aPrefName) { + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::Device::GetWorkaroundPref(): Failed, aPrefName is NULL")); + return aValueIfAutomatic; + } + + int32_t lHackValue = 0; + if (NS_FAILED(Preferences::GetInt(aPrefName, &lHackValue))) { + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::Device::GetWorkaroundPref(): Preferences::GetInt() failed," + " aPrefName=\"%s\", aValueIfAutomatic=%s", + aPrefName, GetBoolName(aValueIfAutomatic))); + return aValueIfAutomatic; + } + + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::Device::GetWorkaroundPref(): Succeeded, " + "aPrefName=\"%s\", aValueIfAutomatic=%s, lHackValue=%d", + aPrefName, GetBoolName(aValueIfAutomatic), lHackValue)); + + switch (lHackValue) { + case 0: // disabled + return false; + case 1: // enabled + return true; + default: // -1: autodetect + return aValueIfAutomatic; + } +} + +/* static */ +void +MouseScrollHandler::Device::Init() +{ + // FYI: Thinkpad's TrackPoint is Apoint of Alps and UltraNav is SynTP of + // Synaptics. So, those drivers' information should be initialized + // before calling methods of TrackPoint and UltraNav. + SynTP::Init(); + Elantech::Init(); + Apoint::Init(); + + sFakeScrollableWindowNeeded = + GetWorkaroundPref("ui.trackpoint_hack.enabled", + (TrackPoint::IsDriverInstalled() || + UltraNav::IsObsoleteDriverInstalled())); + + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::Device::Init(): sFakeScrollableWindowNeeded=%s", + GetBoolName(sFakeScrollableWindowNeeded))); +} + +/****************************************************************************** + * + * Device::SynTP + * + ******************************************************************************/ + +/* static */ +void +MouseScrollHandler::Device::SynTP::Init() +{ + if (sInitialized) { + return; + } + + sInitialized = true; + sMajorVersion = 0; + sMinorVersion = -1; + + wchar_t buf[40]; + bool foundKey = + WinUtils::GetRegistryKey(HKEY_LOCAL_MACHINE, + L"Software\\Synaptics\\SynTP\\Install", + L"DriverVersion", + buf, sizeof buf); + if (!foundKey) { + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::Device::SynTP::Init(): " + "SynTP driver is not found")); + return; + } + + sMajorVersion = wcstol(buf, nullptr, 10); + sMinorVersion = 0; + wchar_t* p = wcschr(buf, L'.'); + if (p) { + sMinorVersion = wcstol(p + 1, nullptr, 10); + } + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::Device::SynTP::Init(): " + "found driver version = %d.%d", + sMajorVersion, sMinorVersion)); +} + +/****************************************************************************** + * + * Device::Elantech + * + ******************************************************************************/ + +/* static */ +void +MouseScrollHandler::Device::Elantech::Init() +{ + int32_t version = GetDriverMajorVersion(); + bool needsHack = + Device::GetWorkaroundPref("ui.elantech_gesture_hacks.enabled", + version != 0); + sUseSwipeHack = needsHack && version <= 7; + sUsePinchHack = needsHack && version <= 8; + + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::Device::Elantech::Init(): version=%d, sUseSwipeHack=%s, " + "sUsePinchHack=%s", + version, GetBoolName(sUseSwipeHack), GetBoolName(sUsePinchHack))); +} + +/* static */ +int32_t +MouseScrollHandler::Device::Elantech::GetDriverMajorVersion() +{ + wchar_t buf[40]; + // The driver version is found in one of these two registry keys. + bool foundKey = + WinUtils::GetRegistryKey(HKEY_CURRENT_USER, + L"Software\\Elantech\\MainOption", + L"DriverVersion", + buf, sizeof buf); + if (!foundKey) { + foundKey = + WinUtils::GetRegistryKey(HKEY_CURRENT_USER, + L"Software\\Elantech", + L"DriverVersion", + buf, sizeof buf); + } + + if (!foundKey) { + return 0; + } + + // Assume that the major version number can be found just after a space + // or at the start of the string. + for (wchar_t* p = buf; *p; p++) { + if (*p >= L'0' && *p <= L'9' && (p == buf || *(p - 1) == L' ')) { + return wcstol(p, nullptr, 10); + } + } + + return 0; +} + +/* static */ +bool +MouseScrollHandler::Device::Elantech::IsHelperWindow(HWND aWnd) +{ + // The helper window cannot be distinguished based on its window class, so we + // need to check if it is owned by the helper process, ETDCtrl.exe. + + const wchar_t* filenameSuffix = L"\\etdctrl.exe"; + const int filenameSuffixLength = 12; + + DWORD pid; + ::GetWindowThreadProcessId(aWnd, &pid); + + HANDLE hProcess = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid); + if (!hProcess) { + return false; + } + + bool result = false; + wchar_t path[256] = {L'\0'}; + if (::GetProcessImageFileNameW(hProcess, path, ArrayLength(path))) { + int pathLength = lstrlenW(path); + if (pathLength >= filenameSuffixLength) { + if (lstrcmpiW(path + pathLength - filenameSuffixLength, + filenameSuffix) == 0) { + result = true; + } + } + } + ::CloseHandle(hProcess); + + return result; +} + +/* static */ +bool +MouseScrollHandler::Device::Elantech::HandleKeyMessage(nsWindowBase* aWidget, + UINT aMsg, + WPARAM aWParam, + LPARAM aLParam) +{ + // The Elantech touchpad driver understands three-finger swipe left and + // right gestures, and translates them into Page Up and Page Down key + // events for most applications. For Firefox 3.6, it instead sends + // Alt+Left and Alt+Right to trigger browser back/forward actions. As + // with the Thinkpad Driver hack in nsWindow::Create, the change in + // HWND structure makes Firefox not trigger the driver's heuristics + // any longer. + // + // The Elantech driver actually sends these messages for a three-finger + // swipe right: + // + // WM_KEYDOWN virtual_key = 0xCC or 0xFF ScanCode = 00 + // WM_KEYDOWN virtual_key = VK_NEXT ScanCode = 00 + // WM_KEYUP virtual_key = VK_NEXT ScanCode = 00 + // WM_KEYUP virtual_key = 0xCC or 0xFF ScanCode = 00 + // + // Whether 0xCC or 0xFF is sent is suspected to depend on the driver + // version. 7.0.4.12_14Jul09_WHQL, 7.0.5.10, and 7.0.6.0 generate 0xCC. + // 7.0.4.3 from Asus on EeePC generates 0xFF. + // + // On some hardware, IS_VK_DOWN(0xFF) returns true even when Elantech + // messages are not involved, meaning that alone is not enough to + // distinguish the gesture from a regular Page Up or Page Down key press. + // The ScanCode is therefore also tested to detect the gesture. + // We then pretend that we should dispatch "Go Forward" command. Similarly + // for VK_PRIOR and "Go Back" command. + if (sUseSwipeHack && + (aWParam == VK_NEXT || aWParam == VK_PRIOR) && + WinUtils::GetScanCode(aLParam) == 0 && + (IS_VK_DOWN(0xFF) || IS_VK_DOWN(0xCC))) { + if (aMsg == WM_KEYDOWN) { + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::Device::Elantech::HandleKeyMessage(): Dispatching " + "%s command event", + aWParam == VK_NEXT ? "Forward" : "Back")); + + WidgetCommandEvent commandEvent(true, nsGkAtoms::onAppCommand, + (aWParam == VK_NEXT) ? nsGkAtoms::Forward : nsGkAtoms::Back, aWidget); + InitEvent(aWidget, commandEvent); + aWidget->DispatchWindowEvent(&commandEvent); + } + else { + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::Device::Elantech::HandleKeyMessage(): Consumed")); + } + return true; // consume the message (doesn't need to dispatch key events) + } + + // Version 8 of the Elantech touchpad driver sends these messages for + // zoom gestures: + // + // WM_KEYDOWN virtual_key = 0xCC time = 10 + // WM_KEYDOWN virtual_key = VK_CONTROL time = 10 + // WM_MOUSEWHEEL time = ::GetTickCount() + // WM_KEYUP virtual_key = VK_CONTROL time = 10 + // WM_KEYUP virtual_key = 0xCC time = 10 + // + // The result of this is that we process all of the WM_KEYDOWN/WM_KEYUP + // messages first because their timestamps make them appear to have + // been sent before the WM_MOUSEWHEEL message. To work around this, + // we store the current time when we process the WM_KEYUP message and + // assume that any WM_MOUSEWHEEL message with a timestamp before that + // time is one that should be processed as if the Control key was down. + if (sUsePinchHack && aMsg == WM_KEYUP && + aWParam == VK_CONTROL && ::GetMessageTime() == 10) { + // We look only at the bottom 31 bits of the system tick count since + // GetMessageTime returns a LONG, which is signed, so we want values + // that are more easily comparable. + sZoomUntil = ::GetTickCount() & 0x7FFFFFFF; + + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::Device::Elantech::HandleKeyMessage(): sZoomUntil=%d", + sZoomUntil)); + } + + return false; +} + +/* static */ +void +MouseScrollHandler::Device::Elantech::UpdateZoomUntil() +{ + if (!sZoomUntil) { + return; + } + + // For the Elantech Touchpad Zoom Gesture Hack, we should check that the + // system time (32-bit milliseconds) hasn't wrapped around. Otherwise we + // might get into the situation where wheel events for the next 50 days of + // system uptime are assumed to be Ctrl+Wheel events. (It is unlikely that + // we would get into that state, because the system would already need to be + // up for 50 days and the Control key message would need to be processed just + // before the system time overflow and the wheel message just after.) + // + // We also take the chance to reset sZoomUntil if we simply have passed that + // time. + LONG msgTime = ::GetMessageTime(); + if ((sZoomUntil >= 0x3fffffffu && DWORD(msgTime) < 0x40000000u) || + (sZoomUntil < DWORD(msgTime))) { + sZoomUntil = 0; + + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::Device::Elantech::UpdateZoomUntil(): " + "sZoomUntil was reset")); + } +} + +/* static */ +bool +MouseScrollHandler::Device::Elantech::IsZooming() +{ + // Assume the Control key is down if the Elantech touchpad has sent the + // mis-ordered WM_KEYDOWN/WM_MOUSEWHEEL messages. (See the comment in + // OnKeyUp.) + return (sZoomUntil && static_cast<DWORD>(::GetMessageTime()) < sZoomUntil); +} + +/****************************************************************************** + * + * Device::Apoint + * + ******************************************************************************/ + +/* static */ +void +MouseScrollHandler::Device::Apoint::Init() +{ + if (sInitialized) { + return; + } + + sInitialized = true; + sMajorVersion = 0; + sMinorVersion = -1; + + wchar_t buf[40]; + bool foundKey = + WinUtils::GetRegistryKey(HKEY_LOCAL_MACHINE, + L"Software\\Alps\\Apoint", + L"ProductVer", + buf, sizeof buf); + if (!foundKey) { + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::Device::Apoint::Init(): " + "Apoint driver is not found")); + return; + } + + sMajorVersion = wcstol(buf, nullptr, 10); + sMinorVersion = 0; + wchar_t* p = wcschr(buf, L'.'); + if (p) { + sMinorVersion = wcstol(p + 1, nullptr, 10); + } + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::Device::Apoint::Init(): " + "found driver version = %d.%d", + sMajorVersion, sMinorVersion)); +} + +/****************************************************************************** + * + * Device::TrackPoint + * + ******************************************************************************/ + +/* static */ +bool +MouseScrollHandler::Device::TrackPoint::IsDriverInstalled() +{ + if (WinUtils::HasRegistryKey(HKEY_CURRENT_USER, + L"Software\\Lenovo\\TrackPoint")) { + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::Device::TrackPoint::IsDriverInstalled(): " + "Lenovo's TrackPoint driver is found")); + return true; + } + + if (WinUtils::HasRegistryKey(HKEY_CURRENT_USER, + L"Software\\Alps\\Apoint\\TrackPoint")) { + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::Device::TrackPoint::IsDriverInstalled(): " + "Alps's TrackPoint driver is found")); + } + + return false; +} + +/****************************************************************************** + * + * Device::UltraNav + * + ******************************************************************************/ + +/* static */ +bool +MouseScrollHandler::Device::UltraNav::IsObsoleteDriverInstalled() +{ + if (WinUtils::HasRegistryKey(HKEY_CURRENT_USER, + L"Software\\Lenovo\\UltraNav")) { + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::Device::UltraNav::IsObsoleteDriverInstalled(): " + "Lenovo's UltraNav driver is found")); + return true; + } + + bool installed = false; + if (WinUtils::HasRegistryKey(HKEY_CURRENT_USER, + L"Software\\Synaptics\\SynTPEnh\\UltraNavUSB")) { + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::Device::UltraNav::IsObsoleteDriverInstalled(): " + "Synaptics's UltraNav (USB) driver is found")); + installed = true; + } else if (WinUtils::HasRegistryKey(HKEY_CURRENT_USER, + L"Software\\Synaptics\\SynTPEnh\\UltraNavPS2")) { + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::Device::UltraNav::IsObsoleteDriverInstalled(): " + "Synaptics's UltraNav (PS/2) driver is found")); + installed = true; + } + + if (!installed) { + return false; + } + + int32_t majorVersion = Device::SynTP::GetDriverMajorVersion(); + if (!majorVersion) { + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::Device::UltraNav::IsObsoleteDriverInstalled(): " + "Failed to get UltraNav driver version")); + return false; + } + int32_t minorVersion = Device::SynTP::GetDriverMinorVersion(); + return majorVersion < 15 || (majorVersion == 15 && minorVersion == 0); +} + +/****************************************************************************** + * + * Device::SetPoint + * + ******************************************************************************/ + +/* static */ +bool +MouseScrollHandler::Device::SetPoint::IsGetMessagePosResponseValid( + UINT aMessage, + WPARAM aWParam, + LPARAM aLParam) +{ + if (aMessage != WM_MOUSEHWHEEL) { + return false; + } + + POINTS pts = MouseScrollHandler::GetCurrentMessagePos(); + LPARAM messagePos = MAKELPARAM(pts.x, pts.y); + + // XXX We should check whether SetPoint is installed or not by registry. + + // SetPoint, Logitech (Logicool) mouse driver, (confirmed with 4.82.11 and + // MX-1100) always sets 0 to the lParam of WM_MOUSEHWHEEL. The driver SENDs + // one message at first time, this time, ::GetMessagePos() works fine. + // Then, we will return 0 (0 means we process it) to the message. Then, the + // driver will POST the same messages continuously during the wheel tilted. + // But ::GetMessagePos() API always returns (0, 0) for them, even if the + // actual mouse cursor isn't 0,0. Therefore, we cannot trust the result of + // ::GetMessagePos API if the sender is SetPoint. + if (!sMightBeUsing && !aLParam && aLParam != messagePos && + ::InSendMessage()) { + sMightBeUsing = true; + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::Device::SetPoint::IsGetMessagePosResponseValid(): " + "Might using SetPoint")); + } else if (sMightBeUsing && aLParam != 0 && ::InSendMessage()) { + // The user has changed the mouse from Logitech's to another one (e.g., + // the user has changed to the touchpad of the notebook. + sMightBeUsing = false; + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::Device::SetPoint::IsGetMessagePosResponseValid(): " + "Might stop using SetPoint")); + } + return (sMightBeUsing && !aLParam && !messagePos); +} + +/****************************************************************************** + * + * SynthesizingEvent + * + ******************************************************************************/ + +/* static */ +bool +MouseScrollHandler::SynthesizingEvent::IsSynthesizing() +{ + return MouseScrollHandler::sInstance && + MouseScrollHandler::sInstance->mSynthesizingEvent && + MouseScrollHandler::sInstance->mSynthesizingEvent->mStatus != + NOT_SYNTHESIZING; +} + +nsresult +MouseScrollHandler::SynthesizingEvent::Synthesize(const POINTS& aCursorPoint, + HWND aWnd, + UINT aMessage, + WPARAM aWParam, + LPARAM aLParam, + const BYTE (&aKeyStates)[256]) +{ + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScrollHandler::SynthesizingEvent::Synthesize(): aCursorPoint: { " + "x: %d, y: %d }, aWnd=0x%X, aMessage=0x%04X, aWParam=0x%08X, " + "aLParam=0x%08X, IsSynthesized()=%s, mStatus=%s", + aCursorPoint.x, aCursorPoint.y, aWnd, aMessage, aWParam, aLParam, + GetBoolName(IsSynthesizing()), GetStatusName())); + + if (IsSynthesizing()) { + return NS_ERROR_NOT_AVAILABLE; + } + + ::GetKeyboardState(mOriginalKeyState); + + // Note that we cannot use ::SetCursorPos() because it works asynchronously. + // We should SEND the message for reducing the possibility of receiving + // unexpected message which were not sent from here. + mCursorPoint = aCursorPoint; + + mWnd = aWnd; + mMessage = aMessage; + mWParam = aWParam; + mLParam = aLParam; + + memcpy(mKeyState, aKeyStates, sizeof(mKeyState)); + ::SetKeyboardState(mKeyState); + + mStatus = SENDING_MESSAGE; + + // Don't assume that aWnd is always managed by nsWindow. It might be + // a plugin window. + ::SendMessage(aWnd, aMessage, aWParam, aLParam); + + return NS_OK; +} + +void +MouseScrollHandler::SynthesizingEvent::NativeMessageReceived(nsWindowBase* aWidget, + UINT aMessage, + WPARAM aWParam, + LPARAM aLParam) +{ + if (mStatus == SENDING_MESSAGE && mMessage == aMessage && + mWParam == aWParam && mLParam == aLParam) { + mStatus = NATIVE_MESSAGE_RECEIVED; + if (aWidget && aWidget->GetWindowHandle() == mWnd) { + return; + } + // If the target window is not ours and received window is our plugin + // window, it comes from child window of the plugin. + if (aWidget && aWidget->IsPlugin() && + !WinUtils::GetNSWindowBasePtr(mWnd)) { + return; + } + // Otherwise, the message may not be sent by us. + } + + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScrollHandler::SynthesizingEvent::NativeMessageReceived(): " + "aWidget=%p, aWidget->GetWindowHandle()=0x%X, mWnd=0x%X, " + "aMessage=0x%04X, aWParam=0x%08X, aLParam=0x%08X, mStatus=%s", + aWidget, aWidget ? aWidget->GetWindowHandle() : 0, mWnd, + aMessage, aWParam, aLParam, GetStatusName())); + + // We failed to receive our sent message, we failed to do the job. + Finish(); + + return; +} + +void +MouseScrollHandler::SynthesizingEvent::NotifyNativeMessageHandlingFinished() +{ + if (!IsSynthesizing()) { + return; + } + + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScrollHandler::SynthesizingEvent::" + "NotifyNativeMessageHandlingFinished(): IsWaitingInternalMessage=%s", + GetBoolName(MouseScrollHandler::IsWaitingInternalMessage()))); + + if (MouseScrollHandler::IsWaitingInternalMessage()) { + mStatus = INTERNAL_MESSAGE_POSTED; + return; + } + + // If the native message handler didn't post our internal message, + // we our job is finished. + // TODO: When we post the message to plugin window, there is remaning job. + Finish(); +} + +void +MouseScrollHandler::SynthesizingEvent::NotifyInternalMessageHandlingFinished() +{ + if (!IsSynthesizing()) { + return; + } + + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScrollHandler::SynthesizingEvent::" + "NotifyInternalMessageHandlingFinished()")); + + Finish(); +} + +void +MouseScrollHandler::SynthesizingEvent::Finish() +{ + if (!IsSynthesizing()) { + return; + } + + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScrollHandler::SynthesizingEvent::Finish()")); + + // Restore the original key state. + ::SetKeyboardState(mOriginalKeyState); + + mStatus = NOT_SYNTHESIZING; +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/windows/WinMouseScrollHandler.h b/widget/windows/WinMouseScrollHandler.h new file mode 100644 index 0000000000..5d8c58ba0e --- /dev/null +++ b/widget/windows/WinMouseScrollHandler.h @@ -0,0 +1,625 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_widget_WinMouseScrollHandler_h__ +#define mozilla_widget_WinMouseScrollHandler_h__ + +#include "nscore.h" +#include "nsDebug.h" +#include "mozilla/Assertions.h" +#include "mozilla/EventForwards.h" +#include "mozilla/TimeStamp.h" +#include "Units.h" +#include <windows.h> +#include "nsPoint.h" + +class nsWindowBase; + +namespace mozilla { +namespace widget { + +class ModifierKeyState; + +struct MSGResult; + +class MouseScrollHandler { +public: + static MouseScrollHandler* GetInstance(); + + static void Initialize(); + static void Shutdown(); + + static bool NeedsMessage(UINT aMsg); + static bool ProcessMessage(nsWindowBase* aWidget, + UINT msg, + WPARAM wParam, + LPARAM lParam, + MSGResult& aResult); + + /** + * See nsIWidget::SynthesizeNativeMouseScrollEvent() for the detail about + * this method. + */ + static nsresult SynthesizeNativeMouseScrollEvent(nsWindowBase* aWidget, + const LayoutDeviceIntPoint& aPoint, + uint32_t aNativeMessage, + int32_t aDelta, + uint32_t aModifierFlags, + uint32_t aAdditionalFlags); + + /** + * IsWaitingInternalMessage() returns true if MouseScrollHandler posted + * an internal message for a native mouse wheel message and has not + * received it. Otherwise, false. + */ + static bool IsWaitingInternalMessage() + { + return sInstance && sInstance->mIsWaitingInternalMessage; + } + +private: + MouseScrollHandler(); + ~MouseScrollHandler(); + + bool mIsWaitingInternalMessage; + + static void MaybeLogKeyState(); + + static MouseScrollHandler* sInstance; + + /** + * InitEvent() initializes the aEvent. If aPoint is null, the result of + * GetCurrentMessagePos() will be used. + */ + static void InitEvent(nsWindowBase* aWidget, + WidgetGUIEvent& aEvent, + LayoutDeviceIntPoint* aPoint = nullptr); + + /** + * GetModifierKeyState() returns current modifier key state. + * Note that some devices need some hack for the modifier key state. + * This method does it automatically. + * + * @param aMessage Handling message. + */ + static ModifierKeyState GetModifierKeyState(UINT aMessage); + + /** + * MozGetMessagePos() returns the mouse cursor position when GetMessage() + * was called last time. However, if we're sending a native message, + * this returns the specified cursor position by + * SynthesizeNativeMouseScrollEvent(). + */ + static POINTS GetCurrentMessagePos(); + + /** + * ProcessNativeMouseWheelMessage() processes WM_MOUSEWHEEL and + * WM_MOUSEHWHEEL. Additionally, processes WM_VSCROLL and WM_HSCROLL if they + * should be processed as mouse wheel message. + * This method posts MOZ_WM_MOUSEVWHEEL, MOZ_WM_MOUSEHWHEEL, + * MOZ_WM_VSCROLL or MOZ_WM_HSCROLL if we need to dispatch mouse scroll + * events. That avoids deadlock with plugin process. + * + * @param aWidget A window which receives the message. + * @param aMessage WM_MOUSEWHEEL, WM_MOUSEHWHEEL, WM_VSCROLL or + * WM_HSCROLL. + * @param aWParam The wParam value of the message. + * @param aLParam The lParam value of the message. + */ + void ProcessNativeMouseWheelMessage(nsWindowBase* aWidget, + UINT aMessage, + WPARAM aWParam, + LPARAM aLParam); + + /** + * ProcessNativeScrollMessage() processes WM_VSCROLL and WM_HSCROLL. + * This method just call ProcessMouseWheelMessage() if the message should be + * processed as mouse wheel message. Otherwise, dispatches a content + * command event. + * + * @param aWidget A window which receives the message. + * @param aMessage WM_VSCROLL or WM_HSCROLL. + * @param aWParam The wParam value of the message. + * @param aLParam The lParam value of the message. + * @return TRUE if the message is processed. Otherwise, FALSE. + */ + bool ProcessNativeScrollMessage(nsWindowBase* aWidget, + UINT aMessage, + WPARAM aWParam, + LPARAM aLParam); + + /** + * HandleMouseWheelMessage() processes MOZ_WM_MOUSEVWHEEL and + * MOZ_WM_MOUSEHWHEEL which are posted when one of our windows received + * WM_MOUSEWHEEL or WM_MOUSEHWHEEL for avoiding deadlock with OOPP. + * + * @param aWidget A window which receives the wheel message. + * @param aMessage MOZ_WM_MOUSEWHEEL or MOZ_WM_MOUSEHWHEEL. + * @param aWParam The wParam value of the original message. + * @param aLParam The lParam value of the original message. + */ + void HandleMouseWheelMessage(nsWindowBase* aWidget, + UINT aMessage, + WPARAM aWParam, + LPARAM aLParam); + + /** + * HandleScrollMessageAsMouseWheelMessage() processes the MOZ_WM_VSCROLL and + * MOZ_WM_HSCROLL which are posted when one of mouse windows received + * WM_VSCROLL or WM_HSCROLL and user wants them to emulate mouse wheel + * message's behavior. + * + * @param aWidget A window which receives the scroll message. + * @param aMessage MOZ_WM_VSCROLL or MOZ_WM_HSCROLL. + * @param aWParam The wParam value of the original message. + * @param aLParam The lParam value of the original message. + */ + void HandleScrollMessageAsMouseWheelMessage(nsWindowBase* aWidget, + UINT aMessage, + WPARAM aWParam, + LPARAM aLParam); + + /** + * ComputeMessagePos() computes the cursor position when the message was + * added to the queue. + * + * @param aMessage Handling message. + * @param aWParam Handling message's wParam. + * @param aLParam Handling message's lParam. + * @return Mouse cursor position when the message is added to + * the queue or current cursor position if the result of + * ::GetMessagePos() is broken. + */ + POINT ComputeMessagePos(UINT aMessage, + WPARAM aWParam, + LPARAM aLParam); + + class EventInfo { + public: + /** + * @param aWidget An nsWindow which is handling the event. + * @param aMessage Must be WM_MOUSEWHEEL or WM_MOUSEHWHEEL. + */ + EventInfo(nsWindowBase* aWidget, UINT aMessage, WPARAM aWParam, LPARAM aLParam); + + bool CanDispatchWheelEvent() const; + + int32_t GetNativeDelta() const { return mDelta; } + HWND GetWindowHandle() const { return mWnd; } + const TimeStamp& GetTimeStamp() const { return mTimeStamp; } + bool IsVertical() const { return mIsVertical; } + bool IsPositive() const { return (mDelta > 0); } + bool IsPage() const { return mIsPage; } + + /** + * @return Number of lines or pages scrolled per WHEEL_DELTA. + */ + int32_t GetScrollAmount() const; + + protected: + EventInfo() : + mIsVertical(false), mIsPage(false), mDelta(0), mWnd(nullptr) + { + } + + // TRUE if event is for vertical scroll. Otherwise, FALSE. + bool mIsVertical; + // TRUE if event scrolls per page, otherwise, FALSE. + bool mIsPage; + // The native delta value. + int32_t mDelta; + // The window handle which is handling the event. + HWND mWnd; + // Timestamp of the event. + TimeStamp mTimeStamp; + }; + + class LastEventInfo : public EventInfo { + public: + LastEventInfo() : + EventInfo(), mAccumulatedDelta(0) + { + } + + /** + * CanContinueTransaction() checks whether the new event can continue the + * last transaction or not. Note that if there is no transaction, this + * returns true. + */ + bool CanContinueTransaction(const EventInfo& aNewEvent); + + /** + * ResetTransaction() resets the transaction, i.e., the instance forgets + * the last event information. + */ + void ResetTransaction(); + + /** + * RecordEvent() saves the information of new event. + */ + void RecordEvent(const EventInfo& aEvent); + + /** + * InitWheelEvent() initializes NS_WHEEL_WHEEL event and + * recomputes the remaning detla for the event. + * This must be called only once during handling a message and after + * RecordEvent() is called. + * + * @param aWidget A window which will dispatch the event. + * @param aWheelEvent An NS_WHEEL_WHEEL event, this will be + * initialized. + * @param aModKeyState Current modifier key state. + * @return TRUE if the event is ready to dispatch. + * Otherwise, FALSE. + */ + bool InitWheelEvent(nsWindowBase* aWidget, + WidgetWheelEvent& aWheelEvent, + const ModifierKeyState& aModKeyState); + + private: + static int32_t RoundDelta(double aDelta); + + int32_t mAccumulatedDelta; + }; + + LastEventInfo mLastEventInfo; + + class SystemSettings { + public: + SystemSettings() : mInitialized(false) {} + + void Init(); + void MarkDirty(); + void NotifyUserPrefsMayOverrideSystemSettings(); + + // On some environments, SystemParametersInfo() may be hooked by touchpad + // utility or something. In such case, when user changes active pointing + // device to another one, the result of SystemParametersInfo() may be + // changed without WM_SETTINGCHANGE message. For avoiding this trouble, + // we need to modify cache of system settings at every wheel message + // handling if we meet known device whose utility may hook the API. + void TrustedScrollSettingsDriver(); + + // Returns true if the system scroll may be overridden for faster scroll. + // Otherwise, false. For example, if the user maybe uses an expensive + // mouse which supports acceleration of scroll speed, faster scroll makes + // the user inconvenient. + bool IsOverridingSystemScrollSpeedAllowed(); + + int32_t GetScrollAmount(bool aForVertical) const + { + MOZ_ASSERT(mInitialized, "SystemSettings must be initialized"); + return aForVertical ? mScrollLines : mScrollChars; + } + + bool IsPageScroll(bool aForVertical) const + { + MOZ_ASSERT(mInitialized, "SystemSettings must be initialized"); + return aForVertical ? (uint32_t(mScrollLines) == WHEEL_PAGESCROLL) : + (uint32_t(mScrollChars) == WHEEL_PAGESCROLL); + } + + // The default vertical and horizontal scrolling speed is 3, this is defined + // on the document of SystemParametersInfo in MSDN. + static int32_t DefaultScrollLines() { return 3; } + static int32_t DefaultScrollChars() { return 3; } + + private: + bool mInitialized; + // The result of SystemParametersInfo() may not be reliable since it may + // be hooked. So, if the values are initialized with prefs, we can trust + // the value. Following mIsReliableScroll* are set true when mScroll* are + // initialized with prefs. + bool mIsReliableScrollLines; + bool mIsReliableScrollChars; + + int32_t mScrollLines; + int32_t mScrollChars; + + // Returns true if cached value is changed. + bool InitScrollLines(); + bool InitScrollChars(); + + void RefreshCache(); + }; + + SystemSettings mSystemSettings; + + class UserPrefs { + public: + UserPrefs(); + ~UserPrefs(); + + void MarkDirty(); + + bool IsScrollMessageHandledAsWheelMessage() + { + Init(); + return mScrollMessageHandledAsWheelMessage; + } + + bool IsSystemSettingCacheEnabled() + { + Init(); + return mEnableSystemSettingCache; + } + + bool IsSystemSettingCacheForciblyEnabled() + { + Init(); + return mForceEnableSystemSettingCache; + } + + int32_t GetOverriddenVerticalScrollAmout() + { + Init(); + return mOverriddenVerticalScrollAmount; + } + + int32_t GetOverriddenHorizontalScrollAmout() + { + Init(); + return mOverriddenHorizontalScrollAmount; + } + + int32_t GetMouseScrollTransactionTimeout() + { + Init(); + return mMouseScrollTransactionTimeout; + } + + private: + void Init(); + + static void OnChange(const char* aPrefName, void* aClosure) + { + static_cast<UserPrefs*>(aClosure)->MarkDirty(); + } + + bool mInitialized; + bool mScrollMessageHandledAsWheelMessage; + bool mEnableSystemSettingCache; + bool mForceEnableSystemSettingCache; + int32_t mOverriddenVerticalScrollAmount; + int32_t mOverriddenHorizontalScrollAmount; + int32_t mMouseScrollTransactionTimeout; + }; + + UserPrefs mUserPrefs; + + class SynthesizingEvent { + public: + SynthesizingEvent() : + mWnd(nullptr), mMessage(0), mWParam(0), mLParam(0), + mStatus(NOT_SYNTHESIZING) + { + } + + ~SynthesizingEvent() {} + + static bool IsSynthesizing(); + + nsresult Synthesize(const POINTS& aCursorPoint, HWND aWnd, + UINT aMessage, WPARAM aWParam, LPARAM aLParam, + const BYTE (&aKeyStates)[256]); + + void NativeMessageReceived(nsWindowBase* aWidget, UINT aMessage, + WPARAM aWParam, LPARAM aLParam); + + void NotifyNativeMessageHandlingFinished(); + void NotifyInternalMessageHandlingFinished(); + + const POINTS& GetCursorPoint() const { return mCursorPoint; } + + private: + POINTS mCursorPoint; + HWND mWnd; + UINT mMessage; + WPARAM mWParam; + LPARAM mLParam; + BYTE mKeyState[256]; + BYTE mOriginalKeyState[256]; + + enum Status { + NOT_SYNTHESIZING, + SENDING_MESSAGE, + NATIVE_MESSAGE_RECEIVED, + INTERNAL_MESSAGE_POSTED, + }; + Status mStatus; + + const char* GetStatusName() + { + switch (mStatus) { + case NOT_SYNTHESIZING: + return "NOT_SYNTHESIZING"; + case SENDING_MESSAGE: + return "SENDING_MESSAGE"; + case NATIVE_MESSAGE_RECEIVED: + return "NATIVE_MESSAGE_RECEIVED"; + case INTERNAL_MESSAGE_POSTED: + return "INTERNAL_MESSAGE_POSTED"; + default: + return "Unknown"; + } + } + + void Finish(); + }; // SynthesizingEvent + + SynthesizingEvent* mSynthesizingEvent; + +public: + + class Device { + public: + // SynTP is a touchpad driver of Synaptics. + class SynTP + { + public: + static bool IsDriverInstalled() + { + return sMajorVersion != 0; + } + /** + * GetDriverMajorVersion() returns the installed driver's major version. + * If SynTP driver isn't installed, this returns 0. + */ + static int32_t GetDriverMajorVersion() + { + return sMajorVersion; + } + /** + * GetDriverMinorVersion() returns the installed driver's minor version. + * If SynTP driver isn't installed, this returns -1. + */ + static int32_t GetDriverMinorVersion() + { + return sMinorVersion; + } + + static void Init(); + + private: + static bool sInitialized; + static int32_t sMajorVersion; + static int32_t sMinorVersion; + }; + + class Elantech { + public: + /** + * GetDriverMajorVersion() returns the installed driver's major version. + * If Elantech's driver was installed, returns 0. + */ + static int32_t GetDriverMajorVersion(); + + /** + * IsHelperWindow() checks whether aWnd is a helper window of Elantech's + * touchpad. Returns TRUE if so. Otherwise, FALSE. + */ + static bool IsHelperWindow(HWND aWnd); + + /** + * Key message handler for Elantech's hack. Returns TRUE if the message + * is consumed by this handler. Otherwise, FALSE. + */ + static bool HandleKeyMessage(nsWindowBase* aWidget, + UINT aMsg, + WPARAM aWParam, + LPARAM aLParam); + + static void UpdateZoomUntil(); + static bool IsZooming(); + + static void Init(); + + static bool IsPinchHackNeeded() { return sUsePinchHack; } + + + private: + // Whether to enable the Elantech swipe gesture hack. + static bool sUseSwipeHack; + // Whether to enable the Elantech pinch-to-zoom gesture hack. + static bool sUsePinchHack; + static DWORD sZoomUntil; + }; // class Elantech + + // Apoint is a touchpad driver of Alps. + class Apoint + { + public: + static bool IsDriverInstalled() + { + return sMajorVersion != 0; + } + /** + * GetDriverMajorVersion() returns the installed driver's major version. + * If Apoint driver isn't installed, this returns 0. + */ + static int32_t GetDriverMajorVersion() + { + return sMajorVersion; + } + /** + * GetDriverMinorVersion() returns the installed driver's minor version. + * If Apoint driver isn't installed, this returns -1. + */ + static int32_t GetDriverMinorVersion() + { + return sMinorVersion; + } + + static void Init(); + + private: + static bool sInitialized; + static int32_t sMajorVersion; + static int32_t sMinorVersion; + }; + + class TrackPoint { + public: + /** + * IsDriverInstalled() returns TRUE if TrackPoint's driver is installed. + * Otherwise, returns FALSE. + */ + static bool IsDriverInstalled(); + }; // class TrackPoint + + class UltraNav { + public: + /** + * IsObsoleteDriverInstalled() checks whether obsoleted UltraNav + * is installed on the environment. + * Returns TRUE if it was installed. Otherwise, FALSE. + */ + static bool IsObsoleteDriverInstalled(); + }; // class UltraNav + + class SetPoint { + public: + /** + * SetPoint, Logitech's mouse driver, may report wrong cursor position + * for WM_MOUSEHWHEEL message. See comment in the implementation for + * the detail. + */ + static bool IsGetMessagePosResponseValid(UINT aMessage, + WPARAM aWParam, + LPARAM aLParam); + private: + static bool sMightBeUsing; + }; + + static void Init(); + + static bool IsFakeScrollableWindowNeeded() + { + return sFakeScrollableWindowNeeded; + } + + private: + /** + * Gets the bool value of aPrefName used to enable or disable an input + * workaround (like the Trackpoint hack). The pref can take values 0 (for + * disabled), 1 (for enabled) or -1 (to automatically detect whether to + * enable the workaround). + * + * @param aPrefName The name of the pref. + * @param aValueIfAutomatic Whether the given input workaround should be + * enabled by default. + */ + static bool GetWorkaroundPref(const char* aPrefName, + bool aValueIfAutomatic); + + static bool sFakeScrollableWindowNeeded; + }; // class Device +}; + +} // namespace widget +} // namespace mozilla + +#endif // mozilla_widget_WinMouseScrollHandler_h__ diff --git a/widget/windows/WinNativeEventData.h b/widget/windows/WinNativeEventData.h new file mode 100644 index 0000000000..5ed5ca44a4 --- /dev/null +++ b/widget/windows/WinNativeEventData.h @@ -0,0 +1,56 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_widget_WinNativeEventData_h_ +#define mozilla_widget_WinNativeEventData_h_ + +#include <windows.h> + +#include "mozilla/EventForwards.h" +#include "mozilla/widget/WinModifierKeyState.h" + +namespace mozilla { +namespace widget { + +/** + * WinNativeKeyEventData is used by nsIWidget::OnWindowedPluginKeyEvent() and + * related IPC methods. This class cannot hold any pointers and references + * since which are not available in different process. + */ + +class WinNativeKeyEventData final +{ +public: + UINT mMessage; + WPARAM mWParam; + LPARAM mLParam; + Modifiers mModifiers; + +private: + uintptr_t mKeyboardLayout; + +public: + WinNativeKeyEventData(UINT aMessage, + WPARAM aWParam, + LPARAM aLParam, + const ModifierKeyState& aModifierKeyState) + : mMessage(aMessage) + , mWParam(aWParam) + , mLParam(aLParam) + , mModifiers(aModifierKeyState.GetModifiers()) + , mKeyboardLayout(reinterpret_cast<uintptr_t>(::GetKeyboardLayout(0))) + { + } + + HKL GetKeyboardLayout() const + { + return reinterpret_cast<HKL>(mKeyboardLayout); + } +}; + +} // namespace widget +} // namespace mozilla + +#endif // #ifndef mozilla_widget_WinNativeEventData_h_ diff --git a/widget/windows/WinTaskbar.cpp b/widget/windows/WinTaskbar.cpp new file mode 100644 index 0000000000..698b7ec0e5 --- /dev/null +++ b/widget/windows/WinTaskbar.cpp @@ -0,0 +1,506 @@ +/* vim: se cin sw=2 ts=2 et : */ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "WinTaskbar.h" +#include "TaskbarPreview.h" +#include <nsITaskbarPreviewController.h> + +#include "mozilla/RefPtr.h" +#include <nsError.h> +#include <nsCOMPtr.h> +#include <nsIWidget.h> +#include <nsIBaseWindow.h> +#include <nsIObserverService.h> +#include <nsServiceManagerUtils.h> +#include "nsIXULAppInfo.h" +#include "nsIJumpListBuilder.h" +#include "nsUXThemeData.h" +#include "nsWindow.h" +#include "WinUtils.h" +#include "TaskbarTabPreview.h" +#include "TaskbarWindowPreview.h" +#include "JumpListBuilder.h" +#include "nsWidgetsCID.h" +#include "nsPIDOMWindow.h" +#include "nsAppDirectoryServiceDefs.h" +#include "mozilla/Preferences.h" +#include "mozilla/WindowsVersion.h" +#include <io.h> +#include <propvarutil.h> +#include <propkey.h> +#include <shellapi.h> + +const wchar_t kShellLibraryName[] = L"shell32.dll"; + +static NS_DEFINE_CID(kJumpListBuilderCID, NS_WIN_JUMPLISTBUILDER_CID); + +namespace { + +HWND +GetHWNDFromDocShell(nsIDocShell *aShell) { + nsCOMPtr<nsIBaseWindow> baseWindow(do_QueryInterface(reinterpret_cast<nsISupports*>(aShell))); + + if (!baseWindow) + return nullptr; + + nsCOMPtr<nsIWidget> widget; + baseWindow->GetMainWidget(getter_AddRefs(widget)); + + return widget ? (HWND)widget->GetNativeData(NS_NATIVE_WINDOW) : nullptr; +} + +HWND +GetHWNDFromDOMWindow(mozIDOMWindow *dw) { + nsCOMPtr<nsIWidget> widget; + + if (!dw) + return nullptr; + + nsCOMPtr<nsPIDOMWindowInner> window = nsPIDOMWindowInner::From(dw); + return GetHWNDFromDocShell(window->GetDocShell()); +} + +nsresult +SetWindowAppUserModelProp(mozIDOMWindow *aParent, + const nsString & aIdentifier) { + NS_ENSURE_ARG_POINTER(aParent); + + if (aIdentifier.IsEmpty()) + return NS_ERROR_INVALID_ARG; + + HWND toplevelHWND = ::GetAncestor(GetHWNDFromDOMWindow(aParent), GA_ROOT); + + if (!toplevelHWND) + return NS_ERROR_INVALID_ARG; + + typedef HRESULT (WINAPI * SHGetPropertyStoreForWindowPtr) + (HWND hwnd, REFIID riid, void** ppv); + SHGetPropertyStoreForWindowPtr funcGetProStore = nullptr; + + HMODULE hDLL = ::LoadLibraryW(kShellLibraryName); + funcGetProStore = (SHGetPropertyStoreForWindowPtr) + GetProcAddress(hDLL, "SHGetPropertyStoreForWindow"); + + if (!funcGetProStore) { + FreeLibrary(hDLL); + return NS_ERROR_NO_INTERFACE; + } + + IPropertyStore* pPropStore; + if (FAILED(funcGetProStore(toplevelHWND, + IID_PPV_ARGS(&pPropStore)))) { + FreeLibrary(hDLL); + return NS_ERROR_INVALID_ARG; + } + + PROPVARIANT pv; + if (FAILED(InitPropVariantFromString(aIdentifier.get(), &pv))) { + pPropStore->Release(); + FreeLibrary(hDLL); + return NS_ERROR_UNEXPECTED; + } + + nsresult rv = NS_OK; + if (FAILED(pPropStore->SetValue(PKEY_AppUserModel_ID, pv)) || + FAILED(pPropStore->Commit())) { + rv = NS_ERROR_FAILURE; + } + + PropVariantClear(&pv); + pPropStore->Release(); + FreeLibrary(hDLL); + + return rv; +} + +/////////////////////////////////////////////////////////////////////////////// +// default nsITaskbarPreviewController + +class DefaultController final : public nsITaskbarPreviewController +{ + ~DefaultController() {} + HWND mWnd; +public: + DefaultController(HWND hWnd) + : mWnd(hWnd) + { + } + + NS_DECL_ISUPPORTS + NS_DECL_NSITASKBARPREVIEWCONTROLLER +}; + +NS_IMETHODIMP +DefaultController::GetWidth(uint32_t *aWidth) +{ + RECT r; + ::GetClientRect(mWnd, &r); + *aWidth = r.right; + return NS_OK; +} + +NS_IMETHODIMP +DefaultController::GetHeight(uint32_t *aHeight) +{ + RECT r; + ::GetClientRect(mWnd, &r); + *aHeight = r.bottom; + return NS_OK; +} + +NS_IMETHODIMP +DefaultController::GetThumbnailAspectRatio(float *aThumbnailAspectRatio) { + uint32_t width, height; + GetWidth(&width); + GetHeight(&height); + if (!height) + height = 1; + + *aThumbnailAspectRatio = width/float(height); + return NS_OK; +} + +// deprecated +NS_IMETHODIMP +DefaultController::DrawPreview(nsISupports *ctx, bool *rDrawFrame) { + *rDrawFrame = true; + return NS_ERROR_UNEXPECTED; +} + +// deprecated +NS_IMETHODIMP +DefaultController::DrawThumbnail(nsISupports *ctx, uint32_t width, uint32_t height, bool *rDrawFrame) { + *rDrawFrame = false; + return NS_ERROR_UNEXPECTED; +} + +NS_IMETHODIMP +DefaultController::RequestThumbnail(nsITaskbarPreviewCallback *aCallback, uint32_t width, uint32_t height) { + return NS_OK; +} + +NS_IMETHODIMP +DefaultController::RequestPreview(nsITaskbarPreviewCallback *aCallback) { + return NS_OK; +} + +NS_IMETHODIMP +DefaultController::OnClose(void) { + NS_NOTREACHED("OnClose should not be called for TaskbarWindowPreviews"); + return NS_OK; +} + +NS_IMETHODIMP +DefaultController::OnActivate(bool *rAcceptActivation) { + *rAcceptActivation = true; + NS_NOTREACHED("OnActivate should not be called for TaskbarWindowPreviews"); + return NS_OK; +} + +NS_IMETHODIMP +DefaultController::OnClick(nsITaskbarPreviewButton *button) { + return NS_OK; +} + +NS_IMPL_ISUPPORTS(DefaultController, nsITaskbarPreviewController) +} + +namespace mozilla { +namespace widget { + +/////////////////////////////////////////////////////////////////////////////// +// nsIWinTaskbar + +NS_IMPL_ISUPPORTS(WinTaskbar, nsIWinTaskbar) + +bool +WinTaskbar::Initialize() { + if (mTaskbar) + return true; + + ::CoInitialize(nullptr); + HRESULT hr = ::CoCreateInstance(CLSID_TaskbarList, + nullptr, + CLSCTX_INPROC_SERVER, + IID_ITaskbarList4, + (void**)&mTaskbar); + if (FAILED(hr)) + return false; + + hr = mTaskbar->HrInit(); + if (FAILED(hr)) { + // This may fail with shell extensions like blackbox installed. + NS_WARNING("Unable to initialize taskbar"); + NS_RELEASE(mTaskbar); + return false; + } + return true; +} + +WinTaskbar::WinTaskbar() + : mTaskbar(nullptr) { +} + +WinTaskbar::~WinTaskbar() { + if (mTaskbar) { // match successful Initialize() call + NS_RELEASE(mTaskbar); + ::CoUninitialize(); + } +} + +// static +bool +WinTaskbar::GetAppUserModelID(nsAString & aDefaultGroupId) { + // If marked as such in prefs, use a hash of the profile path for the id + // instead of the install path hash setup by the installer. + bool useProfile = + Preferences::GetBool("taskbar.grouping.useprofile", false); + if (useProfile) { + nsCOMPtr<nsIFile> profileDir; + NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, + getter_AddRefs(profileDir)); + bool exists = false; + if (profileDir && NS_SUCCEEDED(profileDir->Exists(&exists)) && exists) { + nsAutoCString path; + if (NS_SUCCEEDED(profileDir->GetNativePath(path))) { + nsAutoString id; + id.AppendInt(HashString(path)); + if (!id.IsEmpty()) { + aDefaultGroupId.Assign(id); + return true; + } + } + } + } + + // The default value is set by the installer and is stored in the registry + // under (HKLM||HKCU)/Software/Mozilla/Firefox/TaskBarIDs. If for any reason + // hash generation operation fails, the installer will not store a value in + // the registry or set ids on shortcuts. A lack of an id can also occur for + // zipped builds. We skip setting the global id in this case as well. + nsCOMPtr<nsIXULAppInfo> appInfo = + do_GetService("@mozilla.org/xre/app-info;1"); + if (!appInfo) + return false; + + nsCString appName; + if (NS_FAILED(appInfo->GetName(appName))) { + // We just won't register then, let Windows handle it. + return false; + } + + nsAutoString regKey; + regKey.AssignLiteral("Software\\Mozilla\\"); + AppendASCIItoUTF16(appName, regKey); + regKey.AppendLiteral("\\TaskBarIDs"); + + WCHAR path[MAX_PATH]; + if (GetModuleFileNameW(nullptr, path, MAX_PATH)) { + wchar_t* slash = wcsrchr(path, '\\'); + if (!slash) + return false; + *slash = '\0'; // no trailing slash + + // The hash is short, but users may customize this, so use a respectable + // string buffer. + wchar_t buf[256]; + if (WinUtils::GetRegistryKey(HKEY_LOCAL_MACHINE, + regKey.get(), + path, + buf, + sizeof buf)) { + aDefaultGroupId.Assign(buf); + } else if (WinUtils::GetRegistryKey(HKEY_CURRENT_USER, + regKey.get(), + path, + buf, + sizeof buf)) { + aDefaultGroupId.Assign(buf); + } + } + + return !aDefaultGroupId.IsEmpty(); + + return true; +} + +NS_IMETHODIMP +WinTaskbar::GetDefaultGroupId(nsAString & aDefaultGroupId) { + if (!GetAppUserModelID(aDefaultGroupId)) + return NS_ERROR_UNEXPECTED; + + return NS_OK; +} + +// (static) Called from AppShell +bool +WinTaskbar::RegisterAppUserModelID() { + if (!IsWin7OrLater()) + return false; + + SetCurrentProcessExplicitAppUserModelIDPtr funcAppUserModelID = nullptr; + bool retVal = false; + + nsAutoString uid; + if (!GetAppUserModelID(uid)) + return false; + + HMODULE hDLL = ::LoadLibraryW(kShellLibraryName); + + funcAppUserModelID = (SetCurrentProcessExplicitAppUserModelIDPtr) + GetProcAddress(hDLL, "SetCurrentProcessExplicitAppUserModelID"); + + if (!funcAppUserModelID) { + ::FreeLibrary(hDLL); + return false; + } + + if (SUCCEEDED(funcAppUserModelID(uid.get()))) + retVal = true; + + if (hDLL) + ::FreeLibrary(hDLL); + + return retVal; +} + +NS_IMETHODIMP +WinTaskbar::GetAvailable(bool *aAvailable) { + // ITaskbarList4::HrInit() may fail with shell extensions like blackbox + // installed. Initialize early to return available=false in those cases. + *aAvailable = IsWin7OrLater() && Initialize(); + + return NS_OK; +} + +NS_IMETHODIMP +WinTaskbar::CreateTaskbarTabPreview(nsIDocShell *shell, nsITaskbarPreviewController *controller, nsITaskbarTabPreview **_retval) { + if (!Initialize()) + return NS_ERROR_NOT_AVAILABLE; + + NS_ENSURE_ARG_POINTER(shell); + NS_ENSURE_ARG_POINTER(controller); + + HWND toplevelHWND = ::GetAncestor(GetHWNDFromDocShell(shell), GA_ROOT); + + if (!toplevelHWND) + return NS_ERROR_INVALID_ARG; + + RefPtr<TaskbarTabPreview> preview(new TaskbarTabPreview(mTaskbar, controller, toplevelHWND, shell)); + if (!preview) + return NS_ERROR_OUT_OF_MEMORY; + + preview.forget(_retval); + + return NS_OK; +} + +NS_IMETHODIMP +WinTaskbar::GetTaskbarWindowPreview(nsIDocShell *shell, nsITaskbarWindowPreview **_retval) { + if (!Initialize()) + return NS_ERROR_NOT_AVAILABLE; + + NS_ENSURE_ARG_POINTER(shell); + + HWND toplevelHWND = ::GetAncestor(GetHWNDFromDocShell(shell), GA_ROOT); + + if (!toplevelHWND) + return NS_ERROR_INVALID_ARG; + + nsWindow *window = WinUtils::GetNSWindowPtr(toplevelHWND); + + if (!window) + return NS_ERROR_FAILURE; + + nsCOMPtr<nsITaskbarWindowPreview> preview = window->GetTaskbarPreview(); + if (!preview) { + RefPtr<DefaultController> defaultController = new DefaultController(toplevelHWND); + preview = new TaskbarWindowPreview(mTaskbar, defaultController, toplevelHWND, shell); + if (!preview) + return NS_ERROR_OUT_OF_MEMORY; + window->SetTaskbarPreview(preview); + } + + preview.forget(_retval); + + return NS_OK; +} + +NS_IMETHODIMP +WinTaskbar::GetTaskbarProgress(nsIDocShell *shell, nsITaskbarProgress **_retval) { + nsCOMPtr<nsITaskbarWindowPreview> preview; + nsresult rv = GetTaskbarWindowPreview(shell, getter_AddRefs(preview)); + NS_ENSURE_SUCCESS(rv, rv); + + return CallQueryInterface(preview, _retval); +} + +NS_IMETHODIMP +WinTaskbar::GetOverlayIconController(nsIDocShell *shell, + nsITaskbarOverlayIconController **_retval) { + nsCOMPtr<nsITaskbarWindowPreview> preview; + nsresult rv = GetTaskbarWindowPreview(shell, getter_AddRefs(preview)); + NS_ENSURE_SUCCESS(rv, rv); + + return CallQueryInterface(preview, _retval); +} + +NS_IMETHODIMP +WinTaskbar::CreateJumpListBuilder(nsIJumpListBuilder * *aJumpListBuilder) { + nsresult rv; + + if (JumpListBuilder::sBuildingList) + return NS_ERROR_ALREADY_INITIALIZED; + + nsCOMPtr<nsIJumpListBuilder> builder = + do_CreateInstance(kJumpListBuilderCID, &rv); + if (NS_FAILED(rv)) + return NS_ERROR_UNEXPECTED; + + NS_IF_ADDREF(*aJumpListBuilder = builder); + + return NS_OK; +} + +NS_IMETHODIMP +WinTaskbar::SetGroupIdForWindow(mozIDOMWindow *aParent, + const nsAString & aIdentifier) { + return SetWindowAppUserModelProp(aParent, nsString(aIdentifier)); +} + +NS_IMETHODIMP +WinTaskbar::PrepareFullScreen(mozIDOMWindow *aWindow, bool aFullScreen) { + NS_ENSURE_ARG_POINTER(aWindow); + + HWND toplevelHWND = ::GetAncestor(GetHWNDFromDOMWindow(aWindow), GA_ROOT); + if (!toplevelHWND) + return NS_ERROR_INVALID_ARG; + + return PrepareFullScreenHWND(toplevelHWND, aFullScreen); +} + +NS_IMETHODIMP +WinTaskbar::PrepareFullScreenHWND(void *aHWND, bool aFullScreen) { + if (!Initialize()) + return NS_ERROR_NOT_AVAILABLE; + + NS_ENSURE_ARG_POINTER(aHWND); + + if (!::IsWindow((HWND)aHWND)) + return NS_ERROR_INVALID_ARG; + + HRESULT hr = mTaskbar->MarkFullscreenWindow((HWND)aHWND, aFullScreen); + if (FAILED(hr)) { + return NS_ERROR_UNEXPECTED; + } + + return NS_OK; +} + +} // namespace widget +} // namespace mozilla + diff --git a/widget/windows/WinTaskbar.h b/widget/windows/WinTaskbar.h new file mode 100644 index 0000000000..46968737d9 --- /dev/null +++ b/widget/windows/WinTaskbar.h @@ -0,0 +1,46 @@ +/* vim: se cin sw=2 ts=2 et : */ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __WinTaskbar_h__ +#define __WinTaskbar_h__ + +#include <windows.h> +#include <shobjidl.h> +#undef LogSeverity // SetupAPI.h #defines this as DWORD +#include "nsIWinTaskbar.h" +#include "mozilla/Attributes.h" + +namespace mozilla { +namespace widget { + +class WinTaskbar final : public nsIWinTaskbar +{ + ~WinTaskbar(); + +public: + WinTaskbar(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIWINTASKBAR + + // Registers the global app user model id for the instance. + // See comments in WinTaskbar.cpp for more information. + static bool RegisterAppUserModelID(); + static bool GetAppUserModelID(nsAString & aDefaultGroupId); + +private: + bool Initialize(); + + typedef HRESULT (WINAPI * SetCurrentProcessExplicitAppUserModelIDPtr)(PCWSTR AppID); + ITaskbarList4 *mTaskbar; +}; + +} // namespace widget +} // namespace mozilla + +#endif /* __WinTaskbar_h__ */ + diff --git a/widget/windows/WinTextEventDispatcherListener.cpp b/widget/windows/WinTextEventDispatcherListener.cpp new file mode 100644 index 0000000000..156c7a79b1 --- /dev/null +++ b/widget/windows/WinTextEventDispatcherListener.cpp @@ -0,0 +1,78 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "KeyboardLayout.h" +#include "mozilla/TextEventDispatcher.h" +#include "mozilla/widget/IMEData.h" +#include "nsWindow.h" +#include "WinIMEHandler.h" +#include "WinTextEventDispatcherListener.h" + +namespace mozilla { +namespace widget { + +StaticRefPtr<WinTextEventDispatcherListener> + WinTextEventDispatcherListener::sInstance; + +// static +WinTextEventDispatcherListener* +WinTextEventDispatcherListener::GetInstance() +{ + if (!sInstance) { + sInstance = new WinTextEventDispatcherListener(); + } + return sInstance.get(); +} + +void +WinTextEventDispatcherListener::Shutdown() +{ + sInstance = nullptr; +} + +NS_IMPL_ISUPPORTS(WinTextEventDispatcherListener, + TextEventDispatcherListener, + nsISupportsWeakReference) + +WinTextEventDispatcherListener::WinTextEventDispatcherListener() +{ +} + +WinTextEventDispatcherListener::~WinTextEventDispatcherListener() +{ +} + +NS_IMETHODIMP +WinTextEventDispatcherListener::NotifyIME( + TextEventDispatcher* aTextEventDispatcher, + const IMENotification& aNotification) +{ + nsWindow* window = static_cast<nsWindow*>(aTextEventDispatcher->GetWidget()); + if (NS_WARN_IF(!window)) { + return NS_ERROR_FAILURE; + } + return IMEHandler::NotifyIME(window, aNotification); +} + +NS_IMETHODIMP_(void) +WinTextEventDispatcherListener::OnRemovedFrom( + TextEventDispatcher* aTextEventDispatcher) +{ + // XXX When input transaction is being stolen by add-on, what should we do? +} + +NS_IMETHODIMP_(void) +WinTextEventDispatcherListener::WillDispatchKeyboardEvent( + TextEventDispatcher* aTextEventDispatcher, + WidgetKeyboardEvent& aKeyboardEvent, + uint32_t aIndexOfKeypress, + void* aData) +{ + static_cast<NativeKey*>(aData)-> + WillDispatchKeyboardEvent(aKeyboardEvent, aIndexOfKeypress); +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/windows/WinTextEventDispatcherListener.h b/widget/windows/WinTextEventDispatcherListener.h new file mode 100644 index 0000000000..d4e7965d95 --- /dev/null +++ b/widget/windows/WinTextEventDispatcherListener.h @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef WinTextEventDispatcherListener_h_ +#define WinTextEventDispatcherListener_h_ + +#include "mozilla/Attributes.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/TextEventDispatcherListener.h" + +namespace mozilla { +namespace widget { + +/** + * On Windows, it's enough TextEventDispatcherListener to be a singleton + * because we have only one input context per process (IMM can create + * multiple IM context but we don't support such behavior). + */ + +class WinTextEventDispatcherListener final : public TextEventDispatcherListener +{ +public: + static WinTextEventDispatcherListener* GetInstance(); + static void Shutdown(); + + NS_DECL_ISUPPORTS + + NS_IMETHOD NotifyIME(TextEventDispatcher* aTextEventDispatcher, + const IMENotification& aNotification) override; + NS_IMETHOD_(void) OnRemovedFrom( + TextEventDispatcher* aTextEventDispatcher) override; + NS_IMETHOD_(void) WillDispatchKeyboardEvent( + TextEventDispatcher* aTextEventDispatcher, + WidgetKeyboardEvent& aKeyboardEvent, + uint32_t aIndexOfKeypress, + void* aData) override; + +private: + WinTextEventDispatcherListener(); + virtual ~WinTextEventDispatcherListener(); + + static StaticRefPtr<WinTextEventDispatcherListener> sInstance; +}; + +} // namespace widget +} // namespace mozilla + +#endif // #ifndef WinTextEventDispatcherListener_h_ diff --git a/widget/windows/WinUtils.cpp b/widget/windows/WinUtils.cpp new file mode 100644 index 0000000000..149513b2fa --- /dev/null +++ b/widget/windows/WinUtils.cpp @@ -0,0 +1,2110 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sts=2 sw=2 et cin: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "WinUtils.h" + +#include <knownfolders.h> +#include <winioctl.h> + +#include "gfxPlatform.h" +#include "gfxUtils.h" +#include "nsWindow.h" +#include "nsWindowDefs.h" +#include "KeyboardLayout.h" +#include "nsIDOMMouseEvent.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/DataSurfaceHelpers.h" +#include "mozilla/HangMonitor.h" +#include "mozilla/Preferences.h" +#include "mozilla/RefPtr.h" +#include "mozilla/WindowsVersion.h" +#include "mozilla/Unused.h" +#include "nsIContentPolicy.h" +#include "nsContentUtils.h" + +#include "mozilla/Logging.h" + +#include "nsString.h" +#include "nsDirectoryServiceUtils.h" +#include "imgIContainer.h" +#include "imgITools.h" +#include "nsStringStream.h" +#include "nsNetUtil.h" +#include "nsIOutputStream.h" +#include "nsNetCID.h" +#include "prtime.h" +#ifdef MOZ_PLACES +#include "mozIAsyncFavicons.h" +#endif +#include "nsIIconURI.h" +#include "nsIDownloader.h" +#include "nsINetUtil.h" +#include "nsIChannel.h" +#include "nsIObserver.h" +#include "imgIEncoder.h" +#include "nsIThread.h" +#include "MainThreadUtils.h" +#include "nsLookAndFeel.h" +#include "nsUnicharUtils.h" +#include "nsWindowsHelpers.h" + +#ifdef NS_ENABLE_TSF +#include <textstor.h> +#include "TSFTextStore.h" +#endif // #ifdef NS_ENABLE_TSF + +#include <shlwapi.h> + +mozilla::LazyLogModule gWindowsLog("Widget"); + +using namespace mozilla::gfx; + +namespace mozilla { +namespace widget { + +#define ENTRY(_msg) { #_msg, _msg } +EventMsgInfo gAllEvents[] = { + ENTRY(WM_NULL), + ENTRY(WM_CREATE), + ENTRY(WM_DESTROY), + ENTRY(WM_MOVE), + ENTRY(WM_SIZE), + ENTRY(WM_ACTIVATE), + ENTRY(WM_SETFOCUS), + ENTRY(WM_KILLFOCUS), + ENTRY(WM_ENABLE), + ENTRY(WM_SETREDRAW), + ENTRY(WM_SETTEXT), + ENTRY(WM_GETTEXT), + ENTRY(WM_GETTEXTLENGTH), + ENTRY(WM_PAINT), + ENTRY(WM_CLOSE), + ENTRY(WM_QUERYENDSESSION), + ENTRY(WM_QUIT), + ENTRY(WM_QUERYOPEN), + ENTRY(WM_ERASEBKGND), + ENTRY(WM_SYSCOLORCHANGE), + ENTRY(WM_ENDSESSION), + ENTRY(WM_SHOWWINDOW), + ENTRY(WM_SETTINGCHANGE), + ENTRY(WM_DEVMODECHANGE), + ENTRY(WM_ACTIVATEAPP), + ENTRY(WM_FONTCHANGE), + ENTRY(WM_TIMECHANGE), + ENTRY(WM_CANCELMODE), + ENTRY(WM_SETCURSOR), + ENTRY(WM_MOUSEACTIVATE), + ENTRY(WM_CHILDACTIVATE), + ENTRY(WM_QUEUESYNC), + ENTRY(WM_GETMINMAXINFO), + ENTRY(WM_PAINTICON), + ENTRY(WM_ICONERASEBKGND), + ENTRY(WM_NEXTDLGCTL), + ENTRY(WM_SPOOLERSTATUS), + ENTRY(WM_DRAWITEM), + ENTRY(WM_MEASUREITEM), + ENTRY(WM_DELETEITEM), + ENTRY(WM_VKEYTOITEM), + ENTRY(WM_CHARTOITEM), + ENTRY(WM_SETFONT), + ENTRY(WM_GETFONT), + ENTRY(WM_SETHOTKEY), + ENTRY(WM_GETHOTKEY), + ENTRY(WM_QUERYDRAGICON), + ENTRY(WM_COMPAREITEM), + ENTRY(WM_GETOBJECT), + ENTRY(WM_COMPACTING), + ENTRY(WM_COMMNOTIFY), + ENTRY(WM_WINDOWPOSCHANGING), + ENTRY(WM_WINDOWPOSCHANGED), + ENTRY(WM_POWER), + ENTRY(WM_COPYDATA), + ENTRY(WM_CANCELJOURNAL), + ENTRY(WM_NOTIFY), + ENTRY(WM_INPUTLANGCHANGEREQUEST), + ENTRY(WM_INPUTLANGCHANGE), + ENTRY(WM_TCARD), + ENTRY(WM_HELP), + ENTRY(WM_USERCHANGED), + ENTRY(WM_NOTIFYFORMAT), + ENTRY(WM_CONTEXTMENU), + ENTRY(WM_STYLECHANGING), + ENTRY(WM_STYLECHANGED), + ENTRY(WM_DISPLAYCHANGE), + ENTRY(WM_GETICON), + ENTRY(WM_SETICON), + ENTRY(WM_NCCREATE), + ENTRY(WM_NCDESTROY), + ENTRY(WM_NCCALCSIZE), + ENTRY(WM_NCHITTEST), + ENTRY(WM_NCPAINT), + ENTRY(WM_NCACTIVATE), + ENTRY(WM_GETDLGCODE), + ENTRY(WM_SYNCPAINT), + ENTRY(WM_NCMOUSEMOVE), + ENTRY(WM_NCLBUTTONDOWN), + ENTRY(WM_NCLBUTTONUP), + ENTRY(WM_NCLBUTTONDBLCLK), + ENTRY(WM_NCRBUTTONDOWN), + ENTRY(WM_NCRBUTTONUP), + ENTRY(WM_NCRBUTTONDBLCLK), + ENTRY(WM_NCMBUTTONDOWN), + ENTRY(WM_NCMBUTTONUP), + ENTRY(WM_NCMBUTTONDBLCLK), + ENTRY(EM_GETSEL), + ENTRY(EM_SETSEL), + ENTRY(EM_GETRECT), + ENTRY(EM_SETRECT), + ENTRY(EM_SETRECTNP), + ENTRY(EM_SCROLL), + ENTRY(EM_LINESCROLL), + ENTRY(EM_SCROLLCARET), + ENTRY(EM_GETMODIFY), + ENTRY(EM_SETMODIFY), + ENTRY(EM_GETLINECOUNT), + ENTRY(EM_LINEINDEX), + ENTRY(EM_SETHANDLE), + ENTRY(EM_GETHANDLE), + ENTRY(EM_GETTHUMB), + ENTRY(EM_LINELENGTH), + ENTRY(EM_REPLACESEL), + ENTRY(EM_GETLINE), + ENTRY(EM_LIMITTEXT), + ENTRY(EM_CANUNDO), + ENTRY(EM_UNDO), + ENTRY(EM_FMTLINES), + ENTRY(EM_LINEFROMCHAR), + ENTRY(EM_SETTABSTOPS), + ENTRY(EM_SETPASSWORDCHAR), + ENTRY(EM_EMPTYUNDOBUFFER), + ENTRY(EM_GETFIRSTVISIBLELINE), + ENTRY(EM_SETREADONLY), + ENTRY(EM_SETWORDBREAKPROC), + ENTRY(EM_GETWORDBREAKPROC), + ENTRY(EM_GETPASSWORDCHAR), + ENTRY(EM_SETMARGINS), + ENTRY(EM_GETMARGINS), + ENTRY(EM_GETLIMITTEXT), + ENTRY(EM_POSFROMCHAR), + ENTRY(EM_CHARFROMPOS), + ENTRY(EM_SETIMESTATUS), + ENTRY(EM_GETIMESTATUS), + ENTRY(SBM_SETPOS), + ENTRY(SBM_GETPOS), + ENTRY(SBM_SETRANGE), + ENTRY(SBM_SETRANGEREDRAW), + ENTRY(SBM_GETRANGE), + ENTRY(SBM_ENABLE_ARROWS), + ENTRY(SBM_SETSCROLLINFO), + ENTRY(SBM_GETSCROLLINFO), + ENTRY(WM_KEYDOWN), + ENTRY(WM_KEYUP), + ENTRY(WM_CHAR), + ENTRY(WM_DEADCHAR), + ENTRY(WM_SYSKEYDOWN), + ENTRY(WM_SYSKEYUP), + ENTRY(WM_SYSCHAR), + ENTRY(WM_SYSDEADCHAR), + ENTRY(WM_KEYLAST), + ENTRY(WM_IME_STARTCOMPOSITION), + ENTRY(WM_IME_ENDCOMPOSITION), + ENTRY(WM_IME_COMPOSITION), + ENTRY(WM_INITDIALOG), + ENTRY(WM_COMMAND), + ENTRY(WM_SYSCOMMAND), + ENTRY(WM_TIMER), + ENTRY(WM_HSCROLL), + ENTRY(WM_VSCROLL), + ENTRY(WM_INITMENU), + ENTRY(WM_INITMENUPOPUP), + ENTRY(WM_MENUSELECT), + ENTRY(WM_MENUCHAR), + ENTRY(WM_ENTERIDLE), + ENTRY(WM_MENURBUTTONUP), + ENTRY(WM_MENUDRAG), + ENTRY(WM_MENUGETOBJECT), + ENTRY(WM_UNINITMENUPOPUP), + ENTRY(WM_MENUCOMMAND), + ENTRY(WM_CHANGEUISTATE), + ENTRY(WM_UPDATEUISTATE), + ENTRY(WM_CTLCOLORMSGBOX), + ENTRY(WM_CTLCOLOREDIT), + ENTRY(WM_CTLCOLORLISTBOX), + ENTRY(WM_CTLCOLORBTN), + ENTRY(WM_CTLCOLORDLG), + ENTRY(WM_CTLCOLORSCROLLBAR), + ENTRY(WM_CTLCOLORSTATIC), + ENTRY(CB_GETEDITSEL), + ENTRY(CB_LIMITTEXT), + ENTRY(CB_SETEDITSEL), + ENTRY(CB_ADDSTRING), + ENTRY(CB_DELETESTRING), + ENTRY(CB_DIR), + ENTRY(CB_GETCOUNT), + ENTRY(CB_GETCURSEL), + ENTRY(CB_GETLBTEXT), + ENTRY(CB_GETLBTEXTLEN), + ENTRY(CB_INSERTSTRING), + ENTRY(CB_RESETCONTENT), + ENTRY(CB_FINDSTRING), + ENTRY(CB_SELECTSTRING), + ENTRY(CB_SETCURSEL), + ENTRY(CB_SHOWDROPDOWN), + ENTRY(CB_GETITEMDATA), + ENTRY(CB_SETITEMDATA), + ENTRY(CB_GETDROPPEDCONTROLRECT), + ENTRY(CB_SETITEMHEIGHT), + ENTRY(CB_GETITEMHEIGHT), + ENTRY(CB_SETEXTENDEDUI), + ENTRY(CB_GETEXTENDEDUI), + ENTRY(CB_GETDROPPEDSTATE), + ENTRY(CB_FINDSTRINGEXACT), + ENTRY(CB_SETLOCALE), + ENTRY(CB_GETLOCALE), + ENTRY(CB_GETTOPINDEX), + ENTRY(CB_SETTOPINDEX), + ENTRY(CB_GETHORIZONTALEXTENT), + ENTRY(CB_SETHORIZONTALEXTENT), + ENTRY(CB_GETDROPPEDWIDTH), + ENTRY(CB_SETDROPPEDWIDTH), + ENTRY(CB_INITSTORAGE), + ENTRY(CB_MSGMAX), + ENTRY(LB_ADDSTRING), + ENTRY(LB_INSERTSTRING), + ENTRY(LB_DELETESTRING), + ENTRY(LB_SELITEMRANGEEX), + ENTRY(LB_RESETCONTENT), + ENTRY(LB_SETSEL), + ENTRY(LB_SETCURSEL), + ENTRY(LB_GETSEL), + ENTRY(LB_GETCURSEL), + ENTRY(LB_GETTEXT), + ENTRY(LB_GETTEXTLEN), + ENTRY(LB_GETCOUNT), + ENTRY(LB_SELECTSTRING), + ENTRY(LB_DIR), + ENTRY(LB_GETTOPINDEX), + ENTRY(LB_FINDSTRING), + ENTRY(LB_GETSELCOUNT), + ENTRY(LB_GETSELITEMS), + ENTRY(LB_SETTABSTOPS), + ENTRY(LB_GETHORIZONTALEXTENT), + ENTRY(LB_SETHORIZONTALEXTENT), + ENTRY(LB_SETCOLUMNWIDTH), + ENTRY(LB_ADDFILE), + ENTRY(LB_SETTOPINDEX), + ENTRY(LB_GETITEMRECT), + ENTRY(LB_GETITEMDATA), + ENTRY(LB_SETITEMDATA), + ENTRY(LB_SELITEMRANGE), + ENTRY(LB_SETANCHORINDEX), + ENTRY(LB_GETANCHORINDEX), + ENTRY(LB_SETCARETINDEX), + ENTRY(LB_GETCARETINDEX), + ENTRY(LB_SETITEMHEIGHT), + ENTRY(LB_GETITEMHEIGHT), + ENTRY(LB_FINDSTRINGEXACT), + ENTRY(LB_SETLOCALE), + ENTRY(LB_GETLOCALE), + ENTRY(LB_SETCOUNT), + ENTRY(LB_INITSTORAGE), + ENTRY(LB_ITEMFROMPOINT), + ENTRY(LB_MSGMAX), + ENTRY(WM_MOUSEMOVE), + ENTRY(WM_LBUTTONDOWN), + ENTRY(WM_LBUTTONUP), + ENTRY(WM_LBUTTONDBLCLK), + ENTRY(WM_RBUTTONDOWN), + ENTRY(WM_RBUTTONUP), + ENTRY(WM_RBUTTONDBLCLK), + ENTRY(WM_MBUTTONDOWN), + ENTRY(WM_MBUTTONUP), + ENTRY(WM_MBUTTONDBLCLK), + ENTRY(WM_MOUSEWHEEL), + ENTRY(WM_MOUSEHWHEEL), + ENTRY(WM_PARENTNOTIFY), + ENTRY(WM_ENTERMENULOOP), + ENTRY(WM_EXITMENULOOP), + ENTRY(WM_NEXTMENU), + ENTRY(WM_SIZING), + ENTRY(WM_CAPTURECHANGED), + ENTRY(WM_MOVING), + ENTRY(WM_POWERBROADCAST), + ENTRY(WM_DEVICECHANGE), + ENTRY(WM_MDICREATE), + ENTRY(WM_MDIDESTROY), + ENTRY(WM_MDIACTIVATE), + ENTRY(WM_MDIRESTORE), + ENTRY(WM_MDINEXT), + ENTRY(WM_MDIMAXIMIZE), + ENTRY(WM_MDITILE), + ENTRY(WM_MDICASCADE), + ENTRY(WM_MDIICONARRANGE), + ENTRY(WM_MDIGETACTIVE), + ENTRY(WM_MDISETMENU), + ENTRY(WM_ENTERSIZEMOVE), + ENTRY(WM_EXITSIZEMOVE), + ENTRY(WM_DROPFILES), + ENTRY(WM_MDIREFRESHMENU), + ENTRY(WM_IME_SETCONTEXT), + ENTRY(WM_IME_NOTIFY), + ENTRY(WM_IME_CONTROL), + ENTRY(WM_IME_COMPOSITIONFULL), + ENTRY(WM_IME_SELECT), + ENTRY(WM_IME_CHAR), + ENTRY(WM_IME_REQUEST), + ENTRY(WM_IME_KEYDOWN), + ENTRY(WM_IME_KEYUP), + ENTRY(WM_NCMOUSEHOVER), + ENTRY(WM_MOUSEHOVER), + ENTRY(WM_MOUSELEAVE), + ENTRY(WM_CUT), + ENTRY(WM_COPY), + ENTRY(WM_PASTE), + ENTRY(WM_CLEAR), + ENTRY(WM_UNDO), + ENTRY(WM_RENDERFORMAT), + ENTRY(WM_RENDERALLFORMATS), + ENTRY(WM_DESTROYCLIPBOARD), + ENTRY(WM_DRAWCLIPBOARD), + ENTRY(WM_PAINTCLIPBOARD), + ENTRY(WM_VSCROLLCLIPBOARD), + ENTRY(WM_SIZECLIPBOARD), + ENTRY(WM_ASKCBFORMATNAME), + ENTRY(WM_CHANGECBCHAIN), + ENTRY(WM_HSCROLLCLIPBOARD), + ENTRY(WM_QUERYNEWPALETTE), + ENTRY(WM_PALETTEISCHANGING), + ENTRY(WM_PALETTECHANGED), + ENTRY(WM_HOTKEY), + ENTRY(WM_PRINT), + ENTRY(WM_PRINTCLIENT), + ENTRY(WM_THEMECHANGED), + ENTRY(WM_HANDHELDFIRST), + ENTRY(WM_HANDHELDLAST), + ENTRY(WM_AFXFIRST), + ENTRY(WM_AFXLAST), + ENTRY(WM_PENWINFIRST), + ENTRY(WM_PENWINLAST), + ENTRY(WM_APP), + ENTRY(WM_DWMCOMPOSITIONCHANGED), + ENTRY(WM_DWMNCRENDERINGCHANGED), + ENTRY(WM_DWMCOLORIZATIONCOLORCHANGED), + ENTRY(WM_DWMWINDOWMAXIMIZEDCHANGE), + ENTRY(WM_DWMSENDICONICTHUMBNAIL), + ENTRY(WM_DWMSENDICONICLIVEPREVIEWBITMAP), + ENTRY(WM_TABLET_QUERYSYSTEMGESTURESTATUS), + ENTRY(WM_GESTURE), + ENTRY(WM_GESTURENOTIFY), + ENTRY(WM_GETTITLEBARINFOEX), + {nullptr, 0x0} +}; +#undef ENTRY + +#ifdef MOZ_PLACES +NS_IMPL_ISUPPORTS(myDownloadObserver, nsIDownloadObserver) +NS_IMPL_ISUPPORTS(AsyncFaviconDataReady, nsIFaviconDataCallback) +#endif +NS_IMPL_ISUPPORTS(AsyncEncodeAndWriteIcon, nsIRunnable) +NS_IMPL_ISUPPORTS(AsyncDeleteIconFromDisk, nsIRunnable) +NS_IMPL_ISUPPORTS(AsyncDeleteAllFaviconsFromDisk, nsIRunnable) + + +const char FaviconHelper::kJumpListCacheDir[] = "jumpListCache"; +const char FaviconHelper::kShortcutCacheDir[] = "shortcutCache"; + +// apis available on vista and up. +WinUtils::SHCreateItemFromParsingNamePtr WinUtils::sCreateItemFromParsingName = nullptr; +WinUtils::SHGetKnownFolderPathPtr WinUtils::sGetKnownFolderPath = nullptr; + +// We just leak these DLL HMODULEs. There's no point in calling FreeLibrary +// on them during shutdown anyway. +static const wchar_t kShellLibraryName[] = L"shell32.dll"; +static HMODULE sShellDll = nullptr; +static const wchar_t kDwmLibraryName[] = L"dwmapi.dll"; +static HMODULE sDwmDll = nullptr; + +WinUtils::DwmExtendFrameIntoClientAreaProc WinUtils::dwmExtendFrameIntoClientAreaPtr = nullptr; +WinUtils::DwmIsCompositionEnabledProc WinUtils::dwmIsCompositionEnabledPtr = nullptr; +WinUtils::DwmSetIconicThumbnailProc WinUtils::dwmSetIconicThumbnailPtr = nullptr; +WinUtils::DwmSetIconicLivePreviewBitmapProc WinUtils::dwmSetIconicLivePreviewBitmapPtr = nullptr; +WinUtils::DwmGetWindowAttributeProc WinUtils::dwmGetWindowAttributePtr = nullptr; +WinUtils::DwmSetWindowAttributeProc WinUtils::dwmSetWindowAttributePtr = nullptr; +WinUtils::DwmInvalidateIconicBitmapsProc WinUtils::dwmInvalidateIconicBitmapsPtr = nullptr; +WinUtils::DwmDefWindowProcProc WinUtils::dwmDwmDefWindowProcPtr = nullptr; +WinUtils::DwmGetCompositionTimingInfoProc WinUtils::dwmGetCompositionTimingInfoPtr = nullptr; +WinUtils::DwmFlushProc WinUtils::dwmFlushProcPtr = nullptr; + +// Prefix for path used by NT calls. +const wchar_t kNTPrefix[] = L"\\??\\"; +const size_t kNTPrefixLen = ArrayLength(kNTPrefix) - 1; + +struct CoTaskMemFreePolicy +{ + void operator()(void* aPtr) { + ::CoTaskMemFree(aPtr); + } +}; + +SetThreadDpiAwarenessContextProc WinUtils::sSetThreadDpiAwarenessContext = NULL; +EnableNonClientDpiScalingProc WinUtils::sEnableNonClientDpiScaling = NULL; +#ifdef ACCESSIBILITY +typedef NTSTATUS (NTAPI* NtTestAlertPtr)(VOID); +static NtTestAlertPtr sNtTestAlert = nullptr; +#endif + + +/* static */ +void +WinUtils::Initialize() +{ + if (!sDwmDll && IsVistaOrLater()) { + sDwmDll = ::LoadLibraryW(kDwmLibraryName); + + if (sDwmDll) { + dwmExtendFrameIntoClientAreaPtr = (DwmExtendFrameIntoClientAreaProc)::GetProcAddress(sDwmDll, "DwmExtendFrameIntoClientArea"); + dwmIsCompositionEnabledPtr = (DwmIsCompositionEnabledProc)::GetProcAddress(sDwmDll, "DwmIsCompositionEnabled"); + dwmSetIconicThumbnailPtr = (DwmSetIconicThumbnailProc)::GetProcAddress(sDwmDll, "DwmSetIconicThumbnail"); + dwmSetIconicLivePreviewBitmapPtr = (DwmSetIconicLivePreviewBitmapProc)::GetProcAddress(sDwmDll, "DwmSetIconicLivePreviewBitmap"); + dwmGetWindowAttributePtr = (DwmGetWindowAttributeProc)::GetProcAddress(sDwmDll, "DwmGetWindowAttribute"); + dwmSetWindowAttributePtr = (DwmSetWindowAttributeProc)::GetProcAddress(sDwmDll, "DwmSetWindowAttribute"); + dwmInvalidateIconicBitmapsPtr = (DwmInvalidateIconicBitmapsProc)::GetProcAddress(sDwmDll, "DwmInvalidateIconicBitmaps"); + dwmDwmDefWindowProcPtr = (DwmDefWindowProcProc)::GetProcAddress(sDwmDll, "DwmDefWindowProc"); + dwmGetCompositionTimingInfoPtr = (DwmGetCompositionTimingInfoProc)::GetProcAddress(sDwmDll, "DwmGetCompositionTimingInfo"); + dwmFlushProcPtr = (DwmFlushProc)::GetProcAddress(sDwmDll, "DwmFlush"); + } + } + + if (IsWin10OrLater()) { + HMODULE user32Dll = ::GetModuleHandleW(L"user32"); + if (user32Dll) { + sEnableNonClientDpiScaling = (EnableNonClientDpiScalingProc) + ::GetProcAddress(user32Dll, "EnableNonClientDpiScaling"); + sSetThreadDpiAwarenessContext = (SetThreadDpiAwarenessContextProc) + ::GetProcAddress(user32Dll, "SetThreadDpiAwarenessContext"); + } + } + +#ifdef ACCESSIBILITY + sNtTestAlert = reinterpret_cast<NtTestAlertPtr>( + ::GetProcAddress(::GetModuleHandleW(L"ntdll.dll"), "NtTestAlert")); + MOZ_ASSERT(sNtTestAlert); +#endif +} + +// static +LRESULT WINAPI +WinUtils::NonClientDpiScalingDefWindowProcW(HWND hWnd, UINT msg, + WPARAM wParam, LPARAM lParam) +{ + if (msg == WM_NCCREATE && sEnableNonClientDpiScaling) { + sEnableNonClientDpiScaling(hWnd); + } + return ::DefWindowProcW(hWnd, msg, wParam, lParam); +} + +// static +void +WinUtils::LogW(const wchar_t *fmt, ...) +{ + va_list args = nullptr; + if(!lstrlenW(fmt)) { + return; + } + va_start(args, fmt); + int buflen = _vscwprintf(fmt, args); + wchar_t* buffer = new wchar_t[buflen+1]; + if (!buffer) { + va_end(args); + return; + } + vswprintf(buffer, buflen, fmt, args); + va_end(args); + + // MSVC, including remote debug sessions + OutputDebugStringW(buffer); + OutputDebugStringW(L"\n"); + + int len = WideCharToMultiByte(CP_ACP, 0, buffer, -1, nullptr, 0, nullptr, nullptr); + if (len) { + char* utf8 = new char[len]; + if (WideCharToMultiByte(CP_ACP, 0, buffer, + -1, utf8, len, nullptr, + nullptr) > 0) { + // desktop console + printf("%s\n", utf8); + NS_ASSERTION(gWindowsLog, "Called WinUtils Log() but Widget " + "log module doesn't exist!"); + MOZ_LOG(gWindowsLog, LogLevel::Error, (utf8)); + } + delete[] utf8; + } + delete[] buffer; +} + +// static +void +WinUtils::Log(const char *fmt, ...) +{ + va_list args = nullptr; + if(!strlen(fmt)) { + return; + } + va_start(args, fmt); + int buflen = _vscprintf(fmt, args); + char* buffer = new char[buflen+1]; + if (!buffer) { + va_end(args); + return; + } + vsprintf(buffer, fmt, args); + va_end(args); + + // MSVC, including remote debug sessions + OutputDebugStringA(buffer); + OutputDebugStringW(L"\n"); + + // desktop console + printf("%s\n", buffer); + + NS_ASSERTION(gWindowsLog, "Called WinUtils Log() but Widget " + "log module doesn't exist!"); + MOZ_LOG(gWindowsLog, LogLevel::Error, (buffer)); + delete[] buffer; +} + +// static +double +WinUtils::SystemScaleFactor() +{ + // The result of GetDeviceCaps won't change dynamically, as it predates + // per-monitor DPI and support for on-the-fly resolution changes. + // Therefore, we only need to look it up once. + static double systemScale = 0; + if (systemScale == 0) { + HDC screenDC = GetDC(nullptr); + systemScale = GetDeviceCaps(screenDC, LOGPIXELSY) / 96.0; + ReleaseDC(nullptr, screenDC); + + if (systemScale == 0) { + // Bug 1012487 - This can occur when the Screen DC is used off the + // main thread on windows. For now just assume a 100% DPI for this + // drawing call. + // XXX - fixme! + return 1.0; + } + } + return systemScale; +} + +#ifndef WM_DPICHANGED +typedef enum { + MDT_EFFECTIVE_DPI = 0, + MDT_ANGULAR_DPI = 1, + MDT_RAW_DPI = 2, + MDT_DEFAULT = MDT_EFFECTIVE_DPI +} MONITOR_DPI_TYPE; + +typedef enum { + PROCESS_DPI_UNAWARE = 0, + PROCESS_SYSTEM_DPI_AWARE = 1, + PROCESS_PER_MONITOR_DPI_AWARE = 2 +} PROCESS_DPI_AWARENESS; +#endif + +typedef HRESULT +(WINAPI *GETDPIFORMONITORPROC)(HMONITOR, MONITOR_DPI_TYPE, UINT*, UINT*); + +typedef HRESULT +(WINAPI *GETPROCESSDPIAWARENESSPROC)(HANDLE, PROCESS_DPI_AWARENESS*); + +GETDPIFORMONITORPROC sGetDpiForMonitor; +GETPROCESSDPIAWARENESSPROC sGetProcessDpiAwareness; + +static bool +SlowIsPerMonitorDPIAware() +{ + if (IsVistaOrLater()) { + // Intentionally leak the handle. + HMODULE shcore = + LoadLibraryEx(L"shcore", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); + if (shcore) { + sGetDpiForMonitor = + (GETDPIFORMONITORPROC) GetProcAddress(shcore, "GetDpiForMonitor"); + sGetProcessDpiAwareness = + (GETPROCESSDPIAWARENESSPROC) GetProcAddress(shcore, "GetProcessDpiAwareness"); + } + } + PROCESS_DPI_AWARENESS dpiAwareness; + return sGetDpiForMonitor && sGetProcessDpiAwareness && + SUCCEEDED(sGetProcessDpiAwareness(GetCurrentProcess(), &dpiAwareness)) && + dpiAwareness == PROCESS_PER_MONITOR_DPI_AWARE; +} + +/* static */ bool +WinUtils::IsPerMonitorDPIAware() +{ + static bool perMonitorDPIAware = SlowIsPerMonitorDPIAware(); + return perMonitorDPIAware; +} + +/* static */ +double +WinUtils::LogToPhysFactor(HMONITOR aMonitor) +{ + if (IsPerMonitorDPIAware()) { + UINT dpiX, dpiY = 96; + sGetDpiForMonitor(aMonitor ? aMonitor : GetPrimaryMonitor(), + MDT_EFFECTIVE_DPI, &dpiX, &dpiY); + return dpiY / 96.0; + } + + return SystemScaleFactor(); +} + +/* static */ +int32_t +WinUtils::LogToPhys(HMONITOR aMonitor, double aValue) +{ + return int32_t(NS_round(aValue * LogToPhysFactor(aMonitor))); +} + +/* static */ +HMONITOR +WinUtils::GetPrimaryMonitor() +{ + const POINT pt = { 0, 0 }; + return ::MonitorFromPoint(pt, MONITOR_DEFAULTTOPRIMARY); +} + +/* static */ +HMONITOR +WinUtils::MonitorFromRect(const gfx::Rect& rect) +{ + // convert coordinates from desktop to device pixels for MonitorFromRect + double dpiScale = + IsPerMonitorDPIAware() ? 1.0 : LogToPhysFactor(GetPrimaryMonitor()); + + RECT globalWindowBounds = { + NSToIntRound(dpiScale * rect.x), + NSToIntRound(dpiScale * rect.y), + NSToIntRound(dpiScale * (rect.x + rect.width)), + NSToIntRound(dpiScale * (rect.y + rect.height)) + }; + + return ::MonitorFromRect(&globalWindowBounds, MONITOR_DEFAULTTONEAREST); +} + +#ifdef ACCESSIBILITY +#ifndef STATUS_SUCCESS +#define STATUS_SUCCESS ((NTSTATUS)0x00000000L) +#endif + +static Atomic<bool> sAPCPending; + +/* static */ +void +WinUtils::SetAPCPending() +{ + sAPCPending = true; +} +#endif // ACCESSIBILITY + +/* static */ +bool +WinUtils::PeekMessage(LPMSG aMsg, HWND aWnd, UINT aFirstMessage, + UINT aLastMessage, UINT aOption) +{ +#ifdef ACCESSIBILITY + if (NS_IsMainThread() && sAPCPending.exchange(false)) { + while (sNtTestAlert() != STATUS_SUCCESS) ; + } +#endif +#ifdef NS_ENABLE_TSF + RefPtr<ITfMessagePump> msgPump = TSFTextStore::GetMessagePump(); + if (msgPump) { + BOOL ret = FALSE; + HRESULT hr = msgPump->PeekMessageW(aMsg, aWnd, aFirstMessage, aLastMessage, + aOption, &ret); + NS_ENSURE_TRUE(SUCCEEDED(hr), false); + return ret; + } +#endif // #ifdef NS_ENABLE_TSF + return ::PeekMessageW(aMsg, aWnd, aFirstMessage, aLastMessage, aOption); +} + +/* static */ +bool +WinUtils::GetMessage(LPMSG aMsg, HWND aWnd, UINT aFirstMessage, + UINT aLastMessage) +{ +#ifdef NS_ENABLE_TSF + RefPtr<ITfMessagePump> msgPump = TSFTextStore::GetMessagePump(); + if (msgPump) { + BOOL ret = FALSE; + HRESULT hr = msgPump->GetMessageW(aMsg, aWnd, aFirstMessage, aLastMessage, + &ret); + NS_ENSURE_TRUE(SUCCEEDED(hr), false); + return ret; + } +#endif // #ifdef NS_ENABLE_TSF + return ::GetMessageW(aMsg, aWnd, aFirstMessage, aLastMessage); +} + +#if defined(ACCESSIBILITY) +static DWORD +GetWaitFlags() +{ + DWORD result = MWMO_INPUTAVAILABLE; + if (IsVistaOrLater() && XRE_IsContentProcess()) { + result |= MWMO_ALERTABLE; + } + return result; +} +#endif + +/* static */ +void +WinUtils::WaitForMessage(DWORD aTimeoutMs) +{ +#if defined(ACCESSIBILITY) + static const DWORD waitFlags = GetWaitFlags(); +#else + const DWORD waitFlags = MWMO_INPUTAVAILABLE; +#endif + + const DWORD waitStart = ::GetTickCount(); + DWORD elapsed = 0; + while (true) { + if (aTimeoutMs != INFINITE) { + elapsed = ::GetTickCount() - waitStart; + } + if (elapsed >= aTimeoutMs) { + break; + } + DWORD result = ::MsgWaitForMultipleObjectsEx(0, NULL, aTimeoutMs - elapsed, + MOZ_QS_ALLEVENT, waitFlags); + NS_WARNING_ASSERTION(result != WAIT_FAILED, "Wait failed"); + if (result == WAIT_TIMEOUT) { + break; + } +#if defined(ACCESSIBILITY) + if (result == WAIT_IO_COMPLETION) { + if (NS_IsMainThread()) { + if (sAPCPending.exchange(false)) { + // Clear out any pending APCs + while (sNtTestAlert() != STATUS_SUCCESS) ; + } + // We executed an APC that would have woken up the hang monitor. Since + // there are no more APCs pending and we are now going to sleep again, + // we should notify the hang monitor. + mozilla::HangMonitor::Suspend(); + } + continue; + } +#endif // defined(ACCESSIBILITY) + + // Sent messages (via SendMessage and friends) are processed differently + // than queued messages (via PostMessage); the destination window procedure + // of the sent message is called during (Get|Peek)Message. Since PeekMessage + // does not tell us whether it processed any sent messages, we need to query + // this ahead of time. + bool haveSentMessagesPending = + (HIWORD(::GetQueueStatus(QS_SENDMESSAGE)) & QS_SENDMESSAGE) != 0; + + MSG msg = {0}; + if (haveSentMessagesPending || + ::PeekMessageW(&msg, nullptr, 0, 0, PM_NOREMOVE)) { + break; + } + // The message is intended for another thread that has been synchronized + // with our input queue; yield to give other threads an opportunity to + // process the message. This should prevent busy waiting if resumed due + // to another thread's message. + ::SwitchToThread(); + } +} + +/* static */ +bool +WinUtils::GetRegistryKey(HKEY aRoot, + char16ptr_t aKeyName, + char16ptr_t aValueName, + wchar_t* aBuffer, + DWORD aBufferLength) +{ + NS_PRECONDITION(aKeyName, "The key name is NULL"); + + HKEY key; + LONG result = + ::RegOpenKeyExW(aRoot, aKeyName, 0, KEY_READ | KEY_WOW64_32KEY, &key); + if (result != ERROR_SUCCESS) { + result = + ::RegOpenKeyExW(aRoot, aKeyName, 0, KEY_READ | KEY_WOW64_64KEY, &key); + if (result != ERROR_SUCCESS) { + return false; + } + } + + DWORD type; + result = + ::RegQueryValueExW(key, aValueName, nullptr, &type, (BYTE*) aBuffer, + &aBufferLength); + ::RegCloseKey(key); + if (result != ERROR_SUCCESS || + (type != REG_SZ && type != REG_EXPAND_SZ)) { + return false; + } + if (aBuffer) { + aBuffer[aBufferLength / sizeof(*aBuffer) - 1] = 0; + } + return true; +} + +/* static */ +bool +WinUtils::HasRegistryKey(HKEY aRoot, char16ptr_t aKeyName) +{ + MOZ_ASSERT(aRoot, "aRoot must not be NULL"); + MOZ_ASSERT(aKeyName, "aKeyName must not be NULL"); + HKEY key; + LONG result = + ::RegOpenKeyExW(aRoot, aKeyName, 0, KEY_READ | KEY_WOW64_32KEY, &key); + if (result != ERROR_SUCCESS) { + result = + ::RegOpenKeyExW(aRoot, aKeyName, 0, KEY_READ | KEY_WOW64_64KEY, &key); + if (result != ERROR_SUCCESS) { + return false; + } + } + ::RegCloseKey(key); + return true; +} + +/* static */ +HWND +WinUtils::GetTopLevelHWND(HWND aWnd, + bool aStopIfNotChild, + bool aStopIfNotPopup) +{ + HWND curWnd = aWnd; + HWND topWnd = nullptr; + + while (curWnd) { + topWnd = curWnd; + + if (aStopIfNotChild) { + DWORD_PTR style = ::GetWindowLongPtrW(curWnd, GWL_STYLE); + + VERIFY_WINDOW_STYLE(style); + + if (!(style & WS_CHILD)) // first top-level window + break; + } + + HWND upWnd = ::GetParent(curWnd); // Parent or owner (if has no parent) + + // GetParent will only return the owner if the passed in window + // has the WS_POPUP style. + if (!upWnd && !aStopIfNotPopup) { + upWnd = ::GetWindow(curWnd, GW_OWNER); + } + curWnd = upWnd; + } + + return topWnd; +} + +static const wchar_t* +GetNSWindowPropName() +{ + static wchar_t sPropName[40] = L""; + if (!*sPropName) { + _snwprintf(sPropName, 39, L"MozillansIWidgetPtr%u", + ::GetCurrentProcessId()); + sPropName[39] = '\0'; + } + return sPropName; +} + +/* static */ +bool +WinUtils::SetNSWindowBasePtr(HWND aWnd, nsWindowBase* aWidget) +{ + if (!aWidget) { + ::RemovePropW(aWnd, GetNSWindowPropName()); + return true; + } + return ::SetPropW(aWnd, GetNSWindowPropName(), (HANDLE)aWidget); +} + +/* static */ +nsWindowBase* +WinUtils::GetNSWindowBasePtr(HWND aWnd) +{ + return static_cast<nsWindowBase*>(::GetPropW(aWnd, GetNSWindowPropName())); +} + +/* static */ +nsWindow* +WinUtils::GetNSWindowPtr(HWND aWnd) +{ + return static_cast<nsWindow*>(::GetPropW(aWnd, GetNSWindowPropName())); +} + +static BOOL CALLBACK +AddMonitor(HMONITOR, HDC, LPRECT, LPARAM aParam) +{ + (*(int32_t*)aParam)++; + return TRUE; +} + +/* static */ +int32_t +WinUtils::GetMonitorCount() +{ + int32_t monitorCount = 0; + EnumDisplayMonitors(nullptr, nullptr, AddMonitor, (LPARAM)&monitorCount); + return monitorCount; +} + +/* static */ +bool +WinUtils::IsOurProcessWindow(HWND aWnd) +{ + if (!aWnd) { + return false; + } + DWORD processId = 0; + ::GetWindowThreadProcessId(aWnd, &processId); + return (processId == ::GetCurrentProcessId()); +} + +/* static */ +HWND +WinUtils::FindOurProcessWindow(HWND aWnd) +{ + for (HWND wnd = ::GetParent(aWnd); wnd; wnd = ::GetParent(wnd)) { + if (IsOurProcessWindow(wnd)) { + return wnd; + } + } + return nullptr; +} + +static bool +IsPointInWindow(HWND aWnd, const POINT& aPointInScreen) +{ + RECT bounds; + if (!::GetWindowRect(aWnd, &bounds)) { + return false; + } + + return (aPointInScreen.x >= bounds.left && aPointInScreen.x < bounds.right && + aPointInScreen.y >= bounds.top && aPointInScreen.y < bounds.bottom); +} + +/** + * FindTopmostWindowAtPoint() returns the topmost child window (topmost means + * forground in this context) of aWnd. + */ + +static HWND +FindTopmostWindowAtPoint(HWND aWnd, const POINT& aPointInScreen) +{ + if (!::IsWindowVisible(aWnd) || !IsPointInWindow(aWnd, aPointInScreen)) { + return nullptr; + } + + HWND childWnd = ::GetTopWindow(aWnd); + while (childWnd) { + HWND topmostWnd = FindTopmostWindowAtPoint(childWnd, aPointInScreen); + if (topmostWnd) { + return topmostWnd; + } + childWnd = ::GetNextWindow(childWnd, GW_HWNDNEXT); + } + + return aWnd; +} + +struct FindOurWindowAtPointInfo +{ + POINT mInPointInScreen; + HWND mOutWnd; +}; + +static BOOL CALLBACK +FindOurWindowAtPointCallback(HWND aWnd, LPARAM aLPARAM) +{ + if (!WinUtils::IsOurProcessWindow(aWnd)) { + // This isn't one of our top-level windows; continue enumerating. + return TRUE; + } + + // Get the top-most child window under the point. If there's no child + // window, and the point is within the top-level window, then the top-level + // window will be returned. (This is the usual case. A child window + // would be returned for plugins.) + FindOurWindowAtPointInfo* info = + reinterpret_cast<FindOurWindowAtPointInfo*>(aLPARAM); + HWND childWnd = FindTopmostWindowAtPoint(aWnd, info->mInPointInScreen); + if (!childWnd) { + // This window doesn't contain the point; continue enumerating. + return TRUE; + } + + // Return the HWND and stop enumerating. + info->mOutWnd = childWnd; + return FALSE; +} + +/* static */ +HWND +WinUtils::FindOurWindowAtPoint(const POINT& aPointInScreen) +{ + FindOurWindowAtPointInfo info; + info.mInPointInScreen = aPointInScreen; + info.mOutWnd = nullptr; + + // This will enumerate all top-level windows in order from top to bottom. + EnumWindows(FindOurWindowAtPointCallback, reinterpret_cast<LPARAM>(&info)); + return info.mOutWnd; +} + +/* static */ +UINT +WinUtils::GetInternalMessage(UINT aNativeMessage) +{ + switch (aNativeMessage) { + case WM_MOUSEWHEEL: + return MOZ_WM_MOUSEVWHEEL; + case WM_MOUSEHWHEEL: + return MOZ_WM_MOUSEHWHEEL; + case WM_VSCROLL: + return MOZ_WM_VSCROLL; + case WM_HSCROLL: + return MOZ_WM_HSCROLL; + default: + return aNativeMessage; + } +} + +/* static */ +UINT +WinUtils::GetNativeMessage(UINT aInternalMessage) +{ + switch (aInternalMessage) { + case MOZ_WM_MOUSEVWHEEL: + return WM_MOUSEWHEEL; + case MOZ_WM_MOUSEHWHEEL: + return WM_MOUSEHWHEEL; + case MOZ_WM_VSCROLL: + return WM_VSCROLL; + case MOZ_WM_HSCROLL: + return WM_HSCROLL; + default: + return aInternalMessage; + } +} + +/* static */ +uint16_t +WinUtils::GetMouseInputSource() +{ + int32_t inputSource = nsIDOMMouseEvent::MOZ_SOURCE_MOUSE; + LPARAM lParamExtraInfo = ::GetMessageExtraInfo(); + if ((lParamExtraInfo & TABLET_INK_SIGNATURE) == TABLET_INK_CHECK) { + inputSource = (lParamExtraInfo & TABLET_INK_TOUCH) ? + nsIDOMMouseEvent::MOZ_SOURCE_TOUCH : nsIDOMMouseEvent::MOZ_SOURCE_PEN; + } + return static_cast<uint16_t>(inputSource); +} + +/* static */ +uint16_t +WinUtils::GetMousePointerID() +{ + LPARAM lParamExtraInfo = ::GetMessageExtraInfo(); + return lParamExtraInfo & TABLET_INK_ID_MASK; +} + +/* static */ +bool +WinUtils::GetIsMouseFromTouch(EventMessage aEventMessage) +{ + const uint32_t MOZ_T_I_SIGNATURE = TABLET_INK_TOUCH | TABLET_INK_SIGNATURE; + const uint32_t MOZ_T_I_CHECK_TCH = TABLET_INK_TOUCH | TABLET_INK_CHECK; + return ((aEventMessage == eMouseMove || aEventMessage == eMouseDown || + aEventMessage == eMouseUp || aEventMessage == eMouseDoubleClick) && + (GetMessageExtraInfo() & MOZ_T_I_SIGNATURE) == MOZ_T_I_CHECK_TCH); +} + +/* static */ +MSG +WinUtils::InitMSG(UINT aMessage, WPARAM wParam, LPARAM lParam, HWND aWnd) +{ + MSG msg; + msg.message = aMessage; + msg.wParam = wParam; + msg.lParam = lParam; + msg.hwnd = aWnd; + return msg; +} + +/* static */ +HRESULT +WinUtils::SHCreateItemFromParsingName(PCWSTR pszPath, IBindCtx *pbc, + REFIID riid, void **ppv) +{ + if (sCreateItemFromParsingName) { + return sCreateItemFromParsingName(pszPath, pbc, riid, ppv); + } + + if (!sShellDll) { + sShellDll = ::LoadLibraryW(kShellLibraryName); + if (!sShellDll) { + return false; + } + } + + sCreateItemFromParsingName = (SHCreateItemFromParsingNamePtr) + GetProcAddress(sShellDll, "SHCreateItemFromParsingName"); + if (!sCreateItemFromParsingName) + return E_FAIL; + + return sCreateItemFromParsingName(pszPath, pbc, riid, ppv); +} + +/* static */ +HRESULT +WinUtils::SHGetKnownFolderPath(REFKNOWNFOLDERID rfid, + DWORD dwFlags, + HANDLE hToken, + PWSTR *ppszPath) +{ + if (sGetKnownFolderPath) { + return sGetKnownFolderPath(rfid, dwFlags, hToken, ppszPath); + } + + if (!sShellDll) { + sShellDll = ::LoadLibraryW(kShellLibraryName); + if (!sShellDll) { + return false; + } + } + + sGetKnownFolderPath = (SHGetKnownFolderPathPtr) + GetProcAddress(sShellDll, "SHGetKnownFolderPath"); + if (!sGetKnownFolderPath) + return E_FAIL; + + return sGetKnownFolderPath(rfid, dwFlags, hToken, ppszPath); +} + +static BOOL +WINAPI EnumFirstChild(HWND hwnd, LPARAM lParam) +{ + *((HWND*)lParam) = hwnd; + return FALSE; +} + +/* static */ +void +WinUtils::InvalidatePluginAsWorkaround(nsIWidget* aWidget, + const LayoutDeviceIntRect& aRect) +{ + aWidget->Invalidate(aRect); + + // XXX - Even more evil workaround!! See bug 762948, flash's bottom + // level sandboxed window doesn't seem to get our invalidate. We send + // an invalidate to it manually. This is totally specialized for this + // bug, for other child window structures this will just be a more or + // less bogus invalidate but since that should not have any bad + // side-effects this will have to do for now. + HWND current = (HWND)aWidget->GetNativeData(NS_NATIVE_WINDOW); + + RECT windowRect; + RECT parentRect; + + ::GetWindowRect(current, &parentRect); + + HWND next = current; + do { + current = next; + ::EnumChildWindows(current, &EnumFirstChild, (LPARAM)&next); + ::GetWindowRect(next, &windowRect); + // This is relative to the screen, adjust it to be relative to the + // window we're reconfiguring. + windowRect.left -= parentRect.left; + windowRect.top -= parentRect.top; + } while (next != current && windowRect.top == 0 && windowRect.left == 0); + + if (windowRect.top == 0 && windowRect.left == 0) { + RECT rect; + rect.left = aRect.x; + rect.top = aRect.y; + rect.right = aRect.XMost(); + rect.bottom = aRect.YMost(); + + ::InvalidateRect(next, &rect, FALSE); + } +} + +#ifdef MOZ_PLACES +/************************************************************************ + * Constructs as AsyncFaviconDataReady Object + * @param aIOThread : the thread which performs the action + * @param aURLShortcut : Differentiates between (false)Jumplistcache and (true)Shortcutcache + ************************************************************************/ + +AsyncFaviconDataReady::AsyncFaviconDataReady(nsIURI *aNewURI, + nsCOMPtr<nsIThread> &aIOThread, + const bool aURLShortcut): + mNewURI(aNewURI), + mIOThread(aIOThread), + mURLShortcut(aURLShortcut) +{ +} + +NS_IMETHODIMP +myDownloadObserver::OnDownloadComplete(nsIDownloader *downloader, + nsIRequest *request, + nsISupports *ctxt, + nsresult status, + nsIFile *result) +{ + return NS_OK; +} + +nsresult AsyncFaviconDataReady::OnFaviconDataNotAvailable(void) +{ + if (!mURLShortcut) { + return NS_OK; + } + + nsCOMPtr<nsIFile> icoFile; + nsresult rv = FaviconHelper::GetOutputIconPath(mNewURI, icoFile, mURLShortcut); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIURI> mozIconURI; + rv = NS_NewURI(getter_AddRefs(mozIconURI), "moz-icon://.html?size=32"); + if (NS_FAILED(rv)) { + return rv; + } + + nsCOMPtr<nsIChannel> channel; + rv = NS_NewChannel(getter_AddRefs(channel), + mozIconURI, + nsContentUtils::GetSystemPrincipal(), + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, + nsIContentPolicy::TYPE_INTERNAL_IMAGE); + + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIDownloadObserver> downloadObserver = new myDownloadObserver; + nsCOMPtr<nsIStreamListener> listener; + rv = NS_NewDownloader(getter_AddRefs(listener), downloadObserver, icoFile); + NS_ENSURE_SUCCESS(rv, rv); + + return channel->AsyncOpen2(listener); +} + +NS_IMETHODIMP +AsyncFaviconDataReady::OnComplete(nsIURI *aFaviconURI, + uint32_t aDataLen, + const uint8_t *aData, + const nsACString &aMimeType) +{ + if (!aDataLen || !aData) { + if (mURLShortcut) { + OnFaviconDataNotAvailable(); + } + + return NS_OK; + } + + nsCOMPtr<nsIFile> icoFile; + nsresult rv = FaviconHelper::GetOutputIconPath(mNewURI, icoFile, mURLShortcut); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString path; + rv = icoFile->GetPath(path); + NS_ENSURE_SUCCESS(rv, rv); + + // Convert the obtained favicon data to an input stream + nsCOMPtr<nsIInputStream> stream; + rv = NS_NewByteInputStream(getter_AddRefs(stream), + reinterpret_cast<const char*>(aData), + aDataLen, + NS_ASSIGNMENT_DEPEND); + NS_ENSURE_SUCCESS(rv, rv); + + // Decode the image from the format it was returned to us in (probably PNG) + nsCOMPtr<imgIContainer> container; + nsCOMPtr<imgITools> imgtool = do_CreateInstance("@mozilla.org/image/tools;1"); + rv = imgtool->DecodeImageData(stream, aMimeType, + getter_AddRefs(container)); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr<SourceSurface> surface = + container->GetFrame(imgIContainer::FRAME_FIRST, + imgIContainer::FLAG_SYNC_DECODE | + imgIContainer::FLAG_ASYNC_NOTIFY); + NS_ENSURE_TRUE(surface, NS_ERROR_FAILURE); + + RefPtr<DataSourceSurface> dataSurface; + IntSize size; + + if (mURLShortcut) { + // Create a 48x48 surface and paint the icon into the central 16x16 rect. + size.width = 48; + size.height = 48; + dataSurface = + Factory::CreateDataSourceSurface(size, SurfaceFormat::B8G8R8A8); + NS_ENSURE_TRUE(dataSurface, NS_ERROR_FAILURE); + + DataSourceSurface::MappedSurface map; + if (!dataSurface->Map(DataSourceSurface::MapType::WRITE, &map)) { + return NS_ERROR_FAILURE; + } + + RefPtr<DrawTarget> dt = + Factory::CreateDrawTargetForData(BackendType::CAIRO, + map.mData, + dataSurface->GetSize(), + map.mStride, + dataSurface->GetFormat()); + if (!dt) { + gfxWarning() << "AsyncFaviconDataReady::OnComplete failed in CreateDrawTargetForData"; + return NS_ERROR_OUT_OF_MEMORY; + } + dt->FillRect(Rect(0, 0, size.width, size.height), + ColorPattern(Color(1.0f, 1.0f, 1.0f, 1.0f))); + dt->DrawSurface(surface, + Rect(16, 16, 16, 16), + Rect(Point(0, 0), + Size(surface->GetSize().width, surface->GetSize().height))); + + dataSurface->Unmap(); + } else { + // By using the input image surface's size, we may end up encoding + // to a different size than a 16x16 (or bigger for higher DPI) ICO, but + // Windows will resize appropriately for us. If we want to encode ourselves + // one day because we like our resizing better, we'd have to manually + // resize the image here and use GetSystemMetrics w/ SM_CXSMICON and + // SM_CYSMICON. We don't support resizing images asynchronously at the + // moment anyway so getting the DPI aware icon size won't help. + size.width = surface->GetSize().width; + size.height = surface->GetSize().height; + dataSurface = surface->GetDataSurface(); + NS_ENSURE_TRUE(dataSurface, NS_ERROR_FAILURE); + } + + // Allocate a new buffer that we own and can use out of line in + // another thread. + UniquePtr<uint8_t[]> data = SurfaceToPackedBGRA(dataSurface); + if (!data) { + return NS_ERROR_OUT_OF_MEMORY; + } + int32_t stride = 4 * size.width; + + // AsyncEncodeAndWriteIcon takes ownership of the heap allocated buffer + nsCOMPtr<nsIRunnable> event = new AsyncEncodeAndWriteIcon(path, Move(data), + stride, + size.width, + size.height, + mURLShortcut); + mIOThread->Dispatch(event, NS_DISPATCH_NORMAL); + + return NS_OK; +} +#endif + +// Warning: AsyncEncodeAndWriteIcon assumes ownership of the aData buffer passed in +AsyncEncodeAndWriteIcon::AsyncEncodeAndWriteIcon(const nsAString &aIconPath, + UniquePtr<uint8_t[]> aBuffer, + uint32_t aStride, + uint32_t aWidth, + uint32_t aHeight, + const bool aURLShortcut) : + mURLShortcut(aURLShortcut), + mIconPath(aIconPath), + mBuffer(Move(aBuffer)), + mStride(aStride), + mWidth(aWidth), + mHeight(aHeight) +{ +} + +NS_IMETHODIMP AsyncEncodeAndWriteIcon::Run() +{ + NS_PRECONDITION(!NS_IsMainThread(), "Should not be called on the main thread."); + + // Note that since we're off the main thread we can't use + // gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget() + RefPtr<DataSourceSurface> surface = + Factory::CreateWrappingDataSourceSurface(mBuffer.get(), mStride, + IntSize(mWidth, mHeight), + SurfaceFormat::B8G8R8A8); + + FILE* file = fopen(NS_ConvertUTF16toUTF8(mIconPath).get(), "wb"); + if (!file) { + // Maybe the directory doesn't exist; try creating it, then fopen again. + nsresult rv = NS_ERROR_FAILURE; + nsCOMPtr<nsIFile> comFile = do_CreateInstance("@mozilla.org/file/local;1"); + if (comFile) { + //NS_ConvertUTF8toUTF16 utf16path(mIconPath); + rv = comFile->InitWithPath(mIconPath); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIFile> dirPath; + comFile->GetParent(getter_AddRefs(dirPath)); + if (dirPath) { + rv = dirPath->Create(nsIFile::DIRECTORY_TYPE, 0777); + if (NS_SUCCEEDED(rv) || rv == NS_ERROR_FILE_ALREADY_EXISTS) { + file = fopen(NS_ConvertUTF16toUTF8(mIconPath).get(), "wb"); + if (!file) { + rv = NS_ERROR_FAILURE; + } + } + } + } + } + if (!file) { + return rv; + } + } + nsresult rv = + gfxUtils::EncodeSourceSurface(surface, + NS_LITERAL_CSTRING("image/vnd.microsoft.icon"), + EmptyString(), + gfxUtils::eBinaryEncode, + file); + fclose(file); + NS_ENSURE_SUCCESS(rv, rv); + + if (mURLShortcut) { + SendNotifyMessage(HWND_BROADCAST, WM_SETTINGCHANGE, SPI_SETNONCLIENTMETRICS, 0); + } + return rv; +} + +AsyncEncodeAndWriteIcon::~AsyncEncodeAndWriteIcon() +{ +} + +AsyncDeleteIconFromDisk::AsyncDeleteIconFromDisk(const nsAString &aIconPath) + : mIconPath(aIconPath) +{ +} + +NS_IMETHODIMP AsyncDeleteIconFromDisk::Run() +{ + // Construct the parent path of the passed in path + nsCOMPtr<nsIFile> icoFile = do_CreateInstance("@mozilla.org/file/local;1"); + NS_ENSURE_TRUE(icoFile, NS_ERROR_FAILURE); + nsresult rv = icoFile->InitWithPath(mIconPath); + NS_ENSURE_SUCCESS(rv, rv); + + // Check if the cached ICO file exists + bool exists; + rv = icoFile->Exists(&exists); + NS_ENSURE_SUCCESS(rv, rv); + + // Check that we aren't deleting some arbitrary file that is not an icon + if (StringTail(mIconPath, 4).LowerCaseEqualsASCII(".ico")) { + // Check if the cached ICO file exists + bool exists; + if (NS_FAILED(icoFile->Exists(&exists)) || !exists) + return NS_ERROR_FAILURE; + + // We found an ICO file that exists, so we should remove it + icoFile->Remove(false); + } + + return NS_OK; +} + +AsyncDeleteIconFromDisk::~AsyncDeleteIconFromDisk() +{ +} + +AsyncDeleteAllFaviconsFromDisk:: + AsyncDeleteAllFaviconsFromDisk(bool aIgnoreRecent) + : mIgnoreRecent(aIgnoreRecent) +{ + // We can't call FaviconHelper::GetICOCacheSecondsTimeout() on non-main + // threads, as it reads a pref, so cache its value here. + mIcoNoDeleteSeconds = FaviconHelper::GetICOCacheSecondsTimeout() + 600; +} + +NS_IMETHODIMP AsyncDeleteAllFaviconsFromDisk::Run() +{ + // Construct the path of our jump list cache + nsCOMPtr<nsIFile> jumpListCacheDir; + nsresult rv = NS_GetSpecialDirectory("ProfLDS", + getter_AddRefs(jumpListCacheDir)); + NS_ENSURE_SUCCESS(rv, rv); + rv = jumpListCacheDir->AppendNative( + nsDependentCString(FaviconHelper::kJumpListCacheDir)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsISimpleEnumerator> entries; + rv = jumpListCacheDir->GetDirectoryEntries(getter_AddRefs(entries)); + NS_ENSURE_SUCCESS(rv, rv); + + // Loop through each directory entry and remove all ICO files found + do { + bool hasMore = false; + if (NS_FAILED(entries->HasMoreElements(&hasMore)) || !hasMore) + break; + + nsCOMPtr<nsISupports> supp; + if (NS_FAILED(entries->GetNext(getter_AddRefs(supp)))) + break; + + nsCOMPtr<nsIFile> currFile(do_QueryInterface(supp)); + nsAutoString path; + if (NS_FAILED(currFile->GetPath(path))) + continue; + + if (StringTail(path, 4).LowerCaseEqualsASCII(".ico")) { + // Check if the cached ICO file exists + bool exists; + if (NS_FAILED(currFile->Exists(&exists)) || !exists) + continue; + + if (mIgnoreRecent) { + // Check to make sure the icon wasn't just recently created. + // If it was created recently, don't delete it yet. + int64_t fileModTime = 0; + rv = currFile->GetLastModifiedTime(&fileModTime); + fileModTime /= PR_MSEC_PER_SEC; + // If the icon is older than the regeneration time (+ 10 min to be + // safe), then it's old and we can get rid of it. + // This code is only hit directly after a regeneration. + int64_t nowTime = PR_Now() / int64_t(PR_USEC_PER_SEC); + if (NS_FAILED(rv) || + (nowTime - fileModTime) < mIcoNoDeleteSeconds) { + continue; + } + } + + // We found an ICO file that exists, so we should remove it + currFile->Remove(false); + } + } while(true); + + return NS_OK; +} + +AsyncDeleteAllFaviconsFromDisk::~AsyncDeleteAllFaviconsFromDisk() +{ +} + + +/* + * (static) If the data is available, will return the path on disk where + * the favicon for page aFaviconPageURI is stored. If the favicon does not + * exist, or its cache is expired, this method will kick off an async request + * for the icon so that next time the method is called it will be available. + * @param aFaviconPageURI The URI of the page to obtain + * @param aICOFilePath The path of the icon file + * @param aIOThread The thread to perform the Fetch on + * @param aURLShortcut to distinguish between jumplistcache(false) and shortcutcache(true) + */ +nsresult FaviconHelper::ObtainCachedIconFile(nsCOMPtr<nsIURI> aFaviconPageURI, + nsString &aICOFilePath, + nsCOMPtr<nsIThread> &aIOThread, + bool aURLShortcut) +{ + // Obtain the ICO file path + nsCOMPtr<nsIFile> icoFile; + nsresult rv = GetOutputIconPath(aFaviconPageURI, icoFile, aURLShortcut); + NS_ENSURE_SUCCESS(rv, rv); + + // Check if the cached ICO file already exists + bool exists; + rv = icoFile->Exists(&exists); + NS_ENSURE_SUCCESS(rv, rv); + + if (exists) { + + // Obtain the file's last modification date in seconds + int64_t fileModTime = 0; + rv = icoFile->GetLastModifiedTime(&fileModTime); + fileModTime /= PR_MSEC_PER_SEC; + int32_t icoReCacheSecondsTimeout = GetICOCacheSecondsTimeout(); + int64_t nowTime = PR_Now() / int64_t(PR_USEC_PER_SEC); + + // If the last mod call failed or the icon is old then re-cache it + // This check is in case the favicon of a page changes + // the next time we try to build the jump list, the data will be available. + if (NS_FAILED(rv) || + (nowTime - fileModTime) > icoReCacheSecondsTimeout) { + CacheIconFileFromFaviconURIAsync(aFaviconPageURI, icoFile, aIOThread, aURLShortcut); + return NS_ERROR_NOT_AVAILABLE; + } + } else { + + // The file does not exist yet, obtain it async from the favicon service so that + // the next time we try to build the jump list it'll be available. + CacheIconFileFromFaviconURIAsync(aFaviconPageURI, icoFile, aIOThread, aURLShortcut); + return NS_ERROR_NOT_AVAILABLE; + } + + // The icoFile is filled with a path that exists, get its path + rv = icoFile->GetPath(aICOFilePath); + return rv; +} + +nsresult FaviconHelper::HashURI(nsCOMPtr<nsICryptoHash> &aCryptoHash, + nsIURI *aUri, + nsACString& aUriHash) +{ + if (!aUri) + return NS_ERROR_INVALID_ARG; + + nsAutoCString spec; + nsresult rv = aUri->GetSpec(spec); + NS_ENSURE_SUCCESS(rv, rv); + + if (!aCryptoHash) { + aCryptoHash = do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + } + + rv = aCryptoHash->Init(nsICryptoHash::MD5); + NS_ENSURE_SUCCESS(rv, rv); + rv = aCryptoHash->Update(reinterpret_cast<const uint8_t*>(spec.BeginReading()), + spec.Length()); + NS_ENSURE_SUCCESS(rv, rv); + rv = aCryptoHash->Finish(true, aUriHash); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + + + +// (static) Obtains the ICO file for the favicon at page aFaviconPageURI +// If successful, the file path on disk is in the format: +// <ProfLDS>\jumpListCache\<hash(aFaviconPageURI)>.ico +nsresult FaviconHelper::GetOutputIconPath(nsCOMPtr<nsIURI> aFaviconPageURI, + nsCOMPtr<nsIFile> &aICOFile, + bool aURLShortcut) +{ + // Hash the input URI and replace any / with _ + nsAutoCString inputURIHash; + nsCOMPtr<nsICryptoHash> cryptoHash; + nsresult rv = HashURI(cryptoHash, aFaviconPageURI, + inputURIHash); + NS_ENSURE_SUCCESS(rv, rv); + char* cur = inputURIHash.BeginWriting(); + char* end = inputURIHash.EndWriting(); + for (; cur < end; ++cur) { + if ('/' == *cur) { + *cur = '_'; + } + } + + // Obtain the local profile directory and construct the output icon file path + rv = NS_GetSpecialDirectory("ProfLDS", getter_AddRefs(aICOFile)); + NS_ENSURE_SUCCESS(rv, rv); + if (!aURLShortcut) + rv = aICOFile->AppendNative(nsDependentCString(kJumpListCacheDir)); + else + rv = aICOFile->AppendNative(nsDependentCString(kShortcutCacheDir)); + NS_ENSURE_SUCCESS(rv, rv); + + // Append the icon extension + inputURIHash.AppendLiteral(".ico"); + rv = aICOFile->AppendNative(inputURIHash); + + return rv; +} + +// (static) Asynchronously creates a cached ICO file on disk for the favicon of +// page aFaviconPageURI and stores it to disk at the path of aICOFile. +nsresult + FaviconHelper::CacheIconFileFromFaviconURIAsync(nsCOMPtr<nsIURI> aFaviconPageURI, + nsCOMPtr<nsIFile> aICOFile, + nsCOMPtr<nsIThread> &aIOThread, + bool aURLShortcut) +{ +#ifdef MOZ_PLACES + // Obtain the favicon service and get the favicon for the specified page + nsCOMPtr<mozIAsyncFavicons> favIconSvc( + do_GetService("@mozilla.org/browser/favicon-service;1")); + NS_ENSURE_TRUE(favIconSvc, NS_ERROR_FAILURE); + + nsCOMPtr<nsIFaviconDataCallback> callback = + new mozilla::widget::AsyncFaviconDataReady(aFaviconPageURI, + aIOThread, + aURLShortcut); + + favIconSvc->GetFaviconDataForPage(aFaviconPageURI, callback); +#endif + return NS_OK; +} + +// Obtains the jump list 'ICO cache timeout in seconds' pref +int32_t FaviconHelper::GetICOCacheSecondsTimeout() { + + // Only obtain the setting at most once from the pref service. + // In the rare case that 2 threads call this at the same + // time it is no harm and we will simply obtain the pref twice. + // None of the taskbar list prefs are currently updated via a + // pref observer so I think this should suffice. + const int32_t kSecondsPerDay = 86400; + static bool alreadyObtained = false; + static int32_t icoReCacheSecondsTimeout = kSecondsPerDay; + if (alreadyObtained) { + return icoReCacheSecondsTimeout; + } + + // Obtain the pref + const char PREF_ICOTIMEOUT[] = "browser.taskbar.lists.icoTimeoutInSeconds"; + icoReCacheSecondsTimeout = Preferences::GetInt(PREF_ICOTIMEOUT, + kSecondsPerDay); + alreadyObtained = true; + return icoReCacheSecondsTimeout; +} + + + + +/* static */ +bool +WinUtils::GetShellItemPath(IShellItem* aItem, + nsString& aResultString) +{ + NS_ENSURE_TRUE(aItem, false); + LPWSTR str = nullptr; + if (FAILED(aItem->GetDisplayName(SIGDN_FILESYSPATH, &str))) + return false; + aResultString.Assign(str); + CoTaskMemFree(str); + return !aResultString.IsEmpty(); +} + +/* static */ +nsIntRegion +WinUtils::ConvertHRGNToRegion(HRGN aRgn) +{ + NS_ASSERTION(aRgn, "Don't pass NULL region here"); + + nsIntRegion rgn; + + DWORD size = ::GetRegionData(aRgn, 0, nullptr); + AutoTArray<uint8_t,100> buffer; + buffer.SetLength(size); + + RGNDATA* data = reinterpret_cast<RGNDATA*>(buffer.Elements()); + if (!::GetRegionData(aRgn, size, data)) + return rgn; + + if (data->rdh.nCount > MAX_RECTS_IN_REGION) { + rgn = ToIntRect(data->rdh.rcBound); + return rgn; + } + + RECT* rects = reinterpret_cast<RECT*>(data->Buffer); + for (uint32_t i = 0; i < data->rdh.nCount; ++i) { + RECT* r = rects + i; + rgn.Or(rgn, ToIntRect(*r)); + } + + return rgn; +} + +nsIntRect +WinUtils::ToIntRect(const RECT& aRect) +{ + return nsIntRect(aRect.left, aRect.top, + aRect.right - aRect.left, + aRect.bottom - aRect.top); +} + +/* static */ +bool +WinUtils::IsIMEEnabled(const InputContext& aInputContext) +{ + return IsIMEEnabled(aInputContext.mIMEState.mEnabled); +} + +/* static */ +bool +WinUtils::IsIMEEnabled(IMEState::Enabled aIMEState) +{ + return (aIMEState == IMEState::ENABLED || + aIMEState == IMEState::PLUGIN); +} + +/* static */ +void +WinUtils::SetupKeyModifiersSequence(nsTArray<KeyPair>* aArray, + uint32_t aModifiers) +{ + for (uint32_t i = 0; i < ArrayLength(sModifierKeyMap); ++i) { + const uint32_t* map = sModifierKeyMap[i]; + if (aModifiers & map[0]) { + aArray->AppendElement(KeyPair(map[1], map[2])); + } + } +} + +/* static */ +bool +WinUtils::ShouldHideScrollbars() +{ + return false; +} + +// This is in use here and in dom/events/TouchEvent.cpp +/* static */ +uint32_t +WinUtils::IsTouchDeviceSupportPresent() +{ + int32_t touchCapabilities = ::GetSystemMetrics(SM_DIGITIZER); + return (touchCapabilities & NID_READY) && + (touchCapabilities & (NID_EXTERNAL_TOUCH | NID_INTEGRATED_TOUCH)); +} + +/* static */ +uint32_t +WinUtils::GetMaxTouchPoints() +{ + if (IsWin7OrLater() && IsTouchDeviceSupportPresent()) { + return GetSystemMetrics(SM_MAXIMUMTOUCHES); + } + return 0; +} + +typedef DWORD (WINAPI * GetFinalPathNameByHandlePtr)(HANDLE hFile, + LPTSTR lpszFilePath, + DWORD cchFilePath, + DWORD dwFlags); + +/* static */ +bool +WinUtils::ResolveJunctionPointsAndSymLinks(std::wstring& aPath) +{ + // Users folder was introduced with Vista. + if (!IsVistaOrLater()) { + return true; + } + + wchar_t path[MAX_PATH] = { 0 }; + + nsAutoHandle handle( + ::CreateFileW(aPath.c_str(), + 0, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + nullptr, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, + nullptr)); + + if (handle == INVALID_HANDLE_VALUE) { + return false; + } + + // GetFinalPathNameByHandleW is a Vista and later API. Since ESR builds with + // XP support still, we need to load the function manually. + GetFinalPathNameByHandlePtr getFinalPathNameFnPtr = nullptr; + HMODULE kernel32Dll = ::GetModuleHandleW(L"Kernel32"); + if (kernel32Dll) { + getFinalPathNameFnPtr = (GetFinalPathNameByHandlePtr) + ::GetProcAddress(kernel32Dll, "GetFinalPathNameByHandleW"); + } + + if (!getFinalPathNameFnPtr) { + return false; + } + + DWORD pathLen = getFinalPathNameFnPtr( + handle, path, MAX_PATH, FILE_NAME_NORMALIZED | VOLUME_NAME_DOS); + if (pathLen == 0 || pathLen >= MAX_PATH) { + return false; + } + aPath = path; + + // GetFinalPathNameByHandle sticks a '\\?\' in front of the path, + // but that confuses some APIs so strip it off. It will also put + // '\\?\UNC\' in front of network paths, we convert that to '\\'. + if (aPath.compare(0, 7, L"\\\\?\\UNC") == 0) { + aPath.erase(2, 6); + } else if (aPath.compare(0, 4, L"\\\\?\\") == 0) { + aPath.erase(0, 4); + } + + return true; +} + +/* static */ +bool +WinUtils::SanitizePath(const wchar_t* aInputPath, nsAString& aOutput) +{ + aOutput.Truncate(); + wchar_t buffer[MAX_PATH + 1] = {0}; + if (!PathCanonicalizeW(buffer, aInputPath)) { + return false; + } + wchar_t longBuffer[MAX_PATH + 1] = {0}; + DWORD longResult = GetLongPathNameW(buffer, longBuffer, MAX_PATH); + if (longResult == 0 || longResult > MAX_PATH - 1) { + return false; + } + aOutput.SetLength(MAX_PATH + 1); + wchar_t* output = reinterpret_cast<wchar_t*>(aOutput.BeginWriting()); + if (!PathUnExpandEnvStringsW(longBuffer, output, MAX_PATH)) { + return false; + } + // Truncate to correct length + aOutput.Truncate(wcslen(char16ptr_t(aOutput.BeginReading()))); + MOZ_ASSERT(aOutput.Length() <= MAX_PATH); + return true; +} + +/** + * This function provides an array of (system path, substitution) pairs that are + * considered to be acceptable with respect to privacy, for the purposes of + * submitting within telemetry or crash reports. + * + * The substitution string's void flag may be set. If it is, no subsitution is + * necessary. Otherwise, the consumer should replace the system path with the + * substitution. + * + * @see GetAppInitDLLs for an example of its usage. + */ +/* static */ +void +WinUtils::GetWhitelistedPaths( + nsTArray<mozilla::Pair<nsString,nsDependentString>>& aOutput) +{ + aOutput.Clear(); + aOutput.AppendElement(mozilla::MakePair( + nsString(NS_LITERAL_STRING("%ProgramFiles%")), + nsDependentString())); + // When no substitution is required, set the void flag + aOutput.LastElement().second().SetIsVoid(true); + wchar_t tmpPath[MAX_PATH + 1] = {0}; + if (GetTempPath(MAX_PATH, tmpPath)) { + // GetTempPath's result always ends with a backslash, which we don't want + uint32_t tmpPathLen = wcslen(tmpPath); + if (tmpPathLen) { + tmpPath[tmpPathLen - 1] = 0; + } + nsAutoString cleanTmpPath; + if (SanitizePath(tmpPath, cleanTmpPath)) { + aOutput.AppendElement(mozilla::MakePair(nsString(cleanTmpPath), + nsDependentString(L"%TEMP%"))); + } + } +} + +/** + * This function is located here (as opposed to nsSystemInfo or elsewhere) + * because we need to gather this information as early as possible during + * startup. + */ +/* static */ +bool +WinUtils::GetAppInitDLLs(nsAString& aOutput) +{ + aOutput.Truncate(); + HKEY hkey = NULL; + if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, + L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Windows", + 0, KEY_QUERY_VALUE, &hkey)) { + return false; + } + nsAutoRegKey key(hkey); + LONG status; + if (IsVistaOrLater()) { + const wchar_t kLoadAppInitDLLs[] = L"LoadAppInit_DLLs"; + DWORD loadAppInitDLLs = 0; + DWORD loadAppInitDLLsLen = sizeof(loadAppInitDLLs); + status = RegQueryValueExW(hkey, kLoadAppInitDLLs, nullptr, + nullptr, (LPBYTE)&loadAppInitDLLs, + &loadAppInitDLLsLen); + if (status != ERROR_SUCCESS) { + return false; + } + if (!loadAppInitDLLs) { + // If loadAppInitDLLs is zero then AppInit_DLLs is disabled. + // In this case we'll return true along with an empty output string. + return true; + } + } + DWORD numBytes = 0; + const wchar_t kAppInitDLLs[] = L"AppInit_DLLs"; + // Query for required buffer size + status = RegQueryValueExW(hkey, kAppInitDLLs, nullptr, nullptr, nullptr, + &numBytes); + if (status != ERROR_SUCCESS) { + return false; + } + // Allocate the buffer and query for the actual data + mozilla::UniquePtr<wchar_t[]> data = + mozilla::MakeUnique<wchar_t[]>(numBytes / sizeof(wchar_t)); + status = RegQueryValueExW(hkey, kAppInitDLLs, nullptr, + nullptr, (LPBYTE)data.get(), &numBytes); + if (status != ERROR_SUCCESS) { + return false; + } + nsTArray<mozilla::Pair<nsString,nsDependentString>> whitelistedPaths; + GetWhitelistedPaths(whitelistedPaths); + // For each token, split up the filename components and then check the + // name of the file. + const wchar_t kDelimiters[] = L", "; + wchar_t* tokenContext = nullptr; + wchar_t* token = wcstok_s(data.get(), kDelimiters, &tokenContext); + while (token) { + nsAutoString cleanPath; + // Since these paths are short paths originating from the registry, we need + // to canonicalize them, lengthen them, and sanitize them before we can + // check them against the whitelist + if (SanitizePath(token, cleanPath)) { + bool needsStrip = true; + for (uint32_t i = 0; i < whitelistedPaths.Length(); ++i) { + const nsString& testPath = whitelistedPaths[i].first(); + const nsDependentString& substitution = whitelistedPaths[i].second(); + if (StringBeginsWith(cleanPath, testPath, + nsCaseInsensitiveStringComparator())) { + if (!substitution.IsVoid()) { + cleanPath.Replace(0, testPath.Length(), substitution); + } + // Whitelisted paths may be used as-is provided that they have been + // previously sanitized. + needsStrip = false; + break; + } + } + if (!aOutput.IsEmpty()) { + aOutput += L";"; + } + // For non-whitelisted paths, we strip the path component and just leave + // the filename. + if (needsStrip) { + // nsLocalFile doesn't like non-absolute paths. Since these paths might + // contain environment variables instead of roots, we can't use it. + wchar_t tmpPath[MAX_PATH + 1] = {0}; + wcsncpy(tmpPath, cleanPath.get(), cleanPath.Length()); + PathStripPath(tmpPath); + aOutput += tmpPath; + } else { + aOutput += cleanPath; + } + } + token = wcstok_s(nullptr, kDelimiters, &tokenContext); + } + return true; +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/windows/WinUtils.h b/widget/windows/WinUtils.h new file mode 100644 index 0000000000..37469ce079 --- /dev/null +++ b/widget/windows/WinUtils.h @@ -0,0 +1,648 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_widget_WinUtils_h__ +#define mozilla_widget_WinUtils_h__ + +#include "nscore.h" +#include <windows.h> +#include <shobjidl.h> +#include <uxtheme.h> +#include <dwmapi.h> + +// Undo the windows.h damage +#undef GetMessage +#undef CreateEvent +#undef GetClassName +#undef GetBinaryType +#undef RemoveDirectory + +#include "nsString.h" +#include "nsRegion.h" +#include "nsRect.h" + +#include "nsIRunnable.h" +#include "nsICryptoHash.h" +#ifdef MOZ_PLACES +#include "nsIFaviconService.h" +#endif +#include "nsIDownloader.h" +#include "nsIURI.h" +#include "nsIWidget.h" +#include "nsIThread.h" + +#include "mozilla/Attributes.h" +#include "mozilla/EventForwards.h" +#include "mozilla/UniquePtr.h" + +/** + * NS_INLINE_DECL_IUNKNOWN_REFCOUNTING should be used for defining and + * implementing AddRef() and Release() of IUnknown interface. + * This depends on xpcom/glue/nsISupportsImpl.h. + */ + +#define NS_INLINE_DECL_IUNKNOWN_REFCOUNTING(_class) \ +public: \ + STDMETHODIMP_(ULONG) AddRef() \ + { \ + MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(_class) \ + MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt"); \ + NS_ASSERT_OWNINGTHREAD(_class); \ + ++mRefCnt; \ + NS_LOG_ADDREF(this, mRefCnt, #_class, sizeof(*this)); \ + return static_cast<ULONG>(mRefCnt.get()); \ + } \ + STDMETHODIMP_(ULONG) Release() \ + { \ + MOZ_ASSERT(int32_t(mRefCnt) > 0, \ + "Release called on object that has already been released!"); \ + NS_ASSERT_OWNINGTHREAD(_class); \ + --mRefCnt; \ + NS_LOG_RELEASE(this, mRefCnt, #_class); \ + if (mRefCnt == 0) { \ + NS_ASSERT_OWNINGTHREAD(_class); \ + mRefCnt = 1; /* stabilize */ \ + delete this; \ + return 0; \ + } \ + return static_cast<ULONG>(mRefCnt.get()); \ + } \ +protected: \ + nsAutoRefCnt mRefCnt; \ + NS_DECL_OWNINGTHREAD \ +public: + +class nsWindow; +class nsWindowBase; +struct KeyPair; + +#if !defined(DPI_AWARENESS_CONTEXT_DECLARED) && !defined(DPI_AWARENESS_CONTEXT_UNAWARE) + +DECLARE_HANDLE(DPI_AWARENESS_CONTEXT); + +typedef enum DPI_AWARENESS { + DPI_AWARENESS_INVALID = -1, + DPI_AWARENESS_UNAWARE = 0, + DPI_AWARENESS_SYSTEM_AWARE = 1, + DPI_AWARENESS_PER_MONITOR_AWARE = 2 +} DPI_AWARENESS; + +#define DPI_AWARENESS_CONTEXT_UNAWARE ((DPI_AWARENESS_CONTEXT)-1) +#define DPI_AWARENESS_CONTEXT_SYSTEM_AWARE ((DPI_AWARENESS_CONTEXT)-2) +#define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE ((DPI_AWARENESS_CONTEXT)-3) + +#define DPI_AWARENESS_CONTEXT_DECLARED +#endif // (DPI_AWARENESS_CONTEXT_DECLARED) + +typedef DPI_AWARENESS_CONTEXT(WINAPI * SetThreadDpiAwarenessContextProc)(DPI_AWARENESS_CONTEXT); +typedef BOOL(WINAPI * EnableNonClientDpiScalingProc)(HWND); + +namespace mozilla { +namespace widget { + +// Windows message debugging data +typedef struct { + const char * mStr; + UINT mId; +} EventMsgInfo; +extern EventMsgInfo gAllEvents[]; + +// More complete QS definitions for MsgWaitForMultipleObjects() and +// GetQueueStatus() that include newer win8 specific defines. + +#ifndef QS_RAWINPUT +#define QS_RAWINPUT 0x0400 +#endif + +#ifndef QS_TOUCH +#define QS_TOUCH 0x0800 +#define QS_POINTER 0x1000 +#endif + +#define MOZ_QS_ALLEVENT (QS_KEY | QS_MOUSEMOVE | QS_MOUSEBUTTON | \ + QS_POSTMESSAGE | QS_TIMER | QS_PAINT | \ + QS_SENDMESSAGE | QS_HOTKEY | \ + QS_ALLPOSTMESSAGE | QS_RAWINPUT | \ + QS_TOUCH | QS_POINTER) + +// Logging macros +#define LogFunction() mozilla::widget::WinUtils::Log(__FUNCTION__) +#define LogThread() mozilla::widget::WinUtils::Log("%s: IsMainThread:%d ThreadId:%X", __FUNCTION__, NS_IsMainThread(), GetCurrentThreadId()) +#define LogThis() mozilla::widget::WinUtils::Log("[%X] %s", this, __FUNCTION__) +#define LogException(e) mozilla::widget::WinUtils::Log("%s Exception:%s", __FUNCTION__, e->ToString()->Data()) +#define LogHRESULT(hr) mozilla::widget::WinUtils::Log("%s hr=%X", __FUNCTION__, hr) + +#ifdef MOZ_PLACES +class myDownloadObserver final : public nsIDownloadObserver +{ + ~myDownloadObserver() {} + +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIDOWNLOADOBSERVER +}; +#endif + +class WinUtils +{ + // Function pointers for APIs that may not be available depending on + // the Win10 update version -- will be set up in Initialize(). + static SetThreadDpiAwarenessContextProc sSetThreadDpiAwarenessContext; + static EnableNonClientDpiScalingProc sEnableNonClientDpiScaling; + +public: + class AutoSystemDpiAware + { + public: + AutoSystemDpiAware() + { + if (sSetThreadDpiAwarenessContext) { + mPrevContext = sSetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_SYSTEM_AWARE); + } + } + + ~AutoSystemDpiAware() + { + if (sSetThreadDpiAwarenessContext) { + sSetThreadDpiAwarenessContext(mPrevContext); + } + } + + private: + DPI_AWARENESS_CONTEXT mPrevContext; + }; + + // Wrapper for DefWindowProc that will enable non-client dpi scaling on the + // window during creation. + static LRESULT WINAPI + NonClientDpiScalingDefWindowProcW(HWND hWnd, UINT msg, + WPARAM wParam, LPARAM lParam); + + /** + * Get the system's default logical-to-physical DPI scaling factor, + * which is based on the primary display. Note however that unlike + * LogToPhysFactor(GetPrimaryMonitor()), this will not change during + * a session even if the displays are reconfigured. This scale factor + * is used by Windows theme metrics etc, which do not fully support + * dynamic resolution changes but are only updated on logout. + */ + static double SystemScaleFactor(); + + static bool IsPerMonitorDPIAware(); + /** + * Functions to convert between logical pixels as used by most Windows APIs + * and physical (device) pixels. + */ + static double LogToPhysFactor(HMONITOR aMonitor); + static double LogToPhysFactor(HWND aWnd) { + // if there's an ancestor window, we want to share its DPI setting + HWND ancestor = ::GetAncestor(aWnd, GA_ROOTOWNER); + return LogToPhysFactor(::MonitorFromWindow(ancestor ? ancestor : aWnd, + MONITOR_DEFAULTTOPRIMARY)); + } + static double LogToPhysFactor(HDC aDC) { + return LogToPhysFactor(::WindowFromDC(aDC)); + } + static int32_t LogToPhys(HMONITOR aMonitor, double aValue); + static HMONITOR GetPrimaryMonitor(); + static HMONITOR MonitorFromRect(const gfx::Rect& rect); + + /** + * Logging helpers that dump output to prlog module 'Widget', console, and + * OutputDebugString. Note these output in both debug and release builds. + */ + static void Log(const char *fmt, ...); + static void LogW(const wchar_t *fmt, ...); + + /** + * PeekMessage() and GetMessage() are wrapper methods for PeekMessageW(), + * GetMessageW(), ITfMessageMgr::PeekMessageW() and + * ITfMessageMgr::GetMessageW(). + * Don't call the native APIs directly. You MUST use these methods instead. + */ + static bool PeekMessage(LPMSG aMsg, HWND aWnd, UINT aFirstMessage, + UINT aLastMessage, UINT aOption); + static bool GetMessage(LPMSG aMsg, HWND aWnd, UINT aFirstMessage, + UINT aLastMessage); + + /** + * Wait until a message is ready to be processed. + * Prefer using this method to directly calling ::WaitMessage since + * ::WaitMessage will wait if there is an unread message in the queue. + * That can cause freezes until another message enters the queue if the + * message is marked read by a call to PeekMessage which the caller is + * not aware of (e.g., from a different thread). + * Note that this method may cause sync dispatch of sent (as opposed to + * posted) messages. + * @param aTimeoutMs Timeout for waiting in ms, defaults to INFINITE + */ + static void WaitForMessage(DWORD aTimeoutMs = INFINITE); + + /** + * Gets the value of a string-typed registry value. + * + * @param aRoot The registry root to search in. + * @param aKeyName The name of the registry key to open. + * @param aValueName The name of the registry value in the specified key whose + * value is to be retrieved. Can be null, to retrieve the key's unnamed/ + * default value. + * @param aBuffer The buffer into which to store the string value. Can be + * null, in which case the return value indicates just whether the value + * exists. + * @param aBufferLength The size of aBuffer, in bytes. + * @return Whether the value exists and is a string. + */ + static bool GetRegistryKey(HKEY aRoot, + char16ptr_t aKeyName, + char16ptr_t aValueName, + wchar_t* aBuffer, + DWORD aBufferLength); + + /** + * Checks whether the registry key exists in either 32bit or 64bit branch on + * the environment. + * + * @param aRoot The registry root of aName. + * @param aKeyName The name of the registry key to check. + * @return TRUE if it exists and is readable. Otherwise, FALSE. + */ + static bool HasRegistryKey(HKEY aRoot, + char16ptr_t aKeyName); + + /** + * GetTopLevelHWND() returns a window handle of the top level window which + * aWnd belongs to. Note that the result may not be our window, i.e., it + * may not be managed by nsWindow. + * + * See follwing table for the detail of the result window type. + * + * +-------------------------+-----------------------------------------------+ + * | | aStopIfNotPopup | + * +-------------------------+-----------------------+-----------------------+ + * | | TRUE | FALSE | + + +-----------------+-------+-----------------------+-----------------------+ + * | | | * an independent top level window | + * | | TRUE | * a pupup window (WS_POPUP) | + * | | | * an owned top level window (like dialog) | + * | aStopIfNotChild +-------+-----------------------+-----------------------+ + * | | | * independent window | * only an independent | + * | | FALSE | * non-popup-owned- | top level window | + * | | | window like dialog | | + * +-----------------+-------+-----------------------+-----------------------+ + */ + static HWND GetTopLevelHWND(HWND aWnd, + bool aStopIfNotChild = false, + bool aStopIfNotPopup = true); + + /** + * SetNSWindowBasePtr() associates an nsWindowBase to aWnd. If aWidget is + * nullptr, it dissociate any nsBaseWidget pointer from aWnd. + * GetNSWindowBasePtr() returns an nsWindowBase pointer which was associated by + * SetNSWindowBasePtr(). + * GetNSWindowPtr() is a legacy api for win32 nsWindow and should be avoided + * outside of nsWindow src. + */ + static bool SetNSWindowBasePtr(HWND aWnd, nsWindowBase* aWidget); + static nsWindowBase* GetNSWindowBasePtr(HWND aWnd); + static nsWindow* GetNSWindowPtr(HWND aWnd); + + /** + * GetMonitorCount() returns count of monitors on the environment. + */ + static int32_t GetMonitorCount(); + + /** + * IsOurProcessWindow() returns TRUE if aWnd belongs our process. + * Otherwise, FALSE. + */ + static bool IsOurProcessWindow(HWND aWnd); + + /** + * FindOurProcessWindow() returns the nearest ancestor window which + * belongs to our process. If it fails to find our process's window by the + * top level window, returns nullptr. And note that this is using + * ::GetParent() for climbing the window hierarchy, therefore, it gives + * up at an owned top level window except popup window (e.g., dialog). + */ + static HWND FindOurProcessWindow(HWND aWnd); + + /** + * FindOurWindowAtPoint() returns the topmost child window which belongs to + * our process's top level window. + * + * NOTE: the topmost child window may NOT be our process's window like a + * plugin's window. + */ + static HWND FindOurWindowAtPoint(const POINT& aPointInScreen); + + /** + * InitMSG() returns an MSG struct which was initialized by the params. + * Don't trust the other members in the result. + */ + static MSG InitMSG(UINT aMessage, WPARAM wParam, LPARAM lParam, HWND aWnd); + + /** + * GetScanCode() returns a scan code for the LPARAM of WM_KEYDOWN, WM_KEYUP, + * WM_CHAR and WM_UNICHAR. + * + */ + static WORD GetScanCode(LPARAM aLParam) + { + return (aLParam >> 16) & 0xFF; + } + + /** + * IsExtendedScanCode() returns TRUE if the LPARAM indicates the key message + * is an extended key event. + */ + static bool IsExtendedScanCode(LPARAM aLParam) + { + return (aLParam & 0x1000000) != 0; + } + + /** + * GetInternalMessage() converts a native message to an internal message. + * If there is no internal message for the given native message, returns + * the native message itself. + */ + static UINT GetInternalMessage(UINT aNativeMessage); + + /** + * GetNativeMessage() converts an internal message to a native message. + * If aInternalMessage is a native message, returns the native message itself. + */ + static UINT GetNativeMessage(UINT aInternalMessage); + + /** + * GetMouseInputSource() returns a pointing device information. The value is + * one of nsIDOMMouseEvent::MOZ_SOURCE_*. This method MUST be called during + * mouse message handling. + */ + static uint16_t GetMouseInputSource(); + + /** + * Windows also fires mouse window messages for pens and touches, so we should + * retrieve their pointer ID on receiving mouse events as well. Please refer to + * https://msdn.microsoft.com/en-us/library/windows/desktop/ms703320(v=vs.85).aspx + */ + static uint16_t GetMousePointerID(); + + static bool GetIsMouseFromTouch(EventMessage aEventType); + + /** + * SHCreateItemFromParsingName() calls native SHCreateItemFromParsingName() + * API which is available on Vista and up. + */ + static HRESULT SHCreateItemFromParsingName(PCWSTR pszPath, IBindCtx *pbc, + REFIID riid, void **ppv); + + /** + * SHGetKnownFolderPath() calls native SHGetKnownFolderPath() + * API which is available on Vista and up. + */ + static HRESULT SHGetKnownFolderPath(REFKNOWNFOLDERID rfid, + DWORD dwFlags, + HANDLE hToken, + PWSTR *ppszPath); + /** + * GetShellItemPath return the file or directory path of a shell item. + * Internally calls IShellItem's GetDisplayName. + * + * aItem the shell item containing the path. + * aResultString the resulting string path. + * returns true if a path was retreived. + */ + static bool GetShellItemPath(IShellItem* aItem, + nsString& aResultString); + + /** + * ConvertHRGNToRegion converts a Windows HRGN to an nsIntRegion. + * + * aRgn the HRGN to convert. + * returns the nsIntRegion. + */ + static nsIntRegion ConvertHRGNToRegion(HRGN aRgn); + + /** + * ToIntRect converts a Windows RECT to a nsIntRect. + * + * aRect the RECT to convert. + * returns the nsIntRect. + */ + static nsIntRect ToIntRect(const RECT& aRect); + + /** + * Helper used in invalidating flash plugin windows owned + * by low rights flash containers. + */ + static void InvalidatePluginAsWorkaround(nsIWidget* aWidget, + const LayoutDeviceIntRect& aRect); + + /** + * Returns true if the context or IME state is enabled. Otherwise, false. + */ + static bool IsIMEEnabled(const InputContext& aInputContext); + static bool IsIMEEnabled(IMEState::Enabled aIMEState); + + /** + * Returns modifier key array for aModifiers. This is for + * nsIWidget::SynthethizeNative*Event(). + */ + static void SetupKeyModifiersSequence(nsTArray<KeyPair>* aArray, + uint32_t aModifiers); + + /** + * Does device have touch support + */ + static uint32_t IsTouchDeviceSupportPresent(); + + /** + * The maximum number of simultaneous touch contacts supported by the device. + * In the case of devices with multiple digitizers (e.g. multiple touch screens), + * the value will be the maximum of the set of maximum supported contacts by + * each individual digitizer. + */ + static uint32_t GetMaxTouchPoints(); + + /** + * Fully resolves a path to its final path name. So if path contains + * junction points or symlinks to other folders, we'll resolve the path + * fully to the actual path that the links target. + * + * @param aPath path to be resolved. + * @return true if successful, including if nothing needs to be changed. + * false if something failed or aPath does not exist, aPath will + * remain unchanged. + */ + static bool ResolveJunctionPointsAndSymLinks(std::wstring& aPath); + + /** + * dwmapi.dll function typedefs and declarations + */ + typedef HRESULT (WINAPI*DwmExtendFrameIntoClientAreaProc)(HWND hWnd, const MARGINS *pMarInset); + typedef HRESULT (WINAPI*DwmIsCompositionEnabledProc)(BOOL *pfEnabled); + typedef HRESULT (WINAPI*DwmSetIconicThumbnailProc)(HWND hWnd, HBITMAP hBitmap, DWORD dwSITFlags); + typedef HRESULT (WINAPI*DwmSetIconicLivePreviewBitmapProc)(HWND hWnd, HBITMAP hBitmap, POINT *pptClient, DWORD dwSITFlags); + typedef HRESULT (WINAPI*DwmGetWindowAttributeProc)(HWND hWnd, DWORD dwAttribute, LPCVOID pvAttribute, DWORD cbAttribute); + typedef HRESULT (WINAPI*DwmSetWindowAttributeProc)(HWND hWnd, DWORD dwAttribute, LPCVOID pvAttribute, DWORD cbAttribute); + typedef HRESULT (WINAPI*DwmInvalidateIconicBitmapsProc)(HWND hWnd); + typedef HRESULT (WINAPI*DwmDefWindowProcProc)(HWND hWnd, UINT msg, LPARAM lParam, WPARAM wParam, LRESULT *aRetValue); + typedef HRESULT (WINAPI*DwmGetCompositionTimingInfoProc)(HWND hWnd, DWM_TIMING_INFO *info); + typedef HRESULT (WINAPI*DwmFlushProc)(void); + + static DwmExtendFrameIntoClientAreaProc dwmExtendFrameIntoClientAreaPtr; + static DwmIsCompositionEnabledProc dwmIsCompositionEnabledPtr; + static DwmSetIconicThumbnailProc dwmSetIconicThumbnailPtr; + static DwmSetIconicLivePreviewBitmapProc dwmSetIconicLivePreviewBitmapPtr; + static DwmGetWindowAttributeProc dwmGetWindowAttributePtr; + static DwmSetWindowAttributeProc dwmSetWindowAttributePtr; + static DwmInvalidateIconicBitmapsProc dwmInvalidateIconicBitmapsPtr; + static DwmDefWindowProcProc dwmDwmDefWindowProcPtr; + static DwmGetCompositionTimingInfoProc dwmGetCompositionTimingInfoPtr; + static DwmFlushProc dwmFlushProcPtr; + + static void Initialize(); + + static bool ShouldHideScrollbars(); + + /** + * This function normalizes the input path, converts short filenames to long + * filenames, and substitutes environment variables for system paths. + * The resulting output string length is guaranteed to be <= MAX_PATH. + */ + static bool SanitizePath(const wchar_t* aInputPath, nsAString& aOutput); + + /** + * Retrieve a semicolon-delimited list of DLL files derived from AppInit_DLLs + */ + static bool GetAppInitDLLs(nsAString& aOutput); + +#ifdef ACCESSIBILITY + static void SetAPCPending(); +#endif + +private: + typedef HRESULT (WINAPI * SHCreateItemFromParsingNamePtr)(PCWSTR pszPath, + IBindCtx *pbc, + REFIID riid, + void **ppv); + static SHCreateItemFromParsingNamePtr sCreateItemFromParsingName; + typedef HRESULT (WINAPI * SHGetKnownFolderPathPtr)(REFKNOWNFOLDERID rfid, + DWORD dwFlags, + HANDLE hToken, + PWSTR *ppszPath); + static SHGetKnownFolderPathPtr sGetKnownFolderPath; + + static void GetWhitelistedPaths( + nsTArray<mozilla::Pair<nsString,nsDependentString>>& aOutput); +}; + +#ifdef MOZ_PLACES +class AsyncFaviconDataReady final : public nsIFaviconDataCallback +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIFAVICONDATACALLBACK + + AsyncFaviconDataReady(nsIURI *aNewURI, + nsCOMPtr<nsIThread> &aIOThread, + const bool aURLShortcut); + nsresult OnFaviconDataNotAvailable(void); +private: + ~AsyncFaviconDataReady() {} + + nsCOMPtr<nsIURI> mNewURI; + nsCOMPtr<nsIThread> mIOThread; + const bool mURLShortcut; +}; +#endif + +/** + * Asynchronously tries add the list to the build + */ +class AsyncEncodeAndWriteIcon : public nsIRunnable +{ +public: + const bool mURLShortcut; + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIRUNNABLE + + // Warning: AsyncEncodeAndWriteIcon assumes ownership of the aData buffer passed in + AsyncEncodeAndWriteIcon(const nsAString &aIconPath, + UniquePtr<uint8_t[]> aData, + uint32_t aStride, uint32_t aWidth, uint32_t aHeight, + const bool aURLShortcut); + +private: + virtual ~AsyncEncodeAndWriteIcon(); + + nsAutoString mIconPath; + UniquePtr<uint8_t[]> mBuffer; + uint32_t mStride; + uint32_t mWidth; + uint32_t mHeight; +}; + + +class AsyncDeleteIconFromDisk : public nsIRunnable +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIRUNNABLE + + AsyncDeleteIconFromDisk(const nsAString &aIconPath); + +private: + virtual ~AsyncDeleteIconFromDisk(); + + nsAutoString mIconPath; +}; + +class AsyncDeleteAllFaviconsFromDisk : public nsIRunnable +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIRUNNABLE + + AsyncDeleteAllFaviconsFromDisk(bool aIgnoreRecent = false); + +private: + virtual ~AsyncDeleteAllFaviconsFromDisk(); + + int32_t mIcoNoDeleteSeconds; + bool mIgnoreRecent; +}; + +class FaviconHelper +{ +public: + static const char kJumpListCacheDir[]; + static const char kShortcutCacheDir[]; + static nsresult ObtainCachedIconFile(nsCOMPtr<nsIURI> aFaviconPageURI, + nsString &aICOFilePath, + nsCOMPtr<nsIThread> &aIOThread, + bool aURLShortcut); + + static nsresult HashURI(nsCOMPtr<nsICryptoHash> &aCryptoHash, + nsIURI *aUri, + nsACString& aUriHash); + + static nsresult GetOutputIconPath(nsCOMPtr<nsIURI> aFaviconPageURI, + nsCOMPtr<nsIFile> &aICOFile, + bool aURLShortcut); + + static nsresult + CacheIconFileFromFaviconURIAsync(nsCOMPtr<nsIURI> aFaviconPageURI, + nsCOMPtr<nsIFile> aICOFile, + nsCOMPtr<nsIThread> &aIOThread, + bool aURLShortcut); + + static int32_t GetICOCacheSecondsTimeout(); +}; + +} // namespace widget +} // namespace mozilla + +#endif // mozilla_widget_WinUtils_h__ diff --git a/widget/windows/WindowHook.cpp b/widget/windows/WindowHook.cpp new file mode 100644 index 0000000000..b2a3167d31 --- /dev/null +++ b/widget/windows/WindowHook.cpp @@ -0,0 +1,129 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "WindowHook.h" +#include "nsWindow.h" +#include "nsWindowDefs.h" + +namespace mozilla { +namespace widget { + +nsresult +WindowHook::AddHook(UINT nMsg, Callback callback, void *context) { + MessageData *data = LookupOrCreate(nMsg); + + if (!data) + return NS_ERROR_OUT_OF_MEMORY; + + // Ensure we don't overwrite another hook + NS_ENSURE_TRUE(nullptr == data->hook.cb, NS_ERROR_UNEXPECTED); + + data->hook = CallbackData(callback, context); + + return NS_OK; +} + +nsresult +WindowHook::RemoveHook(UINT nMsg, Callback callback, void *context) { + CallbackData cbdata(callback, context); + MessageData *data = Lookup(nMsg); + if (!data) + return NS_ERROR_UNEXPECTED; + if (data->hook != cbdata) + return NS_ERROR_UNEXPECTED; + data->hook = CallbackData(); + + DeleteIfEmpty(data); + return NS_OK; +} + +nsresult +WindowHook::AddMonitor(UINT nMsg, Callback callback, void *context) { + MessageData *data = LookupOrCreate(nMsg); + return (data && data->monitors.AppendElement(CallbackData(callback, context))) + ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +nsresult +WindowHook::RemoveMonitor(UINT nMsg, Callback callback, void *context) { + CallbackData cbdata(callback, context); + MessageData *data = Lookup(nMsg); + if (!data) + return NS_ERROR_UNEXPECTED; + CallbackDataArray::index_type idx = data->monitors.IndexOf(cbdata); + if (idx == CallbackDataArray::NoIndex) + return NS_ERROR_UNEXPECTED; + data->monitors.RemoveElementAt(idx); + DeleteIfEmpty(data); + return NS_OK; +} + +WindowHook::MessageData * +WindowHook::Lookup(UINT nMsg) { + MessageDataArray::index_type idx; + for (idx = 0; idx < mMessageData.Length(); idx++) { + MessageData &data = mMessageData[idx]; + if (data.nMsg == nMsg) + return &data; + } + return nullptr; +} + +WindowHook::MessageData * +WindowHook::LookupOrCreate(UINT nMsg) { + MessageData *data = Lookup(nMsg); + if (!data) { + data = mMessageData.AppendElement(); + + if (!data) + return nullptr; + + data->nMsg = nMsg; + } + return data; +} + +void +WindowHook::DeleteIfEmpty(MessageData *data) { + // Never remove a MessageData that has still a hook or monitor entries. + if (data->hook || !data->monitors.IsEmpty()) + return; + + MessageDataArray::index_type idx; + idx = data - mMessageData.Elements(); + NS_ASSERTION(idx < mMessageData.Length(), "Attempted to delete MessageData that doesn't belong to this array!"); + mMessageData.RemoveElementAt(idx); +} + +bool +WindowHook::Notify(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam, + MSGResult& aResult) +{ + MessageData *data = Lookup(nMsg); + if (!data) + return false; + + uint32_t length = data->monitors.Length(); + for (uint32_t midx = 0; midx < length; midx++) { + data->monitors[midx].Invoke(hWnd, nMsg, wParam, lParam, &aResult.mResult); + } + + aResult.mConsumed = + data->hook.Invoke(hWnd, nMsg, wParam, lParam, &aResult.mResult); + return aResult.mConsumed; +} + +bool +WindowHook::CallbackData::Invoke(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam, + LRESULT *aResult) { + if (!cb) + return false; + return cb(context, hWnd, msg, wParam, lParam, aResult); +} + +} // namespace widget +} // namespace mozilla + diff --git a/widget/windows/WindowHook.h b/widget/windows/WindowHook.h new file mode 100644 index 0000000000..69df4eb2fe --- /dev/null +++ b/widget/windows/WindowHook.h @@ -0,0 +1,81 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __mozilla_WindowHook_h__ +#define __mozilla_WindowHook_h__ + +#include <windows.h> + +#include <nsHashKeys.h> +#include <nsClassHashtable.h> +#include <nsTArray.h> + +#include "nsAppShell.h" + +class nsWindow; + +namespace mozilla { +namespace widget { + +struct MSGResult; + +class WindowHook { +public: + + // It is expected that most callbacks will return false + typedef bool (*Callback)(void *aContext, HWND hWnd, UINT nMsg, + WPARAM wParam, LPARAM lParam, LRESULT *aResult); + + nsresult AddHook(UINT nMsg, Callback callback, void *context); + nsresult RemoveHook(UINT nMsg, Callback callback, void *context); + nsresult AddMonitor(UINT nMsg, Callback callback, void *context); + nsresult RemoveMonitor(UINT nMsg, Callback callback, void *context); + +private: + struct CallbackData { + Callback cb; + void *context; + + CallbackData() : cb(nullptr), context(nullptr) {} + CallbackData(Callback cb, void *ctx) : cb(cb), context(ctx) {} + bool Invoke(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam, + LRESULT *aResult); + bool operator== (const CallbackData &rhs) const { + return cb == rhs.cb && context == rhs.context; + } + bool operator!= (const CallbackData &rhs) const { + return !(*this == rhs); + } + operator bool () const { + return !!cb; + } + }; + + typedef nsTArray<CallbackData> CallbackDataArray; + struct MessageData { + UINT nMsg; + CallbackData hook; + CallbackDataArray monitors; + }; + + bool Notify(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam, + MSGResult& aResult); + + MessageData *Lookup(UINT nMsg); + MessageData *LookupOrCreate(UINT nMsg); + void DeleteIfEmpty(MessageData *data); + + typedef nsTArray<MessageData> MessageDataArray; + MessageDataArray mMessageData; + + // For Notify + friend class ::nsWindow; +}; + +} +} + +#endif // __mozilla_WindowHook_h__ diff --git a/widget/windows/WindowsUIUtils.cpp b/widget/windows/WindowsUIUtils.cpp new file mode 100644 index 0000000000..2235118597 --- /dev/null +++ b/widget/windows/WindowsUIUtils.cpp @@ -0,0 +1,182 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <windows.h> +#include <winsdkver.h> +#include "mozwrlbase.h" +#include "nsServiceManagerUtils.h" + +#include "WindowsUIUtils.h" + +#include "nsIObserverService.h" +#include "nsIBaseWindow.h" +#include "nsIDocShell.h" +#include "nsIAppShellService.h" +#include "nsAppShellCID.h" +#include "nsIXULWindow.h" +#include "mozilla/Services.h" +#include "mozilla/WindowsVersion.h" +#include "nsString.h" +#include "nsIWidget.h" + +/* mingw currently doesn't support windows.ui.viewmanagement.h, so we disable it until it's fixed. */ +#ifndef __MINGW32__ + +#include <windows.ui.viewmanagement.h> + +#pragma comment(lib, "runtimeobject.lib") + +using namespace mozilla; +using namespace ABI::Windows::UI; +using namespace ABI::Windows::UI::ViewManagement; +using namespace Microsoft::WRL; +using namespace Microsoft::WRL::Wrappers; +using namespace ABI::Windows::Foundation; + +/* All of this is win10 stuff and we're compiling against win81 headers + * for now, so we may need to do some legwork: */ +#if WINVER_MAXVER < 0x0A00 +namespace ABI { + namespace Windows { + namespace UI { + namespace ViewManagement { + enum UserInteractionMode { + UserInteractionMode_Mouse = 0, + UserInteractionMode_Touch = 1 + }; + } + } + } +} + +#endif + +#ifndef RuntimeClass_Windows_UI_ViewManagement_UIViewSettings +#define RuntimeClass_Windows_UI_ViewManagement_UIViewSettings L"Windows.UI.ViewManagement.UIViewSettings" +#endif + +#if WINVER_MAXVER < 0x0A00 +namespace ABI { + namespace Windows { + namespace UI { + namespace ViewManagement { + interface IUIViewSettings; + MIDL_INTERFACE("C63657F6-8850-470D-88F8-455E16EA2C26") + IUIViewSettings : public IInspectable + { + public: + virtual HRESULT STDMETHODCALLTYPE get_UserInteractionMode(UserInteractionMode *value) = 0; + }; + + extern const __declspec(selectany) IID & IID_IUIViewSettings = __uuidof(IUIViewSettings); + } + } + } +} +#endif + +#ifndef IUIViewSettingsInterop + +typedef interface IUIViewSettingsInterop IUIViewSettingsInterop; + +MIDL_INTERFACE("3694dbf9-8f68-44be-8ff5-195c98ede8a6") +IUIViewSettingsInterop : public IInspectable +{ +public: + virtual HRESULT STDMETHODCALLTYPE GetForWindow(HWND hwnd, REFIID riid, void **ppv) = 0; +}; +#endif + +#endif + +WindowsUIUtils::WindowsUIUtils() : + mInTabletMode(eTabletModeUnknown) +{ +} + +WindowsUIUtils::~WindowsUIUtils() +{ +} + +/* + * Implement the nsISupports methods... + */ +NS_IMPL_ISUPPORTS(WindowsUIUtils, + nsIWindowsUIUtils) + +NS_IMETHODIMP +WindowsUIUtils::GetInTabletMode(bool* aResult) +{ + if (mInTabletMode == eTabletModeUnknown) { + UpdateTabletModeState(); + } + *aResult = mInTabletMode == eTabletModeOn; + return NS_OK; +} + +NS_IMETHODIMP +WindowsUIUtils::UpdateTabletModeState() +{ +#ifndef __MINGW32__ + if (!IsWin10OrLater()) { + return NS_OK; + } + + nsCOMPtr<nsIAppShellService> appShell(do_GetService(NS_APPSHELLSERVICE_CONTRACTID)); + nsCOMPtr<nsIXULWindow> hiddenWindow; + + nsresult rv = appShell->GetHiddenWindow(getter_AddRefs(hiddenWindow)); + if (NS_FAILED(rv)) { + return rv; + } + + nsCOMPtr<nsIDocShell> docShell; + rv = hiddenWindow->GetDocShell(getter_AddRefs(docShell)); + if (NS_FAILED(rv) || !docShell) { + return rv; + } + + nsCOMPtr<nsIBaseWindow> baseWindow(do_QueryInterface(docShell)); + + if (!baseWindow) + return NS_ERROR_FAILURE; + + nsCOMPtr<nsIWidget> widget; + baseWindow->GetMainWidget(getter_AddRefs(widget)); + + if (!widget) + return NS_ERROR_FAILURE; + + HWND winPtr = (HWND)widget->GetNativeData(NS_NATIVE_WINDOW); + ComPtr<IUIViewSettingsInterop> uiViewSettingsInterop; + + HRESULT hr = GetActivationFactory( + HStringReference(RuntimeClass_Windows_UI_ViewManagement_UIViewSettings).Get(), + &uiViewSettingsInterop); + if (SUCCEEDED(hr)) { + ComPtr<IUIViewSettings> uiViewSettings; + hr = uiViewSettingsInterop->GetForWindow(winPtr, IID_PPV_ARGS(&uiViewSettings)); + if (SUCCEEDED(hr)) { + UserInteractionMode mode; + hr = uiViewSettings->get_UserInteractionMode(&mode); + if (SUCCEEDED(hr)) { + TabletModeState oldTabletModeState = mInTabletMode; + mInTabletMode = (mode == UserInteractionMode_Touch) ? eTabletModeOn : eTabletModeOff; + if (mInTabletMode != oldTabletModeState) { + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + NS_NAMED_LITERAL_STRING(tabletMode, "tablet-mode"); + NS_NAMED_LITERAL_STRING(normalMode, "normal-mode"); + observerService->NotifyObservers(nullptr, "tablet-mode-change", + ((mInTabletMode == eTabletModeOn) ? tabletMode.get() : normalMode.get())); + } + } + } + } +#endif + + return NS_OK; +} + diff --git a/widget/windows/WindowsUIUtils.h b/widget/windows/WindowsUIUtils.h new file mode 100644 index 0000000000..a33c93a94f --- /dev/null +++ b/widget/windows/WindowsUIUtils.h @@ -0,0 +1,29 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_widget_WindowsUIUtils_h__ +#define mozilla_widget_WindowsUIUtils_h__ + +#include "nsIWindowsUIUtils.h" + +enum TabletModeState { + eTabletModeUnknown = 0, + eTabletModeOff, + eTabletModeOn +}; + +class WindowsUIUtils final : public nsIWindowsUIUtils { +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIWINDOWSUIUTILS + + WindowsUIUtils(); +protected: + ~WindowsUIUtils(); + + TabletModeState mInTabletMode; +}; + +#endif // mozilla_widget_WindowsUIUtils_h__ diff --git a/widget/windows/moz.build b/widget/windows/moz.build new file mode 100644 index 0000000000..d4f089eeac --- /dev/null +++ b/widget/windows/moz.build @@ -0,0 +1,123 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +TEST_DIRS += ['tests'] + +EXPORTS += [ + 'nsdefs.h', + 'WindowHook.h', + 'WinUtils.h', +] + +EXPORTS.mozilla.widget += [ + 'AudioSession.h', + 'CompositorWidgetChild.h', + 'CompositorWidgetParent.h', + 'InProcessWinCompositorWidget.h', + 'WinCompositorWidget.h', + 'WinMessages.h', + 'WinModifierKeyState.h', + 'WinNativeEventData.h', +] + +UNIFIED_SOURCES += [ + 'AudioSession.cpp', + 'CompositorWidgetChild.cpp', + 'CompositorWidgetParent.cpp', + 'GfxInfo.cpp', + 'IEnumFE.cpp', + 'IMMHandler.cpp', + 'InkCollector.cpp', + 'InProcessWinCompositorWidget.cpp', + 'JumpListItem.cpp', + 'KeyboardLayout.cpp', + 'nsAppShell.cpp', + 'nsClipboard.cpp', + 'nsColorPicker.cpp', + 'nsDataObj.cpp', + 'nsDataObjCollection.cpp', + 'nsDragService.cpp', + 'nsIdleServiceWin.cpp', + 'nsImageClipboard.cpp', + 'nsLookAndFeel.cpp', + 'nsNativeDragSource.cpp', + 'nsNativeDragTarget.cpp', + 'nsNativeThemeWin.cpp', + 'nsScreenManagerWin.cpp', + 'nsScreenWin.cpp', + 'nsSound.cpp', + 'nsToolkit.cpp', + 'nsUXThemeData.cpp', + 'nsWindow.cpp', + 'nsWindowBase.cpp', + 'nsWindowDbg.cpp', + 'nsWindowGfx.cpp', + 'nsWinGesture.cpp', + 'TaskbarPreview.cpp', + 'TaskbarPreviewButton.cpp', + 'TaskbarTabPreview.cpp', + 'TaskbarWindowPreview.cpp', + 'WidgetTraceEvent.cpp', + 'WindowHook.cpp', + 'WinIMEHandler.cpp', + 'WinTaskbar.cpp', + 'WinTextEventDispatcherListener.cpp', + 'WinUtils.cpp', +] + +# The following files cannot be built in unified mode because of name clashes. +SOURCES += [ + 'JumpListBuilder.cpp', + 'nsBidiKeyboard.cpp', + 'nsFilePicker.cpp', + 'nsWidgetFactory.cpp', + 'WinCompositorWidget.cpp', + 'WindowsUIUtils.cpp', + 'WinMouseScrollHandler.cpp', +] + +if CONFIG['MOZ_CRASHREPORTER']: + UNIFIED_SOURCES += [ + 'LSPAnnotator.cpp', + ] + +if CONFIG['NS_PRINTING']: + UNIFIED_SOURCES += [ + 'nsDeviceContextSpecWin.cpp', + 'nsPrintOptionsWin.cpp', + 'nsPrintSettingsWin.cpp', + ] + +if CONFIG['NS_ENABLE_TSF']: + SOURCES += [ + 'TSFTextStore.cpp', + ] + +include('/ipc/chromium/chromium-config.mozbuild') + +FINAL_LIBRARY = 'xul' + +LOCAL_INCLUDES += [ + '/layout/generic', + '/layout/xul', + '/toolkit/xre', + '/widget', + '/xpcom/base', +] + +DEFINES['MOZ_UNICODE'] = True + +for var in ('MOZ_ENABLE_D3D10_LAYER'): + if CONFIG[var]: + DEFINES[var] = True + +RESFILE = 'widget.res' + +CXXFLAGS += CONFIG['MOZ_CAIRO_CFLAGS'] + +OS_LIBS += [ + 'rpcrt4', +] diff --git a/widget/windows/mozwrlbase.h b/widget/windows/mozwrlbase.h new file mode 100644 index 0000000000..d82be8f043 --- /dev/null +++ b/widget/windows/mozwrlbase.h @@ -0,0 +1,77 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#pragma once + +/* + * Includes <wrl.h> and it's children. Defines imports needed by + * corewrappers.h in the case where windows.h has already been + * included w/WINVER < 0x600. Also ups WINVER/_WIN32_WINNT prior + * to including wrl.h. Mozilla's build currently has WINVER set to + * 0x502 for XP support. + */ + +#if _WIN32_WINNT < 0x600 + +#include <windows.h> + +VOID +WINAPI +ReleaseSRWLockExclusive( + _Inout_ PSRWLOCK SRWLock + ); + +VOID +WINAPI +ReleaseSRWLockShared( + _Inout_ PSRWLOCK SRWLock + ); + +BOOL +WINAPI +InitializeCriticalSectionEx( + _Out_ LPCRITICAL_SECTION lpCriticalSection, + _In_ DWORD dwSpinCount, + _In_ DWORD Flags + ); + +VOID +WINAPI +InitializeSRWLock( + _Out_ PSRWLOCK SRWLock + ); + +VOID +WINAPI +AcquireSRWLockExclusive( + _Inout_ PSRWLOCK SRWLock + ); + +BOOLEAN +WINAPI +TryAcquireSRWLockExclusive( + _Inout_ PSRWLOCK SRWLock + ); + +BOOLEAN +WINAPI +TryAcquireSRWLockShared( + _Inout_ PSRWLOCK SRWLock + ); + +VOID +WINAPI +AcquireSRWLockShared( + _Inout_ PSRWLOCK SRWLock + ); + +#undef WINVER +#undef _WIN32_WINNT +#define WINVER 0x600 +#define _WIN32_WINNT 0x600 + +#endif // _WIN32_WINNT < 0x600 + +#include <wrl.h> diff --git a/widget/windows/nsAppShell.cpp b/widget/windows/nsAppShell.cpp new file mode 100644 index 0000000000..c8820e7d17 --- /dev/null +++ b/widget/windows/nsAppShell.cpp @@ -0,0 +1,436 @@ +/* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/ipc/MessageChannel.h" +#include "mozilla/ipc/WindowsMessageLoop.h" +#include "nsAppShell.h" +#include "nsToolkit.h" +#include "nsThreadUtils.h" +#include "WinUtils.h" +#include "WinTaskbar.h" +#include "WinMouseScrollHandler.h" +#include "nsWindowDefs.h" +#include "nsString.h" +#include "WinIMEHandler.h" +#include "mozilla/widget/AudioSession.h" +#include "mozilla/HangMonitor.h" +#include "nsIDOMWakeLockListener.h" +#include "nsIPowerManagerService.h" +#include "mozilla/StaticPtr.h" +#include "nsTHashtable.h" +#include "nsHashKeys.h" +#include "GeckoProfiler.h" +#include "nsComponentManagerUtils.h" +#include "nsITimer.h" + +// These are two messages that the code in winspool.drv on Windows 7 explicitly +// waits for while it is pumping other Windows messages, during display of the +// Printer Properties dialog. +#define MOZ_WM_PRINTER_PROPERTIES_COMPLETION 0x5b7a +#define MOZ_WM_PRINTER_PROPERTIES_FAILURE 0x5b7f + +using namespace mozilla; +using namespace mozilla::widget; + +#define WAKE_LOCK_LOG(...) MOZ_LOG(GetWinWakeLockLog(), mozilla::LogLevel::Debug, (__VA_ARGS__)) +PRLogModuleInfo* GetWinWakeLockLog() { + static PRLogModuleInfo* log = nullptr; + if (!log) { + log = PR_NewLogModule("WinWakeLock"); + } + return log; +} + +// A wake lock listener that disables screen saver when requested by +// Gecko. For example when we're playing video in a foreground tab we +// don't want the screen saver to turn on. +class WinWakeLockListener final : public nsIDOMMozWakeLockListener + , public nsITimerCallback { +public: + NS_DECL_ISUPPORTS; + + NS_IMETHOD Notify(nsITimer *timer) override { + WAKE_LOCK_LOG("WinWakeLock: periodic timer fired"); + ResetScreenSaverTimeout(); + return NS_OK; + } +private: + ~WinWakeLockListener() {} + + NS_IMETHOD Callback(const nsAString& aTopic, const nsAString& aState) { + if (!aTopic.EqualsASCII("screen")) { + return NS_OK; + } + // Note the wake lock code ensures that we're not sent duplicate + // "locked-foreground" notifications when multipe wake locks are held. + if (aState.EqualsASCII("locked-foreground")) { + WAKE_LOCK_LOG("WinWakeLock: Blocking screen saver"); + // We block the screen saver by periodically resetting the screen + // saver timeout. + StartTimer(); + // Prevent the display turning off. On Win7 and later this also + // blocks the screen saver, but we need the timer started above + // to block on Win XP and Vista. + SetThreadExecutionState(ES_DISPLAY_REQUIRED|ES_CONTINUOUS); + } else { + WAKE_LOCK_LOG("WinWakeLock: Unblocking screen saver"); + // Re-enable screen saver. + StopTimer(); + // Unblock display turning off. + SetThreadExecutionState(ES_CONTINUOUS); + } + return NS_OK; + } + + void StartTimer() { + ResetScreenSaverTimeout(); + MOZ_ASSERT(!mTimer); + if (mTimer) { + return; + } + + nsresult rv; + nsCOMPtr<nsITimer> timer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to create screen saver timeout reset timer"); + return; + } + // The minimum screensaver timeout that can be specified with Windows' UI + // is 60 seconds. We set a timer to re-jig the screen saver 10 seconds + // before we expect the timer to run out, but always at least in 1 second + // intervals. We reset the timer at a max of 50 seconds, so that if the + // user changes the timeout using the UI, we won't be caught out. + int32_t timeout = std::max(std::min(50, (int32_t)mScreenSaverTimeout - 10), 1); + uint32_t timeoutMs = (uint32_t)timeout * 1000; + WAKE_LOCK_LOG("WinWakeLock: Setting periodic timer for %d ms", timeoutMs); + rv = timer->InitWithCallback(this, + timeoutMs, + nsITimer::TYPE_REPEATING_SLACK); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to initialize screen saver timeout reset timer"); + return; + } + + mTimer = timer.forget(); + } + + void StopTimer() { + WAKE_LOCK_LOG("WinWakeLock: StopTimer()"); + if (!mTimer) { + return; + } + mTimer->Cancel(); + mTimer = nullptr; + } + + // Resets the operating system's timeout for when to disable the screen. + // Called periodically to keep the screensaver off. + void ResetScreenSaverTimeout() { + if (SystemParametersInfo(SPI_GETSCREENSAVETIMEOUT, 0, &mScreenSaverTimeout, 0)) { + SystemParametersInfo(SPI_SETSCREENSAVETIMEOUT, mScreenSaverTimeout, NULL, 0); + } + WAKE_LOCK_LOG("WinWakeLock: ResetScreenSaverTimeout() mScreenSaverTimeout=%d", mScreenSaverTimeout); + } + + UINT mScreenSaverTimeout = 60; + nsCOMPtr<nsITimer> mTimer; +}; + +NS_IMPL_ISUPPORTS(WinWakeLockListener, nsIDOMMozWakeLockListener, nsITimerCallback) +StaticRefPtr<WinWakeLockListener> sWakeLockListener; + +static void +AddScreenWakeLockListener() +{ + nsCOMPtr<nsIPowerManagerService> sPowerManagerService = do_GetService(POWERMANAGERSERVICE_CONTRACTID); + if (sPowerManagerService) { + sWakeLockListener = new WinWakeLockListener(); + sPowerManagerService->AddWakeLockListener(sWakeLockListener); + } else { + NS_WARNING("Failed to retrieve PowerManagerService, wakelocks will be broken!"); + } +} + +static void +RemoveScreenWakeLockListener() +{ + nsCOMPtr<nsIPowerManagerService> sPowerManagerService = do_GetService(POWERMANAGERSERVICE_CONTRACTID); + if (sPowerManagerService) { + sPowerManagerService->RemoveWakeLockListener(sWakeLockListener); + sPowerManagerService = nullptr; + sWakeLockListener = nullptr; + } +} + +namespace mozilla { +namespace widget { +// Native event callback message. +UINT sAppShellGeckoMsgId = RegisterWindowMessageW(L"nsAppShell:EventID"); +} } + +const wchar_t* kTaskbarButtonEventId = L"TaskbarButtonCreated"; +UINT sTaskbarButtonCreatedMsg; + +/* static */ +UINT nsAppShell::GetTaskbarButtonCreatedMessage() { + return sTaskbarButtonCreatedMsg; +} + +namespace mozilla { +namespace crashreporter { +void LSPAnnotate(); +} // namespace crashreporter +} // namespace mozilla + +using mozilla::crashreporter::LSPAnnotate; + +//------------------------------------------------------------------------- + +/*static*/ LRESULT CALLBACK +nsAppShell::EventWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + if (uMsg == sAppShellGeckoMsgId) { + nsAppShell *as = reinterpret_cast<nsAppShell *>(lParam); + as->NativeEventCallback(); + NS_RELEASE(as); + return TRUE; + } + return DefWindowProc(hwnd, uMsg, wParam, lParam); +} + +nsAppShell::~nsAppShell() +{ + if (mEventWnd) { + // DestroyWindow doesn't do anything when called from a non UI thread. + // Since mEventWnd was created on the UI thread, it must be destroyed on + // the UI thread. + SendMessage(mEventWnd, WM_CLOSE, 0, 0); + } +} + +nsresult +nsAppShell::Init() +{ +#ifdef MOZ_CRASHREPORTER + LSPAnnotate(); +#endif + + mLastNativeEventScheduled = TimeStamp::NowLoRes(); + + mozilla::ipc::windows::InitUIThread(); + + sTaskbarButtonCreatedMsg = ::RegisterWindowMessageW(kTaskbarButtonEventId); + NS_ASSERTION(sTaskbarButtonCreatedMsg, "Could not register taskbar button creation message"); + + WNDCLASSW wc; + HINSTANCE module = GetModuleHandle(nullptr); + + const wchar_t *const kWindowClass = L"nsAppShell:EventWindowClass"; + if (!GetClassInfoW(module, kWindowClass, &wc)) { + wc.style = 0; + wc.lpfnWndProc = EventWindowProc; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hInstance = module; + wc.hIcon = nullptr; + wc.hCursor = nullptr; + wc.hbrBackground = (HBRUSH) nullptr; + wc.lpszMenuName = (LPCWSTR) nullptr; + wc.lpszClassName = kWindowClass; + RegisterClassW(&wc); + } + + mEventWnd = CreateWindowW(kWindowClass, L"nsAppShell:EventWindow", + 0, 0, 0, 10, 10, nullptr, nullptr, module, nullptr); + NS_ENSURE_STATE(mEventWnd); + + return nsBaseAppShell::Init(); +} + +NS_IMETHODIMP +nsAppShell::Run(void) +{ + // Content processes initialize audio later through PContent using audio + // tray id information pulled from the browser process AudioSession. This + // way the two share a single volume control. + // Note StopAudioSession() is called from nsAppRunner.cpp after xpcom is torn + // down to insure the browser shuts down after child processes. + if (XRE_IsParentProcess()) { + mozilla::widget::StartAudioSession(); + } + + // Add an observer that disables the screen saver when requested by Gecko. + // For example when we're playing video in the foreground tab. + AddScreenWakeLockListener(); + + nsresult rv = nsBaseAppShell::Run(); + + RemoveScreenWakeLockListener(); + + return rv; +} + +NS_IMETHODIMP +nsAppShell::Exit(void) +{ + return nsBaseAppShell::Exit(); +} + +void +nsAppShell::DoProcessMoreGeckoEvents() +{ + // Called by nsBaseAppShell's NativeEventCallback() after it has finished + // processing pending gecko events and there are still gecko events pending + // for the thread. (This can happen if NS_ProcessPendingEvents reached it's + // starvation timeout limit.) The default behavior in nsBaseAppShell is to + // call ScheduleNativeEventCallback to post a follow up native event callback + // message. This triggers an additional call to NativeEventCallback for more + // gecko event processing. + + // There's a deadlock risk here with certain internal Windows modal loops. In + // our dispatch code, we prioritize messages so that input is handled first. + // However Windows modal dispatch loops often prioritize posted messages. If + // we find ourselves in a tight gecko timer loop where NS_ProcessPendingEvents + // takes longer than the timer duration, NS_HasPendingEvents(thread) will + // always be true. ScheduleNativeEventCallback will be called on every + // NativeEventCallback callback, and in a Windows modal dispatch loop, the + // callback message will be processed first -> input gets starved, dead lock. + + // To avoid, don't post native callback messages from NativeEventCallback + // when we're in a modal loop. This gets us back into the Windows modal + // dispatch loop dispatching input messages. Once we drop out of the modal + // loop, we use mNativeCallbackPending to fire off a final NativeEventCallback + // if we need it, which insures NS_ProcessPendingEvents gets called and all + // gecko events get processed. + if (mEventloopNestingLevel < 2) { + OnDispatchedEvent(nullptr); + mNativeCallbackPending = false; + } else { + mNativeCallbackPending = true; + } +} + +void +nsAppShell::ScheduleNativeEventCallback() +{ + // Post a message to the hidden message window + NS_ADDREF_THIS(); // will be released when the event is processed + { + MutexAutoLock lock(mLastNativeEventScheduledMutex); + // Time stamp this event so we can detect cases where the event gets + // dropping in sub classes / modal loops we do not control. + mLastNativeEventScheduled = TimeStamp::NowLoRes(); + } + ::PostMessage(mEventWnd, sAppShellGeckoMsgId, 0, reinterpret_cast<LPARAM>(this)); +} + +bool +nsAppShell::ProcessNextNativeEvent(bool mayWait) +{ + // Notify ipc we are spinning a (possibly nested) gecko event loop. + mozilla::ipc::MessageChannel::NotifyGeckoEventDispatch(); + + bool gotMessage = false; + + do { + MSG msg; + bool uiMessage = false; + + // For avoiding deadlock between our process and plugin process by + // mouse wheel messages, we're handling actually when we receive one of + // following internal messages which is posted by native mouse wheel + // message handler. Any other events, especially native modifier key + // events, should not be handled between native message and posted + // internal message because it may make different modifier key state or + // mouse cursor position between them. + if (mozilla::widget::MouseScrollHandler::IsWaitingInternalMessage()) { + gotMessage = WinUtils::PeekMessage(&msg, nullptr, MOZ_WM_MOUSEWHEEL_FIRST, + MOZ_WM_MOUSEWHEEL_LAST, PM_REMOVE); + NS_ASSERTION(gotMessage, + "waiting internal wheel message, but it has not come"); + uiMessage = gotMessage; + } + + if (!gotMessage) { + gotMessage = WinUtils::PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE); + uiMessage = + (msg.message >= WM_KEYFIRST && msg.message <= WM_IME_KEYLAST) || + (msg.message >= NS_WM_IMEFIRST && msg.message <= NS_WM_IMELAST) || + (msg.message >= WM_MOUSEFIRST && msg.message <= WM_MOUSELAST); + } + + if (gotMessage) { + if (msg.message == WM_QUIT) { + ::PostQuitMessage(msg.wParam); + Exit(); + } else { + // If we had UI activity we would be processing it now so we know we + // have either kUIActivity or kActivityNoUIAVail. + mozilla::HangMonitor::NotifyActivity( + uiMessage ? mozilla::HangMonitor::kUIActivity : + mozilla::HangMonitor::kActivityNoUIAVail); + + if (msg.message >= WM_KEYFIRST && msg.message <= WM_KEYLAST && + IMEHandler::ProcessRawKeyMessage(msg)) { + continue; // the message is consumed. + } + + // Store Printer Properties messages for reposting, because they are not + // processed by a window procedure, but are explicitly waited for in the + // winspool.drv code that will be further up the stack. + if (msg.message == MOZ_WM_PRINTER_PROPERTIES_COMPLETION || + msg.message == MOZ_WM_PRINTER_PROPERTIES_FAILURE) { + mMsgsToRepost.push_back(msg); + continue; + } + + ::TranslateMessage(&msg); + ::DispatchMessageW(&msg); + } + } else if (mayWait) { + // Block and wait for any posted application message + mozilla::HangMonitor::Suspend(); + { + GeckoProfilerSleepRAII profiler_sleep; + WinUtils::WaitForMessage(); + } + } + } while (!gotMessage && mayWait); + + // See DoProcessNextNativeEvent, mEventloopNestingLevel will be + // one when a modal loop unwinds. + if (mNativeCallbackPending && mEventloopNestingLevel == 1) + DoProcessMoreGeckoEvents(); + + // Check for starved native callbacks. If we haven't processed one + // of these events in NATIVE_EVENT_STARVATION_LIMIT, fire one off. + static const mozilla::TimeDuration nativeEventStarvationLimit = + mozilla::TimeDuration::FromSeconds(NATIVE_EVENT_STARVATION_LIMIT); + + TimeDuration timeSinceLastNativeEventScheduled; + { + MutexAutoLock lock(mLastNativeEventScheduledMutex); + timeSinceLastNativeEventScheduled = + TimeStamp::NowLoRes() - mLastNativeEventScheduled; + } + if (timeSinceLastNativeEventScheduled > nativeEventStarvationLimit) { + ScheduleNativeEventCallback(); + } + + return gotMessage; +} + +nsresult +nsAppShell::AfterProcessNextEvent(nsIThreadInternal* /* unused */, + bool /* unused */) +{ + if (!mMsgsToRepost.empty()) { + for (MSG msg : mMsgsToRepost) { + ::PostMessageW(msg.hwnd, msg.message, msg.wParam, msg.lParam); + } + mMsgsToRepost.clear(); + } + return NS_OK; +} diff --git a/widget/windows/nsAppShell.h b/widget/windows/nsAppShell.h new file mode 100644 index 0000000000..199c300095 --- /dev/null +++ b/widget/windows/nsAppShell.h @@ -0,0 +1,59 @@ +/* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsAppShell_h__ +#define nsAppShell_h__ + +#include "nsBaseAppShell.h" +#include <windows.h> +#include <vector> +#include "mozilla/TimeStamp.h" +#include "mozilla/Mutex.h" + +// The maximum time we allow before forcing a native event callback. +// In seconds. +#define NATIVE_EVENT_STARVATION_LIMIT 1 + +/** + * Native Win32 Application shell wrapper + */ +class nsAppShell : public nsBaseAppShell +{ +public: + nsAppShell() : + mEventWnd(nullptr), + mNativeCallbackPending(false), + mLastNativeEventScheduledMutex("nsAppShell::mLastNativeEventScheduledMutex") + {} + typedef mozilla::TimeStamp TimeStamp; + typedef mozilla::Mutex Mutex; + + nsresult Init(); + void DoProcessMoreGeckoEvents(); + + static UINT GetTaskbarButtonCreatedMessage(); + + NS_IMETHOD AfterProcessNextEvent(nsIThreadInternal* thread, + bool eventWasProcessed) final; + +protected: + NS_IMETHOD Run(); + NS_IMETHOD Exit(); + virtual void ScheduleNativeEventCallback(); + virtual bool ProcessNextNativeEvent(bool mayWait); + virtual ~nsAppShell(); + + static LRESULT CALLBACK EventWindowProc(HWND, UINT, WPARAM, LPARAM); + +protected: + HWND mEventWnd; + bool mNativeCallbackPending; + + Mutex mLastNativeEventScheduledMutex; + TimeStamp mLastNativeEventScheduled; + std::vector<MSG> mMsgsToRepost; +}; + +#endif // nsAppShell_h__ diff --git a/widget/windows/nsBidiKeyboard.cpp b/widget/windows/nsBidiKeyboard.cpp new file mode 100644 index 0000000000..1fac24d6c5 --- /dev/null +++ b/widget/windows/nsBidiKeyboard.cpp @@ -0,0 +1,187 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <stdio.h> +#include "nsBidiKeyboard.h" +#include "WidgetUtils.h" +#include "prmem.h" +#include <tchar.h> + +NS_IMPL_ISUPPORTS(nsBidiKeyboard, nsIBidiKeyboard) + +nsBidiKeyboard::nsBidiKeyboard() : nsIBidiKeyboard() +{ + Reset(); +} + +nsBidiKeyboard::~nsBidiKeyboard() +{ +} + +NS_IMETHODIMP nsBidiKeyboard::Reset() +{ + mInitialized = false; + mHaveBidiKeyboards = false; + mLTRKeyboard[0] = '\0'; + mRTLKeyboard[0] = '\0'; + mCurrentLocaleName[0] = '\0'; + return NS_OK; +} + +NS_IMETHODIMP nsBidiKeyboard::IsLangRTL(bool *aIsRTL) +{ + *aIsRTL = false; + + nsresult result = SetupBidiKeyboards(); + if (NS_FAILED(result)) + return result; + + HKL currentLocale; + + currentLocale = ::GetKeyboardLayout(0); + *aIsRTL = IsRTLLanguage(currentLocale); + + if (!::GetKeyboardLayoutNameW(mCurrentLocaleName)) + return NS_ERROR_FAILURE; + + NS_ASSERTION(*mCurrentLocaleName, + "GetKeyboardLayoutName return string length == 0"); + NS_ASSERTION((wcslen(mCurrentLocaleName) < KL_NAMELENGTH), + "GetKeyboardLayoutName return string length >= KL_NAMELENGTH"); + + // The language set by the user overrides the default language for that direction + if (*aIsRTL) { + wcsncpy(mRTLKeyboard, mCurrentLocaleName, KL_NAMELENGTH); + mRTLKeyboard[KL_NAMELENGTH-1] = '\0'; // null terminate + } else { + wcsncpy(mLTRKeyboard, mCurrentLocaleName, KL_NAMELENGTH); + mLTRKeyboard[KL_NAMELENGTH-1] = '\0'; // null terminate + } + + NS_ASSERTION((wcslen(mRTLKeyboard) < KL_NAMELENGTH), + "mLTRKeyboard has string length >= KL_NAMELENGTH"); + NS_ASSERTION((wcslen(mLTRKeyboard) < KL_NAMELENGTH), + "mRTLKeyboard has string length >= KL_NAMELENGTH"); + return NS_OK; +} + +NS_IMETHODIMP nsBidiKeyboard::GetHaveBidiKeyboards(bool* aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + nsresult result = SetupBidiKeyboards(); + if (NS_FAILED(result)) + return result; + + *aResult = mHaveBidiKeyboards; + return NS_OK; +} + + +// Get the list of keyboard layouts available in the system +// Set mLTRKeyboard to the first LTR keyboard in the list and mRTLKeyboard to the first RTL keyboard in the list +// These defaults will be used unless the user explicitly sets something else. +nsresult nsBidiKeyboard::SetupBidiKeyboards() +{ + if (mInitialized) + return mHaveBidiKeyboards ? NS_OK : NS_ERROR_FAILURE; + + int keyboards; + HKL far* buf; + HKL locale; + wchar_t localeName[KL_NAMELENGTH]; + bool isLTRKeyboardSet = false; + bool isRTLKeyboardSet = false; + + // GetKeyboardLayoutList with 0 as first parameter returns the number of keyboard layouts available + keyboards = ::GetKeyboardLayoutList(0, nullptr); + if (!keyboards) + return NS_ERROR_FAILURE; + + // allocate a buffer to hold the list + buf = (HKL far*) PR_Malloc(keyboards * sizeof(HKL)); + if (!buf) + return NS_ERROR_OUT_OF_MEMORY; + + // Call again to fill the buffer + if (::GetKeyboardLayoutList(keyboards, buf) != keyboards) { + PR_Free(buf); + return NS_ERROR_UNEXPECTED; + } + + // Go through the list and pick a default LTR and RTL keyboard layout + while (keyboards--) { + locale = buf[keyboards]; + if (IsRTLLanguage(locale)) { + _snwprintf(mRTLKeyboard, KL_NAMELENGTH, L"%.*x", KL_NAMELENGTH - 1, + LANGIDFROMLCID((DWORD_PTR)locale)); + isRTLKeyboardSet = true; + } + else { + _snwprintf(mLTRKeyboard, KL_NAMELENGTH, L"%.*x", KL_NAMELENGTH - 1, + LANGIDFROMLCID((DWORD_PTR)locale)); + isLTRKeyboardSet = true; + } + } + PR_Free(buf); + mInitialized = true; + + // If there is not at least one keyboard of each directionality, Bidi + // keyboard functionality will be disabled. + mHaveBidiKeyboards = (isRTLKeyboardSet && isLTRKeyboardSet); + if (!mHaveBidiKeyboards) + return NS_ERROR_FAILURE; + + // Get the current keyboard layout and use it for either mRTLKeyboard or + // mLTRKeyboard as appropriate. If the user has many keyboard layouts + // installed this prevents us from arbitrarily resetting the current + // layout (bug 80274) + locale = ::GetKeyboardLayout(0); + if (!::GetKeyboardLayoutNameW(localeName)) + return NS_ERROR_FAILURE; + + NS_ASSERTION(*localeName, + "GetKeyboardLayoutName return string length == 0"); + NS_ASSERTION((wcslen(localeName) < KL_NAMELENGTH), + "GetKeyboardLayout return string length >= KL_NAMELENGTH"); + + if (IsRTLLanguage(locale)) { + wcsncpy(mRTLKeyboard, localeName, KL_NAMELENGTH); + mRTLKeyboard[KL_NAMELENGTH-1] = '\0'; // null terminate + } + else { + wcsncpy(mLTRKeyboard, localeName, KL_NAMELENGTH); + mLTRKeyboard[KL_NAMELENGTH-1] = '\0'; // null terminate + } + + NS_ASSERTION(*mRTLKeyboard, + "mLTRKeyboard has string length == 0"); + NS_ASSERTION(*mLTRKeyboard, + "mLTRKeyboard has string length == 0"); + + return NS_OK; +} + +// Test whether the language represented by this locale identifier is a +// right-to-left language, using bit 123 of the Unicode subset bitfield in +// the LOCALESIGNATURE +// See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/intl/unicode_63ub.asp +bool nsBidiKeyboard::IsRTLLanguage(HKL aLocale) +{ + LOCALESIGNATURE localesig; + return (::GetLocaleInfoW(PRIMARYLANGID((DWORD_PTR)aLocale), + LOCALE_FONTSIGNATURE, + (LPWSTR)&localesig, + (sizeof(localesig)/sizeof(WCHAR))) && + (localesig.lsUsb[3] & 0x08000000)); +} + +//static +void +nsBidiKeyboard::OnLayoutChange() +{ + mozilla::widget::WidgetUtils::SendBidiKeyboardInfoToContent(); +} diff --git a/widget/windows/nsBidiKeyboard.h b/widget/windows/nsBidiKeyboard.h new file mode 100644 index 0000000000..b3b56f48f6 --- /dev/null +++ b/widget/windows/nsBidiKeyboard.h @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __nsBidiKeyboard +#define __nsBidiKeyboard +#include "nsIBidiKeyboard.h" +#include <windows.h> + +class nsBidiKeyboard : public nsIBidiKeyboard +{ + virtual ~nsBidiKeyboard(); + +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIBIDIKEYBOARD + + nsBidiKeyboard(); + + static void OnLayoutChange(); + +protected: + + nsresult SetupBidiKeyboards(); + bool IsRTLLanguage(HKL aLocale); + + bool mInitialized; + bool mHaveBidiKeyboards; + wchar_t mLTRKeyboard[KL_NAMELENGTH]; + wchar_t mRTLKeyboard[KL_NAMELENGTH]; + wchar_t mCurrentLocaleName[KL_NAMELENGTH]; +}; + + +#endif // __nsBidiKeyboard diff --git a/widget/windows/nsClipboard.cpp b/widget/windows/nsClipboard.cpp new file mode 100644 index 0000000000..1afee34961 --- /dev/null +++ b/widget/windows/nsClipboard.cpp @@ -0,0 +1,1036 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsClipboard.h" +#include <ole2.h> +#include <shlobj.h> +#include <intshcut.h> + +// shellapi.h is needed to build with WIN32_LEAN_AND_MEAN +#include <shellapi.h> + +#include "nsArrayUtils.h" +#include "nsCOMPtr.h" +#include "nsDataObj.h" +#include "nsIClipboardOwner.h" +#include "nsString.h" +#include "nsNativeCharsetUtils.h" +#include "nsIFormatConverter.h" +#include "nsITransferable.h" +#include "nsCOMPtr.h" +#include "nsXPCOM.h" +#include "nsISupportsPrimitives.h" +#include "nsXPIDLString.h" +#include "nsReadableUtils.h" +#include "nsUnicharUtils.h" +#include "nsPrimitiveHelpers.h" +#include "nsImageClipboard.h" +#include "nsIWidget.h" +#include "nsIComponentManager.h" +#include "nsWidgetsCID.h" +#include "nsCRT.h" +#include "nsNetUtil.h" +#include "nsIFileProtocolHandler.h" +#include "nsIOutputStream.h" +#include "nsEscape.h" +#include "nsIObserverService.h" + +using mozilla::LogLevel; + +PRLogModuleInfo* gWin32ClipboardLog = nullptr; + +// oddly, this isn't in the MSVC headers anywhere. +UINT nsClipboard::CF_HTML = ::RegisterClipboardFormatW(L"HTML Format"); +UINT nsClipboard::CF_CUSTOMTYPES = ::RegisterClipboardFormatW(L"application/x-moz-custom-clipdata"); + + +//------------------------------------------------------------------------- +// +// nsClipboard constructor +// +//------------------------------------------------------------------------- +nsClipboard::nsClipboard() : nsBaseClipboard() +{ + if (!gWin32ClipboardLog) { + gWin32ClipboardLog = PR_NewLogModule("nsClipboard"); + } + + mIgnoreEmptyNotification = false; + mWindow = nullptr; + + // Register for a shutdown notification so that we can flush data + // to the OS clipboard. + nsCOMPtr<nsIObserverService> observerService = + do_GetService("@mozilla.org/observer-service;1"); + if (observerService) + observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, PR_FALSE); +} + +//------------------------------------------------------------------------- +// nsClipboard destructor +//------------------------------------------------------------------------- +nsClipboard::~nsClipboard() +{ + +} + +NS_IMPL_ISUPPORTS_INHERITED(nsClipboard, nsBaseClipboard, nsIObserver) + +NS_IMETHODIMP +nsClipboard::Observe(nsISupports *aSubject, const char *aTopic, + const char16_t *aData) +{ + // This will be called on shutdown. + ::OleFlushClipboard(); + ::CloseClipboard(); + + return NS_OK; +} + +//------------------------------------------------------------------------- +UINT nsClipboard::GetFormat(const char* aMimeStr, bool aMapHTMLMime) +{ + UINT format; + + if (strcmp(aMimeStr, kTextMime) == 0) + format = CF_TEXT; + else if (strcmp(aMimeStr, kUnicodeMime) == 0) + format = CF_UNICODETEXT; + else if (strcmp(aMimeStr, kRTFMime) == 0) + format = ::RegisterClipboardFormat(L"Rich Text Format"); + else if (strcmp(aMimeStr, kJPEGImageMime) == 0 || + strcmp(aMimeStr, kJPGImageMime) == 0 || + strcmp(aMimeStr, kPNGImageMime) == 0) + format = CF_DIBV5; + else if (strcmp(aMimeStr, kFileMime) == 0 || + strcmp(aMimeStr, kFilePromiseMime) == 0) + format = CF_HDROP; + else if (strcmp(aMimeStr, kNativeHTMLMime) == 0 || + aMapHTMLMime && strcmp(aMimeStr, kHTMLMime) == 0) + format = CF_HTML; + else if (strcmp(aMimeStr, kCustomTypesMime) == 0) + format = CF_CUSTOMTYPES; + else + format = ::RegisterClipboardFormatW(NS_ConvertASCIItoUTF16(aMimeStr).get()); + + return format; +} + +//------------------------------------------------------------------------- +nsresult nsClipboard::CreateNativeDataObject(nsITransferable * aTransferable, IDataObject ** aDataObj, nsIURI * uri) +{ + if (nullptr == aTransferable) { + return NS_ERROR_FAILURE; + } + + // Create our native DataObject that implements + // the OLE IDataObject interface + nsDataObj * dataObj = new nsDataObj(uri); + + if (!dataObj) + return NS_ERROR_OUT_OF_MEMORY; + + dataObj->AddRef(); + + // Now set it up with all the right data flavors & enums + nsresult res = SetupNativeDataObject(aTransferable, dataObj); + if (NS_OK == res) { + *aDataObj = dataObj; + } else { + delete dataObj; + } + return res; +} + +//------------------------------------------------------------------------- +nsresult nsClipboard::SetupNativeDataObject(nsITransferable * aTransferable, IDataObject * aDataObj) +{ + if (nullptr == aTransferable || nullptr == aDataObj) { + return NS_ERROR_FAILURE; + } + + nsDataObj * dObj = static_cast<nsDataObj *>(aDataObj); + + // Now give the Transferable to the DataObject + // for getting the data out of it + dObj->SetTransferable(aTransferable); + + // Get the transferable list of data flavors + nsCOMPtr<nsIArray> dfList; + aTransferable->FlavorsTransferableCanExport(getter_AddRefs(dfList)); + + // Walk through flavors that contain data and register them + // into the DataObj as supported flavors + uint32_t i; + uint32_t cnt; + dfList->GetLength(&cnt); + for (i=0;i<cnt;i++) { + nsCOMPtr<nsISupportsCString> currentFlavor = do_QueryElementAt(dfList, i); + if ( currentFlavor ) { + nsXPIDLCString flavorStr; + currentFlavor->ToString(getter_Copies(flavorStr)); + // When putting data onto the clipboard, we want to maintain kHTMLMime + // ("text/html") and not map it to CF_HTML here since this will be done below. + UINT format = GetFormat(flavorStr, false); + + // Now tell the native IDataObject about both our mime type and + // the native data format + FORMATETC fe; + SET_FORMATETC(fe, format, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL); + dObj->AddDataFlavor(flavorStr, &fe); + + // Do various things internal to the implementation, like map one + // flavor to another or add additional flavors based on what's required + // for the win32 impl. + if ( strcmp(flavorStr, kUnicodeMime) == 0 ) { + // if we find text/unicode, also advertise text/plain (which we will convert + // on our own in nsDataObj::GetText(). + FORMATETC textFE; + SET_FORMATETC(textFE, CF_TEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL); + dObj->AddDataFlavor(kTextMime, &textFE); + } + else if ( strcmp(flavorStr, kHTMLMime) == 0 ) { + // if we find text/html, also advertise win32's html flavor (which we will convert + // on our own in nsDataObj::GetText(). + FORMATETC htmlFE; + SET_FORMATETC(htmlFE, CF_HTML, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL); + dObj->AddDataFlavor(kHTMLMime, &htmlFE); + } + else if ( strcmp(flavorStr, kURLMime) == 0 ) { + // if we're a url, in addition to also being text, we need to register + // the "file" flavors so that the win32 shell knows to create an internet + // shortcut when it sees one of these beasts. + FORMATETC shortcutFE; + SET_FORMATETC(shortcutFE, ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORA), 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL) + dObj->AddDataFlavor(kURLMime, &shortcutFE); + SET_FORMATETC(shortcutFE, ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW), 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL) + dObj->AddDataFlavor(kURLMime, &shortcutFE); + SET_FORMATETC(shortcutFE, ::RegisterClipboardFormat(CFSTR_FILECONTENTS), 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL) + dObj->AddDataFlavor(kURLMime, &shortcutFE); + SET_FORMATETC(shortcutFE, ::RegisterClipboardFormat(CFSTR_INETURLA), 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL) + dObj->AddDataFlavor(kURLMime, &shortcutFE); + SET_FORMATETC(shortcutFE, ::RegisterClipboardFormat(CFSTR_INETURLW), 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL) + dObj->AddDataFlavor(kURLMime, &shortcutFE); + } + else if ( strcmp(flavorStr, kPNGImageMime) == 0 || strcmp(flavorStr, kJPEGImageMime) == 0 || + strcmp(flavorStr, kJPGImageMime) == 0 || strcmp(flavorStr, kGIFImageMime) == 0 || + strcmp(flavorStr, kNativeImageMime) == 0 ) { + // if we're an image, register the native bitmap flavor + FORMATETC imageFE; + // Add DIBv5 + SET_FORMATETC(imageFE, CF_DIBV5, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL) + dObj->AddDataFlavor(flavorStr, &imageFE); + // Add DIBv3 + SET_FORMATETC(imageFE, CF_DIB, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL) + dObj->AddDataFlavor(flavorStr, &imageFE); + } + else if ( strcmp(flavorStr, kFilePromiseMime) == 0 ) { + // if we're a file promise flavor, also register the + // CFSTR_PREFERREDDROPEFFECT format. The data object + // returns a value of DROPEFFECTS_MOVE to the drop target + // when it asks for the value of this format. This causes + // the file to be moved from the temporary location instead + // of being copied. The right thing to do here is to call + // SetData() on the data object and set the value of this format + // to DROPEFFECTS_MOVE on this particular data object. But, + // since all the other clipboard formats follow the model of setting + // data on the data object only when the drop object calls GetData(), + // I am leaving this format's value hard coded in the data object. + // We can change this if other consumers of this format get added to this + // codebase and they need different values. + FORMATETC shortcutFE; + SET_FORMATETC(shortcutFE, ::RegisterClipboardFormat(CFSTR_PREFERREDDROPEFFECT), 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL) + dObj->AddDataFlavor(kFilePromiseMime, &shortcutFE); + } + } + } + + return NS_OK; +} + +//------------------------------------------------------------------------- +NS_IMETHODIMP nsClipboard::SetNativeClipboardData ( int32_t aWhichClipboard ) +{ + if ( aWhichClipboard != kGlobalClipboard ) + return NS_ERROR_FAILURE; + + mIgnoreEmptyNotification = true; + + // make sure we have a good transferable + if (nullptr == mTransferable) { + return NS_ERROR_FAILURE; + } + + IDataObject * dataObj; + if ( NS_SUCCEEDED(CreateNativeDataObject(mTransferable, &dataObj, nullptr)) ) { // this add refs dataObj + ::OleSetClipboard(dataObj); + dataObj->Release(); + } else { + // Clear the native clipboard + ::OleSetClipboard(nullptr); + } + + mIgnoreEmptyNotification = false; + + return NS_OK; +} + + +//------------------------------------------------------------------------- +nsresult nsClipboard::GetGlobalData(HGLOBAL aHGBL, void ** aData, uint32_t * aLen) +{ + // Allocate a new memory buffer and copy the data from global memory. + // Recall that win98 allocates to nearest DWORD boundary. As a safety + // precaution, allocate an extra 2 bytes (but don't report them!) and + // null them out to ensure that all of our strlen calls will succeed. + nsresult result = NS_ERROR_FAILURE; + if (aHGBL != nullptr) { + LPSTR lpStr = (LPSTR) GlobalLock(aHGBL); + DWORD allocSize = GlobalSize(aHGBL); + char* data = static_cast<char*>(malloc(allocSize + sizeof(char16_t))); + if ( data ) { + memcpy ( data, lpStr, allocSize ); + data[allocSize] = data[allocSize + 1] = '\0'; // null terminate for safety + + GlobalUnlock(aHGBL); + *aData = data; + *aLen = allocSize; + + result = NS_OK; + } + } else { + // We really shouldn't ever get here + // but just in case + *aData = nullptr; + *aLen = 0; + LPVOID lpMsgBuf; + + FormatMessageW( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + nullptr, + GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language + (LPWSTR) &lpMsgBuf, + 0, + nullptr + ); + + // Display the string. + MessageBoxW( nullptr, (LPCWSTR) lpMsgBuf, L"GetLastError", + MB_OK | MB_ICONINFORMATION ); + + // Free the buffer. + LocalFree( lpMsgBuf ); + } + + return result; +} + +//------------------------------------------------------------------------- +nsresult nsClipboard::GetNativeDataOffClipboard(nsIWidget * aWidget, UINT /*aIndex*/, UINT aFormat, void ** aData, uint32_t * aLen) +{ + HGLOBAL hglb; + nsresult result = NS_ERROR_FAILURE; + + HWND nativeWin = nullptr; + if (::OpenClipboard(nativeWin)) { + hglb = ::GetClipboardData(aFormat); + result = GetGlobalData(hglb, aData, aLen); + ::CloseClipboard(); + } + return result; +} + +static void DisplayErrCode(HRESULT hres) +{ +#if defined(DEBUG_rods) || defined(DEBUG_pinkerton) + if (hres == E_INVALIDARG) { + MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("E_INVALIDARG\n")); + } else + if (hres == E_UNEXPECTED) { + MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("E_UNEXPECTED\n")); + } else + if (hres == E_OUTOFMEMORY) { + MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("E_OUTOFMEMORY\n")); + } else + if (hres == DV_E_LINDEX ) { + MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("DV_E_LINDEX\n")); + } else + if (hres == DV_E_FORMATETC) { + MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("DV_E_FORMATETC\n")); + } else + if (hres == DV_E_TYMED) { + MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("DV_E_TYMED\n")); + } else + if (hres == DV_E_DVASPECT) { + MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("DV_E_DVASPECT\n")); + } else + if (hres == OLE_E_NOTRUNNING) { + MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("OLE_E_NOTRUNNING\n")); + } else + if (hres == STG_E_MEDIUMFULL) { + MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("STG_E_MEDIUMFULL\n")); + } else + if (hres == DV_E_CLIPFORMAT) { + MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("DV_E_CLIPFORMAT\n")); + } else + if (hres == S_OK) { + MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("S_OK\n")); + } else { + MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, + ("****** DisplayErrCode 0x%X\n", hres)); + } +#endif +} + +//------------------------------------------------------------------------- +static HRESULT FillSTGMedium(IDataObject * aDataObject, UINT aFormat, LPFORMATETC pFE, LPSTGMEDIUM pSTM, DWORD aTymed) +{ + SET_FORMATETC(*pFE, aFormat, 0, DVASPECT_CONTENT, -1, aTymed); + + // Starting by querying for the data to see if we can get it as from global memory + HRESULT hres = S_FALSE; + hres = aDataObject->QueryGetData(pFE); + DisplayErrCode(hres); + if (S_OK == hres) { + hres = aDataObject->GetData(pFE, pSTM); + DisplayErrCode(hres); + } + return hres; +} + + +//------------------------------------------------------------------------- +// If aFormat is CF_DIBV5, aMIMEImageFormat must be a type for which we have +// an image encoder (e.g. image/png). +// For other values of aFormat, it is OK to pass null for aMIMEImageFormat. +nsresult nsClipboard::GetNativeDataOffClipboard(IDataObject * aDataObject, UINT aIndex, UINT aFormat, const char * aMIMEImageFormat, void ** aData, uint32_t * aLen) +{ + nsresult result = NS_ERROR_FAILURE; + *aData = nullptr; + *aLen = 0; + + if ( !aDataObject ) + return result; + + UINT format = aFormat; + HRESULT hres = S_FALSE; + + // XXX at the moment we only support global memory transfers + // It is here where we will add support for native images + // and IStream + FORMATETC fe; + STGMEDIUM stm; + hres = FillSTGMedium(aDataObject, format, &fe, &stm, TYMED_HGLOBAL); + + // Currently this is only handling TYMED_HGLOBAL data + // For Text, Dibs, Files, and generic data (like HTML) + if (S_OK == hres) { + static CLIPFORMAT fileDescriptorFlavorA = ::RegisterClipboardFormat( CFSTR_FILEDESCRIPTORA ); + static CLIPFORMAT fileDescriptorFlavorW = ::RegisterClipboardFormat( CFSTR_FILEDESCRIPTORW ); + static CLIPFORMAT fileFlavor = ::RegisterClipboardFormat( CFSTR_FILECONTENTS ); + static CLIPFORMAT preferredDropEffect = ::RegisterClipboardFormat(CFSTR_PREFERREDDROPEFFECT); + + switch (stm.tymed) { + case TYMED_HGLOBAL: + { + switch (fe.cfFormat) { + case CF_TEXT: + { + // Get the data out of the global data handle. The size we return + // should not include the null because the other platforms don't + // use nulls, so just return the length we get back from strlen(), + // since we know CF_TEXT is null terminated. Recall that GetGlobalData() + // returns the size of the allocated buffer, not the size of the data + // (on 98, these are not the same) so we can't use that. + uint32_t allocLen = 0; + if ( NS_SUCCEEDED(GetGlobalData(stm.hGlobal, aData, &allocLen)) ) { + *aLen = strlen ( reinterpret_cast<char*>(*aData) ); + result = NS_OK; + } + } break; + + case CF_UNICODETEXT: + { + // Get the data out of the global data handle. The size we return + // should not include the null because the other platforms don't + // use nulls, so just return the length we get back from strlen(), + // since we know CF_UNICODETEXT is null terminated. Recall that GetGlobalData() + // returns the size of the allocated buffer, not the size of the data + // (on 98, these are not the same) so we can't use that. + uint32_t allocLen = 0; + if ( NS_SUCCEEDED(GetGlobalData(stm.hGlobal, aData, &allocLen)) ) { + *aLen = NS_strlen(reinterpret_cast<char16_t*>(*aData)) * 2; + result = NS_OK; + } + } break; + + case CF_DIBV5: + if (aMIMEImageFormat) + { + uint32_t allocLen = 0; + unsigned char * clipboardData; + if (NS_SUCCEEDED(GetGlobalData(stm.hGlobal, (void **)&clipboardData, &allocLen))) + { + nsImageFromClipboard converter; + nsIInputStream * inputStream; + converter.GetEncodedImageStream(clipboardData, aMIMEImageFormat, &inputStream); // addrefs for us, don't release + if ( inputStream ) { + *aData = inputStream; + *aLen = sizeof(nsIInputStream*); + result = NS_OK; + } + } + } break; + + case CF_HDROP : + { + // in the case of a file drop, multiple files are stashed within a + // single data object. In order to match mozilla's D&D apis, we + // just pull out the file at the requested index, pretending as + // if there really are multiple drag items. + HDROP dropFiles = (HDROP) GlobalLock(stm.hGlobal); + + UINT numFiles = ::DragQueryFileW(dropFiles, 0xFFFFFFFF, nullptr, 0); + NS_ASSERTION ( numFiles > 0, "File drop flavor, but no files...hmmmm" ); + NS_ASSERTION ( aIndex < numFiles, "Asked for a file index out of range of list" ); + if (numFiles > 0) { + UINT fileNameLen = ::DragQueryFileW(dropFiles, aIndex, nullptr, 0); + wchar_t* buffer = reinterpret_cast<wchar_t*>(moz_xmalloc((fileNameLen + 1) * sizeof(wchar_t))); + if ( buffer ) { + ::DragQueryFileW(dropFiles, aIndex, buffer, fileNameLen + 1); + *aData = buffer; + *aLen = fileNameLen * sizeof(char16_t); + result = NS_OK; + } + else + result = NS_ERROR_OUT_OF_MEMORY; + } + GlobalUnlock (stm.hGlobal) ; + + } break; + + default: { + if ( fe.cfFormat == fileDescriptorFlavorA || fe.cfFormat == fileDescriptorFlavorW || fe.cfFormat == fileFlavor ) { + NS_WARNING ( "Mozilla doesn't yet understand how to read this type of file flavor" ); + } + else + { + // Get the data out of the global data handle. The size we return + // should not include the null because the other platforms don't + // use nulls, so just return the length we get back from strlen(), + // since we know CF_UNICODETEXT is null terminated. Recall that GetGlobalData() + // returns the size of the allocated buffer, not the size of the data + // (on 98, these are not the same) so we can't use that. + // + // NOTE: we are assuming that anything that falls into this default case + // is unicode. As we start to get more kinds of binary data, this + // may become an incorrect assumption. Stay tuned. + uint32_t allocLen = 0; + if ( NS_SUCCEEDED(GetGlobalData(stm.hGlobal, aData, &allocLen)) ) { + if ( fe.cfFormat == CF_HTML ) { + // CF_HTML is actually UTF8, not unicode, so disregard the assumption + // above. We have to check the header for the actual length, and we'll + // do that in FindPlatformHTML(). For now, return the allocLen. This + // case is mostly to ensure we don't try to call strlen on the buffer. + *aLen = allocLen; + } else if (fe.cfFormat == CF_CUSTOMTYPES) { + // Binary data + *aLen = allocLen; + } else if (fe.cfFormat == preferredDropEffect) { + // As per the MSDN doc entitled: "Shell Clipboard Formats" + // CFSTR_PREFERREDDROPEFFECT should return a DWORD + // Reference: http://msdn.microsoft.com/en-us/library/bb776902(v=vs.85).aspx + NS_ASSERTION(allocLen == sizeof(DWORD), + "CFSTR_PREFERREDDROPEFFECT should return a DWORD"); + *aLen = allocLen; + } else { + *aLen = NS_strlen(reinterpret_cast<char16_t*>(*aData)) * + sizeof(char16_t); + } + result = NS_OK; + } + } + } break; + } // switch + } break; + + case TYMED_GDI: + { +#ifdef DEBUG + MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, + ("*********************** TYMED_GDI\n")); +#endif + } break; + + default: + break; + } //switch + + ReleaseStgMedium(&stm); + } + + return result; +} + + +//------------------------------------------------------------------------- +nsresult nsClipboard::GetDataFromDataObject(IDataObject * aDataObject, + UINT anIndex, + nsIWidget * aWindow, + nsITransferable * aTransferable) +{ + // make sure we have a good transferable + if ( !aTransferable ) + return NS_ERROR_INVALID_ARG; + + nsresult res = NS_ERROR_FAILURE; + + // get flavor list that includes all flavors that can be written (including ones + // obtained through conversion) + nsCOMPtr<nsIArray> flavorList; + res = aTransferable->FlavorsTransferableCanImport ( getter_AddRefs(flavorList) ); + if ( NS_FAILED(res) ) + return NS_ERROR_FAILURE; + + // Walk through flavors and see which flavor is on the clipboard them on the native clipboard, + uint32_t i; + uint32_t cnt; + flavorList->GetLength(&cnt); + for (i=0;i<cnt;i++) { + nsCOMPtr<nsISupportsCString> currentFlavor = do_QueryElementAt(flavorList, i); + if ( currentFlavor ) { + nsXPIDLCString flavorStr; + currentFlavor->ToString(getter_Copies(flavorStr)); + UINT format = GetFormat(flavorStr); + + // Try to get the data using the desired flavor. This might fail, but all is + // not lost. + void* data = nullptr; + uint32_t dataLen = 0; + bool dataFound = false; + if (nullptr != aDataObject) { + if ( NS_SUCCEEDED(GetNativeDataOffClipboard(aDataObject, anIndex, format, flavorStr, &data, &dataLen)) ) + dataFound = true; + } + else if (nullptr != aWindow) { + if ( NS_SUCCEEDED(GetNativeDataOffClipboard(aWindow, anIndex, format, &data, &dataLen)) ) + dataFound = true; + } + + // This is our second chance to try to find some data, having not found it + // when directly asking for the flavor. Let's try digging around in other + // flavors to help satisfy our craving for data. + if ( !dataFound ) { + if ( strcmp(flavorStr, kUnicodeMime) == 0 ) + dataFound = FindUnicodeFromPlainText ( aDataObject, anIndex, &data, &dataLen ); + else if ( strcmp(flavorStr, kURLMime) == 0 ) { + // drags from other windows apps expose the native + // CFSTR_INETURL{A,W} flavor + dataFound = FindURLFromNativeURL ( aDataObject, anIndex, &data, &dataLen ); + if ( !dataFound ) + dataFound = FindURLFromLocalFile ( aDataObject, anIndex, &data, &dataLen ); + } + } // if we try one last ditch effort to find our data + + // Hopefully by this point we've found it and can go about our business + if ( dataFound ) { + nsCOMPtr<nsISupports> genericDataWrapper; + if ( strcmp(flavorStr, kFileMime) == 0 ) { + // we have a file path in |data|. Create an nsLocalFile object. + nsDependentString filepath(reinterpret_cast<char16_t*>(data)); + nsCOMPtr<nsIFile> file; + if ( NS_SUCCEEDED(NS_NewLocalFile(filepath, false, getter_AddRefs(file))) ) + genericDataWrapper = do_QueryInterface(file); + free(data); + } + else if ( strcmp(flavorStr, kNativeHTMLMime) == 0 ) { + uint32_t dummy; + // the editor folks want CF_HTML exactly as it's on the clipboard, no conversions, + // no fancy stuff. Pull it off the clipboard, stuff it into a wrapper and hand + // it back to them. + if ( FindPlatformHTML(aDataObject, anIndex, &data, &dummy, &dataLen) ) + nsPrimitiveHelpers::CreatePrimitiveForData ( flavorStr, data, dataLen, getter_AddRefs(genericDataWrapper) ); + else + { + free(data); + continue; // something wrong with this flavor, keep looking for other data + } + free(data); + } + else if ( strcmp(flavorStr, kHTMLMime) == 0 ) { + uint32_t startOfData = 0; + // The JS folks want CF_HTML exactly as it is on the clipboard, but + // minus the CF_HTML header index information. + // It also needs to be converted to UTF16 and have linebreaks changed. + if ( FindPlatformHTML(aDataObject, anIndex, &data, &startOfData, &dataLen) ) { + dataLen -= startOfData; + nsPrimitiveHelpers::CreatePrimitiveForCFHTML ( static_cast<char*>(data) + startOfData, + &dataLen, getter_AddRefs(genericDataWrapper) ); + } + else + { + free(data); + continue; // something wrong with this flavor, keep looking for other data + } + free(data); + } + else if ( strcmp(flavorStr, kJPEGImageMime) == 0 || + strcmp(flavorStr, kJPGImageMime) == 0 || + strcmp(flavorStr, kPNGImageMime) == 0) { + nsIInputStream * imageStream = reinterpret_cast<nsIInputStream*>(data); + genericDataWrapper = do_QueryInterface(imageStream); + NS_IF_RELEASE(imageStream); + } + else { + // Treat custom types as a string of bytes. + if (strcmp(flavorStr, kCustomTypesMime) != 0) { + // we probably have some form of text. The DOM only wants LF, so convert from Win32 line + // endings to DOM line endings. + int32_t signedLen = static_cast<int32_t>(dataLen); + nsLinebreakHelpers::ConvertPlatformToDOMLinebreaks ( flavorStr, &data, &signedLen ); + dataLen = signedLen; + + if (strcmp(flavorStr, kRTFMime) == 0) { + // RTF on Windows is known to sometimes deliver an extra null byte. + if (dataLen > 0 && static_cast<char*>(data)[dataLen - 1] == '\0') + dataLen--; + } + } + + nsPrimitiveHelpers::CreatePrimitiveForData ( flavorStr, data, dataLen, getter_AddRefs(genericDataWrapper) ); + free(data); + } + + NS_ASSERTION ( genericDataWrapper, "About to put null data into the transferable" ); + aTransferable->SetTransferData(flavorStr, genericDataWrapper, dataLen); + res = NS_OK; + + // we found one, get out of the loop + break; + } + + } + } // foreach flavor + + return res; + +} + + + +// +// FindPlatformHTML +// +// Someone asked for the OS CF_HTML flavor. We give it back to them exactly as-is. +// +bool +nsClipboard :: FindPlatformHTML ( IDataObject* inDataObject, UINT inIndex, + void** outData, uint32_t* outStartOfData, + uint32_t* outDataLen ) +{ + // Reference: MSDN doc entitled "HTML Clipboard Format" + // http://msdn.microsoft.com/en-us/library/aa767917(VS.85).aspx#unknown_854 + // CF_HTML is UTF8, not unicode. We also can't rely on it being null-terminated + // so we have to check the CF_HTML header for the correct length. + // The length we return is the bytecount from the beginning of the selected data to the end + // of the selected data, without the null termination. Because it's UTF8, we're guaranteed + // the header is ASCII. + + if (!outData || !*outData) { + return false; + } + + char version[8] = { 0 }; + int32_t startOfData = 0; + int32_t endOfData = 0; + int numFound = sscanf((char*)*outData, "Version:%7s\nStartHTML:%d\nEndHTML:%d", + version, &startOfData, &endOfData); + + if (numFound != 3 || startOfData < -1 || endOfData < -1) { + return false; + } + + // Fixup the start and end markers if they have no context (set to -1) + if (startOfData == -1) { + startOfData = 0; + } + if (endOfData == -1) { + endOfData = *outDataLen; + } + + // Make sure we were passed sane values within our buffer size. + // (Note that we've handled all cases of negative endOfData above, so we can + // safely cast it to be unsigned here.) + if (!endOfData || startOfData >= endOfData || + static_cast<uint32_t>(endOfData) > *outDataLen) { + return false; + } + + // We want to return the buffer not offset by startOfData because it will be + // parsed out later (probably by HTMLEditor::ParseCFHTML) when it is still + // in CF_HTML format. + + // We return the byte offset from the start of the data buffer to where the + // HTML data starts. The caller might want to extract the HTML only. + *outStartOfData = startOfData; + *outDataLen = endOfData; + return true; +} + + +// +// FindUnicodeFromPlainText +// +// we are looking for text/unicode and we failed to find it on the clipboard first, +// try again with text/plain. If that is present, convert it to unicode. +// +bool +nsClipboard :: FindUnicodeFromPlainText ( IDataObject* inDataObject, UINT inIndex, void** outData, uint32_t* outDataLen ) +{ + // we are looking for text/unicode and we failed to find it on the clipboard first, + // try again with text/plain. If that is present, convert it to unicode. + nsresult rv = GetNativeDataOffClipboard(inDataObject, inIndex, GetFormat(kTextMime), nullptr, outData, outDataLen); + if (NS_FAILED(rv) || !*outData) { + return false; + } + + const char* castedText = static_cast<char*>(*outData); + nsAutoString tmp; + rv = NS_CopyNativeToUnicode(nsDependentCSubstring(castedText, *outDataLen), tmp); + if (NS_FAILED(rv)) { + return false; + } + + // out with the old, in with the new + free(*outData); + *outData = ToNewUnicode(tmp); + *outDataLen = tmp.Length() * sizeof(char16_t); + + return true; + +} // FindUnicodeFromPlainText + + +// +// FindURLFromLocalFile +// +// we are looking for a URL and couldn't find it, try again with looking for +// a local file. If we have one, it may either be a normal file or an internet shortcut. +// In both cases, however, we can get a URL (it will be a file:// url in the +// local file case). +// +bool +nsClipboard :: FindURLFromLocalFile ( IDataObject* inDataObject, UINT inIndex, void** outData, uint32_t* outDataLen ) +{ + bool dataFound = false; + + nsresult loadResult = GetNativeDataOffClipboard(inDataObject, inIndex, GetFormat(kFileMime), nullptr, outData, outDataLen); + if ( NS_SUCCEEDED(loadResult) && *outData ) { + // we have a file path in |data|. Is it an internet shortcut or a normal file? + const nsDependentString filepath(static_cast<char16_t*>(*outData)); + nsCOMPtr<nsIFile> file; + nsresult rv = NS_NewLocalFile(filepath, true, getter_AddRefs(file)); + if (NS_FAILED(rv)) { + free(*outData); + return dataFound; + } + + if ( IsInternetShortcut(filepath) ) { + free(*outData); + nsAutoCString url; + ResolveShortcut( file, url ); + if ( !url.IsEmpty() ) { + // convert it to unicode and pass it out + NS_ConvertUTF8toUTF16 urlString(url); + // the internal mozilla URL format, text/x-moz-url, contains + // URL\ntitle. We can guess the title from the file's name. + nsAutoString title; + file->GetLeafName(title); + // We rely on IsInternetShortcut check that file has a .url extension. + title.SetLength(title.Length() - 4); + if (title.IsEmpty()) + title = urlString; + *outData = ToNewUnicode(urlString + NS_LITERAL_STRING("\n") + title); + *outDataLen = NS_strlen(static_cast<char16_t*>(*outData)) * sizeof(char16_t); + + dataFound = true; + } + } + else { + // we have a normal file, use some Necko objects to get our file path + nsAutoCString urlSpec; + NS_GetURLSpecFromFile(file, urlSpec); + + // convert it to unicode and pass it out + free(*outData); + *outData = UTF8ToNewUnicode(urlSpec); + *outDataLen = NS_strlen(static_cast<char16_t*>(*outData)) * sizeof(char16_t); + dataFound = true; + } // else regular file + } + + return dataFound; +} // FindURLFromLocalFile + +// +// FindURLFromNativeURL +// +// we are looking for a URL and couldn't find it using our internal +// URL flavor, so look for it using the native URL flavor, +// CF_INETURLSTRW (We don't handle CF_INETURLSTRA currently) +// +bool +nsClipboard :: FindURLFromNativeURL ( IDataObject* inDataObject, UINT inIndex, void** outData, uint32_t* outDataLen ) +{ + bool dataFound = false; + + void* tempOutData = nullptr; + uint32_t tempDataLen = 0; + + nsresult loadResult = GetNativeDataOffClipboard(inDataObject, inIndex, ::RegisterClipboardFormat(CFSTR_INETURLW), nullptr, &tempOutData, &tempDataLen); + if ( NS_SUCCEEDED(loadResult) && tempOutData ) { + nsDependentString urlString(static_cast<char16_t*>(tempOutData)); + // the internal mozilla URL format, text/x-moz-url, contains + // URL\ntitle. Since we don't actually have a title here, + // just repeat the URL to fake it. + *outData = ToNewUnicode(urlString + NS_LITERAL_STRING("\n") + urlString); + *outDataLen = NS_strlen(static_cast<char16_t*>(*outData)) * sizeof(char16_t); + free(tempOutData); + dataFound = true; + } + else { + loadResult = GetNativeDataOffClipboard(inDataObject, inIndex, ::RegisterClipboardFormat(CFSTR_INETURLA), nullptr, &tempOutData, &tempDataLen); + if ( NS_SUCCEEDED(loadResult) && tempOutData ) { + // CFSTR_INETURLA is (currently) equal to CFSTR_SHELLURL which is equal to CF_TEXT + // which is by definition ANSI encoded. + nsCString urlUnescapedA; + bool unescaped = NS_UnescapeURL(static_cast<char*>(tempOutData), tempDataLen, esc_OnlyNonASCII | esc_SkipControl, urlUnescapedA); + + nsString urlString; + if (unescaped) + NS_CopyNativeToUnicode(urlUnescapedA, urlString); + else + NS_CopyNativeToUnicode(nsDependentCString(static_cast<char*>(tempOutData), tempDataLen), urlString); + + // the internal mozilla URL format, text/x-moz-url, contains + // URL\ntitle. Since we don't actually have a title here, + // just repeat the URL to fake it. + *outData = ToNewUnicode(urlString + NS_LITERAL_STRING("\n") + urlString); + *outDataLen = NS_strlen(static_cast<char16_t*>(*outData)) * sizeof(char16_t); + free(tempOutData); + dataFound = true; + } + } + + return dataFound; +} // FindURLFromNativeURL + +// +// ResolveShortcut +// +void +nsClipboard :: ResolveShortcut ( nsIFile* aFile, nsACString& outURL ) +{ + nsCOMPtr<nsIFileProtocolHandler> fph; + nsresult rv = NS_GetFileProtocolHandler(getter_AddRefs(fph)); + if (NS_FAILED(rv)) + return; + + nsCOMPtr<nsIURI> uri; + rv = fph->ReadURLFile(aFile, getter_AddRefs(uri)); + if (NS_FAILED(rv)) + return; + + uri->GetSpec(outURL); +} // ResolveShortcut + + +// +// IsInternetShortcut +// +// A file is an Internet Shortcut if it ends with .URL +// +bool +nsClipboard :: IsInternetShortcut ( const nsAString& inFileName ) +{ + return StringEndsWith(inFileName, NS_LITERAL_STRING(".url"), nsCaseInsensitiveStringComparator()); +} // IsInternetShortcut + + +//------------------------------------------------------------------------- +NS_IMETHODIMP +nsClipboard::GetNativeClipboardData ( nsITransferable * aTransferable, int32_t aWhichClipboard ) +{ + // make sure we have a good transferable + if ( !aTransferable || aWhichClipboard != kGlobalClipboard ) + return NS_ERROR_FAILURE; + + nsresult res; + + // This makes sure we can use the OLE functionality for the clipboard + IDataObject * dataObj; + if (S_OK == ::OleGetClipboard(&dataObj)) { + // Use OLE IDataObject for clipboard operations + res = GetDataFromDataObject(dataObj, 0, nullptr, aTransferable); + dataObj->Release(); + } + else { + // do it the old manual way + res = GetDataFromDataObject(nullptr, 0, mWindow, aTransferable); + } + return res; + +} + +NS_IMETHODIMP +nsClipboard::EmptyClipboard(int32_t aWhichClipboard) +{ + // Some programs such as ZoneAlarm monitor clipboard usage and then open the + // clipboard to scan it. If we i) empty and then ii) set data, then the + // 'set data' can sometimes fail with access denied becacuse another program + // has the clipboard open. So to avoid this race condition for OpenClipboard + // we do not empty the clipboard when we're setting it. + if (aWhichClipboard == kGlobalClipboard && !mEmptyingForSetData) { + OleSetClipboard(nullptr); + } + return nsBaseClipboard::EmptyClipboard(aWhichClipboard); +} + +//------------------------------------------------------------------------- +NS_IMETHODIMP nsClipboard::HasDataMatchingFlavors(const char** aFlavorList, + uint32_t aLength, + int32_t aWhichClipboard, + bool *_retval) +{ + *_retval = false; + if (aWhichClipboard != kGlobalClipboard || !aFlavorList) + return NS_OK; + + for (uint32_t i = 0;i < aLength; ++i) { +#ifdef DEBUG + if (strcmp(aFlavorList[i], kTextMime) == 0) + NS_WARNING ( "DO NOT USE THE text/plain DATA FLAVOR ANY MORE. USE text/unicode INSTEAD" ); +#endif + + UINT format = GetFormat(aFlavorList[i]); + if (IsClipboardFormatAvailable(format)) { + *_retval = true; + break; + } + else { + // We haven't found the exact flavor the client asked for, but maybe we can + // still find it from something else that's on the clipboard... + if (strcmp(aFlavorList[i], kUnicodeMime) == 0) { + // client asked for unicode and it wasn't present, check if we have CF_TEXT. + // We'll handle the actual data substitution in the data object. + if (IsClipboardFormatAvailable(GetFormat(kTextMime))) + *_retval = true; + } + } + } + + return NS_OK; +} diff --git a/widget/windows/nsClipboard.h b/widget/windows/nsClipboard.h new file mode 100644 index 0000000000..18308d6dbb --- /dev/null +++ b/widget/windows/nsClipboard.h @@ -0,0 +1,92 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsClipboard_h__ +#define nsClipboard_h__ + +#include "nsBaseClipboard.h" +#include "nsIObserver.h" +#include "nsIURI.h" +#include <windows.h> + +class nsITransferable; +class nsIWidget; +class nsIFile; +struct IDataObject; + +/** + * Native Win32 Clipboard wrapper + */ + +class nsClipboard : public nsBaseClipboard, + public nsIObserver +{ + virtual ~nsClipboard(); + +public: + nsClipboard(); + + NS_DECL_ISUPPORTS_INHERITED + + // nsIObserver + NS_DECL_NSIOBSERVER + + // nsIClipboard + NS_IMETHOD HasDataMatchingFlavors(const char** aFlavorList, uint32_t aLength, + int32_t aWhichClipboard, bool *_retval) override; + NS_IMETHOD EmptyClipboard(int32_t aWhichClipboard) override; + + // Internal Native Routines + static nsresult CreateNativeDataObject(nsITransferable * aTransferable, + IDataObject ** aDataObj, + nsIURI * uri); + static nsresult SetupNativeDataObject(nsITransferable * aTransferable, + IDataObject * aDataObj); + static nsresult GetDataFromDataObject(IDataObject * aDataObject, + UINT anIndex, + nsIWidget * aWindow, + nsITransferable * aTransferable); + static nsresult GetNativeDataOffClipboard(nsIWidget * aWindow, UINT aIndex, UINT aFormat, void ** aData, uint32_t * aLen); + static nsresult GetNativeDataOffClipboard(IDataObject * aDataObject, UINT aIndex, UINT aFormat, const char * aMIMEImageFormat, void ** aData, uint32_t * aLen); + static nsresult GetGlobalData(HGLOBAL aHGBL, void ** aData, uint32_t * aLen); + + // This function returns the internal Windows clipboard format identifier + // for a given Mime string. The default is to map kHTMLMime ("text/html") + // to the clipboard format CF_HTML ("HTLM Format"), but it can also be + // registered as clipboard format "text/html" to support previous versions + // of Gecko. + static UINT GetFormat(const char* aMimeStr, bool aMapHTMLMime = true); + + static UINT CF_HTML; + static UINT CF_CUSTOMTYPES; + +protected: + NS_IMETHOD SetNativeClipboardData ( int32_t aWhichClipboard ) override; + NS_IMETHOD GetNativeClipboardData ( nsITransferable * aTransferable, int32_t aWhichClipboard ) override; + + static bool IsInternetShortcut ( const nsAString& inFileName ) ; + static bool FindURLFromLocalFile ( IDataObject* inDataObject, UINT inIndex, void** outData, uint32_t* outDataLen ) ; + static bool FindURLFromNativeURL ( IDataObject* inDataObject, UINT inIndex, void** outData, uint32_t* outDataLen ) ; + static bool FindUnicodeFromPlainText ( IDataObject* inDataObject, UINT inIndex, void** outData, uint32_t* outDataLen ) ; + static bool FindPlatformHTML ( IDataObject* inDataObject, UINT inIndex, void** outData, + uint32_t* outStartOfData, uint32_t* outDataLen ); + static void ResolveShortcut ( nsIFile* inFileName, nsACString& outURL ) ; + + nsIWidget * mWindow; + +}; + +#define SET_FORMATETC(fe, cf, td, asp, li, med) \ + {\ + (fe).cfFormat=cf;\ + (fe).ptd=td;\ + (fe).dwAspect=asp;\ + (fe).lindex=li;\ + (fe).tymed=med;\ + } + + +#endif // nsClipboard_h__ + diff --git a/widget/windows/nsColorPicker.cpp b/widget/windows/nsColorPicker.cpp new file mode 100644 index 0000000000..777169f978 --- /dev/null +++ b/widget/windows/nsColorPicker.cpp @@ -0,0 +1,212 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsColorPicker.h" + +#include <shlwapi.h> + +#include "mozilla/AutoRestore.h" +#include "nsIWidget.h" +#include "nsString.h" +#include "WidgetUtils.h" + +using namespace mozilla::widget; + +namespace +{ +// Manages NS_NATIVE_TMP_WINDOW child windows. NS_NATIVE_TMP_WINDOWs are +// temporary child windows of mParentWidget created to address RTL issues +// in picker dialogs. We are responsible for destroying these. +class AutoDestroyTmpWindow +{ +public: + explicit AutoDestroyTmpWindow(HWND aTmpWnd) : + mWnd(aTmpWnd) { + } + + ~AutoDestroyTmpWindow() { + if (mWnd) + DestroyWindow(mWnd); + } + + inline HWND get() const { return mWnd; } +private: + HWND mWnd; +}; + +static DWORD ColorStringToRGB(const nsAString& aColor) +{ + DWORD result = 0; + + for (uint32_t i = 1; i < aColor.Length(); ++i) { + result *= 16; + + char16_t c = aColor[i]; + if (c >= '0' && c <= '9') { + result += c - '0'; + } else if (c >= 'a' && c <= 'f') { + result += 10 + (c - 'a'); + } else { + result += 10 + (c - 'A'); + } + } + + DWORD r = result & 0x00FF0000; + DWORD g = result & 0x0000FF00; + DWORD b = result & 0x000000FF; + + r = r >> 16; + b = b << 16; + + result = r | g | b; + + return result; +} + +static nsString ToHexString(BYTE n) +{ + nsString result; + if (n <= 0x0F) { + result.Append('0'); + } + result.AppendInt(n, 16); + return result; +} + + +static void +BGRIntToRGBString(DWORD color, nsAString& aResult) +{ + BYTE r = GetRValue(color); + BYTE g = GetGValue(color); + BYTE b = GetBValue(color); + + aResult.Assign('#'); + aResult.Append(ToHexString(r)); + aResult.Append(ToHexString(g)); + aResult.Append(ToHexString(b)); +} +} // namespace + +static AsyncColorChooser* gColorChooser; + +AsyncColorChooser::AsyncColorChooser(COLORREF aInitialColor, + nsIWidget* aParentWidget, + nsIColorPickerShownCallback* aCallback) + : mInitialColor(aInitialColor) + , mColor(aInitialColor) + , mParentWidget(aParentWidget) + , mCallback(aCallback) +{ +} + +NS_IMETHODIMP +AsyncColorChooser::Run() +{ + static COLORREF sCustomColors[16] = {0} ; + + MOZ_ASSERT(NS_IsMainThread(), + "Color pickers can only be opened from main thread currently"); + + // Allow only one color picker to be opened at a time, to workaround bug 944737 + if (!gColorChooser) { + mozilla::AutoRestore<AsyncColorChooser*> restoreColorChooser(gColorChooser); + gColorChooser = this; + + AutoDestroyTmpWindow adtw((HWND) (mParentWidget.get() ? + mParentWidget->GetNativeData(NS_NATIVE_TMP_WINDOW) : nullptr)); + + CHOOSECOLOR options; + options.lStructSize = sizeof(options); + options.hwndOwner = adtw.get(); + options.Flags = CC_RGBINIT | CC_FULLOPEN | CC_ENABLEHOOK; + options.rgbResult = mInitialColor; + options.lpCustColors = sCustomColors; + options.lpfnHook = HookProc; + + mColor = ChooseColor(&options) ? options.rgbResult : mInitialColor; + } else { + NS_WARNING("Currently, it's not possible to open more than one color " + "picker at a time"); + mColor = mInitialColor; + } + + if (mCallback) { + nsAutoString colorStr; + BGRIntToRGBString(mColor, colorStr); + mCallback->Done(colorStr); + } + + return NS_OK; +} + +void +AsyncColorChooser::Update(COLORREF aColor) +{ + if (mColor != aColor) { + mColor = aColor; + + nsAutoString colorStr; + BGRIntToRGBString(mColor, colorStr); + mCallback->Update(colorStr); + } +} + +/* static */ UINT_PTR CALLBACK +AsyncColorChooser::HookProc(HWND aDialog, UINT aMsg, + WPARAM aWParam, LPARAM aLParam) +{ + if (!gColorChooser) { + return 0; + } + + if (aMsg == WM_CTLCOLORSTATIC) { + // The color picker does not expose a proper way to retrieve the current + // color, so we need to obtain it from the static control displaying the + // current color instead. + const int kCurrentColorBoxID = 709; + if ((HWND)aLParam == GetDlgItem(aDialog, kCurrentColorBoxID)) { + gColorChooser->Update(GetPixel((HDC)aWParam, 0, 0)); + } + } + + return 0; +} + +/////////////////////////////////////////////////////////////////////////////// +// nsIColorPicker + +nsColorPicker::nsColorPicker() +{ +} + +nsColorPicker::~nsColorPicker() +{ +} + +NS_IMPL_ISUPPORTS(nsColorPicker, nsIColorPicker) + +NS_IMETHODIMP +nsColorPicker::Init(mozIDOMWindowProxy* parent, + const nsAString& title, + const nsAString& aInitialColor) +{ + NS_PRECONDITION(parent, + "Null parent passed to colorpicker, no color picker for you!"); + mParentWidget = WidgetUtils::DOMWindowToWidget(nsPIDOMWindowOuter::From(parent)); + mInitialColor = ColorStringToRGB(aInitialColor); + return NS_OK; +} + +NS_IMETHODIMP +nsColorPicker::Open(nsIColorPickerShownCallback* aCallback) +{ + NS_ENSURE_ARG(aCallback); + nsCOMPtr<nsIRunnable> event = new AsyncColorChooser(mInitialColor, + mParentWidget, + aCallback); + return NS_DispatchToMainThread(event); +} diff --git a/widget/windows/nsColorPicker.h b/widget/windows/nsColorPicker.h new file mode 100644 index 0000000000..2227ba604a --- /dev/null +++ b/widget/windows/nsColorPicker.h @@ -0,0 +1,59 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsColorPicker_h__ +#define nsColorPicker_h__ + +#include <windows.h> +#include <commdlg.h> + +#include "nsCOMPtr.h" +#include "nsIColorPicker.h" +#include "nsThreadUtils.h" + +class nsIWidget; + +class AsyncColorChooser : + public mozilla::Runnable +{ +public: + AsyncColorChooser(COLORREF aInitialColor, + nsIWidget* aParentWidget, + nsIColorPickerShownCallback* aCallback); + NS_IMETHOD Run() override; + +private: + void Update(COLORREF aColor); + + static UINT_PTR CALLBACK HookProc(HWND aDialog, UINT aMsg, + WPARAM aWParam, LPARAM aLParam); + + COLORREF mInitialColor; + COLORREF mColor; + nsCOMPtr<nsIWidget> mParentWidget; + nsCOMPtr<nsIColorPickerShownCallback> mCallback; +}; + +class nsColorPicker : + public nsIColorPicker +{ + virtual ~nsColorPicker(); + +public: + nsColorPicker(); + + NS_DECL_ISUPPORTS + + NS_IMETHOD Init(mozIDOMWindowProxy* parent, const nsAString& title, + const nsAString& aInitialColor); + NS_IMETHOD Open(nsIColorPickerShownCallback* aCallback); + +private: + COLORREF mInitialColor; + nsCOMPtr<nsIWidget> mParentWidget; +}; + +#endif // nsColorPicker_h__ diff --git a/widget/windows/nsDataObj.cpp b/widget/windows/nsDataObj.cpp new file mode 100644 index 0000000000..fc45968ae3 --- /dev/null +++ b/widget/windows/nsDataObj.cpp @@ -0,0 +1,2166 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/ArrayUtils.h" + +#include <ole2.h> +#include <shlobj.h> + +#include "nsDataObj.h" +#include "nsArrayUtils.h" +#include "nsClipboard.h" +#include "nsReadableUtils.h" +#include "nsITransferable.h" +#include "nsISupportsPrimitives.h" +#include "IEnumFE.h" +#include "nsPrimitiveHelpers.h" +#include "nsXPIDLString.h" +#include "nsImageClipboard.h" +#include "nsCRT.h" +#include "nsPrintfCString.h" +#include "nsIStringBundle.h" +#include "nsEscape.h" +#include "nsIURL.h" +#include "nsNetUtil.h" +#include "mozilla/Services.h" +#include "nsIOutputStream.h" +#include "nsXPCOMStrings.h" +#include "nscore.h" +#include "nsDirectoryServiceDefs.h" +#include "nsITimer.h" +#include "nsThreadUtils.h" +#include "mozilla/Preferences.h" +#include "nsIContentPolicy.h" +#include "nsContentUtils.h" +#include "nsIPrincipal.h" + +#include "WinUtils.h" +#include "mozilla/LazyIdleThread.h" +#include "mozilla/WindowsVersion.h" +#include <algorithm> + + +using namespace mozilla; +using namespace mozilla::widget; + +#define DEFAULT_THREAD_TIMEOUT_MS 30000 + +NS_IMPL_ISUPPORTS(nsDataObj::CStream, nsIStreamListener) + +//----------------------------------------------------------------------------- +// CStream implementation +nsDataObj::CStream::CStream() : + mChannelRead(false), + mStreamRead(0) +{ +} + +//----------------------------------------------------------------------------- +nsDataObj::CStream::~CStream() +{ +} + +//----------------------------------------------------------------------------- +// helper - initializes the stream +nsresult nsDataObj::CStream::Init(nsIURI *pSourceURI, + uint32_t aContentPolicyType, + nsIPrincipal* aRequestingPrincipal) +{ + // we can not create a channel without a requestingPrincipal + if (!aRequestingPrincipal) { + return NS_ERROR_FAILURE; + } + nsresult rv; + rv = NS_NewChannel(getter_AddRefs(mChannel), + pSourceURI, + aRequestingPrincipal, + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS, + aContentPolicyType, + nullptr, // loadGroup + nullptr, // aCallbacks + nsIRequest::LOAD_FROM_CACHE); + + NS_ENSURE_SUCCESS(rv, rv); + rv = mChannel->AsyncOpen2(this); + NS_ENSURE_SUCCESS(rv, rv); + return NS_OK; +} + +//----------------------------------------------------------------------------- +// IUnknown's QueryInterface, nsISupport's AddRef and Release are shared by +// IUnknown and nsIStreamListener. +STDMETHODIMP nsDataObj::CStream::QueryInterface(REFIID refiid, void** ppvResult) +{ + *ppvResult = nullptr; + if (IID_IUnknown == refiid || + refiid == IID_IStream) + + { + *ppvResult = this; + } + + if (nullptr != *ppvResult) + { + ((LPUNKNOWN)*ppvResult)->AddRef(); + return S_OK; + } + + return E_NOINTERFACE; +} + +// nsIStreamListener implementation +NS_IMETHODIMP +nsDataObj::CStream::OnDataAvailable(nsIRequest *aRequest, + nsISupports *aContext, + nsIInputStream *aInputStream, + uint64_t aOffset, // offset within the stream + uint32_t aCount) // bytes available on this call +{ + // Extend the write buffer for the incoming data. + uint8_t* buffer = mChannelData.AppendElements(aCount, fallible); + if (!buffer) { + return NS_ERROR_OUT_OF_MEMORY; + } + NS_ASSERTION((mChannelData.Length() == (aOffset + aCount)), + "stream length mismatch w/write buffer"); + + // Read() may not return aCount on a single call, so loop until we've + // accumulated all the data OnDataAvailable has promised. + nsresult rv; + uint32_t odaBytesReadTotal = 0; + do { + uint32_t bytesReadByCall = 0; + rv = aInputStream->Read((char*)(buffer + odaBytesReadTotal), + aCount, &bytesReadByCall); + odaBytesReadTotal += bytesReadByCall; + } while (aCount < odaBytesReadTotal && NS_SUCCEEDED(rv)); + return rv; +} + +NS_IMETHODIMP nsDataObj::CStream::OnStartRequest(nsIRequest *aRequest, + nsISupports *aContext) +{ + mChannelResult = NS_OK; + return NS_OK; +} + +NS_IMETHODIMP nsDataObj::CStream::OnStopRequest(nsIRequest *aRequest, + nsISupports *aContext, + nsresult aStatusCode) +{ + mChannelRead = true; + mChannelResult = aStatusCode; + return NS_OK; +} + +// Pumps thread messages while waiting for the async listener operation to +// complete. Failing this call will fail the stream incall from Windows +// and cancel the operation. +nsresult nsDataObj::CStream::WaitForCompletion() +{ + // We are guaranteed OnStopRequest will get called, so this should be ok. + while (!mChannelRead) { + // Pump messages + NS_ProcessNextEvent(nullptr, true); + } + + if (!mChannelData.Length()) + mChannelResult = NS_ERROR_FAILURE; + + return mChannelResult; +} + +//----------------------------------------------------------------------------- +// IStream +STDMETHODIMP nsDataObj::CStream::Clone(IStream** ppStream) +{ + return E_NOTIMPL; +} + +//----------------------------------------------------------------------------- +STDMETHODIMP nsDataObj::CStream::Commit(DWORD dwFrags) +{ + return E_NOTIMPL; +} + +//----------------------------------------------------------------------------- +STDMETHODIMP nsDataObj::CStream::CopyTo(IStream* pDestStream, + ULARGE_INTEGER nBytesToCopy, + ULARGE_INTEGER* nBytesRead, + ULARGE_INTEGER* nBytesWritten) +{ + return E_NOTIMPL; +} + +//----------------------------------------------------------------------------- +STDMETHODIMP nsDataObj::CStream::LockRegion(ULARGE_INTEGER nStart, + ULARGE_INTEGER nBytes, + DWORD dwFlags) +{ + return E_NOTIMPL; +} + +//----------------------------------------------------------------------------- +STDMETHODIMP nsDataObj::CStream::Read(void* pvBuffer, + ULONG nBytesToRead, + ULONG* nBytesRead) +{ + // Wait for the write into our buffer to complete via the stream listener. + // We can't respond to this by saying "call us back later". + if (NS_FAILED(WaitForCompletion())) + return E_FAIL; + + // Bytes left for Windows to read out of our buffer + ULONG bytesLeft = mChannelData.Length() - mStreamRead; + // Let Windows know what we will hand back, usually this is the entire buffer + *nBytesRead = std::min(bytesLeft, nBytesToRead); + // Copy the buffer data over + memcpy(pvBuffer, ((char*)mChannelData.Elements() + mStreamRead), *nBytesRead); + // Update our bytes read tracking + mStreamRead += *nBytesRead; + return S_OK; +} + +//----------------------------------------------------------------------------- +STDMETHODIMP nsDataObj::CStream::Revert(void) +{ + return E_NOTIMPL; +} + +//----------------------------------------------------------------------------- +STDMETHODIMP nsDataObj::CStream::Seek(LARGE_INTEGER nMove, + DWORD dwOrigin, + ULARGE_INTEGER* nNewPos) +{ + if (nNewPos == nullptr) + return STG_E_INVALIDPOINTER; + + if (nMove.LowPart == 0 && nMove.HighPart == 0 && + (dwOrigin == STREAM_SEEK_SET || dwOrigin == STREAM_SEEK_CUR)) { + nNewPos->LowPart = 0; + nNewPos->HighPart = 0; + return S_OK; + } + + return E_NOTIMPL; +} + +//----------------------------------------------------------------------------- +STDMETHODIMP nsDataObj::CStream::SetSize(ULARGE_INTEGER nNewSize) +{ + return E_NOTIMPL; +} + +//----------------------------------------------------------------------------- +STDMETHODIMP nsDataObj::CStream::Stat(STATSTG* statstg, DWORD dwFlags) +{ + if (statstg == nullptr) + return STG_E_INVALIDPOINTER; + + if (!mChannel || NS_FAILED(WaitForCompletion())) + return E_FAIL; + + memset((void*)statstg, 0, sizeof(STATSTG)); + + if (dwFlags != STATFLAG_NONAME) + { + nsCOMPtr<nsIURI> sourceURI; + if (NS_FAILED(mChannel->GetURI(getter_AddRefs(sourceURI)))) { + return E_FAIL; + } + + nsAutoCString strFileName; + nsCOMPtr<nsIURL> sourceURL = do_QueryInterface(sourceURI); + sourceURL->GetFileName(strFileName); + + if (strFileName.IsEmpty()) + return E_FAIL; + + NS_UnescapeURL(strFileName); + NS_ConvertUTF8toUTF16 wideFileName(strFileName); + + uint32_t nMaxNameLength = (wideFileName.Length()*2) + 2; + void * retBuf = CoTaskMemAlloc(nMaxNameLength); // freed by caller + if (!retBuf) + return STG_E_INSUFFICIENTMEMORY; + + ZeroMemory(retBuf, nMaxNameLength); + memcpy(retBuf, wideFileName.get(), wideFileName.Length()*2); + statstg->pwcsName = (LPOLESTR)retBuf; + } + + SYSTEMTIME st; + + statstg->type = STGTY_STREAM; + + GetSystemTime(&st); + SystemTimeToFileTime((const SYSTEMTIME*)&st, (LPFILETIME)&statstg->mtime); + statstg->ctime = statstg->atime = statstg->mtime; + + statstg->cbSize.LowPart = (DWORD)mChannelData.Length(); + statstg->grfMode = STGM_READ; + statstg->grfLocksSupported = LOCK_ONLYONCE; + statstg->clsid = CLSID_NULL; + + return S_OK; +} + +//----------------------------------------------------------------------------- +STDMETHODIMP nsDataObj::CStream::UnlockRegion(ULARGE_INTEGER nStart, + ULARGE_INTEGER nBytes, + DWORD dwFlags) +{ + return E_NOTIMPL; +} + +//----------------------------------------------------------------------------- +STDMETHODIMP nsDataObj::CStream::Write(const void* pvBuffer, + ULONG nBytesToRead, + ULONG* nBytesRead) +{ + return E_NOTIMPL; +} + +//----------------------------------------------------------------------------- +HRESULT nsDataObj::CreateStream(IStream **outStream) +{ + NS_ENSURE_TRUE(outStream, E_INVALIDARG); + + nsresult rv = NS_ERROR_FAILURE; + nsAutoString wideFileName; + nsCOMPtr<nsIURI> sourceURI; + HRESULT res; + + res = GetDownloadDetails(getter_AddRefs(sourceURI), + wideFileName); + if(FAILED(res)) + return res; + + nsDataObj::CStream *pStream = new nsDataObj::CStream(); + NS_ENSURE_TRUE(pStream, E_OUTOFMEMORY); + + pStream->AddRef(); + + // query the requestingPrincipal from the transferable and add it to the new channel + nsCOMPtr<nsIPrincipal> requestingPrincipal; + mTransferable->GetRequestingPrincipal(getter_AddRefs(requestingPrincipal)); + MOZ_ASSERT(requestingPrincipal, "can not create channel without a principal"); + // default transferable content policy is nsIContentPolicy::TYPE_OTHER + uint32_t contentPolicyType = nsIContentPolicy::TYPE_OTHER; + mTransferable->GetContentPolicyType(&contentPolicyType); + rv = pStream->Init(sourceURI, contentPolicyType, requestingPrincipal); + if (NS_FAILED(rv)) + { + pStream->Release(); + return E_FAIL; + } + *outStream = pStream; + + return S_OK; +} + +static GUID CLSID_nsDataObj = + { 0x1bba7640, 0xdf52, 0x11cf, { 0x82, 0x7b, 0, 0xa0, 0x24, 0x3a, 0xe5, 0x05 } }; + +/* + * deliberately not using MAX_PATH. This is because on platforms < XP + * a file created with a long filename may be mishandled by the shell + * resulting in it not being able to be deleted or moved. + * See bug 250392 for more details. + */ +#define NS_MAX_FILEDESCRIPTOR 128 + 1 + +/* + * Class nsDataObj + */ + +//----------------------------------------------------- +// construction +//----------------------------------------------------- +nsDataObj::nsDataObj(nsIURI * uri) + : m_cRef(0), mTransferable(nullptr), + mIsAsyncMode(FALSE), mIsInOperation(FALSE) +{ + mIOThread = new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS, + NS_LITERAL_CSTRING("nsDataObj"), + LazyIdleThread::ManualShutdown); + m_enumFE = new CEnumFormatEtc(); + m_enumFE->AddRef(); + + if (uri) { + // A URI was obtained, so pass this through to the DataObject + // so it can create a SourceURL for CF_HTML flavour + uri->GetSpec(mSourceURL); + } +} +//----------------------------------------------------- +// destruction +//----------------------------------------------------- +nsDataObj::~nsDataObj() +{ + NS_IF_RELEASE(mTransferable); + + mDataFlavors.Clear(); + + m_enumFE->Release(); + + // Free arbitrary system formats + for (uint32_t idx = 0; idx < mDataEntryList.Length(); idx++) { + CoTaskMemFree(mDataEntryList[idx]->fe.ptd); + ReleaseStgMedium(&mDataEntryList[idx]->stgm); + CoTaskMemFree(mDataEntryList[idx]); + } +} + + +//----------------------------------------------------- +// IUnknown interface methods - see inknown.h for documentation +//----------------------------------------------------- +STDMETHODIMP nsDataObj::QueryInterface(REFIID riid, void** ppv) +{ + *ppv=nullptr; + + if ( (IID_IUnknown == riid) || (IID_IDataObject == riid) ) { + *ppv = this; + AddRef(); + return S_OK; + } else if (IID_IAsyncOperation == riid) { + *ppv = static_cast<IAsyncOperation*>(this); + AddRef(); + return S_OK; + } + + return E_NOINTERFACE; +} + +//----------------------------------------------------- +STDMETHODIMP_(ULONG) nsDataObj::AddRef() +{ + ++m_cRef; + NS_LOG_ADDREF(this, m_cRef, "nsDataObj", sizeof(*this)); + return m_cRef; +} + + +//----------------------------------------------------- +STDMETHODIMP_(ULONG) nsDataObj::Release() +{ + --m_cRef; + + NS_LOG_RELEASE(this, m_cRef, "nsDataObj"); + if (0 != m_cRef) + return m_cRef; + + // We have released our last ref on this object and need to delete the + // temp file. External app acting as drop target may still need to open the + // temp file. Addref a timer so it can delay deleting file and destroying + // this object. Delete file anyway and destroy this obj if there's a problem. + if (mCachedTempFile) { + nsresult rv; + mTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) { + mTimer->InitWithFuncCallback(nsDataObj::RemoveTempFile, this, + 500, nsITimer::TYPE_ONE_SHOT); + return AddRef(); + } + mCachedTempFile->Remove(false); + mCachedTempFile = nullptr; + } + + delete this; + + return 0; +} + +//----------------------------------------------------- +BOOL nsDataObj::FormatsMatch(const FORMATETC& source, const FORMATETC& target) const +{ + if ((source.cfFormat == target.cfFormat) && + (source.dwAspect & target.dwAspect) && + (source.tymed & target.tymed)) { + return TRUE; + } else { + return FALSE; + } +} + +//----------------------------------------------------- +// IDataObject methods +//----------------------------------------------------- +STDMETHODIMP nsDataObj::GetData(LPFORMATETC aFormat, LPSTGMEDIUM pSTM) +{ + if (!mTransferable) + return DV_E_FORMATETC; + + uint32_t dfInx = 0; + + static CLIPFORMAT fileDescriptorFlavorA = ::RegisterClipboardFormat( CFSTR_FILEDESCRIPTORA ); + static CLIPFORMAT fileDescriptorFlavorW = ::RegisterClipboardFormat( CFSTR_FILEDESCRIPTORW ); + static CLIPFORMAT uniformResourceLocatorA = ::RegisterClipboardFormat( CFSTR_INETURLA ); + static CLIPFORMAT uniformResourceLocatorW = ::RegisterClipboardFormat( CFSTR_INETURLW ); + static CLIPFORMAT fileFlavor = ::RegisterClipboardFormat( CFSTR_FILECONTENTS ); + static CLIPFORMAT PreferredDropEffect = ::RegisterClipboardFormat( CFSTR_PREFERREDDROPEFFECT ); + + // Arbitrary system formats are used for image feedback during drag + // and drop. We are responsible for storing these internally during + // drag operations. + LPDATAENTRY pde; + if (LookupArbitraryFormat(aFormat, &pde, FALSE)) { + return CopyMediumData(pSTM, &pde->stgm, aFormat, FALSE) + ? S_OK : E_UNEXPECTED; + } + + // Firefox internal formats + ULONG count; + FORMATETC fe; + m_enumFE->Reset(); + while (NOERROR == m_enumFE->Next(1, &fe, &count) + && dfInx < mDataFlavors.Length()) { + nsCString& df = mDataFlavors.ElementAt(dfInx); + if (FormatsMatch(fe, *aFormat)) { + pSTM->pUnkForRelease = nullptr; // caller is responsible for deleting this data + CLIPFORMAT format = aFormat->cfFormat; + switch(format) { + + // Someone is asking for plain or unicode text + case CF_TEXT: + case CF_UNICODETEXT: + return GetText(df, *aFormat, *pSTM); + + // Some 3rd party apps that receive drag and drop files from the browser + // window require support for this. + case CF_HDROP: + return GetFile(*aFormat, *pSTM); + + // Someone is asking for an image + case CF_DIBV5: + case CF_DIB: + return GetDib(df, *aFormat, *pSTM); + + default: + if ( format == fileDescriptorFlavorA ) + return GetFileDescriptor ( *aFormat, *pSTM, false ); + if ( format == fileDescriptorFlavorW ) + return GetFileDescriptor ( *aFormat, *pSTM, true); + if ( format == uniformResourceLocatorA ) + return GetUniformResourceLocator( *aFormat, *pSTM, false); + if ( format == uniformResourceLocatorW ) + return GetUniformResourceLocator( *aFormat, *pSTM, true); + if ( format == fileFlavor ) + return GetFileContents ( *aFormat, *pSTM ); + if ( format == PreferredDropEffect ) + return GetPreferredDropEffect( *aFormat, *pSTM ); + //MOZ_LOG(gWindowsLog, LogLevel::Info, + // ("***** nsDataObj::GetData - Unknown format %u\n", format)); + return GetText(df, *aFormat, *pSTM); + } //switch + } // if + dfInx++; + } // while + + return DATA_E_FORMATETC; +} + +//----------------------------------------------------- +STDMETHODIMP nsDataObj::GetDataHere(LPFORMATETC pFE, LPSTGMEDIUM pSTM) +{ + return E_FAIL; +} + + +//----------------------------------------------------- +// Other objects querying to see if we support a +// particular format +//----------------------------------------------------- +STDMETHODIMP nsDataObj::QueryGetData(LPFORMATETC pFE) +{ + // Arbitrary system formats are used for image feedback during drag + // and drop. We are responsible for storing these internally during + // drag operations. + LPDATAENTRY pde; + if (LookupArbitraryFormat(pFE, &pde, FALSE)) + return S_OK; + + // Firefox internal formats + ULONG count; + FORMATETC fe; + m_enumFE->Reset(); + while (NOERROR == m_enumFE->Next(1, &fe, &count)) { + if (fe.cfFormat == pFE->cfFormat) { + return S_OK; + } + } + return E_FAIL; +} + +//----------------------------------------------------- +STDMETHODIMP nsDataObj::GetCanonicalFormatEtc + (LPFORMATETC pFEIn, LPFORMATETC pFEOut) +{ + return E_NOTIMPL; +} + +//----------------------------------------------------- +STDMETHODIMP nsDataObj::SetData(LPFORMATETC aFormat, LPSTGMEDIUM aMedium, BOOL shouldRel) +{ + // Arbitrary system formats are used for image feedback during drag + // and drop. We are responsible for storing these internally during + // drag operations. + LPDATAENTRY pde; + if (LookupArbitraryFormat(aFormat, &pde, TRUE)) { + // Release the old data the lookup handed us for this format. This + // may have been set in CopyMediumData when we originally stored the + // data. + if (pde->stgm.tymed) { + ReleaseStgMedium(&pde->stgm); + memset(&pde->stgm, 0, sizeof(STGMEDIUM)); + } + + bool result = true; + if (shouldRel) { + // If shouldRel is TRUE, the data object called owns the storage medium + // after the call returns. Store the incoming data in our data array for + // release when we are destroyed. This is the common case with arbitrary + // data from explorer. + pde->stgm = *aMedium; + } else { + // Copy the incoming data into our data array. (AFAICT, this never gets + // called with arbitrary formats for drag images.) + result = CopyMediumData(&pde->stgm, aMedium, aFormat, TRUE); + } + pde->fe.tymed = pde->stgm.tymed; + + return result ? S_OK : DV_E_TYMED; + } + + if (shouldRel) + ReleaseStgMedium(aMedium); + + return S_OK; +} + +bool +nsDataObj::LookupArbitraryFormat(FORMATETC *aFormat, LPDATAENTRY *aDataEntry, BOOL aAddorUpdate) +{ + *aDataEntry = nullptr; + + if (aFormat->ptd != nullptr) + return false; + + // See if it's already in our list. If so return the data entry. + for (uint32_t idx = 0; idx < mDataEntryList.Length(); idx++) { + if (mDataEntryList[idx]->fe.cfFormat == aFormat->cfFormat && + mDataEntryList[idx]->fe.dwAspect == aFormat->dwAspect && + mDataEntryList[idx]->fe.lindex == aFormat->lindex) { + if (aAddorUpdate || (mDataEntryList[idx]->fe.tymed & aFormat->tymed)) { + // If the caller requests we update, or if the + // medium type matches, return the entry. + *aDataEntry = mDataEntryList[idx]; + return true; + } else { + // Medium does not match, not found. + return false; + } + } + } + + if (!aAddorUpdate) + return false; + + // Add another entry to mDataEntryList + LPDATAENTRY dataEntry = (LPDATAENTRY)CoTaskMemAlloc(sizeof(DATAENTRY)); + if (!dataEntry) + return false; + + dataEntry->fe = *aFormat; + *aDataEntry = dataEntry; + memset(&dataEntry->stgm, 0, sizeof(STGMEDIUM)); + + // Add this to our IEnumFORMATETC impl. so we can return it when + // it's requested. + m_enumFE->AddFormatEtc(aFormat); + + // Store a copy internally in the arbitrary formats array. + mDataEntryList.AppendElement(dataEntry); + + return true; +} + +bool +nsDataObj::CopyMediumData(STGMEDIUM *aMediumDst, STGMEDIUM *aMediumSrc, LPFORMATETC aFormat, BOOL aSetData) +{ + STGMEDIUM stgmOut = *aMediumSrc; + + switch (stgmOut.tymed) { + case TYMED_ISTREAM: + stgmOut.pstm->AddRef(); + break; + case TYMED_ISTORAGE: + stgmOut.pstg->AddRef(); + break; + case TYMED_HGLOBAL: + if (!aMediumSrc->pUnkForRelease) { + if (aSetData) { + if (aMediumSrc->tymed != TYMED_HGLOBAL) + return false; + stgmOut.hGlobal = OleDuplicateData(aMediumSrc->hGlobal, aFormat->cfFormat, 0); + if (!stgmOut.hGlobal) + return false; + } else { + // We are returning this data from LookupArbitraryFormat, indicate to the + // shell we hold it and will free it. + stgmOut.pUnkForRelease = static_cast<IDataObject*>(this); + } + } + break; + default: + return false; + } + + if (stgmOut.pUnkForRelease) + stgmOut.pUnkForRelease->AddRef(); + + *aMediumDst = stgmOut; + + return true; +} + +//----------------------------------------------------- +STDMETHODIMP nsDataObj::EnumFormatEtc(DWORD dwDir, LPENUMFORMATETC *ppEnum) +{ + switch (dwDir) { + case DATADIR_GET: + m_enumFE->Clone(ppEnum); + break; + case DATADIR_SET: + // fall through + default: + *ppEnum = nullptr; + } // switch + + if (nullptr == *ppEnum) + return E_FAIL; + + (*ppEnum)->Reset(); + // Clone already AddRefed the result so don't addref it again. + return NOERROR; +} + +//----------------------------------------------------- +STDMETHODIMP nsDataObj::DAdvise(LPFORMATETC pFE, DWORD dwFlags, + LPADVISESINK pIAdviseSink, DWORD* pdwConn) +{ + return OLE_E_ADVISENOTSUPPORTED; +} + + +//----------------------------------------------------- +STDMETHODIMP nsDataObj::DUnadvise(DWORD dwConn) +{ + return OLE_E_ADVISENOTSUPPORTED; +} + +//----------------------------------------------------- +STDMETHODIMP nsDataObj::EnumDAdvise(LPENUMSTATDATA *ppEnum) +{ + return OLE_E_ADVISENOTSUPPORTED; +} + +// IAsyncOperation methods +STDMETHODIMP nsDataObj::EndOperation(HRESULT hResult, + IBindCtx *pbcReserved, + DWORD dwEffects) +{ + mIsInOperation = FALSE; + return S_OK; +} + +STDMETHODIMP nsDataObj::GetAsyncMode(BOOL *pfIsOpAsync) +{ + *pfIsOpAsync = mIsAsyncMode; + + return S_OK; +} + +STDMETHODIMP nsDataObj::InOperation(BOOL *pfInAsyncOp) +{ + *pfInAsyncOp = mIsInOperation; + + return S_OK; +} + +STDMETHODIMP nsDataObj::SetAsyncMode(BOOL fDoOpAsync) +{ + mIsAsyncMode = fDoOpAsync; + return S_OK; +} + +STDMETHODIMP nsDataObj::StartOperation(IBindCtx *pbcReserved) +{ + mIsInOperation = TRUE; + return S_OK; +} + +//----------------------------------------------------- +// GetData and SetData helper functions +//----------------------------------------------------- +HRESULT nsDataObj::AddSetFormat(FORMATETC& aFE) +{ + return S_OK; +} + +//----------------------------------------------------- +HRESULT nsDataObj::AddGetFormat(FORMATETC& aFE) +{ + return S_OK; +} + +// +// GetDIB +// +// Someone is asking for a bitmap. The data in the transferable will be a straight +// imgIContainer, so just QI it. +// +HRESULT +nsDataObj::GetDib(const nsACString& inFlavor, + FORMATETC &aFormat, + STGMEDIUM & aSTG) +{ + ULONG result = E_FAIL; + uint32_t len = 0; + nsCOMPtr<nsISupports> genericDataWrapper; + mTransferable->GetTransferData(PromiseFlatCString(inFlavor).get(), getter_AddRefs(genericDataWrapper), &len); + nsCOMPtr<imgIContainer> image ( do_QueryInterface(genericDataWrapper) ); + if ( !image ) { + // Check if the image was put in an nsISupportsInterfacePointer wrapper. + // This might not be necessary any more, but could be useful for backwards + // compatibility. + nsCOMPtr<nsISupportsInterfacePointer> ptr(do_QueryInterface(genericDataWrapper)); + if ( ptr ) { + nsCOMPtr<nsISupports> supports; + ptr->GetData(getter_AddRefs(supports)); + image = do_QueryInterface(supports); + } + } + + if ( image ) { + // use the |nsImageToClipboard| helper class to build up a bitmap. We now own + // the bits, and pass them back to the OS in |aSTG|. + nsImageToClipboard converter(image, aFormat.cfFormat == CF_DIBV5); + HANDLE bits = nullptr; + nsresult rv = converter.GetPicture ( &bits ); + if ( NS_SUCCEEDED(rv) && bits ) { + aSTG.hGlobal = bits; + aSTG.tymed = TYMED_HGLOBAL; + result = S_OK; + } + } // if we have an image + else + NS_WARNING ( "Definitely not an image on clipboard" ); + return result; +} + + + +// +// GetFileDescriptor +// + +HRESULT +nsDataObj :: GetFileDescriptor ( FORMATETC& aFE, STGMEDIUM& aSTG, bool aIsUnicode ) +{ + HRESULT res = S_OK; + + // How we handle this depends on if we're dealing with an internet + // shortcut, since those are done under the covers. + if (IsFlavourPresent(kFilePromiseMime) || + IsFlavourPresent(kFileMime)) + { + if (aIsUnicode) + return GetFileDescriptor_IStreamW(aFE, aSTG); + else + return GetFileDescriptor_IStreamA(aFE, aSTG); + } + else if (IsFlavourPresent(kURLMime)) + { + if ( aIsUnicode ) + res = GetFileDescriptorInternetShortcutW ( aFE, aSTG ); + else + res = GetFileDescriptorInternetShortcutA ( aFE, aSTG ); + } + else + NS_WARNING ( "Not yet implemented\n" ); + + return res; +} // GetFileDescriptor + + +// +HRESULT +nsDataObj :: GetFileContents ( FORMATETC& aFE, STGMEDIUM& aSTG ) +{ + HRESULT res = S_OK; + + // How we handle this depends on if we're dealing with an internet + // shortcut, since those are done under the covers. + if (IsFlavourPresent(kFilePromiseMime) || + IsFlavourPresent(kFileMime)) + return GetFileContents_IStream(aFE, aSTG); + else if (IsFlavourPresent(kURLMime)) + return GetFileContentsInternetShortcut ( aFE, aSTG ); + else + NS_WARNING ( "Not yet implemented\n" ); + + return res; + +} // GetFileContents + +// +// Given a unicode string, we ensure that it contains only characters which are valid within +// the file system. Remove all forbidden characters from the name, and completely disallow +// any title that starts with a forbidden name and extension (e.g. "nul" is invalid, but +// "nul." and "nul.txt" are also invalid and will cause problems). +// +// It would seem that this is more functionality suited to being in nsIFile. +// +static void +MangleTextToValidFilename(nsString & aText) +{ + static const char* forbiddenNames[] = { + "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", + "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9", + "CON", "PRN", "AUX", "NUL", "CLOCK$" + }; + + aText.StripChars(FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS); + aText.CompressWhitespace(true, true); + uint32_t nameLen; + for (size_t n = 0; n < ArrayLength(forbiddenNames); ++n) { + nameLen = (uint32_t) strlen(forbiddenNames[n]); + if (aText.EqualsIgnoreCase(forbiddenNames[n], nameLen)) { + // invalid name is either the entire string, or a prefix with a period + if (aText.Length() == nameLen || aText.CharAt(nameLen) == char16_t('.')) { + aText.Truncate(); + break; + } + } + } +} + +// +// Given a unicode string, convert it down to a valid local charset filename +// with the supplied extension. This ensures that we do not cut MBCS characters +// in the middle. +// +// It would seem that this is more functionality suited to being in nsIFile. +// +static bool +CreateFilenameFromTextA(nsString & aText, const char * aExtension, + char * aFilename, uint32_t aFilenameLen) +{ + // ensure that the supplied name doesn't have invalid characters. If + // a valid mangled filename couldn't be created then it will leave the + // text empty. + MangleTextToValidFilename(aText); + if (aText.IsEmpty()) + return false; + + // repeatably call WideCharToMultiByte as long as the title doesn't fit in the buffer + // available to us. Continually reduce the length of the source title until the MBCS + // version will fit in the buffer with room for the supplied extension. Doing it this + // way ensures that even in MBCS environments there will be a valid MBCS filename of + // the correct length. + int maxUsableFilenameLen = aFilenameLen - strlen(aExtension) - 1; // space for ext + null byte + int currLen, textLen = (int) std::min(aText.Length(), aFilenameLen); + char defaultChar = '_'; + do { + currLen = WideCharToMultiByte(CP_ACP, + WC_COMPOSITECHECK|WC_DEFAULTCHAR, + aText.get(), textLen--, aFilename, maxUsableFilenameLen, &defaultChar, nullptr); + } + while (currLen == 0 && textLen > 0 && GetLastError() == ERROR_INSUFFICIENT_BUFFER); + if (currLen > 0 && textLen > 0) { + strcpy(&aFilename[currLen], aExtension); + return true; + } + else { + // empty names aren't permitted + return false; + } +} + +static bool +CreateFilenameFromTextW(nsString & aText, const wchar_t * aExtension, + wchar_t * aFilename, uint32_t aFilenameLen) +{ + // ensure that the supplied name doesn't have invalid characters. If + // a valid mangled filename couldn't be created then it will leave the + // text empty. + MangleTextToValidFilename(aText); + if (aText.IsEmpty()) + return false; + + const int extensionLen = wcslen(aExtension); + if (aText.Length() + extensionLen + 1 > aFilenameLen) + aText.Truncate(aFilenameLen - extensionLen - 1); + wcscpy(&aFilename[0], aText.get()); + wcscpy(&aFilename[aText.Length()], aExtension); + return true; +} + +#define PAGEINFO_PROPERTIES "chrome://navigator/locale/pageInfo.properties" + +static bool +GetLocalizedString(const char16_t * aName, nsXPIDLString & aString) +{ + nsCOMPtr<nsIStringBundleService> stringService = + mozilla::services::GetStringBundleService(); + if (!stringService) + return false; + + nsCOMPtr<nsIStringBundle> stringBundle; + nsresult rv = stringService->CreateBundle(PAGEINFO_PROPERTIES, + getter_AddRefs(stringBundle)); + if (NS_FAILED(rv)) + return false; + + rv = stringBundle->GetStringFromName(aName, getter_Copies(aString)); + return NS_SUCCEEDED(rv); +} + +// +// GetFileDescriptorInternetShortcut +// +// Create the special format for an internet shortcut and build up the data +// structures the shell is expecting. +// +HRESULT +nsDataObj :: GetFileDescriptorInternetShortcutA ( FORMATETC& aFE, STGMEDIUM& aSTG ) +{ + // get the title of the shortcut + nsAutoString title; + if ( NS_FAILED(ExtractShortcutTitle(title)) ) + return E_OUTOFMEMORY; + + HGLOBAL fileGroupDescHandle = ::GlobalAlloc(GMEM_ZEROINIT|GMEM_SHARE,sizeof(FILEGROUPDESCRIPTORA)); + if (!fileGroupDescHandle) + return E_OUTOFMEMORY; + + LPFILEGROUPDESCRIPTORA fileGroupDescA = reinterpret_cast<LPFILEGROUPDESCRIPTORA>(::GlobalLock(fileGroupDescHandle)); + if (!fileGroupDescA) { + ::GlobalFree(fileGroupDescHandle); + return E_OUTOFMEMORY; + } + + // get a valid filename in the following order: 1) from the page title, + // 2) localized string for an untitled page, 3) just use "Untitled.URL" + if (!CreateFilenameFromTextA(title, ".URL", + fileGroupDescA->fgd[0].cFileName, NS_MAX_FILEDESCRIPTOR)) { + nsXPIDLString untitled; + if (!GetLocalizedString(u"noPageTitle", untitled) || + !CreateFilenameFromTextA(untitled, ".URL", + fileGroupDescA->fgd[0].cFileName, NS_MAX_FILEDESCRIPTOR)) { + strcpy(fileGroupDescA->fgd[0].cFileName, "Untitled.URL"); + } + } + + // one file in the file block + fileGroupDescA->cItems = 1; + fileGroupDescA->fgd[0].dwFlags = FD_LINKUI; + + ::GlobalUnlock( fileGroupDescHandle ); + aSTG.hGlobal = fileGroupDescHandle; + aSTG.tymed = TYMED_HGLOBAL; + + return S_OK; +} // GetFileDescriptorInternetShortcutA + +HRESULT +nsDataObj :: GetFileDescriptorInternetShortcutW ( FORMATETC& aFE, STGMEDIUM& aSTG ) +{ + // get the title of the shortcut + nsAutoString title; + if ( NS_FAILED(ExtractShortcutTitle(title)) ) + return E_OUTOFMEMORY; + + HGLOBAL fileGroupDescHandle = ::GlobalAlloc(GMEM_ZEROINIT|GMEM_SHARE,sizeof(FILEGROUPDESCRIPTORW)); + if (!fileGroupDescHandle) + return E_OUTOFMEMORY; + + LPFILEGROUPDESCRIPTORW fileGroupDescW = reinterpret_cast<LPFILEGROUPDESCRIPTORW>(::GlobalLock(fileGroupDescHandle)); + if (!fileGroupDescW) { + ::GlobalFree(fileGroupDescHandle); + return E_OUTOFMEMORY; + } + + // get a valid filename in the following order: 1) from the page title, + // 2) localized string for an untitled page, 3) just use "Untitled.URL" + if (!CreateFilenameFromTextW(title, L".URL", + fileGroupDescW->fgd[0].cFileName, NS_MAX_FILEDESCRIPTOR)) { + nsXPIDLString untitled; + if (!GetLocalizedString(u"noPageTitle", untitled) || + !CreateFilenameFromTextW(untitled, L".URL", + fileGroupDescW->fgd[0].cFileName, NS_MAX_FILEDESCRIPTOR)) { + wcscpy(fileGroupDescW->fgd[0].cFileName, L"Untitled.URL"); + } + } + + // one file in the file block + fileGroupDescW->cItems = 1; + fileGroupDescW->fgd[0].dwFlags = FD_LINKUI; + + ::GlobalUnlock( fileGroupDescHandle ); + aSTG.hGlobal = fileGroupDescHandle; + aSTG.tymed = TYMED_HGLOBAL; + + return S_OK; +} // GetFileDescriptorInternetShortcutW + + +// +// GetFileContentsInternetShortcut +// +// Create the special format for an internet shortcut and build up the data +// structures the shell is expecting. +// +HRESULT +nsDataObj :: GetFileContentsInternetShortcut ( FORMATETC& aFE, STGMEDIUM& aSTG ) +{ + static const char * kShellIconPref = "browser.shell.shortcutFavicons"; + nsAutoString url; + if ( NS_FAILED(ExtractShortcutURL(url)) ) + return E_OUTOFMEMORY; + + nsCOMPtr<nsIURI> aUri; + nsresult rv = NS_NewURI(getter_AddRefs(aUri), url); + if (NS_FAILED(rv)) { + return E_FAIL; + } + + nsAutoCString asciiUrl; + rv = aUri->GetAsciiSpec(asciiUrl); + if (NS_FAILED(rv)) { + return E_FAIL; + } + + const char *shortcutFormatStr; + int totalLen; + nsCString path; + if (!Preferences::GetBool(kShellIconPref, true) || + !IsVistaOrLater()) { + shortcutFormatStr = "[InternetShortcut]\r\nURL=%s\r\n"; + const int formatLen = strlen(shortcutFormatStr) - 2; // don't include %s + totalLen = formatLen + asciiUrl.Length(); // don't include null character + } else { + nsCOMPtr<nsIFile> icoFile; + + nsAutoString aUriHash; + + mozilla::widget::FaviconHelper::ObtainCachedIconFile(aUri, aUriHash, mIOThread, true); + + rv = mozilla::widget::FaviconHelper::GetOutputIconPath(aUri, icoFile, true); + NS_ENSURE_SUCCESS(rv, E_FAIL); + rv = icoFile->GetNativePath(path); + NS_ENSURE_SUCCESS(rv, E_FAIL); + + shortcutFormatStr = "[InternetShortcut]\r\nURL=%s\r\n" + "IDList=\r\nHotKey=0\r\nIconFile=%s\r\n" + "IconIndex=0\r\n"; + const int formatLen = strlen(shortcutFormatStr) - 2 * 2; // no %s twice + totalLen = formatLen + asciiUrl.Length() + + path.Length(); // we don't want a null character on the end + } + + // create a global memory area and build up the file contents w/in it + HGLOBAL hGlobalMemory = ::GlobalAlloc(GMEM_SHARE, totalLen); + if ( !hGlobalMemory ) + return E_OUTOFMEMORY; + + char* contents = reinterpret_cast<char*>(::GlobalLock(hGlobalMemory)); + if ( !contents ) { + ::GlobalFree( hGlobalMemory ); + return E_OUTOFMEMORY; + } + + //NOTE: we intentionally use the Microsoft version of snprintf here because it does NOT null + // terminate strings which reach the maximum size of the buffer. Since we know that the + // formatted length here is totalLen, this call to _snprintf will format the string into + // the buffer without appending the null character. + + if (!Preferences::GetBool(kShellIconPref, true)) { + _snprintf(contents, totalLen, shortcutFormatStr, asciiUrl.get()); + } else { + _snprintf(contents, totalLen, shortcutFormatStr, asciiUrl.get(), path.get()); + } + + ::GlobalUnlock(hGlobalMemory); + aSTG.hGlobal = hGlobalMemory; + aSTG.tymed = TYMED_HGLOBAL; + + return S_OK; +} // GetFileContentsInternetShortcut + +// check if specified flavour is present in the transferable +bool nsDataObj :: IsFlavourPresent(const char *inFlavour) +{ + bool retval = false; + NS_ENSURE_TRUE(mTransferable, false); + + // get the list of flavors available in the transferable + nsCOMPtr<nsIArray> flavorList; + mTransferable->FlavorsTransferableCanExport(getter_AddRefs(flavorList)); + NS_ENSURE_TRUE(flavorList, false); + + // try to find requested flavour + uint32_t cnt; + flavorList->GetLength(&cnt); + for (uint32_t i = 0; i < cnt; ++i) { + nsCOMPtr<nsISupportsCString> currentFlavor = do_QueryElementAt(flavorList, i); + if (currentFlavor) { + nsAutoCString flavorStr; + currentFlavor->GetData(flavorStr); + if (flavorStr.Equals(inFlavour)) { + retval = true; // found it! + break; + } + } + } // for each flavor + + return retval; +} + +HRESULT nsDataObj::GetPreferredDropEffect ( FORMATETC& aFE, STGMEDIUM& aSTG ) +{ + HRESULT res = S_OK; + aSTG.tymed = TYMED_HGLOBAL; + aSTG.pUnkForRelease = nullptr; + HGLOBAL hGlobalMemory = nullptr; + hGlobalMemory = ::GlobalAlloc(GMEM_MOVEABLE, sizeof(DWORD)); + if (hGlobalMemory) { + DWORD* pdw = (DWORD*) GlobalLock(hGlobalMemory); + // The PreferredDropEffect clipboard format is only registered if a drag/drop + // of an image happens from Mozilla to the desktop. We want its value + // to be DROPEFFECT_MOVE in that case so that the file is moved from the + // temporary location, not copied. + // This value should, ideally, be set on the data object via SetData() but + // our IDataObject implementation doesn't implement SetData. It adds data + // to the data object lazily only when the drop target asks for it. + *pdw = (DWORD) DROPEFFECT_MOVE; + GlobalUnlock(hGlobalMemory); + } + else { + res = E_OUTOFMEMORY; + } + aSTG.hGlobal = hGlobalMemory; + return res; +} + +//----------------------------------------------------- +HRESULT nsDataObj::GetText(const nsACString & aDataFlavor, FORMATETC& aFE, STGMEDIUM& aSTG) +{ + void* data = nullptr; + uint32_t len; + + // if someone asks for text/plain, look up text/unicode instead in the transferable. + const char* flavorStr; + const nsPromiseFlatCString& flat = PromiseFlatCString(aDataFlavor); + if (aDataFlavor.EqualsLiteral("text/plain")) + flavorStr = kUnicodeMime; + else + flavorStr = flat.get(); + + // NOTE: CreateDataFromPrimitive creates new memory, that needs to be deleted + nsCOMPtr<nsISupports> genericDataWrapper; + mTransferable->GetTransferData(flavorStr, getter_AddRefs(genericDataWrapper), &len); + if ( !len ) + return E_FAIL; + nsPrimitiveHelpers::CreateDataFromPrimitive ( flavorStr, genericDataWrapper, &data, len ); + if ( !data ) + return E_FAIL; + + HGLOBAL hGlobalMemory = nullptr; + + aSTG.tymed = TYMED_HGLOBAL; + aSTG.pUnkForRelease = nullptr; + + // We play games under the hood and advertise flavors that we know we + // can support, only they require a bit of conversion or munging of the data. + // Do that here. + // + // The transferable gives us data that is null-terminated, but this isn't reflected in + // the |len| parameter. Windoze apps expect this null to be there so bump our data buffer + // by the appropriate size to account for the null (one char for CF_TEXT, one char16_t for + // CF_UNICODETEXT). + DWORD allocLen = (DWORD)len; + if ( aFE.cfFormat == CF_TEXT ) { + // Someone is asking for text/plain; convert the unicode (assuming it's present) + // to text with the correct platform encoding. + size_t bufferSize = sizeof(char)*(len + 2); + char* plainTextData = static_cast<char*>(moz_xmalloc(bufferSize)); + char16_t* castedUnicode = reinterpret_cast<char16_t*>(data); + int32_t plainTextLen = WideCharToMultiByte(CP_ACP, 0, (LPCWSTR)castedUnicode, len / 2 + 1, plainTextData, bufferSize, NULL, NULL); + // replace the unicode data with our plaintext data. Recall that |plainTextLen| doesn't include + // the null in the length. + free(data); + if ( plainTextLen ) { + data = plainTextData; + allocLen = plainTextLen; + } + else { + free(plainTextData); + NS_WARNING ( "Oh no, couldn't convert unicode to plain text" ); + return S_OK; + } + } + else if ( aFE.cfFormat == nsClipboard::CF_HTML ) { + // Someone is asking for win32's HTML flavor. Convert our html fragment + // from unicode to UTF-8 then put it into a format specified by msft. + NS_ConvertUTF16toUTF8 converter ( reinterpret_cast<char16_t*>(data) ); + char* utf8HTML = nullptr; + nsresult rv = BuildPlatformHTML ( converter.get(), &utf8HTML ); // null terminates + + free(data); + if ( NS_SUCCEEDED(rv) && utf8HTML ) { + // replace the unicode data with our HTML data. Don't forget the null. + data = utf8HTML; + allocLen = strlen(utf8HTML) + sizeof(char); + } + else { + NS_WARNING ( "Oh no, couldn't convert to HTML" ); + return S_OK; + } + } + else if ( aFE.cfFormat != nsClipboard::CF_CUSTOMTYPES ) { + // we assume that any data that isn't caught above is unicode. This may + // be an erroneous assumption, but is true so far. + allocLen += sizeof(char16_t); + } + + hGlobalMemory = (HGLOBAL)GlobalAlloc(GMEM_MOVEABLE, allocLen); + + // Copy text to Global Memory Area + if ( hGlobalMemory ) { + char* dest = reinterpret_cast<char*>(GlobalLock(hGlobalMemory)); + char* source = reinterpret_cast<char*>(data); + memcpy ( dest, source, allocLen ); // copies the null as well + GlobalUnlock(hGlobalMemory); + } + aSTG.hGlobal = hGlobalMemory; + + // Now, delete the memory that was created by CreateDataFromPrimitive (or our text/plain data) + free(data); + + return S_OK; +} + +//----------------------------------------------------- +HRESULT nsDataObj::GetFile(FORMATETC& aFE, STGMEDIUM& aSTG) +{ + uint32_t dfInx = 0; + ULONG count; + FORMATETC fe; + m_enumFE->Reset(); + while (NOERROR == m_enumFE->Next(1, &fe, &count) + && dfInx < mDataFlavors.Length()) { + if (mDataFlavors[dfInx].EqualsLiteral(kNativeImageMime)) + return DropImage(aFE, aSTG); + if (mDataFlavors[dfInx].EqualsLiteral(kFileMime)) + return DropFile(aFE, aSTG); + if (mDataFlavors[dfInx].EqualsLiteral(kFilePromiseMime)) + return DropTempFile(aFE, aSTG); + dfInx++; + } + return E_FAIL; +} + +HRESULT nsDataObj::DropFile(FORMATETC& aFE, STGMEDIUM& aSTG) +{ + nsresult rv; + uint32_t len = 0; + nsCOMPtr<nsISupports> genericDataWrapper; + + mTransferable->GetTransferData(kFileMime, getter_AddRefs(genericDataWrapper), + &len); + nsCOMPtr<nsIFile> file ( do_QueryInterface(genericDataWrapper) ); + + if (!file) + { + nsCOMPtr<nsISupportsInterfacePointer> ptr(do_QueryInterface(genericDataWrapper)); + if (ptr) { + nsCOMPtr<nsISupports> supports; + ptr->GetData(getter_AddRefs(supports)); + file = do_QueryInterface(supports); + } + } + + if (!file) + return E_FAIL; + + aSTG.tymed = TYMED_HGLOBAL; + aSTG.pUnkForRelease = nullptr; + + nsAutoString path; + rv = file->GetPath(path); + if (NS_FAILED(rv)) + return E_FAIL; + + uint32_t allocLen = path.Length() + 2; + HGLOBAL hGlobalMemory = nullptr; + char16_t *dest; + + hGlobalMemory = GlobalAlloc(GMEM_MOVEABLE, sizeof(DROPFILES) + + allocLen * sizeof(char16_t)); + if (!hGlobalMemory) + return E_FAIL; + + DROPFILES* pDropFile = (DROPFILES*)GlobalLock(hGlobalMemory); + + // First, populate the drop file structure + pDropFile->pFiles = sizeof(DROPFILES); //Offset to start of file name string + pDropFile->fNC = 0; + pDropFile->pt.x = 0; + pDropFile->pt.y = 0; + pDropFile->fWide = TRUE; + + // Copy the filename right after the DROPFILES structure + dest = (char16_t*)(((char*)pDropFile) + pDropFile->pFiles); + memcpy(dest, path.get(), (allocLen - 1) * sizeof(char16_t)); + + // Two null characters are needed at the end of the file name. + // Lookup the CF_HDROP shell clipboard format for more info. + // Add the second null character right after the first one. + dest[allocLen - 1] = L'\0'; + + GlobalUnlock(hGlobalMemory); + + aSTG.hGlobal = hGlobalMemory; + + return S_OK; +} + +HRESULT nsDataObj::DropImage(FORMATETC& aFE, STGMEDIUM& aSTG) +{ + nsresult rv; + if (!mCachedTempFile) { + uint32_t len = 0; + nsCOMPtr<nsISupports> genericDataWrapper; + + mTransferable->GetTransferData(kNativeImageMime, getter_AddRefs(genericDataWrapper), &len); + nsCOMPtr<imgIContainer> image(do_QueryInterface(genericDataWrapper)); + + if (!image) { + // Check if the image was put in an nsISupportsInterfacePointer wrapper. + // This might not be necessary any more, but could be useful for backwards + // compatibility. + nsCOMPtr<nsISupportsInterfacePointer> ptr(do_QueryInterface(genericDataWrapper)); + if (ptr) { + nsCOMPtr<nsISupports> supports; + ptr->GetData(getter_AddRefs(supports)); + image = do_QueryInterface(supports); + } + } + + if (!image) + return E_FAIL; + + // Use the clipboard helper class to build up a memory bitmap. + nsImageToClipboard converter(image); + HANDLE bits = nullptr; + rv = converter.GetPicture(&bits); // Clipboard routines return a global handle we own. + + if (NS_FAILED(rv) || !bits) + return E_FAIL; + + // We now own these bits! + uint32_t bitmapSize = GlobalSize(bits); + if (!bitmapSize) { + GlobalFree(bits); + return E_FAIL; + } + + // Save the bitmap to a temporary location. + nsCOMPtr<nsIFile> dropFile; + rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(dropFile)); + if (!dropFile) { + GlobalFree(bits); + return E_FAIL; + } + + // Filename must be random so as not to confuse apps like + // Photoshop which handle multiple drags into a single window. + char buf[13]; + nsCString filename; + NS_MakeRandomString(buf, 8); + memcpy(buf+8, ".bmp", 5); + filename.Append(nsDependentCString(buf, 12)); + dropFile->AppendNative(filename); + rv = dropFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0660); + if (NS_FAILED(rv)) { + GlobalFree(bits); + return E_FAIL; + } + + // Cache the temp file so we can delete it later and so + // it doesn't get recreated over and over on multiple calls + // which does occur from windows shell. + dropFile->Clone(getter_AddRefs(mCachedTempFile)); + + // Write the data to disk. + nsCOMPtr<nsIOutputStream> outStream; + rv = NS_NewLocalFileOutputStream(getter_AddRefs(outStream), dropFile); + if (NS_FAILED(rv)) { + GlobalFree(bits); + return E_FAIL; + } + + char * bm = (char *)GlobalLock(bits); + + BITMAPFILEHEADER fileHdr; + BITMAPINFOHEADER *bmpHdr = (BITMAPINFOHEADER*)bm; + + fileHdr.bfType = ((WORD) ('M' << 8) | 'B'); + fileHdr.bfSize = GlobalSize (bits) + sizeof(fileHdr); + fileHdr.bfReserved1 = 0; + fileHdr.bfReserved2 = 0; + fileHdr.bfOffBits = (DWORD) (sizeof(fileHdr) + bmpHdr->biSize); + + uint32_t writeCount = 0; + if (NS_FAILED(outStream->Write((const char *)&fileHdr, sizeof(fileHdr), &writeCount)) || + NS_FAILED(outStream->Write((const char *)bm, bitmapSize, &writeCount))) + rv = NS_ERROR_FAILURE; + + outStream->Close(); + + GlobalUnlock(bits); + GlobalFree(bits); + + if (NS_FAILED(rv)) + return E_FAIL; + } + + // Pass the file name back to the drop target so that it can access the file. + nsAutoString path; + rv = mCachedTempFile->GetPath(path); + if (NS_FAILED(rv)) + return E_FAIL; + + // Two null characters are needed to terminate the file name list. + HGLOBAL hGlobalMemory = nullptr; + + uint32_t allocLen = path.Length() + 2; + + aSTG.tymed = TYMED_HGLOBAL; + aSTG.pUnkForRelease = nullptr; + + hGlobalMemory = GlobalAlloc(GMEM_MOVEABLE, sizeof(DROPFILES) + allocLen * sizeof(char16_t)); + if (!hGlobalMemory) + return E_FAIL; + + DROPFILES* pDropFile = (DROPFILES*)GlobalLock(hGlobalMemory); + + // First, populate the drop file structure. + pDropFile->pFiles = sizeof(DROPFILES); // Offset to start of file name char array. + pDropFile->fNC = 0; + pDropFile->pt.x = 0; + pDropFile->pt.y = 0; + pDropFile->fWide = TRUE; + + // Copy the filename right after the DROPFILES structure. + char16_t* dest = (char16_t*)(((char*)pDropFile) + pDropFile->pFiles); + memcpy(dest, path.get(), (allocLen - 1) * sizeof(char16_t)); // Copies the null character in path as well. + + // Two null characters are needed at the end of the file name. + // Lookup the CF_HDROP shell clipboard format for more info. + // Add the second null character right after the first one. + dest[allocLen - 1] = L'\0'; + + GlobalUnlock(hGlobalMemory); + + aSTG.hGlobal = hGlobalMemory; + + return S_OK; +} + +HRESULT nsDataObj::DropTempFile(FORMATETC& aFE, STGMEDIUM& aSTG) +{ + nsresult rv; + if (!mCachedTempFile) { + // Tempfile will need a temporary location. + nsCOMPtr<nsIFile> dropFile; + rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(dropFile)); + if (!dropFile) + return E_FAIL; + + // Filename must be random + nsCString filename; + nsAutoString wideFileName; + nsCOMPtr<nsIURI> sourceURI; + HRESULT res; + res = GetDownloadDetails(getter_AddRefs(sourceURI), + wideFileName); + if (FAILED(res)) + return res; + NS_UTF16ToCString(wideFileName, NS_CSTRING_ENCODING_NATIVE_FILESYSTEM, filename); + + dropFile->AppendNative(filename); + rv = dropFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0660); + if (NS_FAILED(rv)) + return E_FAIL; + + // Cache the temp file so we can delete it later and so + // it doesn't get recreated over and over on multiple calls + // which does occur from windows shell. + dropFile->Clone(getter_AddRefs(mCachedTempFile)); + + // Write the data to disk. + nsCOMPtr<nsIOutputStream> outStream; + rv = NS_NewLocalFileOutputStream(getter_AddRefs(outStream), dropFile); + if (NS_FAILED(rv)) + return E_FAIL; + + IStream *pStream = nullptr; + nsDataObj::CreateStream(&pStream); + NS_ENSURE_TRUE(pStream, E_FAIL); + + char buffer[512]; + ULONG readCount = 0; + uint32_t writeCount = 0; + while (1) { + HRESULT hres = pStream->Read(buffer, sizeof(buffer), &readCount); + if (FAILED(hres)) + return E_FAIL; + if (readCount == 0) + break; + rv = outStream->Write(buffer, readCount, &writeCount); + if (NS_FAILED(rv)) + return E_FAIL; + } + outStream->Close(); + pStream->Release(); + } + + // Pass the file name back to the drop target so that it can access the file. + nsAutoString path; + rv = mCachedTempFile->GetPath(path); + if (NS_FAILED(rv)) + return E_FAIL; + + uint32_t allocLen = path.Length() + 2; + + // Two null characters are needed to terminate the file name list. + HGLOBAL hGlobalMemory = nullptr; + + aSTG.tymed = TYMED_HGLOBAL; + aSTG.pUnkForRelease = nullptr; + + hGlobalMemory = GlobalAlloc(GMEM_MOVEABLE, sizeof(DROPFILES) + allocLen * sizeof(char16_t)); + if (!hGlobalMemory) + return E_FAIL; + + DROPFILES* pDropFile = (DROPFILES*)GlobalLock(hGlobalMemory); + + // First, populate the drop file structure. + pDropFile->pFiles = sizeof(DROPFILES); // Offset to start of file name char array. + pDropFile->fNC = 0; + pDropFile->pt.x = 0; + pDropFile->pt.y = 0; + pDropFile->fWide = TRUE; + + // Copy the filename right after the DROPFILES structure. + char16_t* dest = (char16_t*)(((char*)pDropFile) + pDropFile->pFiles); + memcpy(dest, path.get(), (allocLen - 1) * sizeof(char16_t)); // Copies the null character in path as well. + + // Two null characters are needed at the end of the file name. + // Lookup the CF_HDROP shell clipboard format for more info. + // Add the second null character right after the first one. + dest[allocLen - 1] = L'\0'; + + GlobalUnlock(hGlobalMemory); + + aSTG.hGlobal = hGlobalMemory; + + return S_OK; +} + +//----------------------------------------------------- +HRESULT nsDataObj::GetMetafilePict(FORMATETC&, STGMEDIUM&) +{ + return E_NOTIMPL; +} + +//----------------------------------------------------- +HRESULT nsDataObj::SetBitmap(FORMATETC&, STGMEDIUM&) +{ + return E_NOTIMPL; +} + +//----------------------------------------------------- +HRESULT nsDataObj::SetDib(FORMATETC&, STGMEDIUM&) +{ + return E_FAIL; +} + +//----------------------------------------------------- +HRESULT nsDataObj::SetText (FORMATETC& aFE, STGMEDIUM& aSTG) +{ + return E_FAIL; +} + +//----------------------------------------------------- +HRESULT nsDataObj::SetMetafilePict(FORMATETC&, STGMEDIUM&) +{ + return E_FAIL; +} + + + +//----------------------------------------------------- +//----------------------------------------------------- +CLSID nsDataObj::GetClassID() const +{ + return CLSID_nsDataObj; +} + +//----------------------------------------------------- +// Registers the DataFlavor/FE pair. +//----------------------------------------------------- +void nsDataObj::AddDataFlavor(const char* aDataFlavor, LPFORMATETC aFE) +{ + // These two lists are the mapping to and from data flavors and FEs. + // Later, OLE will tell us it needs a certain type of FORMATETC (text, unicode, etc) + // unicode, etc), so we will look up the data flavor that corresponds to + // the FE and then ask the transferable for that type of data. + mDataFlavors.AppendElement(aDataFlavor); + m_enumFE->AddFormatEtc(aFE); +} + +//----------------------------------------------------- +// Sets the transferable object +//----------------------------------------------------- +void nsDataObj::SetTransferable(nsITransferable * aTransferable) +{ + NS_IF_RELEASE(mTransferable); + + mTransferable = aTransferable; + if (nullptr == mTransferable) { + return; + } + + NS_ADDREF(mTransferable); + + return; +} + + +// +// ExtractURL +// +// Roots around in the transferable for the appropriate flavor that indicates +// a url and pulls out the url portion of the data. Used mostly for creating +// internet shortcuts on the desktop. The url flavor is of the format: +// +// <url> <linefeed> <page title> +// +nsresult +nsDataObj :: ExtractShortcutURL ( nsString & outURL ) +{ + NS_ASSERTION ( mTransferable, "We don't have a good transferable" ); + nsresult rv = NS_ERROR_FAILURE; + + uint32_t len = 0; + nsCOMPtr<nsISupports> genericURL; + if ( NS_SUCCEEDED(mTransferable->GetTransferData(kURLMime, getter_AddRefs(genericURL), &len)) ) { + nsCOMPtr<nsISupportsString> urlObject ( do_QueryInterface(genericURL) ); + if ( urlObject ) { + nsAutoString url; + urlObject->GetData ( url ); + outURL = url; + + // find the first linefeed in the data, that's where the url ends. trunc the + // result string at that point. + int32_t lineIndex = outURL.FindChar ( '\n' ); + NS_ASSERTION ( lineIndex > 0, "Format for url flavor is <url> <linefeed> <page title>" ); + if ( lineIndex > 0 ) { + outURL.Truncate ( lineIndex ); + rv = NS_OK; + } + } + } else if ( NS_SUCCEEDED(mTransferable->GetTransferData(kURLDataMime, getter_AddRefs(genericURL), &len)) || + NS_SUCCEEDED(mTransferable->GetTransferData(kURLPrivateMime, getter_AddRefs(genericURL), &len)) ) { + nsCOMPtr<nsISupportsString> urlObject ( do_QueryInterface(genericURL) ); + if ( urlObject ) { + nsAutoString url; + urlObject->GetData ( url ); + outURL = url; + + rv = NS_OK; + } + + } // if found flavor + + return rv; + +} // ExtractShortcutURL + + +// +// ExtractShortcutTitle +// +// Roots around in the transferable for the appropriate flavor that indicates +// a url and pulls out the title portion of the data. Used mostly for creating +// internet shortcuts on the desktop. The url flavor is of the format: +// +// <url> <linefeed> <page title> +// +nsresult +nsDataObj :: ExtractShortcutTitle ( nsString & outTitle ) +{ + NS_ASSERTION ( mTransferable, "We'd don't have a good transferable" ); + nsresult rv = NS_ERROR_FAILURE; + + uint32_t len = 0; + nsCOMPtr<nsISupports> genericURL; + if ( NS_SUCCEEDED(mTransferable->GetTransferData(kURLMime, getter_AddRefs(genericURL), &len)) ) { + nsCOMPtr<nsISupportsString> urlObject ( do_QueryInterface(genericURL) ); + if ( urlObject ) { + nsAutoString url; + urlObject->GetData ( url ); + + // find the first linefeed in the data, that's where the url ends. we want + // everything after that linefeed. FindChar() returns -1 if we can't find + int32_t lineIndex = url.FindChar ( '\n' ); + NS_ASSERTION ( lineIndex != -1, "Format for url flavor is <url> <linefeed> <page title>" ); + if ( lineIndex != -1 ) { + url.Mid ( outTitle, lineIndex + 1, (len/2) - (lineIndex + 1) ); + rv = NS_OK; + } + } + } // if found flavor + + return rv; + +} // ExtractShortcutTitle + + +// +// BuildPlatformHTML +// +// Munge our HTML data to win32's CF_HTML spec. Basically, put the requisite +// header information on it. This will null terminate |outPlatformHTML|. See +// http://msdn.microsoft.com/workshop/networking/clipboard/htmlclipboard.asp +// for details. +// +// We assume that |inOurHTML| is already a fragment (ie, doesn't have <HTML> +// or <BODY> tags). We'll wrap the fragment with them to make other apps +// happy. +// +nsresult +nsDataObj :: BuildPlatformHTML ( const char* inOurHTML, char** outPlatformHTML ) +{ + *outPlatformHTML = nullptr; + + nsDependentCString inHTMLString(inOurHTML); + const char* const numPlaceholder = "00000000"; + const char* const startHTMLPrefix = "Version:0.9\r\nStartHTML:"; + const char* const endHTMLPrefix = "\r\nEndHTML:"; + const char* const startFragPrefix = "\r\nStartFragment:"; + const char* const endFragPrefix = "\r\nEndFragment:"; + const char* const startSourceURLPrefix = "\r\nSourceURL:"; + const char* const endFragTrailer = "\r\n"; + + // Do we already have mSourceURL from a drag? + if (mSourceURL.IsEmpty()) { + nsAutoString url; + ExtractShortcutURL(url); + + AppendUTF16toUTF8(url, mSourceURL); + } + + const int32_t kSourceURLLength = mSourceURL.Length(); + const int32_t kNumberLength = strlen(numPlaceholder); + + const int32_t kTotalHeaderLen = strlen(startHTMLPrefix) + + strlen(endHTMLPrefix) + + strlen(startFragPrefix) + + strlen(endFragPrefix) + + strlen(endFragTrailer) + + (kSourceURLLength > 0 ? strlen(startSourceURLPrefix) : 0) + + kSourceURLLength + + (4 * kNumberLength); + + NS_NAMED_LITERAL_CSTRING(htmlHeaderString, "<html><body>\r\n"); + + NS_NAMED_LITERAL_CSTRING(fragmentHeaderString, "<!--StartFragment-->"); + + nsDependentCString trailingString( + "<!--EndFragment-->\r\n" + "</body>\r\n" + "</html>"); + + // calculate the offsets + int32_t startHTMLOffset = kTotalHeaderLen; + int32_t startFragOffset = startHTMLOffset + + htmlHeaderString.Length() + + fragmentHeaderString.Length(); + + int32_t endFragOffset = startFragOffset + + inHTMLString.Length(); + + int32_t endHTMLOffset = endFragOffset + + trailingString.Length(); + + // now build the final version + nsCString clipboardString; + clipboardString.SetCapacity(endHTMLOffset); + + clipboardString.Append(startHTMLPrefix); + clipboardString.Append(nsPrintfCString("%08u", startHTMLOffset)); + + clipboardString.Append(endHTMLPrefix); + clipboardString.Append(nsPrintfCString("%08u", endHTMLOffset)); + + clipboardString.Append(startFragPrefix); + clipboardString.Append(nsPrintfCString("%08u", startFragOffset)); + + clipboardString.Append(endFragPrefix); + clipboardString.Append(nsPrintfCString("%08u", endFragOffset)); + + if (kSourceURLLength > 0) { + clipboardString.Append(startSourceURLPrefix); + clipboardString.Append(mSourceURL); + } + + clipboardString.Append(endFragTrailer); + + clipboardString.Append(htmlHeaderString); + clipboardString.Append(fragmentHeaderString); + clipboardString.Append(inHTMLString); + clipboardString.Append(trailingString); + + *outPlatformHTML = ToNewCString(clipboardString); + if (!*outPlatformHTML) + return NS_ERROR_OUT_OF_MEMORY; + + return NS_OK; +} + +HRESULT +nsDataObj :: GetUniformResourceLocator( FORMATETC& aFE, STGMEDIUM& aSTG, bool aIsUnicode ) +{ + HRESULT res = S_OK; + if (IsFlavourPresent(kURLMime)) { + if ( aIsUnicode ) + res = ExtractUniformResourceLocatorW( aFE, aSTG ); + else + res = ExtractUniformResourceLocatorA( aFE, aSTG ); + } + else + NS_WARNING ("Not yet implemented\n"); + return res; +} + +HRESULT +nsDataObj::ExtractUniformResourceLocatorA(FORMATETC& aFE, STGMEDIUM& aSTG ) +{ + HRESULT result = S_OK; + + nsAutoString url; + if (NS_FAILED(ExtractShortcutURL(url))) + return E_OUTOFMEMORY; + + NS_LossyConvertUTF16toASCII asciiUrl(url); + const int totalLen = asciiUrl.Length() + 1; + HGLOBAL hGlobalMemory = GlobalAlloc(GMEM_ZEROINIT|GMEM_SHARE, totalLen); + if (!hGlobalMemory) + return E_OUTOFMEMORY; + + char* contents = reinterpret_cast<char*>(GlobalLock(hGlobalMemory)); + if (!contents) { + GlobalFree(hGlobalMemory); + return E_OUTOFMEMORY; + } + + strcpy(contents, asciiUrl.get()); + GlobalUnlock(hGlobalMemory); + aSTG.hGlobal = hGlobalMemory; + aSTG.tymed = TYMED_HGLOBAL; + + return result; +} + +HRESULT +nsDataObj::ExtractUniformResourceLocatorW(FORMATETC& aFE, STGMEDIUM& aSTG ) +{ + HRESULT result = S_OK; + + nsAutoString url; + if (NS_FAILED(ExtractShortcutURL(url))) + return E_OUTOFMEMORY; + + const int totalLen = (url.Length() + 1) * sizeof(char16_t); + HGLOBAL hGlobalMemory = GlobalAlloc(GMEM_ZEROINIT|GMEM_SHARE, totalLen); + if (!hGlobalMemory) + return E_OUTOFMEMORY; + + wchar_t* contents = reinterpret_cast<wchar_t*>(GlobalLock(hGlobalMemory)); + if (!contents) { + GlobalFree(hGlobalMemory); + return E_OUTOFMEMORY; + } + + wcscpy(contents, url.get()); + GlobalUnlock(hGlobalMemory); + aSTG.hGlobal = hGlobalMemory; + aSTG.tymed = TYMED_HGLOBAL; + + return result; +} + + +// Gets the filename from the kFilePromiseURLMime flavour +HRESULT nsDataObj::GetDownloadDetails(nsIURI **aSourceURI, + nsAString &aFilename) +{ + *aSourceURI = nullptr; + + NS_ENSURE_TRUE(mTransferable, E_FAIL); + + // get the URI from the kFilePromiseURLMime flavor + nsCOMPtr<nsISupports> urlPrimitive; + uint32_t dataSize = 0; + mTransferable->GetTransferData(kFilePromiseURLMime, getter_AddRefs(urlPrimitive), &dataSize); + nsCOMPtr<nsISupportsString> srcUrlPrimitive = do_QueryInterface(urlPrimitive); + NS_ENSURE_TRUE(srcUrlPrimitive, E_FAIL); + + nsAutoString srcUri; + srcUrlPrimitive->GetData(srcUri); + if (srcUri.IsEmpty()) + return E_FAIL; + nsCOMPtr<nsIURI> sourceURI; + NS_NewURI(getter_AddRefs(sourceURI), srcUri); + + nsAutoString srcFileName; + nsCOMPtr<nsISupports> fileNamePrimitive; + mTransferable->GetTransferData(kFilePromiseDestFilename, getter_AddRefs(fileNamePrimitive), &dataSize); + nsCOMPtr<nsISupportsString> srcFileNamePrimitive = do_QueryInterface(fileNamePrimitive); + if (srcFileNamePrimitive) { + srcFileNamePrimitive->GetData(srcFileName); + } else { + nsCOMPtr<nsIURL> sourceURL = do_QueryInterface(sourceURI); + if (!sourceURL) + return E_FAIL; + + nsAutoCString urlFileName; + sourceURL->GetFileName(urlFileName); + NS_UnescapeURL(urlFileName); + CopyUTF8toUTF16(urlFileName, srcFileName); + } + if (srcFileName.IsEmpty()) + return E_FAIL; + + // make the name safe for the filesystem + MangleTextToValidFilename(srcFileName); + + sourceURI.swap(*aSourceURI); + aFilename = srcFileName; + return S_OK; +} + +HRESULT nsDataObj::GetFileDescriptor_IStreamA(FORMATETC& aFE, STGMEDIUM& aSTG) +{ + HGLOBAL fileGroupDescHandle = ::GlobalAlloc(GMEM_ZEROINIT|GMEM_SHARE,sizeof(FILEGROUPDESCRIPTORW)); + NS_ENSURE_TRUE(fileGroupDescHandle, E_OUTOFMEMORY); + + LPFILEGROUPDESCRIPTORA fileGroupDescA = reinterpret_cast<LPFILEGROUPDESCRIPTORA>(GlobalLock(fileGroupDescHandle)); + if (!fileGroupDescA) { + ::GlobalFree(fileGroupDescHandle); + return E_OUTOFMEMORY; + } + + nsAutoString wideFileName; + HRESULT res; + nsCOMPtr<nsIURI> sourceURI; + res = GetDownloadDetails(getter_AddRefs(sourceURI), wideFileName); + if (FAILED(res)) + { + ::GlobalFree(fileGroupDescHandle); + return res; + } + + nsAutoCString nativeFileName; + NS_UTF16ToCString(wideFileName, NS_CSTRING_ENCODING_NATIVE_FILESYSTEM, nativeFileName); + + strncpy(fileGroupDescA->fgd[0].cFileName, nativeFileName.get(), NS_MAX_FILEDESCRIPTOR - 1); + fileGroupDescA->fgd[0].cFileName[NS_MAX_FILEDESCRIPTOR - 1] = '\0'; + + // one file in the file block + fileGroupDescA->cItems = 1; + fileGroupDescA->fgd[0].dwFlags = FD_PROGRESSUI; + + GlobalUnlock( fileGroupDescHandle ); + aSTG.hGlobal = fileGroupDescHandle; + aSTG.tymed = TYMED_HGLOBAL; + + return S_OK; +} + +HRESULT nsDataObj::GetFileDescriptor_IStreamW(FORMATETC& aFE, STGMEDIUM& aSTG) +{ + HGLOBAL fileGroupDescHandle = ::GlobalAlloc(GMEM_ZEROINIT|GMEM_SHARE,sizeof(FILEGROUPDESCRIPTORW)); + NS_ENSURE_TRUE(fileGroupDescHandle, E_OUTOFMEMORY); + + LPFILEGROUPDESCRIPTORW fileGroupDescW = reinterpret_cast<LPFILEGROUPDESCRIPTORW>(GlobalLock(fileGroupDescHandle)); + if (!fileGroupDescW) { + ::GlobalFree(fileGroupDescHandle); + return E_OUTOFMEMORY; + } + + nsAutoString wideFileName; + HRESULT res; + nsCOMPtr<nsIURI> sourceURI; + res = GetDownloadDetails(getter_AddRefs(sourceURI), + wideFileName); + if (FAILED(res)) + { + ::GlobalFree(fileGroupDescHandle); + return res; + } + + wcsncpy(fileGroupDescW->fgd[0].cFileName, wideFileName.get(), NS_MAX_FILEDESCRIPTOR - 1); + fileGroupDescW->fgd[0].cFileName[NS_MAX_FILEDESCRIPTOR - 1] = '\0'; + // one file in the file block + fileGroupDescW->cItems = 1; + fileGroupDescW->fgd[0].dwFlags = FD_PROGRESSUI; + + GlobalUnlock(fileGroupDescHandle); + aSTG.hGlobal = fileGroupDescHandle; + aSTG.tymed = TYMED_HGLOBAL; + + return S_OK; +} + +HRESULT nsDataObj::GetFileContents_IStream(FORMATETC& aFE, STGMEDIUM& aSTG) +{ + IStream *pStream = nullptr; + + nsDataObj::CreateStream(&pStream); + NS_ENSURE_TRUE(pStream, E_FAIL); + + aSTG.tymed = TYMED_ISTREAM; + aSTG.pstm = pStream; + aSTG.pUnkForRelease = nullptr; + + return S_OK; +} + +void nsDataObj::RemoveTempFile(nsITimer* aTimer, void* aClosure) +{ + nsDataObj *timedDataObj = static_cast<nsDataObj *>(aClosure); + if (timedDataObj->mCachedTempFile) { + timedDataObj->mCachedTempFile->Remove(false); + timedDataObj->mCachedTempFile = nullptr; + } + timedDataObj->Release(); +} diff --git a/widget/windows/nsDataObj.h b/widget/windows/nsDataObj.h new file mode 100644 index 0000000000..2d7fb75ee6 --- /dev/null +++ b/widget/windows/nsDataObj.h @@ -0,0 +1,296 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _NSDATAOBJ_H_ +#define _NSDATAOBJ_H_ + +#include <oleidl.h> +#include <shldisp.h> + +#include "nsCOMPtr.h" +#include "nsString.h" +#include "nsIFile.h" +#include "nsIURI.h" +#include "nsIInputStream.h" +#include "nsIStreamListener.h" +#include "nsIChannel.h" +#include "nsCOMArray.h" +#include "nsITimer.h" + +class nsIThread; +class nsIPrincipal; + +// The SDK shipping with VC11 has renamed IAsyncOperation to +// IDataObjectAsyncCapability. We try to detect this, and rename this in our +// code too to make sure that we pick the correct name when building. +#ifdef __IDataObjectAsyncCapability_INTERFACE_DEFINED__ +#define IAsyncOperation IDataObjectAsyncCapability +#define IID_IAsyncOperation IID_IDataObjectAsyncCapability +#else +// XXX for older version of PSDK where IAsyncOperation and related stuff is not available +// but thisdefine should be removed when parocles config is updated +#ifndef __IAsyncOperation_INTERFACE_DEFINED__ +// IAsyncOperation interface definition +EXTERN_C const IID IID_IAsyncOperation; + +MIDL_INTERFACE("3D8B0590-F691-11d2-8EA9-006097DF5BD4") +IAsyncOperation : public IUnknown +{ + virtual HRESULT STDMETHODCALLTYPE SetAsyncMode(BOOL fDoOpAsync) = 0; + virtual HRESULT STDMETHODCALLTYPE GetAsyncMode(BOOL *pfIsOpAsync) = 0; + virtual HRESULT STDMETHODCALLTYPE StartOperation(IBindCtx *pbcReserved) = 0; + virtual HRESULT STDMETHODCALLTYPE InOperation(BOOL *pfInAsyncOp) = 0; + virtual HRESULT STDMETHODCALLTYPE EndOperation(HRESULT hResult, + IBindCtx *pbcReserved, + DWORD dwEffects) = 0; +}; +// this is not defined in the old headers for some reason +#ifndef FD_PROGRESSUI + #define FD_PROGRESSUI 0x4000 +#endif + +#endif // __IAsyncOperation_INTERFACE_DEFINED__ +#endif // __IDataObjectAsyncCapability_INTERFACE_DEFINED__ + +/* + * CFSTR_SHELLURL is deprecated and doesn't have a Unicode version. + * Therefore we are using CFSTR_INETURL instead of CFSTR_SHELLURL. + * See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/shellcc/platform/shell/programmersguide/shell_basics/shell_basics_programming/transferring/clipboard.asp + */ +#ifndef CFSTR_INETURLA +#define CFSTR_INETURLA L"UniformResourceLocator" +#endif +#ifndef CFSTR_INETURLW +#define CFSTR_INETURLW L"UniformResourceLocatorW" +#endif + +// For support of MinGW w32api v2.4. +// When the next version of w32api is released with shlobj.h rev 1.35 +// http://sources.redhat.com/cgi-bin/cvsweb.cgi/src/winsup/w32api/include/shlobj.h?cvsroot=src +// then that can be made the base required version and this code should be removed. +#ifndef CFSTR_FILEDESCRIPTORA +# define CFSTR_FILEDESCRIPTORA L"FileGroupDescriptor" +#endif +#ifndef CFSTR_FILEDESCRIPTORW +# define CFSTR_FILEDESCRIPTORW L"FileGroupDescriptorW" +#endif + +class CEnumFormatEtc; +class nsITransferable; + +/* + * This ole registered class is used to facilitate drag-drop of objects which + * can be adapted by an object derived from CfDragDrop. The CfDragDrop is + * associated with instances via SetDragDrop(). + */ +class nsDataObj : public IDataObject, + public IAsyncOperation +{ + +protected: + nsCOMPtr<nsIThread> mIOThread; + + public: // construction, destruction + nsDataObj(nsIURI *uri = nullptr); + virtual ~nsDataObj(); + + public: // IUnknown methods - see iunknown.h for documentation + STDMETHODIMP_(ULONG) AddRef (); + STDMETHODIMP QueryInterface(REFIID, void**); + STDMETHODIMP_(ULONG) Release (); + + // support for clipboard + virtual void AddDataFlavor(const char* aDataFlavor, LPFORMATETC aFE); + void SetTransferable(nsITransferable * aTransferable); + + // Return the registered OLE class ID of this object's CfDataObj. + CLSID GetClassID() const; + + public: // IDataObject methods - these are general comments. see CfDragDrop + // for overriding behavior + + // Store data in pSTM according to the format specified by pFE, if the + // format is supported (supported formats are specified in CfDragDrop:: + // GetFormats) and return NOERROR; otherwise return DATA_E_FORMATETC. It + // is the callers responsibility to free pSTM if NOERROR is returned. + STDMETHODIMP GetData (LPFORMATETC pFE, LPSTGMEDIUM pSTM); + + // Similar to GetData except that the caller allocates the structure + // referenced by pSTM. + STDMETHODIMP GetDataHere (LPFORMATETC pFE, LPSTGMEDIUM pSTM); + + // Returns S_TRUE if this object supports the format specified by pSTM, + // S_FALSE otherwise. + STDMETHODIMP QueryGetData (LPFORMATETC pFE); + + // Set pCanonFE to the canonical format of pFE if one exists and return + // NOERROR, otherwise return DATA_S_SAMEFORMATETC. A canonical format + // implies an identical rendering. + STDMETHODIMP GetCanonicalFormatEtc (LPFORMATETC pFE, LPFORMATETC pCanonFE); + + // Set this objects data according to the format specified by pFE and + // the storage medium specified by pSTM and return NOERROR, if the format + // is supported. If release is TRUE this object must release the storage + // associated with pSTM. + STDMETHODIMP SetData (LPFORMATETC pFE, LPSTGMEDIUM pSTM, BOOL release); + + // Set ppEnum to an IEnumFORMATETC object which will iterate all of the + // data formats that this object supports. direction is either DATADIR_GET + // or DATADIR_SET. + STDMETHODIMP EnumFormatEtc (DWORD direction, LPENUMFORMATETC* ppEnum); + + // Set up an advisory connection to this object based on the format specified + // by pFE, flags, and the pAdvise. Set pConn to the established advise + // connection. + STDMETHODIMP DAdvise (LPFORMATETC pFE, DWORD flags, LPADVISESINK pAdvise, + DWORD* pConn); + + // Turn off advising of a previous call to DAdvise which set pConn. + STDMETHODIMP DUnadvise (DWORD pConn); + + // Set ppEnum to an IEnumSTATDATA object which will iterate over the + // existing objects which have established advisory connections to this + // object. + STDMETHODIMP EnumDAdvise (LPENUMSTATDATA *ppEnum); + + // IAsyncOperation methods + STDMETHOD(EndOperation)(HRESULT hResult, IBindCtx *pbcReserved, DWORD dwEffects); + STDMETHOD(GetAsyncMode)(BOOL *pfIsOpAsync); + STDMETHOD(InOperation)(BOOL *pfInAsyncOp); + STDMETHOD(SetAsyncMode)(BOOL fDoOpAsync); + STDMETHOD(StartOperation)(IBindCtx *pbcReserved); + + public: // other methods + + // Gets the filename from the kFilePromiseURLMime flavour + HRESULT GetDownloadDetails(nsIURI **aSourceURI, + nsAString &aFilename); + + protected: + // help determine the kind of drag + bool IsFlavourPresent(const char *inFlavour); + + virtual HRESULT AddSetFormat(FORMATETC& FE); + virtual HRESULT AddGetFormat(FORMATETC& FE); + + virtual HRESULT GetFile ( FORMATETC& aFE, STGMEDIUM& aSTG ); + virtual HRESULT GetText ( const nsACString& aDF, FORMATETC& aFE, STGMEDIUM & aSTG ); + virtual HRESULT GetDib ( const nsACString& inFlavor, FORMATETC &, STGMEDIUM & aSTG ); + virtual HRESULT GetMetafilePict(FORMATETC& FE, STGMEDIUM& STM); + + virtual HRESULT DropImage( FORMATETC& aFE, STGMEDIUM& aSTG ); + virtual HRESULT DropFile( FORMATETC& aFE, STGMEDIUM& aSTG ); + virtual HRESULT DropTempFile( FORMATETC& aFE, STGMEDIUM& aSTG ); + + virtual HRESULT GetUniformResourceLocator ( FORMATETC& aFE, STGMEDIUM& aSTG, bool aIsUnicode ) ; + virtual HRESULT ExtractUniformResourceLocatorA ( FORMATETC& aFE, STGMEDIUM& aSTG ) ; + virtual HRESULT ExtractUniformResourceLocatorW ( FORMATETC& aFE, STGMEDIUM& aSTG ) ; + virtual HRESULT GetFileDescriptor ( FORMATETC& aFE, STGMEDIUM& aSTG, bool aIsUnicode ) ; + virtual HRESULT GetFileContents ( FORMATETC& aFE, STGMEDIUM& aSTG ) ; + virtual HRESULT GetPreferredDropEffect ( FORMATETC& aFE, STGMEDIUM& aSTG ); + + virtual HRESULT SetBitmap(FORMATETC& FE, STGMEDIUM& STM); + virtual HRESULT SetDib (FORMATETC& FE, STGMEDIUM& STM); + virtual HRESULT SetText (FORMATETC& FE, STGMEDIUM& STM); + virtual HRESULT SetMetafilePict(FORMATETC& FE, STGMEDIUM& STM); + + // Provide the structures needed for an internet shortcut by the shell + virtual HRESULT GetFileDescriptorInternetShortcutA ( FORMATETC& aFE, STGMEDIUM& aSTG ) ; + virtual HRESULT GetFileDescriptorInternetShortcutW ( FORMATETC& aFE, STGMEDIUM& aSTG ) ; + virtual HRESULT GetFileContentsInternetShortcut ( FORMATETC& aFE, STGMEDIUM& aSTG ) ; + + // IStream implementation + virtual HRESULT GetFileDescriptor_IStreamA ( FORMATETC& aFE, STGMEDIUM& aSTG ) ; + virtual HRESULT GetFileDescriptor_IStreamW ( FORMATETC& aFE, STGMEDIUM& aSTG ) ; + virtual HRESULT GetFileContents_IStream ( FORMATETC& aFE, STGMEDIUM& aSTG ) ; + + nsresult ExtractShortcutURL ( nsString & outURL ) ; + nsresult ExtractShortcutTitle ( nsString & outTitle ) ; + + // munge our HTML data to win32's CF_HTML spec. Will null terminate + nsresult BuildPlatformHTML ( const char* inOurHTML, char** outPlatformHTML ) ; + + // Used for the SourceURL part of CF_HTML + nsCString mSourceURL; + + BOOL FormatsMatch(const FORMATETC& source, const FORMATETC& target) const; + + ULONG m_cRef; // the reference count + + nsTArray<nsCString> mDataFlavors; + + nsITransferable * mTransferable; // nsDataObj owns and ref counts nsITransferable, + // the nsITransferable does know anything about the nsDataObj + + CEnumFormatEtc * m_enumFE; // Ownership Rules: + // nsDataObj owns and ref counts CEnumFormatEtc, + + nsCOMPtr<nsIFile> mCachedTempFile; + + BOOL mIsAsyncMode; + BOOL mIsInOperation; + /////////////////////////////////////////////////////////////////////////////// + // CStream class implementation + // this class is used in Drag and drop with download sample + // called from IDataObject::GetData + class CStream : public IStream, public nsIStreamListener + { + nsCOMPtr<nsIChannel> mChannel; + FallibleTArray<uint8_t> mChannelData; + bool mChannelRead; + nsresult mChannelResult; + uint32_t mStreamRead; + + protected: + virtual ~CStream(); + nsresult WaitForCompletion(); + + public: + CStream(); + nsresult Init(nsIURI *pSourceURI, + uint32_t aContentPolicyType, + nsIPrincipal* aRequestingPrincipal); + + NS_DECL_ISUPPORTS + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + + // IUnknown + STDMETHOD(QueryInterface)(REFIID refiid, void** ppvResult); + + // IStream + STDMETHOD(Clone)(IStream** ppStream); + STDMETHOD(Commit)(DWORD dwFrags); + STDMETHOD(CopyTo)(IStream* pDestStream, ULARGE_INTEGER nBytesToCopy, ULARGE_INTEGER* nBytesRead, ULARGE_INTEGER* nBytesWritten); + STDMETHOD(LockRegion)(ULARGE_INTEGER nStart, ULARGE_INTEGER nBytes, DWORD dwFlags); + STDMETHOD(Read)(void* pvBuffer, ULONG nBytesToRead, ULONG* nBytesRead); + STDMETHOD(Revert)(void); + STDMETHOD(Seek)(LARGE_INTEGER nMove, DWORD dwOrigin, ULARGE_INTEGER* nNewPos); + STDMETHOD(SetSize)(ULARGE_INTEGER nNewSize); + STDMETHOD(Stat)(STATSTG* statstg, DWORD dwFlags); + STDMETHOD(UnlockRegion)(ULARGE_INTEGER nStart, ULARGE_INTEGER nBytes, DWORD dwFlags); + STDMETHOD(Write)(const void* pvBuffer, ULONG nBytesToRead, ULONG* nBytesRead); + }; + + HRESULT CreateStream(IStream **outStream); + + private: + + // Drag and drop helper data for implementing drag and drop image support + typedef struct { + FORMATETC fe; + STGMEDIUM stgm; + } DATAENTRY, *LPDATAENTRY; + + nsTArray <LPDATAENTRY> mDataEntryList; + nsCOMPtr<nsITimer> mTimer; + + bool LookupArbitraryFormat(FORMATETC *aFormat, LPDATAENTRY *aDataEntry, BOOL aAddorUpdate); + bool CopyMediumData(STGMEDIUM *aMediumDst, STGMEDIUM *aMediumSrc, LPFORMATETC aFormat, BOOL aSetData); + static void RemoveTempFile(nsITimer* aTimer, void* aClosure); +}; + + +#endif // _NSDATAOBJ_H_ diff --git a/widget/windows/nsDataObjCollection.cpp b/widget/windows/nsDataObjCollection.cpp new file mode 100644 index 0000000000..7399272e78 --- /dev/null +++ b/widget/windows/nsDataObjCollection.cpp @@ -0,0 +1,405 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <shlobj.h> + +#include "nsDataObjCollection.h" +#include "nsClipboard.h" +#include "IEnumFE.h" + +#include <ole2.h> + +// {25589C3E-1FAC-47b9-BF43-CAEA89B79533} +const IID IID_IDataObjCollection = + {0x25589c3e, 0x1fac, 0x47b9, {0xbf, 0x43, 0xca, 0xea, 0x89, 0xb7, 0x95, 0x33}}; + +/* + * Class nsDataObjCollection + */ + +nsDataObjCollection::nsDataObjCollection() + : m_cRef(0) +{ +} + +nsDataObjCollection::~nsDataObjCollection() +{ + mDataObjects.Clear(); +} + + +// IUnknown interface methods - see iunknown.h for documentation +STDMETHODIMP nsDataObjCollection::QueryInterface(REFIID riid, void** ppv) +{ + *ppv=nullptr; + + if ( (IID_IUnknown == riid) || (IID_IDataObject == riid) ) { + *ppv = static_cast<IDataObject*>(this); + AddRef(); + return NOERROR; + } + + if ( IID_IDataObjCollection == riid ) { + *ppv = static_cast<nsIDataObjCollection*>(this); + AddRef(); + return NOERROR; + } + //offer to operate asynchronously (required by nsDragService) + if (IID_IAsyncOperation == riid) { + *ppv = static_cast<IAsyncOperation*>(this); + AddRef(); + return NOERROR; + } + + return E_NOINTERFACE; +} + +STDMETHODIMP_(ULONG) nsDataObjCollection::AddRef() +{ + return ++m_cRef; +} + +STDMETHODIMP_(ULONG) nsDataObjCollection::Release() +{ + if (0 != --m_cRef) + return m_cRef; + + delete this; + + return 0; +} + +// IDataObject methods +STDMETHODIMP nsDataObjCollection::GetData(LPFORMATETC pFE, LPSTGMEDIUM pSTM) +{ + static CLIPFORMAT fileDescriptorFlavorA = + ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORA); + static CLIPFORMAT fileDescriptorFlavorW = + ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW); + static CLIPFORMAT fileFlavor = ::RegisterClipboardFormat(CFSTR_FILECONTENTS); + + switch (pFE->cfFormat) { + case CF_TEXT: + case CF_UNICODETEXT: + return GetText(pFE, pSTM); + case CF_HDROP: + return GetFile(pFE, pSTM); + default: + if (pFE->cfFormat == fileDescriptorFlavorA || + pFE->cfFormat == fileDescriptorFlavorW) { + return GetFileDescriptors(pFE, pSTM); + } + if (pFE->cfFormat == fileFlavor) { + return GetFileContents(pFE, pSTM); + } + } + return GetFirstSupporting(pFE, pSTM); +} + +STDMETHODIMP nsDataObjCollection::GetDataHere(LPFORMATETC pFE, LPSTGMEDIUM pSTM) +{ + return E_FAIL; +} + +// Other objects querying to see if we support a particular format +STDMETHODIMP nsDataObjCollection::QueryGetData(LPFORMATETC pFE) +{ + UINT format = nsClipboard::GetFormat(MULTI_MIME); + + if (format == pFE->cfFormat) { + return S_OK; + } + + for (uint32_t i = 0; i < mDataObjects.Length(); ++i) { + IDataObject * dataObj = mDataObjects.ElementAt(i); + if (S_OK == dataObj->QueryGetData(pFE)) { + return S_OK; + } + } + + return DV_E_FORMATETC; +} + +STDMETHODIMP nsDataObjCollection::SetData(LPFORMATETC pFE, + LPSTGMEDIUM pSTM, + BOOL fRelease) +{ + // Set arbitrary data formats on the first object in the collection and let + // it handle the heavy lifting + if (mDataObjects.Length() == 0) + return E_FAIL; + return mDataObjects.ElementAt(0)->SetData(pFE, pSTM, fRelease); +} + +// Registers a DataFlavor/FE pair +void nsDataObjCollection::AddDataFlavor(const char * aDataFlavor, + LPFORMATETC aFE) +{ + // Add the FormatEtc to our list if it's not already there. We don't care + // about the internal aDataFlavor because nsDataObj handles that. + IEnumFORMATETC * ifEtc; + FORMATETC fEtc; + ULONG num; + if (S_OK != this->EnumFormatEtc(DATADIR_GET, &ifEtc)) + return; + while (S_OK == ifEtc->Next(1, &fEtc, &num)) { + NS_ASSERTION(1 == num, + "Bit off more than we can chew in nsDataObjCollection::AddDataFlavor"); + if (FormatsMatch(fEtc, *aFE)) { + ifEtc->Release(); + return; + } + } // If we didn't find a matching format, add this one + ifEtc->Release(); + m_enumFE->AddFormatEtc(aFE); +} + +// We accept ownership of the nsDataObj which we free on destruction +void nsDataObjCollection::AddDataObject(IDataObject * aDataObj) +{ + nsDataObj* dataObj = reinterpret_cast<nsDataObj*>(aDataObj); + mDataObjects.AppendElement(dataObj); +} + +// Methods for getting data +HRESULT nsDataObjCollection::GetFile(LPFORMATETC pFE, LPSTGMEDIUM pSTM) +{ + STGMEDIUM workingmedium; + FORMATETC fe = *pFE; + HGLOBAL hGlobalMemory; + HRESULT hr; + // Make enough space for the header and the trailing null + uint32_t buffersize = sizeof(DROPFILES) + sizeof(char16_t); + uint32_t alloclen = 0; + char16_t* realbuffer; + nsAutoString filename; + + hGlobalMemory = GlobalAlloc(GHND, buffersize); + + for (uint32_t i = 0; i < mDataObjects.Length(); ++i) { + nsDataObj* dataObj = mDataObjects.ElementAt(i); + hr = dataObj->GetData(&fe, &workingmedium); + if (hr != S_OK) { + switch (hr) { + case DV_E_FORMATETC: + continue; + default: + return hr; + } + } + // Now we need to pull out the filename + char16_t* buffer = (char16_t*)GlobalLock(workingmedium.hGlobal); + if (buffer == nullptr) + return E_FAIL; + buffer += sizeof(DROPFILES)/sizeof(char16_t); + filename = buffer; + GlobalUnlock(workingmedium.hGlobal); + ReleaseStgMedium(&workingmedium); + // Now put the filename into our buffer + alloclen = (filename.Length() + 1) * sizeof(char16_t); + hGlobalMemory = ::GlobalReAlloc(hGlobalMemory, buffersize + alloclen, GHND); + if (hGlobalMemory == nullptr) + return E_FAIL; + realbuffer = (char16_t*)((char*)GlobalLock(hGlobalMemory) + buffersize); + if (!realbuffer) + return E_FAIL; + realbuffer--; // Overwrite the preceding null + memcpy(realbuffer, filename.get(), alloclen); + GlobalUnlock(hGlobalMemory); + buffersize += alloclen; + } + // We get the last null (on the double null terminator) for free since we used + // the zero memory flag when we allocated. All we need to do is fill the + // DROPFILES structure + DROPFILES* df = (DROPFILES*)GlobalLock(hGlobalMemory); + if (!df) + return E_FAIL; + df->pFiles = sizeof(DROPFILES); //Offset to start of file name string + df->fNC = 0; + df->pt.x = 0; + df->pt.y = 0; + df->fWide = TRUE; // utf-16 chars + GlobalUnlock(hGlobalMemory); + // Finally fill out the STGMEDIUM struct + pSTM->tymed = TYMED_HGLOBAL; + pSTM->pUnkForRelease = nullptr; // Caller gets to free the data + pSTM->hGlobal = hGlobalMemory; + return S_OK; +} + +HRESULT nsDataObjCollection::GetText(LPFORMATETC pFE, LPSTGMEDIUM pSTM) +{ + STGMEDIUM workingmedium; + FORMATETC fe = *pFE; + HGLOBAL hGlobalMemory; + HRESULT hr; + uint32_t buffersize = 1; + uint32_t alloclen = 0; + + hGlobalMemory = GlobalAlloc(GHND, buffersize); + + if (pFE->cfFormat == CF_TEXT) { + nsAutoCString text; + for (uint32_t i = 0; i < mDataObjects.Length(); ++i) { + nsDataObj* dataObj = mDataObjects.ElementAt(i); + hr = dataObj->GetData(&fe, &workingmedium); + if (hr != S_OK) { + switch (hr) { + case DV_E_FORMATETC: + continue; + default: + return hr; + } + } + // Now we need to pull out the text + char* buffer = (char*)GlobalLock(workingmedium.hGlobal); + if (buffer == nullptr) + return E_FAIL; + text = buffer; + GlobalUnlock(workingmedium.hGlobal); + ReleaseStgMedium(&workingmedium); + // Now put the text into our buffer + alloclen = text.Length(); + hGlobalMemory = ::GlobalReAlloc(hGlobalMemory, buffersize + alloclen, + GHND); + if (hGlobalMemory == nullptr) + return E_FAIL; + buffer = ((char*)GlobalLock(hGlobalMemory) + buffersize); + if (!buffer) + return E_FAIL; + buffer--; // Overwrite the preceding null + memcpy(buffer, text.get(), alloclen); + GlobalUnlock(hGlobalMemory); + buffersize += alloclen; + } + pSTM->tymed = TYMED_HGLOBAL; + pSTM->pUnkForRelease = nullptr; // Caller gets to free the data + pSTM->hGlobal = hGlobalMemory; + return S_OK; + } + if (pFE->cfFormat == CF_UNICODETEXT) { + buffersize = sizeof(char16_t); + nsAutoString text; + for (uint32_t i = 0; i < mDataObjects.Length(); ++i) { + nsDataObj* dataObj = mDataObjects.ElementAt(i); + hr = dataObj->GetData(&fe, &workingmedium); + if (hr != S_OK) { + switch (hr) { + case DV_E_FORMATETC: + continue; + default: + return hr; + } + } + // Now we need to pull out the text + char16_t* buffer = (char16_t*)GlobalLock(workingmedium.hGlobal); + if (buffer == nullptr) + return E_FAIL; + text = buffer; + GlobalUnlock(workingmedium.hGlobal); + ReleaseStgMedium(&workingmedium); + // Now put the text into our buffer + alloclen = text.Length() * sizeof(char16_t); + hGlobalMemory = ::GlobalReAlloc(hGlobalMemory, buffersize + alloclen, + GHND); + if (hGlobalMemory == nullptr) + return E_FAIL; + buffer = (char16_t*)((char*)GlobalLock(hGlobalMemory) + buffersize); + if (!buffer) + return E_FAIL; + buffer--; // Overwrite the preceding null + memcpy(buffer, text.get(), alloclen); + GlobalUnlock(hGlobalMemory); + buffersize += alloclen; + } + pSTM->tymed = TYMED_HGLOBAL; + pSTM->pUnkForRelease = nullptr; // Caller gets to free the data + pSTM->hGlobal = hGlobalMemory; + return S_OK; + } + + return E_FAIL; +} + +HRESULT nsDataObjCollection::GetFileDescriptors(LPFORMATETC pFE, + LPSTGMEDIUM pSTM) +{ + STGMEDIUM workingmedium; + FORMATETC fe = *pFE; + HGLOBAL hGlobalMemory; + HRESULT hr; + uint32_t buffersize = sizeof(UINT); + uint32_t alloclen = sizeof(FILEDESCRIPTOR); + + hGlobalMemory = GlobalAlloc(GHND, buffersize); + + for (uint32_t i = 0; i < mDataObjects.Length(); ++i) { + nsDataObj* dataObj = mDataObjects.ElementAt(i); + hr = dataObj->GetData(&fe, &workingmedium); + if (hr != S_OK) { + switch (hr) { + case DV_E_FORMATETC: + continue; + default: + return hr; + } + } + // Now we need to pull out the filedescriptor + FILEDESCRIPTOR* buffer = + (FILEDESCRIPTOR*)((char*)GlobalLock(workingmedium.hGlobal) + sizeof(UINT)); + if (buffer == nullptr) + return E_FAIL; + hGlobalMemory = ::GlobalReAlloc(hGlobalMemory, buffersize + alloclen, GHND); + if (hGlobalMemory == nullptr) + return E_FAIL; + FILEGROUPDESCRIPTOR* realbuffer = + (FILEGROUPDESCRIPTOR*)GlobalLock(hGlobalMemory); + if (!realbuffer) + return E_FAIL; + FILEDESCRIPTOR* copyloc = (FILEDESCRIPTOR*)((char*)realbuffer + buffersize); + memcpy(copyloc, buffer, alloclen); + realbuffer->cItems++; + GlobalUnlock(hGlobalMemory); + GlobalUnlock(workingmedium.hGlobal); + ReleaseStgMedium(&workingmedium); + buffersize += alloclen; + } + pSTM->tymed = TYMED_HGLOBAL; + pSTM->pUnkForRelease = nullptr; // Caller gets to free the data + pSTM->hGlobal = hGlobalMemory; + return S_OK; +} + +HRESULT nsDataObjCollection::GetFileContents(LPFORMATETC pFE, LPSTGMEDIUM pSTM) +{ + ULONG num = 0; + ULONG numwanted = (pFE->lindex == -1) ? 0 : pFE->lindex; + FORMATETC fEtc = *pFE; + fEtc.lindex = -1; // We're lying to the data object so it thinks it's alone + + // The key for this data type is to figure out which data object the index + // corresponds to and then just pass it along + for (uint32_t i = 0; i < mDataObjects.Length(); ++i) { + nsDataObj* dataObj = mDataObjects.ElementAt(i); + if (dataObj->QueryGetData(&fEtc) != S_OK) + continue; + if (num == numwanted) + return dataObj->GetData(pFE, pSTM); + num++; + } + return DV_E_LINDEX; +} + +HRESULT nsDataObjCollection::GetFirstSupporting(LPFORMATETC pFE, + LPSTGMEDIUM pSTM) +{ + // There is no way to pass more than one of this, so just find the first data + // object that supports it and pass it along + for (uint32_t i = 0; i < mDataObjects.Length(); ++i) { + if (mDataObjects.ElementAt(i)->QueryGetData(pFE) == S_OK) + return mDataObjects.ElementAt(i)->GetData(pFE, pSTM); + } + return DV_E_FORMATETC; +} diff --git a/widget/windows/nsDataObjCollection.h b/widget/windows/nsDataObjCollection.h new file mode 100644 index 0000000000..06bd36a7d0 --- /dev/null +++ b/widget/windows/nsDataObjCollection.h @@ -0,0 +1,95 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _NSDATAOBJCOLLECTION_H_ +#define _NSDATAOBJCOLLECTION_H_ + +#include <oleidl.h> + +#include "mozilla/RefPtr.h" +#include "nsString.h" +#include "nsTArray.h" +#include "nsDataObj.h" +#include "mozilla/Attributes.h" + +#define MULTI_MIME "Mozilla/IDataObjectCollectionFormat" + +EXTERN_C const IID IID_IDataObjCollection; + +// An interface to make sure we have the right kind of object for D&D +// this way we can filter out collection objects that aren't ours +class nsIDataObjCollection : public IUnknown { +public: + +}; + +/* + * This ole registered class is used to facilitate drag-drop of objects which + * can be adapted by an object derived from CfDragDrop. The CfDragDrop is + * associated with instances via SetDragDrop(). + */ + +class nsDataObjCollection final : public nsIDataObjCollection, public nsDataObj +{ + public: + nsDataObjCollection(); + ~nsDataObjCollection(); + + public: // IUnknown methods - see iunknown.h for documentation + STDMETHODIMP_(ULONG) AddRef (); + STDMETHODIMP QueryInterface(REFIID, void**); + STDMETHODIMP_(ULONG) Release (); + + public: // DataGet and DataSet helper methods + virtual HRESULT GetFile(LPFORMATETC pFE, LPSTGMEDIUM pSTM); + virtual HRESULT GetText(LPFORMATETC pFE, LPSTGMEDIUM pSTM); + virtual HRESULT GetFileDescriptors(LPFORMATETC pFE, LPSTGMEDIUM pSTM); + virtual HRESULT GetFileContents(LPFORMATETC pFE, LPSTGMEDIUM pSTM); + virtual HRESULT GetFirstSupporting(LPFORMATETC pFE, LPSTGMEDIUM pSTM); + + using nsDataObj::GetFile; + using nsDataObj::GetFileContents; + using nsDataObj::GetText; + + // support for clipboard + void AddDataFlavor(const char * aDataFlavor, LPFORMATETC aFE); + + // from nsPIDataObjCollection + void AddDataObject(IDataObject * aDataObj); + int32_t GetNumDataObjects() { return mDataObjects.Length(); } + nsDataObj* GetDataObjectAt(uint32_t aItem) + { return mDataObjects.SafeElementAt(aItem, RefPtr<nsDataObj>()); } + + // Return the registered OLE class ID of this object's CfDataObj. + CLSID GetClassID() const; + + public: + // Store data in pSTM according to the format specified by pFE, if the + // format is supported (supported formats are specified in CfDragDrop:: + // GetFormats) and return NOERROR; otherwise return DATA_E_FORMATETC. It + // is the callers responsibility to free pSTM if NOERROR is returned. + STDMETHODIMP GetData (LPFORMATETC pFE, LPSTGMEDIUM pSTM); + + // Similar to GetData except that the caller allocates the structure + // referenced by pSTM. + STDMETHODIMP GetDataHere (LPFORMATETC pFE, LPSTGMEDIUM pSTM); + + // Returns S_TRUE if this object supports the format specified by pSTM, + // S_FALSE otherwise. + STDMETHODIMP QueryGetData (LPFORMATETC pFE); + + // Set this objects data according to the format specified by pFE and + // the storage medium specified by pSTM and return NOERROR, if the format + // is supported. If release is TRUE this object must release the storage + // associated with pSTM. + STDMETHODIMP SetData (LPFORMATETC pFE, LPSTGMEDIUM pSTM, BOOL release); + + protected: + ULONG m_cRef; // the reference count + + nsTArray<RefPtr<nsDataObj> > mDataObjects; +}; + +#endif // diff --git a/widget/windows/nsDeviceContextSpecWin.cpp b/widget/windows/nsDeviceContextSpecWin.cpp new file mode 100644 index 0000000000..b7750be58f --- /dev/null +++ b/widget/windows/nsDeviceContextSpecWin.cpp @@ -0,0 +1,669 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsDeviceContextSpecWin.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/gfx/PrintTargetPDF.h" +#include "mozilla/gfx/PrintTargetWindows.h" +#include "mozilla/Logging.h" +#include "mozilla/Preferences.h" +#include "mozilla/RefPtr.h" + +#include "prmem.h" + +#include <winspool.h> + +#include "nsIWidget.h" + +#include "nsTArray.h" +#include "nsIPrintSettingsWin.h" + +#include "nsString.h" +#include "nsIServiceManager.h" +#include "nsReadableUtils.h" +#include "nsStringEnumerator.h" + +#include "gfxWindowsSurface.h" + +#include "nsIFileStreams.h" +#include "nsIWindowWatcher.h" +#include "nsIDOMWindow.h" +#include "mozilla/Services.h" +#include "nsWindowsHelpers.h" + +#include "mozilla/gfx/Logging.h" + +#include "mozilla/Logging.h" +static mozilla::LazyLogModule kWidgetPrintingLogMod("printing-widget"); +#define PR_PL(_p1) MOZ_LOG(kWidgetPrintingLogMod, mozilla::LogLevel::Debug, _p1) + +using namespace mozilla; +using namespace mozilla::gfx; + +static const wchar_t kDriverName[] = L"WINSPOOL"; + +//---------------------------------------------------------------------------------- +// The printer data is shared between the PrinterEnumerator and the nsDeviceContextSpecWin +// The PrinterEnumerator creates the printer info +// but the nsDeviceContextSpecWin cleans it up +// If it gets created (via the Page Setup Dialog) but the user never prints anything +// then it will never be delete, so this class takes care of that. +class GlobalPrinters { +public: + static GlobalPrinters* GetInstance() { return &mGlobalPrinters; } + ~GlobalPrinters() { FreeGlobalPrinters(); } + + void FreeGlobalPrinters(); + + bool PrintersAreAllocated() { return mPrinters != nullptr; } + LPWSTR GetItemFromList(int32_t aInx) { return mPrinters?mPrinters->ElementAt(aInx):nullptr; } + nsresult EnumeratePrinterList(); + void GetDefaultPrinterName(nsString& aDefaultPrinterName); + uint32_t GetNumPrinters() { return mPrinters?mPrinters->Length():0; } + +protected: + GlobalPrinters() {} + nsresult EnumerateNativePrinters(); + void ReallocatePrinters(); + + static GlobalPrinters mGlobalPrinters; + static nsTArray<LPWSTR>* mPrinters; +}; +//--------------- +// static members +GlobalPrinters GlobalPrinters::mGlobalPrinters; +nsTArray<LPWSTR>* GlobalPrinters::mPrinters = nullptr; + +struct AutoFreeGlobalPrinters +{ + ~AutoFreeGlobalPrinters() { + GlobalPrinters::GetInstance()->FreeGlobalPrinters(); + } +}; + +//---------------------------------------------------------------------------------- +nsDeviceContextSpecWin::nsDeviceContextSpecWin() +{ + mDriverName = nullptr; + mDeviceName = nullptr; + mDevMode = nullptr; + +} + + +//---------------------------------------------------------------------------------- + +NS_IMPL_ISUPPORTS(nsDeviceContextSpecWin, nsIDeviceContextSpec) + +nsDeviceContextSpecWin::~nsDeviceContextSpecWin() +{ + SetDeviceName(nullptr); + SetDriverName(nullptr); + SetDevMode(nullptr); + + nsCOMPtr<nsIPrintSettingsWin> psWin(do_QueryInterface(mPrintSettings)); + if (psWin) { + psWin->SetDeviceName(nullptr); + psWin->SetDriverName(nullptr); + psWin->SetDevMode(nullptr); + } + + // Free them, we won't need them for a while + GlobalPrinters::GetInstance()->FreeGlobalPrinters(); +} + + +//------------------------------------------------------------------ +// helper +static char16_t * GetDefaultPrinterNameFromGlobalPrinters() +{ + nsAutoString printerName; + GlobalPrinters::GetInstance()->GetDefaultPrinterName(printerName); + return ToNewUnicode(printerName); +} + +//---------------------------------------------------------------------------------- +NS_IMETHODIMP nsDeviceContextSpecWin::Init(nsIWidget* aWidget, + nsIPrintSettings* aPrintSettings, + bool aIsPrintPreview) +{ + mPrintSettings = aPrintSettings; + + nsresult rv = NS_ERROR_GFX_PRINTER_NO_PRINTER_AVAILABLE; + if (aPrintSettings) { + // If we're in the child and printing via the parent or we're printing to + // PDF we only need information from the print settings. + mPrintSettings->GetOutputFormat(&mOutputFormat); + if ((XRE_IsContentProcess() && + Preferences::GetBool("print.print_via_parent")) || + mOutputFormat == nsIPrintSettings::kOutputFormatPDF) { + return NS_OK; + } + + nsCOMPtr<nsIPrintSettingsWin> psWin(do_QueryInterface(aPrintSettings)); + if (psWin) { + char16_t* deviceName; + char16_t* driverName; + psWin->GetDeviceName(&deviceName); // creates new memory (makes a copy) + psWin->GetDriverName(&driverName); // creates new memory (makes a copy) + + LPDEVMODEW devMode; + psWin->GetDevMode(&devMode); // creates new memory (makes a copy) + + if (deviceName && driverName && devMode) { + // Scaling is special, it is one of the few + // devMode items that we control in layout + if (devMode->dmFields & DM_SCALE) { + double scale = double(devMode->dmScale) / 100.0f; + if (scale != 1.0) { + aPrintSettings->SetScaling(scale); + devMode->dmScale = 100; + } + } + + SetDeviceName(deviceName); + SetDriverName(driverName); + SetDevMode(devMode); + + // clean up + free(deviceName); + free(driverName); + + return NS_OK; + } else { + PR_PL(("***** nsDeviceContextSpecWin::Init - deviceName/driverName/devMode was NULL!\n")); + if (deviceName) free(deviceName); + if (driverName) free(driverName); + if (devMode) ::HeapFree(::GetProcessHeap(), 0, devMode); + } + } + } else { + PR_PL(("***** nsDeviceContextSpecWin::Init - aPrintSettingswas NULL!\n")); + } + + // Get the Printer Name to be used and output format. + char16_t * printerName = nullptr; + if (mPrintSettings) { + mPrintSettings->GetPrinterName(&printerName); + } + + // If there is no name then use the default printer + if (!printerName || (printerName && !*printerName)) { + printerName = GetDefaultPrinterNameFromGlobalPrinters(); + } + + NS_ASSERTION(printerName, "We have to have a printer name"); + if (!printerName || !*printerName) return rv; + + return GetDataFromPrinter(printerName, mPrintSettings); +} + +//---------------------------------------------------------- +// Helper Function - Free and reallocate the string +static void CleanAndCopyString(wchar_t*& aStr, const wchar_t* aNewStr) +{ + if (aStr != nullptr) { + if (aNewStr != nullptr && wcslen(aStr) > wcslen(aNewStr)) { // reuse it if we can + wcscpy(aStr, aNewStr); + return; + } else { + PR_Free(aStr); + aStr = nullptr; + } + } + + if (nullptr != aNewStr) { + aStr = (wchar_t *)PR_Malloc(sizeof(wchar_t)*(wcslen(aNewStr) + 1)); + wcscpy(aStr, aNewStr); + } +} + +already_AddRefed<PrintTarget> nsDeviceContextSpecWin::MakePrintTarget() +{ + NS_ASSERTION(mDevMode, "DevMode can't be NULL here"); + + if (mOutputFormat == nsIPrintSettings::kOutputFormatPDF) { + nsXPIDLString filename; + mPrintSettings->GetToFileName(getter_Copies(filename)); + + double width, height; + mPrintSettings->GetEffectivePageSize(&width, &height); + if (width <= 0 || height <= 0) { + return nullptr; + } + + // convert twips to points + width /= TWIPS_PER_POINT_FLOAT; + height /= TWIPS_PER_POINT_FLOAT; + + nsCOMPtr<nsIFile> file = do_CreateInstance("@mozilla.org/file/local;1"); + nsresult rv = file->InitWithPath(filename); + if (NS_FAILED(rv)) { + return nullptr; + } + + nsCOMPtr<nsIFileOutputStream> stream = do_CreateInstance("@mozilla.org/network/file-output-stream;1"); + rv = stream->Init(file, -1, -1, 0); + if (NS_FAILED(rv)) { + return nullptr; + } + + return PrintTargetPDF::CreateOrNull(stream, IntSize::Truncate(width, height)); + } + + if (mDevMode) { + NS_WARNING_ASSERTION(mDriverName, "No driver!"); + HDC dc = ::CreateDCW(mDriverName, mDeviceName, nullptr, mDevMode); + if (!dc) { + gfxCriticalError(gfxCriticalError::DefaultOptions(false)) + << "Failed to create device context in GetSurfaceForPrinter"; + return nullptr; + } + + // The PrintTargetWindows takes over ownership of this DC + return PrintTargetWindows::CreateOrNull(dc); + } + + return nullptr; +} + +float +nsDeviceContextSpecWin::GetDPI() +{ + // To match the previous printing code we need to return 72 when printing to + // PDF and 144 when printing to a Windows surface. + return mOutputFormat == nsIPrintSettings::kOutputFormatPDF ? 72.0f : 144.0f; +} + +float +nsDeviceContextSpecWin::GetPrintingScale() +{ + MOZ_ASSERT(mPrintSettings); + + // To match the previous printing code there is no scaling for PDF. + if (mOutputFormat == nsIPrintSettings::kOutputFormatPDF) { + return 1.0f; + } + + // The print settings will have the resolution stored from the real device. + int32_t resolution; + mPrintSettings->GetResolution(&resolution); + return float(resolution) / GetDPI(); +} + +//---------------------------------------------------------------------------------- +void nsDeviceContextSpecWin::SetDeviceName(char16ptr_t aDeviceName) +{ + CleanAndCopyString(mDeviceName, aDeviceName); +} + +//---------------------------------------------------------------------------------- +void nsDeviceContextSpecWin::SetDriverName(char16ptr_t aDriverName) +{ + CleanAndCopyString(mDriverName, aDriverName); +} + +//---------------------------------------------------------------------------------- +void nsDeviceContextSpecWin::SetDevMode(LPDEVMODEW aDevMode) +{ + if (mDevMode) { + ::HeapFree(::GetProcessHeap(), 0, mDevMode); + } + + mDevMode = aDevMode; +} + +//------------------------------------------------------------------ +void +nsDeviceContextSpecWin::GetDevMode(LPDEVMODEW &aDevMode) +{ + aDevMode = mDevMode; +} + +#define DISPLAY_LAST_ERROR + +//---------------------------------------------------------------------------------- +// Setup the object's data member with the selected printer's data +nsresult +nsDeviceContextSpecWin::GetDataFromPrinter(char16ptr_t aName, nsIPrintSettings* aPS) +{ + nsresult rv = NS_ERROR_FAILURE; + + if (!GlobalPrinters::GetInstance()->PrintersAreAllocated()) { + rv = GlobalPrinters::GetInstance()->EnumeratePrinterList(); + if (NS_FAILED(rv)) { + PR_PL(("***** nsDeviceContextSpecWin::GetDataFromPrinter - Couldn't enumerate printers!\n")); + DISPLAY_LAST_ERROR + } + NS_ENSURE_SUCCESS(rv, rv); + } + + nsHPRINTER hPrinter = nullptr; + wchar_t *name = (wchar_t*)aName; // Windows APIs use non-const name argument + + BOOL status = ::OpenPrinterW(name, &hPrinter, nullptr); + if (status) { + nsAutoPrinter autoPrinter(hPrinter); + + LPDEVMODEW pDevMode; + + // Allocate a buffer of the correct size. + LONG needed = ::DocumentPropertiesW(nullptr, hPrinter, name, nullptr, + nullptr, 0); + if (needed < 0) { + PR_PL(("**** nsDeviceContextSpecWin::GetDataFromPrinter - Couldn't get " + "size of DEVMODE using DocumentPropertiesW(pDeviceName = \"%s\"). " + "GetLastEror() = %08x\n", + aName ? NS_ConvertUTF16toUTF8(aName).get() : "", GetLastError())); + return NS_ERROR_FAILURE; + } + + pDevMode = (LPDEVMODEW)::HeapAlloc(::GetProcessHeap(), HEAP_ZERO_MEMORY, + needed); + if (!pDevMode) return NS_ERROR_FAILURE; + + // Get the default DevMode for the printer and modify it for our needs. + LONG ret = ::DocumentPropertiesW(nullptr, hPrinter, name, + pDevMode, nullptr, DM_OUT_BUFFER); + + if (ret == IDOK && aPS) { + nsCOMPtr<nsIPrintSettingsWin> psWin = do_QueryInterface(aPS); + MOZ_ASSERT(psWin); + psWin->CopyToNative(pDevMode); + // Sets back the changes we made to the DevMode into the Printer Driver + ret = ::DocumentPropertiesW(nullptr, hPrinter, name, + pDevMode, pDevMode, + DM_IN_BUFFER | DM_OUT_BUFFER); + + // We need to copy the final DEVMODE settings back to our print settings, + // because they may have been set from invalid prefs. + if (ret == IDOK) { + // We need to get information from the device as well. + nsAutoHDC printerDC(::CreateICW(kDriverName, aName, nullptr, pDevMode)); + if (NS_WARN_IF(!printerDC)) { + ::HeapFree(::GetProcessHeap(), 0, pDevMode); + return NS_ERROR_FAILURE; + } + + psWin->CopyFromNative(printerDC, pDevMode); + } + } + + if (ret != IDOK) { + ::HeapFree(::GetProcessHeap(), 0, pDevMode); + PR_PL(("***** nsDeviceContextSpecWin::GetDataFromPrinter - DocumentProperties call failed code: %d/0x%x\n", ret, ret)); + DISPLAY_LAST_ERROR + return NS_ERROR_FAILURE; + } + + SetDevMode(pDevMode); // cache the pointer and takes responsibility for the memory + + SetDeviceName(aName); + + SetDriverName(kDriverName); + + rv = NS_OK; + } else { + rv = NS_ERROR_GFX_PRINTER_NAME_NOT_FOUND; + PR_PL(("***** nsDeviceContextSpecWin::GetDataFromPrinter - Couldn't open printer: [%s]\n", NS_ConvertUTF16toUTF8(aName).get())); + DISPLAY_LAST_ERROR + } + return rv; +} + +//*********************************************************** +// Printer Enumerator +//*********************************************************** +nsPrinterEnumeratorWin::nsPrinterEnumeratorWin() +{ +} + +nsPrinterEnumeratorWin::~nsPrinterEnumeratorWin() +{ + // Do not free printers here + // GlobalPrinters::GetInstance()->FreeGlobalPrinters(); +} + +NS_IMPL_ISUPPORTS(nsPrinterEnumeratorWin, nsIPrinterEnumerator) + +//---------------------------------------------------------------------------------- +// Return the Default Printer name +NS_IMETHODIMP +nsPrinterEnumeratorWin::GetDefaultPrinterName(char16_t * *aDefaultPrinterName) +{ + NS_ENSURE_ARG_POINTER(aDefaultPrinterName); + + *aDefaultPrinterName = GetDefaultPrinterNameFromGlobalPrinters(); // helper + + return NS_OK; +} + +NS_IMETHODIMP +nsPrinterEnumeratorWin::InitPrintSettingsFromPrinter(const char16_t *aPrinterName, nsIPrintSettings *aPrintSettings) +{ + NS_ENSURE_ARG_POINTER(aPrinterName); + NS_ENSURE_ARG_POINTER(aPrintSettings); + + if (!*aPrinterName) { + return NS_OK; + } + + // When printing to PDF on Windows there is no associated printer driver. + int16_t outputFormat; + aPrintSettings->GetOutputFormat(&outputFormat); + if (outputFormat == nsIPrintSettings::kOutputFormatPDF) { + return NS_OK; + } + + RefPtr<nsDeviceContextSpecWin> devSpecWin = new nsDeviceContextSpecWin(); + if (!devSpecWin) return NS_ERROR_OUT_OF_MEMORY; + + if (NS_FAILED(GlobalPrinters::GetInstance()->EnumeratePrinterList())) { + return NS_ERROR_FAILURE; + } + + AutoFreeGlobalPrinters autoFreeGlobalPrinters; + + // If the settings have already been initialized from prefs then pass these to + // GetDataFromPrinter, so that they are saved to the printer. + bool initializedFromPrefs; + nsresult rv = + aPrintSettings->GetIsInitializedFromPrefs(&initializedFromPrefs); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + if (initializedFromPrefs) { + // If we pass in print settings to GetDataFromPrinter it already copies + // things back to the settings, so we can return here. + return devSpecWin->GetDataFromPrinter(aPrinterName, aPrintSettings); + } + + devSpecWin->GetDataFromPrinter(aPrinterName); + + LPDEVMODEW devmode; + devSpecWin->GetDevMode(devmode); + if (NS_WARN_IF(!devmode)) { + return NS_ERROR_FAILURE; + } + + aPrintSettings->SetPrinterName(aPrinterName); + + // We need to get information from the device as well. + char16ptr_t printerName = aPrinterName; + HDC dc = ::CreateICW(kDriverName, printerName, nullptr, devmode); + if (NS_WARN_IF(!dc)) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIPrintSettingsWin> psWin = do_QueryInterface(aPrintSettings); + MOZ_ASSERT(psWin); + psWin->CopyFromNative(dc, devmode); + ::DeleteDC(dc); + + return NS_OK; +} + + +//---------------------------------------------------------------------------------- +// Enumerate all the Printers from the global array and pass their +// names back (usually to script) +NS_IMETHODIMP +nsPrinterEnumeratorWin::GetPrinterNameList(nsIStringEnumerator **aPrinterNameList) +{ + NS_ENSURE_ARG_POINTER(aPrinterNameList); + *aPrinterNameList = nullptr; + + nsresult rv = GlobalPrinters::GetInstance()->EnumeratePrinterList(); + if (NS_FAILED(rv)) { + PR_PL(("***** nsDeviceContextSpecWin::GetPrinterNameList - Couldn't enumerate printers!\n")); + return rv; + } + + uint32_t numPrinters = GlobalPrinters::GetInstance()->GetNumPrinters(); + nsTArray<nsString> *printers = new nsTArray<nsString>(numPrinters); + if (!printers) + return NS_ERROR_OUT_OF_MEMORY; + + nsString* names = printers->AppendElements(numPrinters); + for (uint32_t printerInx = 0; printerInx < numPrinters; ++printerInx) { + LPWSTR name = GlobalPrinters::GetInstance()->GetItemFromList(printerInx); + names[printerInx].Assign(name); + } + + return NS_NewAdoptingStringEnumerator(aPrinterNameList, printers); +} + +//---------------------------------------------------------------------------------- +//-- Global Printers +//---------------------------------------------------------------------------------- + +//---------------------------------------------------------------------------------- +// THe array hold the name and port for each printer +void +GlobalPrinters::ReallocatePrinters() +{ + if (PrintersAreAllocated()) { + FreeGlobalPrinters(); + } + mPrinters = new nsTArray<LPWSTR>(); + NS_ASSERTION(mPrinters, "Printers Array is NULL!"); +} + +//---------------------------------------------------------------------------------- +void +GlobalPrinters::FreeGlobalPrinters() +{ + if (mPrinters != nullptr) { + for (uint32_t i=0;i<mPrinters->Length();i++) { + free(mPrinters->ElementAt(i)); + } + delete mPrinters; + mPrinters = nullptr; + } +} + +//---------------------------------------------------------------------------------- +nsresult +GlobalPrinters::EnumerateNativePrinters() +{ + nsresult rv = NS_ERROR_GFX_PRINTER_NO_PRINTER_AVAILABLE; + PR_PL(("-----------------------\n")); + PR_PL(("EnumerateNativePrinters\n")); + + WCHAR szDefaultPrinterName[1024]; + DWORD status = GetProfileStringW(L"devices", 0, L",", + szDefaultPrinterName, + ArrayLength(szDefaultPrinterName)); + if (status > 0) { + DWORD count = 0; + LPWSTR sPtr = szDefaultPrinterName; + LPWSTR ePtr = szDefaultPrinterName + status; + LPWSTR prvPtr = sPtr; + while (sPtr < ePtr) { + if (*sPtr == 0) { + LPWSTR name = wcsdup(prvPtr); + mPrinters->AppendElement(name); + PR_PL(("Printer Name: %s\n", prvPtr)); + prvPtr = sPtr+1; + count++; + } + sPtr++; + } + rv = NS_OK; + } + PR_PL(("-----------------------\n")); + return rv; +} + +//------------------------------------------------------------------ +// Uses the GetProfileString to get the default printer from the registry +void +GlobalPrinters::GetDefaultPrinterName(nsString& aDefaultPrinterName) +{ + aDefaultPrinterName.Truncate(); + WCHAR szDefaultPrinterName[1024]; + DWORD status = GetProfileStringW(L"windows", L"device", 0, + szDefaultPrinterName, + ArrayLength(szDefaultPrinterName)); + if (status > 0) { + WCHAR comma = ','; + LPWSTR sPtr = szDefaultPrinterName; + while (*sPtr != comma && *sPtr != 0) + sPtr++; + if (*sPtr == comma) { + *sPtr = 0; + } + aDefaultPrinterName = szDefaultPrinterName; + } else { + aDefaultPrinterName = EmptyString(); + } + + PR_PL(("DEFAULT PRINTER [%s]\n", aDefaultPrinterName.get())); +} + +//---------------------------------------------------------------------------------- +// This goes and gets the list of available printers and puts +// the default printer at the beginning of the list +nsresult +GlobalPrinters::EnumeratePrinterList() +{ + // reallocate and get a new list each time it is asked for + // this deletes the list and re-allocates them + ReallocatePrinters(); + + // any of these could only fail with an OUT_MEMORY_ERROR + // PRINTER_ENUM_LOCAL should get the network printers on Win95 + nsresult rv = EnumerateNativePrinters(); + if (NS_FAILED(rv)) return rv; + + // get the name of the default printer + nsAutoString defPrinterName; + GetDefaultPrinterName(defPrinterName); + + // put the default printer at the beginning of list + if (!defPrinterName.IsEmpty()) { + for (uint32_t i=0;i<mPrinters->Length();i++) { + LPWSTR name = mPrinters->ElementAt(i); + if (defPrinterName.Equals(name)) { + if (i > 0) { + LPWSTR ptr = mPrinters->ElementAt(0); + mPrinters->ElementAt(0) = name; + mPrinters->ElementAt(i) = ptr; + } + break; + } + } + } + + // make sure we at least tried to get the printers + if (!PrintersAreAllocated()) { + PR_PL(("***** nsDeviceContextSpecWin::EnumeratePrinterList - Printers aren`t allocated\n")); + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + diff --git a/widget/windows/nsDeviceContextSpecWin.h b/widget/windows/nsDeviceContextSpecWin.h new file mode 100644 index 0000000000..e3706a291f --- /dev/null +++ b/widget/windows/nsDeviceContextSpecWin.h @@ -0,0 +1,84 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsDeviceContextSpecWin_h___ +#define nsDeviceContextSpecWin_h___ + +#include "nsCOMPtr.h" +#include "nsIDeviceContextSpec.h" +#include "nsIPrinterEnumerator.h" +#include "nsIPrintSettings.h" +#include "nsISupportsPrimitives.h" +#include <windows.h> +#include "mozilla/Attributes.h" +#include "mozilla/RefPtr.h" + +class nsIWidget; + +class nsDeviceContextSpecWin : public nsIDeviceContextSpec +{ +public: + nsDeviceContextSpecWin(); + + NS_DECL_ISUPPORTS + + virtual already_AddRefed<PrintTarget> MakePrintTarget() final; + NS_IMETHOD BeginDocument(const nsAString& aTitle, + const nsAString& aPrintToFileName, + int32_t aStartPage, + int32_t aEndPage) override { return NS_OK; } + NS_IMETHOD EndDocument() override { return NS_OK; } + NS_IMETHOD BeginPage() override { return NS_OK; } + NS_IMETHOD EndPage() override { return NS_OK; } + + NS_IMETHOD Init(nsIWidget* aWidget, nsIPrintSettings* aPS, bool aIsPrintPreview) override; + + float GetDPI() final; + + float GetPrintingScale() final; + + void GetDriverName(wchar_t *&aDriverName) const { aDriverName = mDriverName; } + void GetDeviceName(wchar_t *&aDeviceName) const { aDeviceName = mDeviceName; } + + // The GetDevMode will return a pointer to a DevMode + // whether it is from the Global memory handle or just the DevMode + // To get the DevMode from the Global memory Handle it must lock it + // So this call must be paired with a call to UnlockGlobalHandle + void GetDevMode(LPDEVMODEW &aDevMode); + + // helper functions + nsresult GetDataFromPrinter(char16ptr_t aName, nsIPrintSettings* aPS = nullptr); + +protected: + + void SetDeviceName(char16ptr_t aDeviceName); + void SetDriverName(char16ptr_t aDriverName); + void SetDevMode(LPDEVMODEW aDevMode); + + virtual ~nsDeviceContextSpecWin(); + + wchar_t* mDriverName; + wchar_t* mDeviceName; + LPDEVMODEW mDevMode; + + nsCOMPtr<nsIPrintSettings> mPrintSettings; + int16_t mOutputFormat = nsIPrintSettings::kOutputFormatNative; +}; + + +//------------------------------------------------------------------------- +// Printer Enumerator +//------------------------------------------------------------------------- +class nsPrinterEnumeratorWin final : public nsIPrinterEnumerator +{ + ~nsPrinterEnumeratorWin(); + +public: + nsPrinterEnumeratorWin(); + NS_DECL_ISUPPORTS + NS_DECL_NSIPRINTERENUMERATOR +}; + +#endif diff --git a/widget/windows/nsDragService.cpp b/widget/windows/nsDragService.cpp new file mode 100644 index 0000000000..0040f550f9 --- /dev/null +++ b/widget/windows/nsDragService.cpp @@ -0,0 +1,641 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <ole2.h> +#include <oleidl.h> +#include <shlobj.h> +#include <shlwapi.h> + +// shellapi.h is needed to build with WIN32_LEAN_AND_MEAN +#include <shellapi.h> + +#include "mozilla/RefPtr.h" +#include "nsDragService.h" +#include "nsITransferable.h" +#include "nsDataObj.h" + +#include "nsWidgetsCID.h" +#include "nsNativeDragTarget.h" +#include "nsNativeDragSource.h" +#include "nsClipboard.h" +#include "nsIDocument.h" +#include "nsDataObjCollection.h" + +#include "nsArrayUtils.h" +#include "nsString.h" +#include "nsEscape.h" +#include "nsIScreenManager.h" +#include "nsISupportsPrimitives.h" +#include "nsIURL.h" +#include "nsCWebBrowserPersist.h" +#include "nsToolkit.h" +#include "nsCRT.h" +#include "nsDirectoryServiceDefs.h" +#include "nsUnicharUtils.h" +#include "gfxContext.h" +#include "nsRect.h" +#include "nsMathUtils.h" +#include "WinUtils.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/DataSurfaceHelpers.h" +#include "mozilla/gfx/Tools.h" + +using namespace mozilla; +using namespace mozilla::gfx; + +//------------------------------------------------------------------------- +// +// DragService constructor +// +//------------------------------------------------------------------------- +nsDragService::nsDragService() + : mDataObject(nullptr), mSentLocalDropEvent(false) +{ +} + +//------------------------------------------------------------------------- +// +// DragService destructor +// +//------------------------------------------------------------------------- +nsDragService::~nsDragService() +{ + NS_IF_RELEASE(mDataObject); +} + +bool +nsDragService::CreateDragImage(nsIDOMNode *aDOMNode, + nsIScriptableRegion *aRegion, + SHDRAGIMAGE *psdi) +{ + if (!psdi) + return false; + + memset(psdi, 0, sizeof(SHDRAGIMAGE)); + if (!aDOMNode) + return false; + + // Prepare the drag image + LayoutDeviceIntRect dragRect; + RefPtr<SourceSurface> surface; + nsPresContext* pc; + DrawDrag(aDOMNode, aRegion, mScreenPosition, &dragRect, &surface, &pc); + if (!surface) + return false; + + uint32_t bmWidth = dragRect.width, bmHeight = dragRect.height; + + if (bmWidth == 0 || bmHeight == 0) + return false; + + psdi->crColorKey = CLR_NONE; + + RefPtr<DataSourceSurface> dataSurface = + Factory::CreateDataSourceSurface(IntSize(bmWidth, bmHeight), + SurfaceFormat::B8G8R8A8); + NS_ENSURE_TRUE(dataSurface, false); + + DataSourceSurface::MappedSurface map; + if (!dataSurface->Map(DataSourceSurface::MapType::READ_WRITE, &map)) { + return false; + } + + RefPtr<DrawTarget> dt = + Factory::CreateDrawTargetForData(BackendType::CAIRO, + map.mData, + dataSurface->GetSize(), + map.mStride, + dataSurface->GetFormat()); + if (!dt) { + dataSurface->Unmap(); + return false; + } + + dt->DrawSurface(surface, + Rect(0, 0, dataSurface->GetSize().width, dataSurface->GetSize().height), + Rect(0, 0, surface->GetSize().width, surface->GetSize().height), + DrawSurfaceOptions(), + DrawOptions(1.0f, CompositionOp::OP_SOURCE)); + dt->Flush(); + + BITMAPV5HEADER bmih; + memset((void*)&bmih, 0, sizeof(BITMAPV5HEADER)); + bmih.bV5Size = sizeof(BITMAPV5HEADER); + bmih.bV5Width = bmWidth; + bmih.bV5Height = -(int32_t)bmHeight; // flip vertical + bmih.bV5Planes = 1; + bmih.bV5BitCount = 32; + bmih.bV5Compression = BI_BITFIELDS; + bmih.bV5RedMask = 0x00FF0000; + bmih.bV5GreenMask = 0x0000FF00; + bmih.bV5BlueMask = 0x000000FF; + bmih.bV5AlphaMask = 0xFF000000; + + HDC hdcSrc = CreateCompatibleDC(nullptr); + void *lpBits = nullptr; + if (hdcSrc) { + psdi->hbmpDragImage = + ::CreateDIBSection(hdcSrc, (BITMAPINFO*)&bmih, DIB_RGB_COLORS, + (void**)&lpBits, nullptr, 0); + if (psdi->hbmpDragImage && lpBits) { + CopySurfaceDataToPackedArray(map.mData, static_cast<uint8_t*>(lpBits), + dataSurface->GetSize(), map.mStride, + BytesPerPixel(dataSurface->GetFormat())); + } + + psdi->sizeDragImage.cx = bmWidth; + psdi->sizeDragImage.cy = bmHeight; + + LayoutDeviceIntPoint screenPoint = + ConvertToUnscaledDevPixels(pc, mScreenPosition); + psdi->ptOffset.x = screenPoint.x - dragRect.x; + psdi->ptOffset.y = screenPoint.y - dragRect.y; + + DeleteDC(hdcSrc); + } + + dataSurface->Unmap(); + + return psdi->hbmpDragImage != nullptr; +} + +//------------------------------------------------------------------------- +nsresult +nsDragService::InvokeDragSessionImpl(nsIArray* anArrayTransferables, + nsIScriptableRegion* aRegion, + uint32_t aActionType) +{ + // Try and get source URI of the items that are being dragged + nsIURI *uri = nullptr; + + nsCOMPtr<nsIDocument> doc(do_QueryInterface(mSourceDocument)); + if (doc) { + uri = doc->GetDocumentURI(); + } + + uint32_t numItemsToDrag = 0; + nsresult rv = anArrayTransferables->GetLength(&numItemsToDrag); + if (!numItemsToDrag) + return NS_ERROR_FAILURE; + + // The clipboard class contains some static utility methods that we + // can use to create an IDataObject from the transferable + + // if we're dragging more than one item, we need to create a + // "collection" object to fake out the OS. This collection contains + // one |IDataObject| for each transferable. If there is just the one + // (most cases), only pass around the native |IDataObject|. + RefPtr<IDataObject> itemToDrag; + if (numItemsToDrag > 1) { + nsDataObjCollection * dataObjCollection = new nsDataObjCollection(); + if (!dataObjCollection) + return NS_ERROR_OUT_OF_MEMORY; + itemToDrag = dataObjCollection; + for (uint32_t i=0; i<numItemsToDrag; ++i) { + nsCOMPtr<nsITransferable> trans = + do_QueryElementAt(anArrayTransferables, i); + if (trans) { + // set the requestingPrincipal on the transferable + nsCOMPtr<nsINode> node = do_QueryInterface(mSourceNode); + trans->SetRequestingPrincipal(node->NodePrincipal()); + trans->SetContentPolicyType(mContentPolicyType); + RefPtr<IDataObject> dataObj; + rv = nsClipboard::CreateNativeDataObject(trans, + getter_AddRefs(dataObj), uri); + NS_ENSURE_SUCCESS(rv, rv); + // Add the flavors to the collection object too + rv = nsClipboard::SetupNativeDataObject(trans, dataObjCollection); + NS_ENSURE_SUCCESS(rv, rv); + + dataObjCollection->AddDataObject(dataObj); + } + } + } // if dragging multiple items + else { + nsCOMPtr<nsITransferable> trans = + do_QueryElementAt(anArrayTransferables, 0); + if (trans) { + // set the requestingPrincipal on the transferable + nsCOMPtr<nsINode> node = do_QueryInterface(mSourceNode); + trans->SetRequestingPrincipal(node->NodePrincipal()); + trans->SetContentPolicyType(mContentPolicyType); + rv = nsClipboard::CreateNativeDataObject(trans, + getter_AddRefs(itemToDrag), + uri); + NS_ENSURE_SUCCESS(rv, rv); + } + } // else dragging a single object + + // Create a drag image if support is available + IDragSourceHelper *pdsh; + if (SUCCEEDED(CoCreateInstance(CLSID_DragDropHelper, nullptr, + CLSCTX_INPROC_SERVER, + IID_IDragSourceHelper, (void**)&pdsh))) { + SHDRAGIMAGE sdi; + if (CreateDragImage(mSourceNode, aRegion, &sdi)) { + if (FAILED(pdsh->InitializeFromBitmap(&sdi, itemToDrag))) + DeleteObject(sdi.hbmpDragImage); + } + pdsh->Release(); + } + + // Kick off the native drag session + return StartInvokingDragSession(itemToDrag, aActionType); +} + +static bool +LayoutDevicePointToCSSPoint(const LayoutDevicePoint& aDevPos, + CSSPoint& aCSSPos) +{ + nsCOMPtr<nsIScreenManager> screenMgr = + do_GetService("@mozilla.org/gfx/screenmanager;1"); + if (!screenMgr) { + return false; + } + + nsCOMPtr<nsIScreen> screen; + screenMgr->ScreenForRect(NSToIntRound(aDevPos.x), NSToIntRound(aDevPos.y), + 1, 1, getter_AddRefs(screen)); + if (!screen) { + return false; + } + + int32_t w,h; // unused + LayoutDeviceIntPoint screenOriginDev; + screen->GetRect(&screenOriginDev.x, &screenOriginDev.y, &w, &h); + + double scale; + screen->GetDefaultCSSScaleFactor(&scale); + LayoutDeviceToCSSScale devToCSSScale = + CSSToLayoutDeviceScale(scale).Inverse(); + + // Desktop pixels and CSS pixels share the same screen origin. + CSSIntPoint screenOriginCSS; + screen->GetRectDisplayPix(&screenOriginCSS.x, &screenOriginCSS.y, &w, &h); + + aCSSPos = (aDevPos - screenOriginDev) * devToCSSScale + screenOriginCSS; + return true; +} + +//------------------------------------------------------------------------- +NS_IMETHODIMP +nsDragService::StartInvokingDragSession(IDataObject * aDataObj, + uint32_t aActionType) +{ + // To do the drag we need to create an object that + // implements the IDataObject interface (for OLE) + RefPtr<nsNativeDragSource> nativeDragSrc = + new nsNativeDragSource(mDataTransfer); + + // Now figure out what the native drag effect should be + DWORD winDropRes; + DWORD effects = DROPEFFECT_SCROLL; + if (aActionType & DRAGDROP_ACTION_COPY) { + effects |= DROPEFFECT_COPY; + } + if (aActionType & DRAGDROP_ACTION_MOVE) { + effects |= DROPEFFECT_MOVE; + } + if (aActionType & DRAGDROP_ACTION_LINK) { + effects |= DROPEFFECT_LINK; + } + + // XXX not sure why we bother to cache this, it can change during + // the drag + mDragAction = aActionType; + mSentLocalDropEvent = false; + + // Start dragging + StartDragSession(); + OpenDragPopup(); + + RefPtr<IAsyncOperation> pAsyncOp; + // Offer to do an async drag + if (SUCCEEDED(aDataObj->QueryInterface(IID_IAsyncOperation, + getter_AddRefs(pAsyncOp)))) { + pAsyncOp->SetAsyncMode(VARIANT_TRUE); + } else { + NS_NOTREACHED("When did our data object stop being async"); + } + + // Call the native D&D method + HRESULT res = ::DoDragDrop(aDataObj, nativeDragSrc, effects, &winDropRes); + + // In cases where the drop operation completed outside the application, update + // the source node's nsIDOMDataTransfer dropEffect value so it is up to date. + if (!mSentLocalDropEvent) { + uint32_t dropResult; + // Order is important, since multiple flags can be returned. + if (winDropRes & DROPEFFECT_COPY) + dropResult = DRAGDROP_ACTION_COPY; + else if (winDropRes & DROPEFFECT_LINK) + dropResult = DRAGDROP_ACTION_LINK; + else if (winDropRes & DROPEFFECT_MOVE) + dropResult = DRAGDROP_ACTION_MOVE; + else + dropResult = DRAGDROP_ACTION_NONE; + + if (mDataTransfer) { + if (res == DRAGDROP_S_DROP) // Success + mDataTransfer->SetDropEffectInt(dropResult); + else + mDataTransfer->SetDropEffectInt(DRAGDROP_ACTION_NONE); + } + } + + mUserCancelled = nativeDragSrc->UserCancelled(); + + // We're done dragging, get the cursor position and end the drag + // Use GetMessagePos to get the position of the mouse at the last message + // seen by the event loop. (Bug 489729) + // Note that we must convert this from device pixels back to Windows logical + // pixels (bug 818927). + DWORD pos = ::GetMessagePos(); + CSSPoint cssPos; + if (!LayoutDevicePointToCSSPoint(LayoutDevicePoint(GET_X_LPARAM(pos), + GET_Y_LPARAM(pos)), + cssPos)) { + // fallback to the simple scaling + POINT pt = { GET_X_LPARAM(pos), GET_Y_LPARAM(pos) }; + HMONITOR monitor = ::MonitorFromPoint(pt, MONITOR_DEFAULTTOPRIMARY); + double dpiScale = widget::WinUtils::LogToPhysFactor(monitor); + cssPos.x = GET_X_LPARAM(pos) / dpiScale; + cssPos.y = GET_Y_LPARAM(pos) / dpiScale; + } + // We have to abuse SetDragEndPoint to pass CSS pixels because + // Event::GetScreenCoords will not convert pixels for dragend events + // until bug 1224754 is fixed. + SetDragEndPoint(LayoutDeviceIntPoint(NSToIntRound(cssPos.x), + NSToIntRound(cssPos.y))); + EndDragSession(true); + + mDoingDrag = false; + + return DRAGDROP_S_DROP == res ? NS_OK : NS_ERROR_FAILURE; +} + +//------------------------------------------------------------------------- +// Make Sure we have the right kind of object +nsDataObjCollection* +nsDragService::GetDataObjCollection(IDataObject* aDataObj) +{ + nsDataObjCollection * dataObjCol = nullptr; + if (aDataObj) { + nsIDataObjCollection* dataObj; + if (aDataObj->QueryInterface(IID_IDataObjCollection, + (void**)&dataObj) == S_OK) { + dataObjCol = static_cast<nsDataObjCollection*>(aDataObj); + dataObj->Release(); + } + } + + return dataObjCol; +} + +//------------------------------------------------------------------------- +NS_IMETHODIMP +nsDragService::GetNumDropItems(uint32_t * aNumItems) +{ + if (!mDataObject) { + *aNumItems = 0; + return NS_OK; + } + + if (IsCollectionObject(mDataObject)) { + nsDataObjCollection * dataObjCol = GetDataObjCollection(mDataObject); + if (dataObjCol) { + *aNumItems = dataObjCol->GetNumDataObjects(); + } + else { + // If the count cannot be determined just return 0. + // This can happen if we have collection data of type + // MULTI_MIME ("Mozilla/IDataObjectCollectionFormat") on the clipboard + // from another process but we can't obtain an IID_IDataObjCollection + // from this process. + *aNumItems = 0; + } + } + else { + // Next check if we have a file drop. Return the number of files in + // the file drop as the number of items we have, pretending like we + // actually have > 1 drag item. + FORMATETC fe2; + SET_FORMATETC(fe2, CF_HDROP, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL); + if (mDataObject->QueryGetData(&fe2) == S_OK) { + STGMEDIUM stm; + if (mDataObject->GetData(&fe2, &stm) == S_OK) { + HDROP hdrop = (HDROP)GlobalLock(stm.hGlobal); + *aNumItems = ::DragQueryFileW(hdrop, 0xFFFFFFFF, nullptr, 0); + ::GlobalUnlock(stm.hGlobal); + ::ReleaseStgMedium(&stm); + // Data may be provided later, so assume we have 1 item + if (*aNumItems == 0) + *aNumItems = 1; + } + else + *aNumItems = 1; + } + else + *aNumItems = 1; + } + + return NS_OK; +} + +//------------------------------------------------------------------------- +NS_IMETHODIMP +nsDragService::GetData(nsITransferable * aTransferable, uint32_t anItem) +{ + // This typcially happens on a drop, the target would be asking + // for it's transferable to be filled in + // Use a static clipboard utility method for this + if (!mDataObject) + return NS_ERROR_FAILURE; + + nsresult dataFound = NS_ERROR_FAILURE; + + if (IsCollectionObject(mDataObject)) { + // multiple items, use |anItem| as an index into our collection + nsDataObjCollection * dataObjCol = GetDataObjCollection(mDataObject); + uint32_t cnt = dataObjCol->GetNumDataObjects(); + if (anItem < cnt) { + IDataObject * dataObj = dataObjCol->GetDataObjectAt(anItem); + dataFound = nsClipboard::GetDataFromDataObject(dataObj, 0, nullptr, + aTransferable); + } + else + NS_WARNING("Index out of range!"); + } + else { + // If they are asking for item "0", we can just get it... + if (anItem == 0) { + dataFound = nsClipboard::GetDataFromDataObject(mDataObject, anItem, + nullptr, aTransferable); + } else { + // It better be a file drop, or else non-zero indexes are invalid! + FORMATETC fe2; + SET_FORMATETC(fe2, CF_HDROP, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL); + if (mDataObject->QueryGetData(&fe2) == S_OK) + dataFound = nsClipboard::GetDataFromDataObject(mDataObject, anItem, + nullptr, aTransferable); + else + NS_WARNING("Reqesting non-zero index, but clipboard data is not a collection!"); + } + } + return dataFound; +} + +//--------------------------------------------------------- +NS_IMETHODIMP +nsDragService::SetIDataObject(IDataObject * aDataObj) +{ + // When the native drag starts the DragService gets + // the IDataObject that is being dragged + NS_IF_RELEASE(mDataObject); + mDataObject = aDataObj; + NS_IF_ADDREF(mDataObject); + + return NS_OK; +} + +//--------------------------------------------------------- +void +nsDragService::SetDroppedLocal() +{ + // Sent from the native drag handler, letting us know + // a drop occurred within the application vs. outside of it. + mSentLocalDropEvent = true; + return; +} + +//------------------------------------------------------------------------- +NS_IMETHODIMP +nsDragService::IsDataFlavorSupported(const char *aDataFlavor, bool *_retval) +{ + if (!aDataFlavor || !mDataObject || !_retval) + return NS_ERROR_FAILURE; + +#ifdef DEBUG + if (strcmp(aDataFlavor, kTextMime) == 0) + NS_WARNING("DO NOT USE THE text/plain DATA FLAVOR ANY MORE. USE text/unicode INSTEAD"); +#endif + + *_retval = false; + + FORMATETC fe; + UINT format = 0; + + if (IsCollectionObject(mDataObject)) { + // We know we have one of our special collection objects. + format = nsClipboard::GetFormat(aDataFlavor); + SET_FORMATETC(fe, format, 0, DVASPECT_CONTENT, -1, + TYMED_HGLOBAL | TYMED_FILE | TYMED_GDI); + + // See if any one of the IDataObjects in the collection supports + // this data type + nsDataObjCollection* dataObjCol = GetDataObjCollection(mDataObject); + if (dataObjCol) { + uint32_t cnt = dataObjCol->GetNumDataObjects(); + for (uint32_t i=0;i<cnt;++i) { + IDataObject * dataObj = dataObjCol->GetDataObjectAt(i); + if (S_OK == dataObj->QueryGetData(&fe)) + *_retval = true; // found it! + } + } + } // if special collection object + else { + // Ok, so we have a single object. Check to see if has the correct + // data type. Since this can come from an outside app, we also + // need to see if we need to perform text->unicode conversion if + // the client asked for unicode and it wasn't available. + format = nsClipboard::GetFormat(aDataFlavor); + SET_FORMATETC(fe, format, 0, DVASPECT_CONTENT, -1, + TYMED_HGLOBAL | TYMED_FILE | TYMED_GDI); + if (mDataObject->QueryGetData(&fe) == S_OK) + *_retval = true; // found it! + else { + // We haven't found the exact flavor the client asked for, but + // maybe we can still find it from something else that's on the + // clipboard + if (strcmp(aDataFlavor, kUnicodeMime) == 0) { + // client asked for unicode and it wasn't present, check if we + // have CF_TEXT. We'll handle the actual data substitution in + // the data object. + format = nsClipboard::GetFormat(kTextMime); + SET_FORMATETC(fe, format, 0, DVASPECT_CONTENT, -1, + TYMED_HGLOBAL | TYMED_FILE | TYMED_GDI); + if (mDataObject->QueryGetData(&fe) == S_OK) + *_retval = true; // found it! + } + else if (strcmp(aDataFlavor, kURLMime) == 0) { + // client asked for a url and it wasn't present, but if we + // have a file, then we have a URL to give them (the path, or + // the internal URL if an InternetShortcut). + format = nsClipboard::GetFormat(kFileMime); + SET_FORMATETC(fe, format, 0, DVASPECT_CONTENT, -1, + TYMED_HGLOBAL | TYMED_FILE | TYMED_GDI); + if (mDataObject->QueryGetData(&fe) == S_OK) + *_retval = true; // found it! + } + } // else try again + } + + return NS_OK; +} + + +// +// IsCollectionObject +// +// Determine if this is a single |IDataObject| or one of our private +// collection objects. We know the difference because our collection +// object will respond to supporting the private |MULTI_MIME| format. +// +bool +nsDragService::IsCollectionObject(IDataObject* inDataObj) +{ + bool isCollection = false; + + // setup the format object to ask for the MULTI_MIME format. We only + // need to do this once + static UINT sFormat = 0; + static FORMATETC sFE; + if (!sFormat) { + sFormat = nsClipboard::GetFormat(MULTI_MIME); + SET_FORMATETC(sFE, sFormat, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL); + } + + // ask the object if it supports it. If yes, we have a collection + // object + if (inDataObj->QueryGetData(&sFE) == S_OK) + isCollection = true; + + return isCollection; + +} // IsCollectionObject + + +// +// EndDragSession +// +// Override the default to make sure that we release the data object +// when the drag ends. It seems that OLE doesn't like to let apps quit +// w/out crashing when we're still holding onto their data +// +NS_IMETHODIMP +nsDragService::EndDragSession(bool aDoneDrag) +{ + // Bug 100180: If we've got mouse events captured, make sure we release it - + // that way, if we happen to call EndDragSession before diving into a nested + // event loop, we can still respond to mouse events. + if (::GetCapture()) { + ::ReleaseCapture(); + } + + nsBaseDragService::EndDragSession(aDoneDrag); + NS_IF_RELEASE(mDataObject); + + return NS_OK; +} diff --git a/widget/windows/nsDragService.h b/widget/windows/nsDragService.h new file mode 100644 index 0000000000..7a2d297094 --- /dev/null +++ b/widget/windows/nsDragService.h @@ -0,0 +1,61 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsDragService_h__ +#define nsDragService_h__ + +#include "nsBaseDragService.h" +#include <windows.h> +#include <shlobj.h> + +struct IDataObject; +class nsDataObjCollection; + +/** + * Native Win32 DragService wrapper + */ + +class nsDragService : public nsBaseDragService +{ +public: + nsDragService(); + virtual ~nsDragService(); + + // nsBaseDragService + virtual nsresult InvokeDragSessionImpl(nsIArray* anArrayTransferables, + nsIScriptableRegion* aRegion, + uint32_t aActionType); + + // nsIDragSession + NS_IMETHOD GetData(nsITransferable * aTransferable, uint32_t anItem); + NS_IMETHOD GetNumDropItems(uint32_t * aNumItems); + NS_IMETHOD IsDataFlavorSupported(const char *aDataFlavor, bool *_retval); + NS_IMETHOD EndDragSession(bool aDoneDrag); + + // native impl. + NS_IMETHOD SetIDataObject(IDataObject * aDataObj); + NS_IMETHOD StartInvokingDragSession(IDataObject * aDataObj, + uint32_t aActionType); + + // A drop occurred within the application vs. outside of it. + void SetDroppedLocal(); + +protected: + nsDataObjCollection* GetDataObjCollection(IDataObject * aDataObj); + + // determine if we have a single data object or one of our private + // collections + bool IsCollectionObject(IDataObject* inDataObj); + + // Create a bitmap for drag operations + bool CreateDragImage(nsIDOMNode *aDOMNode, + nsIScriptableRegion *aRegion, + SHDRAGIMAGE *psdi); + + IDataObject * mDataObject; + bool mSentLocalDropEvent; +}; + +#endif // nsDragService_h__ diff --git a/widget/windows/nsFilePicker.cpp b/widget/windows/nsFilePicker.cpp new file mode 100644 index 0000000000..53857cf5e4 --- /dev/null +++ b/widget/windows/nsFilePicker.cpp @@ -0,0 +1,1383 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsFilePicker.h" + +#include <shlobj.h> +#include <shlwapi.h> +#include <cderr.h> + +#include "mozilla/mscom/EnsureMTA.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/WindowsVersion.h" +#include "nsReadableUtils.h" +#include "nsNetUtil.h" +#include "nsWindow.h" +#include "nsILoadContext.h" +#include "nsIServiceManager.h" +#include "nsIURL.h" +#include "nsIStringBundle.h" +#include "nsEnumeratorUtils.h" +#include "nsCRT.h" +#include "nsString.h" +#include "nsToolkit.h" +#include "WinUtils.h" +#include "nsPIDOMWindow.h" +#include "GeckoProfiler.h" + +using mozilla::IsVistaOrLater; +using mozilla::IsWin8OrLater; +using mozilla::MakeUnique; +using mozilla::mscom::EnsureMTA; +using mozilla::UniquePtr; +using namespace mozilla::widget; + +char16_t *nsFilePicker::mLastUsedUnicodeDirectory; +char nsFilePicker::mLastUsedDirectory[MAX_PATH+1] = { 0 }; + +static const wchar_t kDialogPtrProp[] = L"DialogPtrProperty"; +static const DWORD kDialogTimerID = 9999; +static const unsigned long kDialogTimerTimeout = 300; + +#define MAX_EXTENSION_LENGTH 10 +#define FILE_BUFFER_SIZE 4096 + +typedef DWORD FILEOPENDIALOGOPTIONS; + +/////////////////////////////////////////////////////////////////////////////// +// Helper classes + +// Manages matching SuppressBlurEvents calls on the parent widget. +class AutoSuppressEvents +{ +public: + explicit AutoSuppressEvents(nsIWidget* aWidget) : + mWindow(static_cast<nsWindow *>(aWidget)) { + SuppressWidgetEvents(true); + } + + ~AutoSuppressEvents() { + SuppressWidgetEvents(false); + } +private: + void SuppressWidgetEvents(bool aFlag) { + if (mWindow) { + mWindow->SuppressBlurEvents(aFlag); + } + } + RefPtr<nsWindow> mWindow; +}; + +// Manages the current working path. +class AutoRestoreWorkingPath +{ +public: + AutoRestoreWorkingPath() { + DWORD bufferLength = GetCurrentDirectoryW(0, nullptr); + mWorkingPath = MakeUnique<wchar_t[]>(bufferLength); + if (GetCurrentDirectoryW(bufferLength, mWorkingPath.get()) == 0) { + mWorkingPath = nullptr; + } + } + + ~AutoRestoreWorkingPath() { + if (HasWorkingPath()) { + ::SetCurrentDirectoryW(mWorkingPath.get()); + } + } + + inline bool HasWorkingPath() const { + return mWorkingPath != nullptr; + } +private: + UniquePtr<wchar_t[]> mWorkingPath; +}; + +// Manages NS_NATIVE_TMP_WINDOW child windows. NS_NATIVE_TMP_WINDOWs are +// temporary child windows of mParentWidget created to address RTL issues +// in picker dialogs. We are responsible for destroying these. +class AutoDestroyTmpWindow +{ +public: + explicit AutoDestroyTmpWindow(HWND aTmpWnd) : + mWnd(aTmpWnd) { + } + + ~AutoDestroyTmpWindow() { + if (mWnd) + DestroyWindow(mWnd); + } + + inline HWND get() const { return mWnd; } +private: + HWND mWnd; +}; + +// Manages matching PickerOpen/PickerClosed calls on the parent widget. +class AutoWidgetPickerState +{ +public: + explicit AutoWidgetPickerState(nsIWidget* aWidget) : + mWindow(static_cast<nsWindow *>(aWidget)) { + PickerState(true); + } + + ~AutoWidgetPickerState() { + PickerState(false); + } +private: + void PickerState(bool aFlag) { + if (mWindow) { + if (aFlag) + mWindow->PickerOpen(); + else + mWindow->PickerClosed(); + } + } + RefPtr<nsWindow> mWindow; +}; + +// Manages a simple callback timer +class AutoTimerCallbackCancel +{ +public: + AutoTimerCallbackCancel(nsFilePicker* aTarget, + nsTimerCallbackFunc aCallbackFunc) { + Init(aTarget, aCallbackFunc); + } + + ~AutoTimerCallbackCancel() { + if (mPickerCallbackTimer) { + mPickerCallbackTimer->Cancel(); + } + } + +private: + void Init(nsFilePicker* aTarget, + nsTimerCallbackFunc aCallbackFunc) { + mPickerCallbackTimer = do_CreateInstance("@mozilla.org/timer;1"); + if (!mPickerCallbackTimer) { + NS_WARNING("do_CreateInstance for timer failed??"); + return; + } + mPickerCallbackTimer->InitWithFuncCallback(aCallbackFunc, + aTarget, + kDialogTimerTimeout, + nsITimer::TYPE_REPEATING_SLACK); + } + nsCOMPtr<nsITimer> mPickerCallbackTimer; + +}; + +/////////////////////////////////////////////////////////////////////////////// +// nsIFilePicker + +nsFilePicker::nsFilePicker() : + mSelectedType(1) + , mDlgWnd(nullptr) + , mFDECookie(0) +{ + CoInitialize(nullptr); +} + +nsFilePicker::~nsFilePicker() +{ + if (mLastUsedUnicodeDirectory) { + free(mLastUsedUnicodeDirectory); + mLastUsedUnicodeDirectory = nullptr; + } + CoUninitialize(); +} + +NS_IMPL_ISUPPORTS(nsFilePicker, nsIFilePicker) + +NS_IMETHODIMP nsFilePicker::Init(mozIDOMWindowProxy *aParent, const nsAString& aTitle, int16_t aMode) +{ + nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryInterface(aParent); + nsIDocShell* docShell = window ? window->GetDocShell() : nullptr; + mLoadContext = do_QueryInterface(docShell); + + return nsBaseFilePicker::Init(aParent, aTitle, aMode); +} + +STDMETHODIMP nsFilePicker::QueryInterface(REFIID refiid, void** ppvResult) +{ + *ppvResult = nullptr; + if (IID_IUnknown == refiid || + refiid == IID_IFileDialogEvents) { + *ppvResult = this; + } + + if (nullptr != *ppvResult) { + ((LPUNKNOWN)*ppvResult)->AddRef(); + return S_OK; + } + + return E_NOINTERFACE; +} + +/* + * XP picker callbacks + */ + +// Show - Display the file dialog +int CALLBACK +BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData) +{ + if (uMsg == BFFM_INITIALIZED) + { + char16_t * filePath = (char16_t *) lpData; + if (filePath) + ::SendMessageW(hwnd, BFFM_SETSELECTIONW, + TRUE /* true because lpData is a path string */, + lpData); + } + return 0; +} + +static void +EnsureWindowVisible(HWND hwnd) +{ + // Obtain the monitor which has the largest area of intersection + // with the window, or nullptr if there is no intersection. + HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONULL); + if (!monitor) { + // The window is not visible, we should reposition it to the same place as its parent + HWND parentHwnd = GetParent(hwnd); + RECT parentRect; + GetWindowRect(parentHwnd, &parentRect); + SetWindowPos(hwnd, nullptr, parentRect.left, parentRect.top, 0, 0, + SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER); + } +} + +// Callback hook which will ensure that the window is visible. Currently +// only in use on os <= XP. +UINT_PTR CALLBACK +nsFilePicker::FilePickerHook(HWND hwnd, + UINT msg, + WPARAM wParam, + LPARAM lParam) +{ + switch(msg) { + case WM_NOTIFY: + { + LPOFNOTIFYW lpofn = (LPOFNOTIFYW) lParam; + if (!lpofn || !lpofn->lpOFN) { + return 0; + } + + if (CDN_INITDONE == lpofn->hdr.code) { + // The Window will be automatically moved to the last position after + // CDN_INITDONE. We post a message to ensure the window will be visible + // so it will be done after the automatic last position window move. + PostMessage(hwnd, MOZ_WM_ENSUREVISIBLE, 0, 0); + } + } + break; + case MOZ_WM_ENSUREVISIBLE: + EnsureWindowVisible(GetParent(hwnd)); + break; + case WM_INITDIALOG: + { + OPENFILENAMEW* pofn = reinterpret_cast<OPENFILENAMEW*>(lParam); + SetProp(hwnd, kDialogPtrProp, (HANDLE)pofn->lCustData); + nsFilePicker* picker = reinterpret_cast<nsFilePicker*>(pofn->lCustData); + if (picker) { + picker->SetDialogHandle(hwnd); + SetTimer(hwnd, kDialogTimerID, kDialogTimerTimeout, nullptr); + } + } + break; + case WM_TIMER: + { + // Check to see if our parent has been torn down, if so, we close too. + if (wParam == kDialogTimerID) { + nsFilePicker* picker = + reinterpret_cast<nsFilePicker*>(GetProp(hwnd, kDialogPtrProp)); + if (picker && picker->ClosePickerIfNeeded(true)) { + KillTimer(hwnd, kDialogTimerID); + } + } + } + break; + } + return 0; +} + + +// Callback hook which will dynamically allocate a buffer large enough +// for the file picker dialog. Currently only in use on os <= XP. +UINT_PTR CALLBACK +nsFilePicker::MultiFilePickerHook(HWND hwnd, + UINT msg, + WPARAM wParam, + LPARAM lParam) +{ + switch (msg) { + case WM_INITDIALOG: + { + // Finds the child drop down of a File Picker dialog and sets the + // maximum amount of text it can hold when typed in manually. + // A wParam of 0 mean 0x7FFFFFFE characters. + HWND comboBox = FindWindowEx(GetParent(hwnd), nullptr, + L"ComboBoxEx32", nullptr ); + if(comboBox) + SendMessage(comboBox, CB_LIMITTEXT, 0, 0); + // Store our nsFilePicker ptr for future use + OPENFILENAMEW* pofn = reinterpret_cast<OPENFILENAMEW*>(lParam); + SetProp(hwnd, kDialogPtrProp, (HANDLE)pofn->lCustData); + nsFilePicker* picker = + reinterpret_cast<nsFilePicker*>(pofn->lCustData); + if (picker) { + picker->SetDialogHandle(hwnd); + SetTimer(hwnd, kDialogTimerID, kDialogTimerTimeout, nullptr); + } + } + break; + case WM_NOTIFY: + { + LPOFNOTIFYW lpofn = (LPOFNOTIFYW) lParam; + if (!lpofn || !lpofn->lpOFN) { + return 0; + } + // CDN_SELCHANGE is sent when the selection in the list box of the file + // selection dialog changes + if (lpofn->hdr.code == CDN_SELCHANGE) { + HWND parentHWND = GetParent(hwnd); + + // Get the required size for the selected files buffer + UINT newBufLength = 0; + int requiredBufLength = CommDlg_OpenSave_GetSpecW(parentHWND, + nullptr, 0); + if(requiredBufLength >= 0) + newBufLength += requiredBufLength; + else + newBufLength += MAX_PATH; + + // If the user selects multiple files, the buffer contains the + // current directory followed by the file names of the selected + // files. So make room for the directory path. If the user + // selects a single file, it is no harm to add extra space. + requiredBufLength = CommDlg_OpenSave_GetFolderPathW(parentHWND, + nullptr, 0); + if(requiredBufLength >= 0) + newBufLength += requiredBufLength; + else + newBufLength += MAX_PATH; + + // Check if lpstrFile and nMaxFile are large enough + if (newBufLength > lpofn->lpOFN->nMaxFile) { + if (lpofn->lpOFN->lpstrFile) + delete[] lpofn->lpOFN->lpstrFile; + + // We allocate FILE_BUFFER_SIZE more bytes than is needed so that + // if the user selects a file and holds down shift and down to + // select additional items, we will not continuously reallocate + newBufLength += FILE_BUFFER_SIZE; + + wchar_t* filesBuffer = new wchar_t[newBufLength]; + ZeroMemory(filesBuffer, newBufLength * sizeof(wchar_t)); + + lpofn->lpOFN->lpstrFile = filesBuffer; + lpofn->lpOFN->nMaxFile = newBufLength; + } + } + } + break; + case WM_TIMER: + { + // Check to see if our parent has been torn down, if so, we close too. + if (wParam == kDialogTimerID) { + nsFilePicker* picker = + reinterpret_cast<nsFilePicker*>(GetProp(hwnd, kDialogPtrProp)); + if (picker && picker->ClosePickerIfNeeded(true)) { + KillTimer(hwnd, kDialogTimerID); + } + } + } + break; + } + + return FilePickerHook(hwnd, msg, wParam, lParam); +} + +/* + * Vista+ callbacks + */ + +HRESULT +nsFilePicker::OnFileOk(IFileDialog *pfd) +{ + return S_OK; +} + +HRESULT +nsFilePicker::OnFolderChanging(IFileDialog *pfd, + IShellItem *psiFolder) +{ + return S_OK; +} + +HRESULT +nsFilePicker::OnFolderChange(IFileDialog *pfd) +{ + return S_OK; +} + +HRESULT +nsFilePicker::OnSelectionChange(IFileDialog *pfd) +{ + return S_OK; +} + +HRESULT +nsFilePicker::OnShareViolation(IFileDialog *pfd, + IShellItem *psi, + FDE_SHAREVIOLATION_RESPONSE *pResponse) +{ + return S_OK; +} + +HRESULT +nsFilePicker::OnTypeChange(IFileDialog *pfd) +{ + // Failures here result in errors due to security concerns. + RefPtr<IOleWindow> win; + pfd->QueryInterface(IID_IOleWindow, getter_AddRefs(win)); + if (!win) { + NS_ERROR("Could not retrieve the IOleWindow interface for IFileDialog."); + return S_OK; + } + HWND hwnd = nullptr; + win->GetWindow(&hwnd); + if (!hwnd) { + NS_ERROR("Could not retrieve the HWND for IFileDialog."); + return S_OK; + } + + SetDialogHandle(hwnd); + return S_OK; +} + +HRESULT +nsFilePicker::OnOverwrite(IFileDialog *pfd, + IShellItem *psi, + FDE_OVERWRITE_RESPONSE *pResponse) +{ + return S_OK; +} + +/* + * Close on parent close logic + */ + +bool +nsFilePicker::ClosePickerIfNeeded(bool aIsXPDialog) +{ + if (!mParentWidget || !mDlgWnd) + return false; + + nsWindow *win = static_cast<nsWindow *>(mParentWidget.get()); + // Note, the xp callbacks hand us an inner window, so we have to step up + // one to get the actual dialog. + HWND dlgWnd; + if (aIsXPDialog) + dlgWnd = GetParent(mDlgWnd); + else + dlgWnd = mDlgWnd; + if (IsWindow(dlgWnd) && IsWindowVisible(dlgWnd) && win->DestroyCalled()) { + wchar_t className[64]; + // Make sure we have the right window + if (GetClassNameW(dlgWnd, className, mozilla::ArrayLength(className)) && + !wcscmp(className, L"#32770") && + DestroyWindow(dlgWnd)) { + mDlgWnd = nullptr; + return true; + } + } + return false; +} + +void +nsFilePicker::PickerCallbackTimerFunc(nsITimer *aTimer, void *aCtx) +{ + nsFilePicker* picker = (nsFilePicker*)aCtx; + if (picker->ClosePickerIfNeeded(false)) { + aTimer->Cancel(); + } +} + +void +nsFilePicker::SetDialogHandle(HWND aWnd) +{ + if (!aWnd || mDlgWnd) + return; + mDlgWnd = aWnd; +} + +/* + * Folder picker invocation + */ + +// Open the older XP style folder picker dialog. We end up in this call +// on XP systems or when platform is built without the longhorn SDK. +bool +nsFilePicker::ShowXPFolderPicker(const nsString& aInitialDir) +{ + bool result = false; + + auto dirBuffer = MakeUnique<wchar_t[]>(FILE_BUFFER_SIZE); + wcsncpy(dirBuffer.get(), aInitialDir.get(), FILE_BUFFER_SIZE); + dirBuffer[FILE_BUFFER_SIZE-1] = '\0'; + + AutoDestroyTmpWindow adtw((HWND)(mParentWidget.get() ? + mParentWidget->GetNativeData(NS_NATIVE_TMP_WINDOW) : nullptr)); + + BROWSEINFOW browserInfo = {0}; + browserInfo.pidlRoot = nullptr; + browserInfo.pszDisplayName = dirBuffer.get(); + browserInfo.lpszTitle = mTitle.get(); + browserInfo.ulFlags = BIF_USENEWUI | BIF_RETURNONLYFSDIRS; + browserInfo.hwndOwner = adtw.get(); + browserInfo.iImage = 0; + browserInfo.lParam = reinterpret_cast<LPARAM>(this); + + if (!aInitialDir.IsEmpty()) { + // the dialog is modal so that |initialDir.get()| will be valid in + // BrowserCallbackProc. Thus, we don't need to clone it. + browserInfo.lParam = (LPARAM) aInitialDir.get(); + browserInfo.lpfn = &BrowseCallbackProc; + } else { + browserInfo.lParam = 0; + browserInfo.lpfn = nullptr; + } + + LPITEMIDLIST list = ::SHBrowseForFolderW(&browserInfo); + if (list) { + result = ::SHGetPathFromIDListW(list, dirBuffer.get()); + if (result) + mUnicodeFile.Assign(static_cast<const wchar_t*>(dirBuffer.get())); + // free PIDL + CoTaskMemFree(list); + } + + return result; +} + +/* + * Show a folder picker post Windows XP + * + * @param aInitialDir The initial directory, the last used directory will be + * used if left blank. + * @param aWasInitError Out parameter will hold true if there was an error + * before the folder picker is shown. + * @return true if a file was selected successfully. +*/ +bool +nsFilePicker::ShowFolderPicker(const nsString& aInitialDir, bool &aWasInitError) +{ + if (!IsWin8OrLater()) { + // Some Windows 7 users are experiencing a race condition when some dlls + // that are loaded by the file picker cause a crash while attempting to shut + // down the COM multithreaded apartment. By instantiating EnsureMTA, we hold + // an additional reference to the MTA that should prevent this race, since + // the MTA will remain alive until shutdown. + EnsureMTA ensureMTA; + } + + RefPtr<IFileOpenDialog> dialog; + if (FAILED(CoCreateInstance(CLSID_FileOpenDialog, nullptr, CLSCTX_INPROC, + IID_IFileOpenDialog, + getter_AddRefs(dialog)))) { + aWasInitError = true; + return false; + } + aWasInitError = false; + + // hook up event callbacks + dialog->Advise(this, &mFDECookie); + + // options + FILEOPENDIALOGOPTIONS fos = FOS_PICKFOLDERS; + dialog->SetOptions(fos); + + // initial strings + dialog->SetTitle(mTitle.get()); + + if (!mOkButtonLabel.IsEmpty()) { + dialog->SetOkButtonLabel(mOkButtonLabel.get()); + } + + if (!aInitialDir.IsEmpty()) { + RefPtr<IShellItem> folder; + if (SUCCEEDED( + WinUtils::SHCreateItemFromParsingName(aInitialDir.get(), nullptr, + IID_IShellItem, + getter_AddRefs(folder)))) { + dialog->SetFolder(folder); + } + } + + AutoDestroyTmpWindow adtw((HWND)(mParentWidget.get() ? + mParentWidget->GetNativeData(NS_NATIVE_TMP_WINDOW) : nullptr)); + + // display + RefPtr<IShellItem> item; + if (FAILED(dialog->Show(adtw.get())) || + FAILED(dialog->GetResult(getter_AddRefs(item))) || + !item) { + dialog->Unadvise(mFDECookie); + return false; + } + dialog->Unadvise(mFDECookie); + + // results + + // If the user chose a Win7 Library, resolve to the library's + // default save folder. + RefPtr<IShellItem> folderPath; + RefPtr<IShellLibrary> shellLib; + CoCreateInstance(CLSID_ShellLibrary, nullptr, CLSCTX_INPROC, + IID_IShellLibrary, getter_AddRefs(shellLib)); + if (shellLib && + SUCCEEDED(shellLib->LoadLibraryFromItem(item, STGM_READ)) && + SUCCEEDED(shellLib->GetDefaultSaveFolder(DSFT_DETECT, IID_IShellItem, + getter_AddRefs(folderPath)))) { + item.swap(folderPath); + } + + // get the folder's file system path + return WinUtils::GetShellItemPath(item, mUnicodeFile); +} + +/* + * File open and save picker invocation + */ + +/* static */ bool +nsFilePicker::GetFileNameWrapper(OPENFILENAMEW* ofn, PickerType aType) +{ + MOZ_SEH_TRY { + if (aType == PICKER_TYPE_OPEN) + return ::GetOpenFileNameW(ofn); + else if (aType == PICKER_TYPE_SAVE) + return ::GetSaveFileNameW(ofn); + } MOZ_SEH_EXCEPT(true) { + NS_ERROR("nsFilePicker GetFileName win32 call generated an exception! This is bad!"); + } + return false; +} + +bool +nsFilePicker::FilePickerWrapper(OPENFILENAMEW* ofn, PickerType aType) +{ + if (!ofn) + return false; + AutoWidgetPickerState awps(mParentWidget); + return GetFileNameWrapper(ofn, aType); +} + +bool +nsFilePicker::ShowXPFilePicker(const nsString& aInitialDir) +{ + OPENFILENAMEW ofn = {0}; + ofn.lStructSize = sizeof(ofn); + nsString filterBuffer = mFilterList; + + auto fileBuffer = MakeUnique<wchar_t[]>(FILE_BUFFER_SIZE); + wcsncpy(fileBuffer.get(), mDefaultFilePath.get(), FILE_BUFFER_SIZE); + fileBuffer[FILE_BUFFER_SIZE-1] = '\0'; // null terminate in case copy truncated + + if (!aInitialDir.IsEmpty()) { + ofn.lpstrInitialDir = aInitialDir.get(); + } + + AutoDestroyTmpWindow adtw((HWND) (mParentWidget.get() ? + mParentWidget->GetNativeData(NS_NATIVE_TMP_WINDOW) : nullptr)); + + ofn.lpstrTitle = (LPCWSTR)mTitle.get(); + ofn.lpstrFilter = (LPCWSTR)filterBuffer.get(); + ofn.nFilterIndex = mSelectedType; + ofn.lpstrFile = fileBuffer.get(); + ofn.nMaxFile = FILE_BUFFER_SIZE; + ofn.hwndOwner = adtw.get(); + ofn.lCustData = reinterpret_cast<LPARAM>(this); + ofn.Flags = OFN_SHAREAWARE | OFN_LONGNAMES | OFN_OVERWRITEPROMPT | + OFN_HIDEREADONLY | OFN_PATHMUSTEXIST | OFN_ENABLESIZING | + OFN_EXPLORER; + + // Windows Vista and up won't allow you to use the new looking dialogs with + // a hook procedure. The hook procedure fixes a problem on XP dialogs for + // file picker visibility. Vista and up automatically ensures the file + // picker is always visible. + if (!IsVistaOrLater()) { + ofn.lpfnHook = FilePickerHook; + ofn.Flags |= OFN_ENABLEHOOK; + } + + // Handle add to recent docs settings + if (IsPrivacyModeEnabled() || !mAddToRecentDocs) { + ofn.Flags |= OFN_DONTADDTORECENT; + } + + NS_NAMED_LITERAL_STRING(htmExt, "html"); + + if (!mDefaultExtension.IsEmpty()) { + ofn.lpstrDefExt = mDefaultExtension.get(); + } else if (IsDefaultPathHtml()) { + // Get file extension from suggested filename to detect if we are + // saving an html file. + // This is supposed to append ".htm" if user doesn't supply an + // extension but the behavior is sort of weird: + // - Often appends ".html" even if you have an extension + // - It obeys your extension if you put quotes around name + ofn.lpstrDefExt = htmExt.get(); + } + + // When possible, instead of using OFN_NOCHANGEDIR to ensure the current + // working directory will not change from this call, we will retrieve the + // current working directory before the call and restore it after the + // call. This flag causes problems on Windows XP for paths that are + // selected like C:test.txt where the user is currently at C:\somepath + // In which case expected result should be C:\somepath\test.txt + AutoRestoreWorkingPath restoreWorkingPath; + // If we can't get the current working directory, the best case is to + // use the OFN_NOCHANGEDIR flag + if (!restoreWorkingPath.HasWorkingPath()) { + ofn.Flags |= OFN_NOCHANGEDIR; + } + + bool result = false; + + switch(mMode) { + case modeOpen: + // FILE MUST EXIST! + ofn.Flags |= OFN_FILEMUSTEXIST; + result = FilePickerWrapper(&ofn, PICKER_TYPE_OPEN); + break; + + case modeOpenMultiple: + ofn.Flags |= OFN_FILEMUSTEXIST | OFN_ALLOWMULTISELECT; + + // The hook set here ensures that the buffer returned will always be + // large enough to hold all selected files. The hook may modify the + // value of ofn.lpstrFile and deallocate the old buffer that it pointed + // to (fileBuffer). The hook assumes that the passed in value is heap + // allocated and that the returned value should be freed by the caller. + // If the hook changes the buffer, it will deallocate the old buffer. + // This fix would be nice to have in Vista and up, but it would force + // the file picker to use the old style dialogs because hooks are not + // allowed in the new file picker UI. We need to eventually move to + // the new Common File Dialogs for Vista and up. + if (!IsVistaOrLater()) { + ofn.lpfnHook = MultiFilePickerHook; + fileBuffer.release(); + result = FilePickerWrapper(&ofn, PICKER_TYPE_OPEN); + fileBuffer.reset(ofn.lpstrFile); + } else { + result = FilePickerWrapper(&ofn, PICKER_TYPE_OPEN); + } + break; + + case modeSave: + { + ofn.Flags |= OFN_NOREADONLYRETURN; + + // Don't follow shortcuts when saving a shortcut, this can be used + // to trick users (bug 271732) + if (IsDefaultPathLink()) + ofn.Flags |= OFN_NODEREFERENCELINKS; + + result = FilePickerWrapper(&ofn, PICKER_TYPE_SAVE); + if (!result) { + // Error, find out what kind. + if (GetLastError() == ERROR_INVALID_PARAMETER || + CommDlgExtendedError() == FNERR_INVALIDFILENAME) { + // Probably the default file name is too long or contains illegal + // characters. Try again, without a starting file name. + ofn.lpstrFile[0] = L'\0'; + result = FilePickerWrapper(&ofn, PICKER_TYPE_SAVE); + } + } + } + break; + + default: + NS_NOTREACHED("unsupported file picker mode"); + return false; + } + + if (!result) + return false; + + // Remember what filter type the user selected + mSelectedType = (int16_t)ofn.nFilterIndex; + + // Single file selection, we're done + if (mMode != modeOpenMultiple) { + GetQualifiedPath(fileBuffer.get(), mUnicodeFile); + return true; + } + + // Set user-selected location of file or directory. From msdn's "Open and + // Save As Dialog Boxes" section: + // If you specify OFN_EXPLORER, the directory and file name strings are '\0' + // separated, with an extra '\0' character after the last file name. This + // format enables the Explorer-style dialog boxes to return long file names + // that include spaces. + wchar_t *current = fileBuffer.get(); + + nsAutoString dirName(current); + // Sometimes dirName contains a trailing slash and sometimes it doesn't: + if (current[dirName.Length() - 1] != '\\') + dirName.Append((char16_t)'\\'); + + while (current && *current && *(current + wcslen(current) + 1)) { + current = current + wcslen(current) + 1; + + nsCOMPtr<nsIFile> file = do_CreateInstance("@mozilla.org/file/local;1"); + NS_ENSURE_TRUE(file, false); + + // Only prepend the directory if the path specified is a relative path + nsAutoString path; + if (PathIsRelativeW(current)) { + path = dirName + nsDependentString(current); + } else { + path = current; + } + + nsAutoString canonicalizedPath; + GetQualifiedPath(path.get(), canonicalizedPath); + if (NS_FAILED(file->InitWithPath(canonicalizedPath)) || + !mFiles.AppendObject(file)) + return false; + } + + // Handle the case where the user selected just one file. From msdn: If you + // specify OFN_ALLOWMULTISELECT and the user selects only one file the + // lpstrFile string does not have a separator between the path and file name. + if (current && *current && (current == fileBuffer.get())) { + nsCOMPtr<nsIFile> file = do_CreateInstance("@mozilla.org/file/local;1"); + NS_ENSURE_TRUE(file, false); + + nsAutoString canonicalizedPath; + GetQualifiedPath(current, canonicalizedPath); + if (NS_FAILED(file->InitWithPath(canonicalizedPath)) || + !mFiles.AppendObject(file)) + return false; + } + + return true; +} + +/* + * Show a file picker post Windows XP + * + * @param aInitialDir The initial directory, the last used directory will be + * used if left blank. + * @param aWasInitError Out parameter will hold true if there was an error + * before the file picker is shown. + * @return true if a file was selected successfully. +*/ +bool +nsFilePicker::ShowFilePicker(const nsString& aInitialDir, bool &aWasInitError) +{ + PROFILER_LABEL_FUNC(js::ProfileEntry::Category::OTHER); + + if (!IsWin8OrLater()) { + // Some Windows 7 users are experiencing a race condition when some dlls + // that are loaded by the file picker cause a crash while attempting to shut + // down the COM multithreaded apartment. By instantiating EnsureMTA, we hold + // an additional reference to the MTA that should prevent this race, since + // the MTA will remain alive until shutdown. + EnsureMTA ensureMTA; + } + + RefPtr<IFileDialog> dialog; + if (mMode != modeSave) { + if (FAILED(CoCreateInstance(CLSID_FileOpenDialog, nullptr, CLSCTX_INPROC, + IID_IFileOpenDialog, + getter_AddRefs(dialog)))) { + aWasInitError = true; + return false; + } + } else { + if (FAILED(CoCreateInstance(CLSID_FileSaveDialog, nullptr, CLSCTX_INPROC, + IID_IFileSaveDialog, + getter_AddRefs(dialog)))) { + aWasInitError = true; + return false; + } + } + aWasInitError = false; + + // hook up event callbacks + dialog->Advise(this, &mFDECookie); + + // options + + FILEOPENDIALOGOPTIONS fos = 0; + fos |= FOS_SHAREAWARE | FOS_OVERWRITEPROMPT | + FOS_FORCEFILESYSTEM; + + // Handle add to recent docs settings + if (IsPrivacyModeEnabled() || !mAddToRecentDocs) { + fos |= FOS_DONTADDTORECENT; + } + + // Msdn claims FOS_NOCHANGEDIR is not needed. We'll add this + // just in case. + AutoRestoreWorkingPath arw; + + // mode specific + switch(mMode) { + case modeOpen: + fos |= FOS_FILEMUSTEXIST; + break; + + case modeOpenMultiple: + fos |= FOS_FILEMUSTEXIST | FOS_ALLOWMULTISELECT; + break; + + case modeSave: + fos |= FOS_NOREADONLYRETURN; + // Don't follow shortcuts when saving a shortcut, this can be used + // to trick users (bug 271732) + if (IsDefaultPathLink()) + fos |= FOS_NODEREFERENCELINKS; + break; + } + + dialog->SetOptions(fos); + + // initial strings + + // title + dialog->SetTitle(mTitle.get()); + + // default filename + if (!mDefaultFilename.IsEmpty()) { + dialog->SetFileName(mDefaultFilename.get()); + } + + NS_NAMED_LITERAL_STRING(htmExt, "html"); + + // default extension to append to new files + if (!mDefaultExtension.IsEmpty()) { + dialog->SetDefaultExtension(mDefaultExtension.get()); + } else if (IsDefaultPathHtml()) { + dialog->SetDefaultExtension(htmExt.get()); + } + + // initial location + if (!aInitialDir.IsEmpty()) { + RefPtr<IShellItem> folder; + if (SUCCEEDED( + WinUtils::SHCreateItemFromParsingName(aInitialDir.get(), nullptr, + IID_IShellItem, + getter_AddRefs(folder)))) { + dialog->SetFolder(folder); + } + } + + // filter types and the default index + if (!mComFilterList.IsEmpty()) { + dialog->SetFileTypes(mComFilterList.Length(), mComFilterList.get()); + dialog->SetFileTypeIndex(mSelectedType); + } + + // display + + { + AutoDestroyTmpWindow adtw((HWND)(mParentWidget.get() ? + mParentWidget->GetNativeData(NS_NATIVE_TMP_WINDOW) : nullptr)); + AutoTimerCallbackCancel atcc(this, PickerCallbackTimerFunc); + AutoWidgetPickerState awps(mParentWidget); + + if (FAILED(dialog->Show(adtw.get()))) { + dialog->Unadvise(mFDECookie); + return false; + } + dialog->Unadvise(mFDECookie); + } + + // results + + // Remember what filter type the user selected + UINT filterIdxResult; + if (SUCCEEDED(dialog->GetFileTypeIndex(&filterIdxResult))) { + mSelectedType = (int16_t)filterIdxResult; + } + + // single selection + if (mMode != modeOpenMultiple) { + RefPtr<IShellItem> item; + if (FAILED(dialog->GetResult(getter_AddRefs(item))) || !item) + return false; + return WinUtils::GetShellItemPath(item, mUnicodeFile); + } + + // multiple selection + RefPtr<IFileOpenDialog> openDlg; + dialog->QueryInterface(IID_IFileOpenDialog, getter_AddRefs(openDlg)); + if (!openDlg) { + // should not happen + return false; + } + + RefPtr<IShellItemArray> items; + if (FAILED(openDlg->GetResults(getter_AddRefs(items))) || !items) { + return false; + } + + DWORD count = 0; + items->GetCount(&count); + for (unsigned int idx = 0; idx < count; idx++) { + RefPtr<IShellItem> item; + nsAutoString str; + if (SUCCEEDED(items->GetItemAt(idx, getter_AddRefs(item)))) { + if (!WinUtils::GetShellItemPath(item, str)) + continue; + nsCOMPtr<nsIFile> file = do_CreateInstance("@mozilla.org/file/local;1"); + if (file && NS_SUCCEEDED(file->InitWithPath(str))) + mFiles.AppendObject(file); + } + } + return true; +} + +/////////////////////////////////////////////////////////////////////////////// +// nsIFilePicker impl. + +NS_IMETHODIMP +nsFilePicker::ShowW(int16_t *aReturnVal) +{ + NS_ENSURE_ARG_POINTER(aReturnVal); + + *aReturnVal = returnCancel; + + AutoSuppressEvents supress(mParentWidget); + + nsAutoString initialDir; + if (mDisplayDirectory) + mDisplayDirectory->GetPath(initialDir); + + // If no display directory, re-use the last one. + if(initialDir.IsEmpty()) { + // Allocate copy of last used dir. + initialDir = mLastUsedUnicodeDirectory; + } + + // Clear previous file selections + mUnicodeFile.Truncate(); + mFiles.Clear(); + + // On Win10, the picker doesn't support per-monitor DPI, so we open it + // with our context set temporarily to system-dpi-aware + WinUtils::AutoSystemDpiAware dpiAwareness; + + // Launch the XP file/folder picker on XP and as a fallback on Vista+. + // The CoCreateInstance call to CLSID_FileOpenDialog fails with "(0x80040111) + // ClassFactory cannot supply requested class" when the checkbox for + // Disable Visual Themes is on in the compatability tab within the shortcut + // properties. + bool result = false, wasInitError = true; + if (mMode == modeGetFolder) { + if (IsVistaOrLater()) + result = ShowFolderPicker(initialDir, wasInitError); + if (!result && wasInitError) + result = ShowXPFolderPicker(initialDir); + } else { + if (IsVistaOrLater()) + result = ShowFilePicker(initialDir, wasInitError); + if (!result && wasInitError) + result = ShowXPFilePicker(initialDir); + } + + // exit, and return returnCancel in aReturnVal + if (!result) + return NS_OK; + + RememberLastUsedDirectory(); + + int16_t retValue = returnOK; + if (mMode == modeSave) { + // Windows does not return resultReplace, we must check if file + // already exists. + nsCOMPtr<nsIFile> file(do_CreateInstance("@mozilla.org/file/local;1")); + bool flag = false; + if (file && NS_SUCCEEDED(file->InitWithPath(mUnicodeFile)) && + NS_SUCCEEDED(file->Exists(&flag)) && flag) { + retValue = returnReplace; + } + } + + *aReturnVal = retValue; + return NS_OK; +} + +NS_IMETHODIMP +nsFilePicker::Show(int16_t *aReturnVal) +{ + return ShowW(aReturnVal); +} + +NS_IMETHODIMP +nsFilePicker::GetFile(nsIFile **aFile) +{ + NS_ENSURE_ARG_POINTER(aFile); + *aFile = nullptr; + + if (mUnicodeFile.IsEmpty()) + return NS_OK; + + nsCOMPtr<nsIFile> file(do_CreateInstance("@mozilla.org/file/local;1")); + + NS_ENSURE_TRUE(file, NS_ERROR_FAILURE); + + file->InitWithPath(mUnicodeFile); + + NS_ADDREF(*aFile = file); + + return NS_OK; +} + +NS_IMETHODIMP +nsFilePicker::GetFileURL(nsIURI **aFileURL) +{ + *aFileURL = nullptr; + nsCOMPtr<nsIFile> file; + nsresult rv = GetFile(getter_AddRefs(file)); + if (!file) + return rv; + + return NS_NewFileURI(aFileURL, file); +} + +NS_IMETHODIMP +nsFilePicker::GetFiles(nsISimpleEnumerator **aFiles) +{ + NS_ENSURE_ARG_POINTER(aFiles); + return NS_NewArrayEnumerator(aFiles, mFiles); +} + +// Get the file + path +NS_IMETHODIMP +nsBaseWinFilePicker::SetDefaultString(const nsAString& aString) +{ + mDefaultFilePath = aString; + + // First, make sure the file name is not too long. + int32_t nameLength; + int32_t nameIndex = mDefaultFilePath.RFind("\\"); + if (nameIndex == kNotFound) + nameIndex = 0; + else + nameIndex ++; + nameLength = mDefaultFilePath.Length() - nameIndex; + mDefaultFilename.Assign(Substring(mDefaultFilePath, nameIndex)); + + if (nameLength > MAX_PATH) { + int32_t extIndex = mDefaultFilePath.RFind("."); + if (extIndex == kNotFound) + extIndex = mDefaultFilePath.Length(); + + // Let's try to shave the needed characters from the name part. + int32_t charsToRemove = nameLength - MAX_PATH; + if (extIndex - nameIndex >= charsToRemove) { + mDefaultFilePath.Cut(extIndex - charsToRemove, charsToRemove); + } + } + + // Then, we need to replace illegal characters. At this stage, we cannot + // replace the backslash as the string might represent a file path. + mDefaultFilePath.ReplaceChar(FILE_ILLEGAL_CHARACTERS, '-'); + mDefaultFilename.ReplaceChar(FILE_ILLEGAL_CHARACTERS, '-'); + + return NS_OK; +} + +NS_IMETHODIMP +nsBaseWinFilePicker::GetDefaultString(nsAString& aString) +{ + return NS_ERROR_FAILURE; +} + +// The default extension to use for files +NS_IMETHODIMP +nsBaseWinFilePicker::GetDefaultExtension(nsAString& aExtension) +{ + aExtension = mDefaultExtension; + return NS_OK; +} + +NS_IMETHODIMP +nsBaseWinFilePicker::SetDefaultExtension(const nsAString& aExtension) +{ + mDefaultExtension = aExtension; + return NS_OK; +} + +// Set the filter index +NS_IMETHODIMP +nsFilePicker::GetFilterIndex(int32_t *aFilterIndex) +{ + // Windows' filter index is 1-based, we use a 0-based system. + *aFilterIndex = mSelectedType - 1; + return NS_OK; +} + +NS_IMETHODIMP +nsFilePicker::SetFilterIndex(int32_t aFilterIndex) +{ + // Windows' filter index is 1-based, we use a 0-based system. + mSelectedType = aFilterIndex + 1; + return NS_OK; +} + +void +nsFilePicker::InitNative(nsIWidget *aParent, + const nsAString& aTitle) +{ + mParentWidget = aParent; + mTitle.Assign(aTitle); +} + +void +nsFilePicker::GetQualifiedPath(const wchar_t *aInPath, nsString &aOutPath) +{ + // Prefer a qualified path over a non qualified path. + // Things like c:file.txt would be accepted in Win XP but would later + // fail to open from the download manager. + wchar_t qualifiedFileBuffer[MAX_PATH]; + if (PathSearchAndQualifyW(aInPath, qualifiedFileBuffer, MAX_PATH)) { + aOutPath.Assign(qualifiedFileBuffer); + } else { + aOutPath.Assign(aInPath); + } +} + +void +nsFilePicker::AppendXPFilter(const nsAString& aTitle, const nsAString& aFilter) +{ + mFilterList.Append(aTitle); + mFilterList.Append(char16_t('\0')); + + if (aFilter.EqualsLiteral("..apps")) + mFilterList.AppendLiteral("*.exe;*.com"); + else + { + nsAutoString filter(aFilter); + filter.StripWhitespace(); + if (filter.EqualsLiteral("*")) + filter.AppendLiteral(".*"); + mFilterList.Append(filter); + } + + mFilterList.Append(char16_t('\0')); +} + +NS_IMETHODIMP +nsFilePicker::AppendFilter(const nsAString& aTitle, const nsAString& aFilter) +{ + if (IsVistaOrLater()) { + mComFilterList.Append(aTitle, aFilter); + } else { + AppendXPFilter(aTitle, aFilter); + } + return NS_OK; +} + +void +nsFilePicker::RememberLastUsedDirectory() +{ + nsCOMPtr<nsIFile> file(do_CreateInstance("@mozilla.org/file/local;1")); + if (!file || NS_FAILED(file->InitWithPath(mUnicodeFile))) { + NS_WARNING("RememberLastUsedDirectory failed to init file path."); + return; + } + + nsCOMPtr<nsIFile> dir; + nsAutoString newDir; + if (NS_FAILED(file->GetParent(getter_AddRefs(dir))) || + !(mDisplayDirectory = do_QueryInterface(dir)) || + NS_FAILED(mDisplayDirectory->GetPath(newDir)) || + newDir.IsEmpty()) { + NS_WARNING("RememberLastUsedDirectory failed to get parent directory."); + return; + } + + if (mLastUsedUnicodeDirectory) { + free(mLastUsedUnicodeDirectory); + mLastUsedUnicodeDirectory = nullptr; + } + mLastUsedUnicodeDirectory = ToNewUnicode(newDir); +} + +bool +nsFilePicker::IsPrivacyModeEnabled() +{ + return mLoadContext && mLoadContext->UsePrivateBrowsing(); +} + +bool +nsFilePicker::IsDefaultPathLink() +{ + NS_ConvertUTF16toUTF8 ext(mDefaultFilePath); + ext.Trim(" .", false, true); // watch out for trailing space and dots + ToLowerCase(ext); + if (StringEndsWith(ext, NS_LITERAL_CSTRING(".lnk")) || + StringEndsWith(ext, NS_LITERAL_CSTRING(".pif")) || + StringEndsWith(ext, NS_LITERAL_CSTRING(".url"))) + return true; + return false; +} + +bool +nsFilePicker::IsDefaultPathHtml() +{ + int32_t extIndex = mDefaultFilePath.RFind("."); + if (extIndex >= 0) { + nsAutoString ext; + mDefaultFilePath.Right(ext, mDefaultFilePath.Length() - extIndex); + if (ext.LowerCaseEqualsLiteral(".htm") || + ext.LowerCaseEqualsLiteral(".html") || + ext.LowerCaseEqualsLiteral(".shtml")) + return true; + } + return false; +} + +void +nsFilePicker::ComDlgFilterSpec::Append(const nsAString& aTitle, const nsAString& aFilter) +{ + COMDLG_FILTERSPEC* pSpecForward = mSpecList.AppendElement(); + if (!pSpecForward) { + NS_WARNING("mSpecList realloc failed."); + return; + } + memset(pSpecForward, 0, sizeof(*pSpecForward)); + nsString* pStr = mStrings.AppendElement(aTitle); + if (!pStr) { + NS_WARNING("mStrings.AppendElement failed."); + return; + } + pSpecForward->pszName = pStr->get(); + pStr = mStrings.AppendElement(aFilter); + if (!pStr) { + NS_WARNING("mStrings.AppendElement failed."); + return; + } + if (aFilter.EqualsLiteral("..apps")) + pStr->AssignLiteral("*.exe;*.com"); + else { + pStr->StripWhitespace(); + if (pStr->EqualsLiteral("*")) + pStr->AppendLiteral(".*"); + } + pSpecForward->pszSpec = pStr->get(); +} diff --git a/widget/windows/nsFilePicker.h b/widget/windows/nsFilePicker.h new file mode 100644 index 0000000000..90d8c15bc1 --- /dev/null +++ b/widget/windows/nsFilePicker.h @@ -0,0 +1,164 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsFilePicker_h__ +#define nsFilePicker_h__ + +#include <windows.h> + +// For Vista IFileDialog interfaces which aren't exposed +// unless _WIN32_WINNT >= _WIN32_WINNT_LONGHORN. +#if _WIN32_WINNT < _WIN32_WINNT_LONGHORN +#define _WIN32_WINNT_bak _WIN32_WINNT +#undef _WIN32_WINNT +#define _WIN32_WINNT _WIN32_WINNT_LONGHORN +#define _WIN32_IE_bak _WIN32_IE +#undef _WIN32_IE +#define _WIN32_IE _WIN32_IE_IE70 +#endif + +#include "nsIFile.h" +#include "nsITimer.h" +#include "nsISimpleEnumerator.h" +#include "nsCOMArray.h" +#include "nsBaseFilePicker.h" +#include "nsString.h" +#include "nsdefs.h" +#include <commdlg.h> +#include <shobjidl.h> +#undef LogSeverity // SetupAPI.h #defines this as DWORD + +class nsILoadContext; + +class nsBaseWinFilePicker : + public nsBaseFilePicker +{ +public: + NS_IMETHOD GetDefaultString(nsAString& aDefaultString); + NS_IMETHOD SetDefaultString(const nsAString& aDefaultString); + NS_IMETHOD GetDefaultExtension(nsAString& aDefaultExtension); + NS_IMETHOD SetDefaultExtension(const nsAString& aDefaultExtension); + +protected: + nsString mDefaultFilePath; + nsString mDefaultFilename; + nsString mDefaultExtension; +}; + +/** + * Native Windows FileSelector wrapper + */ + +class nsFilePicker : + public IFileDialogEvents, + public nsBaseWinFilePicker +{ + virtual ~nsFilePicker(); +public: + nsFilePicker(); + + NS_IMETHOD Init(mozIDOMWindowProxy *aParent, const nsAString& aTitle, int16_t aMode); + + NS_DECL_ISUPPORTS + + // IUnknown's QueryInterface + STDMETHODIMP QueryInterface(REFIID refiid, void** ppvResult); + + // nsIFilePicker (less what's in nsBaseFilePicker and nsBaseWinFilePicker) + NS_IMETHOD GetFilterIndex(int32_t *aFilterIndex); + NS_IMETHOD SetFilterIndex(int32_t aFilterIndex); + NS_IMETHOD GetFile(nsIFile * *aFile); + NS_IMETHOD GetFileURL(nsIURI * *aFileURL); + NS_IMETHOD GetFiles(nsISimpleEnumerator **aFiles); + NS_IMETHOD Show(int16_t *aReturnVal); + NS_IMETHOD ShowW(int16_t *aReturnVal); + NS_IMETHOD AppendFilter(const nsAString& aTitle, const nsAString& aFilter); + + // IFileDialogEvents + HRESULT STDMETHODCALLTYPE OnFileOk(IFileDialog *pfd); + HRESULT STDMETHODCALLTYPE OnFolderChanging(IFileDialog *pfd, IShellItem *psiFolder); + HRESULT STDMETHODCALLTYPE OnFolderChange(IFileDialog *pfd); + HRESULT STDMETHODCALLTYPE OnSelectionChange(IFileDialog *pfd); + HRESULT STDMETHODCALLTYPE OnShareViolation(IFileDialog *pfd, IShellItem *psi, FDE_SHAREVIOLATION_RESPONSE *pResponse); + HRESULT STDMETHODCALLTYPE OnTypeChange(IFileDialog *pfd); + HRESULT STDMETHODCALLTYPE OnOverwrite(IFileDialog *pfd, IShellItem *psi, FDE_OVERWRITE_RESPONSE *pResponse); + +protected: + enum PickerType { + PICKER_TYPE_OPEN, + PICKER_TYPE_SAVE, + }; + + /* method from nsBaseFilePicker */ + virtual void InitNative(nsIWidget *aParent, + const nsAString& aTitle); + static void GetQualifiedPath(const wchar_t *aInPath, nsString &aOutPath); + void GetFilterListArray(nsString& aFilterList); + static bool GetFileNameWrapper(OPENFILENAMEW* ofn, PickerType aType); + bool FilePickerWrapper(OPENFILENAMEW* ofn, PickerType aType); + bool ShowXPFolderPicker(const nsString& aInitialDir); + bool ShowXPFilePicker(const nsString& aInitialDir); + bool ShowFolderPicker(const nsString& aInitialDir, bool &aWasInitError); + bool ShowFilePicker(const nsString& aInitialDir, bool &aWasInitError); + void AppendXPFilter(const nsAString& aTitle, const nsAString& aFilter); + void RememberLastUsedDirectory(); + bool IsPrivacyModeEnabled(); + bool IsDefaultPathLink(); + bool IsDefaultPathHtml(); + void SetDialogHandle(HWND aWnd); + bool ClosePickerIfNeeded(bool aIsXPDialog); + static void PickerCallbackTimerFunc(nsITimer *aTimer, void *aPicker); + static UINT_PTR CALLBACK MultiFilePickerHook(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); + static UINT_PTR CALLBACK FilePickerHook(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); + + nsCOMPtr<nsILoadContext> mLoadContext; + nsCOMPtr<nsIWidget> mParentWidget; + nsString mTitle; + nsCString mFile; + nsString mFilterList; + int16_t mSelectedType; + nsCOMArray<nsIFile> mFiles; + static char mLastUsedDirectory[]; + nsString mUnicodeFile; + static char16_t *mLastUsedUnicodeDirectory; + HWND mDlgWnd; + + class ComDlgFilterSpec + { + public: + ComDlgFilterSpec() {} + ~ComDlgFilterSpec() {} + + const uint32_t Length() { + return mSpecList.Length(); + } + + const bool IsEmpty() { + return (mSpecList.Length() == 0); + } + + const COMDLG_FILTERSPEC* get() { + return mSpecList.Elements(); + } + + void Append(const nsAString& aTitle, const nsAString& aFilter); + private: + AutoTArray<COMDLG_FILTERSPEC, 1> mSpecList; + AutoTArray<nsString, 2> mStrings; + }; + + ComDlgFilterSpec mComFilterList; + DWORD mFDECookie; +}; + +#if defined(_WIN32_WINNT_bak) +#undef _WIN32_WINNT +#define _WIN32_WINNT _WIN32_WINNT_bak +#undef _WIN32_IE +#define _WIN32_IE _WIN32_IE_bak +#endif + +#endif // nsFilePicker_h__ diff --git a/widget/windows/nsIdleServiceWin.cpp b/widget/windows/nsIdleServiceWin.cpp new file mode 100644 index 0000000000..4bafe7253c --- /dev/null +++ b/widget/windows/nsIdleServiceWin.cpp @@ -0,0 +1,30 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim:expandtab:shiftwidth=4:tabstop=4: + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIdleServiceWin.h" +#include <windows.h> + +NS_IMPL_ISUPPORTS_INHERITED0(nsIdleServiceWin, nsIdleService) + +bool +nsIdleServiceWin::PollIdleTime(uint32_t *aIdleTime) +{ + LASTINPUTINFO inputInfo; + inputInfo.cbSize = sizeof(inputInfo); + if (!::GetLastInputInfo(&inputInfo)) + return false; + + *aIdleTime = SAFE_COMPARE_EVEN_WITH_WRAPPING(GetTickCount(), inputInfo.dwTime); + + return true; +} + +bool +nsIdleServiceWin::UsePollMode() +{ + return true; +} diff --git a/widget/windows/nsIdleServiceWin.h b/widget/windows/nsIdleServiceWin.h new file mode 100644 index 0000000000..c3fb1243fd --- /dev/null +++ b/widget/windows/nsIdleServiceWin.h @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim:expandtab:shiftwidth=4:tabstop=4: + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsIdleServiceWin_h__ +#define nsIdleServiceWin_h__ + +#include "nsIdleService.h" + + +/* NOTE: Compare of GetTickCount() could overflow. This corrects for +* overflow situations. +***/ +#ifndef SAFE_COMPARE_EVEN_WITH_WRAPPING +#define SAFE_COMPARE_EVEN_WITH_WRAPPING(A, B) (((int)((long)A - (long)B) & 0xFFFFFFFF)) +#endif + + +class nsIdleServiceWin : public nsIdleService +{ +public: + NS_DECL_ISUPPORTS_INHERITED + + bool PollIdleTime(uint32_t* aIdleTime) override; + + static already_AddRefed<nsIdleServiceWin> GetInstance() + { + RefPtr<nsIdleServiceWin> idleService = + nsIdleService::GetInstance().downcast<nsIdleServiceWin>(); + if (!idleService) { + idleService = new nsIdleServiceWin(); + } + + return idleService.forget(); + } + +protected: + nsIdleServiceWin() { } + virtual ~nsIdleServiceWin() { } + bool UsePollMode() override; +}; + +#endif // nsIdleServiceWin_h__ diff --git a/widget/windows/nsImageClipboard.cpp b/widget/windows/nsImageClipboard.cpp new file mode 100644 index 0000000000..fab62eab58 --- /dev/null +++ b/widget/windows/nsImageClipboard.cpp @@ -0,0 +1,497 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsImageClipboard.h" + +#include "gfxUtils.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/DataSurfaceHelpers.h" +#include "mozilla/RefPtr.h" +#include "nsITransferable.h" +#include "nsGfxCIID.h" +#include "nsMemory.h" +#include "prmem.h" +#include "imgIEncoder.h" +#include "nsLiteralString.h" +#include "nsComponentManagerUtils.h" + +#define BFH_LENGTH 14 + +using namespace mozilla; +using namespace mozilla::gfx; + +/* Things To Do 11/8/00 + +Check image metrics, can we support them? Do we need to? +Any other render format? HTML? + +*/ + + +// +// nsImageToClipboard ctor +// +// Given an imgIContainer, convert it to a DIB that is ready to go on the win32 clipboard +// +nsImageToClipboard::nsImageToClipboard(imgIContainer* aInImage, bool aWantDIBV5) + : mImage(aInImage) + , mWantDIBV5(aWantDIBV5) +{ + // nothing to do here +} + + +// +// nsImageToClipboard dtor +// +// Clean up after ourselves. We know that we have created the bitmap +// successfully if we still have a pointer to the header. +// +nsImageToClipboard::~nsImageToClipboard() +{ +} + + +// +// GetPicture +// +// Call to get the actual bits that go on the clipboard. If an error +// ocurred during conversion, |outBits| will be null. +// +// NOTE: The caller owns the handle and must delete it with ::GlobalRelease() +// +nsresult +nsImageToClipboard :: GetPicture ( HANDLE* outBits ) +{ + NS_ASSERTION ( outBits, "Bad parameter" ); + + return CreateFromImage ( mImage, outBits ); + +} // GetPicture + + +// +// CalcSize +// +// Computes # of bytes needed by a bitmap with the specified attributes. +// +int32_t +nsImageToClipboard :: CalcSize ( int32_t aHeight, int32_t aColors, WORD aBitsPerPixel, int32_t aSpanBytes ) +{ + int32_t HeaderMem = sizeof(BITMAPINFOHEADER); + + // add size of pallette to header size + if (aBitsPerPixel < 16) + HeaderMem += aColors * sizeof(RGBQUAD); + + if (aHeight < 0) + aHeight = -aHeight; + + return (HeaderMem + (aHeight * aSpanBytes)); +} + + +// +// CalcSpanLength +// +// Computes the span bytes for determining the overall size of the image +// +int32_t +nsImageToClipboard::CalcSpanLength(uint32_t aWidth, uint32_t aBitCount) +{ + int32_t spanBytes = (aWidth * aBitCount) >> 5; + + if ((aWidth * aBitCount) & 0x1F) + spanBytes++; + spanBytes <<= 2; + + return spanBytes; +} + + +// +// CreateFromImage +// +// Do the work to setup the bitmap header and copy the bits out of the +// image. +// +nsresult +nsImageToClipboard::CreateFromImage ( imgIContainer* inImage, HANDLE* outBitmap ) +{ + nsresult rv; + *outBitmap = nullptr; + + RefPtr<SourceSurface> surface = + inImage->GetFrame(imgIContainer::FRAME_CURRENT, + imgIContainer::FLAG_SYNC_DECODE); + NS_ENSURE_TRUE(surface, NS_ERROR_FAILURE); + + MOZ_ASSERT(surface->GetFormat() == SurfaceFormat::B8G8R8A8 || + surface->GetFormat() == SurfaceFormat::B8G8R8X8); + + RefPtr<DataSourceSurface> dataSurface; + if (surface->GetFormat() == SurfaceFormat::B8G8R8A8) { + dataSurface = surface->GetDataSurface(); + } else { + // XXXjwatt Bug 995923 - get rid of this copy and handle B8G8R8X8 + // directly below once bug 995807 is fixed. + dataSurface = gfxUtils:: + CopySurfaceToDataSourceSurfaceWithFormat(surface, + SurfaceFormat::B8G8R8A8); + } + NS_ENSURE_TRUE(dataSurface, NS_ERROR_FAILURE); + + nsCOMPtr<imgIEncoder> encoder = do_CreateInstance("@mozilla.org/image/encoder;2?type=image/bmp", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t format; + nsAutoString options; + if (mWantDIBV5) { + options.AppendLiteral("version=5;bpp="); + } else { + options.AppendLiteral("version=3;bpp="); + } + switch (dataSurface->GetFormat()) { + case SurfaceFormat::B8G8R8A8: + format = imgIEncoder::INPUT_FORMAT_HOSTARGB; + options.AppendInt(32); + break; +#if 0 + // XXXjwatt Bug 995923 - fix |format| and reenable once bug 995807 is fixed. + case SurfaceFormat::B8G8R8X8: + format = imgIEncoder::INPUT_FORMAT_RGB; + options.AppendInt(24); + break; +#endif + default: + NS_NOTREACHED("Unexpected surface format"); + return NS_ERROR_INVALID_ARG; + } + + DataSourceSurface::MappedSurface map; + bool mappedOK = dataSurface->Map(DataSourceSurface::MapType::READ, &map); + NS_ENSURE_TRUE(mappedOK, NS_ERROR_FAILURE); + + rv = encoder->InitFromData(map.mData, 0, + dataSurface->GetSize().width, + dataSurface->GetSize().height, + map.mStride, + format, options); + dataSurface->Unmap(); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t size; + encoder->GetImageBufferUsed(&size); + NS_ENSURE_TRUE(size > BFH_LENGTH, NS_ERROR_FAILURE); + HGLOBAL glob = ::GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE | GMEM_ZEROINIT, + size - BFH_LENGTH); + if (!glob) + return NS_ERROR_OUT_OF_MEMORY; + + char *dst = (char*) ::GlobalLock(glob); + char *src; + rv = encoder->GetImageBuffer(&src); + NS_ENSURE_SUCCESS(rv, rv); + + ::CopyMemory(dst, src + BFH_LENGTH, size - BFH_LENGTH); + ::GlobalUnlock(glob); + + *outBitmap = (HANDLE)glob; + return NS_OK; +} + +nsImageFromClipboard :: nsImageFromClipboard () +{ + // nothing to do here +} + +nsImageFromClipboard :: ~nsImageFromClipboard ( ) +{ +} + +// +// GetEncodedImageStream +// +// Take the raw clipboard image data and convert it to aMIMEFormat in the form of a nsIInputStream +// +nsresult +nsImageFromClipboard ::GetEncodedImageStream (unsigned char * aClipboardData, const char * aMIMEFormat, nsIInputStream** aInputStream ) +{ + NS_ENSURE_ARG_POINTER (aInputStream); + NS_ENSURE_ARG_POINTER (aMIMEFormat); + nsresult rv; + *aInputStream = nullptr; + + // pull the size information out of the BITMAPINFO header and + // initialize the image + BITMAPINFO* header = (BITMAPINFO *) aClipboardData; + int32_t width = header->bmiHeader.biWidth; + int32_t height = header->bmiHeader.biHeight; + // neg. heights mean the Y axis is inverted and we don't handle that case + NS_ENSURE_TRUE(height > 0, NS_ERROR_FAILURE); + + unsigned char * rgbData = new unsigned char[width * height * 3 /* RGB */]; + + if (rgbData) { + BYTE * pGlobal = (BYTE *) aClipboardData; + // Convert the clipboard image into RGB packed pixel data + rv = ConvertColorBitMap((unsigned char *) (pGlobal + header->bmiHeader.biSize), header, rgbData); + // if that succeeded, encode the bitmap as aMIMEFormat data. Don't return early or we risk leaking rgbData + if (NS_SUCCEEDED(rv)) { + nsAutoCString encoderCID(NS_LITERAL_CSTRING("@mozilla.org/image/encoder;2?type=")); + + // Map image/jpg to image/jpeg (which is how the encoder is registered). + if (strcmp(aMIMEFormat, kJPGImageMime) == 0) + encoderCID.AppendLiteral("image/jpeg"); + else + encoderCID.Append(aMIMEFormat); + nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(encoderCID.get(), &rv); + if (NS_SUCCEEDED(rv)){ + rv = encoder->InitFromData(rgbData, 0, width, height, 3 * width /* RGB * # pixels in a row */, + imgIEncoder::INPUT_FORMAT_RGB, EmptyString()); + if (NS_SUCCEEDED(rv)) { + encoder.forget(aInputStream); + } + } + } + delete [] rgbData; + } + else + rv = NS_ERROR_OUT_OF_MEMORY; + + return rv; +} // GetImage + +// +// InvertRows +// +// Take the image data from the clipboard and invert the rows. Modifying aInitialBuffer in place. +// +void +nsImageFromClipboard::InvertRows(unsigned char * aInitialBuffer, uint32_t aSizeOfBuffer, uint32_t aNumBytesPerRow) +{ + if (!aNumBytesPerRow) + return; + + uint32_t numRows = aSizeOfBuffer / aNumBytesPerRow; + unsigned char * row = new unsigned char[aNumBytesPerRow]; + + uint32_t currentRow = 0; + uint32_t lastRow = (numRows - 1) * aNumBytesPerRow; + while (currentRow < lastRow) + { + // store the current row into a temporary buffer + memcpy(row, &aInitialBuffer[currentRow], aNumBytesPerRow); + memcpy(&aInitialBuffer[currentRow], &aInitialBuffer[lastRow], aNumBytesPerRow); + memcpy(&aInitialBuffer[lastRow], row, aNumBytesPerRow); + lastRow -= aNumBytesPerRow; + currentRow += aNumBytesPerRow; + } + + delete[] row; +} + +// +// ConvertColorBitMap +// +// Takes the clipboard bitmap and converts it into a RGB packed pixel values. +// +nsresult +nsImageFromClipboard::ConvertColorBitMap(unsigned char * aInputBuffer, PBITMAPINFO pBitMapInfo, unsigned char * aOutBuffer) +{ + uint8_t bitCount = pBitMapInfo->bmiHeader.biBitCount; + uint32_t imageSize = pBitMapInfo->bmiHeader.biSizeImage; // may be zero for BI_RGB bitmaps which means we need to calculate by hand + uint32_t bytesPerPixel = bitCount / 8; + + if (bitCount <= 4) + bytesPerPixel = 1; + + // rows are DWORD aligned. Calculate how many real bytes are in each row in the bitmap. This number won't + // correspond to biWidth. + uint32_t rowSize = (bitCount * pBitMapInfo->bmiHeader.biWidth + 7) / 8; // +7 to round up + if (rowSize % 4) + rowSize += (4 - (rowSize % 4)); // Pad to DWORD Boundary + + // if our buffer includes a color map, skip over it + if (bitCount <= 8) + { + int32_t bytesToSkip = (pBitMapInfo->bmiHeader.biClrUsed ? pBitMapInfo->bmiHeader.biClrUsed : (1 << bitCount) ) * sizeof(RGBQUAD); + aInputBuffer += bytesToSkip; + } + + bitFields colorMasks; // only used if biCompression == BI_BITFIELDS + + if (pBitMapInfo->bmiHeader.biCompression == BI_BITFIELDS) + { + // color table consists of 3 DWORDS containing the color masks... + colorMasks.red = (*((uint32_t*)&(pBitMapInfo->bmiColors[0]))); + colorMasks.green = (*((uint32_t*)&(pBitMapInfo->bmiColors[1]))); + colorMasks.blue = (*((uint32_t*)&(pBitMapInfo->bmiColors[2]))); + CalcBitShift(&colorMasks); + aInputBuffer += 3 * sizeof(DWORD); + } + else if (pBitMapInfo->bmiHeader.biCompression == BI_RGB && !imageSize) // BI_RGB can have a size of zero which means we figure it out + { + // XXX: note use rowSize here and not biWidth. rowSize accounts for the DWORD padding for each row + imageSize = rowSize * pBitMapInfo->bmiHeader.biHeight; + } + + // The windows clipboard image format inverts the rows + InvertRows(aInputBuffer, imageSize, rowSize); + + if (!pBitMapInfo->bmiHeader.biCompression || pBitMapInfo->bmiHeader.biCompression == BI_BITFIELDS) + { + uint32_t index = 0; + uint32_t writeIndex = 0; + + unsigned char redValue, greenValue, blueValue; + uint8_t colorTableEntry = 0; + int8_t bit; // used for grayscale bitmaps where each bit is a pixel + uint32_t numPixelsLeftInRow = pBitMapInfo->bmiHeader.biWidth; // how many more pixels do we still need to read for the current row + uint32_t pos = 0; + + while (index < imageSize) + { + switch (bitCount) + { + case 1: + for (bit = 7; bit >= 0 && numPixelsLeftInRow; bit--) + { + colorTableEntry = (aInputBuffer[index] >> bit) & 1; + aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbRed; + aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbGreen; + aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbBlue; + numPixelsLeftInRow--; + } + pos += 1; + break; + case 4: + { + // each aInputBuffer[index] entry contains data for two pixels. + // read the first pixel + colorTableEntry = aInputBuffer[index] >> 4; + aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbRed; + aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbGreen; + aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbBlue; + numPixelsLeftInRow--; + + if (numPixelsLeftInRow) // now read the second pixel + { + colorTableEntry = aInputBuffer[index] & 0xF; + aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbRed; + aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbGreen; + aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbBlue; + numPixelsLeftInRow--; + } + pos += 1; + } + break; + case 8: + aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[aInputBuffer[index]].rgbRed; + aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[aInputBuffer[index]].rgbGreen; + aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[aInputBuffer[index]].rgbBlue; + numPixelsLeftInRow--; + pos += 1; + break; + case 16: + { + uint16_t num = 0; + num = (uint8_t) aInputBuffer[index+1]; + num <<= 8; + num |= (uint8_t) aInputBuffer[index]; + + redValue = ((uint32_t) (((float)(num & 0xf800) / 0xf800) * 0xFF0000) & 0xFF0000)>> 16; + greenValue = ((uint32_t)(((float)(num & 0x07E0) / 0x07E0) * 0x00FF00) & 0x00FF00)>> 8; + blueValue = ((uint32_t)(((float)(num & 0x001F) / 0x001F) * 0x0000FF) & 0x0000FF); + + // now we have the right RGB values... + aOutBuffer[writeIndex++] = redValue; + aOutBuffer[writeIndex++] = greenValue; + aOutBuffer[writeIndex++] = blueValue; + numPixelsLeftInRow--; + pos += 2; + } + break; + case 32: + case 24: + if (pBitMapInfo->bmiHeader.biCompression == BI_BITFIELDS) + { + uint32_t val = *((uint32_t*) (aInputBuffer + index) ); + aOutBuffer[writeIndex++] = (val & colorMasks.red) >> colorMasks.redRightShift << colorMasks.redLeftShift; + aOutBuffer[writeIndex++] = (val & colorMasks.green) >> colorMasks.greenRightShift << colorMasks.greenLeftShift; + aOutBuffer[writeIndex++] = (val & colorMasks.blue) >> colorMasks.blueRightShift << colorMasks.blueLeftShift; + numPixelsLeftInRow--; + pos += 4; // we read in 4 bytes of data in order to process this pixel + } + else + { + aOutBuffer[writeIndex++] = aInputBuffer[index+2]; + aOutBuffer[writeIndex++] = aInputBuffer[index+1]; + aOutBuffer[writeIndex++] = aInputBuffer[index]; + numPixelsLeftInRow--; + pos += bytesPerPixel; // 3 bytes for 24 bit data, 4 bytes for 32 bit data (we skip over the 4th byte)... + } + break; + default: + // This is probably the wrong place to check this... + return NS_ERROR_FAILURE; + } + + index += bytesPerPixel; // increment our loop counter + + if (!numPixelsLeftInRow) + { + if (rowSize != pos) + { + // advance index to skip over remaining padding bytes + index += (rowSize - pos); + } + numPixelsLeftInRow = pBitMapInfo->bmiHeader.biWidth; + pos = 0; + } + + } // while we still have bytes to process + } + + return NS_OK; +} + +void nsImageFromClipboard::CalcBitmask(uint32_t aMask, uint8_t& aBegin, uint8_t& aLength) +{ + // find the rightmost 1 + uint8_t pos; + bool started = false; + aBegin = aLength = 0; + for (pos = 0; pos <= 31; pos++) + { + if (!started && (aMask & (1 << pos))) + { + aBegin = pos; + started = true; + } + else if (started && !(aMask & (1 << pos))) + { + aLength = pos - aBegin; + break; + } + } +} + +void nsImageFromClipboard::CalcBitShift(bitFields * aColorMask) +{ + uint8_t begin, length; + // red + CalcBitmask(aColorMask->red, begin, length); + aColorMask->redRightShift = begin; + aColorMask->redLeftShift = 8 - length; + // green + CalcBitmask(aColorMask->green, begin, length); + aColorMask->greenRightShift = begin; + aColorMask->greenLeftShift = 8 - length; + // blue + CalcBitmask(aColorMask->blue, begin, length); + aColorMask->blueRightShift = begin; + aColorMask->blueLeftShift = 8 - length; +} diff --git a/widget/windows/nsImageClipboard.h b/widget/windows/nsImageClipboard.h new file mode 100644 index 0000000000..25b33cc56c --- /dev/null +++ b/widget/windows/nsImageClipboard.h @@ -0,0 +1,93 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsImageClipboard_h +#define nsImageClipboard_h + +/* Things To Do 11/8/00 + +Check image metrics, can we support them? Do we need to? +Any other render format? HTML? + +*/ + +#include "nsError.h" +#include <windows.h> + +#include "nsCOMPtr.h" +#include "imgIContainer.h" +#include "nsIInputStream.h" + + +// +// nsImageToClipboard +// +// A utility class that takes an imgIContainer and does all the bitmap magic +// to allow us to put it on the clipboard +// +class nsImageToClipboard +{ +public: + nsImageToClipboard(imgIContainer* aInImage, bool aWantDIBV5 = true); + ~nsImageToClipboard(); + + // Call to get the actual bits that go on the clipboard. If |nullptr|, the + // setup operations have failed. + // + // NOTE: The caller owns the handle and must delete it with ::GlobalRelease() + nsresult GetPicture ( HANDLE* outBits ) ; + +private: + + // Computes # of bytes needed by a bitmap with the specified attributes. + int32_t CalcSize(int32_t aHeight, int32_t aColors, WORD aBitsPerPixel, int32_t aSpanBytes); + int32_t CalcSpanLength(uint32_t aWidth, uint32_t aBitCount); + + // Do the work + nsresult CreateFromImage ( imgIContainer* inImage, HANDLE* outBitmap ); + + nsCOMPtr<imgIContainer> mImage; // the image we're working with + bool mWantDIBV5; + +}; // class nsImageToClipboard + + +struct bitFields { + uint32_t red; + uint32_t green; + uint32_t blue; + uint8_t redLeftShift; + uint8_t redRightShift; + uint8_t greenLeftShift; + uint8_t greenRightShift; + uint8_t blueLeftShift; + uint8_t blueRightShift; +}; + +// +// nsImageFromClipboard +// +// A utility class that takes a DIB from the win32 clipboard and does +// all the bitmap magic to convert it to a PNG or a JPEG in the form of a nsIInputStream +// +class nsImageFromClipboard +{ +public: + nsImageFromClipboard () ; + ~nsImageFromClipboard ( ) ; + + // Retrieve the newly created image + nsresult GetEncodedImageStream (unsigned char * aClipboardData, const char * aMIMEFormat, nsIInputStream** outImage); + +private: + + void InvertRows(unsigned char * aInitialBuffer, uint32_t aSizeOfBuffer, uint32_t aNumBytesPerRow); + nsresult ConvertColorBitMap(unsigned char * aInputBuffer, PBITMAPINFO pBitMapInfo, unsigned char * aOutBuffer); + void CalcBitmask(uint32_t aMask, uint8_t& aBegin, uint8_t& aLength); + void CalcBitShift(bitFields * aColorMask); + +}; // nsImageFromClipboard + +#endif diff --git a/widget/windows/nsLookAndFeel.cpp b/widget/windows/nsLookAndFeel.cpp new file mode 100644 index 0000000000..7c427ac9fe --- /dev/null +++ b/widget/windows/nsLookAndFeel.cpp @@ -0,0 +1,701 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsLookAndFeel.h" +#include <windows.h> +#include <shellapi.h> +#include "nsStyleConsts.h" +#include "nsUXThemeData.h" +#include "nsUXThemeConstants.h" +#include "gfxFont.h" +#include "WinUtils.h" +#include "mozilla/Telemetry.h" +#include "mozilla/WindowsVersion.h" +#include "gfxFontConstants.h" + +using namespace mozilla; +using namespace mozilla::widget; + +//static +LookAndFeel::OperatingSystemVersion +nsLookAndFeel::GetOperatingSystemVersion() +{ + static OperatingSystemVersion version = eOperatingSystemVersion_Unknown; + + if (version != eOperatingSystemVersion_Unknown) { + return version; + } + + if (IsWin10OrLater()) { + version = eOperatingSystemVersion_Windows10; + } else if (IsWin8OrLater()) { + version = eOperatingSystemVersion_Windows8; + } else if (IsWin7OrLater()) { + version = eOperatingSystemVersion_Windows7; + } else if (IsVistaOrLater()) { + version = eOperatingSystemVersion_WindowsVista; + } else { + version = eOperatingSystemVersion_WindowsXP; + } + + return version; +} + +static nsresult GetColorFromTheme(nsUXThemeClass cls, + int32_t aPart, + int32_t aState, + int32_t aPropId, + nscolor &aColor) +{ + COLORREF color; + HRESULT hr = GetThemeColor(nsUXThemeData::GetTheme(cls), aPart, aState, aPropId, &color); + if (hr == S_OK) + { + aColor = COLOREF_2_NSRGB(color); + return NS_OK; + } + return NS_ERROR_FAILURE; +} + +static int32_t GetSystemParam(long flag, int32_t def) +{ + DWORD value; + return ::SystemParametersInfo(flag, 0, &value, 0) ? value : def; +} + +nsLookAndFeel::nsLookAndFeel() + : nsXPLookAndFeel() + , mUseAccessibilityTheme(0) +{ + mozilla::Telemetry::Accumulate(mozilla::Telemetry::TOUCH_ENABLED_DEVICE, + WinUtils::IsTouchDeviceSupportPresent()); +} + +nsLookAndFeel::~nsLookAndFeel() +{ +} + +nsresult +nsLookAndFeel::NativeGetColor(ColorID aID, nscolor &aColor) +{ + nsresult res = NS_OK; + + int idx; + switch (aID) { + case eColorID_WindowBackground: + idx = COLOR_WINDOW; + break; + case eColorID_WindowForeground: + idx = COLOR_WINDOWTEXT; + break; + case eColorID_WidgetBackground: + idx = COLOR_BTNFACE; + break; + case eColorID_WidgetForeground: + idx = COLOR_BTNTEXT; + break; + case eColorID_WidgetSelectBackground: + idx = COLOR_HIGHLIGHT; + break; + case eColorID_WidgetSelectForeground: + idx = COLOR_HIGHLIGHTTEXT; + break; + case eColorID_Widget3DHighlight: + idx = COLOR_BTNHIGHLIGHT; + break; + case eColorID_Widget3DShadow: + idx = COLOR_BTNSHADOW; + break; + case eColorID_TextBackground: + idx = COLOR_WINDOW; + break; + case eColorID_TextForeground: + idx = COLOR_WINDOWTEXT; + break; + case eColorID_TextSelectBackground: + case eColorID_IMESelectedRawTextBackground: + case eColorID_IMESelectedConvertedTextBackground: + idx = COLOR_HIGHLIGHT; + break; + case eColorID_TextSelectForeground: + case eColorID_IMESelectedRawTextForeground: + case eColorID_IMESelectedConvertedTextForeground: + idx = COLOR_HIGHLIGHTTEXT; + break; + case eColorID_IMERawInputBackground: + case eColorID_IMEConvertedTextBackground: + aColor = NS_TRANSPARENT; + return NS_OK; + case eColorID_IMERawInputForeground: + case eColorID_IMEConvertedTextForeground: + aColor = NS_SAME_AS_FOREGROUND_COLOR; + return NS_OK; + case eColorID_IMERawInputUnderline: + case eColorID_IMEConvertedTextUnderline: + aColor = NS_SAME_AS_FOREGROUND_COLOR; + return NS_OK; + case eColorID_IMESelectedRawTextUnderline: + case eColorID_IMESelectedConvertedTextUnderline: + aColor = NS_TRANSPARENT; + return NS_OK; + case eColorID_SpellCheckerUnderline: + aColor = NS_RGB(0xff, 0, 0); + return NS_OK; + + // New CSS 2 Color definitions + case eColorID_activeborder: + idx = COLOR_ACTIVEBORDER; + break; + case eColorID_activecaption: + idx = COLOR_ACTIVECAPTION; + break; + case eColorID_appworkspace: + idx = COLOR_APPWORKSPACE; + break; + case eColorID_background: + idx = COLOR_BACKGROUND; + break; + case eColorID_buttonface: + case eColorID__moz_buttonhoverface: + idx = COLOR_BTNFACE; + break; + case eColorID_buttonhighlight: + idx = COLOR_BTNHIGHLIGHT; + break; + case eColorID_buttonshadow: + idx = COLOR_BTNSHADOW; + break; + case eColorID_buttontext: + case eColorID__moz_buttonhovertext: + idx = COLOR_BTNTEXT; + break; + case eColorID_captiontext: + idx = COLOR_CAPTIONTEXT; + break; + case eColorID_graytext: + idx = COLOR_GRAYTEXT; + break; + case eColorID_highlight: + case eColorID__moz_html_cellhighlight: + case eColorID__moz_menuhover: + idx = COLOR_HIGHLIGHT; + break; + case eColorID__moz_menubarhovertext: + if (!IsVistaOrLater() || !IsAppThemed()) + { + idx = nsUXThemeData::sFlatMenus ? + COLOR_HIGHLIGHTTEXT : + COLOR_MENUTEXT; + break; + } + // Fall through + case eColorID__moz_menuhovertext: + if (IsVistaOrLater() && IsAppThemed()) + { + res = ::GetColorFromTheme(eUXMenu, + MENU_POPUPITEM, MPI_HOT, TMT_TEXTCOLOR, aColor); + if (NS_SUCCEEDED(res)) + return res; + // fall through to highlight case + } + case eColorID_highlighttext: + case eColorID__moz_html_cellhighlighttext: + idx = COLOR_HIGHLIGHTTEXT; + break; + case eColorID_inactiveborder: + idx = COLOR_INACTIVEBORDER; + break; + case eColorID_inactivecaption: + idx = COLOR_INACTIVECAPTION; + break; + case eColorID_inactivecaptiontext: + idx = COLOR_INACTIVECAPTIONTEXT; + break; + case eColorID_infobackground: + idx = COLOR_INFOBK; + break; + case eColorID_infotext: + idx = COLOR_INFOTEXT; + break; + case eColorID_menu: + idx = COLOR_MENU; + break; + case eColorID_menutext: + case eColorID__moz_menubartext: + idx = COLOR_MENUTEXT; + break; + case eColorID_scrollbar: + idx = COLOR_SCROLLBAR; + break; + case eColorID_threeddarkshadow: + idx = COLOR_3DDKSHADOW; + break; + case eColorID_threedface: + idx = COLOR_3DFACE; + break; + case eColorID_threedhighlight: + idx = COLOR_3DHIGHLIGHT; + break; + case eColorID_threedlightshadow: + idx = COLOR_3DLIGHT; + break; + case eColorID_threedshadow: + idx = COLOR_3DSHADOW; + break; + case eColorID_window: + idx = COLOR_WINDOW; + break; + case eColorID_windowframe: + idx = COLOR_WINDOWFRAME; + break; + case eColorID_windowtext: + idx = COLOR_WINDOWTEXT; + break; + case eColorID__moz_eventreerow: + case eColorID__moz_oddtreerow: + case eColorID__moz_field: + case eColorID__moz_combobox: + idx = COLOR_WINDOW; + break; + case eColorID__moz_fieldtext: + case eColorID__moz_comboboxtext: + idx = COLOR_WINDOWTEXT; + break; + case eColorID__moz_dialog: + case eColorID__moz_cellhighlight: + idx = COLOR_3DFACE; + break; + case eColorID__moz_win_mediatext: + if (IsVistaOrLater() && IsAppThemed()) { + res = ::GetColorFromTheme(eUXMediaToolbar, + TP_BUTTON, TS_NORMAL, TMT_TEXTCOLOR, aColor); + if (NS_SUCCEEDED(res)) + return res; + } + // if we've gotten here just return -moz-dialogtext instead + idx = COLOR_WINDOWTEXT; + break; + case eColorID__moz_win_communicationstext: + if (IsVistaOrLater() && IsAppThemed()) + { + res = ::GetColorFromTheme(eUXCommunicationsToolbar, + TP_BUTTON, TS_NORMAL, TMT_TEXTCOLOR, aColor); + if (NS_SUCCEEDED(res)) + return res; + } + // if we've gotten here just return -moz-dialogtext instead + idx = COLOR_WINDOWTEXT; + break; + case eColorID__moz_dialogtext: + case eColorID__moz_cellhighlighttext: + idx = COLOR_WINDOWTEXT; + break; + case eColorID__moz_dragtargetzone: + idx = COLOR_HIGHLIGHTTEXT; + break; + case eColorID__moz_buttondefault: + idx = COLOR_3DDKSHADOW; + break; + case eColorID__moz_nativehyperlinktext: + idx = COLOR_HOTLIGHT; + break; + default: + idx = COLOR_WINDOW; + break; + } + + DWORD color = ::GetSysColor(idx); + aColor = COLOREF_2_NSRGB(color); + + return res; +} + +nsresult +nsLookAndFeel::GetIntImpl(IntID aID, int32_t &aResult) +{ + nsresult res = nsXPLookAndFeel::GetIntImpl(aID, aResult); + if (NS_SUCCEEDED(res)) + return res; + res = NS_OK; + + switch (aID) { + case eIntID_CaretBlinkTime: + aResult = (int32_t)::GetCaretBlinkTime(); + break; + case eIntID_CaretWidth: + aResult = 1; + break; + case eIntID_ShowCaretDuringSelection: + aResult = 0; + break; + case eIntID_SelectTextfieldsOnKeyFocus: + // Select textfield content when focused by kbd + // used by EventStateManager::sTextfieldSelectModel + aResult = 1; + break; + case eIntID_SubmenuDelay: + // This will default to the Windows' default + // (400ms) on error. + aResult = GetSystemParam(SPI_GETMENUSHOWDELAY, 400); + break; + case eIntID_TooltipDelay: + aResult = 500; + break; + case eIntID_MenusCanOverlapOSBar: + // we want XUL popups to be able to overlap the task bar. + aResult = 1; + break; + case eIntID_DragThresholdX: + // The system metric is the number of pixels at which a drag should + // start. Our look and feel metric is the number of pixels you can + // move before starting a drag, so subtract 1. + + aResult = ::GetSystemMetrics(SM_CXDRAG) - 1; + break; + case eIntID_DragThresholdY: + aResult = ::GetSystemMetrics(SM_CYDRAG) - 1; + break; + case eIntID_UseAccessibilityTheme: + // High contrast is a misnomer under Win32 -- any theme can be used with it, + // e.g. normal contrast with large fonts, low contrast, etc. + // The high contrast flag really means -- use this theme and don't override it. + if (XRE_IsContentProcess()) { + // If we're running in the content process, then the parent should + // have sent us the accessibility state when nsLookAndFeel + // initialized, and stashed it in the mUseAccessibilityTheme cache. + aResult = mUseAccessibilityTheme; + } else { + // Otherwise, we can ask the OS to see if we're using High Contrast + // mode. + aResult = nsUXThemeData::IsHighContrastOn(); + } + break; + case eIntID_ScrollArrowStyle: + aResult = eScrollArrowStyle_Single; + break; + case eIntID_ScrollSliderStyle: + aResult = eScrollThumbStyle_Proportional; + break; + case eIntID_TreeOpenDelay: + aResult = 1000; + break; + case eIntID_TreeCloseDelay: + aResult = 0; + break; + case eIntID_TreeLazyScrollDelay: + aResult = 150; + break; + case eIntID_TreeScrollDelay: + aResult = 100; + break; + case eIntID_TreeScrollLinesMax: + aResult = 3; + break; + case eIntID_WindowsClassic: + aResult = !IsAppThemed(); + break; + case eIntID_TouchEnabled: + aResult = WinUtils::IsTouchDeviceSupportPresent(); + break; + case eIntID_WindowsDefaultTheme: + aResult = nsUXThemeData::IsDefaultWindowTheme(); + break; + case eIntID_WindowsThemeIdentifier: + aResult = nsUXThemeData::GetNativeThemeId(); + break; + + case eIntID_OperatingSystemVersionIdentifier: + { + aResult = GetOperatingSystemVersion(); + break; + } + + case eIntID_MacGraphiteTheme: + aResult = 0; + res = NS_ERROR_NOT_IMPLEMENTED; + break; + case eIntID_DWMCompositor: + aResult = nsUXThemeData::CheckForCompositor(); + break; + case eIntID_WindowsGlass: + // Aero Glass is only available prior to Windows 8 when DWM is used. + aResult = (nsUXThemeData::CheckForCompositor() && !IsWin8OrLater()); + break; + case eIntID_AlertNotificationOrigin: + aResult = 0; + { + // Get task bar window handle + HWND shellWindow = FindWindowW(L"Shell_TrayWnd", nullptr); + + if (shellWindow != nullptr) + { + // Determine position + APPBARDATA appBarData; + appBarData.hWnd = shellWindow; + appBarData.cbSize = sizeof(appBarData); + if (SHAppBarMessage(ABM_GETTASKBARPOS, &appBarData)) + { + // Set alert origin as a bit field - see LookAndFeel.h + // 0 represents bottom right, sliding vertically. + switch(appBarData.uEdge) + { + case ABE_LEFT: + aResult = NS_ALERT_HORIZONTAL | NS_ALERT_LEFT; + break; + case ABE_RIGHT: + aResult = NS_ALERT_HORIZONTAL; + break; + case ABE_TOP: + aResult = NS_ALERT_TOP; + // fall through for the right-to-left handling. + case ABE_BOTTOM: + // If the task bar is right-to-left, + // move the origin to the left + if (::GetWindowLong(shellWindow, GWL_EXSTYLE) & + WS_EX_LAYOUTRTL) + aResult |= NS_ALERT_LEFT; + break; + } + } + } + } + break; + case eIntID_IMERawInputUnderlineStyle: + case eIntID_IMEConvertedTextUnderlineStyle: + aResult = NS_STYLE_TEXT_DECORATION_STYLE_DASHED; + break; + case eIntID_IMESelectedRawTextUnderlineStyle: + case eIntID_IMESelectedConvertedTextUnderline: + aResult = NS_STYLE_TEXT_DECORATION_STYLE_NONE; + break; + case eIntID_SpellCheckerUnderlineStyle: + aResult = NS_STYLE_TEXT_DECORATION_STYLE_WAVY; + break; + case eIntID_ScrollbarButtonAutoRepeatBehavior: + aResult = 0; + break; + case eIntID_SwipeAnimationEnabled: + aResult = 0; + break; + case eIntID_ColorPickerAvailable: + aResult = true; + break; + case eIntID_UseOverlayScrollbars: + aResult = false; + break; + case eIntID_AllowOverlayScrollbarsOverlap: + aResult = 0; + break; + case eIntID_ScrollbarDisplayOnMouseMove: + aResult = 1; + break; + case eIntID_ScrollbarFadeBeginDelay: + aResult = 2500; + break; + case eIntID_ScrollbarFadeDuration: + aResult = 350; + break; + case eIntID_ContextMenuOffsetVertical: + case eIntID_ContextMenuOffsetHorizontal: + aResult = 2; + break; + default: + aResult = 0; + res = NS_ERROR_FAILURE; + } + return res; +} + +nsresult +nsLookAndFeel::GetFloatImpl(FloatID aID, float &aResult) +{ + nsresult res = nsXPLookAndFeel::GetFloatImpl(aID, aResult); + if (NS_SUCCEEDED(res)) + return res; + res = NS_OK; + + switch (aID) { + case eFloatID_IMEUnderlineRelativeSize: + aResult = 1.0f; + break; + case eFloatID_SpellCheckerUnderlineRelativeSize: + aResult = 1.0f; + break; + default: + aResult = -1.0; + res = NS_ERROR_FAILURE; + } + return res; +} + +static bool +GetSysFontInfo(HDC aHDC, LookAndFeel::FontID anID, + nsString &aFontName, + gfxFontStyle &aFontStyle) +{ + LOGFONTW* ptrLogFont = nullptr; + LOGFONTW logFont; + NONCLIENTMETRICSW ncm; + char16_t name[LF_FACESIZE]; + bool useShellDlg = false; + + // Depending on which stock font we want, there are a couple of + // places we might have to look it up. + switch (anID) { + case LookAndFeel::eFont_Icon: + if (!::SystemParametersInfoW(SPI_GETICONTITLELOGFONT, + sizeof(logFont), (PVOID)&logFont, 0)) + return false; + + ptrLogFont = &logFont; + break; + + default: + ncm.cbSize = sizeof(NONCLIENTMETRICSW); + if (!::SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, + sizeof(ncm), (PVOID)&ncm, 0)) + return false; + + switch (anID) { + case LookAndFeel::eFont_Menu: + case LookAndFeel::eFont_PullDownMenu: + ptrLogFont = &ncm.lfMenuFont; + break; + case LookAndFeel::eFont_Caption: + ptrLogFont = &ncm.lfCaptionFont; + break; + case LookAndFeel::eFont_SmallCaption: + ptrLogFont = &ncm.lfSmCaptionFont; + break; + case LookAndFeel::eFont_StatusBar: + case LookAndFeel::eFont_Tooltips: + ptrLogFont = &ncm.lfStatusFont; + break; + case LookAndFeel::eFont_Widget: + case LookAndFeel::eFont_Dialog: + case LookAndFeel::eFont_Button: + case LookAndFeel::eFont_Field: + case LookAndFeel::eFont_List: + // XXX It's not clear to me whether this is exactly the right + // set of LookAndFeel values to map to the dialog font; we may + // want to add or remove cases here after reviewing the visual + // results under various Windows versions. + useShellDlg = true; + // Fall through so that we can get size from lfMessageFont; + // but later we'll use the (virtual) "MS Shell Dlg 2" font name + // instead of the LOGFONT's. + default: + ptrLogFont = &ncm.lfMessageFont; + break; + } + break; + } + + // Get scaling factor from physical to logical pixels + double pixelScale = 1.0 / WinUtils::SystemScaleFactor(); + + // The lfHeight is in pixels, and it needs to be adjusted for the + // device it will be displayed on. + // Screens and Printers will differ in DPI + // + // So this accounts for the difference in the DeviceContexts + // The pixelScale will typically be 1.0 for the screen + // (though larger for hi-dpi screens where the Windows resolution + // scale factor is 125% or 150% or even more), and could be + // any value when going to a printer, for example pixelScale is + // 6.25 when going to a 600dpi printer. + float pixelHeight = -ptrLogFont->lfHeight; + if (pixelHeight < 0) { + HFONT hFont = ::CreateFontIndirectW(ptrLogFont); + if (!hFont) + return false; + HGDIOBJ hObject = ::SelectObject(aHDC, hFont); + TEXTMETRIC tm; + ::GetTextMetrics(aHDC, &tm); + ::SelectObject(aHDC, hObject); + ::DeleteObject(hFont); + pixelHeight = tm.tmAscent; + } + pixelHeight *= pixelScale; + + // we have problem on Simplified Chinese system because the system + // report the default font size is 8 points. but if we use 8, the text + // display very ugly. force it to be at 9 points (12 pixels) on that + // system (cp936), but leave other sizes alone. + if (pixelHeight < 12 && ::GetACP() == 936) + pixelHeight = 12; + + aFontStyle.size = pixelHeight; + + // FIXME: What about oblique? + aFontStyle.style = + (ptrLogFont->lfItalic) ? NS_FONT_STYLE_ITALIC : NS_FONT_STYLE_NORMAL; + + // FIXME: Other weights? + aFontStyle.weight = + (ptrLogFont->lfWeight == FW_BOLD ? + NS_FONT_WEIGHT_BOLD : NS_FONT_WEIGHT_NORMAL); + + // FIXME: Set aFontStyle->stretch correctly! + aFontStyle.stretch = NS_FONT_STRETCH_NORMAL; + + aFontStyle.systemFont = true; + + if (useShellDlg) { + aFontName = NS_LITERAL_STRING("MS Shell Dlg 2"); + } else { + memcpy(name, ptrLogFont->lfFaceName, LF_FACESIZE*sizeof(char16_t)); + aFontName = name; + } + + return true; +} + +bool +nsLookAndFeel::GetFontImpl(FontID anID, nsString &aFontName, + gfxFontStyle &aFontStyle, + float aDevPixPerCSSPixel) +{ + HDC tdc = GetDC(nullptr); + bool status = GetSysFontInfo(tdc, anID, aFontName, aFontStyle); + ReleaseDC(nullptr, tdc); + // now convert the logical font size from GetSysFontInfo into device pixels for layout + aFontStyle.size *= aDevPixPerCSSPixel; + return status; +} + +/* virtual */ +char16_t +nsLookAndFeel::GetPasswordCharacterImpl() +{ +#define UNICODE_BLACK_CIRCLE_CHAR 0x25cf + return UNICODE_BLACK_CIRCLE_CHAR; +} + +nsTArray<LookAndFeelInt> +nsLookAndFeel::GetIntCacheImpl() +{ + nsTArray<LookAndFeelInt> lookAndFeelIntCache = + nsXPLookAndFeel::GetIntCacheImpl(); + + LookAndFeelInt useAccessibilityTheme; + useAccessibilityTheme.id = eIntID_UseAccessibilityTheme; + useAccessibilityTheme.value = GetInt(eIntID_UseAccessibilityTheme); + lookAndFeelIntCache.AppendElement(useAccessibilityTheme); + + return lookAndFeelIntCache; +} + +void +nsLookAndFeel::SetIntCacheImpl(const nsTArray<LookAndFeelInt>& aLookAndFeelIntCache) +{ + for (auto entry : aLookAndFeelIntCache) { + if (entry.id == eIntID_UseAccessibilityTheme) { + mUseAccessibilityTheme = entry.value; + break; + } + } +} + diff --git a/widget/windows/nsLookAndFeel.h b/widget/windows/nsLookAndFeel.h new file mode 100644 index 0000000000..bc2d158b6f --- /dev/null +++ b/widget/windows/nsLookAndFeel.h @@ -0,0 +1,65 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __nsLookAndFeel +#define __nsLookAndFeel +#include "nsXPLookAndFeel.h" + +/* + * Gesture System Metrics + */ +#ifndef SM_DIGITIZER +#define SM_DIGITIZER 94 +#define TABLET_CONFIG_NONE 0x00000000 +#define NID_INTEGRATED_TOUCH 0x00000001 +#define NID_EXTERNAL_TOUCH 0x00000002 +#define NID_INTEGRATED_PEN 0x00000004 +#define NID_EXTERNAL_PEN 0x00000008 +#define NID_MULTI_INPUT 0x00000040 +#define NID_READY 0x00000080 +#endif + +/* + * Tablet mode detection + */ +#ifndef SM_SYSTEMDOCKED +#define SM_CONVERTIBLESLATEMODE 0x00002003 +#define SM_SYSTEMDOCKED 0x00002004 +typedef enum _AR_STATE +{ + AR_ENABLED = 0x0, + AR_DISABLED = 0x1, + AR_SUPPRESSED = 0x2, + AR_REMOTESESSION = 0x4, + AR_MULTIMON = 0x8, + AR_NOSENSOR = 0x10, + AR_NOT_SUPPORTED = 0x20, + AR_DOCKED = 0x40, + AR_LAPTOP = 0x80 +} AR_STATE, *PAR_STATE; +#endif + +class nsLookAndFeel: public nsXPLookAndFeel { + static OperatingSystemVersion GetOperatingSystemVersion(); +public: + nsLookAndFeel(); + virtual ~nsLookAndFeel(); + + virtual nsresult NativeGetColor(ColorID aID, nscolor &aResult); + virtual nsresult GetIntImpl(IntID aID, int32_t &aResult); + virtual nsresult GetFloatImpl(FloatID aID, float &aResult); + virtual bool GetFontImpl(FontID aID, nsString& aFontName, + gfxFontStyle& aFontStyle, + float aDevPixPerCSSPixel); + virtual char16_t GetPasswordCharacterImpl(); + + virtual nsTArray<LookAndFeelInt> GetIntCacheImpl(); + virtual void SetIntCacheImpl(const nsTArray<LookAndFeelInt>& aLookAndFeelIntCache); + +private: + int32_t mUseAccessibilityTheme; +}; + +#endif diff --git a/widget/windows/nsNativeDragSource.cpp b/widget/windows/nsNativeDragSource.cpp new file mode 100644 index 0000000000..8579070767 --- /dev/null +++ b/widget/windows/nsNativeDragSource.cpp @@ -0,0 +1,109 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsNativeDragSource.h" +#include <stdio.h> +#include "nsISupportsImpl.h" +#include "nsString.h" +#include "nsIServiceManager.h" +#include "nsToolkit.h" +#include "nsWidgetsCID.h" +#include "nsIDragService.h" + +/* + * class nsNativeDragSource + */ +nsNativeDragSource::nsNativeDragSource(nsIDOMDataTransfer* aDataTransfer) : + m_cRef(0), + m_hCursor(nullptr), + mUserCancelled(false) +{ + mDataTransfer = do_QueryInterface(aDataTransfer); +} + +nsNativeDragSource::~nsNativeDragSource() +{ +} + +STDMETHODIMP +nsNativeDragSource::QueryInterface(REFIID riid, void** ppv) +{ + *ppv=nullptr; + + if (IID_IUnknown==riid || IID_IDropSource==riid) + *ppv=this; + + if (nullptr!=*ppv) { + ((LPUNKNOWN)*ppv)->AddRef(); + return S_OK; + } + + return E_NOINTERFACE; +} + +STDMETHODIMP_(ULONG) +nsNativeDragSource::AddRef(void) +{ + ++m_cRef; + NS_LOG_ADDREF(this, m_cRef, "nsNativeDragSource", sizeof(*this)); + return m_cRef; +} + +STDMETHODIMP_(ULONG) +nsNativeDragSource::Release(void) +{ + --m_cRef; + NS_LOG_RELEASE(this, m_cRef, "nsNativeDragSource"); + if (0 != m_cRef) + return m_cRef; + + delete this; + return 0; +} + +STDMETHODIMP +nsNativeDragSource::QueryContinueDrag(BOOL fEsc, DWORD grfKeyState) +{ + static NS_DEFINE_IID(kCDragServiceCID, NS_DRAGSERVICE_CID); + + nsCOMPtr<nsIDragService> dragService = do_GetService(kCDragServiceCID); + if (dragService) { + DWORD pos = ::GetMessagePos(); + dragService->DragMoved(GET_X_LPARAM(pos), GET_Y_LPARAM(pos)); + } + + if (fEsc) { + mUserCancelled = true; + return DRAGDROP_S_CANCEL; + } + + if (!(grfKeyState & MK_LBUTTON) || (grfKeyState & MK_RBUTTON)) + return DRAGDROP_S_DROP; + + return S_OK; +} + +STDMETHODIMP +nsNativeDragSource::GiveFeedback(DWORD dwEffect) +{ + // For drags involving tabs, we do some custom work with cursors. + if (mDataTransfer) { + nsAutoString cursor; + mDataTransfer->GetMozCursor(cursor); + if (cursor.EqualsLiteral("default")) { + m_hCursor = ::LoadCursor(0, IDC_ARROW); + } else { + m_hCursor = nullptr; + } + } + + if (m_hCursor) { + ::SetCursor(m_hCursor); + return S_OK; + } + + // Let the system choose which cursor to apply. + return DRAGDROP_S_USEDEFAULTCURSORS; +} diff --git a/widget/windows/nsNativeDragSource.h b/widget/windows/nsNativeDragSource.h new file mode 100644 index 0000000000..19d1275993 --- /dev/null +++ b/widget/windows/nsNativeDragSource.h @@ -0,0 +1,66 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef _nsNativeDragSource_h_ +#define _nsNativeDragSource_h_ + +#include "nscore.h" +#include "nsIDOMDataTransfer.h" +#include "nsCOMPtr.h" +#include <ole2.h> +#include <oleidl.h> +#include "mozilla/Attributes.h" + +//class nsIDragSource; + +/* + * nsNativeDragSource implements the IDropSource interface and gets + * most of its behavior from the associated adapter (m_dragDrop). + */ +class nsNativeDragSource final : public IDropSource +{ +public: + + // construct an nsNativeDragSource referencing adapter + // nsNativeDragSource(nsIDragSource * adapter); + nsNativeDragSource(nsIDOMDataTransfer* aDataTransfer); + ~nsNativeDragSource(); + + // IUnknown methods - see iunknown.h for documentation + + STDMETHODIMP QueryInterface(REFIID, void**); + STDMETHODIMP_(ULONG) AddRef(); + STDMETHODIMP_(ULONG) Release(); + + // IDropSource methods - see idropsrc.h for documentation + + // Return DRAGDROP_S_USEDEFAULTCURSORS if this object lets OLE provide + // default cursors, otherwise return NOERROR. This method gets called in + // response to changes that the target makes to dEffect (DragEnter, + // DragOver). + STDMETHODIMP GiveFeedback(DWORD dEffect); + + // This method gets called if there is any change in the mouse or key + // state. Return DRAGDROP_S_CANCEL to stop the drag, DRAGDROP_S_DROP + // to execute the drop, otherwise NOERROR. + STDMETHODIMP QueryContinueDrag(BOOL fESC, DWORD grfKeyState); + + bool UserCancelled() { return mUserCancelled; } + +protected: + // Reference count + ULONG m_cRef; + + // Data object, hold information about cursor state + nsCOMPtr<nsIDOMDataTransfer> mDataTransfer; + + // Custom drag cursor + HCURSOR m_hCursor; + + // true if the user cancelled the drag by pressing escape + bool mUserCancelled; +}; + +#endif // _nsNativeDragSource_h_ + diff --git a/widget/windows/nsNativeDragTarget.cpp b/widget/windows/nsNativeDragTarget.cpp new file mode 100644 index 0000000000..1686642a30 --- /dev/null +++ b/widget/windows/nsNativeDragTarget.cpp @@ -0,0 +1,486 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <stdio.h> +#include "nsIDragService.h" +#include "nsWidgetsCID.h" +#include "nsNativeDragTarget.h" +#include "nsDragService.h" +#include "nsIServiceManager.h" +#include "nsIDOMNode.h" +#include "nsCOMPtr.h" + +#include "nsIWidget.h" +#include "nsWindow.h" +#include "nsClipboard.h" +#include "KeyboardLayout.h" + +#include "mozilla/MouseEvents.h" + +using namespace mozilla; +using namespace mozilla::widget; + +/* Define Interface IDs */ +static NS_DEFINE_IID(kIDragServiceIID, NS_IDRAGSERVICE_IID); + +// This is cached for Leave notification +static POINTL gDragLastPoint; + +/* + * class nsNativeDragTarget + */ +nsNativeDragTarget::nsNativeDragTarget(nsIWidget * aWidget) + : m_cRef(0), + mEffectsAllowed(DROPEFFECT_MOVE | DROPEFFECT_COPY | DROPEFFECT_LINK), + mEffectsPreferred(DROPEFFECT_NONE), + mTookOwnRef(false), mWidget(aWidget), mDropTargetHelper(nullptr) +{ + static NS_DEFINE_IID(kCDragServiceCID, NS_DRAGSERVICE_CID); + + mHWnd = (HWND)mWidget->GetNativeData(NS_NATIVE_WINDOW); + + /* + * Create/Get the DragService that we have implemented + */ + CallGetService(kCDragServiceCID, &mDragService); +} + +nsNativeDragTarget::~nsNativeDragTarget() +{ + NS_RELEASE(mDragService); + + if (mDropTargetHelper) { + mDropTargetHelper->Release(); + mDropTargetHelper = nullptr; + } +} + +// IUnknown methods - see iunknown.h for documentation +STDMETHODIMP +nsNativeDragTarget::QueryInterface(REFIID riid, void** ppv) +{ + *ppv=nullptr; + + if (IID_IUnknown == riid || IID_IDropTarget == riid) + *ppv=this; + + if (nullptr!=*ppv) { + ((LPUNKNOWN)*ppv)->AddRef(); + return S_OK; + } + + return E_NOINTERFACE; +} + +STDMETHODIMP_(ULONG) +nsNativeDragTarget::AddRef(void) +{ + ++m_cRef; + NS_LOG_ADDREF(this, m_cRef, "nsNativeDragTarget", sizeof(*this)); + return m_cRef; +} + +STDMETHODIMP_(ULONG) nsNativeDragTarget::Release(void) +{ + --m_cRef; + NS_LOG_RELEASE(this, m_cRef, "nsNativeDragTarget"); + if (0 != m_cRef) + return m_cRef; + + delete this; + return 0; +} + +void +nsNativeDragTarget::GetGeckoDragAction(DWORD grfKeyState, LPDWORD pdwEffect, + uint32_t * aGeckoAction) +{ + // If a window is disabled or a modal window is on top of it + // (which implies it is disabled), then we should not allow dropping. + if (!mWidget->IsEnabled()) { + *pdwEffect = DROPEFFECT_NONE; + *aGeckoAction = nsIDragService::DRAGDROP_ACTION_NONE; + return; + } + + // If the user explicitly uses a modifier key, they want the associated action + // Shift + Control -> LINK, Shift -> MOVE, Ctrl -> COPY + DWORD desiredEffect = DROPEFFECT_NONE; + if ((grfKeyState & MK_CONTROL) && (grfKeyState & MK_SHIFT)) { + desiredEffect = DROPEFFECT_LINK; + } else if (grfKeyState & MK_SHIFT) { + desiredEffect = DROPEFFECT_MOVE; + } else if (grfKeyState & MK_CONTROL) { + desiredEffect = DROPEFFECT_COPY; + } + + // Determine the desired effect from what is allowed and preferred. + if (!(desiredEffect &= mEffectsAllowed)) { + // No modifier key effect is set which is also allowed, check + // the preference of the data. + desiredEffect = mEffectsPreferred & mEffectsAllowed; + if (!desiredEffect) { + // No preference is set, so just fall back to the allowed effect itself + desiredEffect = mEffectsAllowed; + } + } + + // Otherwise we should specify the first available effect + // from MOVE, COPY, or LINK. + if (desiredEffect & DROPEFFECT_MOVE) { + *pdwEffect = DROPEFFECT_MOVE; + *aGeckoAction = nsIDragService::DRAGDROP_ACTION_MOVE; + } else if (desiredEffect & DROPEFFECT_COPY) { + *pdwEffect = DROPEFFECT_COPY; + *aGeckoAction = nsIDragService::DRAGDROP_ACTION_COPY; + } else if (desiredEffect & DROPEFFECT_LINK) { + *pdwEffect = DROPEFFECT_LINK; + *aGeckoAction = nsIDragService::DRAGDROP_ACTION_LINK; + } else { + *pdwEffect = DROPEFFECT_NONE; + *aGeckoAction = nsIDragService::DRAGDROP_ACTION_NONE; + } +} + +inline +bool +IsKeyDown(char key) +{ + return GetKeyState(key) < 0; +} + +void +nsNativeDragTarget::DispatchDragDropEvent(EventMessage aEventMessage, + const POINTL& aPT) +{ + nsEventStatus status; + WidgetDragEvent event(true, aEventMessage, mWidget); + + nsWindow * win = static_cast<nsWindow *>(mWidget); + win->InitEvent(event); + POINT cpos; + + cpos.x = aPT.x; + cpos.y = aPT.y; + + if (mHWnd != nullptr) { + ::ScreenToClient(mHWnd, &cpos); + event.mRefPoint = LayoutDeviceIntPoint(cpos.x, cpos.y); + } else { + event.mRefPoint = LayoutDeviceIntPoint(0, 0); + } + + ModifierKeyState modifierKeyState; + modifierKeyState.InitInputEvent(event); + + event.inputSource = static_cast<nsBaseDragService*>(mDragService)->GetInputSource(); + + mWidget->DispatchEvent(&event, status); +} + +void +nsNativeDragTarget::ProcessDrag(EventMessage aEventMessage, + DWORD grfKeyState, + POINTL ptl, + DWORD* pdwEffect) +{ + // Before dispatching the event make sure we have the correct drop action set + uint32_t geckoAction; + GetGeckoDragAction(grfKeyState, pdwEffect, &geckoAction); + + // Set the current action into the Gecko specific type + nsCOMPtr<nsIDragSession> currSession; + mDragService->GetCurrentSession(getter_AddRefs(currSession)); + if (!currSession) { + return; + } + + currSession->SetDragAction(geckoAction); + + // Dispatch the event into Gecko + DispatchDragDropEvent(aEventMessage, ptl); + + // If TakeChildProcessDragAction returns something other than + // DRAGDROP_ACTION_UNINITIALIZED, it means that the last event was sent + // to the child process and this event is also being sent to the child + // process. In this case, use the last event's action instead. + nsDragService* dragService = static_cast<nsDragService *>(mDragService); + currSession->GetDragAction(&geckoAction); + + int32_t childDragAction = dragService->TakeChildProcessDragAction(); + if (childDragAction != nsIDragService::DRAGDROP_ACTION_UNINITIALIZED) { + geckoAction = childDragAction; + } + + if (nsIDragService::DRAGDROP_ACTION_LINK & geckoAction) { + *pdwEffect = DROPEFFECT_LINK; + } + else if (nsIDragService::DRAGDROP_ACTION_COPY & geckoAction) { + *pdwEffect = DROPEFFECT_COPY; + } + else if (nsIDragService::DRAGDROP_ACTION_MOVE & geckoAction) { + *pdwEffect = DROPEFFECT_MOVE; + } + else { + *pdwEffect = DROPEFFECT_NONE; + } + + if (aEventMessage != eDrop) { + // Get the cached drag effect from the drag service, the data member should + // have been set by whoever handled the WidgetGUIEvent or nsIDOMEvent on + // drags. + bool canDrop; + currSession->GetCanDrop(&canDrop); + if (!canDrop) { + *pdwEffect = DROPEFFECT_NONE; + } + } + + // Clear the cached value + currSession->SetCanDrop(false); +} + +// IDropTarget methods +STDMETHODIMP +nsNativeDragTarget::DragEnter(LPDATAOBJECT pIDataSource, + DWORD grfKeyState, + POINTL ptl, + DWORD* pdwEffect) +{ + if (!mDragService) { + return E_FAIL; + } + + mEffectsAllowed = *pdwEffect; + AddLinkSupportIfCanBeGenerated(pIDataSource); + + // Drag and drop image helper + if (GetDropTargetHelper()) { + POINT pt = { ptl.x, ptl.y }; + GetDropTargetHelper()->DragEnter(mHWnd, pIDataSource, &pt, *pdwEffect); + } + + // save a ref to this, in case the window is destroyed underneath us + NS_ASSERTION(!mTookOwnRef, "own ref already taken!"); + this->AddRef(); + mTookOwnRef = true; + + // tell the drag service about this drag (it may have come from an + // outside app). + mDragService->StartDragSession(); + + void* tempOutData = nullptr; + uint32_t tempDataLen = 0; + nsresult loadResult = nsClipboard::GetNativeDataOffClipboard( + pIDataSource, 0, ::RegisterClipboardFormat(CFSTR_PREFERREDDROPEFFECT), nullptr, &tempOutData, &tempDataLen); + if (NS_SUCCEEDED(loadResult) && tempOutData) { + mEffectsPreferred = *((DWORD*)tempOutData); + free(tempOutData); + } else { + // We have no preference if we can't obtain it + mEffectsPreferred = DROPEFFECT_NONE; + } + + // Set the native data object into drag service + // + // This cast is ok because in the constructor we created a + // the actual implementation we wanted, so we know this is + // a nsDragService. It should be a private interface, though. + nsDragService * winDragService = + static_cast<nsDragService *>(mDragService); + winDragService->SetIDataObject(pIDataSource); + + // Now process the native drag state and then dispatch the event + ProcessDrag(eDragEnter, grfKeyState, ptl, pdwEffect); + + return S_OK; +} + +void +nsNativeDragTarget::AddLinkSupportIfCanBeGenerated(LPDATAOBJECT aIDataSource) +{ + // If we don't have a link effect, but we can generate one, fix the + // drop effect to include it. + if (!(mEffectsAllowed & DROPEFFECT_LINK) && aIDataSource) { + if (S_OK == ::OleQueryLinkFromData(aIDataSource)) { + mEffectsAllowed |= DROPEFFECT_LINK; + } + } +} + +STDMETHODIMP +nsNativeDragTarget::DragOver(DWORD grfKeyState, + POINTL ptl, + LPDWORD pdwEffect) +{ + if (!mDragService) { + return E_FAIL; + } + + // If a LINK effect could be generated previously from a DragEnter(), + // then we should include it as an allowed effect. + mEffectsAllowed = (*pdwEffect) | (mEffectsAllowed & DROPEFFECT_LINK); + + nsCOMPtr<nsIDragSession> currentDragSession; + mDragService->GetCurrentSession(getter_AddRefs(currentDragSession)); + if (!currentDragSession) { + return S_OK; // Drag was canceled. + } + + // without the AddRef() |this| can get destroyed in an event handler + this->AddRef(); + + // Drag and drop image helper + if (GetDropTargetHelper()) { + POINT pt = { ptl.x, ptl.y }; + GetDropTargetHelper()->DragOver(&pt, *pdwEffect); + } + + mDragService->FireDragEventAtSource(eDrag); + // Now process the native drag state and then dispatch the event + ProcessDrag(eDragOver, grfKeyState, ptl, pdwEffect); + + this->Release(); + + return S_OK; +} + +STDMETHODIMP +nsNativeDragTarget::DragLeave() +{ + if (!mDragService) { + return E_FAIL; + } + + // Drag and drop image helper + if (GetDropTargetHelper()) { + GetDropTargetHelper()->DragLeave(); + } + + // dispatch the event into Gecko + DispatchDragDropEvent(eDragExit, gDragLastPoint); + + nsCOMPtr<nsIDragSession> currentDragSession; + mDragService->GetCurrentSession(getter_AddRefs(currentDragSession)); + + if (currentDragSession) { + nsCOMPtr<nsIDOMNode> sourceNode; + currentDragSession->GetSourceNode(getter_AddRefs(sourceNode)); + + if (!sourceNode) { + // We're leaving a window while doing a drag that was + // initiated in a different app. End the drag session, since + // we're done with it for now (until the user drags back into + // mozilla). + mDragService->EndDragSession(false); + } + } + + // release the ref that was taken in DragEnter + NS_ASSERTION(mTookOwnRef, "want to release own ref, but not taken!"); + if (mTookOwnRef) { + this->Release(); + mTookOwnRef = false; + } + + return S_OK; +} + +void +nsNativeDragTarget::DragCancel() +{ + // Cancel the drag session if we did DragEnter. + if (mTookOwnRef) { + if (GetDropTargetHelper()) { + GetDropTargetHelper()->DragLeave(); + } + if (mDragService) { + mDragService->EndDragSession(false); + } + this->Release(); // matching the AddRef in DragEnter + mTookOwnRef = false; + } +} + +STDMETHODIMP +nsNativeDragTarget::Drop(LPDATAOBJECT pData, + DWORD grfKeyState, + POINTL aPT, + LPDWORD pdwEffect) +{ + if (!mDragService) { + return E_FAIL; + } + + mEffectsAllowed = *pdwEffect; + AddLinkSupportIfCanBeGenerated(pData); + + // Drag and drop image helper + if (GetDropTargetHelper()) { + POINT pt = { aPT.x, aPT.y }; + GetDropTargetHelper()->Drop(pData, &pt, *pdwEffect); + } + + // Set the native data object into the drag service + // + // This cast is ok because in the constructor we created a + // the actual implementation we wanted, so we know this is + // a nsDragService (but it should still be a private interface) + nsDragService* winDragService = static_cast<nsDragService*>(mDragService); + winDragService->SetIDataObject(pData); + + // NOTE: ProcessDrag spins the event loop which may destroy arbitrary objects. + // We use strong refs to prevent it from destroying these: + RefPtr<nsNativeDragTarget> kungFuDeathGrip = this; + nsCOMPtr<nsIDragService> serv = mDragService; + + // Now process the native drag state and then dispatch the event + ProcessDrag(eDrop, grfKeyState, aPT, pdwEffect); + + nsCOMPtr<nsIDragSession> currentDragSession; + serv->GetCurrentSession(getter_AddRefs(currentDragSession)); + if (!currentDragSession) { + return S_OK; // DragCancel() was called. + } + + // Let the win drag service know whether this session experienced + // a drop event within the application. Drop will not oocur if the + // drop landed outside the app. (used in tab tear off, bug 455884) + winDragService->SetDroppedLocal(); + + // tell the drag service we're done with the session + // Use GetMessagePos to get the position of the mouse at the last message + // seen by the event loop. (Bug 489729) + DWORD pos = ::GetMessagePos(); + POINT cpos; + cpos.x = GET_X_LPARAM(pos); + cpos.y = GET_Y_LPARAM(pos); + winDragService->SetDragEndPoint(nsIntPoint(cpos.x, cpos.y)); + serv->EndDragSession(true); + + // release the ref that was taken in DragEnter + NS_ASSERTION(mTookOwnRef, "want to release own ref, but not taken!"); + if (mTookOwnRef) { + this->Release(); + mTookOwnRef = false; + } + + return S_OK; +} + +/** + * By lazy loading mDropTargetHelper we save 50-70ms of startup time + * which is ~5% of startup time. +*/ +IDropTargetHelper* +nsNativeDragTarget::GetDropTargetHelper() +{ + if (!mDropTargetHelper) { + CoCreateInstance(CLSID_DragDropHelper, nullptr, CLSCTX_INPROC_SERVER, + IID_IDropTargetHelper, (LPVOID*)&mDropTargetHelper); + } + + return mDropTargetHelper; +} diff --git a/widget/windows/nsNativeDragTarget.h b/widget/windows/nsNativeDragTarget.h new file mode 100644 index 0000000000..fbc28fc929 --- /dev/null +++ b/widget/windows/nsNativeDragTarget.h @@ -0,0 +1,102 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef _nsNativeDragTarget_h_ +#define _nsNativeDragTarget_h_ + +#include "nsCOMPtr.h" +#include "nsIDragSession.h" +#include <ole2.h> +#include <shlobj.h> + +#ifndef IDropTargetHelper +#include <shobjidl.h> // Vista drag image interfaces +#undef LogSeverity // SetupAPI.h #defines this as DWORD +#endif + +#include "mozilla/Attributes.h" +#include "mozilla/EventForwards.h" + +class nsIDragService; +class nsIWidget; + +/* + * nsNativeDragTarget implements the IDropTarget interface and gets most of its + * behavior from the associated adapter (m_dragDrop). + */ + +class nsNativeDragTarget final : public IDropTarget +{ +public: + nsNativeDragTarget(nsIWidget * aWidget); + ~nsNativeDragTarget(); + + // IUnknown members - see iunknown.h for documentation + STDMETHODIMP QueryInterface(REFIID, void**); + STDMETHODIMP_(ULONG) AddRef(); + STDMETHODIMP_(ULONG) Release(); + + // IDataTarget members + + // Set pEffect based on whether this object can support a drop based on + // the data available from pSource, the key and mouse states specified + // in grfKeyState, and the coordinates specified by point. This is + // called by OLE when a drag enters this object's window (as registered + // by Initialize). + STDMETHODIMP DragEnter(LPDATAOBJECT pSource, DWORD grfKeyState, + POINTL point, DWORD* pEffect); + + // Similar to DragEnter except it is called frequently while the drag + // is over this object's window. + STDMETHODIMP DragOver(DWORD grfKeyState, POINTL point, DWORD* pEffect); + + // Release the drag-drop source and put internal state back to the point + // before the call to DragEnter. This is called when the drag leaves + // without a drop occurring. + STDMETHODIMP DragLeave(); + + // If point is within our region of interest and pSource's data supports + // one of our formats, get the data and set pEffect according to + // grfKeyState (DROPEFFECT_MOVE if the control key was not pressed, + // DROPEFFECT_COPY if the control key was pressed). Otherwise return + // E_FAIL. + STDMETHODIMP Drop(LPDATAOBJECT pSource, DWORD grfKeyState, + POINTL point, DWORD* pEffect); + /** + * Cancel the current drag session, if any. + */ + void DragCancel(); + +protected: + + void GetGeckoDragAction(DWORD grfKeyState, LPDWORD pdwEffect, + uint32_t * aGeckoAction); + void ProcessDrag(mozilla::EventMessage aEventMessage, DWORD grfKeyState, + POINTL pt, DWORD* pdwEffect); + void DispatchDragDropEvent(mozilla::EventMessage aEventMessage, + const POINTL& aPT); + void AddLinkSupportIfCanBeGenerated(LPDATAOBJECT aIDataSource); + + // Native Stuff + ULONG m_cRef; // reference count + HWND mHWnd; + DWORD mEffectsAllowed; + DWORD mEffectsPreferred; + bool mTookOwnRef; + + // Gecko Stuff + nsIWidget * mWidget; + nsIDragService * mDragService; + // Drag target helper + IDropTargetHelper * GetDropTargetHelper(); + + +private: + // Drag target helper + IDropTargetHelper * mDropTargetHelper; +}; + +#endif // _nsNativeDragTarget_h_ + + diff --git a/widget/windows/nsNativeThemeWin.cpp b/widget/windows/nsNativeThemeWin.cpp new file mode 100644 index 0000000000..4ff6b0af96 --- /dev/null +++ b/widget/windows/nsNativeThemeWin.cpp @@ -0,0 +1,4168 @@ +/* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsNativeThemeWin.h" + +#include "mozilla/EventStates.h" +#include "mozilla/Logging.h" +#include "mozilla/WindowsVersion.h" +#include "nsDeviceContext.h" +#include "nsRenderingContext.h" +#include "nsRect.h" +#include "nsSize.h" +#include "nsTransform2D.h" +#include "nsThemeConstants.h" +#include "nsIPresShell.h" +#include "nsPresContext.h" +#include "nsIContent.h" +#include "nsIFrame.h" +#include "nsNameSpaceManager.h" +#include "nsIDOMHTMLInputElement.h" +#include "nsLookAndFeel.h" +#include "nsMenuFrame.h" +#include "nsGkAtoms.h" +#include <malloc.h> +#include "nsWindow.h" +#include "nsIComboboxControlFrame.h" +#include "prinrval.h" +#include "WinUtils.h" + +#include "gfxPlatform.h" +#include "gfxContext.h" +#include "gfxWindowsPlatform.h" +#include "gfxWindowsSurface.h" +#include "gfxWindowsNativeDrawing.h" + +#include "nsUXThemeData.h" +#include "nsUXThemeConstants.h" +#include <algorithm> + +using mozilla::IsVistaOrLater; +using namespace mozilla; +using namespace mozilla::widget; + +extern mozilla::LazyLogModule gWindowsLog; + +NS_IMPL_ISUPPORTS_INHERITED(nsNativeThemeWin, nsNativeTheme, nsITheme) + +nsNativeThemeWin::nsNativeThemeWin() : + mProgressDeterminateTimeStamp(TimeStamp::Now()), + mProgressIndeterminateTimeStamp(TimeStamp::Now()) +{ + // If there is a relevant change in forms.css for windows platform, + // static widget style variables (e.g. sButtonBorderSize) should be + // reinitialized here. +} + +nsNativeThemeWin::~nsNativeThemeWin() +{ + nsUXThemeData::Invalidate(); +} + +static int32_t +GetTopLevelWindowActiveState(nsIFrame *aFrame) +{ + // Used by window frame and button box rendering. We can end up in here in + // the content process when rendering one of these moz styles freely in a + // page. Bail in this case, there is no applicable window focus state. + if (!XRE_IsParentProcess()) { + return mozilla::widget::themeconst::FS_INACTIVE; + } + // Get the widget. nsIFrame's GetNearestWidget walks up the view chain + // until it finds a real window. + nsIWidget* widget = aFrame->GetNearestWidget(); + nsWindowBase * window = static_cast<nsWindowBase*>(widget); + if (!window) + return mozilla::widget::themeconst::FS_INACTIVE; + if (widget && !window->IsTopLevelWidget() && + !(window = window->GetParentWindowBase(false))) + return mozilla::widget::themeconst::FS_INACTIVE; + + if (window->GetWindowHandle() == ::GetActiveWindow()) + return mozilla::widget::themeconst::FS_ACTIVE; + return mozilla::widget::themeconst::FS_INACTIVE; +} + +static int32_t +GetWindowFrameButtonState(nsIFrame* aFrame, EventStates eventState) +{ + if (GetTopLevelWindowActiveState(aFrame) == + mozilla::widget::themeconst::FS_INACTIVE) { + if (eventState.HasState(NS_EVENT_STATE_HOVER)) + return mozilla::widget::themeconst::BS_HOT; + return mozilla::widget::themeconst::BS_INACTIVE; + } + + if (eventState.HasState(NS_EVENT_STATE_HOVER)) { + if (eventState.HasState(NS_EVENT_STATE_ACTIVE)) + return mozilla::widget::themeconst::BS_PUSHED; + return mozilla::widget::themeconst::BS_HOT; + } + return mozilla::widget::themeconst::BS_NORMAL; +} + +static int32_t +GetClassicWindowFrameButtonState(EventStates eventState) +{ + if (eventState.HasState(NS_EVENT_STATE_ACTIVE) && + eventState.HasState(NS_EVENT_STATE_HOVER)) + return DFCS_BUTTONPUSH|DFCS_PUSHED; + return DFCS_BUTTONPUSH; +} + +static bool +IsTopLevelMenu(nsIFrame *aFrame) +{ + bool isTopLevel(false); + nsMenuFrame *menuFrame = do_QueryFrame(aFrame); + if (menuFrame) { + isTopLevel = menuFrame->IsOnMenuBar(); + } + return isTopLevel; +} + +static MARGINS +GetCheckboxMargins(HANDLE theme, HDC hdc) +{ + MARGINS checkboxContent = {0}; + GetThemeMargins(theme, hdc, MENU_POPUPCHECK, MCB_NORMAL, + TMT_CONTENTMARGINS, nullptr, &checkboxContent); + return checkboxContent; +} + +static SIZE +GetCheckboxBGSize(HANDLE theme, HDC hdc) +{ + SIZE checkboxSize; + GetThemePartSize(theme, hdc, MENU_POPUPCHECK, MC_CHECKMARKNORMAL, + nullptr, TS_TRUE, &checkboxSize); + + MARGINS checkboxMargins = GetCheckboxMargins(theme, hdc); + + int leftMargin = checkboxMargins.cxLeftWidth; + int rightMargin = checkboxMargins.cxRightWidth; + int topMargin = checkboxMargins.cyTopHeight; + int bottomMargin = checkboxMargins.cyBottomHeight; + + int width = leftMargin + checkboxSize.cx + rightMargin; + int height = topMargin + checkboxSize.cy + bottomMargin; + SIZE ret; + ret.cx = width; + ret.cy = height; + return ret; +} + +static SIZE +GetCheckboxBGBounds(HANDLE theme, HDC hdc) +{ + MARGINS checkboxBGSizing = {0}; + MARGINS checkboxBGContent = {0}; + GetThemeMargins(theme, hdc, MENU_POPUPCHECKBACKGROUND, MCB_NORMAL, + TMT_SIZINGMARGINS, nullptr, &checkboxBGSizing); + GetThemeMargins(theme, hdc, MENU_POPUPCHECKBACKGROUND, MCB_NORMAL, + TMT_CONTENTMARGINS, nullptr, &checkboxBGContent); + +#define posdx(d) ((d) > 0 ? d : 0) + + int dx = posdx(checkboxBGContent.cxRightWidth - + checkboxBGSizing.cxRightWidth) + + posdx(checkboxBGContent.cxLeftWidth - + checkboxBGSizing.cxLeftWidth); + int dy = posdx(checkboxBGContent.cyTopHeight - + checkboxBGSizing.cyTopHeight) + + posdx(checkboxBGContent.cyBottomHeight - + checkboxBGSizing.cyBottomHeight); + +#undef posdx + + SIZE ret(GetCheckboxBGSize(theme, hdc)); + ret.cx += dx; + ret.cy += dy; + return ret; +} + +static SIZE +GetGutterSize(HANDLE theme, HDC hdc) +{ + SIZE gutterSize; + GetThemePartSize(theme, hdc, MENU_POPUPGUTTER, 0, nullptr, TS_TRUE, &gutterSize); + + SIZE checkboxBGSize(GetCheckboxBGBounds(theme, hdc)); + + SIZE itemSize; + GetThemePartSize(theme, hdc, MENU_POPUPITEM, MPI_NORMAL, nullptr, TS_TRUE, &itemSize); + + // Figure out how big the menuitem's icon will be (if present) at current DPI + double scaleFactor = nsIWidget::DefaultScaleOverride(); + if (scaleFactor <= 0.0) { + scaleFactor = WinUtils::LogToPhysFactor(hdc); + } + int iconDevicePixels = NSToIntRound(16 * scaleFactor); + SIZE iconSize = { + iconDevicePixels, iconDevicePixels + }; + // Not really sure what margins should be used here, but this seems to work in practice... + MARGINS margins = {0}; + GetThemeMargins(theme, hdc, MENU_POPUPCHECKBACKGROUND, MCB_NORMAL, + TMT_CONTENTMARGINS, nullptr, &margins); + iconSize.cx += margins.cxLeftWidth + margins.cxRightWidth; + iconSize.cy += margins.cyTopHeight + margins.cyBottomHeight; + + int width = std::max(itemSize.cx, std::max(iconSize.cx, checkboxBGSize.cx) + gutterSize.cx); + int height = std::max(itemSize.cy, std::max(iconSize.cy, checkboxBGSize.cy)); + + SIZE ret; + ret.cx = width; + ret.cy = height; + return ret; +} + +/* DrawThemeBGRTLAware - render a theme part based on rtl state. + * Some widgets are not direction-neutral and need to be drawn reversed for + * RTL. Windows provides a way to do this with SetLayout, but this reverses + * the entire drawing area of a given device context, which means that its + * use will also affect the positioning of the widget. There are two ways + * to work around this: + * + * Option 1: Alter the position of the rect that we send so that we cancel + * out the positioning effects of SetLayout + * Option 2: Create a memory DC with the widgetRect's dimensions, draw onto + * that, and then transfer the results back to our DC + * + * This function tries to implement option 1, under the assumption that the + * correct way to reverse the effects of SetLayout is to translate the rect + * such that the offset from the DC bitmap's left edge to the old rect's + * left edge is equal to the offset from the DC bitmap's right edge to the + * new rect's right edge. In other words, + * (oldRect.left + vpOrg.x) == ((dcBMP.width - vpOrg.x) - newRect.right) + */ +static HRESULT +DrawThemeBGRTLAware(HANDLE aTheme, HDC aHdc, int aPart, int aState, + const RECT *aWidgetRect, const RECT *aClipRect, + bool aIsRtl) +{ + NS_ASSERTION(aTheme, "Bad theme handle."); + NS_ASSERTION(aHdc, "Bad hdc."); + NS_ASSERTION(aWidgetRect, "Bad rect."); + NS_ASSERTION(aClipRect, "Bad clip rect."); + + if (!aIsRtl) { + return DrawThemeBackground(aTheme, aHdc, aPart, aState, + aWidgetRect, aClipRect); + } + + HGDIOBJ hObj = GetCurrentObject(aHdc, OBJ_BITMAP); + BITMAP bitmap; + POINT vpOrg; + + if (hObj && GetObject(hObj, sizeof(bitmap), &bitmap) && + GetViewportOrgEx(aHdc, &vpOrg)) { + RECT newWRect(*aWidgetRect); + newWRect.left = bitmap.bmWidth - (aWidgetRect->right + 2*vpOrg.x); + newWRect.right = bitmap.bmWidth - (aWidgetRect->left + 2*vpOrg.x); + + RECT newCRect; + RECT *newCRectPtr = nullptr; + + if (aClipRect) { + newCRect.top = aClipRect->top; + newCRect.bottom = aClipRect->bottom; + newCRect.left = bitmap.bmWidth - (aClipRect->right + 2*vpOrg.x); + newCRect.right = bitmap.bmWidth - (aClipRect->left + 2*vpOrg.x); + newCRectPtr = &newCRect; + } + + SetLayout(aHdc, LAYOUT_RTL); + HRESULT hr = DrawThemeBackground(aTheme, aHdc, aPart, aState, &newWRect, + newCRectPtr); + SetLayout(aHdc, 0); + if (SUCCEEDED(hr)) { + return hr; + } + } + return DrawThemeBackground(aTheme, aHdc, aPart, aState, + aWidgetRect, aClipRect); +} + +/* + * Caption button padding data - 'hot' button padding. + * These areas are considered hot, in that they activate + * a button when hovered or clicked. The button graphic + * is drawn inside the padding border. Unrecognized themes + * are treated as their recognized counterparts for now. + * left top right bottom + * classic min 1 2 0 1 + * classic max 0 2 1 1 + * classic close 1 2 2 1 + * + * aero basic min 1 2 0 2 + * aero basic max 0 2 1 2 + * aero basic close 1 2 1 2 + * + * xp theme min 0 2 0 2 + * xp theme max 0 2 1 2 + * xp theme close 1 2 2 2 + * + * 'cold' button padding - generic button padding, should + * be handled in css. + * left top right bottom + * classic min 0 0 0 0 + * classic max 0 0 0 0 + * classic close 0 0 0 0 + * + * aero basic min 0 0 1 0 + * aero basic max 1 0 0 0 + * aero basic close 0 0 0 0 + * + * xp theme min 0 0 1 0 + * xp theme max 1 0 0 0 + * xp theme close 0 0 0 0 + */ + +enum CaptionDesktopTheme { + CAPTION_CLASSIC = 0, + CAPTION_BASIC, + CAPTION_XPTHEME, +}; + +enum CaptionButton { + CAPTIONBUTTON_MINIMIZE = 0, + CAPTIONBUTTON_RESTORE, + CAPTIONBUTTON_CLOSE, +}; + +struct CaptionButtonPadding { + RECT hotPadding[3]; +}; + +// RECT: left, top, right, bottom +static CaptionButtonPadding buttonData[3] = { + { + { { 1, 2, 0, 1 }, { 0, 2, 1, 1 }, { 1, 2, 2, 1 } } + }, + { + { { 1, 2, 0, 2 }, { 0, 2, 1, 2 }, { 1, 2, 2, 2 } } + }, + { + { { 0, 2, 0, 2 }, { 0, 2, 1, 2 }, { 1, 2, 2, 2 } } + } +}; + +// Adds "hot" caption button padding to minimum widget size. +static void +AddPaddingRect(LayoutDeviceIntSize* aSize, CaptionButton button) { + if (!aSize) + return; + RECT offset; + if (!IsAppThemed()) + offset = buttonData[CAPTION_CLASSIC].hotPadding[button]; + else if (!IsVistaOrLater()) + offset = buttonData[CAPTION_XPTHEME].hotPadding[button]; + else + offset = buttonData[CAPTION_BASIC].hotPadding[button]; + aSize->width += offset.left + offset.right; + aSize->height += offset.top + offset.bottom; +} + +// If we've added padding to the minimum widget size, offset +// the area we draw into to compensate. +static void +OffsetBackgroundRect(RECT& rect, CaptionButton button) { + RECT offset; + if (!IsAppThemed()) + offset = buttonData[CAPTION_CLASSIC].hotPadding[button]; + else if (!IsVistaOrLater()) + offset = buttonData[CAPTION_XPTHEME].hotPadding[button]; + else + offset = buttonData[CAPTION_BASIC].hotPadding[button]; + rect.left += offset.left; + rect.top += offset.top; + rect.right -= offset.right; + rect.bottom -= offset.bottom; +} + +/* + * Notes on progress track and meter part constants: + * xp and up: + * PP_BAR(_VERT) - base progress track + * PP_TRANSPARENTBAR(_VERT) - transparent progress track. this only works if + * the underlying surface supports alpha. otherwise + * theme lib's DrawThemeBackground falls back on + * opaque PP_BAR. we currently don't use this. + * PP_CHUNK(_VERT) - xp progress meter. this does not draw an xp style + * progress w/chunks, it draws fill using the chunk + * graphic. + * vista and up: + * PP_FILL(_VERT) - progress meter. these have four states/colors. + * PP_PULSEOVERLAY(_VERT) - white reflection - an overlay, not sure what this + * is used for. + * PP_MOVEOVERLAY(_VERT) - green pulse - the pulse effect overlay on + * determined progress bars. we also use this for + * indeterminate chunk. + * + * Notes on state constants: + * PBBS_NORMAL - green progress + * PBBVS_PARTIAL/PBFVS_ERROR - red error progress + * PBFS_PAUSED - yellow paused progress + * + * There is no common controls style indeterminate part on vista and up. + */ + +/* + * Progress bar related constants. These values are found by experimenting and + * comparing against native widgets used by the system. They are very unlikely + * exact but try to not be too wrong. + */ +// The amount of time we animate progress meters parts across the frame. +static const double kProgressDeterminateTimeSpan = 3.0; +static const double kProgressIndeterminateTimeSpan = 5.0; +// The width of the overlay used to animate the horizontal progress bar (Vista and later). +static const int32_t kProgressHorizontalVistaOverlaySize = 120; +// The width of the overlay used for the horizontal indeterminate progress bars on XP. +static const int32_t kProgressHorizontalXPOverlaySize = 55; +// The height of the overlay used to animate the vertical progress bar (Vista and later). +static const int32_t kProgressVerticalOverlaySize = 45; +// The height of the overlay used for the vertical indeterminate progress bar (Vista and later). +static const int32_t kProgressVerticalIndeterminateOverlaySize = 60; +// The width of the overlay used to animate the indeterminate progress bar (Windows Classic). +static const int32_t kProgressClassicOverlaySize = 40; + +/* + * GetProgressOverlayStyle - returns the proper overlay part for themed + * progress bars based on os and orientation. + */ +static int32_t +GetProgressOverlayStyle(bool aIsVertical) +{ + if (aIsVertical) { + if (IsVistaOrLater()) { + return PP_MOVEOVERLAYVERT; + } + return PP_CHUNKVERT; + } else { + if (IsVistaOrLater()) { + return PP_MOVEOVERLAY; + } + return PP_CHUNK; + } +} + +/* + * GetProgressOverlaySize - returns the minimum width or height for themed + * progress bar overlays. This includes the width of indeterminate chunks + * and vista pulse overlays. + */ +static int32_t +GetProgressOverlaySize(bool aIsVertical, bool aIsIndeterminate) +{ + if (IsVistaOrLater()) { + if (aIsVertical) { + return aIsIndeterminate ? kProgressVerticalIndeterminateOverlaySize + : kProgressVerticalOverlaySize; + } + return kProgressHorizontalVistaOverlaySize; + } + return kProgressHorizontalXPOverlaySize; +} + +/* + * IsProgressMeterFilled - Determines if a progress meter is at 100% fill based + * on a comparison of the current value and maximum. + */ +static bool +IsProgressMeterFilled(nsIFrame* aFrame) +{ + NS_ENSURE_TRUE(aFrame, false); + nsIFrame* parentFrame = aFrame->GetParent(); + NS_ENSURE_TRUE(parentFrame, false); + return nsNativeTheme::GetProgressValue(parentFrame) == + nsNativeTheme::GetProgressMaxValue(parentFrame); +} + +/* + * CalculateProgressOverlayRect - returns the padded overlay animation rect + * used in rendering progress bars. Resulting rects are used in rendering + * vista+ pulse overlays and indeterminate progress meters. Graphics should + * be rendered at the origin. + */ +RECT +nsNativeThemeWin::CalculateProgressOverlayRect(nsIFrame* aFrame, + RECT* aWidgetRect, + bool aIsVertical, + bool aIsIndeterminate, + bool aIsClassic) +{ + NS_ASSERTION(aFrame, "bad frame pointer"); + NS_ASSERTION(aWidgetRect, "bad rect pointer"); + + int32_t frameSize = aIsVertical ? aWidgetRect->bottom - aWidgetRect->top + : aWidgetRect->right - aWidgetRect->left; + + // Recycle a set of progress pulse timers - these timers control the position + // of all progress overlays and indeterminate chunks that get rendered. + double span = aIsIndeterminate ? kProgressIndeterminateTimeSpan + : kProgressDeterminateTimeSpan; + TimeDuration period; + if (!aIsIndeterminate) { + if (TimeStamp::Now() > (mProgressDeterminateTimeStamp + + TimeDuration::FromSeconds(span))) { + mProgressDeterminateTimeStamp = TimeStamp::Now(); + } + period = TimeStamp::Now() - mProgressDeterminateTimeStamp; + } else { + if (TimeStamp::Now() > (mProgressIndeterminateTimeStamp + + TimeDuration::FromSeconds(span))) { + mProgressIndeterminateTimeStamp = TimeStamp::Now(); + } + period = TimeStamp::Now() - mProgressIndeterminateTimeStamp; + } + + double percent = period / TimeDuration::FromSeconds(span); + + if (!aIsVertical && IsFrameRTL(aFrame)) + percent = 1 - percent; + + RECT overlayRect = *aWidgetRect; + int32_t overlaySize; + if (!aIsClassic) { + overlaySize = GetProgressOverlaySize(aIsVertical, aIsIndeterminate); + } else { + overlaySize = kProgressClassicOverlaySize; + } + + // Calculate a bounds that is larger than the meters frame such that the + // overlay starts and ends completely off the edge of the frame: + // [overlay][frame][overlay] + // This also yields a nice delay on rotation. Use overlaySize as the minimum + // size for [overlay] based on the graphics dims. If [frame] is larger, use + // the frame size instead. + int trackWidth = frameSize > overlaySize ? frameSize : overlaySize; + if (!aIsVertical) { + int xPos = aWidgetRect->left - trackWidth; + xPos += (int)ceil(((double)(trackWidth*2) * percent)); + overlayRect.left = xPos; + overlayRect.right = xPos + overlaySize; + } else { + int yPos = aWidgetRect->bottom + trackWidth; + yPos -= (int)ceil(((double)(trackWidth*2) * percent)); + overlayRect.bottom = yPos; + overlayRect.top = yPos - overlaySize; + } + return overlayRect; +} + +/* + * DrawChunkProgressMeter - renders an xp style chunked progress meter. Called + * by DrawProgressMeter. + * + * @param aTheme progress theme handle + * @param aHdc hdc returned by gfxWindowsNativeDrawing + * @param aPart the PP_X progress part + * @param aState the theme state + * @param aFrame the elements frame + * @param aWidgetRect bounding rect for the widget + * @param aClipRect dirty rect that needs drawing. + * @param aAppUnits app units per device pixel + * @param aIsIndeterm is an indeterminate progress? + * @param aIsVertical render a vertical progress? + * @param aIsRtl direction is rtl + */ +static void +DrawChunkProgressMeter(HTHEME aTheme, HDC aHdc, int aPart, + int aState, nsIFrame* aFrame, RECT* aWidgetRect, + RECT* aClipRect, gfxFloat aAppUnits, bool aIsIndeterm, + bool aIsVertical, bool aIsRtl) +{ + NS_ASSERTION(aTheme, "Bad theme."); + NS_ASSERTION(aHdc, "Bad hdc."); + NS_ASSERTION(aWidgetRect, "Bad rect."); + NS_ASSERTION(aClipRect, "Bad clip rect."); + NS_ASSERTION(aFrame, "Bad frame."); + + // For horizontal meters, the theme lib paints the right graphic but doesn't + // paint the chunks, so we do that manually. For vertical meters, the theme + // library draws everything correctly. + if (aIsVertical) { + DrawThemeBackground(aTheme, aHdc, aPart, aState, aWidgetRect, aClipRect); + return; + } + + // query for the proper chunk metrics + int chunkSize, spaceSize; + if (FAILED(GetThemeMetric(aTheme, aHdc, aPart, aState, + TMT_PROGRESSCHUNKSIZE, &chunkSize)) || + FAILED(GetThemeMetric(aTheme, aHdc, aPart, aState, + TMT_PROGRESSSPACESIZE, &spaceSize))) { + DrawThemeBackground(aTheme, aHdc, aPart, aState, aWidgetRect, aClipRect); + return; + } + + // render chunks + if (!aIsRtl || aIsIndeterm) { + for (int chunk = aWidgetRect->left; chunk <= aWidgetRect->right; + chunk += (chunkSize+spaceSize)) { + if (!aIsIndeterm && ((chunk + chunkSize) > aWidgetRect->right)) { + // aWidgetRect->right represents the end of the meter. Partial blocks + // don't get rendered with one exception, so exit here if we don't have + // a full chunk to draw. + // The above is true *except* when the meter is at 100% fill, in which + // case Windows renders any remaining partial block. Query the parent + // frame to find out if we're at 100%. + if (!IsProgressMeterFilled(aFrame)) { + break; + } + } + RECT bounds = + { chunk, aWidgetRect->top, chunk + chunkSize, aWidgetRect->bottom }; + DrawThemeBackground(aTheme, aHdc, aPart, aState, &bounds, aClipRect); + } + } else { + // rtl needs to grow in the opposite direction to look right. + for (int chunk = aWidgetRect->right; chunk >= aWidgetRect->left; + chunk -= (chunkSize+spaceSize)) { + if ((chunk - chunkSize) < aWidgetRect->left) { + if (!IsProgressMeterFilled(aFrame)) { + break; + } + } + RECT bounds = + { chunk - chunkSize, aWidgetRect->top, chunk, aWidgetRect->bottom }; + DrawThemeBackground(aTheme, aHdc, aPart, aState, &bounds, aClipRect); + } + } +} + +/* + * DrawProgressMeter - render an appropriate progress meter based on progress + * meter style, orientation, and os. Note, this does not render the underlying + * progress track. + * + * @param aFrame the widget frame + * @param aWidgetType type of widget + * @param aTheme progress theme handle + * @param aHdc hdc returned by gfxWindowsNativeDrawing + * @param aPart the PP_X progress part + * @param aState the theme state + * @param aWidgetRect bounding rect for the widget + * @param aClipRect dirty rect that needs drawing. + * @param aAppUnits app units per device pixel + */ +void +nsNativeThemeWin::DrawThemedProgressMeter(nsIFrame* aFrame, int aWidgetType, + HANDLE aTheme, HDC aHdc, + int aPart, int aState, + RECT* aWidgetRect, RECT* aClipRect, + gfxFloat aAppUnits) +{ + if (!aFrame || !aTheme || !aHdc) + return; + + NS_ASSERTION(aWidgetRect, "bad rect pointer"); + NS_ASSERTION(aClipRect, "bad clip rect pointer"); + + RECT adjWidgetRect, adjClipRect; + adjWidgetRect = *aWidgetRect; + adjClipRect = *aClipRect; + if (!IsVistaOrLater()) { + // Adjust clipping out by one pixel. XP progress meters are inset, + // Vista+ are not. + InflateRect(&adjWidgetRect, 1, 1); + InflateRect(&adjClipRect, 1, 1); + } + + nsIFrame* parentFrame = aFrame->GetParent(); + if (!parentFrame) { + // We have no parent to work with, just bail. + NS_WARNING("No parent frame for progress rendering. Can't paint."); + return; + } + + EventStates eventStates = GetContentState(parentFrame, aWidgetType); + bool vertical = IsVerticalProgress(parentFrame) || + aWidgetType == NS_THEME_PROGRESSCHUNK_VERTICAL; + bool indeterminate = IsIndeterminateProgress(parentFrame, eventStates); + bool animate = indeterminate; + + if (IsVistaOrLater()) { + // Vista and up progress meter is fill style, rendered here. We render + // the pulse overlay in the follow up section below. + DrawThemeBackground(aTheme, aHdc, aPart, aState, + &adjWidgetRect, &adjClipRect); + if (!IsProgressMeterFilled(aFrame)) { + animate = true; + } + } else if (!indeterminate) { + // XP progress meters are 'chunk' style. + DrawChunkProgressMeter(aTheme, aHdc, aPart, aState, aFrame, + &adjWidgetRect, &adjClipRect, aAppUnits, + indeterminate, vertical, IsFrameRTL(aFrame)); + } + + if (animate) { + // Indeterminate rendering + int32_t overlayPart = GetProgressOverlayStyle(vertical); + RECT overlayRect = + CalculateProgressOverlayRect(aFrame, &adjWidgetRect, vertical, + indeterminate, false); + if (IsVistaOrLater()) { + DrawThemeBackground(aTheme, aHdc, overlayPart, aState, &overlayRect, + &adjClipRect); + } else { + DrawChunkProgressMeter(aTheme, aHdc, overlayPart, aState, aFrame, + &overlayRect, &adjClipRect, aAppUnits, + indeterminate, vertical, IsFrameRTL(aFrame)); + } + + if (!QueueAnimatedContentForRefresh(aFrame->GetContent(), 60)) { + NS_WARNING("unable to animate progress widget!"); + } + } +} + +HANDLE +nsNativeThemeWin::GetTheme(uint8_t aWidgetType) +{ + if (!IsVistaOrLater()) { + // On XP or earlier, render dropdowns as textfields; + // doing it the right way works fine with the MS themes, + // but breaks on a lot of custom themes (presumably because MS + // apps do the textfield border business as well). + if (aWidgetType == NS_THEME_MENULIST) + aWidgetType = NS_THEME_TEXTFIELD; + } + + switch (aWidgetType) { + case NS_THEME_BUTTON: + case NS_THEME_RADIO: + case NS_THEME_CHECKBOX: + case NS_THEME_GROUPBOX: + return nsUXThemeData::GetTheme(eUXButton); + case NS_THEME_NUMBER_INPUT: + case NS_THEME_TEXTFIELD: + case NS_THEME_TEXTFIELD_MULTILINE: + case NS_THEME_FOCUS_OUTLINE: + return nsUXThemeData::GetTheme(eUXEdit); + case NS_THEME_TOOLTIP: + // XP/2K3 should force a classic treatment of tooltips + return !IsVistaOrLater() ? + nullptr : nsUXThemeData::GetTheme(eUXTooltip); + case NS_THEME_TOOLBOX: + return nsUXThemeData::GetTheme(eUXRebar); + case NS_THEME_WIN_MEDIA_TOOLBOX: + return nsUXThemeData::GetTheme(eUXMediaRebar); + case NS_THEME_WIN_COMMUNICATIONS_TOOLBOX: + return nsUXThemeData::GetTheme(eUXCommunicationsRebar); + case NS_THEME_WIN_BROWSERTABBAR_TOOLBOX: + return nsUXThemeData::GetTheme(eUXBrowserTabBarRebar); + case NS_THEME_TOOLBAR: + case NS_THEME_TOOLBARBUTTON: + case NS_THEME_SEPARATOR: + return nsUXThemeData::GetTheme(eUXToolbar); + case NS_THEME_PROGRESSBAR: + case NS_THEME_PROGRESSBAR_VERTICAL: + case NS_THEME_PROGRESSCHUNK: + case NS_THEME_PROGRESSCHUNK_VERTICAL: + return nsUXThemeData::GetTheme(eUXProgress); + case NS_THEME_TAB: + case NS_THEME_TABPANEL: + case NS_THEME_TABPANELS: + return nsUXThemeData::GetTheme(eUXTab); + case NS_THEME_SCROLLBAR: + case NS_THEME_SCROLLBAR_SMALL: + case NS_THEME_SCROLLBAR_VERTICAL: + case NS_THEME_SCROLLBAR_HORIZONTAL: + case NS_THEME_SCROLLBARBUTTON_UP: + case NS_THEME_SCROLLBARBUTTON_DOWN: + case NS_THEME_SCROLLBARBUTTON_LEFT: + case NS_THEME_SCROLLBARBUTTON_RIGHT: + case NS_THEME_SCROLLBARTHUMB_VERTICAL: + case NS_THEME_SCROLLBARTHUMB_HORIZONTAL: + return nsUXThemeData::GetTheme(eUXScrollbar); + case NS_THEME_RANGE: + case NS_THEME_RANGE_THUMB: + case NS_THEME_SCALE_HORIZONTAL: + case NS_THEME_SCALE_VERTICAL: + case NS_THEME_SCALETHUMB_HORIZONTAL: + case NS_THEME_SCALETHUMB_VERTICAL: + return nsUXThemeData::GetTheme(eUXTrackbar); + case NS_THEME_SPINNER_UPBUTTON: + case NS_THEME_SPINNER_DOWNBUTTON: + return nsUXThemeData::GetTheme(eUXSpin); + case NS_THEME_STATUSBAR: + case NS_THEME_STATUSBARPANEL: + case NS_THEME_RESIZERPANEL: + case NS_THEME_RESIZER: + return nsUXThemeData::GetTheme(eUXStatus); + case NS_THEME_MENULIST: + case NS_THEME_MENULIST_BUTTON: + return nsUXThemeData::GetTheme(eUXCombobox); + case NS_THEME_TREEHEADERCELL: + case NS_THEME_TREEHEADERSORTARROW: + return nsUXThemeData::GetTheme(eUXHeader); + case NS_THEME_LISTBOX: + case NS_THEME_LISTITEM: + case NS_THEME_TREEVIEW: + case NS_THEME_TREETWISTYOPEN: + case NS_THEME_TREEITEM: + return nsUXThemeData::GetTheme(eUXListview); + case NS_THEME_MENUBAR: + case NS_THEME_MENUPOPUP: + case NS_THEME_MENUITEM: + case NS_THEME_CHECKMENUITEM: + case NS_THEME_RADIOMENUITEM: + case NS_THEME_MENUCHECKBOX: + case NS_THEME_MENURADIO: + case NS_THEME_MENUSEPARATOR: + case NS_THEME_MENUARROW: + case NS_THEME_MENUIMAGE: + case NS_THEME_MENUITEMTEXT: + return nsUXThemeData::GetTheme(eUXMenu); + case NS_THEME_WINDOW_TITLEBAR: + case NS_THEME_WINDOW_TITLEBAR_MAXIMIZED: + case NS_THEME_WINDOW_FRAME_LEFT: + case NS_THEME_WINDOW_FRAME_RIGHT: + case NS_THEME_WINDOW_FRAME_BOTTOM: + case NS_THEME_WINDOW_BUTTON_CLOSE: + case NS_THEME_WINDOW_BUTTON_MINIMIZE: + case NS_THEME_WINDOW_BUTTON_MAXIMIZE: + case NS_THEME_WINDOW_BUTTON_RESTORE: + case NS_THEME_WINDOW_BUTTON_BOX: + case NS_THEME_WINDOW_BUTTON_BOX_MAXIMIZED: + case NS_THEME_WIN_GLASS: + case NS_THEME_WIN_BORDERLESS_GLASS: + return nsUXThemeData::GetTheme(eUXWindowFrame); + } + return nullptr; +} + +int32_t +nsNativeThemeWin::StandardGetState(nsIFrame* aFrame, uint8_t aWidgetType, + bool wantFocused) +{ + EventStates eventState = GetContentState(aFrame, aWidgetType); + if (eventState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE)) + return TS_ACTIVE; + if (eventState.HasState(NS_EVENT_STATE_HOVER)) + return TS_HOVER; + if (wantFocused && eventState.HasState(NS_EVENT_STATE_FOCUS)) + return TS_FOCUSED; + + return TS_NORMAL; +} + +bool +nsNativeThemeWin::IsMenuActive(nsIFrame *aFrame, uint8_t aWidgetType) +{ + nsIContent* content = aFrame->GetContent(); + if (content->IsXULElement() && + content->NodeInfo()->Equals(nsGkAtoms::richlistitem)) + return CheckBooleanAttr(aFrame, nsGkAtoms::selected); + + return CheckBooleanAttr(aFrame, nsGkAtoms::menuactive); +} + +/** + * aPart is filled in with the UXTheme part code. On return, values > 0 + * are the actual UXTheme part code; -1 means the widget will be drawn by + * us; 0 means that we should use part code 0, which isn't a real part code + * but elicits some kind of default behaviour from UXTheme when drawing + * (but isThemeBackgroundPartiallyTransparent may not work). + */ +nsresult +nsNativeThemeWin::GetThemePartAndState(nsIFrame* aFrame, uint8_t aWidgetType, + int32_t& aPart, int32_t& aState) +{ + if (!IsVistaOrLater()) { + // See GetTheme + if (aWidgetType == NS_THEME_MENULIST) + aWidgetType = NS_THEME_TEXTFIELD; + } + + switch (aWidgetType) { + case NS_THEME_BUTTON: { + aPart = BP_BUTTON; + if (!aFrame) { + aState = TS_NORMAL; + return NS_OK; + } + + EventStates eventState = GetContentState(aFrame, aWidgetType); + if (IsDisabled(aFrame, eventState)) { + aState = TS_DISABLED; + return NS_OK; + } else if (IsOpenButton(aFrame) || + IsCheckedButton(aFrame)) { + aState = TS_ACTIVE; + return NS_OK; + } + + aState = StandardGetState(aFrame, aWidgetType, true); + + // Check for default dialog buttons. These buttons should always look + // focused. + if (aState == TS_NORMAL && IsDefaultButton(aFrame)) + aState = TS_FOCUSED; + return NS_OK; + } + case NS_THEME_CHECKBOX: + case NS_THEME_RADIO: { + bool isCheckbox = (aWidgetType == NS_THEME_CHECKBOX); + aPart = isCheckbox ? BP_CHECKBOX : BP_RADIO; + + enum InputState { + UNCHECKED = 0, CHECKED, INDETERMINATE + }; + InputState inputState = UNCHECKED; + bool isXULCheckboxRadio = false; + + if (!aFrame) { + aState = TS_NORMAL; + } else { + if (GetCheckedOrSelected(aFrame, !isCheckbox)) { + inputState = CHECKED; + } if (isCheckbox && GetIndeterminate(aFrame)) { + inputState = INDETERMINATE; + } + + EventStates eventState = + GetContentState(isXULCheckboxRadio ? aFrame->GetParent() : aFrame, + aWidgetType); + if (IsDisabled(aFrame, eventState)) { + aState = TS_DISABLED; + } else { + aState = StandardGetState(aFrame, aWidgetType, false); + } + } + + // 4 unchecked states, 4 checked states, 4 indeterminate states. + aState += inputState * 4; + return NS_OK; + } + case NS_THEME_GROUPBOX: { + aPart = BP_GROUPBOX; + aState = TS_NORMAL; + // Since we don't support groupbox disabled and GBS_DISABLED looks the + // same as GBS_NORMAL don't bother supporting GBS_DISABLED. + return NS_OK; + } + case NS_THEME_NUMBER_INPUT: + case NS_THEME_TEXTFIELD: + case NS_THEME_TEXTFIELD_MULTILINE: { + EventStates eventState = GetContentState(aFrame, aWidgetType); + + if (IsVistaOrLater()) { + /* Note: the NOSCROLL type has a rounded corner in each + * corner. The more specific HSCROLL, VSCROLL, HVSCROLL types + * have side and/or top/bottom edges rendered as straight + * horizontal lines with sharp corners to accommodate a + * scrollbar. However, the scrollbar gets rendered on top of + * this for us, so we don't care, and can just use NOSCROLL + * here. + */ + aPart = TFP_EDITBORDER_NOSCROLL; + + if (!aFrame) { + aState = TFS_EDITBORDER_NORMAL; + } else if (IsDisabled(aFrame, eventState)) { + aState = TFS_EDITBORDER_DISABLED; + } else if (IsReadOnly(aFrame)) { + /* no special read-only state */ + aState = TFS_EDITBORDER_NORMAL; + } else { + nsIContent* content = aFrame->GetContent(); + + /* XUL textboxes don't get focused themselves, because they have child + * html:input.. but we can check the XUL focused attributes on them + */ + if (content && content->IsXULElement() && IsFocused(aFrame)) + aState = TFS_EDITBORDER_FOCUSED; + else if (eventState.HasAtLeastOneOfStates(NS_EVENT_STATE_ACTIVE | NS_EVENT_STATE_FOCUS)) + aState = TFS_EDITBORDER_FOCUSED; + else if (eventState.HasState(NS_EVENT_STATE_HOVER)) + aState = TFS_EDITBORDER_HOVER; + else + aState = TFS_EDITBORDER_NORMAL; + } + } else { + aPart = TFP_TEXTFIELD; + + if (!aFrame) + aState = TS_NORMAL; + else if (IsDisabled(aFrame, eventState)) + aState = TS_DISABLED; + else if (IsReadOnly(aFrame)) + aState = TFS_READONLY; + else + aState = StandardGetState(aFrame, aWidgetType, true); + } + + return NS_OK; + } + case NS_THEME_FOCUS_OUTLINE: { + if (IsVistaOrLater()) { + // XXX the EDITBORDER values don't respect DTBG_OMITCONTENT + aPart = TFP_TEXTFIELD; //TFP_EDITBORDER_NOSCROLL; + aState = TS_FOCUSED; //TFS_EDITBORDER_FOCUSED; + } else { + aPart = TFP_TEXTFIELD; + aState = TS_FOCUSED; + } + return NS_OK; + } + case NS_THEME_TOOLTIP: { + aPart = TTP_STANDARD; + aState = TS_NORMAL; + return NS_OK; + } + case NS_THEME_PROGRESSBAR: + case NS_THEME_PROGRESSBAR_VERTICAL: { + // Note IsVerticalProgress only tests for orient css attrribute, + // NS_THEME_PROGRESSBAR_VERTICAL is dedicated to -moz-appearance: + // progressbar-vertical. + bool vertical = IsVerticalProgress(aFrame) || + aWidgetType == NS_THEME_PROGRESSBAR_VERTICAL; + aPart = vertical ? PP_BARVERT : PP_BAR; + aState = PBBS_NORMAL; + return NS_OK; + } + case NS_THEME_PROGRESSCHUNK: + case NS_THEME_PROGRESSCHUNK_VERTICAL: { + nsIFrame* parentFrame = aFrame->GetParent(); + if (aWidgetType == NS_THEME_PROGRESSCHUNK_VERTICAL || + IsVerticalProgress(parentFrame)) { + aPart = IsVistaOrLater() ? + PP_FILLVERT : PP_CHUNKVERT; + } else { + aPart = IsVistaOrLater() ? + PP_FILL : PP_CHUNK; + } + + aState = PBBVS_NORMAL; + return NS_OK; + } + case NS_THEME_TOOLBARBUTTON: { + aPart = BP_BUTTON; + if (!aFrame) { + aState = TS_NORMAL; + return NS_OK; + } + + EventStates eventState = GetContentState(aFrame, aWidgetType); + if (IsDisabled(aFrame, eventState)) { + aState = TS_DISABLED; + return NS_OK; + } + if (IsOpenButton(aFrame)) { + aState = TS_ACTIVE; + return NS_OK; + } + + if (eventState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE)) + aState = TS_ACTIVE; + else if (eventState.HasState(NS_EVENT_STATE_HOVER)) { + if (IsCheckedButton(aFrame)) + aState = TB_HOVER_CHECKED; + else + aState = TS_HOVER; + } + else { + if (IsCheckedButton(aFrame)) + aState = TB_CHECKED; + else + aState = TS_NORMAL; + } + + return NS_OK; + } + case NS_THEME_SEPARATOR: { + aPart = TP_SEPARATOR; + aState = TS_NORMAL; + return NS_OK; + } + case NS_THEME_SCROLLBARBUTTON_UP: + case NS_THEME_SCROLLBARBUTTON_DOWN: + case NS_THEME_SCROLLBARBUTTON_LEFT: + case NS_THEME_SCROLLBARBUTTON_RIGHT: { + aPart = SP_BUTTON; + aState = (aWidgetType - NS_THEME_SCROLLBARBUTTON_UP)*4; + EventStates eventState = GetContentState(aFrame, aWidgetType); + if (!aFrame) + aState += TS_NORMAL; + else if (IsDisabled(aFrame, eventState)) + aState += TS_DISABLED; + else { + nsIFrame *parent = aFrame->GetParent(); + EventStates parentState = + GetContentState(parent, parent->StyleDisplay()->mAppearance); + if (eventState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE)) + aState += TS_ACTIVE; + else if (eventState.HasState(NS_EVENT_STATE_HOVER)) + aState += TS_HOVER; + else if (IsVistaOrLater() && + parentState.HasState(NS_EVENT_STATE_HOVER)) + aState = (aWidgetType - NS_THEME_SCROLLBARBUTTON_UP) + SP_BUTTON_IMPLICIT_HOVER_BASE; + else + aState += TS_NORMAL; + } + return NS_OK; + } + case NS_THEME_SCROLLBAR_HORIZONTAL: + case NS_THEME_SCROLLBAR_VERTICAL: { + aPart = (aWidgetType == NS_THEME_SCROLLBAR_HORIZONTAL) ? + SP_TRACKSTARTHOR : SP_TRACKSTARTVERT; + aState = TS_NORMAL; + return NS_OK; + } + case NS_THEME_SCROLLBARTHUMB_HORIZONTAL: + case NS_THEME_SCROLLBARTHUMB_VERTICAL: { + aPart = (aWidgetType == NS_THEME_SCROLLBARTHUMB_HORIZONTAL) ? + SP_THUMBHOR : SP_THUMBVERT; + EventStates eventState = GetContentState(aFrame, aWidgetType); + if (!aFrame) + aState = TS_NORMAL; + else if (IsDisabled(aFrame, eventState)) + aState = TS_DISABLED; + else { + if (eventState.HasState(NS_EVENT_STATE_ACTIVE)) // Hover is not also a requirement for + // the thumb, since the drag is not canceled + // when you move outside the thumb. + aState = TS_ACTIVE; + else if (eventState.HasState(NS_EVENT_STATE_HOVER)) + aState = TS_HOVER; + else + aState = TS_NORMAL; + } + return NS_OK; + } + case NS_THEME_RANGE: + case NS_THEME_SCALE_HORIZONTAL: + case NS_THEME_SCALE_VERTICAL: { + if (aWidgetType == NS_THEME_SCALE_HORIZONTAL || + (aWidgetType == NS_THEME_RANGE && + IsRangeHorizontal(aFrame))) { + aPart = TKP_TRACK; + aState = TRS_NORMAL; + } else { + aPart = TKP_TRACKVERT; + aState = TRVS_NORMAL; + } + return NS_OK; + } + case NS_THEME_RANGE_THUMB: + case NS_THEME_SCALETHUMB_HORIZONTAL: + case NS_THEME_SCALETHUMB_VERTICAL: { + if (aWidgetType == NS_THEME_RANGE_THUMB) { + if (IsRangeHorizontal(aFrame)) { + aPart = TKP_THUMBBOTTOM; + } else { + aPart = IsFrameRTL(aFrame) ? TKP_THUMBLEFT : TKP_THUMBRIGHT; + } + } else { + aPart = (aWidgetType == NS_THEME_SCALETHUMB_HORIZONTAL) ? + TKP_THUMB : TKP_THUMBVERT; + } + EventStates eventState = GetContentState(aFrame, aWidgetType); + if (!aFrame) + aState = TS_NORMAL; + else if (IsDisabled(aFrame, eventState)) { + aState = TKP_DISABLED; + } + else { + if (eventState.HasState(NS_EVENT_STATE_ACTIVE)) // Hover is not also a requirement for + // the thumb, since the drag is not canceled + // when you move outside the thumb. + aState = TS_ACTIVE; + else if (eventState.HasState(NS_EVENT_STATE_FOCUS)) + aState = TKP_FOCUSED; + else if (eventState.HasState(NS_EVENT_STATE_HOVER)) + aState = TS_HOVER; + else + aState = TS_NORMAL; + } + return NS_OK; + } + case NS_THEME_SPINNER_UPBUTTON: + case NS_THEME_SPINNER_DOWNBUTTON: { + aPart = (aWidgetType == NS_THEME_SPINNER_UPBUTTON) ? + SPNP_UP : SPNP_DOWN; + EventStates eventState = GetContentState(aFrame, aWidgetType); + if (!aFrame) + aState = TS_NORMAL; + else if (IsDisabled(aFrame, eventState)) + aState = TS_DISABLED; + else + aState = StandardGetState(aFrame, aWidgetType, false); + return NS_OK; + } + case NS_THEME_TOOLBOX: + case NS_THEME_WIN_MEDIA_TOOLBOX: + case NS_THEME_WIN_COMMUNICATIONS_TOOLBOX: + case NS_THEME_WIN_BROWSERTABBAR_TOOLBOX: + case NS_THEME_STATUSBAR: + case NS_THEME_SCROLLBAR: + case NS_THEME_SCROLLBAR_SMALL: { + aState = 0; + if (IsVistaOrLater()) { + // On vista, they have a part + aPart = RP_BACKGROUND; + } else { + // Otherwise, they don't. (But I bet + // RP_BACKGROUND would work here, too); + aPart = 0; + } + return NS_OK; + } + case NS_THEME_TOOLBAR: { + // Use -1 to indicate we don't wish to have the theme background drawn + // for this item. We will pass any nessessary information via aState, + // and will render the item using separate code. + aPart = -1; + aState = 0; + if (aFrame) { + nsIContent* content = aFrame->GetContent(); + nsIContent* parent = content->GetParent(); + // XXXzeniko hiding the first toolbar will result in an unwanted margin + if (parent && parent->GetFirstChild() == content) { + aState = 1; + } + } + return NS_OK; + } + case NS_THEME_STATUSBARPANEL: + case NS_THEME_RESIZERPANEL: + case NS_THEME_RESIZER: { + aPart = (aWidgetType - NS_THEME_STATUSBARPANEL) + 1; + aState = TS_NORMAL; + return NS_OK; + } + case NS_THEME_TREEVIEW: + case NS_THEME_LISTBOX: { + aPart = TREEVIEW_BODY; + aState = TS_NORMAL; + return NS_OK; + } + case NS_THEME_TABPANELS: { + aPart = TABP_PANELS; + aState = TS_NORMAL; + return NS_OK; + } + case NS_THEME_TABPANEL: { + aPart = TABP_PANEL; + aState = TS_NORMAL; + return NS_OK; + } + case NS_THEME_TAB: { + aPart = TABP_TAB; + if (!aFrame) { + aState = TS_NORMAL; + return NS_OK; + } + + EventStates eventState = GetContentState(aFrame, aWidgetType); + if (IsDisabled(aFrame, eventState)) { + aState = TS_DISABLED; + return NS_OK; + } + + if (IsSelectedTab(aFrame)) { + aPart = TABP_TAB_SELECTED; + aState = TS_ACTIVE; // The selected tab is always "pressed". + } + else + aState = StandardGetState(aFrame, aWidgetType, true); + + return NS_OK; + } + case NS_THEME_TREEHEADERSORTARROW: { + // XXX Probably will never work due to a bug in the Luna theme. + aPart = 4; + aState = 1; + return NS_OK; + } + case NS_THEME_TREEHEADERCELL: { + aPart = 1; + if (!aFrame) { + aState = TS_NORMAL; + return NS_OK; + } + + aState = StandardGetState(aFrame, aWidgetType, true); + + return NS_OK; + } + case NS_THEME_MENULIST: { + nsIContent* content = aFrame->GetContent(); + bool isHTML = content && content->IsHTMLElement(); + bool useDropBorder = isHTML || IsMenuListEditable(aFrame); + EventStates eventState = GetContentState(aFrame, aWidgetType); + + /* On Vista/Win7, we use CBP_DROPBORDER instead of DROPFRAME for HTML + * content or for editable menulists; this gives us the thin outline, + * instead of the gradient-filled background */ + if (useDropBorder) + aPart = CBP_DROPBORDER; + else + aPart = CBP_DROPFRAME; + + if (IsDisabled(aFrame, eventState)) { + aState = TS_DISABLED; + } else if (IsReadOnly(aFrame)) { + aState = TS_NORMAL; + } else if (IsOpenButton(aFrame)) { + aState = TS_ACTIVE; + } else { + if (useDropBorder && (eventState.HasState(NS_EVENT_STATE_FOCUS) || IsFocused(aFrame))) + aState = TS_ACTIVE; + else if (eventState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE)) + aState = TS_ACTIVE; + else if (eventState.HasState(NS_EVENT_STATE_HOVER)) + aState = TS_HOVER; + else + aState = TS_NORMAL; + } + + return NS_OK; + } + case NS_THEME_MENULIST_BUTTON: { + bool isHTML = IsHTMLContent(aFrame); + nsIFrame* parentFrame = aFrame->GetParent(); + bool isMenulist = !isHTML && parentFrame->GetType() == nsGkAtoms::menuFrame; + bool isOpen = false; + + // HTML select and XUL menulist dropdown buttons get state from the parent. + if (isHTML || isMenulist) + aFrame = parentFrame; + + EventStates eventState = GetContentState(aFrame, aWidgetType); + aPart = IsVistaOrLater() ? + CBP_DROPMARKER_VISTA : CBP_DROPMARKER; + + // For HTML controls with author styling, we should fall + // back to the old dropmarker style to avoid clashes with + // author-specified backgrounds and borders (bug #441034) + if (isHTML && IsWidgetStyled(aFrame->PresContext(), aFrame, NS_THEME_MENULIST)) + aPart = CBP_DROPMARKER; + + if (IsDisabled(aFrame, eventState)) { + aState = TS_DISABLED; + return NS_OK; + } + + if (isHTML) { + nsIComboboxControlFrame* ccf = do_QueryFrame(aFrame); + isOpen = (ccf && ccf->IsDroppedDownOrHasParentPopup()); + } + else + isOpen = IsOpenButton(aFrame); + + if (IsVistaOrLater()) { + if (isHTML || IsMenuListEditable(aFrame)) { + if (isOpen) { + /* Hover is propagated, but we need to know whether we're + * hovering just the combobox frame, not the dropdown frame. + * But, we can't get that information, since hover is on the + * content node, and they share the same content node. So, + * instead, we cheat -- if the dropdown is open, we always + * show the hover state. This looks fine in practice. + */ + aState = TS_HOVER; + return NS_OK; + } + } else { + /* On Vista, the dropdown indicator on a menulist button in + * chrome is not given a hover effect. When the frame isn't + * isn't HTML content, we cheat and force the dropdown state + * to be normal. (Bug 430434) + */ + aState = TS_NORMAL; + return NS_OK; + } + } + + aState = TS_NORMAL; + + // Dropdown button active state doesn't need :hover. + if (eventState.HasState(NS_EVENT_STATE_ACTIVE)) { + if (isOpen && (isHTML || isMenulist)) { + // XXX Button should look active until the mouse is released, but + // without making it look active when the popup is clicked. + return NS_OK; + } + aState = TS_ACTIVE; + } + else if (eventState.HasState(NS_EVENT_STATE_HOVER)) { + // No hover effect for XUL menulists and autocomplete dropdown buttons + // while the dropdown menu is open. + if (isOpen) { + // XXX HTML select dropdown buttons should have the hover effect when + // hovering the combobox frame, but not the popup frame. + return NS_OK; + } + aState = TS_HOVER; + } + return NS_OK; + } + case NS_THEME_MENUPOPUP: { + aPart = MENU_POPUPBACKGROUND; + aState = MB_ACTIVE; + return NS_OK; + } + case NS_THEME_MENUITEM: + case NS_THEME_CHECKMENUITEM: + case NS_THEME_RADIOMENUITEM: { + bool isTopLevel = false; + bool isOpen = false; + bool isHover = false; + nsMenuFrame *menuFrame = do_QueryFrame(aFrame); + EventStates eventState = GetContentState(aFrame, aWidgetType); + + isTopLevel = IsTopLevelMenu(aFrame); + + if (menuFrame) + isOpen = menuFrame->IsOpen(); + + isHover = IsMenuActive(aFrame, aWidgetType); + + if (isTopLevel) { + aPart = MENU_BARITEM; + + if (isOpen) + aState = MBI_PUSHED; + else if (isHover) + aState = MBI_HOT; + else + aState = MBI_NORMAL; + + // the disabled states are offset by 3 + if (IsDisabled(aFrame, eventState)) + aState += 3; + } else { + aPart = MENU_POPUPITEM; + + if (isHover) + aState = MPI_HOT; + else + aState = MPI_NORMAL; + + // the disabled states are offset by 2 + if (IsDisabled(aFrame, eventState)) + aState += 2; + } + + return NS_OK; + } + case NS_THEME_MENUSEPARATOR: + aPart = MENU_POPUPSEPARATOR; + aState = 0; + return NS_OK; + case NS_THEME_MENUARROW: + { + aPart = MENU_POPUPSUBMENU; + EventStates eventState = GetContentState(aFrame, aWidgetType); + aState = IsDisabled(aFrame, eventState) ? MSM_DISABLED : MSM_NORMAL; + return NS_OK; + } + case NS_THEME_MENUCHECKBOX: + case NS_THEME_MENURADIO: + { + EventStates eventState = GetContentState(aFrame, aWidgetType); + + aPart = MENU_POPUPCHECK; + aState = MC_CHECKMARKNORMAL; + + // Radio states are offset by 2 + if (aWidgetType == NS_THEME_MENURADIO) + aState += 2; + + // the disabled states are offset by 1 + if (IsDisabled(aFrame, eventState)) + aState += 1; + + return NS_OK; + } + case NS_THEME_MENUITEMTEXT: + case NS_THEME_MENUIMAGE: + aPart = -1; + aState = 0; + return NS_OK; + + case NS_THEME_WINDOW_TITLEBAR: + aPart = mozilla::widget::themeconst::WP_CAPTION; + aState = GetTopLevelWindowActiveState(aFrame); + return NS_OK; + case NS_THEME_WINDOW_TITLEBAR_MAXIMIZED: + aPart = mozilla::widget::themeconst::WP_MAXCAPTION; + aState = GetTopLevelWindowActiveState(aFrame); + return NS_OK; + case NS_THEME_WINDOW_FRAME_LEFT: + aPart = mozilla::widget::themeconst::WP_FRAMELEFT; + aState = GetTopLevelWindowActiveState(aFrame); + return NS_OK; + case NS_THEME_WINDOW_FRAME_RIGHT: + aPart = mozilla::widget::themeconst::WP_FRAMERIGHT; + aState = GetTopLevelWindowActiveState(aFrame); + return NS_OK; + case NS_THEME_WINDOW_FRAME_BOTTOM: + aPart = mozilla::widget::themeconst::WP_FRAMEBOTTOM; + aState = GetTopLevelWindowActiveState(aFrame); + return NS_OK; + case NS_THEME_WINDOW_BUTTON_CLOSE: + aPart = mozilla::widget::themeconst::WP_CLOSEBUTTON; + aState = GetWindowFrameButtonState(aFrame, GetContentState(aFrame, aWidgetType)); + return NS_OK; + case NS_THEME_WINDOW_BUTTON_MINIMIZE: + aPart = mozilla::widget::themeconst::WP_MINBUTTON; + aState = GetWindowFrameButtonState(aFrame, GetContentState(aFrame, aWidgetType)); + return NS_OK; + case NS_THEME_WINDOW_BUTTON_MAXIMIZE: + aPart = mozilla::widget::themeconst::WP_MAXBUTTON; + aState = GetWindowFrameButtonState(aFrame, GetContentState(aFrame, aWidgetType)); + return NS_OK; + case NS_THEME_WINDOW_BUTTON_RESTORE: + aPart = mozilla::widget::themeconst::WP_RESTOREBUTTON; + aState = GetWindowFrameButtonState(aFrame, GetContentState(aFrame, aWidgetType)); + return NS_OK; + case NS_THEME_WINDOW_BUTTON_BOX: + case NS_THEME_WINDOW_BUTTON_BOX_MAXIMIZED: + case NS_THEME_WIN_GLASS: + case NS_THEME_WIN_BORDERLESS_GLASS: + aPart = -1; + aState = 0; + return NS_OK; + } + + aPart = 0; + aState = 0; + return NS_ERROR_FAILURE; +} + +static bool +AssumeThemePartAndStateAreTransparent(int32_t aPart, int32_t aState) +{ + if (aPart == MENU_POPUPITEM && aState == MBI_NORMAL) { + return true; + } + return false; +} + +// When running with per-monitor DPI (on Win8.1+), and rendering on a display +// with a different DPI setting from the system's default scaling, we need to +// apply scaling to native-themed elements as the Windows theme APIs assume +// the system default resolution. +static inline double +GetThemeDpiScaleFactor(nsIFrame* aFrame) +{ + if (WinUtils::IsPerMonitorDPIAware()) { + nsIWidget* rootWidget = aFrame->PresContext()->GetRootWidget(); + if (rootWidget) { + double systemScale = WinUtils::SystemScaleFactor(); + return rootWidget->GetDefaultScale().scale / systemScale; + } + } + return 1.0; +} + +NS_IMETHODIMP +nsNativeThemeWin::DrawWidgetBackground(nsRenderingContext* aContext, + nsIFrame* aFrame, + uint8_t aWidgetType, + const nsRect& aRect, + const nsRect& aDirtyRect) +{ + HANDLE theme = GetTheme(aWidgetType); + if (!theme) + return ClassicDrawWidgetBackground(aContext, aFrame, aWidgetType, aRect, aDirtyRect); + + // ^^ without the right sdk, assume xp theming and fall through. + if (nsUXThemeData::CheckForCompositor()) { + switch (aWidgetType) { + case NS_THEME_WINDOW_TITLEBAR: + case NS_THEME_WINDOW_TITLEBAR_MAXIMIZED: + case NS_THEME_WINDOW_FRAME_LEFT: + case NS_THEME_WINDOW_FRAME_RIGHT: + case NS_THEME_WINDOW_FRAME_BOTTOM: + // Nothing to draw, these areas are glass. Minimum dimensions + // should be set, so xul content should be layed out correctly. + return NS_OK; + break; + case NS_THEME_WINDOW_BUTTON_CLOSE: + case NS_THEME_WINDOW_BUTTON_MINIMIZE: + case NS_THEME_WINDOW_BUTTON_MAXIMIZE: + case NS_THEME_WINDOW_BUTTON_RESTORE: + // Not conventional bitmaps, can't be retrieved. If we fall + // through here and call the theme library we'll get aero + // basic bitmaps. + return NS_OK; + break; + case NS_THEME_WIN_GLASS: + case NS_THEME_WIN_BORDERLESS_GLASS: + // Nothing to draw, this is the glass background. + return NS_OK; + case NS_THEME_WINDOW_BUTTON_BOX: + case NS_THEME_WINDOW_BUTTON_BOX_MAXIMIZED: + // We handle these through nsIWidget::UpdateThemeGeometries + return NS_OK; + break; + } + } + + int32_t part, state; + nsresult rv = GetThemePartAndState(aFrame, aWidgetType, part, state); + if (NS_FAILED(rv)) + return rv; + + if (AssumeThemePartAndStateAreTransparent(part, state)) { + return NS_OK; + } + + RefPtr<gfxContext> ctx = aContext->ThebesContext(); + gfxContextMatrixAutoSaveRestore save(ctx); + + double themeScale = GetThemeDpiScaleFactor(aFrame); + if (themeScale != 1.0) { + ctx->SetMatrix(ctx->CurrentMatrix().Scale(themeScale, themeScale)); + } + + gfxFloat p2a = gfxFloat(aFrame->PresContext()->AppUnitsPerDevPixel()); + RECT widgetRect; + RECT clipRect; + gfxRect tr(aRect.x, aRect.y, aRect.width, aRect.height), + dr(aDirtyRect.x, aDirtyRect.y, aDirtyRect.width, aDirtyRect.height); + + tr.ScaleInverse(p2a * themeScale); + dr.ScaleInverse(p2a * themeScale); + + gfxWindowsNativeDrawing nativeDrawing(ctx, dr, GetWidgetNativeDrawingFlags(aWidgetType)); + +RENDER_AGAIN: + + HDC hdc = nativeDrawing.BeginNativeDrawing(); + if (!hdc) + return NS_ERROR_FAILURE; + + nativeDrawing.TransformToNativeRect(tr, widgetRect); + nativeDrawing.TransformToNativeRect(dr, clipRect); + +#if 0 + { + MOZ_LOG(gWindowsLog, LogLevel::Error, + (stderr, "xform: %f %f %f %f [%f %f]\n", m._11, m._21, m._12, m._22, + m._31, m._32)); + MOZ_LOG(gWindowsLog, LogLevel::Error, + (stderr, "tr: [%d %d %d %d]\ndr: [%d %d %d %d]\noff: [%f %f]\n", + tr.x, tr.y, tr.width, tr.height, dr.x, dr.y, dr.width, dr.height, + offset.x, offset.y)); + } +#endif + + if (aWidgetType == NS_THEME_WINDOW_TITLEBAR) { + // Clip out the left and right corners of the frame, all we want in + // is the middle section. + widgetRect.left -= GetSystemMetrics(SM_CXFRAME); + widgetRect.right += GetSystemMetrics(SM_CXFRAME); + } else if (aWidgetType == NS_THEME_WINDOW_TITLEBAR_MAXIMIZED) { + // The origin of the window is off screen when maximized and windows + // doesn't compensate for this in rendering the background. Push the + // top of the bitmap down by SM_CYFRAME so we get the full graphic. + widgetRect.top += GetSystemMetrics(SM_CYFRAME); + } else if (aWidgetType == NS_THEME_TAB) { + // For left edge and right edge tabs, we need to adjust the widget + // rects and clip rects so that the edges don't get drawn. + bool isLeft = IsLeftToSelectedTab(aFrame); + bool isRight = !isLeft && IsRightToSelectedTab(aFrame); + + if (isLeft || isRight) { + // HACK ALERT: There appears to be no way to really obtain this value, so we're forced + // to just use the default value for Luna (which also happens to be correct for + // all the other skins I've tried). + int32_t edgeSize = 2; + + // Armed with the size of the edge, we now need to either shift to the left or to the + // right. The clip rect won't include this extra area, so we know that we're + // effectively shifting the edge out of view (such that it won't be painted). + if (isLeft) + // The right edge should not be drawn. Extend our rect by the edge size. + widgetRect.right += edgeSize; + else + // The left edge should not be drawn. Move the widget rect's left coord back. + widgetRect.left -= edgeSize; + } + } + else if (aWidgetType == NS_THEME_WINDOW_BUTTON_MINIMIZE) { + OffsetBackgroundRect(widgetRect, CAPTIONBUTTON_MINIMIZE); + } + else if (aWidgetType == NS_THEME_WINDOW_BUTTON_MAXIMIZE || + aWidgetType == NS_THEME_WINDOW_BUTTON_RESTORE) { + OffsetBackgroundRect(widgetRect, CAPTIONBUTTON_RESTORE); + } + else if (aWidgetType == NS_THEME_WINDOW_BUTTON_CLOSE) { + OffsetBackgroundRect(widgetRect, CAPTIONBUTTON_CLOSE); + } + + // widgetRect is the bounding box for a widget, yet the scale track is only + // a small portion of this size, so the edges of the scale need to be + // adjusted to the real size of the track. + if (aWidgetType == NS_THEME_RANGE || + aWidgetType == NS_THEME_SCALE_HORIZONTAL || + aWidgetType == NS_THEME_SCALE_VERTICAL) { + RECT contentRect; + GetThemeBackgroundContentRect(theme, hdc, part, state, &widgetRect, &contentRect); + + SIZE siz; + GetThemePartSize(theme, hdc, part, state, &widgetRect, TS_TRUE, &siz); + + // When rounding is necessary, we round the position of the track + // away from the chevron of the thumb to make it look better. + if (aWidgetType == NS_THEME_SCALE_HORIZONTAL || + (aWidgetType == NS_THEME_RANGE && IsRangeHorizontal(aFrame))) { + contentRect.top += (contentRect.bottom - contentRect.top - siz.cy) / 2; + contentRect.bottom = contentRect.top + siz.cy; + } + else { + if (!IsFrameRTL(aFrame)) { + contentRect.left += (contentRect.right - contentRect.left - siz.cx) / 2; + contentRect.right = contentRect.left + siz.cx; + } else { + contentRect.right -= (contentRect.right - contentRect.left - siz.cx) / 2; + contentRect.left = contentRect.right - siz.cx; + } + } + + DrawThemeBackground(theme, hdc, part, state, &contentRect, &clipRect); + } + else if (aWidgetType == NS_THEME_MENUCHECKBOX || aWidgetType == NS_THEME_MENURADIO) + { + bool isChecked = false; + isChecked = CheckBooleanAttr(aFrame, nsGkAtoms::checked); + + if (isChecked) + { + int bgState = MCB_NORMAL; + EventStates eventState = GetContentState(aFrame, aWidgetType); + + // the disabled states are offset by 1 + if (IsDisabled(aFrame, eventState)) + bgState += 1; + + SIZE checkboxBGSize(GetCheckboxBGSize(theme, hdc)); + + RECT checkBGRect = widgetRect; + if (IsFrameRTL(aFrame)) { + checkBGRect.left = checkBGRect.right-checkboxBGSize.cx; + } else { + checkBGRect.right = checkBGRect.left+checkboxBGSize.cx; + } + + // Center the checkbox background vertically in the menuitem + checkBGRect.top += (checkBGRect.bottom - checkBGRect.top)/2 - checkboxBGSize.cy/2; + checkBGRect.bottom = checkBGRect.top + checkboxBGSize.cy; + + DrawThemeBackground(theme, hdc, MENU_POPUPCHECKBACKGROUND, bgState, &checkBGRect, &clipRect); + + MARGINS checkMargins = GetCheckboxMargins(theme, hdc); + RECT checkRect = checkBGRect; + checkRect.left += checkMargins.cxLeftWidth; + checkRect.right -= checkMargins.cxRightWidth; + checkRect.top += checkMargins.cyTopHeight; + checkRect.bottom -= checkMargins.cyBottomHeight; + DrawThemeBackground(theme, hdc, MENU_POPUPCHECK, state, &checkRect, &clipRect); + } + } + else if (aWidgetType == NS_THEME_MENUPOPUP) + { + DrawThemeBackground(theme, hdc, MENU_POPUPBORDERS, /* state */ 0, &widgetRect, &clipRect); + SIZE borderSize; + GetThemePartSize(theme, hdc, MENU_POPUPBORDERS, 0, nullptr, TS_TRUE, &borderSize); + + RECT bgRect = widgetRect; + bgRect.top += borderSize.cy; + bgRect.bottom -= borderSize.cy; + bgRect.left += borderSize.cx; + bgRect.right -= borderSize.cx; + + DrawThemeBackground(theme, hdc, MENU_POPUPBACKGROUND, /* state */ 0, &bgRect, &clipRect); + + SIZE gutterSize(GetGutterSize(theme, hdc)); + + RECT gutterRect; + gutterRect.top = bgRect.top; + gutterRect.bottom = bgRect.bottom; + if (IsFrameRTL(aFrame)) { + gutterRect.right = bgRect.right; + gutterRect.left = gutterRect.right-gutterSize.cx; + } else { + gutterRect.left = bgRect.left; + gutterRect.right = gutterRect.left+gutterSize.cx; + } + + DrawThemeBGRTLAware(theme, hdc, MENU_POPUPGUTTER, /* state */ 0, + &gutterRect, &clipRect, IsFrameRTL(aFrame)); + } + else if (aWidgetType == NS_THEME_MENUSEPARATOR) + { + SIZE gutterSize(GetGutterSize(theme,hdc)); + + RECT sepRect = widgetRect; + if (IsFrameRTL(aFrame)) + sepRect.right -= gutterSize.cx; + else + sepRect.left += gutterSize.cx; + + DrawThemeBackground(theme, hdc, MENU_POPUPSEPARATOR, /* state */ 0, &sepRect, &clipRect); + } + else if (aWidgetType == NS_THEME_MENUARROW) + { + // We're dpi aware and as such on systems that have dpi > 96 set, the + // theme library expects us to do proper positioning and scaling of glyphs. + // For NS_THEME_MENUARROW, layout may hand us a widget rect larger than the + // glyph rect we request in GetMinimumWidgetSize. To prevent distortion we + // have to position and scale what we draw. + + SIZE glyphSize; + GetThemePartSize(theme, hdc, part, state, nullptr, TS_TRUE, &glyphSize); + + int32_t widgetHeight = widgetRect.bottom - widgetRect.top; + + RECT renderRect = widgetRect; + + // We request (glyph width * 2, glyph height) in GetMinimumWidgetSize. In + // Firefox some menu items provide the full height of the item to us, in + // others our widget rect is the exact dims of our arrow glyph. Adjust the + // vertical position by the added space, if any exists. + renderRect.top += ((widgetHeight - glyphSize.cy) / 2); + renderRect.bottom = renderRect.top + glyphSize.cy; + // I'm using the width of the arrow glyph for the arrow-side padding. + // AFAICT there doesn't appear to be a theme constant we can query + // for this value. Generally this looks correct, and has the added + // benefit of being a dpi adjusted value. + if (!IsFrameRTL(aFrame)) { + renderRect.right = widgetRect.right - glyphSize.cx; + renderRect.left = renderRect.right - glyphSize.cx; + } else { + renderRect.left = glyphSize.cx; + renderRect.right = renderRect.left + glyphSize.cx; + } + DrawThemeBGRTLAware(theme, hdc, part, state, &renderRect, &clipRect, + IsFrameRTL(aFrame)); + } + // The following widgets need to be RTL-aware + else if (aWidgetType == NS_THEME_RESIZER || + aWidgetType == NS_THEME_MENULIST_BUTTON) + { + DrawThemeBGRTLAware(theme, hdc, part, state, + &widgetRect, &clipRect, IsFrameRTL(aFrame)); + } + else if (aWidgetType == NS_THEME_NUMBER_INPUT || + aWidgetType == NS_THEME_TEXTFIELD || + aWidgetType == NS_THEME_TEXTFIELD_MULTILINE) { + DrawThemeBackground(theme, hdc, part, state, &widgetRect, &clipRect); + if (state == TFS_EDITBORDER_DISABLED) { + InflateRect(&widgetRect, -1, -1); + ::FillRect(hdc, &widgetRect, reinterpret_cast<HBRUSH>(COLOR_BTNFACE+1)); + } + } + else if (aWidgetType == NS_THEME_PROGRESSBAR || + aWidgetType == NS_THEME_PROGRESSBAR_VERTICAL) { + // DrawThemeBackground renders each corner with a solid white pixel. + // Restore these pixels to the underlying color. Tracks are rendered + // using alpha recovery, so this makes the corners transparent. + COLORREF color; + color = GetPixel(hdc, widgetRect.left, widgetRect.top); + DrawThemeBackground(theme, hdc, part, state, &widgetRect, &clipRect); + SetPixel(hdc, widgetRect.left, widgetRect.top, color); + SetPixel(hdc, widgetRect.right-1, widgetRect.top, color); + SetPixel(hdc, widgetRect.right-1, widgetRect.bottom-1, color); + SetPixel(hdc, widgetRect.left, widgetRect.bottom-1, color); + } + else if (aWidgetType == NS_THEME_PROGRESSCHUNK || + aWidgetType == NS_THEME_PROGRESSCHUNK_VERTICAL) { + DrawThemedProgressMeter(aFrame, aWidgetType, theme, hdc, part, state, + &widgetRect, &clipRect, p2a); + } + else if (aWidgetType == NS_THEME_FOCUS_OUTLINE) { + // Inflate 'widgetRect' with the focus outline size. + nsIntMargin border; + if (NS_SUCCEEDED(GetWidgetBorder(aFrame->PresContext()->DeviceContext(), + aFrame, aWidgetType, &border))) { + widgetRect.left -= border.left; + widgetRect.right += border.right; + widgetRect.top -= border.top; + widgetRect.bottom += border.bottom; + } + + DTBGOPTS opts = { + sizeof(DTBGOPTS), + DTBG_OMITCONTENT | DTBG_CLIPRECT, + clipRect + }; + DrawThemeBackgroundEx(theme, hdc, part, state, &widgetRect, &opts); + } + // If part is negative, the element wishes us to not render a themed + // background, instead opting to be drawn specially below. + else if (part >= 0) { + DrawThemeBackground(theme, hdc, part, state, &widgetRect, &clipRect); + } + + // Draw focus rectangles for range and scale elements + // XXX it'd be nice to draw these outside of the frame + if (aWidgetType == NS_THEME_RANGE || + aWidgetType == NS_THEME_SCALE_HORIZONTAL || + aWidgetType == NS_THEME_SCALE_VERTICAL) { + EventStates contentState = GetContentState(aFrame, aWidgetType); + + if (contentState.HasState(NS_EVENT_STATE_FOCUS)) { + POINT vpOrg; + HPEN hPen = nullptr; + + uint8_t id = SaveDC(hdc); + + ::SelectClipRgn(hdc, nullptr); + ::GetViewportOrgEx(hdc, &vpOrg); + ::SetBrushOrgEx(hdc, vpOrg.x + widgetRect.left, vpOrg.y + widgetRect.top, nullptr); + ::SetTextColor(hdc, 0); + ::DrawFocusRect(hdc, &widgetRect); + ::RestoreDC(hdc, id); + if (hPen) { + ::DeleteObject(hPen); + } + } + } + else if (aWidgetType == NS_THEME_TOOLBAR && state == 0) { + // Draw toolbar separator lines above all toolbars except the first one. + // The lines are part of the Rebar theme, which is loaded for NS_THEME_TOOLBOX. + theme = GetTheme(NS_THEME_TOOLBOX); + if (!theme) + return NS_ERROR_FAILURE; + + widgetRect.bottom = widgetRect.top + TB_SEPARATOR_HEIGHT; + DrawThemeEdge(theme, hdc, RP_BAND, 0, &widgetRect, EDGE_ETCHED, BF_TOP, nullptr); + } + else if (aWidgetType == NS_THEME_SCROLLBARTHUMB_HORIZONTAL || + aWidgetType == NS_THEME_SCROLLBARTHUMB_VERTICAL) + { + // Draw the decorative gripper for the scrollbar thumb button, if it fits + + SIZE gripSize; + MARGINS thumbMgns; + int gripPart = (aWidgetType == NS_THEME_SCROLLBARTHUMB_HORIZONTAL) ? + SP_GRIPPERHOR : SP_GRIPPERVERT; + + if (GetThemePartSize(theme, hdc, gripPart, state, nullptr, TS_TRUE, &gripSize) == S_OK && + GetThemeMargins(theme, hdc, part, state, TMT_CONTENTMARGINS, nullptr, &thumbMgns) == S_OK && + gripSize.cx + thumbMgns.cxLeftWidth + thumbMgns.cxRightWidth <= widgetRect.right - widgetRect.left && + gripSize.cy + thumbMgns.cyTopHeight + thumbMgns.cyBottomHeight <= widgetRect.bottom - widgetRect.top) + { + DrawThemeBackground(theme, hdc, gripPart, state, &widgetRect, &clipRect); + } + } + + nativeDrawing.EndNativeDrawing(); + + if (nativeDrawing.ShouldRenderAgain()) + goto RENDER_AGAIN; + + nativeDrawing.PaintToContext(); + + return NS_OK; +} + +static void +ScaleForFrameDPI(nsIntMargin* aMargin, nsIFrame* aFrame) +{ + double themeScale = GetThemeDpiScaleFactor(aFrame); + if (themeScale != 1.0) { + aMargin->top = NSToIntRound(aMargin->top * themeScale); + aMargin->left = NSToIntRound(aMargin->left * themeScale); + aMargin->bottom = NSToIntRound(aMargin->bottom * themeScale); + aMargin->right = NSToIntRound(aMargin->right * themeScale); + } +} + +static void +ScaleForFrameDPI(LayoutDeviceIntSize* aSize, nsIFrame* aFrame) +{ + double themeScale = GetThemeDpiScaleFactor(aFrame); + if (themeScale != 1.0) { + aSize->width = NSToIntRound(aSize->width * themeScale); + aSize->height = NSToIntRound(aSize->height * themeScale); + } +} + +NS_IMETHODIMP +nsNativeThemeWin::GetWidgetBorder(nsDeviceContext* aContext, + nsIFrame* aFrame, + uint8_t aWidgetType, + nsIntMargin* aResult) +{ + HANDLE theme = GetTheme(aWidgetType); + nsresult rv = NS_OK; + if (!theme) { + rv = ClassicGetWidgetBorder(aContext, aFrame, aWidgetType, aResult); + ScaleForFrameDPI(aResult, aFrame); + return rv; + } + + aResult->top = aResult->bottom = aResult->left = aResult->right = 0; + + if (!WidgetIsContainer(aWidgetType) || + aWidgetType == NS_THEME_TOOLBOX || + aWidgetType == NS_THEME_WIN_MEDIA_TOOLBOX || + aWidgetType == NS_THEME_WIN_COMMUNICATIONS_TOOLBOX || + aWidgetType == NS_THEME_WIN_BROWSERTABBAR_TOOLBOX || + aWidgetType == NS_THEME_STATUSBAR || + aWidgetType == NS_THEME_RESIZER || aWidgetType == NS_THEME_TABPANEL || + aWidgetType == NS_THEME_SCROLLBAR_HORIZONTAL || + aWidgetType == NS_THEME_SCROLLBAR_VERTICAL || + aWidgetType == NS_THEME_MENUITEM || aWidgetType == NS_THEME_CHECKMENUITEM || + aWidgetType == NS_THEME_RADIOMENUITEM || aWidgetType == NS_THEME_MENUPOPUP || + aWidgetType == NS_THEME_MENUIMAGE || aWidgetType == NS_THEME_MENUITEMTEXT || + aWidgetType == NS_THEME_SEPARATOR || + aWidgetType == NS_THEME_WINDOW_TITLEBAR || + aWidgetType == NS_THEME_WINDOW_TITLEBAR_MAXIMIZED || + aWidgetType == NS_THEME_WIN_GLASS || aWidgetType == NS_THEME_WIN_BORDERLESS_GLASS) + return NS_OK; // Don't worry about it. + + int32_t part, state; + rv = GetThemePartAndState(aFrame, aWidgetType, part, state); + if (NS_FAILED(rv)) + return rv; + + if (aWidgetType == NS_THEME_TOOLBAR) { + // make space for the separator line above all toolbars but the first + if (state == 0) + aResult->top = TB_SEPARATOR_HEIGHT; + return NS_OK; + } + + // Get our info. + RECT outerRect; // Create a fake outer rect. + outerRect.top = outerRect.left = 100; + outerRect.right = outerRect.bottom = 200; + RECT contentRect(outerRect); + HRESULT res = GetThemeBackgroundContentRect(theme, nullptr, part, state, &outerRect, &contentRect); + + if (FAILED(res)) + return NS_ERROR_FAILURE; + + // Now compute the delta in each direction and place it in our + // nsIntMargin struct. + aResult->top = contentRect.top - outerRect.top; + aResult->bottom = outerRect.bottom - contentRect.bottom; + aResult->left = contentRect.left - outerRect.left; + aResult->right = outerRect.right - contentRect.right; + + // Remove the edges for tabs that are before or after the selected tab, + if (aWidgetType == NS_THEME_TAB) { + if (IsLeftToSelectedTab(aFrame)) + // Remove the right edge, since we won't be drawing it. + aResult->right = 0; + else if (IsRightToSelectedTab(aFrame)) + // Remove the left edge, since we won't be drawing it. + aResult->left = 0; + } + + if (aFrame && (aWidgetType == NS_THEME_NUMBER_INPUT || + aWidgetType == NS_THEME_TEXTFIELD || + aWidgetType == NS_THEME_TEXTFIELD_MULTILINE)) { + nsIContent* content = aFrame->GetContent(); + if (content && content->IsHTMLElement()) { + // We need to pad textfields by 1 pixel, since the caret will draw + // flush against the edge by default if we don't. + aResult->top++; + aResult->left++; + aResult->bottom++; + aResult->right++; + } + } + + ScaleForFrameDPI(aResult, aFrame); + return rv; +} + +bool +nsNativeThemeWin::GetWidgetPadding(nsDeviceContext* aContext, + nsIFrame* aFrame, + uint8_t aWidgetType, + nsIntMargin* aResult) +{ + switch (aWidgetType) { + // Radios and checkboxes return a fixed size in GetMinimumWidgetSize + // and have a meaningful baseline, so they can't have + // author-specified padding. + case NS_THEME_CHECKBOX: + case NS_THEME_RADIO: + aResult->SizeTo(0, 0, 0, 0); + return true; + } + + bool ok = true; + + if (aWidgetType == NS_THEME_WINDOW_BUTTON_BOX || + aWidgetType == NS_THEME_WINDOW_BUTTON_BOX_MAXIMIZED) { + aResult->SizeTo(0, 0, 0, 0); + + // aero glass doesn't display custom buttons + if (nsUXThemeData::CheckForCompositor()) + return true; + + // button padding for standard windows + if (aWidgetType == NS_THEME_WINDOW_BUTTON_BOX) { + aResult->top = GetSystemMetrics(SM_CXFRAME); + } + ScaleForFrameDPI(aResult, aFrame); + return ok; + } + + // Content padding + if (aWidgetType == NS_THEME_WINDOW_TITLEBAR || + aWidgetType == NS_THEME_WINDOW_TITLEBAR_MAXIMIZED) { + aResult->SizeTo(0, 0, 0, 0); + // XXX Maximized windows have an offscreen offset equal to + // the border padding. This should be addressed in nsWindow, + // but currently can't be, see UpdateNonClientMargins. + if (aWidgetType == NS_THEME_WINDOW_TITLEBAR_MAXIMIZED) + aResult->top = GetSystemMetrics(SM_CXFRAME) + + GetSystemMetrics(SM_CXPADDEDBORDER); + return ok; + } + + HANDLE theme = GetTheme(aWidgetType); + if (!theme) { + ok = ClassicGetWidgetPadding(aContext, aFrame, aWidgetType, aResult); + ScaleForFrameDPI(aResult, aFrame); + return ok; + } + + if (aWidgetType == NS_THEME_MENUPOPUP) + { + SIZE popupSize; + GetThemePartSize(theme, nullptr, MENU_POPUPBORDERS, /* state */ 0, nullptr, TS_TRUE, &popupSize); + aResult->top = aResult->bottom = popupSize.cy; + aResult->left = aResult->right = popupSize.cx; + ScaleForFrameDPI(aResult, aFrame); + return ok; + } + + if (IsVistaOrLater()) { + if (aWidgetType == NS_THEME_NUMBER_INPUT || + aWidgetType == NS_THEME_TEXTFIELD || + aWidgetType == NS_THEME_TEXTFIELD_MULTILINE || + aWidgetType == NS_THEME_MENULIST) + { + /* If we have author-specified padding for these elements, don't do the fixups below */ + if (aFrame->PresContext()->HasAuthorSpecifiedRules(aFrame, NS_AUTHOR_SPECIFIED_PADDING)) + return false; + } + + /* textfields need extra pixels on all sides, otherwise they + * wrap their content too tightly. The actual border is drawn 1px + * inside the specified rectangle, so Gecko will end up making the + * contents look too small. Instead, we add 2px padding for the + * contents and fix this. (Used to be 1px added, see bug 430212) + */ + if (aWidgetType == NS_THEME_NUMBER_INPUT || + aWidgetType == NS_THEME_TEXTFIELD || + aWidgetType == NS_THEME_TEXTFIELD_MULTILINE) { + aResult->top = aResult->bottom = 2; + aResult->left = aResult->right = 2; + ScaleForFrameDPI(aResult, aFrame); + return ok; + } else if (IsHTMLContent(aFrame) && aWidgetType == NS_THEME_MENULIST) { + /* For content menulist controls, we need an extra pixel so + * that we have room to draw our focus rectangle stuff. + * Otherwise, the focus rect might overlap the control's + * border. + */ + aResult->top = aResult->bottom = 1; + aResult->left = aResult->right = 1; + ScaleForFrameDPI(aResult, aFrame); + return ok; + } + } + + int32_t right, left, top, bottom; + right = left = top = bottom = 0; + switch (aWidgetType) + { + case NS_THEME_MENUIMAGE: + right = 8; + left = 3; + break; + case NS_THEME_MENUCHECKBOX: + case NS_THEME_MENURADIO: + right = 8; + left = 0; + break; + case NS_THEME_MENUITEMTEXT: + // There seem to be exactly 4 pixels from the edge + // of the gutter to the text: 2px margin (CSS) + 2px padding (here) + { + SIZE size(GetGutterSize(theme, nullptr)); + left = size.cx + 2; + } + break; + case NS_THEME_MENUSEPARATOR: + { + SIZE size(GetGutterSize(theme, nullptr)); + left = size.cx + 5; + top = 10; + bottom = 7; + } + break; + default: + return false; + } + + if (IsFrameRTL(aFrame)) + { + aResult->right = left; + aResult->left = right; + } + else + { + aResult->right = right; + aResult->left = left; + } + + ScaleForFrameDPI(aResult, aFrame); + return ok; +} + +bool +nsNativeThemeWin::GetWidgetOverflow(nsDeviceContext* aContext, + nsIFrame* aFrame, + uint8_t aWidgetType, + nsRect* aOverflowRect) +{ + /* This is disabled for now, because it causes invalidation problems -- + * see bug 420381. The effect of not updating the overflow area is that + * for dropdown buttons in content areas, there is a 1px border on 3 sides + * where, if invalidated, the dropdown control probably won't be repainted. + * This is fairly minor, as by default there is nothing in that area, and + * a border only shows up if the widget is being hovered. + */ +#if 0 + if (IsVistaOrLater()) { + /* We explicitly draw dropdown buttons in HTML content 1px bigger + * up, right, and bottom so that they overlap the dropdown's border + * like they're supposed to. + */ + if (aWidgetType == NS_THEME_MENULIST_BUTTON && + IsHTMLContent(aFrame) && + !IsWidgetStyled(aFrame->GetParent()->PresContext(), + aFrame->GetParent(), + NS_THEME_MENULIST)) + { + int32_t p2a = aContext->AppUnitsPerDevPixel(); + /* Note: no overflow on the left */ + nsMargin m(p2a, p2a, p2a, 0); + aOverflowRect->Inflate (m); + return true; + } + } +#endif + + if (aWidgetType == NS_THEME_FOCUS_OUTLINE) { + nsIntMargin border; + nsresult rv = GetWidgetBorder(aContext, aFrame, aWidgetType, &border); + if (NS_SUCCEEDED(rv)) { + int32_t p2a = aContext->AppUnitsPerDevPixel(); + nsMargin m(NSIntPixelsToAppUnits(border.top, p2a), + NSIntPixelsToAppUnits(border.right, p2a), + NSIntPixelsToAppUnits(border.bottom, p2a), + NSIntPixelsToAppUnits(border.left, p2a)); + aOverflowRect->Inflate(m); + return true; + } + } + + return false; +} + +NS_IMETHODIMP +nsNativeThemeWin::GetMinimumWidgetSize(nsPresContext* aPresContext, nsIFrame* aFrame, + uint8_t aWidgetType, + LayoutDeviceIntSize* aResult, bool* aIsOverridable) +{ + aResult->width = aResult->height = 0; + *aIsOverridable = true; + nsresult rv = NS_OK; + + HANDLE theme = GetTheme(aWidgetType); + if (!theme) { + rv = ClassicGetMinimumWidgetSize(aFrame, aWidgetType, aResult, aIsOverridable); + ScaleForFrameDPI(aResult, aFrame); + return rv; + } + switch (aWidgetType) { + case NS_THEME_GROUPBOX: + case NS_THEME_NUMBER_INPUT: + case NS_THEME_TEXTFIELD: + case NS_THEME_TOOLBOX: + case NS_THEME_WIN_MEDIA_TOOLBOX: + case NS_THEME_WIN_COMMUNICATIONS_TOOLBOX: + case NS_THEME_WIN_BROWSERTABBAR_TOOLBOX: + case NS_THEME_TOOLBAR: + case NS_THEME_STATUSBAR: + case NS_THEME_PROGRESSCHUNK: + case NS_THEME_PROGRESSCHUNK_VERTICAL: + case NS_THEME_TABPANELS: + case NS_THEME_TABPANEL: + case NS_THEME_LISTBOX: + case NS_THEME_TREEVIEW: + case NS_THEME_MENUITEMTEXT: + case NS_THEME_WIN_GLASS: + case NS_THEME_WIN_BORDERLESS_GLASS: + return NS_OK; // Don't worry about it. + } + + if (aWidgetType == NS_THEME_MENUITEM && IsTopLevelMenu(aFrame)) + return NS_OK; // Don't worry about it for top level menus + + // Call GetSystemMetrics to determine size for WinXP scrollbars + // (GetThemeSysSize API returns the optimal size for the theme, but + // Windows appears to always use metrics when drawing standard scrollbars) + THEMESIZE sizeReq = TS_TRUE; // Best-fit size + switch (aWidgetType) { + case NS_THEME_SCROLLBARTHUMB_VERTICAL: + case NS_THEME_SCROLLBARTHUMB_HORIZONTAL: + case NS_THEME_SCROLLBARBUTTON_UP: + case NS_THEME_SCROLLBARBUTTON_DOWN: + case NS_THEME_SCROLLBARBUTTON_LEFT: + case NS_THEME_SCROLLBARBUTTON_RIGHT: + case NS_THEME_SCROLLBAR_HORIZONTAL: + case NS_THEME_SCROLLBAR_VERTICAL: + case NS_THEME_MENULIST_BUTTON: { + rv = ClassicGetMinimumWidgetSize(aFrame, aWidgetType, aResult, aIsOverridable); + ScaleForFrameDPI(aResult, aFrame); + return rv; + } + case NS_THEME_MENUITEM: + case NS_THEME_CHECKMENUITEM: + case NS_THEME_RADIOMENUITEM: + if(!IsTopLevelMenu(aFrame)) + { + SIZE gutterSize(GetGutterSize(theme, nullptr)); + aResult->width = gutterSize.cx; + aResult->height = gutterSize.cy; + ScaleForFrameDPI(aResult, aFrame); + return rv; + } + break; + + case NS_THEME_MENUIMAGE: + case NS_THEME_MENUCHECKBOX: + case NS_THEME_MENURADIO: + { + SIZE boxSize(GetGutterSize(theme, nullptr)); + aResult->width = boxSize.cx+2; + aResult->height = boxSize.cy; + *aIsOverridable = false; + ScaleForFrameDPI(aResult, aFrame); + return rv; + } + + case NS_THEME_MENUITEMTEXT: + return NS_OK; + + case NS_THEME_PROGRESSBAR: + case NS_THEME_PROGRESSBAR_VERTICAL: + // Best-fit size for progress meters is too large for most + // themes. We want these widgets to be able to really shrink + // down, so use the min-size request value (of 0). + sizeReq = TS_MIN; + break; + + case NS_THEME_RESIZER: + *aIsOverridable = false; + break; + + case NS_THEME_RANGE_THUMB: + case NS_THEME_SCALETHUMB_HORIZONTAL: + case NS_THEME_SCALETHUMB_VERTICAL: + { + *aIsOverridable = false; + // on Vista, GetThemePartAndState returns odd values for + // scale thumbs, so use a hardcoded size instead. + if (IsVistaOrLater()) { + if (aWidgetType == NS_THEME_SCALETHUMB_HORIZONTAL || + (aWidgetType == NS_THEME_RANGE_THUMB && IsRangeHorizontal(aFrame))) { + aResult->width = 12; + aResult->height = 20; + } + else { + aResult->width = 20; + aResult->height = 12; + } + ScaleForFrameDPI(aResult, aFrame); + return rv; + } + break; + } + + case NS_THEME_SCROLLBAR: + { + if (nsLookAndFeel::GetInt( + nsLookAndFeel::eIntID_UseOverlayScrollbars) != 0) { + aResult->SizeTo(::GetSystemMetrics(SM_CXHSCROLL), + ::GetSystemMetrics(SM_CYVSCROLL)); + ScaleForFrameDPI(aResult, aFrame); + return rv; + } + break; + } + + case NS_THEME_SEPARATOR: + // that's 2px left margin, 2px right margin and 2px separator + // (the margin is drawn as part of the separator, though) + aResult->width = 6; + ScaleForFrameDPI(aResult, aFrame); + return rv; + + case NS_THEME_BUTTON: + // We should let HTML buttons shrink to their min size. + // FIXME bug 403934: We should probably really separate + // GetPreferredWidgetSize from GetMinimumWidgetSize, so callers can + // use the one they want. + if (aFrame->GetContent()->IsHTMLElement()) { + sizeReq = TS_MIN; + } + break; + + case NS_THEME_WINDOW_BUTTON_MAXIMIZE: + case NS_THEME_WINDOW_BUTTON_RESTORE: + // The only way to get accurate titlebar button info is to query a + // window w/buttons when it's visible. nsWindow takes care of this and + // stores that info in nsUXThemeData. + aResult->width = nsUXThemeData::sCommandButtons[CMDBUTTONIDX_RESTORE].cx; + aResult->height = nsUXThemeData::sCommandButtons[CMDBUTTONIDX_RESTORE].cy; + // For XP, subtract 4 from system metrics dimensions. + if (!IsVistaOrLater()) { + aResult->width -= 4; + aResult->height -= 4; + } + AddPaddingRect(aResult, CAPTIONBUTTON_RESTORE); + *aIsOverridable = false; + return rv; + + case NS_THEME_WINDOW_BUTTON_MINIMIZE: + aResult->width = nsUXThemeData::sCommandButtons[CMDBUTTONIDX_MINIMIZE].cx; + aResult->height = nsUXThemeData::sCommandButtons[CMDBUTTONIDX_MINIMIZE].cy; + if (!IsVistaOrLater()) { + aResult->width -= 4; + aResult->height -= 4; + } + AddPaddingRect(aResult, CAPTIONBUTTON_MINIMIZE); + *aIsOverridable = false; + return rv; + + case NS_THEME_WINDOW_BUTTON_CLOSE: + aResult->width = nsUXThemeData::sCommandButtons[CMDBUTTONIDX_CLOSE].cx; + aResult->height = nsUXThemeData::sCommandButtons[CMDBUTTONIDX_CLOSE].cy; + if (!IsVistaOrLater()) { + aResult->width -= 4; + aResult->height -= 4; + } + AddPaddingRect(aResult, CAPTIONBUTTON_CLOSE); + *aIsOverridable = false; + return rv; + + case NS_THEME_WINDOW_TITLEBAR: + case NS_THEME_WINDOW_TITLEBAR_MAXIMIZED: + aResult->height = GetSystemMetrics(SM_CYCAPTION); + aResult->height += GetSystemMetrics(SM_CYFRAME); + aResult->height += GetSystemMetrics(SM_CXPADDEDBORDER); + // On Win8.1, we don't want this scaling, because Windows doesn't scale + // the non-client area of the window, and we can end up with ugly overlap + // of the window frame controls into the tab bar or content area. But on + // Win10, we render the window controls ourselves, and the result looks + // better if we do apply this scaling (particularly with themes such as + // DevEdition; see bug 1267636). + if (IsWin10OrLater()) { + ScaleForFrameDPI(aResult, aFrame); + } + *aIsOverridable = false; + return rv; + + case NS_THEME_WINDOW_BUTTON_BOX: + case NS_THEME_WINDOW_BUTTON_BOX_MAXIMIZED: + if (nsUXThemeData::CheckForCompositor()) { + aResult->width = nsUXThemeData::sCommandButtons[CMDBUTTONIDX_BUTTONBOX].cx; + aResult->height = nsUXThemeData::sCommandButtons[CMDBUTTONIDX_BUTTONBOX].cy + - GetSystemMetrics(SM_CYFRAME) + - GetSystemMetrics(SM_CXPADDEDBORDER); + if (aWidgetType == NS_THEME_WINDOW_BUTTON_BOX_MAXIMIZED) { + aResult->width += 1; + aResult->height -= 2; + } + *aIsOverridable = false; + return rv; + } + break; + + case NS_THEME_WINDOW_FRAME_LEFT: + case NS_THEME_WINDOW_FRAME_RIGHT: + case NS_THEME_WINDOW_FRAME_BOTTOM: + aResult->width = GetSystemMetrics(SM_CXFRAME); + aResult->height = GetSystemMetrics(SM_CYFRAME); + *aIsOverridable = false; + return rv; + } + + int32_t part, state; + rv = GetThemePartAndState(aFrame, aWidgetType, part, state); + if (NS_FAILED(rv)) + return rv; + + HDC hdc = ::GetDC(NULL); + if (!hdc) + return NS_ERROR_FAILURE; + + SIZE sz; + GetThemePartSize(theme, hdc, part, state, nullptr, sizeReq, &sz); + aResult->width = sz.cx; + aResult->height = sz.cy; + + switch(aWidgetType) { + case NS_THEME_SPINNER_UPBUTTON: + case NS_THEME_SPINNER_DOWNBUTTON: + aResult->width++; + aResult->height = aResult->height / 2 + 1; + break; + + case NS_THEME_MENUSEPARATOR: + { + SIZE gutterSize(GetGutterSize(theme, hdc)); + aResult->width += gutterSize.cx; + break; + } + + case NS_THEME_MENUARROW: + { + // Use the width of the arrow glyph as padding. See the drawing + // code for details. + aResult->width *= 2; + break; + } + } + + ::ReleaseDC(nullptr, hdc); + + ScaleForFrameDPI(aResult, aFrame); + return rv; +} + +NS_IMETHODIMP +nsNativeThemeWin::WidgetStateChanged(nsIFrame* aFrame, uint8_t aWidgetType, + nsIAtom* aAttribute, bool* aShouldRepaint, + const nsAttrValue* aOldValue) +{ + // Some widget types just never change state. + if (aWidgetType == NS_THEME_TOOLBOX || + aWidgetType == NS_THEME_WIN_MEDIA_TOOLBOX || + aWidgetType == NS_THEME_WIN_COMMUNICATIONS_TOOLBOX || + aWidgetType == NS_THEME_WIN_BROWSERTABBAR_TOOLBOX || + aWidgetType == NS_THEME_TOOLBAR || + aWidgetType == NS_THEME_STATUSBAR || aWidgetType == NS_THEME_STATUSBARPANEL || + aWidgetType == NS_THEME_RESIZERPANEL || + aWidgetType == NS_THEME_PROGRESSCHUNK || + aWidgetType == NS_THEME_PROGRESSCHUNK_VERTICAL || + aWidgetType == NS_THEME_PROGRESSBAR || + aWidgetType == NS_THEME_PROGRESSBAR_VERTICAL || + aWidgetType == NS_THEME_TOOLTIP || + aWidgetType == NS_THEME_TABPANELS || + aWidgetType == NS_THEME_TABPANEL || + aWidgetType == NS_THEME_SEPARATOR || + aWidgetType == NS_THEME_WIN_GLASS || + aWidgetType == NS_THEME_WIN_BORDERLESS_GLASS) { + *aShouldRepaint = false; + return NS_OK; + } + + if (aWidgetType == NS_THEME_WINDOW_TITLEBAR || + aWidgetType == NS_THEME_WINDOW_TITLEBAR_MAXIMIZED || + aWidgetType == NS_THEME_WINDOW_FRAME_LEFT || + aWidgetType == NS_THEME_WINDOW_FRAME_RIGHT || + aWidgetType == NS_THEME_WINDOW_FRAME_BOTTOM || + aWidgetType == NS_THEME_WINDOW_BUTTON_CLOSE || + aWidgetType == NS_THEME_WINDOW_BUTTON_MINIMIZE || + aWidgetType == NS_THEME_WINDOW_BUTTON_MAXIMIZE || + aWidgetType == NS_THEME_WINDOW_BUTTON_RESTORE) { + *aShouldRepaint = true; + return NS_OK; + } + + // On Vista, the scrollbar buttons need to change state when the track has/doesn't have hover + if (!IsVistaOrLater() && + (aWidgetType == NS_THEME_SCROLLBAR_VERTICAL || + aWidgetType == NS_THEME_SCROLLBAR_HORIZONTAL)) { + *aShouldRepaint = false; + return NS_OK; + } + + // We need to repaint the dropdown arrow in vista HTML combobox controls when + // the control is closed to get rid of the hover effect. + if (IsVistaOrLater() && + (aWidgetType == NS_THEME_MENULIST || aWidgetType == NS_THEME_MENULIST_BUTTON) && + IsHTMLContent(aFrame)) + { + *aShouldRepaint = true; + return NS_OK; + } + + // XXXdwh Not sure what can really be done here. Can at least guess for + // specific widgets that they're highly unlikely to have certain states. + // For example, a toolbar doesn't care about any states. + if (!aAttribute) { + // Hover/focus/active changed. Always repaint. + *aShouldRepaint = true; + } + else { + // Check the attribute to see if it's relevant. + // disabled, checked, dlgtype, default, etc. + *aShouldRepaint = false; + if (aAttribute == nsGkAtoms::disabled || + aAttribute == nsGkAtoms::checked || + aAttribute == nsGkAtoms::selected || + aAttribute == nsGkAtoms::visuallyselected || + aAttribute == nsGkAtoms::readonly || + aAttribute == nsGkAtoms::open || + aAttribute == nsGkAtoms::menuactive || + aAttribute == nsGkAtoms::focused) + *aShouldRepaint = true; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsNativeThemeWin::ThemeChanged() +{ + nsUXThemeData::Invalidate(); + return NS_OK; +} + +bool +nsNativeThemeWin::ThemeSupportsWidget(nsPresContext* aPresContext, + nsIFrame* aFrame, + uint8_t aWidgetType) +{ + // XXXdwh We can go even further and call the API to ask if support exists for + // specific widgets. + + if (aPresContext && !aPresContext->PresShell()->IsThemeSupportEnabled()) + return false; + + if (aWidgetType == NS_THEME_FOCUS_OUTLINE) { + return true; + } + + HANDLE theme = nullptr; + if (aWidgetType == NS_THEME_CHECKBOX_CONTAINER) + theme = GetTheme(NS_THEME_CHECKBOX); + else if (aWidgetType == NS_THEME_RADIO_CONTAINER) + theme = GetTheme(NS_THEME_RADIO); + else + theme = GetTheme(aWidgetType); + + if (theme && aWidgetType == NS_THEME_RESIZER) + return true; + + if ((theme) || (!theme && ClassicThemeSupportsWidget(aFrame, aWidgetType))) + // turn off theming for some HTML widgets styled by the page + return (!IsWidgetStyled(aPresContext, aFrame, aWidgetType)); + + return false; +} + +bool +nsNativeThemeWin::WidgetIsContainer(uint8_t aWidgetType) +{ + // XXXdwh At some point flesh all of this out. + if (aWidgetType == NS_THEME_MENULIST_BUTTON || + aWidgetType == NS_THEME_RADIO || + aWidgetType == NS_THEME_CHECKBOX) + return false; + return true; +} + +bool +nsNativeThemeWin::ThemeDrawsFocusForWidget(uint8_t aWidgetType) +{ + return false; +} + +bool +nsNativeThemeWin::ThemeNeedsComboboxDropmarker() +{ + return true; +} + +bool +nsNativeThemeWin::WidgetAppearanceDependsOnWindowFocus(uint8_t aWidgetType) +{ + switch (aWidgetType) { + case NS_THEME_WINDOW_TITLEBAR: + case NS_THEME_WINDOW_TITLEBAR_MAXIMIZED: + case NS_THEME_WINDOW_FRAME_LEFT: + case NS_THEME_WINDOW_FRAME_RIGHT: + case NS_THEME_WINDOW_FRAME_BOTTOM: + case NS_THEME_WINDOW_BUTTON_CLOSE: + case NS_THEME_WINDOW_BUTTON_MINIMIZE: + case NS_THEME_WINDOW_BUTTON_MAXIMIZE: + case NS_THEME_WINDOW_BUTTON_RESTORE: + return true; + default: + return false; + } +} + +nsITheme::ThemeGeometryType +nsNativeThemeWin::ThemeGeometryTypeForWidget(nsIFrame* aFrame, + uint8_t aWidgetType) +{ + switch (aWidgetType) { + case NS_THEME_WINDOW_BUTTON_BOX: + case NS_THEME_WINDOW_BUTTON_BOX_MAXIMIZED: + return eThemeGeometryTypeWindowButtons; + default: + return eThemeGeometryTypeUnknown; + } +} + +bool +nsNativeThemeWin::ShouldHideScrollbars() +{ + return WinUtils::ShouldHideScrollbars(); +} + +nsITheme::Transparency +nsNativeThemeWin::GetWidgetTransparency(nsIFrame* aFrame, uint8_t aWidgetType) +{ + switch (aWidgetType) { + case NS_THEME_SCROLLBAR_SMALL: + case NS_THEME_SCROLLBAR: + case NS_THEME_STATUSBAR: + // Knowing that scrollbars and statusbars are opaque improves + // performance, because we create layers for them. This better be + // true across all Windows themes! If it's not true, we should + // paint an opaque background for them to make it true! + return eOpaque; + case NS_THEME_WIN_GLASS: + case NS_THEME_WIN_BORDERLESS_GLASS: + case NS_THEME_SCALE_HORIZONTAL: + case NS_THEME_SCALE_VERTICAL: + case NS_THEME_PROGRESSBAR: + case NS_THEME_PROGRESSBAR_VERTICAL: + case NS_THEME_PROGRESSCHUNK: + case NS_THEME_PROGRESSCHUNK_VERTICAL: + case NS_THEME_RANGE: + return eTransparent; + } + + HANDLE theme = GetTheme(aWidgetType); + // For the classic theme we don't really have a way of knowing + if (!theme) { + // menu backgrounds and tooltips which can't be themed are opaque + if (aWidgetType == NS_THEME_MENUPOPUP || aWidgetType == NS_THEME_TOOLTIP) { + return eOpaque; + } + return eUnknownTransparency; + } + + int32_t part, state; + nsresult rv = GetThemePartAndState(aFrame, aWidgetType, part, state); + // Fail conservatively + NS_ENSURE_SUCCESS(rv, eUnknownTransparency); + + if (part <= 0) { + // Not a real part code, so IsThemeBackgroundPartiallyTransparent may + // not work, so don't call it. + return eUnknownTransparency; + } + + if (IsThemeBackgroundPartiallyTransparent(theme, part, state)) + return eTransparent; + return eOpaque; +} + +/* Windows 9x/NT/2000/Classic XP Theme Support */ + +bool +nsNativeThemeWin::ClassicThemeSupportsWidget(nsIFrame* aFrame, + uint8_t aWidgetType) +{ + switch (aWidgetType) { + case NS_THEME_RESIZER: + { + // The classic native resizer has an opaque grey background which doesn't + // match the usually white background of the scrollable container, so + // only support the native resizer if not in a scrollframe. + nsIFrame* parentFrame = aFrame->GetParent(); + return (!parentFrame || parentFrame->GetType() != nsGkAtoms::scrollFrame); + } + case NS_THEME_MENUBAR: + case NS_THEME_MENUPOPUP: + // Classic non-flat menus are handled almost entirely through CSS. + if (!nsUXThemeData::sFlatMenus) + return false; + case NS_THEME_BUTTON: + case NS_THEME_NUMBER_INPUT: + case NS_THEME_TEXTFIELD: + case NS_THEME_TEXTFIELD_MULTILINE: + case NS_THEME_CHECKBOX: + case NS_THEME_RADIO: + case NS_THEME_RANGE: + case NS_THEME_RANGE_THUMB: + case NS_THEME_GROUPBOX: + case NS_THEME_SCROLLBARBUTTON_UP: + case NS_THEME_SCROLLBARBUTTON_DOWN: + case NS_THEME_SCROLLBARBUTTON_LEFT: + case NS_THEME_SCROLLBARBUTTON_RIGHT: + case NS_THEME_SCROLLBARTHUMB_VERTICAL: + case NS_THEME_SCROLLBARTHUMB_HORIZONTAL: + case NS_THEME_SCROLLBAR_VERTICAL: + case NS_THEME_SCROLLBAR_HORIZONTAL: + case NS_THEME_SCROLLBAR_NON_DISAPPEARING: + case NS_THEME_SCALE_HORIZONTAL: + case NS_THEME_SCALE_VERTICAL: + case NS_THEME_SCALETHUMB_HORIZONTAL: + case NS_THEME_SCALETHUMB_VERTICAL: + case NS_THEME_MENULIST_BUTTON: + case NS_THEME_SPINNER_UPBUTTON: + case NS_THEME_SPINNER_DOWNBUTTON: + case NS_THEME_LISTBOX: + case NS_THEME_TREEVIEW: + case NS_THEME_MENULIST_TEXTFIELD: + case NS_THEME_MENULIST: + case NS_THEME_TOOLTIP: + case NS_THEME_STATUSBAR: + case NS_THEME_STATUSBARPANEL: + case NS_THEME_RESIZERPANEL: + case NS_THEME_PROGRESSBAR: + case NS_THEME_PROGRESSBAR_VERTICAL: + case NS_THEME_PROGRESSCHUNK: + case NS_THEME_PROGRESSCHUNK_VERTICAL: + case NS_THEME_TAB: + case NS_THEME_TABPANEL: + case NS_THEME_TABPANELS: + case NS_THEME_MENUITEM: + case NS_THEME_CHECKMENUITEM: + case NS_THEME_RADIOMENUITEM: + case NS_THEME_MENUCHECKBOX: + case NS_THEME_MENURADIO: + case NS_THEME_MENUARROW: + case NS_THEME_MENUSEPARATOR: + case NS_THEME_MENUITEMTEXT: + case NS_THEME_WINDOW_TITLEBAR: + case NS_THEME_WINDOW_TITLEBAR_MAXIMIZED: + case NS_THEME_WINDOW_FRAME_LEFT: + case NS_THEME_WINDOW_FRAME_RIGHT: + case NS_THEME_WINDOW_FRAME_BOTTOM: + case NS_THEME_WINDOW_BUTTON_CLOSE: + case NS_THEME_WINDOW_BUTTON_MINIMIZE: + case NS_THEME_WINDOW_BUTTON_MAXIMIZE: + case NS_THEME_WINDOW_BUTTON_RESTORE: + case NS_THEME_WINDOW_BUTTON_BOX: + case NS_THEME_WINDOW_BUTTON_BOX_MAXIMIZED: + return true; + } + return false; +} + +nsresult +nsNativeThemeWin::ClassicGetWidgetBorder(nsDeviceContext* aContext, + nsIFrame* aFrame, + uint8_t aWidgetType, + nsIntMargin* aResult) +{ + switch (aWidgetType) { + case NS_THEME_GROUPBOX: + case NS_THEME_BUTTON: + (*aResult).top = (*aResult).left = (*aResult).bottom = (*aResult).right = 2; + break; + case NS_THEME_STATUSBAR: + (*aResult).bottom = (*aResult).left = (*aResult).right = 0; + (*aResult).top = 2; + break; + case NS_THEME_LISTBOX: + case NS_THEME_TREEVIEW: + case NS_THEME_MENULIST: + case NS_THEME_MENULIST_TEXTFIELD: + case NS_THEME_TAB: + case NS_THEME_NUMBER_INPUT: + case NS_THEME_TEXTFIELD: + case NS_THEME_TEXTFIELD_MULTILINE: + case NS_THEME_FOCUS_OUTLINE: + (*aResult).top = (*aResult).left = (*aResult).bottom = (*aResult).right = 2; + break; + case NS_THEME_STATUSBARPANEL: + case NS_THEME_RESIZERPANEL: { + (*aResult).top = 1; + (*aResult).left = 1; + (*aResult).bottom = 1; + (*aResult).right = aFrame->GetNextSibling() ? 3 : 1; + break; + } + case NS_THEME_TOOLTIP: + (*aResult).top = (*aResult).left = (*aResult).bottom = (*aResult).right = 1; + break; + case NS_THEME_PROGRESSBAR: + case NS_THEME_PROGRESSBAR_VERTICAL: + (*aResult).top = (*aResult).left = (*aResult).bottom = (*aResult).right = 1; + break; + case NS_THEME_MENUBAR: + (*aResult).top = (*aResult).left = (*aResult).bottom = (*aResult).right = 0; + break; + case NS_THEME_MENUPOPUP: + (*aResult).top = (*aResult).left = (*aResult).bottom = (*aResult).right = 3; + break; + default: + (*aResult).top = (*aResult).bottom = (*aResult).left = (*aResult).right = 0; + break; + } + return NS_OK; +} + +bool +nsNativeThemeWin::ClassicGetWidgetPadding(nsDeviceContext* aContext, + nsIFrame* aFrame, + uint8_t aWidgetType, + nsIntMargin* aResult) +{ + switch (aWidgetType) { + case NS_THEME_MENUITEM: + case NS_THEME_CHECKMENUITEM: + case NS_THEME_RADIOMENUITEM: { + int32_t part, state; + bool focused; + + if (NS_FAILED(ClassicGetThemePartAndState(aFrame, aWidgetType, part, state, focused))) + return false; + + if (part == 1) { // top-level menu + if (nsUXThemeData::sFlatMenus || !(state & DFCS_PUSHED)) { + (*aResult).top = (*aResult).bottom = (*aResult).left = (*aResult).right = 2; + } + else { + // make top-level menus look sunken when pushed in the Classic look + (*aResult).top = (*aResult).left = 3; + (*aResult).bottom = (*aResult).right = 1; + } + } + else { + (*aResult).top = 0; + (*aResult).bottom = (*aResult).left = (*aResult).right = 2; + } + return true; + } + case NS_THEME_PROGRESSBAR: + case NS_THEME_PROGRESSBAR_VERTICAL: + (*aResult).top = (*aResult).left = (*aResult).bottom = (*aResult).right = 1; + return true; + default: + return false; + } +} + +nsresult +nsNativeThemeWin::ClassicGetMinimumWidgetSize(nsIFrame* aFrame, + uint8_t aWidgetType, + LayoutDeviceIntSize* aResult, + bool* aIsOverridable) +{ + (*aResult).width = (*aResult).height = 0; + *aIsOverridable = true; + switch (aWidgetType) { + case NS_THEME_RADIO: + case NS_THEME_CHECKBOX: + (*aResult).width = (*aResult).height = 13; + break; + case NS_THEME_MENUCHECKBOX: + case NS_THEME_MENURADIO: + case NS_THEME_MENUARROW: + (*aResult).width = ::GetSystemMetrics(SM_CXMENUCHECK); + (*aResult).height = ::GetSystemMetrics(SM_CYMENUCHECK); + break; + case NS_THEME_SPINNER_UPBUTTON: + case NS_THEME_SPINNER_DOWNBUTTON: + (*aResult).width = ::GetSystemMetrics(SM_CXVSCROLL); + (*aResult).height = 8; // No good metrics available for this + *aIsOverridable = false; + break; + case NS_THEME_SCROLLBARBUTTON_UP: + case NS_THEME_SCROLLBARBUTTON_DOWN: + (*aResult).width = ::GetSystemMetrics(SM_CXVSCROLL); + (*aResult).height = ::GetSystemMetrics(SM_CYVSCROLL); + *aIsOverridable = false; + break; + case NS_THEME_SCROLLBARBUTTON_LEFT: + case NS_THEME_SCROLLBARBUTTON_RIGHT: + (*aResult).width = ::GetSystemMetrics(SM_CXHSCROLL); + (*aResult).height = ::GetSystemMetrics(SM_CYHSCROLL); + *aIsOverridable = false; + break; + case NS_THEME_SCROLLBAR_VERTICAL: + // XXX HACK We should be able to have a minimum height for the scrollbar + // track. However, this causes problems when uncollapsing a scrollbar + // inside a tree. See bug 201379 for details. + + // (*aResult).height = ::GetSystemMetrics(SM_CYVTHUMB) << 1; + break; + case NS_THEME_SCROLLBAR_NON_DISAPPEARING: + { + aResult->SizeTo(::GetSystemMetrics(SM_CXHSCROLL), + ::GetSystemMetrics(SM_CYVSCROLL)); + break; + } + case NS_THEME_RANGE_THUMB: { + if (IsRangeHorizontal(aFrame)) { + (*aResult).width = 12; + (*aResult).height = 20; + } else { + (*aResult).width = 20; + (*aResult).height = 12; + } + *aIsOverridable = false; + break; + } + case NS_THEME_SCALETHUMB_HORIZONTAL: + (*aResult).width = 12; + (*aResult).height = 20; + *aIsOverridable = false; + break; + case NS_THEME_SCALETHUMB_VERTICAL: + (*aResult).width = 20; + (*aResult).height = 12; + *aIsOverridable = false; + break; + case NS_THEME_MENULIST_BUTTON: + (*aResult).width = ::GetSystemMetrics(SM_CXVSCROLL); + break; + case NS_THEME_MENULIST: + case NS_THEME_BUTTON: + case NS_THEME_GROUPBOX: + case NS_THEME_LISTBOX: + case NS_THEME_TREEVIEW: + case NS_THEME_NUMBER_INPUT: + case NS_THEME_TEXTFIELD: + case NS_THEME_TEXTFIELD_MULTILINE: + case NS_THEME_MENULIST_TEXTFIELD: + case NS_THEME_STATUSBAR: + case NS_THEME_STATUSBARPANEL: + case NS_THEME_RESIZERPANEL: + case NS_THEME_PROGRESSCHUNK: + case NS_THEME_PROGRESSCHUNK_VERTICAL: + case NS_THEME_TOOLTIP: + case NS_THEME_PROGRESSBAR: + case NS_THEME_PROGRESSBAR_VERTICAL: + case NS_THEME_TAB: + case NS_THEME_TABPANEL: + case NS_THEME_TABPANELS: + // no minimum widget size + break; + case NS_THEME_RESIZER: { + NONCLIENTMETRICS nc; + nc.cbSize = sizeof(nc); + if (SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(nc), &nc, 0)) + (*aResult).width = (*aResult).height = abs(nc.lfStatusFont.lfHeight) + 4; + else + (*aResult).width = (*aResult).height = 15; + *aIsOverridable = false; + break; + case NS_THEME_SCROLLBARTHUMB_VERTICAL: + (*aResult).width = ::GetSystemMetrics(SM_CXVSCROLL); + (*aResult).height = ::GetSystemMetrics(SM_CYVTHUMB); + // Without theming, divide the thumb size by two in order to look more + // native + if (!GetTheme(aWidgetType)) + (*aResult).height >>= 1; + *aIsOverridable = false; + break; + case NS_THEME_SCROLLBARTHUMB_HORIZONTAL: + (*aResult).width = ::GetSystemMetrics(SM_CXHTHUMB); + (*aResult).height = ::GetSystemMetrics(SM_CYHSCROLL); + // Without theming, divide the thumb size by two in order to look more + // native + if (!GetTheme(aWidgetType)) + (*aResult).width >>= 1; + *aIsOverridable = false; + break; + case NS_THEME_SCROLLBAR_HORIZONTAL: + (*aResult).width = ::GetSystemMetrics(SM_CXHTHUMB) << 1; + break; + } + case NS_THEME_MENUSEPARATOR: + { + aResult->width = 0; + aResult->height = 10; + break; + } + + case NS_THEME_WINDOW_TITLEBAR_MAXIMIZED: + case NS_THEME_WINDOW_TITLEBAR: + aResult->height = GetSystemMetrics(SM_CYCAPTION); + aResult->height += GetSystemMetrics(SM_CYFRAME); + aResult->width = 0; + break; + case NS_THEME_WINDOW_FRAME_LEFT: + case NS_THEME_WINDOW_FRAME_RIGHT: + aResult->width = GetSystemMetrics(SM_CXFRAME); + aResult->height = 0; + break; + + case NS_THEME_WINDOW_FRAME_BOTTOM: + aResult->height = GetSystemMetrics(SM_CYFRAME); + aResult->width = 0; + break; + + case NS_THEME_WINDOW_BUTTON_CLOSE: + case NS_THEME_WINDOW_BUTTON_MINIMIZE: + case NS_THEME_WINDOW_BUTTON_MAXIMIZE: + case NS_THEME_WINDOW_BUTTON_RESTORE: + aResult->width = GetSystemMetrics(SM_CXSIZE); + aResult->height = GetSystemMetrics(SM_CYSIZE); + // XXX I have no idea why these caption metrics are always off, + // but they are. + aResult->width -= 2; + aResult->height -= 4; + if (aWidgetType == NS_THEME_WINDOW_BUTTON_MINIMIZE) { + AddPaddingRect(aResult, CAPTIONBUTTON_MINIMIZE); + } + else if (aWidgetType == NS_THEME_WINDOW_BUTTON_MAXIMIZE || + aWidgetType == NS_THEME_WINDOW_BUTTON_RESTORE) { + AddPaddingRect(aResult, CAPTIONBUTTON_RESTORE); + } + else if (aWidgetType == NS_THEME_WINDOW_BUTTON_CLOSE) { + AddPaddingRect(aResult, CAPTIONBUTTON_CLOSE); + } + break; + + default: + return NS_ERROR_FAILURE; + } + return NS_OK; +} + + +nsresult nsNativeThemeWin::ClassicGetThemePartAndState(nsIFrame* aFrame, uint8_t aWidgetType, + int32_t& aPart, int32_t& aState, bool& aFocused) +{ + aFocused = false; + switch (aWidgetType) { + case NS_THEME_BUTTON: { + EventStates contentState; + + aPart = DFC_BUTTON; + aState = DFCS_BUTTONPUSH; + aFocused = false; + + contentState = GetContentState(aFrame, aWidgetType); + if (IsDisabled(aFrame, contentState)) + aState |= DFCS_INACTIVE; + else if (IsOpenButton(aFrame)) + aState |= DFCS_PUSHED; + else if (IsCheckedButton(aFrame)) + aState |= DFCS_CHECKED; + else { + if (contentState.HasAllStates(NS_EVENT_STATE_ACTIVE | NS_EVENT_STATE_HOVER)) { + aState |= DFCS_PUSHED; + const nsStyleUserInterface *uiData = aFrame->StyleUserInterface(); + // The down state is flat if the button is focusable + if (uiData->mUserFocus == StyleUserFocus::Normal) { + if (!aFrame->GetContent()->IsHTMLElement()) + aState |= DFCS_FLAT; + + aFocused = true; + } + } + if (contentState.HasState(NS_EVENT_STATE_FOCUS) || + (aState == DFCS_BUTTONPUSH && IsDefaultButton(aFrame))) { + aFocused = true; + } + + } + + return NS_OK; + } + case NS_THEME_CHECKBOX: + case NS_THEME_RADIO: { + EventStates contentState; + aFocused = false; + + aPart = DFC_BUTTON; + aState = 0; + nsIContent* content = aFrame->GetContent(); + bool isCheckbox = (aWidgetType == NS_THEME_CHECKBOX); + bool isChecked = GetCheckedOrSelected(aFrame, !isCheckbox); + bool isIndeterminate = isCheckbox && GetIndeterminate(aFrame); + + if (isCheckbox) { + // indeterminate state takes precedence over checkedness. + if (isIndeterminate) { + aState = DFCS_BUTTON3STATE | DFCS_CHECKED; + } else { + aState = DFCS_BUTTONCHECK; + } + } else { + aState = DFCS_BUTTONRADIO; + } + if (isChecked) { + aState |= DFCS_CHECKED; + } + + contentState = GetContentState(aFrame, aWidgetType); + if (!content->IsXULElement() && + contentState.HasState(NS_EVENT_STATE_FOCUS)) { + aFocused = true; + } + + if (IsDisabled(aFrame, contentState)) { + aState |= DFCS_INACTIVE; + } else if (contentState.HasAllStates(NS_EVENT_STATE_ACTIVE | + NS_EVENT_STATE_HOVER)) { + aState |= DFCS_PUSHED; + } + + return NS_OK; + } + case NS_THEME_MENUITEM: + case NS_THEME_CHECKMENUITEM: + case NS_THEME_RADIOMENUITEM: { + bool isTopLevel = false; + bool isOpen = false; + nsMenuFrame *menuFrame = do_QueryFrame(aFrame); + EventStates eventState = GetContentState(aFrame, aWidgetType); + + // We indicate top-level-ness using aPart. 0 is a normal menu item, + // 1 is a top-level menu item. The state of the item is composed of + // DFCS_* flags only. + aPart = 0; + aState = 0; + + if (menuFrame) { + // If this is a real menu item, we should check if it is part of the + // main menu bar or not, and if it is a container, as these affect + // rendering. + isTopLevel = menuFrame->IsOnMenuBar(); + isOpen = menuFrame->IsOpen(); + } + + if (IsDisabled(aFrame, eventState)) + aState |= DFCS_INACTIVE; + + if (isTopLevel) { + aPart = 1; + if (isOpen) + aState |= DFCS_PUSHED; + } + + if (IsMenuActive(aFrame, aWidgetType)) + aState |= DFCS_HOT; + + return NS_OK; + } + case NS_THEME_MENUCHECKBOX: + case NS_THEME_MENURADIO: + case NS_THEME_MENUARROW: { + aState = 0; + EventStates eventState = GetContentState(aFrame, aWidgetType); + + if (IsDisabled(aFrame, eventState)) + aState |= DFCS_INACTIVE; + if (IsMenuActive(aFrame, aWidgetType)) + aState |= DFCS_HOT; + + if (aWidgetType == NS_THEME_MENUCHECKBOX || aWidgetType == NS_THEME_MENURADIO) { + if (IsCheckedButton(aFrame)) + aState |= DFCS_CHECKED; + } else if (IsFrameRTL(aFrame)) { + aState |= DFCS_RTL; + } + return NS_OK; + } + case NS_THEME_LISTBOX: + case NS_THEME_TREEVIEW: + case NS_THEME_NUMBER_INPUT: + case NS_THEME_FOCUS_OUTLINE: + case NS_THEME_TEXTFIELD: + case NS_THEME_TEXTFIELD_MULTILINE: + case NS_THEME_MENULIST: + case NS_THEME_MENULIST_TEXTFIELD: + case NS_THEME_RANGE: + case NS_THEME_RANGE_THUMB: + case NS_THEME_SCROLLBARTHUMB_VERTICAL: + case NS_THEME_SCROLLBARTHUMB_HORIZONTAL: + case NS_THEME_SCROLLBAR_VERTICAL: + case NS_THEME_SCROLLBAR_HORIZONTAL: + case NS_THEME_SCALE_HORIZONTAL: + case NS_THEME_SCALE_VERTICAL: + case NS_THEME_SCALETHUMB_HORIZONTAL: + case NS_THEME_SCALETHUMB_VERTICAL: + case NS_THEME_STATUSBAR: + case NS_THEME_STATUSBARPANEL: + case NS_THEME_RESIZERPANEL: + case NS_THEME_PROGRESSCHUNK: + case NS_THEME_PROGRESSCHUNK_VERTICAL: + case NS_THEME_TOOLTIP: + case NS_THEME_PROGRESSBAR: + case NS_THEME_PROGRESSBAR_VERTICAL: + case NS_THEME_TAB: + case NS_THEME_TABPANEL: + case NS_THEME_TABPANELS: + case NS_THEME_MENUBAR: + case NS_THEME_MENUPOPUP: + case NS_THEME_GROUPBOX: + // these don't use DrawFrameControl + return NS_OK; + case NS_THEME_MENULIST_BUTTON: { + + aPart = DFC_SCROLL; + aState = DFCS_SCROLLCOMBOBOX; + + nsIFrame* parentFrame = aFrame->GetParent(); + bool isHTML = IsHTMLContent(aFrame); + bool isMenulist = !isHTML && parentFrame->GetType() == nsGkAtoms::menuFrame; + bool isOpen = false; + + // HTML select and XUL menulist dropdown buttons get state from the parent. + if (isHTML || isMenulist) + aFrame = parentFrame; + + EventStates eventState = GetContentState(aFrame, aWidgetType); + + if (IsDisabled(aFrame, eventState)) { + aState |= DFCS_INACTIVE; + return NS_OK; + } + + if (isHTML) { + nsIComboboxControlFrame* ccf = do_QueryFrame(aFrame); + isOpen = (ccf && ccf->IsDroppedDownOrHasParentPopup()); + } + else + isOpen = IsOpenButton(aFrame); + + // XXX Button should look active until the mouse is released, but + // without making it look active when the popup is clicked. + if (isOpen && (isHTML || isMenulist)) + return NS_OK; + + // Dropdown button active state doesn't need :hover. + if (eventState.HasState(NS_EVENT_STATE_ACTIVE)) + aState |= DFCS_PUSHED | DFCS_FLAT; + + return NS_OK; + } + case NS_THEME_SCROLLBARBUTTON_UP: + case NS_THEME_SCROLLBARBUTTON_DOWN: + case NS_THEME_SCROLLBARBUTTON_LEFT: + case NS_THEME_SCROLLBARBUTTON_RIGHT: { + EventStates contentState = GetContentState(aFrame, aWidgetType); + + aPart = DFC_SCROLL; + switch (aWidgetType) { + case NS_THEME_SCROLLBARBUTTON_UP: + aState = DFCS_SCROLLUP; + break; + case NS_THEME_SCROLLBARBUTTON_DOWN: + aState = DFCS_SCROLLDOWN; + break; + case NS_THEME_SCROLLBARBUTTON_LEFT: + aState = DFCS_SCROLLLEFT; + break; + case NS_THEME_SCROLLBARBUTTON_RIGHT: + aState = DFCS_SCROLLRIGHT; + break; + } + + if (IsDisabled(aFrame, contentState)) + aState |= DFCS_INACTIVE; + else { + if (contentState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE)) + aState |= DFCS_PUSHED | DFCS_FLAT; + } + + return NS_OK; + } + case NS_THEME_SPINNER_UPBUTTON: + case NS_THEME_SPINNER_DOWNBUTTON: { + EventStates contentState = GetContentState(aFrame, aWidgetType); + + aPart = DFC_SCROLL; + switch (aWidgetType) { + case NS_THEME_SPINNER_UPBUTTON: + aState = DFCS_SCROLLUP; + break; + case NS_THEME_SPINNER_DOWNBUTTON: + aState = DFCS_SCROLLDOWN; + break; + } + + if (IsDisabled(aFrame, contentState)) + aState |= DFCS_INACTIVE; + else { + if (contentState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE)) + aState |= DFCS_PUSHED; + } + + return NS_OK; + } + case NS_THEME_RESIZER: + aPart = DFC_SCROLL; + aState = (IsFrameRTL(aFrame)) ? + DFCS_SCROLLSIZEGRIPRIGHT : DFCS_SCROLLSIZEGRIP; + return NS_OK; + case NS_THEME_MENUSEPARATOR: + aPart = 0; + aState = 0; + return NS_OK; + case NS_THEME_WINDOW_TITLEBAR: + aPart = mozilla::widget::themeconst::WP_CAPTION; + aState = GetTopLevelWindowActiveState(aFrame); + return NS_OK; + case NS_THEME_WINDOW_TITLEBAR_MAXIMIZED: + aPart = mozilla::widget::themeconst::WP_MAXCAPTION; + aState = GetTopLevelWindowActiveState(aFrame); + return NS_OK; + case NS_THEME_WINDOW_FRAME_LEFT: + aPart = mozilla::widget::themeconst::WP_FRAMELEFT; + aState = GetTopLevelWindowActiveState(aFrame); + return NS_OK; + case NS_THEME_WINDOW_FRAME_RIGHT: + aPart = mozilla::widget::themeconst::WP_FRAMERIGHT; + aState = GetTopLevelWindowActiveState(aFrame); + return NS_OK; + case NS_THEME_WINDOW_FRAME_BOTTOM: + aPart = mozilla::widget::themeconst::WP_FRAMEBOTTOM; + aState = GetTopLevelWindowActiveState(aFrame); + return NS_OK; + case NS_THEME_WINDOW_BUTTON_CLOSE: + aPart = DFC_CAPTION; + aState = DFCS_CAPTIONCLOSE | + GetClassicWindowFrameButtonState(GetContentState(aFrame, + aWidgetType)); + return NS_OK; + case NS_THEME_WINDOW_BUTTON_MINIMIZE: + aPart = DFC_CAPTION; + aState = DFCS_CAPTIONMIN | + GetClassicWindowFrameButtonState(GetContentState(aFrame, + aWidgetType)); + return NS_OK; + case NS_THEME_WINDOW_BUTTON_MAXIMIZE: + aPart = DFC_CAPTION; + aState = DFCS_CAPTIONMAX | + GetClassicWindowFrameButtonState(GetContentState(aFrame, + aWidgetType)); + return NS_OK; + case NS_THEME_WINDOW_BUTTON_RESTORE: + aPart = DFC_CAPTION; + aState = DFCS_CAPTIONRESTORE | + GetClassicWindowFrameButtonState(GetContentState(aFrame, + aWidgetType)); + return NS_OK; + } + return NS_ERROR_FAILURE; +} + +// Draw classic Windows tab +// (no system API for this, but DrawEdge can draw all the parts of a tab) +static void DrawTab(HDC hdc, const RECT& R, int32_t aPosition, bool aSelected, + bool aDrawLeft, bool aDrawRight) +{ + int32_t leftFlag, topFlag, rightFlag, lightFlag, shadeFlag; + RECT topRect, sideRect, bottomRect, lightRect, shadeRect; + int32_t selectedOffset, lOffset, rOffset; + + selectedOffset = aSelected ? 1 : 0; + lOffset = aDrawLeft ? 2 : 0; + rOffset = aDrawRight ? 2 : 0; + + // Get info for tab orientation/position (Left, Top, Right, Bottom) + switch (aPosition) { + case BF_LEFT: + leftFlag = BF_TOP; topFlag = BF_LEFT; + rightFlag = BF_BOTTOM; + lightFlag = BF_DIAGONAL_ENDTOPRIGHT; + shadeFlag = BF_DIAGONAL_ENDBOTTOMRIGHT; + + ::SetRect(&topRect, R.left, R.top+lOffset, R.right, R.bottom-rOffset); + ::SetRect(&sideRect, R.left+2, R.top, R.right-2+selectedOffset, R.bottom); + ::SetRect(&bottomRect, R.right-2, R.top, R.right, R.bottom); + ::SetRect(&lightRect, R.left, R.top, R.left+3, R.top+3); + ::SetRect(&shadeRect, R.left+1, R.bottom-2, R.left+2, R.bottom-1); + break; + case BF_TOP: + leftFlag = BF_LEFT; topFlag = BF_TOP; + rightFlag = BF_RIGHT; + lightFlag = BF_DIAGONAL_ENDTOPRIGHT; + shadeFlag = BF_DIAGONAL_ENDBOTTOMRIGHT; + + ::SetRect(&topRect, R.left+lOffset, R.top, R.right-rOffset, R.bottom); + ::SetRect(&sideRect, R.left, R.top+2, R.right, R.bottom-1+selectedOffset); + ::SetRect(&bottomRect, R.left, R.bottom-1, R.right, R.bottom); + ::SetRect(&lightRect, R.left, R.top, R.left+3, R.top+3); + ::SetRect(&shadeRect, R.right-2, R.top+1, R.right-1, R.top+2); + break; + case BF_RIGHT: + leftFlag = BF_TOP; topFlag = BF_RIGHT; + rightFlag = BF_BOTTOM; + lightFlag = BF_DIAGONAL_ENDTOPLEFT; + shadeFlag = BF_DIAGONAL_ENDBOTTOMLEFT; + + ::SetRect(&topRect, R.left, R.top+lOffset, R.right, R.bottom-rOffset); + ::SetRect(&sideRect, R.left+2-selectedOffset, R.top, R.right-2, R.bottom); + ::SetRect(&bottomRect, R.left, R.top, R.left+2, R.bottom); + ::SetRect(&lightRect, R.right-3, R.top, R.right-1, R.top+2); + ::SetRect(&shadeRect, R.right-2, R.bottom-3, R.right, R.bottom-1); + break; + case BF_BOTTOM: + leftFlag = BF_LEFT; topFlag = BF_BOTTOM; + rightFlag = BF_RIGHT; + lightFlag = BF_DIAGONAL_ENDTOPLEFT; + shadeFlag = BF_DIAGONAL_ENDBOTTOMLEFT; + + ::SetRect(&topRect, R.left+lOffset, R.top, R.right-rOffset, R.bottom); + ::SetRect(&sideRect, R.left, R.top+2-selectedOffset, R.right, R.bottom-2); + ::SetRect(&bottomRect, R.left, R.top, R.right, R.top+2); + ::SetRect(&lightRect, R.left, R.bottom-3, R.left+2, R.bottom-1); + ::SetRect(&shadeRect, R.right-2, R.bottom-3, R.right, R.bottom-1); + break; + default: + MOZ_CRASH(); + } + + // Background + ::FillRect(hdc, &R, (HBRUSH) (COLOR_3DFACE+1) ); + + // Tab "Top" + ::DrawEdge(hdc, &topRect, EDGE_RAISED, BF_SOFT | topFlag); + + // Tab "Bottom" + if (!aSelected) + ::DrawEdge(hdc, &bottomRect, EDGE_RAISED, BF_SOFT | topFlag); + + // Tab "Sides" + if (!aDrawLeft) + leftFlag = 0; + if (!aDrawRight) + rightFlag = 0; + ::DrawEdge(hdc, &sideRect, EDGE_RAISED, BF_SOFT | leftFlag | rightFlag); + + // Tab Diagonal Corners + if (aDrawLeft) + ::DrawEdge(hdc, &lightRect, EDGE_RAISED, BF_SOFT | lightFlag); + + if (aDrawRight) + ::DrawEdge(hdc, &shadeRect, EDGE_RAISED, BF_SOFT | shadeFlag); +} + +static void DrawMenuImage(HDC hdc, const RECT& rc, int32_t aComponent, uint32_t aColor) +{ + // This procedure creates a memory bitmap to contain the check mark, draws + // it into the bitmap (it is a mask image), then composes it onto the menu + // item in appropriate colors. + HDC hMemoryDC = ::CreateCompatibleDC(hdc); + if (hMemoryDC) { + // XXXjgr We should ideally be caching these, but we wont be notified when + // they change currently, so we can't do so easily. Same for the bitmap. + int checkW = ::GetSystemMetrics(SM_CXMENUCHECK); + int checkH = ::GetSystemMetrics(SM_CYMENUCHECK); + + HBITMAP hMonoBitmap = ::CreateBitmap(checkW, checkH, 1, 1, nullptr); + if (hMonoBitmap) { + + HBITMAP hPrevBitmap = (HBITMAP) ::SelectObject(hMemoryDC, hMonoBitmap); + if (hPrevBitmap) { + + // XXXjgr This will go pear-shaped if the image is bigger than the + // provided rect. What should we do? + RECT imgRect = { 0, 0, checkW, checkH }; + POINT imgPos = { + rc.left + (rc.right - rc.left - checkW) / 2, + rc.top + (rc.bottom - rc.top - checkH) / 2 + }; + + // XXXzeniko Windows renders these 1px lower than you'd expect + if (aComponent == DFCS_MENUCHECK || aComponent == DFCS_MENUBULLET) + imgPos.y++; + + ::DrawFrameControl(hMemoryDC, &imgRect, DFC_MENU, aComponent); + COLORREF oldTextCol = ::SetTextColor(hdc, 0x00000000); + COLORREF oldBackCol = ::SetBkColor(hdc, 0x00FFFFFF); + ::BitBlt(hdc, imgPos.x, imgPos.y, checkW, checkH, hMemoryDC, 0, 0, SRCAND); + ::SetTextColor(hdc, ::GetSysColor(aColor)); + ::SetBkColor(hdc, 0x00000000); + ::BitBlt(hdc, imgPos.x, imgPos.y, checkW, checkH, hMemoryDC, 0, 0, SRCPAINT); + ::SetTextColor(hdc, oldTextCol); + ::SetBkColor(hdc, oldBackCol); + ::SelectObject(hMemoryDC, hPrevBitmap); + } + ::DeleteObject(hMonoBitmap); + } + ::DeleteDC(hMemoryDC); + } +} + +void nsNativeThemeWin::DrawCheckedRect(HDC hdc, const RECT& rc, int32_t fore, int32_t back, + HBRUSH defaultBack) +{ + static WORD patBits[8] = { + 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55 + }; + + HBITMAP patBmp = ::CreateBitmap(8, 8, 1, 1, patBits); + if (patBmp) { + HBRUSH brush = (HBRUSH) ::CreatePatternBrush(patBmp); + if (brush) { + COLORREF oldForeColor = ::SetTextColor(hdc, ::GetSysColor(fore)); + COLORREF oldBackColor = ::SetBkColor(hdc, ::GetSysColor(back)); + POINT vpOrg; + + ::UnrealizeObject(brush); + ::GetViewportOrgEx(hdc, &vpOrg); + ::SetBrushOrgEx(hdc, vpOrg.x + rc.left, vpOrg.y + rc.top, nullptr); + HBRUSH oldBrush = (HBRUSH) ::SelectObject(hdc, brush); + ::FillRect(hdc, &rc, brush); + ::SetTextColor(hdc, oldForeColor); + ::SetBkColor(hdc, oldBackColor); + ::SelectObject(hdc, oldBrush); + ::DeleteObject(brush); + } + else + ::FillRect(hdc, &rc, defaultBack); + + ::DeleteObject(patBmp); + } +} + +nsresult nsNativeThemeWin::ClassicDrawWidgetBackground(nsRenderingContext* aContext, + nsIFrame* aFrame, + uint8_t aWidgetType, + const nsRect& aRect, + const nsRect& aDirtyRect) +{ + int32_t part, state; + bool focused; + nsresult rv; + rv = ClassicGetThemePartAndState(aFrame, aWidgetType, part, state, focused); + if (NS_FAILED(rv)) + return rv; + + if (AssumeThemePartAndStateAreTransparent(part, state)) { + return NS_OK; + } + + gfxFloat p2a = gfxFloat(aFrame->PresContext()->AppUnitsPerDevPixel()); + RECT widgetRect; + gfxRect tr(aRect.x, aRect.y, aRect.width, aRect.height), + dr(aDirtyRect.x, aDirtyRect.y, aDirtyRect.width, aDirtyRect.height); + + tr.ScaleInverse(p2a); + dr.ScaleInverse(p2a); + + RefPtr<gfxContext> ctx = aContext->ThebesContext(); + + gfxWindowsNativeDrawing nativeDrawing(ctx, dr, GetWidgetNativeDrawingFlags(aWidgetType)); + +RENDER_AGAIN: + + HDC hdc = nativeDrawing.BeginNativeDrawing(); + if (!hdc) + return NS_ERROR_FAILURE; + + nativeDrawing.TransformToNativeRect(tr, widgetRect); + + rv = NS_OK; + switch (aWidgetType) { + // Draw button + case NS_THEME_BUTTON: { + if (focused) { + // draw dark button focus border first + HBRUSH brush; + brush = ::GetSysColorBrush(COLOR_3DDKSHADOW); + if (brush) + ::FrameRect(hdc, &widgetRect, brush); + InflateRect(&widgetRect, -1, -1); + } + // fall-through... + } + // Draw controls supported by DrawFrameControl + case NS_THEME_CHECKBOX: + case NS_THEME_RADIO: + case NS_THEME_SCROLLBARBUTTON_UP: + case NS_THEME_SCROLLBARBUTTON_DOWN: + case NS_THEME_SCROLLBARBUTTON_LEFT: + case NS_THEME_SCROLLBARBUTTON_RIGHT: + case NS_THEME_SPINNER_UPBUTTON: + case NS_THEME_SPINNER_DOWNBUTTON: + case NS_THEME_MENULIST_BUTTON: + case NS_THEME_RESIZER: { + int32_t oldTA; + // setup DC to make DrawFrameControl draw correctly + oldTA = ::SetTextAlign(hdc, TA_TOP | TA_LEFT | TA_NOUPDATECP); + ::DrawFrameControl(hdc, &widgetRect, part, state); + ::SetTextAlign(hdc, oldTA); + break; + } + // Draw controls with 2px 3D inset border + case NS_THEME_NUMBER_INPUT: + case NS_THEME_TEXTFIELD: + case NS_THEME_TEXTFIELD_MULTILINE: + case NS_THEME_LISTBOX: + case NS_THEME_MENULIST: + case NS_THEME_MENULIST_TEXTFIELD: { + // Draw inset edge + ::DrawEdge(hdc, &widgetRect, EDGE_SUNKEN, BF_RECT | BF_ADJUST); + EventStates eventState = GetContentState(aFrame, aWidgetType); + + // Fill in background + if (IsDisabled(aFrame, eventState) || + (aFrame->GetContent()->IsXULElement() && + IsReadOnly(aFrame))) + ::FillRect(hdc, &widgetRect, (HBRUSH) (COLOR_BTNFACE+1)); + else + ::FillRect(hdc, &widgetRect, (HBRUSH) (COLOR_WINDOW+1)); + + break; + } + case NS_THEME_TREEVIEW: { + // Draw inset edge + ::DrawEdge(hdc, &widgetRect, EDGE_SUNKEN, BF_RECT | BF_ADJUST); + + // Fill in window color background + ::FillRect(hdc, &widgetRect, (HBRUSH) (COLOR_WINDOW+1)); + + break; + } + // Draw ToolTip background + case NS_THEME_TOOLTIP: + ::FrameRect(hdc, &widgetRect, ::GetSysColorBrush(COLOR_WINDOWFRAME)); + InflateRect(&widgetRect, -1, -1); + ::FillRect(hdc, &widgetRect, ::GetSysColorBrush(COLOR_INFOBK)); + + break; + case NS_THEME_GROUPBOX: + ::DrawEdge(hdc, &widgetRect, EDGE_ETCHED, BF_RECT | BF_ADJUST); + ::FillRect(hdc, &widgetRect, (HBRUSH) (COLOR_BTNFACE+1)); + break; + // Draw 3D face background controls + case NS_THEME_PROGRESSBAR: + case NS_THEME_PROGRESSBAR_VERTICAL: + // Draw 3D border + ::DrawEdge(hdc, &widgetRect, BDR_SUNKENOUTER, BF_RECT | BF_MIDDLE); + InflateRect(&widgetRect, -1, -1); + // fall through + case NS_THEME_TABPANEL: + case NS_THEME_STATUSBAR: + case NS_THEME_RESIZERPANEL: { + ::FillRect(hdc, &widgetRect, (HBRUSH) (COLOR_BTNFACE+1)); + + break; + } + // Draw 3D inset statusbar panel + case NS_THEME_STATUSBARPANEL: { + if (aFrame->GetNextSibling()) + widgetRect.right -= 2; // space between sibling status panels + + ::DrawEdge(hdc, &widgetRect, BDR_SUNKENOUTER, BF_RECT | BF_MIDDLE); + + break; + } + // Draw scrollbar thumb + case NS_THEME_SCROLLBARTHUMB_VERTICAL: + case NS_THEME_SCROLLBARTHUMB_HORIZONTAL: + ::DrawEdge(hdc, &widgetRect, EDGE_RAISED, BF_RECT | BF_MIDDLE); + + break; + case NS_THEME_RANGE_THUMB: + case NS_THEME_SCALETHUMB_VERTICAL: + case NS_THEME_SCALETHUMB_HORIZONTAL: { + EventStates eventState = GetContentState(aFrame, aWidgetType); + + ::DrawEdge(hdc, &widgetRect, EDGE_RAISED, BF_RECT | BF_SOFT | BF_MIDDLE | BF_ADJUST); + if (IsDisabled(aFrame, eventState)) { + DrawCheckedRect(hdc, widgetRect, COLOR_3DFACE, COLOR_3DHILIGHT, + (HBRUSH) COLOR_3DHILIGHT); + } + + break; + } + // Draw scrollbar track background + case NS_THEME_SCROLLBAR_VERTICAL: + case NS_THEME_SCROLLBAR_HORIZONTAL: { + + // Windows fills in the scrollbar track differently + // depending on whether these are equal + DWORD color3D, colorScrollbar, colorWindow; + + color3D = ::GetSysColor(COLOR_3DFACE); + colorWindow = ::GetSysColor(COLOR_WINDOW); + colorScrollbar = ::GetSysColor(COLOR_SCROLLBAR); + + if ((color3D != colorScrollbar) && (colorWindow != colorScrollbar)) + // Use solid brush + ::FillRect(hdc, &widgetRect, (HBRUSH) (COLOR_SCROLLBAR+1)); + else + { + DrawCheckedRect(hdc, widgetRect, COLOR_3DHILIGHT, COLOR_3DFACE, + (HBRUSH) COLOR_SCROLLBAR+1); + } + // XXX should invert the part of the track being clicked here + // but the track is never :active + + break; + } + // Draw scale track background + case NS_THEME_RANGE: + case NS_THEME_SCALE_VERTICAL: + case NS_THEME_SCALE_HORIZONTAL: { + const int32_t trackWidth = 4; + // When rounding is necessary, we round the position of the track + // away from the chevron of the thumb to make it look better. + if (aWidgetType == NS_THEME_SCALE_HORIZONTAL || + (aWidgetType == NS_THEME_RANGE && IsRangeHorizontal(aFrame))) { + widgetRect.top += (widgetRect.bottom - widgetRect.top - trackWidth) / 2; + widgetRect.bottom = widgetRect.top + trackWidth; + } + else { + if (!IsFrameRTL(aFrame)) { + widgetRect.left += (widgetRect.right - widgetRect.left - trackWidth) / 2; + widgetRect.right = widgetRect.left + trackWidth; + } else { + widgetRect.right -= (widgetRect.right - widgetRect.left - trackWidth) / 2; + widgetRect.left = widgetRect.right - trackWidth; + } + } + + ::DrawEdge(hdc, &widgetRect, EDGE_SUNKEN, BF_RECT | BF_ADJUST); + ::FillRect(hdc, &widgetRect, (HBRUSH) GetStockObject(GRAY_BRUSH)); + + break; + } + case NS_THEME_PROGRESSCHUNK_VERTICAL: + ::FillRect(hdc, &widgetRect, (HBRUSH) (COLOR_HIGHLIGHT+1)); + break; + + case NS_THEME_PROGRESSCHUNK: { + nsIFrame* stateFrame = aFrame->GetParent(); + EventStates eventStates = GetContentState(stateFrame, aWidgetType); + + bool indeterminate = IsIndeterminateProgress(stateFrame, eventStates); + bool vertical = IsVerticalProgress(stateFrame) || + aWidgetType == NS_THEME_PROGRESSCHUNK_VERTICAL; + + nsIContent* content = aFrame->GetContent(); + if (!indeterminate || !content) { + ::FillRect(hdc, &widgetRect, (HBRUSH) (COLOR_HIGHLIGHT+1)); + break; + } + + RECT overlayRect = + CalculateProgressOverlayRect(aFrame, &widgetRect, vertical, + indeterminate, true); + + ::FillRect(hdc, &overlayRect, (HBRUSH) (COLOR_HIGHLIGHT+1)); + + if (!QueueAnimatedContentForRefresh(aFrame->GetContent(), 30)) { + NS_WARNING("unable to animate progress widget!"); + } + break; + } + + // Draw Tab + case NS_THEME_TAB: { + DrawTab(hdc, widgetRect, + IsBottomTab(aFrame) ? BF_BOTTOM : BF_TOP, + IsSelectedTab(aFrame), + !IsRightToSelectedTab(aFrame), + !IsLeftToSelectedTab(aFrame)); + + break; + } + case NS_THEME_TABPANELS: + ::DrawEdge(hdc, &widgetRect, EDGE_RAISED, BF_SOFT | BF_MIDDLE | + BF_LEFT | BF_RIGHT | BF_BOTTOM); + + break; + case NS_THEME_MENUBAR: + break; + case NS_THEME_MENUPOPUP: + NS_ASSERTION(nsUXThemeData::sFlatMenus, "Classic menus are styled entirely through CSS"); + ::FillRect(hdc, &widgetRect, (HBRUSH) (COLOR_MENU+1)); + ::FrameRect(hdc, &widgetRect, ::GetSysColorBrush(COLOR_BTNSHADOW)); + break; + case NS_THEME_MENUITEM: + case NS_THEME_CHECKMENUITEM: + case NS_THEME_RADIOMENUITEM: + // part == 0 for normal items + // part == 1 for top-level menu items + if (nsUXThemeData::sFlatMenus) { + // Not disabled and hot/pushed. + if ((state & (DFCS_HOT | DFCS_PUSHED)) != 0) { + ::FillRect(hdc, &widgetRect, (HBRUSH) (COLOR_MENUHILIGHT+1)); + ::FrameRect(hdc, &widgetRect, ::GetSysColorBrush(COLOR_HIGHLIGHT)); + } + } else { + if (part == 1) { + if ((state & DFCS_INACTIVE) == 0) { + if ((state & DFCS_PUSHED) != 0) { + ::DrawEdge(hdc, &widgetRect, BDR_SUNKENOUTER, BF_RECT); + } else if ((state & DFCS_HOT) != 0) { + ::DrawEdge(hdc, &widgetRect, BDR_RAISEDINNER, BF_RECT); + } + } + } else { + if ((state & (DFCS_HOT | DFCS_PUSHED)) != 0) { + ::FillRect(hdc, &widgetRect, (HBRUSH) (COLOR_HIGHLIGHT+1)); + } + } + } + break; + case NS_THEME_MENUCHECKBOX: + case NS_THEME_MENURADIO: + if (!(state & DFCS_CHECKED)) + break; // nothin' to do + case NS_THEME_MENUARROW: { + uint32_t color = COLOR_MENUTEXT; + if ((state & DFCS_INACTIVE)) + color = COLOR_GRAYTEXT; + else if ((state & DFCS_HOT)) + color = COLOR_HIGHLIGHTTEXT; + + if (aWidgetType == NS_THEME_MENUCHECKBOX) + DrawMenuImage(hdc, widgetRect, DFCS_MENUCHECK, color); + else if (aWidgetType == NS_THEME_MENURADIO) + DrawMenuImage(hdc, widgetRect, DFCS_MENUBULLET, color); + else if (aWidgetType == NS_THEME_MENUARROW) + DrawMenuImage(hdc, widgetRect, + (state & DFCS_RTL) ? DFCS_MENUARROWRIGHT : DFCS_MENUARROW, + color); + break; + } + case NS_THEME_MENUSEPARATOR: { + // separators are offset by a bit (see menu.css) + widgetRect.left++; + widgetRect.right--; + + // This magic number is brought to you by the value in menu.css + widgetRect.top += 4; + // Our rectangles are 1 pixel high (see border size in menu.css) + widgetRect.bottom = widgetRect.top+1; + ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_3DSHADOW+1)); + widgetRect.top++; + widgetRect.bottom++; + ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_3DHILIGHT+1)); + break; + } + + case NS_THEME_WINDOW_TITLEBAR: + case NS_THEME_WINDOW_TITLEBAR_MAXIMIZED: + { + RECT rect = widgetRect; + int32_t offset = GetSystemMetrics(SM_CXFRAME); + + // first fill the area to the color of the window background + FillRect(hdc, &rect, (HBRUSH)(COLOR_3DFACE+1)); + + // inset the caption area so it doesn't overflow. + rect.top += offset; + // if enabled, draw a gradient titlebar background, otherwise + // fill with a solid color. + BOOL bFlag = TRUE; + SystemParametersInfo(SPI_GETGRADIENTCAPTIONS, 0, &bFlag, 0); + if (!bFlag) { + if (state == mozilla::widget::themeconst::FS_ACTIVE) + FillRect(hdc, &rect, (HBRUSH)(COLOR_ACTIVECAPTION+1)); + else + FillRect(hdc, &rect, (HBRUSH)(COLOR_INACTIVECAPTION+1)); + } else { + DWORD startColor, endColor; + if (state == mozilla::widget::themeconst::FS_ACTIVE) { + startColor = GetSysColor(COLOR_ACTIVECAPTION); + endColor = GetSysColor(COLOR_GRADIENTACTIVECAPTION); + } else { + startColor = GetSysColor(COLOR_INACTIVECAPTION); + endColor = GetSysColor(COLOR_GRADIENTINACTIVECAPTION); + } + + TRIVERTEX vertex[2]; + vertex[0].x = rect.left; + vertex[0].y = rect.top; + vertex[0].Red = GetRValue(startColor) << 8; + vertex[0].Green = GetGValue(startColor) << 8; + vertex[0].Blue = GetBValue(startColor) << 8; + vertex[0].Alpha = 0; + + vertex[1].x = rect.right; + vertex[1].y = rect.bottom; + vertex[1].Red = GetRValue(endColor) << 8; + vertex[1].Green = GetGValue(endColor) << 8; + vertex[1].Blue = GetBValue(endColor) << 8; + vertex[1].Alpha = 0; + + GRADIENT_RECT gRect; + gRect.UpperLeft = 0; + gRect.LowerRight = 1; + // available on win2k & up + GradientFill(hdc, vertex, 2, &gRect, 1, GRADIENT_FILL_RECT_H); + } + + if (aWidgetType == NS_THEME_WINDOW_TITLEBAR) { + // frame things up with a top raised border. + DrawEdge(hdc, &widgetRect, EDGE_RAISED, BF_TOP); + } + break; + } + + case NS_THEME_WINDOW_FRAME_LEFT: + DrawEdge(hdc, &widgetRect, EDGE_RAISED, BF_LEFT); + break; + + case NS_THEME_WINDOW_FRAME_RIGHT: + DrawEdge(hdc, &widgetRect, EDGE_RAISED, BF_RIGHT); + break; + + case NS_THEME_WINDOW_FRAME_BOTTOM: + DrawEdge(hdc, &widgetRect, EDGE_RAISED, BF_BOTTOM); + break; + + case NS_THEME_WINDOW_BUTTON_CLOSE: + case NS_THEME_WINDOW_BUTTON_MINIMIZE: + case NS_THEME_WINDOW_BUTTON_MAXIMIZE: + case NS_THEME_WINDOW_BUTTON_RESTORE: + { + if (aWidgetType == NS_THEME_WINDOW_BUTTON_MINIMIZE) { + OffsetBackgroundRect(widgetRect, CAPTIONBUTTON_MINIMIZE); + } + else if (aWidgetType == NS_THEME_WINDOW_BUTTON_MAXIMIZE || + aWidgetType == NS_THEME_WINDOW_BUTTON_RESTORE) { + OffsetBackgroundRect(widgetRect, CAPTIONBUTTON_RESTORE); + } + else if (aWidgetType == NS_THEME_WINDOW_BUTTON_CLOSE) { + OffsetBackgroundRect(widgetRect, CAPTIONBUTTON_CLOSE); + } + int32_t oldTA = SetTextAlign(hdc, TA_TOP | TA_LEFT | TA_NOUPDATECP); + DrawFrameControl(hdc, &widgetRect, part, state); + SetTextAlign(hdc, oldTA); + break; + } + + default: + rv = NS_ERROR_FAILURE; + break; + } + + nativeDrawing.EndNativeDrawing(); + + if (NS_FAILED(rv)) + return rv; + + if (nativeDrawing.ShouldRenderAgain()) + goto RENDER_AGAIN; + + nativeDrawing.PaintToContext(); + + return rv; +} + +uint32_t +nsNativeThemeWin::GetWidgetNativeDrawingFlags(uint8_t aWidgetType) +{ + switch (aWidgetType) { + case NS_THEME_BUTTON: + case NS_THEME_NUMBER_INPUT: + case NS_THEME_FOCUS_OUTLINE: + case NS_THEME_TEXTFIELD: + case NS_THEME_TEXTFIELD_MULTILINE: + + case NS_THEME_MENULIST: + case NS_THEME_MENULIST_TEXTFIELD: + return + gfxWindowsNativeDrawing::CANNOT_DRAW_TO_COLOR_ALPHA | + gfxWindowsNativeDrawing::CAN_AXIS_ALIGNED_SCALE | + gfxWindowsNativeDrawing::CANNOT_COMPLEX_TRANSFORM; + + // need to check these others + case NS_THEME_RANGE: + case NS_THEME_RANGE_THUMB: + case NS_THEME_SCROLLBARBUTTON_UP: + case NS_THEME_SCROLLBARBUTTON_DOWN: + case NS_THEME_SCROLLBARBUTTON_LEFT: + case NS_THEME_SCROLLBARBUTTON_RIGHT: + case NS_THEME_SCROLLBARTHUMB_VERTICAL: + case NS_THEME_SCROLLBARTHUMB_HORIZONTAL: + case NS_THEME_SCROLLBAR_VERTICAL: + case NS_THEME_SCROLLBAR_HORIZONTAL: + case NS_THEME_SCALE_HORIZONTAL: + case NS_THEME_SCALE_VERTICAL: + case NS_THEME_SCALETHUMB_HORIZONTAL: + case NS_THEME_SCALETHUMB_VERTICAL: + case NS_THEME_SPINNER_UPBUTTON: + case NS_THEME_SPINNER_DOWNBUTTON: + case NS_THEME_LISTBOX: + case NS_THEME_TREEVIEW: + case NS_THEME_TOOLTIP: + case NS_THEME_STATUSBAR: + case NS_THEME_STATUSBARPANEL: + case NS_THEME_RESIZERPANEL: + case NS_THEME_RESIZER: + case NS_THEME_PROGRESSBAR: + case NS_THEME_PROGRESSBAR_VERTICAL: + case NS_THEME_PROGRESSCHUNK: + case NS_THEME_PROGRESSCHUNK_VERTICAL: + case NS_THEME_TAB: + case NS_THEME_TABPANEL: + case NS_THEME_TABPANELS: + case NS_THEME_MENUBAR: + case NS_THEME_MENUPOPUP: + case NS_THEME_MENUITEM: + break; + + // the dropdown button /almost/ renders correctly with scaling, + // except that the graphic in the dropdown button (the downward arrow) + // doesn't get scaled up. + case NS_THEME_MENULIST_BUTTON: + // these are definitely no; they're all graphics that don't get scaled up + case NS_THEME_CHECKBOX: + case NS_THEME_RADIO: + case NS_THEME_GROUPBOX: + case NS_THEME_CHECKMENUITEM: + case NS_THEME_RADIOMENUITEM: + case NS_THEME_MENUCHECKBOX: + case NS_THEME_MENURADIO: + case NS_THEME_MENUARROW: + return + gfxWindowsNativeDrawing::CANNOT_DRAW_TO_COLOR_ALPHA | + gfxWindowsNativeDrawing::CANNOT_AXIS_ALIGNED_SCALE | + gfxWindowsNativeDrawing::CANNOT_COMPLEX_TRANSFORM; + } + + return + gfxWindowsNativeDrawing::CANNOT_DRAW_TO_COLOR_ALPHA | + gfxWindowsNativeDrawing::CANNOT_AXIS_ALIGNED_SCALE | + gfxWindowsNativeDrawing::CANNOT_COMPLEX_TRANSFORM; +} + +/////////////////////////////////////////// +// Creation Routine +/////////////////////////////////////////// + +// from nsWindow.cpp +extern bool gDisableNativeTheme; + +nsresult NS_NewNativeTheme(nsISupports *aOuter, REFNSIID aIID, void **aResult) +{ + if (gDisableNativeTheme) + return NS_ERROR_NO_INTERFACE; + + if (aOuter) + return NS_ERROR_NO_AGGREGATION; + + nsNativeThemeWin* theme = new nsNativeThemeWin(); + if (!theme) + return NS_ERROR_OUT_OF_MEMORY; + return theme->QueryInterface(aIID, aResult); +} diff --git a/widget/windows/nsNativeThemeWin.h b/widget/windows/nsNativeThemeWin.h new file mode 100644 index 0000000000..f206494447 --- /dev/null +++ b/widget/windows/nsNativeThemeWin.h @@ -0,0 +1,128 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsNativeThemeWin_h +#define nsNativeThemeWin_h + +#include "nsITheme.h" +#include "nsCOMPtr.h" +#include "nsIAtom.h" +#include "nsNativeTheme.h" +#include "gfxTypes.h" +#include <windows.h> +#include "mozilla/TimeStamp.h" +#include "nsSize.h" + +class nsNativeThemeWin : private nsNativeTheme, + public nsITheme { + virtual ~nsNativeThemeWin(); + +public: + typedef mozilla::TimeStamp TimeStamp; + typedef mozilla::TimeDuration TimeDuration; + + NS_DECL_ISUPPORTS_INHERITED + + // The nsITheme interface. + NS_IMETHOD DrawWidgetBackground(nsRenderingContext* aContext, + nsIFrame* aFrame, + uint8_t aWidgetType, + const nsRect& aRect, + const nsRect& aDirtyRect) override; + + NS_IMETHOD GetWidgetBorder(nsDeviceContext* aContext, + nsIFrame* aFrame, + uint8_t aWidgetType, + nsIntMargin* aResult) override; + + virtual bool GetWidgetPadding(nsDeviceContext* aContext, + nsIFrame* aFrame, + uint8_t aWidgetType, + nsIntMargin* aResult) override; + + virtual bool GetWidgetOverflow(nsDeviceContext* aContext, + nsIFrame* aFrame, + uint8_t aWidgetType, + nsRect* aOverflowRect) override; + + NS_IMETHOD GetMinimumWidgetSize(nsPresContext* aPresContext, nsIFrame* aFrame, + uint8_t aWidgetType, + mozilla::LayoutDeviceIntSize* aResult, + bool* aIsOverridable) override; + + virtual Transparency GetWidgetTransparency(nsIFrame* aFrame, uint8_t aWidgetType) override; + + NS_IMETHOD WidgetStateChanged(nsIFrame* aFrame, uint8_t aWidgetType, + nsIAtom* aAttribute, bool* aShouldRepaint, + const nsAttrValue* aOldValue) override; + + NS_IMETHOD ThemeChanged() override; + + bool ThemeSupportsWidget(nsPresContext* aPresContext, + nsIFrame* aFrame, + uint8_t aWidgetType) override; + + bool WidgetIsContainer(uint8_t aWidgetType) override; + + bool ThemeDrawsFocusForWidget(uint8_t aWidgetType) override; + + bool ThemeNeedsComboboxDropmarker() override; + + virtual bool WidgetAppearanceDependsOnWindowFocus(uint8_t aWidgetType) override; + + enum { + eThemeGeometryTypeWindowButtons = eThemeGeometryTypeUnknown + 1 + }; + virtual ThemeGeometryType ThemeGeometryTypeForWidget(nsIFrame* aFrame, + uint8_t aWidgetType) override; + + virtual bool ShouldHideScrollbars() override; + + nsNativeThemeWin(); + +protected: + HANDLE GetTheme(uint8_t aWidgetType); + nsresult GetThemePartAndState(nsIFrame* aFrame, uint8_t aWidgetType, + int32_t& aPart, int32_t& aState); + nsresult ClassicGetThemePartAndState(nsIFrame* aFrame, uint8_t aWidgetType, + int32_t& aPart, int32_t& aState, bool& aFocused); + nsresult ClassicDrawWidgetBackground(nsRenderingContext* aContext, + nsIFrame* aFrame, + uint8_t aWidgetType, + const nsRect& aRect, + const nsRect& aClipRect); + nsresult ClassicGetWidgetBorder(nsDeviceContext* aContext, + nsIFrame* aFrame, + uint8_t aWidgetType, + nsIntMargin* aResult); + bool ClassicGetWidgetPadding(nsDeviceContext* aContext, + nsIFrame* aFrame, + uint8_t aWidgetType, + nsIntMargin* aResult); + nsresult ClassicGetMinimumWidgetSize(nsIFrame* aFrame, uint8_t aWidgetType, + mozilla::LayoutDeviceIntSize* aResult, + bool* aIsOverridable); + bool ClassicThemeSupportsWidget(nsIFrame* aFrame, uint8_t aWidgetType); + void DrawCheckedRect(HDC hdc, const RECT& rc, int32_t fore, int32_t back, + HBRUSH defaultBack); + uint32_t GetWidgetNativeDrawingFlags(uint8_t aWidgetType); + int32_t StandardGetState(nsIFrame* aFrame, uint8_t aWidgetType, bool wantFocused); + bool IsMenuActive(nsIFrame* aFrame, uint8_t aWidgetType); + RECT CalculateProgressOverlayRect(nsIFrame* aFrame, RECT* aWidgetRect, + bool aIsVertical, bool aIsIndeterminate, + bool aIsClassic); + void DrawThemedProgressMeter(nsIFrame* aFrame, int aWidgetType, + HANDLE aTheme, HDC aHdc, + int aPart, int aState, + RECT* aWidgetRect, RECT* aClipRect, + gfxFloat aAppUnits); + +private: + TimeStamp mProgressDeterminateTimeStamp; + TimeStamp mProgressIndeterminateTimeStamp; +}; + +#endif diff --git a/widget/windows/nsPrintOptionsWin.cpp b/widget/windows/nsPrintOptionsWin.cpp new file mode 100644 index 0000000000..97b15fa3fb --- /dev/null +++ b/widget/windows/nsPrintOptionsWin.cpp @@ -0,0 +1,162 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "nsCOMPtr.h" +#include "nsPrintOptionsWin.h" +#include "nsPrintSettingsWin.h" +#include "nsPrintDialogUtil.h" + +#include "nsGfxCIID.h" +#include "nsIServiceManager.h" +#include "nsIWebBrowserPrint.h" +#include "nsWindowsHelpers.h" +#include "ipc/IPCMessageUtils.h" + +const char kPrinterEnumeratorContractID[] = "@mozilla.org/gfx/printerenumerator;1"; + +using namespace mozilla::embedding; + +/** --------------------------------------------------- + * See documentation in nsPrintOptionsWin.h + * @update 6/21/00 dwc + */ +nsPrintOptionsWin::nsPrintOptionsWin() +{ + +} + +/** --------------------------------------------------- + * See documentation in nsPrintOptionsImpl.h + * @update 6/21/00 dwc + */ +nsPrintOptionsWin::~nsPrintOptionsWin() +{ +} + +NS_IMETHODIMP +nsPrintOptionsWin::SerializeToPrintData(nsIPrintSettings* aSettings, + nsIWebBrowserPrint* aWBP, + PrintData* data) +{ + nsresult rv = nsPrintOptions::SerializeToPrintData(aSettings, aWBP, data); + NS_ENSURE_SUCCESS(rv, rv); + + // Windows wants this information for its print dialogs + if (aWBP) { + aWBP->GetIsFramesetDocument(&data->isFramesetDocument()); + aWBP->GetIsFramesetFrameSelected(&data->isFramesetFrameSelected()); + aWBP->GetIsIFrameSelected(&data->isIFrameSelected()); + aWBP->GetIsRangeSelection(&data->isRangeSelection()); + } + + nsCOMPtr<nsIPrintSettingsWin> psWin = do_QueryInterface(aSettings); + if (!psWin) { + return NS_ERROR_FAILURE; + } + + char16_t* deviceName; + char16_t* driverName; + + psWin->GetDeviceName(&deviceName); + psWin->GetDriverName(&driverName); + + data->deviceName().Assign(deviceName); + data->driverName().Assign(driverName); + + free(deviceName); + free(driverName); + + // When creating the print dialog on Windows, we only need to send certain + // print settings information from the parent to the child not vice versa. + if (XRE_IsParentProcess()) { + psWin->GetPrintableWidthInInches(&data->printableWidthInInches()); + psWin->GetPrintableHeightInInches(&data->printableHeightInInches()); + + // A DEVMODE can actually be of arbitrary size. If it turns out that it'll + // make our IPC message larger than the limit, then we'll error out. + LPDEVMODEW devModeRaw; + psWin->GetDevMode(&devModeRaw); // This actually allocates a copy of the + // the nsIPrintSettingsWin DEVMODE, so + // we're now responsible for deallocating + // it. We'll use an nsAutoDevMode helper + // to do this. + if (devModeRaw) { + nsAutoDevMode devMode(devModeRaw); + devModeRaw = nullptr; + + size_t devModeTotalSize = devMode->dmSize + devMode->dmDriverExtra; + size_t msgTotalSize = sizeof(PrintData) + devModeTotalSize; + + if (msgTotalSize > IPC::MAX_MESSAGE_SIZE) { + return NS_ERROR_FAILURE; + } + + // Instead of reaching in and manually reading each member, we'll just + // copy the bits over. + const char* devModeData = reinterpret_cast<const char*>(devMode.get()); + nsTArray<uint8_t> arrayBuf; + arrayBuf.AppendElements(devModeData, devModeTotalSize); + data->devModeData().SwapElements(arrayBuf); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsPrintOptionsWin::DeserializeToPrintSettings(const PrintData& data, + nsIPrintSettings* settings) +{ + nsresult rv = nsPrintOptions::DeserializeToPrintSettings(data, settings); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIPrintSettingsWin> psWin = do_QueryInterface(settings); + if (!settings) { + return NS_ERROR_FAILURE; + } + + if (XRE_IsContentProcess()) { + psWin->SetDeviceName(data.deviceName().get()); + psWin->SetDriverName(data.driverName().get()); + + psWin->SetPrintableWidthInInches(data.printableWidthInInches()); + psWin->SetPrintableHeightInInches(data.printableHeightInInches()); + + if (data.devModeData().IsEmpty()) { + psWin->SetDevMode(nullptr); + } else { + // Check minimum length of DEVMODE data. + auto devModeDataLength = data.devModeData().Length(); + if (devModeDataLength < sizeof(DEVMODEW)) { + NS_WARNING("DEVMODE data is too short."); + return NS_ERROR_FAILURE; + } + + DEVMODEW* devMode = reinterpret_cast<DEVMODEW*>( + const_cast<uint8_t*>(data.devModeData().Elements())); + + // Check actual length of DEVMODE data. + if ((devMode->dmSize + devMode->dmDriverExtra) != devModeDataLength) { + NS_WARNING("DEVMODE length is incorrect."); + return NS_ERROR_FAILURE; + } + + psWin->SetDevMode(devMode); // Copies + } + } + + return NS_OK; +} + +nsresult nsPrintOptionsWin::_CreatePrintSettings(nsIPrintSettings **_retval) +{ + *_retval = nullptr; + nsPrintSettingsWin* printSettings = new nsPrintSettingsWin(); // does not initially ref count + NS_ENSURE_TRUE(printSettings, NS_ERROR_OUT_OF_MEMORY); + + NS_ADDREF(*_retval = printSettings); // ref count + + return NS_OK; +} + diff --git a/widget/windows/nsPrintOptionsWin.h b/widget/windows/nsPrintOptionsWin.h new file mode 100644 index 0000000000..7a8a89fc92 --- /dev/null +++ b/widget/windows/nsPrintOptionsWin.h @@ -0,0 +1,36 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsPrintOptionsWin_h__ +#define nsPrintOptionsWin_h__ + +#include "mozilla/embedding/PPrinting.h" +#include "nsPrintOptionsImpl.h" + +class nsIPrintSettings; +class nsIWebBrowserPrint; + +//***************************************************************************** +//*** nsPrintOptions +//***************************************************************************** +class nsPrintOptionsWin : public nsPrintOptions +{ +public: + nsPrintOptionsWin(); + virtual ~nsPrintOptionsWin(); + + NS_IMETHODIMP SerializeToPrintData(nsIPrintSettings* aSettings, + nsIWebBrowserPrint* aWBP, + mozilla::embedding::PrintData* data); + NS_IMETHODIMP DeserializeToPrintSettings(const mozilla::embedding::PrintData& data, + nsIPrintSettings* settings); + + virtual nsresult _CreatePrintSettings(nsIPrintSettings **_retval); +}; + + + +#endif /* nsPrintOptions_h__ */ diff --git a/widget/windows/nsPrintSettingsWin.cpp b/widget/windows/nsPrintSettingsWin.cpp new file mode 100644 index 0000000000..80e73e530b --- /dev/null +++ b/widget/windows/nsPrintSettingsWin.cpp @@ -0,0 +1,528 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "nsPrintSettingsWin.h" + +#include "mozilla/ArrayUtils.h" +#include "nsCRT.h" + +// Using paper sizes from wingdi.h and the units given there, plus a little +// extra research for the ones it doesn't give. Looks like the list hasn't +// changed since Windows 2000, so should be fairly stable now. +const short kPaperSizeUnits[] = { + nsIPrintSettings::kPaperSizeMillimeters, // Not Used default to mm as DEVMODE + // uses tenths of mm, just in case + nsIPrintSettings::kPaperSizeInches, // DMPAPER_LETTER + nsIPrintSettings::kPaperSizeInches, // DMPAPER_LETTERSMALL + nsIPrintSettings::kPaperSizeInches, // DMPAPER_TABLOID + nsIPrintSettings::kPaperSizeInches, // DMPAPER_LEDGER + nsIPrintSettings::kPaperSizeInches, // DMPAPER_LEGAL + nsIPrintSettings::kPaperSizeInches, // DMPAPER_STATEMENT + nsIPrintSettings::kPaperSizeInches, // DMPAPER_EXECUTIVE + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A3 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A4 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A4SMALL + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A5 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_B4 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_B5 + nsIPrintSettings::kPaperSizeInches, // DMPAPER_FOLIO + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_QUARTO + nsIPrintSettings::kPaperSizeInches, // DMPAPER_10X14 + nsIPrintSettings::kPaperSizeInches, // DMPAPER_11X17 + nsIPrintSettings::kPaperSizeInches, // DMPAPER_NOTE + nsIPrintSettings::kPaperSizeInches, // DMPAPER_ENV_9 + nsIPrintSettings::kPaperSizeInches, // DMPAPER_ENV_10 + nsIPrintSettings::kPaperSizeInches, // DMPAPER_ENV_11 + nsIPrintSettings::kPaperSizeInches, // DMPAPER_ENV_12 + nsIPrintSettings::kPaperSizeInches, // DMPAPER_ENV_14 + nsIPrintSettings::kPaperSizeInches, // DMPAPER_CSHEET + nsIPrintSettings::kPaperSizeInches, // DMPAPER_DSHEET + nsIPrintSettings::kPaperSizeInches, // DMPAPER_ESHEET + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_ENV_DL + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_ENV_C5 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_ENV_C3 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_ENV_C4 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_ENV_C6 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_ENV_C65 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_ENV_B4 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_ENV_B5 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_ENV_B6 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_ENV_ITALY + nsIPrintSettings::kPaperSizeInches, // DMPAPER_ENV_MONARCH + nsIPrintSettings::kPaperSizeInches, // DMPAPER_ENV_PERSONAL + nsIPrintSettings::kPaperSizeInches, // DMPAPER_FANFOLD_US + nsIPrintSettings::kPaperSizeInches, // DMPAPER_FANFOLD_STD_GERMAN + nsIPrintSettings::kPaperSizeInches, // DMPAPER_FANFOLD_LGL_GERMAN + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_ISO_B4 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_JAPANESE_POSTCARD + nsIPrintSettings::kPaperSizeInches, // DMPAPER_9X11 + nsIPrintSettings::kPaperSizeInches, // DMPAPER_10X11 + nsIPrintSettings::kPaperSizeInches, // DMPAPER_15X11 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_ENV_INVITE + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_RESERVED_48 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_RESERVED_49 + nsIPrintSettings::kPaperSizeInches, // DMPAPER_LETTER_EXTRA + nsIPrintSettings::kPaperSizeInches, // DMPAPER_LEGAL_EXTRA + nsIPrintSettings::kPaperSizeInches, // DMPAPER_TABLOID_EXTRA + nsIPrintSettings::kPaperSizeInches, // DMPAPER_A4_EXTRA + nsIPrintSettings::kPaperSizeInches, // DMPAPER_LETTER_TRANSVERSE + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A4_TRANSVERSE + nsIPrintSettings::kPaperSizeInches, // DMPAPER_LETTER_EXTRA_TRANSVERSE + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A_PLUS + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_B_PLUS + nsIPrintSettings::kPaperSizeInches, // DMPAPER_LETTER_PLUS + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A4_PLUS + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A5_TRANSVERSE + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_B5_TRANSVERSE + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A3_EXTRA + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A5_EXTRA + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_B5_EXTRA + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A2 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A3_TRANSVERSE + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A3_EXTRA_TRANSVERSE + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_DBL_JAPANESE_POSTCARD + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A6 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_JENV_KAKU2 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_JENV_KAKU3 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_JENV_CHOU3 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_JENV_CHOU4 + nsIPrintSettings::kPaperSizeInches, // DMPAPER_LETTER_ROTATED + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A3_ROTATED + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A4_ROTATED + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A5_ROTATED + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_B4_JIS_ROTATED + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_B5_JIS_ROTATED + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_JAPANESE_POSTCARD_ROTATED + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_DBL_JAPANESE_POSTCARD_ROTATED + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A6_ROTATED + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_JENV_KAKU2_ROTATED + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_JENV_KAKU3_ROTATED + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_JENV_CHOU3_ROTATED + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_JENV_CHOU4_ROTATED + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_B6_JIS + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_B6_JIS_ROTATED + nsIPrintSettings::kPaperSizeInches, // DMPAPER_12X11 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_JENV_YOU4 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_JENV_YOU4_ROTATED + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_P16K + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_P32K + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_P32KBIG + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_1 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_2 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_3 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_4 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_5 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_6 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_7 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_8 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_9 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_10 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_P16K_ROTATED + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_P32K_ROTATED + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_P32KBIG_ROTATED + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_1_ROTATED + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_2_ROTATED + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_3_ROTATED + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_4_ROTATED + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_5_ROTATED + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_6_ROTATED + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_7_ROTATED + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_8_ROTATED + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_9_ROTATED + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_10_ROTATED +}; + +NS_IMPL_ISUPPORTS_INHERITED(nsPrintSettingsWin, + nsPrintSettings, + nsIPrintSettingsWin) + +/** --------------------------------------------------- + * See documentation in nsPrintSettingsWin.h + * @update + */ +nsPrintSettingsWin::nsPrintSettingsWin() : + nsPrintSettings(), + mDeviceName(nullptr), + mDriverName(nullptr), + mDevMode(nullptr) +{ + +} + +/** --------------------------------------------------- + * See documentation in nsPrintSettingsWin.h + * @update + */ +nsPrintSettingsWin::nsPrintSettingsWin(const nsPrintSettingsWin& aPS) : + mDeviceName(nullptr), + mDriverName(nullptr), + mDevMode(nullptr) +{ + *this = aPS; +} + +/** --------------------------------------------------- + * See documentation in nsPrintSettingsWin.h + * @update + */ +nsPrintSettingsWin::~nsPrintSettingsWin() +{ + if (mDeviceName) free(mDeviceName); + if (mDriverName) free(mDriverName); + if (mDevMode) ::HeapFree(::GetProcessHeap(), 0, mDevMode); +} + +NS_IMETHODIMP nsPrintSettingsWin::SetDeviceName(const char16_t * aDeviceName) +{ + if (mDeviceName) { + free(mDeviceName); + } + mDeviceName = aDeviceName?wcsdup(char16ptr_t(aDeviceName)):nullptr; + return NS_OK; +} +NS_IMETHODIMP nsPrintSettingsWin::GetDeviceName(char16_t **aDeviceName) +{ + NS_ENSURE_ARG_POINTER(aDeviceName); + *aDeviceName = mDeviceName?reinterpret_cast<char16_t*>(wcsdup(mDeviceName)):nullptr; + return NS_OK; +} + +NS_IMETHODIMP nsPrintSettingsWin::SetDriverName(const char16_t * aDriverName) +{ + if (mDriverName) { + free(mDriverName); + } + mDriverName = aDriverName?wcsdup(char16ptr_t(aDriverName)):nullptr; + return NS_OK; +} +NS_IMETHODIMP nsPrintSettingsWin::GetDriverName(char16_t **aDriverName) +{ + NS_ENSURE_ARG_POINTER(aDriverName); + *aDriverName = mDriverName?reinterpret_cast<char16_t*>(wcsdup(mDriverName)):nullptr; + return NS_OK; +} + +void nsPrintSettingsWin::CopyDevMode(DEVMODEW* aInDevMode, DEVMODEW *& aOutDevMode) +{ + aOutDevMode = nullptr; + size_t size = aInDevMode->dmSize + aInDevMode->dmDriverExtra; + aOutDevMode = (LPDEVMODEW)::HeapAlloc (::GetProcessHeap(), HEAP_ZERO_MEMORY, size); + if (aOutDevMode) { + memcpy(aOutDevMode, aInDevMode, size); + } + +} + +NS_IMETHODIMP nsPrintSettingsWin::GetDevMode(DEVMODEW * *aDevMode) +{ + NS_ENSURE_ARG_POINTER(aDevMode); + + if (mDevMode) { + CopyDevMode(mDevMode, *aDevMode); + } else { + *aDevMode = nullptr; + } + return NS_OK; +} + +NS_IMETHODIMP nsPrintSettingsWin::SetDevMode(DEVMODEW * aDevMode) +{ + if (mDevMode) { + ::HeapFree(::GetProcessHeap(), 0, mDevMode); + mDevMode = nullptr; + } + + if (aDevMode) { + CopyDevMode(aDevMode, mDevMode); + } + return NS_OK; +} + +NS_IMETHODIMP +nsPrintSettingsWin::GetPrintableWidthInInches(double* aPrintableWidthInInches) +{ + MOZ_ASSERT(aPrintableWidthInInches); + *aPrintableWidthInInches = mPrintableWidthInInches; + return NS_OK; +} + +NS_IMETHODIMP +nsPrintSettingsWin::SetPrintableWidthInInches(double aPrintableWidthInInches) +{ + mPrintableWidthInInches = aPrintableWidthInInches; + return NS_OK; +} + +NS_IMETHODIMP +nsPrintSettingsWin::GetPrintableHeightInInches(double* aPrintableHeightInInches) +{ + MOZ_ASSERT(aPrintableHeightInInches); + *aPrintableHeightInInches = mPrintableHeightInInches; + return NS_OK; +} + +NS_IMETHODIMP +nsPrintSettingsWin::SetPrintableHeightInInches(double aPrintableHeightInInches) +{ + mPrintableHeightInInches = aPrintableHeightInInches; + return NS_OK; +} + +NS_IMETHODIMP +nsPrintSettingsWin::GetEffectivePageSize(double *aWidth, double *aHeight) +{ + // If printable page size not set, fall back to nsPrintSettings. + if (mPrintableWidthInInches == 0l || mPrintableHeightInInches == 0l) { + return nsPrintSettings::GetEffectivePageSize(aWidth, aHeight); + } + + if (mOrientation == kPortraitOrientation) { + *aWidth = NS_INCHES_TO_TWIPS(mPrintableWidthInInches); + *aHeight = NS_INCHES_TO_TWIPS(mPrintableHeightInInches); + } else { + *aHeight = NS_INCHES_TO_TWIPS(mPrintableWidthInInches); + *aWidth = NS_INCHES_TO_TWIPS(mPrintableHeightInInches); + } + return NS_OK; +} + +void +nsPrintSettingsWin::CopyFromNative(HDC aHdc, DEVMODEW* aDevMode) +{ + MOZ_ASSERT(aHdc); + MOZ_ASSERT(aDevMode); + + mIsInitedFromPrinter = true; + if (aDevMode->dmFields & DM_ORIENTATION) { + mOrientation = int32_t(aDevMode->dmOrientation == DMORIENT_PORTRAIT + ? kPortraitOrientation : kLandscapeOrientation); + } + + if (aDevMode->dmFields & DM_COPIES) { + mNumCopies = aDevMode->dmCopies; + } + + // Since we do the scaling, grab their value and reset back to 100. + if (aDevMode->dmFields & DM_SCALE) { + double scale = double(aDevMode->dmScale) / 100.0f; + if (mScaling == 1.0 || scale != 1.0) { + mScaling = scale; + } + aDevMode->dmScale = 100; + } + + if (aDevMode->dmFields & DM_PAPERSIZE) { + mPaperData = aDevMode->dmPaperSize; + // If not a paper size we know about, the unit will be the last one saved. + if (mPaperData > 0 && + mPaperData < int32_t(mozilla::ArrayLength(kPaperSizeUnits))) { + mPaperSizeUnit = kPaperSizeUnits[mPaperData]; + } + } else { + mPaperData = -1; + } + + // The length and width in DEVMODE are always in tenths of a millimeter. + double sizeUnitToTenthsOfAmm = + 10L * (mPaperSizeUnit == kPaperSizeInches ? MM_PER_INCH_FLOAT : 1L); + if (aDevMode->dmFields & DM_PAPERLENGTH) { + mPaperHeight = aDevMode->dmPaperLength / sizeUnitToTenthsOfAmm; + } else { + mPaperHeight = -1l; + } + + if (aDevMode->dmFields & DM_PAPERWIDTH) { + mPaperWidth = aDevMode->dmPaperWidth / sizeUnitToTenthsOfAmm; + } else { + mPaperWidth = -1l; + } + + // On Windows we currently create a surface using the printable area of the + // page and don't set the unwriteable [sic] margins. Using the unwriteable + // margins doesn't appear to work on Windows, but I am not sure if this is a + // bug elsewhere in our code or a Windows quirk. + // Note: we only scale the printing using the LOGPIXELSY, so we use that + // when calculating the surface width as well as the height. + int32_t printableWidthInDots = GetDeviceCaps(aHdc, HORZRES); + int32_t printableHeightInDots = GetDeviceCaps(aHdc, VERTRES); + int32_t heightDPI = GetDeviceCaps(aHdc, LOGPIXELSY); + + // Keep these values in portrait format, so we can reflect our own changes + // to mOrientation. + if (mOrientation == kPortraitOrientation) { + mPrintableWidthInInches = double(printableWidthInDots) / heightDPI; + mPrintableHeightInInches = double(printableHeightInDots) / heightDPI; + } else { + mPrintableHeightInInches = double(printableWidthInDots) / heightDPI; + mPrintableWidthInInches = double(printableHeightInDots) / heightDPI; + } + + // Using Y to match existing code for print scaling calculations. + mResolution = heightDPI; +} + +void +nsPrintSettingsWin::CopyToNative(DEVMODEW* aDevMode) +{ + MOZ_ASSERT(aDevMode); + + if (mPaperData >= 0) { + aDevMode->dmPaperSize = mPaperData; + aDevMode->dmFields |= DM_PAPERSIZE; + } else { + aDevMode->dmPaperSize = 0; + aDevMode->dmFields &= ~DM_PAPERSIZE; + } + + // The length and width in DEVMODE are always in tenths of a millimeter. + double sizeUnitToTenthsOfAmm = + 10L * (mPaperSizeUnit == kPaperSizeInches ? MM_PER_INCH_FLOAT : 1L); + + // Note: small page sizes can be required here for sticker, label and slide + // printers etc. see bug 1271900. + if (mPaperHeight > 0) { + aDevMode->dmPaperLength = mPaperHeight * sizeUnitToTenthsOfAmm; + aDevMode->dmFields |= DM_PAPERLENGTH; + } else { + aDevMode->dmPaperLength = 0; + aDevMode->dmFields &= ~DM_PAPERLENGTH; + } + + if (mPaperWidth > 0) { + aDevMode->dmPaperWidth = mPaperWidth * sizeUnitToTenthsOfAmm; + aDevMode->dmFields |= DM_PAPERWIDTH; + } else { + aDevMode->dmPaperWidth = 0; + aDevMode->dmFields &= ~DM_PAPERWIDTH; + } + + // Setup Orientation + aDevMode->dmOrientation = mOrientation == kPortraitOrientation + ? DMORIENT_PORTRAIT : DMORIENT_LANDSCAPE; + aDevMode->dmFields |= DM_ORIENTATION; + + // Setup Number of Copies + aDevMode->dmCopies = mNumCopies; + aDevMode->dmFields |= DM_COPIES; +} + +//------------------------------------------- +nsresult +nsPrintSettingsWin::_Clone(nsIPrintSettings **_retval) +{ + RefPtr<nsPrintSettingsWin> printSettings = new nsPrintSettingsWin(*this); + printSettings.forget(_retval); + return NS_OK; +} + +//------------------------------------------- +nsPrintSettingsWin& nsPrintSettingsWin::operator=(const nsPrintSettingsWin& rhs) +{ + if (this == &rhs) { + return *this; + } + + ((nsPrintSettings&) *this) = rhs; + + if (mDeviceName) { + free(mDeviceName); + } + + if (mDriverName) { + free(mDriverName); + } + + // Use free because we used the native malloc to create the memory + if (mDevMode) { + ::HeapFree(::GetProcessHeap(), 0, mDevMode); + } + + mDeviceName = rhs.mDeviceName?wcsdup(rhs.mDeviceName):nullptr; + mDriverName = rhs.mDriverName?wcsdup(rhs.mDriverName):nullptr; + + if (rhs.mDevMode) { + CopyDevMode(rhs.mDevMode, mDevMode); + } else { + mDevMode = nullptr; + } + + return *this; +} + +//------------------------------------------- +nsresult +nsPrintSettingsWin::_Assign(nsIPrintSettings *aPS) +{ + nsPrintSettingsWin *psWin = static_cast<nsPrintSettingsWin*>(aPS); + *this = *psWin; + return NS_OK; +} + +//---------------------------------------------------------------------- +// Testing of assign and clone +// This define turns on the testing module below +// so at start up it writes and reads the prefs. +#ifdef DEBUG_rodsX +#include "nsIPrintOptions.h" +#include "nsIServiceManager.h" +class Tester { +public: + Tester(); +}; +Tester::Tester() +{ + nsCOMPtr<nsIPrintSettings> ps; + nsresult rv; + nsCOMPtr<nsIPrintOptions> printService = do_GetService("@mozilla.org/gfx/printsettings-service;1", &rv); + if (NS_SUCCEEDED(rv)) { + rv = printService->CreatePrintSettings(getter_AddRefs(ps)); + } + + if (ps) { + ps->SetPrintOptions(nsIPrintSettings::kPrintOddPages, true); + ps->SetPrintOptions(nsIPrintSettings::kPrintEvenPages, false); + ps->SetMarginTop(1.0); + ps->SetMarginLeft(1.0); + ps->SetMarginBottom(1.0); + ps->SetMarginRight(1.0); + ps->SetScaling(0.5); + ps->SetPrintBGColors(true); + ps->SetPrintBGImages(true); + ps->SetPrintRange(15); + ps->SetHeaderStrLeft(NS_ConvertUTF8toUTF16("Left").get()); + ps->SetHeaderStrCenter(NS_ConvertUTF8toUTF16("Center").get()); + ps->SetHeaderStrRight(NS_ConvertUTF8toUTF16("Right").get()); + ps->SetFooterStrLeft(NS_ConvertUTF8toUTF16("Left").get()); + ps->SetFooterStrCenter(NS_ConvertUTF8toUTF16("Center").get()); + ps->SetFooterStrRight(NS_ConvertUTF8toUTF16("Right").get()); + ps->SetPaperName(NS_ConvertUTF8toUTF16("Paper Name").get()); + ps->SetPaperData(1); + ps->SetPaperWidth(100.0); + ps->SetPaperHeight(50.0); + ps->SetPaperSizeUnit(nsIPrintSettings::kPaperSizeMillimeters); + ps->SetPrintReversed(true); + ps->SetPrintInColor(true); + ps->SetOrientation(nsIPrintSettings::kLandscapeOrientation); + ps->SetPrintCommand(NS_ConvertUTF8toUTF16("Command").get()); + ps->SetNumCopies(2); + ps->SetPrinterName(NS_ConvertUTF8toUTF16("Printer Name").get()); + ps->SetPrintToFile(true); + ps->SetToFileName(NS_ConvertUTF8toUTF16("File Name").get()); + ps->SetPrintPageDelay(1000); + + nsCOMPtr<nsIPrintSettings> ps2; + if (NS_SUCCEEDED(rv)) { + rv = printService->CreatePrintSettings(getter_AddRefs(ps2)); + } + + ps2->Assign(ps); + + nsCOMPtr<nsIPrintSettings> psClone; + ps2->Clone(getter_AddRefs(psClone)); + + } + +} +Tester gTester; +#endif diff --git a/widget/windows/nsPrintSettingsWin.h b/widget/windows/nsPrintSettingsWin.h new file mode 100644 index 0000000000..d4a31b41e2 --- /dev/null +++ b/widget/windows/nsPrintSettingsWin.h @@ -0,0 +1,59 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsPrintSettingsWin_h__ +#define nsPrintSettingsWin_h__ + +#include "nsPrintSettingsImpl.h" +#include "nsIPrintSettingsWin.h" +#include <windows.h> + + +//***************************************************************************** +//*** nsPrintSettingsWin +//***************************************************************************** +class nsPrintSettingsWin : public nsPrintSettings, + public nsIPrintSettingsWin +{ + virtual ~nsPrintSettingsWin(); + +public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIPRINTSETTINGSWIN + + nsPrintSettingsWin(); + nsPrintSettingsWin(const nsPrintSettingsWin& aPS); + + /** + * Makes a new copy + */ + virtual nsresult _Clone(nsIPrintSettings **_retval); + + /** + * Assigns values + */ + virtual nsresult _Assign(nsIPrintSettings* aPS); + + /** + * Assignment + */ + nsPrintSettingsWin& operator=(const nsPrintSettingsWin& rhs); + + NS_IMETHOD GetEffectivePageSize(double *aWidth, double *aHeight) override; + +protected: + void CopyDevMode(DEVMODEW* aInDevMode, DEVMODEW *& aOutDevMode); + + wchar_t* mDeviceName; + wchar_t* mDriverName; + LPDEVMODEW mDevMode; + double mPrintableWidthInInches = 0l; + double mPrintableHeightInInches = 0l; +}; + + + +#endif /* nsPrintSettingsWin_h__ */ diff --git a/widget/windows/nsScreenManagerWin.cpp b/widget/windows/nsScreenManagerWin.cpp new file mode 100644 index 0000000000..5440be7dd7 --- /dev/null +++ b/widget/windows/nsScreenManagerWin.cpp @@ -0,0 +1,181 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsScreenManagerWin.h" +#include "mozilla/gfx/2D.h" +#include "nsScreenWin.h" +#include "gfxWindowsPlatform.h" +#include "nsIWidget.h" +#include "WinUtils.h" + +using namespace mozilla; + +BOOL CALLBACK CountMonitors(HMONITOR, HDC, LPRECT, LPARAM ioCount); + +nsScreenManagerWin::nsScreenManagerWin() + : mNumberOfScreens(0) +{ + // nothing to do. I guess we could cache a bunch of information + // here, but we want to ask the device at runtime in case anything + // has changed. +} + + +nsScreenManagerWin::~nsScreenManagerWin() +{ +} + + +// addref, release, QI +NS_IMPL_ISUPPORTS(nsScreenManagerWin, nsIScreenManager) + + +// +// CreateNewScreenObject +// +// Utility routine. Creates a new screen object from the given device handle +// +// NOTE: For this "single-monitor" impl, we just always return the cached primary +// screen. This should change when a multi-monitor impl is done. +// +nsIScreen* +nsScreenManagerWin::CreateNewScreenObject(HMONITOR inScreen) +{ + nsIScreen* retScreen = nullptr; + + // look through our screen list, hoping to find it. If it's not there, + // add it and return the new one. + for (unsigned i = 0; i < mScreenList.Length(); ++i) { + ScreenListItem& curr = mScreenList[i]; + if (inScreen == curr.mMon) { + NS_IF_ADDREF(retScreen = curr.mScreen.get()); + return retScreen; + } + } // for each screen. + + retScreen = new nsScreenWin(inScreen); + mScreenList.AppendElement(ScreenListItem(inScreen, retScreen)); + + NS_IF_ADDREF(retScreen); + return retScreen; +} + +NS_IMETHODIMP +nsScreenManagerWin::ScreenForId(uint32_t aId, nsIScreen **outScreen) +{ + *outScreen = nullptr; + + for (unsigned i = 0; i < mScreenList.Length(); ++i) { + ScreenListItem& curr = mScreenList[i]; + uint32_t id; + nsresult rv = curr.mScreen->GetId(&id); + if (NS_SUCCEEDED(rv) && id == aId) { + NS_IF_ADDREF(*outScreen = curr.mScreen.get()); + return NS_OK; + } + } + + return NS_ERROR_FAILURE; +} + +// +// ScreenForRect +// +// Returns the screen that contains the rectangle. If the rect overlaps +// multiple screens, it picks the screen with the greatest area of intersection. +// +// The coordinates are in pixels (not twips) and in logical screen coordinates. +// +NS_IMETHODIMP +nsScreenManagerWin::ScreenForRect(int32_t inLeft, int32_t inTop, + int32_t inWidth, int32_t inHeight, + nsIScreen **outScreen) +{ + if (!(inWidth || inHeight)) { + NS_WARNING("trying to find screen for sizeless window, using primary monitor"); + *outScreen = CreateNewScreenObject(nullptr); // addrefs + return NS_OK; + } + + gfx::Rect logicalBounds(inLeft, inTop, inWidth, inHeight); + HMONITOR genScreen = widget::WinUtils::MonitorFromRect(logicalBounds); + + *outScreen = CreateNewScreenObject(genScreen); // addrefs + + return NS_OK; + +} // ScreenForRect + + +// +// GetPrimaryScreen +// +// The screen with the menubar/taskbar. This shouldn't be needed very +// often. +// +NS_IMETHODIMP +nsScreenManagerWin::GetPrimaryScreen(nsIScreen** aPrimaryScreen) +{ + *aPrimaryScreen = CreateNewScreenObject(nullptr); // addrefs + return NS_OK; + +} // GetPrimaryScreen + + +// +// CountMonitors +// +// Will be called once for every monitor in the system. Just +// increments the parameter, which holds a ptr to a PRUin32 holding the +// count up to this point. +// +BOOL CALLBACK +CountMonitors(HMONITOR, HDC, LPRECT, LPARAM ioParam) +{ + uint32_t* countPtr = reinterpret_cast<uint32_t*>(ioParam); + ++(*countPtr); + + return TRUE; // continue the enumeration + +} // CountMonitors + + +// +// GetNumberOfScreens +// +// Returns how many physical screens are available. +// +NS_IMETHODIMP +nsScreenManagerWin::GetNumberOfScreens(uint32_t *aNumberOfScreens) +{ + if (mNumberOfScreens) + *aNumberOfScreens = mNumberOfScreens; + else { + uint32_t count = 0; + BOOL result = ::EnumDisplayMonitors(nullptr, nullptr, (MONITORENUMPROC)CountMonitors, (LPARAM)&count); + if (!result) + return NS_ERROR_FAILURE; + *aNumberOfScreens = mNumberOfScreens = count; + } + + return NS_OK; + +} // GetNumberOfScreens + +NS_IMETHODIMP +nsScreenManagerWin::GetSystemDefaultScale(float *aDefaultScale) +{ + HMONITOR primary = widget::WinUtils::GetPrimaryMonitor(); + *aDefaultScale = float(widget::WinUtils::LogToPhysFactor(primary)); + return NS_OK; +} + +NS_IMETHODIMP +nsScreenManagerWin::ScreenForNativeWidget(void *aWidget, nsIScreen **outScreen) +{ + HMONITOR mon = MonitorFromWindow((HWND) aWidget, MONITOR_DEFAULTTOPRIMARY); + *outScreen = CreateNewScreenObject(mon); + return NS_OK; +} diff --git a/widget/windows/nsScreenManagerWin.h b/widget/windows/nsScreenManagerWin.h new file mode 100644 index 0000000000..c4757418a3 --- /dev/null +++ b/widget/windows/nsScreenManagerWin.h @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsScreenManagerWin_h___ +#define nsScreenManagerWin_h___ + +#include "nsIScreenManager.h" + +#include <windows.h> +#include "nsCOMPtr.h" +#include "nsTArray.h" +#include "mozilla/Attributes.h" + +class nsIScreen; + +//------------------------------------------------------------------------ + +class ScreenListItem +{ +public: + ScreenListItem ( HMONITOR inMon, nsIScreen* inScreen ) + : mMon(inMon), mScreen(inScreen) { } ; + + HMONITOR mMon; + nsCOMPtr<nsIScreen> mScreen; +}; + +class nsScreenManagerWin final : public nsIScreenManager +{ +public: + nsScreenManagerWin ( ); + + NS_DECL_ISUPPORTS + NS_DECL_NSISCREENMANAGER + +private: + ~nsScreenManagerWin(); + + nsIScreen* CreateNewScreenObject ( HMONITOR inScreen ) ; + + uint32_t mNumberOfScreens; + + // cache the screens to avoid the memory allocations + AutoTArray<ScreenListItem, 8> mScreenList; + +}; + +#endif // nsScreenManagerWin_h___ diff --git a/widget/windows/nsScreenWin.cpp b/widget/windows/nsScreenWin.cpp new file mode 100644 index 0000000000..beacbf05fd --- /dev/null +++ b/widget/windows/nsScreenWin.cpp @@ -0,0 +1,199 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsScreenWin.h" +#include "nsCoord.h" +#include "nsIWidget.h" +#include "WinUtils.h" + +using namespace mozilla; + +static uint32_t sScreenId; + +nsScreenWin::nsScreenWin(HMONITOR inScreen) + : mScreen(inScreen) + , mId(++sScreenId) +{ +#ifdef DEBUG + HDC hDCScreen = ::GetDC(nullptr); + NS_ASSERTION(hDCScreen,"GetDC Failure"); + NS_ASSERTION(::GetDeviceCaps(hDCScreen, TECHNOLOGY) == DT_RASDISPLAY, "Not a display screen"); + ::ReleaseDC(nullptr,hDCScreen); +#endif + + // nothing else to do. I guess we could cache a bunch of information + // here, but we want to ask the device at runtime in case anything + // has changed. +} + + +nsScreenWin::~nsScreenWin() +{ + // nothing to see here. +} + + +NS_IMETHODIMP +nsScreenWin::GetId(uint32_t *outId) +{ + *outId = mId; + return NS_OK; +} + + +NS_IMETHODIMP +nsScreenWin::GetRect(int32_t *outLeft, int32_t *outTop, int32_t *outWidth, int32_t *outHeight) +{ + BOOL success = FALSE; + if (mScreen) { + MONITORINFO info; + info.cbSize = sizeof(MONITORINFO); + success = ::GetMonitorInfoW(mScreen, &info); + if (success) { + *outLeft = info.rcMonitor.left; + *outTop = info.rcMonitor.top; + *outWidth = info.rcMonitor.right - info.rcMonitor.left; + *outHeight = info.rcMonitor.bottom - info.rcMonitor.top; + } + } + if (!success) { + HDC hDCScreen = ::GetDC(nullptr); + NS_ASSERTION(hDCScreen,"GetDC Failure"); + + *outTop = *outLeft = 0; + *outWidth = ::GetDeviceCaps(hDCScreen, HORZRES); + *outHeight = ::GetDeviceCaps(hDCScreen, VERTRES); + + ::ReleaseDC(nullptr, hDCScreen); + } + return NS_OK; + +} // GetRect + + +NS_IMETHODIMP +nsScreenWin::GetAvailRect(int32_t *outLeft, int32_t *outTop, int32_t *outWidth, int32_t *outHeight) +{ + BOOL success = FALSE; + + if (mScreen) { + MONITORINFO info; + info.cbSize = sizeof(MONITORINFO); + success = ::GetMonitorInfoW(mScreen, &info); + if (success) { + *outLeft = info.rcWork.left; + *outTop = info.rcWork.top; + *outWidth = info.rcWork.right - info.rcWork.left; + *outHeight = info.rcWork.bottom - info.rcWork.top; + } + } + if (!success) { + RECT workArea; + ::SystemParametersInfo(SPI_GETWORKAREA, 0, &workArea, 0); + *outLeft = workArea.left; + *outTop = workArea.top; + *outWidth = workArea.right - workArea.left; + *outHeight = workArea.bottom - workArea.top; + } + + return NS_OK; + +} // GetAvailRect + +NS_IMETHODIMP +nsScreenWin::GetRectDisplayPix(int32_t *outLeft, int32_t *outTop, + int32_t *outWidth, int32_t *outHeight) +{ + if (widget::WinUtils::IsPerMonitorDPIAware()) { + // on per-monitor-dpi config, display pixels are device pixels + return GetRect(outLeft, outTop, outWidth, outHeight); + } + int32_t left, top, width, height; + nsresult rv = GetRect(&left, &top, &width, &height); + if (NS_FAILED(rv)) { + return rv; + } + double scaleFactor = 1.0 / widget::WinUtils::LogToPhysFactor(mScreen); + *outLeft = NSToIntRound(left * scaleFactor); + *outTop = NSToIntRound(top * scaleFactor); + *outWidth = NSToIntRound(width * scaleFactor); + *outHeight = NSToIntRound(height * scaleFactor); + return NS_OK; +} + +NS_IMETHODIMP +nsScreenWin::GetAvailRectDisplayPix(int32_t *outLeft, int32_t *outTop, + int32_t *outWidth, int32_t *outHeight) +{ + if (widget::WinUtils::IsPerMonitorDPIAware()) { + // on per-monitor-dpi config, display pixels are device pixels + return GetAvailRect(outLeft, outTop, outWidth, outHeight); + } + int32_t left, top, width, height; + nsresult rv = GetAvailRect(&left, &top, &width, &height); + if (NS_FAILED(rv)) { + return rv; + } + double scaleFactor = 1.0 / widget::WinUtils::LogToPhysFactor(mScreen); + *outLeft = NSToIntRound(left * scaleFactor); + *outTop = NSToIntRound(top * scaleFactor); + *outWidth = NSToIntRound(width * scaleFactor); + *outHeight = NSToIntRound(height * scaleFactor); + return NS_OK; +} + + +NS_IMETHODIMP +nsScreenWin :: GetPixelDepth(int32_t *aPixelDepth) +{ + //XXX not sure how to get this info for multiple monitors, this might be ok... + HDC hDCScreen = ::GetDC(nullptr); + NS_ASSERTION(hDCScreen,"GetDC Failure"); + + int32_t depth = ::GetDeviceCaps(hDCScreen, BITSPIXEL); + if (depth == 32) { + // If a device uses 32 bits per pixel, it's still only using 8 bits + // per color component, which is what our callers want to know. + // (Some devices report 32 and some devices report 24.) + depth = 24; + } + *aPixelDepth = depth; + + ::ReleaseDC(nullptr, hDCScreen); + return NS_OK; + +} // GetPixelDepth + + +NS_IMETHODIMP +nsScreenWin::GetColorDepth(int32_t *aColorDepth) +{ + return GetPixelDepth(aColorDepth); + +} // GetColorDepth + + +NS_IMETHODIMP +nsScreenWin::GetContentsScaleFactor(double *aContentsScaleFactor) +{ + if (widget::WinUtils::IsPerMonitorDPIAware()) { + *aContentsScaleFactor = 1.0; + } else { + *aContentsScaleFactor = widget::WinUtils::LogToPhysFactor(mScreen); + } + return NS_OK; +} + +NS_IMETHODIMP +nsScreenWin::GetDefaultCSSScaleFactor(double* aScaleFactor) +{ + double scale = nsIWidget::DefaultScaleOverride(); + if (scale > 0.0) { + *aScaleFactor = scale; + } else { + *aScaleFactor = widget::WinUtils::LogToPhysFactor(mScreen); + } + return NS_OK; +} diff --git a/widget/windows/nsScreenWin.h b/widget/windows/nsScreenWin.h new file mode 100644 index 0000000000..0560c55342 --- /dev/null +++ b/widget/windows/nsScreenWin.h @@ -0,0 +1,45 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsScreenWin_h___ +#define nsScreenWin_h___ + +#include <windows.h> +#include "nsBaseScreen.h" + +//------------------------------------------------------------------------ + +class nsScreenWin final : public nsBaseScreen +{ +public: + nsScreenWin ( HMONITOR inScreen ); + ~nsScreenWin(); + + NS_IMETHOD GetId(uint32_t* aId); + + // These methods return the size in device (physical) pixels + NS_IMETHOD GetRect(int32_t* aLeft, int32_t* aTop, int32_t* aWidth, int32_t* aHeight); + NS_IMETHOD GetAvailRect(int32_t* aLeft, int32_t* aTop, int32_t* aWidth, int32_t* aHeight); + + // And these methods get the screen size in 'desktop pixels' (AKA 'logical pixels') + // that are dependent on the logical DPI setting in windows + NS_IMETHOD GetRectDisplayPix(int32_t *outLeft, int32_t *outTop, + int32_t *outWidth, int32_t *outHeight); + NS_IMETHOD GetAvailRectDisplayPix(int32_t *outLeft, int32_t *outTop, + int32_t *outWidth, int32_t *outHeight); + + NS_IMETHOD GetPixelDepth(int32_t* aPixelDepth); + NS_IMETHOD GetColorDepth(int32_t* aColorDepth); + + NS_IMETHOD GetContentsScaleFactor(double* aContentsScaleFactor) override; + + NS_IMETHOD GetDefaultCSSScaleFactor(double* aScaleFactor) override; + +private: + HMONITOR mScreen; + uint32_t mId; +}; + +#endif // nsScreenWin_h___ diff --git a/widget/windows/nsSound.cpp b/widget/windows/nsSound.cpp new file mode 100644 index 0000000000..a7e3f8e7ca --- /dev/null +++ b/widget/windows/nsSound.cpp @@ -0,0 +1,305 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nscore.h" +#include "plstr.h" +#include <stdio.h> +#include "nsString.h" +#include <windows.h> + +// mmsystem.h is needed to build with WIN32_LEAN_AND_MEAN +#include <mmsystem.h> + +#include "nsSound.h" +#include "nsIURL.h" +#include "nsNetUtil.h" +#include "nsIChannel.h" +#include "nsContentUtils.h" +#include "nsCRT.h" + +#include "mozilla/Logging.h" +#include "prtime.h" +#include "prprf.h" +#include "prmem.h" + +#include "nsNativeCharsetUtils.h" +#include "nsThreadUtils.h" + +using mozilla::LogLevel; + +PRLogModuleInfo* gWin32SoundLog = nullptr; + +class nsSoundPlayer: public mozilla::Runnable { +public: + nsSoundPlayer(nsSound *aSound, const wchar_t* aSoundName) : + mSoundName(aSoundName), mSound(aSound) + { + Init(); + } + + nsSoundPlayer(nsSound *aSound, const nsAString& aSoundName) : + mSoundName(aSoundName), mSound(aSound) + { + Init(); + } + + NS_DECL_NSIRUNNABLE + +protected: + nsString mSoundName; + nsSound *mSound; // Strong, but this will be released from SoundReleaser. + nsCOMPtr<nsIThread> mThread; + + void Init() + { + NS_GetCurrentThread(getter_AddRefs(mThread)); + NS_ASSERTION(mThread, "failed to get current thread"); + NS_IF_ADDREF(mSound); + } + + class SoundReleaser: public mozilla::Runnable { + public: + SoundReleaser(nsSound* aSound) : + mSound(aSound) + { + } + + NS_DECL_NSIRUNNABLE + + protected: + nsSound *mSound; + }; +}; + +NS_IMETHODIMP +nsSoundPlayer::Run() +{ + PR_SetCurrentThreadName("Play Sound"); + + NS_PRECONDITION(!mSoundName.IsEmpty(), "Sound name should not be empty"); + ::PlaySoundW(mSoundName.get(), nullptr, + SND_NODEFAULT | SND_ALIAS | SND_ASYNC); + nsCOMPtr<nsIRunnable> releaser = new SoundReleaser(mSound); + // Don't release nsSound from here, because here is not an owning thread of + // the nsSound. nsSound must be released in its owning thread. + mThread->Dispatch(releaser, NS_DISPATCH_NORMAL); + return NS_OK; +} + +NS_IMETHODIMP +nsSoundPlayer::SoundReleaser::Run() +{ + mSound->ShutdownOldPlayerThread(); + NS_IF_RELEASE(mSound); + return NS_OK; +} + + +#ifndef SND_PURGE +// Not available on Windows CE, and according to MSDN +// doesn't do anything on recent windows either. +#define SND_PURGE 0 +#endif + +NS_IMPL_ISUPPORTS(nsSound, nsISound, nsIStreamLoaderObserver) + + +nsSound::nsSound() +{ + if (!gWin32SoundLog) { + gWin32SoundLog = PR_NewLogModule("nsSound"); + } + + mLastSound = nullptr; +} + +nsSound::~nsSound() +{ + NS_ASSERTION(!mPlayerThread, "player thread is not null but should be"); + PurgeLastSound(); +} + +void nsSound::ShutdownOldPlayerThread() +{ + nsCOMPtr<nsIThread> playerThread(mPlayerThread.forget()); + if (playerThread) + playerThread->Shutdown(); +} + +void nsSound::PurgeLastSound() +{ + if (mLastSound) { + // Halt any currently playing sound. + ::PlaySound(nullptr, nullptr, SND_PURGE); + + // Now delete the buffer. + free(mLastSound); + mLastSound = nullptr; + } +} + +NS_IMETHODIMP nsSound::Beep() +{ + ::MessageBeep(0); + + return NS_OK; +} + +NS_IMETHODIMP nsSound::OnStreamComplete(nsIStreamLoader *aLoader, + nsISupports *context, + nsresult aStatus, + uint32_t dataLen, + const uint8_t *data) +{ + // print a load error on bad status + if (NS_FAILED(aStatus)) { +#ifdef DEBUG + if (aLoader) { + nsCOMPtr<nsIRequest> request; + nsCOMPtr<nsIChannel> channel; + aLoader->GetRequest(getter_AddRefs(request)); + if (request) + channel = do_QueryInterface(request); + if (channel) { + nsCOMPtr<nsIURI> uri; + channel->GetURI(getter_AddRefs(uri)); + if (uri) { + nsAutoCString uriSpec; + uri->GetSpec(uriSpec); + MOZ_LOG(gWin32SoundLog, LogLevel::Info, + ("Failed to load %s\n", uriSpec.get())); + } + } + } +#endif + return aStatus; + } + + ShutdownOldPlayerThread(); + PurgeLastSound(); + + if (data && dataLen > 0) { + DWORD flags = SND_MEMORY | SND_NODEFAULT; + // We try to make a copy so we can play it async. + mLastSound = (uint8_t *) malloc(dataLen); + if (mLastSound) { + memcpy(mLastSound, data, dataLen); + data = mLastSound; + flags |= SND_ASYNC; + } + ::PlaySoundW(reinterpret_cast<LPCWSTR>(data), 0, flags); + } + + return NS_OK; +} + +NS_IMETHODIMP nsSound::Play(nsIURL *aURL) +{ + nsresult rv; + +#ifdef DEBUG_SOUND + char *url; + aURL->GetSpec(&url); + MOZ_LOG(gWin32SoundLog, LogLevel::Info, + ("%s\n", url)); +#endif + + nsCOMPtr<nsIStreamLoader> loader; + rv = NS_NewStreamLoader(getter_AddRefs(loader), + aURL, + this, // aObserver + nsContentUtils::GetSystemPrincipal(), + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, + nsIContentPolicy::TYPE_OTHER); + return rv; +} + + +NS_IMETHODIMP nsSound::Init() +{ + // This call halts a sound if it was still playing. + // We have to use the sound library for something to make sure + // it is initialized. + // If we wait until the first sound is played, there will + // be a time lag as the library gets loaded. + ::PlaySound(nullptr, nullptr, SND_PURGE); + + return NS_OK; +} + + +NS_IMETHODIMP nsSound::PlaySystemSound(const nsAString &aSoundAlias) +{ + ShutdownOldPlayerThread(); + PurgeLastSound(); + + if (!NS_IsMozAliasSound(aSoundAlias)) { + if (aSoundAlias.IsEmpty()) + return NS_OK; + nsCOMPtr<nsIRunnable> player = new nsSoundPlayer(this, aSoundAlias); + NS_ENSURE_TRUE(player, NS_ERROR_OUT_OF_MEMORY); + nsresult rv = NS_NewThread(getter_AddRefs(mPlayerThread), player); + NS_ENSURE_SUCCESS(rv, rv); + return NS_OK; + } + + NS_WARNING("nsISound::playSystemSound is called with \"_moz_\" events, they are obsolete, use nsISound::playEventSound instead"); + + uint32_t eventId; + if (aSoundAlias.Equals(NS_SYSSOUND_MAIL_BEEP)) + eventId = EVENT_NEW_MAIL_RECEIVED; + else if (aSoundAlias.Equals(NS_SYSSOUND_CONFIRM_DIALOG)) + eventId = EVENT_CONFIRM_DIALOG_OPEN; + else if (aSoundAlias.Equals(NS_SYSSOUND_ALERT_DIALOG)) + eventId = EVENT_ALERT_DIALOG_OPEN; + else if (aSoundAlias.Equals(NS_SYSSOUND_MENU_EXECUTE)) + eventId = EVENT_MENU_EXECUTE; + else if (aSoundAlias.Equals(NS_SYSSOUND_MENU_POPUP)) + eventId = EVENT_MENU_POPUP; + else + return NS_OK; + + return PlayEventSound(eventId); +} + +NS_IMETHODIMP nsSound::PlayEventSound(uint32_t aEventId) +{ + ShutdownOldPlayerThread(); + PurgeLastSound(); + + const wchar_t *sound = nullptr; + switch (aEventId) { + case EVENT_NEW_MAIL_RECEIVED: + sound = L"MailBeep"; + break; + case EVENT_ALERT_DIALOG_OPEN: + sound = L"SystemExclamation"; + break; + case EVENT_CONFIRM_DIALOG_OPEN: + sound = L"SystemQuestion"; + break; + case EVENT_MENU_EXECUTE: + sound = L"MenuCommand"; + break; + case EVENT_MENU_POPUP: + sound = L"MenuPopup"; + break; + case EVENT_EDITOR_MAX_LEN: + sound = L".Default"; + break; + default: + // Win32 plays no sounds at NS_SYSSOUND_PROMPT_DIALOG and + // NS_SYSSOUND_SELECT_DIALOG. + return NS_OK; + } + NS_ASSERTION(sound, "sound is null"); + + nsCOMPtr<nsIRunnable> player = new nsSoundPlayer(this, sound); + NS_ENSURE_TRUE(player, NS_ERROR_OUT_OF_MEMORY); + nsresult rv = NS_NewThread(getter_AddRefs(mPlayerThread), player); + NS_ENSURE_SUCCESS(rv, rv); + return NS_OK; +} diff --git a/widget/windows/nsSound.h b/widget/windows/nsSound.h new file mode 100644 index 0000000000..fc3ea30ca6 --- /dev/null +++ b/widget/windows/nsSound.h @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __nsSound_h__ +#define __nsSound_h__ + +#include "nsISound.h" +#include "nsIStreamLoader.h" +#include "nsCOMPtr.h" + +class nsIThread; + +class nsSound : public nsISound, + public nsIStreamLoaderObserver + +{ +public: + nsSound(); + void ShutdownOldPlayerThread(); + + NS_DECL_ISUPPORTS + NS_DECL_NSISOUND + NS_DECL_NSISTREAMLOADEROBSERVER + +private: + virtual ~nsSound(); + void PurgeLastSound(); + +private: + uint8_t* mLastSound; + nsCOMPtr<nsIThread> mPlayerThread; +}; + +#endif /* __nsSound_h__ */ diff --git a/widget/windows/nsToolkit.cpp b/widget/windows/nsToolkit.cpp new file mode 100644 index 0000000000..6c3cc0508e --- /dev/null +++ b/widget/windows/nsToolkit.cpp @@ -0,0 +1,81 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsToolkit.h" +#include "nsAppShell.h" +#include "nsWindow.h" +#include "nsWidgetsCID.h" +#include "prmon.h" +#include "prtime.h" +#include "nsIServiceManager.h" +#include "nsComponentManagerUtils.h" +#include <objbase.h> +#include "WinUtils.h" + +#include "nsUXThemeData.h" + +// unknwn.h is needed to build with WIN32_LEAN_AND_MEAN +#include <unknwn.h> + +using namespace mozilla::widget; + +nsToolkit* nsToolkit::gToolkit = nullptr; +HINSTANCE nsToolkit::mDllInstance = 0; + +//------------------------------------------------------------------------- +// +// constructor +// +//------------------------------------------------------------------------- +nsToolkit::nsToolkit() +{ + MOZ_COUNT_CTOR(nsToolkit); + +#if defined(MOZ_STATIC_COMPONENT_LIBS) + nsToolkit::Startup(GetModuleHandle(nullptr)); +#endif +} + + +//------------------------------------------------------------------------- +// +// destructor +// +//------------------------------------------------------------------------- +nsToolkit::~nsToolkit() +{ + MOZ_COUNT_DTOR(nsToolkit); +} + +void +nsToolkit::Startup(HMODULE hModule) +{ + nsToolkit::mDllInstance = hModule; + WinUtils::Initialize(); + nsUXThemeData::Initialize(); +} + +void +nsToolkit::Shutdown() +{ + delete gToolkit; + gToolkit = nullptr; +} + +//------------------------------------------------------------------------- +// +// Return the nsToolkit for the current thread. If a toolkit does not +// yet exist, then one will be created... +// +//------------------------------------------------------------------------- +// static +nsToolkit* nsToolkit::GetToolkit() +{ + if (!gToolkit) { + gToolkit = new nsToolkit(); + } + + return gToolkit; +} diff --git a/widget/windows/nsToolkit.h b/widget/windows/nsToolkit.h new file mode 100644 index 0000000000..14bb22c189 --- /dev/null +++ b/widget/windows/nsToolkit.h @@ -0,0 +1,49 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsToolkit_h__ +#define nsToolkit_h__ + +#include "nsdefs.h" + +#include "nsITimer.h" +#include "nsCOMPtr.h" +#include <windows.h> + +// Avoid including windowsx.h to prevent macro pollution +#ifndef GET_X_LPARAM +#define GET_X_LPARAM(pt) (short(LOWORD(pt))) +#endif +#ifndef GET_Y_LPARAM +#define GET_Y_LPARAM(pt) (short(HIWORD(pt))) +#endif + +/** + * Wrapper around the thread running the message pump. + * The toolkit abstraction is necessary because the message pump must + * execute within the same thread that created the widget under Win32. + */ + +class nsToolkit +{ +public: + nsToolkit(); + +private: + ~nsToolkit(); + +public: + static nsToolkit* GetToolkit(); + + static HINSTANCE mDllInstance; + + static void Startup(HMODULE hModule); + static void Shutdown(); + +protected: + static nsToolkit* gToolkit; +}; + +#endif // TOOLKIT_H diff --git a/widget/windows/nsUXThemeConstants.h b/widget/windows/nsUXThemeConstants.h new file mode 100644 index 0000000000..731dcedf25 --- /dev/null +++ b/widget/windows/nsUXThemeConstants.h @@ -0,0 +1,251 @@ +/* vim: se cin sw=2 ts=2 et : */ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsUXThemeConstants_h +#define nsUXThemeConstants_h + +/* + * The following constants are used to determine how a widget is drawn using + * Windows' Theme API. For more information on theme parts and states see + * http://msdn.microsoft.com/en-us/library/bb773210(VS.85).aspx + */ + +#include <vssym32.h> +#include <vsstyle.h> + +#define THEME_COLOR 204 +#define THEME_FONT 210 + +// Generic state constants +#define TS_NORMAL 1 +#define TS_HOVER 2 +#define TS_ACTIVE 3 +#define TS_DISABLED 4 +#define TS_FOCUSED 5 + +// These constants are reversed for the trackbar (scale) thumb +#define TKP_FOCUSED 4 +#define TKP_DISABLED 5 + +// Toolbarbutton constants +#define TB_CHECKED 5 +#define TB_HOVER_CHECKED 6 + +// Button constants +#define BP_BUTTON 1 +#define BP_RADIO 2 +#define BP_CHECKBOX 3 +#define BP_GROUPBOX 4 + +// Textfield constants +/* This is the EP_EDITTEXT part */ +#define TFP_TEXTFIELD 1 +#define TFP_EDITBORDER_NOSCROLL 6 +#define TFS_READONLY 6 + +/* These are the state constants for the EDITBORDER parts */ +#define TFS_EDITBORDER_NORMAL 1 +#define TFS_EDITBORDER_HOVER 2 +#define TFS_EDITBORDER_FOCUSED 3 +#define TFS_EDITBORDER_DISABLED 4 + +// Treeview/listbox constants +#define TREEVIEW_BODY 1 + +// Scrollbar constants +#define SP_BUTTON 1 +#define SP_THUMBHOR 2 +#define SP_THUMBVERT 3 +#define SP_TRACKSTARTHOR 4 +#define SP_TRACKENDHOR 5 +#define SP_TRACKSTARTVERT 6 +#define SP_TRACKENDVERT 7 +#define SP_GRIPPERHOR 8 +#define SP_GRIPPERVERT 9 + +// Vista only; implict hover state. +// BASE + 0 = UP, + 1 = DOWN, etc. +#define SP_BUTTON_IMPLICIT_HOVER_BASE 17 + +// Scale constants +#define TKP_TRACK 1 +#define TKP_TRACKVERT 2 +#define TKP_THUMB 3 +#define TKP_THUMBBOTTOM 4 +#define TKP_THUMBTOP 5 +#define TKP_THUMBVERT 6 +#define TKP_THUMBLEFT 7 +#define TKP_THUMBRIGHT 8 + +// Track state contstants +#define TRS_NORMAL 1 + +// Track vertical state constants +#define TRVS_NORMAL 1 + +// Spin constants +#define SPNP_UP 1 +#define SPNP_DOWN 2 + +// Tab constants +#define TABP_TAB 4 +#define TABP_TAB_SELECTED 5 +#define TABP_PANELS 9 +#define TABP_PANEL 10 + +// Tooltip constants +#define TTP_STANDARD 1 + +// Dropdown constants +#define CBP_DROPMARKER 1 +#define CBP_DROPBORDER 4 +/* This is actually the 'READONLY' style */ +#define CBP_DROPFRAME 5 +#define CBP_DROPMARKER_VISTA 6 + +// Menu Constants +#define MENU_BARBACKGROUND 7 +#define MENU_BARITEM 8 +#define MENU_POPUPBACKGROUND 9 +#define MENU_POPUPBORDERS 10 +#define MENU_POPUPCHECK 11 +#define MENU_POPUPCHECKBACKGROUND 12 +#define MENU_POPUPGUTTER 13 +#define MENU_POPUPITEM 14 +#define MENU_POPUPSEPARATOR 15 +#define MENU_POPUPSUBMENU 16 +#define MENU_SYSTEMCLOSE 17 +#define MENU_SYSTEMMAXIMIZE 18 +#define MENU_SYSTEMMINIMIZE 19 +#define MENU_SYSTEMRESTORE 20 + +#define MB_ACTIVE 1 +#define MB_INACTIVE 2 + +#define MS_NORMAL 1 +#define MS_SELECTED 2 +#define MS_DEMOTED 3 + +#define MBI_NORMAL 1 +#define MBI_HOT 2 +#define MBI_PUSHED 3 +#define MBI_DISABLED 4 +#define MBI_DISABLEDHOT 5 +#define MBI_DISABLEDPUSHED 6 + +#define MC_CHECKMARKNORMAL 1 +#define MC_CHECKMARKDISABLED 2 +#define MC_BULLETNORMAL 3 +#define MC_BULLETDISABLED 4 + +#define MCB_DISABLED 1 +#define MCB_NORMAL 2 +#define MCB_BITMAP 3 + +#define MPI_NORMAL 1 +#define MPI_HOT 2 +#define MPI_DISABLED 3 +#define MPI_DISABLEDHOT 4 + +#define MSM_NORMAL 1 +#define MSM_DISABLED 2 + +// Rebar constants +#define RP_BAND 3 +#define RP_BACKGROUND 6 + +// Constants only found in new (98+, 2K+, XP+, etc.) Windows. +#ifdef DFCS_HOT +#undef DFCS_HOT +#endif +#define DFCS_HOT 0x00001000 + +#ifdef COLOR_MENUHILIGHT +#undef COLOR_MENUHILIGHT +#endif +#define COLOR_MENUHILIGHT 29 + +#ifdef SPI_GETFLATMENU +#undef SPI_GETFLATMENU +#endif +#define SPI_GETFLATMENU 0x1022 +#ifndef SPI_GETMENUSHOWDELAY +#define SPI_GETMENUSHOWDELAY 106 +#endif //SPI_GETMENUSHOWDELAY +#ifndef WS_EX_LAYOUTRTL +#define WS_EX_LAYOUTRTL 0x00400000L // Right to left mirroring +#endif + + +// Our extra constants for passing a little bit more info to the renderer. +#define DFCS_RTL 0x00010000 + +// Toolbar separator dimension which can't be gotten from Windows +#define TB_SEPARATOR_HEIGHT 2 + +namespace mozilla { +namespace widget { +namespace themeconst { + +// Pulled from sdk/include/vsstyle.h +enum { + WP_CAPTION = 1, + WP_SMALLCAPTION = 2, + WP_MINCAPTION = 3, + WP_SMALLMINCAPTION = 4, + WP_MAXCAPTION = 5, + WP_SMALLMAXCAPTION = 6, + WP_FRAMELEFT = 7, + WP_FRAMERIGHT = 8, + WP_FRAMEBOTTOM = 9, + WP_SMALLFRAMELEFT = 10, + WP_SMALLFRAMERIGHT = 11, + WP_SMALLFRAMEBOTTOM = 12, + WP_SYSBUTTON = 13, + WP_MDISYSBUTTON = 14, + WP_MINBUTTON = 15, + WP_MDIMINBUTTON = 16, + WP_MAXBUTTON = 17, + WP_CLOSEBUTTON = 18, + WP_SMALLCLOSEBUTTON = 19, + WP_MDICLOSEBUTTON = 20, + WP_RESTOREBUTTON = 21, + WP_MDIRESTOREBUTTON = 22, + WP_HELPBUTTON = 23, + WP_MDIHELPBUTTON = 24, + WP_HORZSCROLL = 25, + WP_HORZTHUMB = 26, + WP_VERTSCROLL = 27, + WP_VERTTHUMB = 28, + WP_DIALOG = 29, + WP_CAPTIONSIZINGTEMPLATE = 30, + WP_SMALLCAPTIONSIZINGTEMPLATE = 31, + WP_FRAMELEFTSIZINGTEMPLATE = 32, + WP_SMALLFRAMELEFTSIZINGTEMPLATE = 33, + WP_FRAMERIGHTSIZINGTEMPLATE = 34, + WP_SMALLFRAMERIGHTSIZINGTEMPLATE = 35, + WP_FRAMEBOTTOMSIZINGTEMPLATE = 36, + WP_SMALLFRAMEBOTTOMSIZINGTEMPLATE = 37, + WP_FRAME = 38 +}; + +enum FRAMESTATES { + FS_ACTIVE = 1, + FS_INACTIVE = 2 +}; + +enum { + BS_NORMAL = 1, + BS_HOT = 2, + BS_PUSHED = 3, + BS_DISABLED = 4, + BS_INACTIVE = 5 /* undocumented, inactive caption button */ +}; + +}}} // mozilla::widget::themeconst + +#endif diff --git a/widget/windows/nsUXThemeData.cpp b/widget/windows/nsUXThemeData.cpp new file mode 100644 index 0000000000..bcbd32484c --- /dev/null +++ b/widget/windows/nsUXThemeData.cpp @@ -0,0 +1,405 @@ +/* vim: se cin sw=2 ts=2 et : */ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/ArrayUtils.h" +#include "mozilla/WindowsVersion.h" + +#include "nsUXThemeData.h" +#include "nsDebug.h" +#include "nsToolkit.h" +#include "nsUXThemeConstants.h" + +using namespace mozilla; +using namespace mozilla::widget; + +const wchar_t +nsUXThemeData::kThemeLibraryName[] = L"uxtheme.dll"; + +HANDLE +nsUXThemeData::sThemes[eUXNumClasses]; + +HMODULE +nsUXThemeData::sThemeDLL = nullptr; + +bool +nsUXThemeData::sFlatMenus = false; + +bool nsUXThemeData::sTitlebarInfoPopulatedAero = false; +bool nsUXThemeData::sTitlebarInfoPopulatedThemed = false; +const int NUM_COMMAND_BUTTONS = 4; +SIZE nsUXThemeData::sCommandButtons[NUM_COMMAND_BUTTONS]; + +void +nsUXThemeData::Teardown() { + Invalidate(); + if(sThemeDLL) + FreeLibrary(sThemeDLL); +} + +void +nsUXThemeData::Initialize() +{ + ::ZeroMemory(sThemes, sizeof(sThemes)); + NS_ASSERTION(!sThemeDLL, "nsUXThemeData being initialized twice!"); + + CheckForCompositor(true); + Invalidate(); +} + +void +nsUXThemeData::Invalidate() { + for(int i = 0; i < eUXNumClasses; i++) { + if(sThemes[i]) { + CloseThemeData(sThemes[i]); + sThemes[i] = nullptr; + } + } + BOOL useFlat = FALSE; + sFlatMenus = ::SystemParametersInfo(SPI_GETFLATMENU, 0, &useFlat, 0) ? + useFlat : false; +} + +HANDLE +nsUXThemeData::GetTheme(nsUXThemeClass cls) { + NS_ASSERTION(cls < eUXNumClasses, "Invalid theme class!"); + if(!sThemes[cls]) + { + sThemes[cls] = OpenThemeData(nullptr, GetClassName(cls)); + } + return sThemes[cls]; +} + +HMODULE +nsUXThemeData::GetThemeDLL() { + if (!sThemeDLL) + sThemeDLL = ::LoadLibraryW(kThemeLibraryName); + return sThemeDLL; +} + +const wchar_t *nsUXThemeData::GetClassName(nsUXThemeClass cls) { + switch(cls) { + case eUXButton: + return L"Button"; + case eUXEdit: + return L"Edit"; + case eUXTooltip: + return L"Tooltip"; + case eUXRebar: + return L"Rebar"; + case eUXMediaRebar: + return L"Media::Rebar"; + case eUXCommunicationsRebar: + return L"Communications::Rebar"; + case eUXBrowserTabBarRebar: + return L"BrowserTabBar::Rebar"; + case eUXToolbar: + return L"Toolbar"; + case eUXMediaToolbar: + return L"Media::Toolbar"; + case eUXCommunicationsToolbar: + return L"Communications::Toolbar"; + case eUXProgress: + return L"Progress"; + case eUXTab: + return L"Tab"; + case eUXScrollbar: + return L"Scrollbar"; + case eUXTrackbar: + return L"Trackbar"; + case eUXSpin: + return L"Spin"; + case eUXStatus: + return L"Status"; + case eUXCombobox: + return L"Combobox"; + case eUXHeader: + return L"Header"; + case eUXListview: + return L"Listview"; + case eUXMenu: + return L"Menu"; + case eUXWindowFrame: + return L"Window"; + default: + NS_NOTREACHED("unknown uxtheme class"); + return L""; + } +} + +// static +void +nsUXThemeData::InitTitlebarInfo() +{ + // Pre-populate with generic metrics. These likley will not match + // the current theme, but they insure the buttons at least show up. + sCommandButtons[0].cx = GetSystemMetrics(SM_CXSIZE); + sCommandButtons[0].cy = GetSystemMetrics(SM_CYSIZE); + sCommandButtons[1].cx = sCommandButtons[2].cx = sCommandButtons[0].cx; + sCommandButtons[1].cy = sCommandButtons[2].cy = sCommandButtons[0].cy; + sCommandButtons[3].cx = sCommandButtons[0].cx * 3; + sCommandButtons[3].cy = sCommandButtons[0].cy; + + // Use system metrics for pre-vista, otherwise trigger a + // refresh on the next layout. + sTitlebarInfoPopulatedAero = sTitlebarInfoPopulatedThemed = + !IsVistaOrLater(); +} + +// static +void +nsUXThemeData::UpdateTitlebarInfo(HWND aWnd) +{ + if (!aWnd) + return; + + if (!sTitlebarInfoPopulatedAero && nsUXThemeData::CheckForCompositor()) { + RECT captionButtons; + if (SUCCEEDED(WinUtils::dwmGetWindowAttributePtr(aWnd, + DWMWA_CAPTION_BUTTON_BOUNDS, + &captionButtons, + sizeof(captionButtons)))) { + sCommandButtons[CMDBUTTONIDX_BUTTONBOX].cx = captionButtons.right - captionButtons.left - 3; + sCommandButtons[CMDBUTTONIDX_BUTTONBOX].cy = (captionButtons.bottom - captionButtons.top) - 1; + sTitlebarInfoPopulatedAero = true; + } + } + + // NB: sTitlebarInfoPopulatedThemed is always true pre-vista. + if (sTitlebarInfoPopulatedThemed || IsWin8OrLater()) + return; + + // Query a temporary, visible window with command buttons to get + // the right metrics. + WNDCLASSW wc; + wc.style = 0; + wc.lpfnWndProc = ::DefWindowProcW; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hInstance = nsToolkit::mDllInstance; + wc.hIcon = nullptr; + wc.hCursor = nullptr; + wc.hbrBackground = nullptr; + wc.lpszMenuName = nullptr; + wc.lpszClassName = kClassNameTemp; + ::RegisterClassW(&wc); + + // Create a transparent descendant of the window passed in. This + // keeps the window from showing up on the desktop or the taskbar. + // Note the parent (browser) window is usually still hidden, we + // don't want to display it, so we can't query it directly. + HWND hWnd = CreateWindowExW(WS_EX_LAYERED, + kClassNameTemp, L"", + WS_OVERLAPPEDWINDOW, + 0, 0, 0, 0, aWnd, nullptr, + nsToolkit::mDllInstance, nullptr); + NS_ASSERTION(hWnd, "UpdateTitlebarInfo window creation failed."); + + int showType = SW_SHOWNA; + // We try to avoid activating this window, but on Aero basic (aero without + // compositor) and aero lite (special theme for win server 2012/2013) we may + // get the wrong information if the window isn't activated, so we have to: + if (sThemeId == LookAndFeel::eWindowsTheme_AeroLite || + (sThemeId == LookAndFeel::eWindowsTheme_Aero && !nsUXThemeData::CheckForCompositor())) { + showType = SW_SHOW; + } + ShowWindow(hWnd, showType); + TITLEBARINFOEX info = {0}; + info.cbSize = sizeof(TITLEBARINFOEX); + SendMessage(hWnd, WM_GETTITLEBARINFOEX, 0, (LPARAM)&info); + DestroyWindow(hWnd); + + // Only set if we have valid data for all three buttons we use. + if ((info.rgrect[2].right - info.rgrect[2].left) == 0 || + (info.rgrect[3].right - info.rgrect[3].left) == 0 || + (info.rgrect[5].right - info.rgrect[5].left) == 0) { + NS_WARNING("WM_GETTITLEBARINFOEX query failed to find usable metrics."); + return; + } + // minimize + sCommandButtons[0].cx = info.rgrect[2].right - info.rgrect[2].left; + sCommandButtons[0].cy = info.rgrect[2].bottom - info.rgrect[2].top; + // maximize/restore + sCommandButtons[1].cx = info.rgrect[3].right - info.rgrect[3].left; + sCommandButtons[1].cy = info.rgrect[3].bottom - info.rgrect[3].top; + // close + sCommandButtons[2].cx = info.rgrect[5].right - info.rgrect[5].left; + sCommandButtons[2].cy = info.rgrect[5].bottom - info.rgrect[5].top; + +#ifdef DEBUG + // Verify that all values for the command buttons are positive values + // otherwise we have cached bad values for the caption buttons + for (int i = 0; i < NUM_COMMAND_BUTTONS; i++) { + MOZ_ASSERT(sCommandButtons[i].cx > 0); + MOZ_ASSERT(sCommandButtons[i].cy > 0); + } +#endif + + sTitlebarInfoPopulatedThemed = true; +} + +// visual style (aero glass, aero basic) +// theme (aero, luna, zune) +// theme color (silver, olive, blue) +// system colors + +struct THEMELIST { + LPCWSTR name; + int type; +}; + +const THEMELIST knownThemes[] = { + { L"aero.msstyles", WINTHEME_AERO }, + { L"aerolite.msstyles", WINTHEME_AERO_LITE }, + { L"luna.msstyles", WINTHEME_LUNA }, + { L"zune.msstyles", WINTHEME_ZUNE }, + { L"royale.msstyles", WINTHEME_ROYALE } +}; + +const THEMELIST knownColors[] = { + { L"normalcolor", WINTHEMECOLOR_NORMAL }, + { L"homestead", WINTHEMECOLOR_HOMESTEAD }, + { L"metallic", WINTHEMECOLOR_METALLIC } +}; + +LookAndFeel::WindowsTheme +nsUXThemeData::sThemeId = LookAndFeel::eWindowsTheme_Generic; + +bool +nsUXThemeData::sIsDefaultWindowsTheme = false; +bool +nsUXThemeData::sIsHighContrastOn = false; + +// static +LookAndFeel::WindowsTheme +nsUXThemeData::GetNativeThemeId() +{ + return sThemeId; +} + +// static +bool nsUXThemeData::IsDefaultWindowTheme() +{ + return sIsDefaultWindowsTheme; +} + +bool nsUXThemeData::IsHighContrastOn() +{ + return sIsHighContrastOn; +} + +// static +bool nsUXThemeData::CheckForCompositor(bool aUpdateCache) +{ + static BOOL sCachedValue = FALSE; + if (aUpdateCache && WinUtils::dwmIsCompositionEnabledPtr) { + WinUtils::dwmIsCompositionEnabledPtr(&sCachedValue); + } + return sCachedValue; +} + +// static +void +nsUXThemeData::UpdateNativeThemeInfo() +{ + // Trigger a refresh of themed button metrics if needed + sTitlebarInfoPopulatedThemed = !IsVistaOrLater(); + + sIsDefaultWindowsTheme = false; + sThemeId = LookAndFeel::eWindowsTheme_Generic; + + HIGHCONTRAST highContrastInfo; + highContrastInfo.cbSize = sizeof(HIGHCONTRAST); + if (SystemParametersInfo(SPI_GETHIGHCONTRAST, 0, &highContrastInfo, 0)) { + sIsHighContrastOn = ((highContrastInfo.dwFlags & HCF_HIGHCONTRASTON) != 0); + } else { + sIsHighContrastOn = false; + } + + if (!IsAppThemed()) { + sThemeId = LookAndFeel::eWindowsTheme_Classic; + return; + } + + WCHAR themeFileName[MAX_PATH + 1]; + WCHAR themeColor[MAX_PATH + 1]; + if (FAILED(GetCurrentThemeName(themeFileName, + MAX_PATH, + themeColor, + MAX_PATH, + nullptr, 0))) { + sThemeId = LookAndFeel::eWindowsTheme_Classic; + return; + } + + LPCWSTR themeName = wcsrchr(themeFileName, L'\\'); + themeName = themeName ? themeName + 1 : themeFileName; + + WindowsTheme theme = WINTHEME_UNRECOGNIZED; + for (size_t i = 0; i < ArrayLength(knownThemes); ++i) { + if (!lstrcmpiW(themeName, knownThemes[i].name)) { + theme = (WindowsTheme)knownThemes[i].type; + break; + } + } + + if (theme == WINTHEME_UNRECOGNIZED) + return; + + // We're using the default theme if we're using any of Aero, Aero Lite, or + // luna. However, on Win8, GetCurrentThemeName (see above) returns + // AeroLite.msstyles for the 4 builtin highcontrast themes as well. Those + // themes "don't count" as default themes, so we specifically check for high + // contrast mode in that situation. + if (!(IsWin8OrLater() && sIsHighContrastOn) && + (theme == WINTHEME_AERO || theme == WINTHEME_AERO_LITE || theme == WINTHEME_LUNA)) { + sIsDefaultWindowsTheme = true; + } + + if (theme != WINTHEME_LUNA) { + switch(theme) { + case WINTHEME_AERO: + sThemeId = LookAndFeel::eWindowsTheme_Aero; + return; + case WINTHEME_AERO_LITE: + sThemeId = LookAndFeel::eWindowsTheme_AeroLite; + return; + case WINTHEME_ZUNE: + sThemeId = LookAndFeel::eWindowsTheme_Zune; + return; + case WINTHEME_ROYALE: + sThemeId = LookAndFeel::eWindowsTheme_Royale; + return; + default: + NS_WARNING("unhandled theme type."); + return; + } + } + + // calculate the luna color scheme + WindowsThemeColor color = WINTHEMECOLOR_UNRECOGNIZED; + for (size_t i = 0; i < ArrayLength(knownColors); ++i) { + if (!lstrcmpiW(themeColor, knownColors[i].name)) { + color = (WindowsThemeColor)knownColors[i].type; + break; + } + } + + switch(color) { + case WINTHEMECOLOR_NORMAL: + sThemeId = LookAndFeel::eWindowsTheme_LunaBlue; + return; + case WINTHEMECOLOR_HOMESTEAD: + sThemeId = LookAndFeel::eWindowsTheme_LunaOlive; + return; + case WINTHEMECOLOR_METALLIC: + sThemeId = LookAndFeel::eWindowsTheme_LunaSilver; + return; + default: + NS_WARNING("unhandled theme color."); + return; + } +} diff --git a/widget/windows/nsUXThemeData.h b/widget/windows/nsUXThemeData.h new file mode 100644 index 0000000000..2688ec6592 --- /dev/null +++ b/widget/windows/nsUXThemeData.h @@ -0,0 +1,120 @@ +/* vim: se cin sw=2 ts=2 et : */ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef __UXThemeData_h__ +#define __UXThemeData_h__ +#include <windows.h> +#include <uxtheme.h> + +#include "nscore.h" +#include "mozilla/LookAndFeel.h" +#include "WinUtils.h" + +#include <dwmapi.h> + +#include "nsWindowDefs.h" + +// These window messages are not defined in dwmapi.h +#ifndef WM_DWMCOMPOSITIONCHANGED +#define WM_DWMCOMPOSITIONCHANGED 0x031E +#endif + +// Windows 7 additions +#ifndef WM_DWMSENDICONICTHUMBNAIL +#define WM_DWMSENDICONICTHUMBNAIL 0x0323 +#define WM_DWMSENDICONICLIVEPREVIEWBITMAP 0x0326 +#endif + +#define DWMWA_FORCE_ICONIC_REPRESENTATION 7 +#define DWMWA_HAS_ICONIC_BITMAP 10 + +enum nsUXThemeClass { + eUXButton = 0, + eUXEdit, + eUXTooltip, + eUXRebar, + eUXMediaRebar, + eUXCommunicationsRebar, + eUXBrowserTabBarRebar, + eUXToolbar, + eUXMediaToolbar, + eUXCommunicationsToolbar, + eUXProgress, + eUXTab, + eUXScrollbar, + eUXTrackbar, + eUXSpin, + eUXStatus, + eUXCombobox, + eUXHeader, + eUXListview, + eUXMenu, + eUXWindowFrame, + eUXNumClasses +}; + +// Native windows style constants +enum WindowsTheme { + WINTHEME_UNRECOGNIZED = 0, + WINTHEME_CLASSIC = 1, // no theme + WINTHEME_AERO = 2, + WINTHEME_LUNA = 3, + WINTHEME_ROYALE = 4, + WINTHEME_ZUNE = 5, + WINTHEME_AERO_LITE = 6 +}; +enum WindowsThemeColor { + WINTHEMECOLOR_UNRECOGNIZED = 0, + WINTHEMECOLOR_NORMAL = 1, + WINTHEMECOLOR_HOMESTEAD = 2, + WINTHEMECOLOR_METALLIC = 3 +}; + +#define CMDBUTTONIDX_MINIMIZE 0 +#define CMDBUTTONIDX_RESTORE 1 +#define CMDBUTTONIDX_CLOSE 2 +#define CMDBUTTONIDX_BUTTONBOX 3 + +class nsUXThemeData { + static HMODULE sThemeDLL; + static HANDLE sThemes[eUXNumClasses]; + + static const wchar_t *GetClassName(nsUXThemeClass); + +public: + static const wchar_t kThemeLibraryName[]; + static bool sFlatMenus; + static bool sTitlebarInfoPopulatedAero; + static bool sTitlebarInfoPopulatedThemed; + static SIZE sCommandButtons[4]; + static mozilla::LookAndFeel::WindowsTheme sThemeId; + static bool sIsDefaultWindowsTheme; + static bool sIsHighContrastOn; + + static void Initialize(); + static void Teardown(); + static void Invalidate(); + static HANDLE GetTheme(nsUXThemeClass cls); + static HMODULE GetThemeDLL(); + + // nsWindow calls this to update desktop settings info + static void InitTitlebarInfo(); + static void UpdateTitlebarInfo(HWND aWnd); + + static void UpdateNativeThemeInfo(); + static mozilla::LookAndFeel::WindowsTheme GetNativeThemeId(); + static bool IsDefaultWindowTheme(); + static bool IsHighContrastOn(); + + // This method returns the cached compositor state. Most + // callers should call without the argument. The cache + // should be modified only when the application receives + // WM_DWMCOMPOSITIONCHANGED. This rule prevents inconsistent + // results for two or more calls which check the state during + // composition transition. + static bool CheckForCompositor(bool aUpdateCache = false); +}; +#endif // __UXThemeData_h__ diff --git a/widget/windows/nsWidgetFactory.cpp b/widget/windows/nsWidgetFactory.cpp new file mode 100644 index 0000000000..7e6ffd638a --- /dev/null +++ b/widget/windows/nsWidgetFactory.cpp @@ -0,0 +1,262 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + +#include "nsIFactory.h" +#include "nsISupports.h" +#include "nsdefs.h" +#include "nsWidgetsCID.h" +#include "nsAppShell.h" +#include "nsAppShellSingleton.h" +#include "mozilla/ModuleUtils.h" +#include "mozilla/WidgetUtils.h" +#include "nsIServiceManager.h" +#include "nsIdleServiceWin.h" +#include "nsLookAndFeel.h" +#include "nsScreenManagerWin.h" +#include "nsSound.h" +#include "WinMouseScrollHandler.h" +#include "KeyboardLayout.h" +#include "GfxInfo.h" +#include "nsToolkit.h" + +// Modules that switch out based on the environment +#include "nsXULAppAPI.h" +// Desktop +#include "nsFilePicker.h" // needs to be included before other shobjidl.h includes +#include "nsColorPicker.h" +#include "nsNativeThemeWin.h" +#include "nsWindow.h" +// Content processes +#include "nsFilePickerProxy.h" + +// Drag & Drop, Clipboard +#include "nsClipboardHelper.h" +#include "nsClipboard.h" +#include "nsBidiKeyboard.h" +#include "nsDragService.h" +#include "nsTransferable.h" +#include "nsHTMLFormatConverter.h" + +#include "WinTaskbar.h" +#include "JumpListBuilder.h" +#include "JumpListItem.h" + +#include "WindowsUIUtils.h" + +#ifdef NS_PRINTING +#include "nsDeviceContextSpecWin.h" +#include "nsPrintOptionsWin.h" +#include "nsPrintSession.h" +#endif + +using namespace mozilla; +using namespace mozilla::widget; + +static nsresult +WindowConstructor(nsISupports *aOuter, REFNSIID aIID, + void **aResult) +{ + *aResult = nullptr; + if (aOuter != nullptr) { + return NS_ERROR_NO_AGGREGATION; + } + nsCOMPtr<nsIWidget> widget = new nsWindow; + return widget->QueryInterface(aIID, aResult); +} + +static nsresult +ChildWindowConstructor(nsISupports *aOuter, REFNSIID aIID, + void **aResult) +{ + *aResult = nullptr; + if (aOuter != nullptr) { + return NS_ERROR_NO_AGGREGATION; + } + nsCOMPtr<nsIWidget> widget = new ChildWindow; + return widget->QueryInterface(aIID, aResult); +} + +static nsresult +FilePickerConstructor(nsISupports *aOuter, REFNSIID aIID, + void **aResult) +{ + *aResult = nullptr; + if (aOuter != nullptr) { + return NS_ERROR_NO_AGGREGATION; + } + nsCOMPtr<nsIFilePicker> picker = new nsFilePicker; + return picker->QueryInterface(aIID, aResult); +} + +static nsresult +ColorPickerConstructor(nsISupports *aOuter, REFNSIID aIID, + void **aResult) +{ + *aResult = nullptr; + if (aOuter != nullptr) { + return NS_ERROR_NO_AGGREGATION; + } + nsCOMPtr<nsIColorPicker> picker = new nsColorPicker; + return picker->QueryInterface(aIID, aResult); +} + +NS_GENERIC_FACTORY_CONSTRUCTOR(nsScreenManagerWin) +NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsIdleServiceWin, nsIdleServiceWin::GetInstance) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsClipboard) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsClipboardHelper) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsSound) +NS_GENERIC_FACTORY_CONSTRUCTOR(WinTaskbar) +NS_GENERIC_FACTORY_CONSTRUCTOR(JumpListBuilder) +NS_GENERIC_FACTORY_CONSTRUCTOR(JumpListItem) +NS_GENERIC_FACTORY_CONSTRUCTOR(JumpListSeparator) +NS_GENERIC_FACTORY_CONSTRUCTOR(JumpListLink) +NS_GENERIC_FACTORY_CONSTRUCTOR(JumpListShortcut) +NS_GENERIC_FACTORY_CONSTRUCTOR(WindowsUIUtils) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsTransferable) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsHTMLFormatConverter) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsDragService) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsBidiKeyboard) +NS_GENERIC_FACTORY_CONSTRUCTOR(TaskbarPreviewCallback) +#ifdef NS_PRINTING +NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPrintOptionsWin, Init) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsPrinterEnumeratorWin) +NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPrintSession, Init) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsDeviceContextSpecWin) +#endif + +namespace mozilla { +namespace widget { +// This constructor should really be shared with all platforms. +NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(GfxInfo, Init) +} +} + +NS_DEFINE_NAMED_CID(NS_WINDOW_CID); +NS_DEFINE_NAMED_CID(NS_CHILD_CID); +NS_DEFINE_NAMED_CID(NS_FILEPICKER_CID); +NS_DEFINE_NAMED_CID(NS_COLORPICKER_CID); +NS_DEFINE_NAMED_CID(NS_APPSHELL_CID); +NS_DEFINE_NAMED_CID(NS_SCREENMANAGER_CID); +NS_DEFINE_NAMED_CID(NS_GFXINFO_CID); +NS_DEFINE_NAMED_CID(NS_THEMERENDERER_CID); +NS_DEFINE_NAMED_CID(NS_IDLE_SERVICE_CID); +NS_DEFINE_NAMED_CID(NS_CLIPBOARD_CID); +NS_DEFINE_NAMED_CID(NS_CLIPBOARDHELPER_CID); +NS_DEFINE_NAMED_CID(NS_SOUND_CID); +NS_DEFINE_NAMED_CID(NS_TRANSFERABLE_CID); +NS_DEFINE_NAMED_CID(NS_HTMLFORMATCONVERTER_CID); +NS_DEFINE_NAMED_CID(NS_WIN_TASKBAR_CID); +NS_DEFINE_NAMED_CID(NS_WIN_JUMPLISTBUILDER_CID); +NS_DEFINE_NAMED_CID(NS_WIN_JUMPLISTITEM_CID); +NS_DEFINE_NAMED_CID(NS_WIN_JUMPLISTSEPARATOR_CID); +NS_DEFINE_NAMED_CID(NS_WIN_JUMPLISTLINK_CID); +NS_DEFINE_NAMED_CID(NS_WIN_JUMPLISTSHORTCUT_CID); +NS_DEFINE_NAMED_CID(NS_WINDOWS_UIUTILS_CID); +NS_DEFINE_NAMED_CID(NS_DRAGSERVICE_CID); +NS_DEFINE_NAMED_CID(NS_BIDIKEYBOARD_CID); +NS_DEFINE_NAMED_CID(NS_TASKBARPREVIEWCALLBACK_CID); +#ifdef NS_PRINTING +NS_DEFINE_NAMED_CID(NS_PRINTSETTINGSSERVICE_CID); +NS_DEFINE_NAMED_CID(NS_PRINTER_ENUMERATOR_CID); +NS_DEFINE_NAMED_CID(NS_PRINTSESSION_CID); +NS_DEFINE_NAMED_CID(NS_DEVICE_CONTEXT_SPEC_CID); +#endif + + +static const mozilla::Module::CIDEntry kWidgetCIDs[] = { + { &kNS_WINDOW_CID, false, nullptr, WindowConstructor }, + { &kNS_CHILD_CID, false, nullptr, ChildWindowConstructor }, + { &kNS_FILEPICKER_CID, false, nullptr, FilePickerConstructor, Module::MAIN_PROCESS_ONLY }, + { &kNS_COLORPICKER_CID, false, nullptr, ColorPickerConstructor, Module::MAIN_PROCESS_ONLY }, + { &kNS_APPSHELL_CID, false, nullptr, nsAppShellConstructor, Module::ALLOW_IN_GPU_PROCESS }, + { &kNS_SCREENMANAGER_CID, false, nullptr, nsScreenManagerWinConstructor, + Module::MAIN_PROCESS_ONLY }, + { &kNS_GFXINFO_CID, false, nullptr, GfxInfoConstructor }, + { &kNS_THEMERENDERER_CID, false, nullptr, NS_NewNativeTheme }, + { &kNS_IDLE_SERVICE_CID, false, nullptr, nsIdleServiceWinConstructor }, + { &kNS_CLIPBOARD_CID, false, nullptr, nsClipboardConstructor, Module::MAIN_PROCESS_ONLY }, + { &kNS_CLIPBOARDHELPER_CID, false, nullptr, nsClipboardHelperConstructor }, + { &kNS_SOUND_CID, false, nullptr, nsSoundConstructor, Module::MAIN_PROCESS_ONLY }, + { &kNS_TRANSFERABLE_CID, false, nullptr, nsTransferableConstructor }, + { &kNS_HTMLFORMATCONVERTER_CID, false, nullptr, nsHTMLFormatConverterConstructor }, + { &kNS_WIN_TASKBAR_CID, false, nullptr, WinTaskbarConstructor }, + { &kNS_WIN_JUMPLISTBUILDER_CID, false, nullptr, JumpListBuilderConstructor }, + { &kNS_WIN_JUMPLISTITEM_CID, false, nullptr, JumpListItemConstructor }, + { &kNS_WIN_JUMPLISTSEPARATOR_CID, false, nullptr, JumpListSeparatorConstructor }, + { &kNS_WIN_JUMPLISTLINK_CID, false, nullptr, JumpListLinkConstructor }, + { &kNS_WIN_JUMPLISTSHORTCUT_CID, false, nullptr, JumpListShortcutConstructor }, + { &kNS_WINDOWS_UIUTILS_CID, false, nullptr, WindowsUIUtilsConstructor }, + { &kNS_DRAGSERVICE_CID, false, nullptr, nsDragServiceConstructor, Module::MAIN_PROCESS_ONLY }, + { &kNS_BIDIKEYBOARD_CID, false, nullptr, nsBidiKeyboardConstructor, Module::MAIN_PROCESS_ONLY }, + { &kNS_TASKBARPREVIEWCALLBACK_CID, false, nullptr, TaskbarPreviewCallbackConstructor }, +#ifdef NS_PRINTING + { &kNS_PRINTSETTINGSSERVICE_CID, false, nullptr, nsPrintOptionsWinConstructor }, + { &kNS_PRINTER_ENUMERATOR_CID, false, nullptr, nsPrinterEnumeratorWinConstructor }, + { &kNS_PRINTSESSION_CID, false, nullptr, nsPrintSessionConstructor }, + { &kNS_DEVICE_CONTEXT_SPEC_CID, false, nullptr, nsDeviceContextSpecWinConstructor }, +#endif + { nullptr } +}; + +static const mozilla::Module::ContractIDEntry kWidgetContracts[] = { + { "@mozilla.org/widgets/window/win;1", &kNS_WINDOW_CID }, + { "@mozilla.org/widgets/child_window/win;1", &kNS_CHILD_CID }, + { "@mozilla.org/filepicker;1", &kNS_FILEPICKER_CID, Module::MAIN_PROCESS_ONLY }, + { "@mozilla.org/colorpicker;1", &kNS_COLORPICKER_CID, Module::MAIN_PROCESS_ONLY }, + { "@mozilla.org/widget/appshell/win;1", &kNS_APPSHELL_CID, Module::ALLOW_IN_GPU_PROCESS }, + { "@mozilla.org/gfx/screenmanager;1", &kNS_SCREENMANAGER_CID, Module::MAIN_PROCESS_ONLY }, + { "@mozilla.org/gfx/info;1", &kNS_GFXINFO_CID }, + { "@mozilla.org/chrome/chrome-native-theme;1", &kNS_THEMERENDERER_CID }, + { "@mozilla.org/widget/idleservice;1", &kNS_IDLE_SERVICE_CID }, + { "@mozilla.org/widget/clipboard;1", &kNS_CLIPBOARD_CID, Module::MAIN_PROCESS_ONLY }, + { "@mozilla.org/widget/clipboardhelper;1", &kNS_CLIPBOARDHELPER_CID }, + { "@mozilla.org/sound;1", &kNS_SOUND_CID, Module::MAIN_PROCESS_ONLY }, + { "@mozilla.org/widget/transferable;1", &kNS_TRANSFERABLE_CID }, + { "@mozilla.org/widget/htmlformatconverter;1", &kNS_HTMLFORMATCONVERTER_CID }, + { "@mozilla.org/windows-taskbar;1", &kNS_WIN_TASKBAR_CID }, + { "@mozilla.org/windows-jumplistbuilder;1", &kNS_WIN_JUMPLISTBUILDER_CID }, + { "@mozilla.org/windows-jumplistitem;1", &kNS_WIN_JUMPLISTITEM_CID }, + { "@mozilla.org/windows-jumplistseparator;1", &kNS_WIN_JUMPLISTSEPARATOR_CID }, + { "@mozilla.org/windows-jumplistlink;1", &kNS_WIN_JUMPLISTLINK_CID }, + { "@mozilla.org/windows-jumplistshortcut;1", &kNS_WIN_JUMPLISTSHORTCUT_CID }, + { "@mozilla.org/windows-ui-utils;1", &kNS_WINDOWS_UIUTILS_CID }, + { "@mozilla.org/widget/dragservice;1", &kNS_DRAGSERVICE_CID, Module::MAIN_PROCESS_ONLY }, + { "@mozilla.org/widget/bidikeyboard;1", &kNS_BIDIKEYBOARD_CID, Module::MAIN_PROCESS_ONLY }, + { "@mozilla.org/widget/taskbar-preview-callback;1", &kNS_TASKBARPREVIEWCALLBACK_CID }, +#ifdef NS_PRINTING + { "@mozilla.org/gfx/printsettings-service;1", &kNS_PRINTSETTINGSSERVICE_CID }, + { "@mozilla.org/gfx/printerenumerator;1", &kNS_PRINTER_ENUMERATOR_CID }, + { "@mozilla.org/gfx/printsession;1", &kNS_PRINTSESSION_CID }, + { "@mozilla.org/gfx/devicecontextspec;1", &kNS_DEVICE_CONTEXT_SPEC_CID }, +#endif + { nullptr } +}; + +static void +nsWidgetWindowsModuleDtor() +{ + // Shutdown all XP level widget classes. + WidgetUtils::Shutdown(); + + KeyboardLayout::Shutdown(); + MouseScrollHandler::Shutdown(); + nsLookAndFeel::Shutdown(); + nsToolkit::Shutdown(); + nsAppShellShutdown(); +} + +static const mozilla::Module kWidgetModule = { + mozilla::Module::kVersion, + kWidgetCIDs, + kWidgetContracts, + nullptr, + nullptr, + nsAppShellInit, + nsWidgetWindowsModuleDtor, + Module::ALLOW_IN_GPU_PROCESS +}; + +NSMODULE_DEFN(nsWidgetModule) = &kWidgetModule; diff --git a/widget/windows/nsWinGesture.cpp b/widget/windows/nsWinGesture.cpp new file mode 100644 index 0000000000..faec5ec220 --- /dev/null +++ b/widget/windows/nsWinGesture.cpp @@ -0,0 +1,590 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * nsWinGesture - Touch input handling for tablet displays. + */ + +#include "nscore.h" +#include "nsWinGesture.h" +#include "nsUXThemeData.h" +#include "nsIDOMSimpleGestureEvent.h" +#include "nsIDOMWheelEvent.h" +#include "mozilla/Logging.h" +#include "mozilla/MouseEvents.h" +#include "mozilla/Preferences.h" +#include "mozilla/TouchEvents.h" + +#include <cmath> + +using namespace mozilla; +using namespace mozilla::widget; + +extern mozilla::LazyLogModule gWindowsLog; + +const wchar_t nsWinGesture::kGestureLibraryName[] = L"user32.dll"; +HMODULE nsWinGesture::sLibraryHandle = nullptr; +nsWinGesture::GetGestureInfoPtr nsWinGesture::getGestureInfo = nullptr; +nsWinGesture::CloseGestureInfoHandlePtr nsWinGesture::closeGestureInfoHandle = nullptr; +nsWinGesture::GetGestureExtraArgsPtr nsWinGesture::getGestureExtraArgs = nullptr; +nsWinGesture::SetGestureConfigPtr nsWinGesture::setGestureConfig = nullptr; +nsWinGesture::GetGestureConfigPtr nsWinGesture::getGestureConfig = nullptr; +nsWinGesture::BeginPanningFeedbackPtr nsWinGesture::beginPanningFeedback = nullptr; +nsWinGesture::EndPanningFeedbackPtr nsWinGesture::endPanningFeedback = nullptr; +nsWinGesture::UpdatePanningFeedbackPtr nsWinGesture::updatePanningFeedback = nullptr; + +nsWinGesture::RegisterTouchWindowPtr nsWinGesture::registerTouchWindow = nullptr; +nsWinGesture::UnregisterTouchWindowPtr nsWinGesture::unregisterTouchWindow = nullptr; +nsWinGesture::GetTouchInputInfoPtr nsWinGesture::getTouchInputInfo = nullptr; +nsWinGesture::CloseTouchInputHandlePtr nsWinGesture::closeTouchInputHandle = nullptr; + +static bool gEnableSingleFingerPanEvents = false; + +nsWinGesture::nsWinGesture() : + mPanActive(false), + mFeedbackActive(false), + mXAxisFeedback(false), + mYAxisFeedback(false), + mPanInertiaActive(false) +{ + (void)InitLibrary(); + mPixelScrollOverflow = 0; +} + +/* Load and shutdown */ + +bool nsWinGesture::InitLibrary() +{ + if (getGestureInfo) { + return true; + } else if (sLibraryHandle) { + return false; + } + + sLibraryHandle = ::LoadLibraryW(kGestureLibraryName); + HMODULE hTheme = nsUXThemeData::GetThemeDLL(); + + // gesture interfaces + if (sLibraryHandle) { + getGestureInfo = (GetGestureInfoPtr)GetProcAddress(sLibraryHandle, "GetGestureInfo"); + closeGestureInfoHandle = (CloseGestureInfoHandlePtr)GetProcAddress(sLibraryHandle, "CloseGestureInfoHandle"); + getGestureExtraArgs = (GetGestureExtraArgsPtr)GetProcAddress(sLibraryHandle, "GetGestureExtraArgs"); + setGestureConfig = (SetGestureConfigPtr)GetProcAddress(sLibraryHandle, "SetGestureConfig"); + getGestureConfig = (GetGestureConfigPtr)GetProcAddress(sLibraryHandle, "GetGestureConfig"); + registerTouchWindow = (RegisterTouchWindowPtr)GetProcAddress(sLibraryHandle, "RegisterTouchWindow"); + unregisterTouchWindow = (UnregisterTouchWindowPtr)GetProcAddress(sLibraryHandle, "UnregisterTouchWindow"); + getTouchInputInfo = (GetTouchInputInfoPtr)GetProcAddress(sLibraryHandle, "GetTouchInputInfo"); + closeTouchInputHandle = (CloseTouchInputHandlePtr)GetProcAddress(sLibraryHandle, "CloseTouchInputHandle"); + } + + if (!getGestureInfo || !closeGestureInfoHandle || !getGestureExtraArgs || + !setGestureConfig || !getGestureConfig) { + getGestureInfo = nullptr; + closeGestureInfoHandle = nullptr; + getGestureExtraArgs = nullptr; + setGestureConfig = nullptr; + getGestureConfig = nullptr; + return false; + } + + if (!registerTouchWindow || !unregisterTouchWindow || !getTouchInputInfo || !closeTouchInputHandle) { + registerTouchWindow = nullptr; + unregisterTouchWindow = nullptr; + getTouchInputInfo = nullptr; + closeTouchInputHandle = nullptr; + } + + // panning feedback interfaces + if (hTheme) { + beginPanningFeedback = (BeginPanningFeedbackPtr)GetProcAddress(hTheme, "BeginPanningFeedback"); + endPanningFeedback = (EndPanningFeedbackPtr)GetProcAddress(hTheme, "EndPanningFeedback"); + updatePanningFeedback = (UpdatePanningFeedbackPtr)GetProcAddress(hTheme, "UpdatePanningFeedback"); + } + + if (!beginPanningFeedback || !endPanningFeedback || !updatePanningFeedback) { + beginPanningFeedback = nullptr; + endPanningFeedback = nullptr; + updatePanningFeedback = nullptr; + } + + // Check to see if we want single finger gesture input. Only do this once + // for the app so we don't have to look it up on every window create. + gEnableSingleFingerPanEvents = + Preferences::GetBool("gestures.enable_single_finger_input", false); + + return true; +} + +#define GCOUNT 5 + +bool nsWinGesture::SetWinGestureSupport(HWND hWnd, + WidgetGestureNotifyEvent::PanDirection aDirection) +{ + if (!getGestureInfo) + return false; + + GESTURECONFIG config[GCOUNT]; + + memset(&config, 0, sizeof(config)); + + config[0].dwID = GID_ZOOM; + config[0].dwWant = GC_ZOOM; + config[0].dwBlock = 0; + + config[1].dwID = GID_ROTATE; + config[1].dwWant = GC_ROTATE; + config[1].dwBlock = 0; + + config[2].dwID = GID_PAN; + config[2].dwWant = GC_PAN|GC_PAN_WITH_INERTIA| + GC_PAN_WITH_GUTTER; + config[2].dwBlock = GC_PAN_WITH_SINGLE_FINGER_VERTICALLY| + GC_PAN_WITH_SINGLE_FINGER_HORIZONTALLY; + + if (gEnableSingleFingerPanEvents) { + + if (aDirection == WidgetGestureNotifyEvent::ePanVertical || + aDirection == WidgetGestureNotifyEvent::ePanBoth) + { + config[2].dwWant |= GC_PAN_WITH_SINGLE_FINGER_VERTICALLY; + config[2].dwBlock -= GC_PAN_WITH_SINGLE_FINGER_VERTICALLY; + } + + if (aDirection == WidgetGestureNotifyEvent::ePanHorizontal || + aDirection == WidgetGestureNotifyEvent::ePanBoth) + { + config[2].dwWant |= GC_PAN_WITH_SINGLE_FINGER_HORIZONTALLY; + config[2].dwBlock -= GC_PAN_WITH_SINGLE_FINGER_HORIZONTALLY; + } + + } + + config[3].dwWant = GC_TWOFINGERTAP; + config[3].dwID = GID_TWOFINGERTAP; + config[3].dwBlock = 0; + + config[4].dwWant = GC_PRESSANDTAP; + config[4].dwID = GID_PRESSANDTAP; + config[4].dwBlock = 0; + + return SetGestureConfig(hWnd, GCOUNT, (PGESTURECONFIG)&config); +} + +/* Helpers */ + +bool nsWinGesture::IsAvailable() +{ + return getGestureInfo != nullptr; +} + +bool nsWinGesture::RegisterTouchWindow(HWND hWnd) +{ + if (!registerTouchWindow) + return false; + + return registerTouchWindow(hWnd, TWF_WANTPALM); +} + +bool nsWinGesture::UnregisterTouchWindow(HWND hWnd) +{ + if (!unregisterTouchWindow) + return false; + + return unregisterTouchWindow(hWnd); +} + +bool nsWinGesture::GetTouchInputInfo(HTOUCHINPUT hTouchInput, uint32_t cInputs, PTOUCHINPUT pInputs) +{ + if (!getTouchInputInfo) + return false; + + return getTouchInputInfo(hTouchInput, cInputs, pInputs, sizeof(TOUCHINPUT)); +} + +bool nsWinGesture::CloseTouchInputHandle(HTOUCHINPUT hTouchInput) +{ + if (!closeTouchInputHandle) + return false; + + return closeTouchInputHandle(hTouchInput); +} + +bool nsWinGesture::GetGestureInfo(HGESTUREINFO hGestureInfo, PGESTUREINFO pGestureInfo) +{ + if (!getGestureInfo || !hGestureInfo || !pGestureInfo) + return false; + + ZeroMemory(pGestureInfo, sizeof(GESTUREINFO)); + pGestureInfo->cbSize = sizeof(GESTUREINFO); + + return getGestureInfo(hGestureInfo, pGestureInfo); +} + +bool nsWinGesture::CloseGestureInfoHandle(HGESTUREINFO hGestureInfo) +{ + if (!getGestureInfo || !hGestureInfo) + return false; + + return closeGestureInfoHandle(hGestureInfo); +} + +bool nsWinGesture::GetGestureExtraArgs(HGESTUREINFO hGestureInfo, UINT cbExtraArgs, PBYTE pExtraArgs) +{ + if (!getGestureInfo || !hGestureInfo || !pExtraArgs) + return false; + + return getGestureExtraArgs(hGestureInfo, cbExtraArgs, pExtraArgs); +} + +bool nsWinGesture::SetGestureConfig(HWND hWnd, UINT cIDs, PGESTURECONFIG pGestureConfig) +{ + if (!getGestureInfo || !pGestureConfig) + return false; + + return setGestureConfig(hWnd, 0, cIDs, pGestureConfig, sizeof(GESTURECONFIG)); +} + +bool nsWinGesture::GetGestureConfig(HWND hWnd, DWORD dwFlags, PUINT pcIDs, PGESTURECONFIG pGestureConfig) +{ + if (!getGestureInfo || !pGestureConfig) + return false; + + return getGestureConfig(hWnd, 0, dwFlags, pcIDs, pGestureConfig, sizeof(GESTURECONFIG)); +} + +bool nsWinGesture::BeginPanningFeedback(HWND hWnd) +{ + if (!beginPanningFeedback) + return false; + + return beginPanningFeedback(hWnd); +} + +bool nsWinGesture::EndPanningFeedback(HWND hWnd) +{ + if (!beginPanningFeedback) + return false; + + return endPanningFeedback(hWnd, TRUE); +} + +bool nsWinGesture::UpdatePanningFeedback(HWND hWnd, LONG offsetX, LONG offsetY, BOOL fInInertia) +{ + if (!beginPanningFeedback) + return false; + + return updatePanningFeedback(hWnd, offsetX, offsetY, fInInertia); +} + +bool nsWinGesture::IsPanEvent(LPARAM lParam) +{ + GESTUREINFO gi; + + ZeroMemory(&gi,sizeof(GESTUREINFO)); + gi.cbSize = sizeof(GESTUREINFO); + + BOOL result = GetGestureInfo((HGESTUREINFO)lParam, &gi); + if (!result) + return false; + + if (gi.dwID == GID_PAN) + return true; + + return false; +} + +/* Gesture event processing */ + +bool +nsWinGesture::ProcessGestureMessage(HWND hWnd, WPARAM wParam, LPARAM lParam, + WidgetSimpleGestureEvent& evt) +{ + GESTUREINFO gi; + + ZeroMemory(&gi,sizeof(GESTUREINFO)); + gi.cbSize = sizeof(GESTUREINFO); + + BOOL result = GetGestureInfo((HGESTUREINFO)lParam, &gi); + if (!result) + return false; + + // The coordinates of this event + nsPointWin coord; + coord = gi.ptsLocation; + coord.ScreenToClient(hWnd); + + evt.mRefPoint = LayoutDeviceIntPoint(coord.x, coord.y); + + // Multiple gesture can occur at the same time so gesture state + // info can't be shared. + switch(gi.dwID) + { + case GID_BEGIN: + case GID_END: + // These should always fall through to DefWndProc + return false; + break; + + case GID_ZOOM: + { + if (gi.dwFlags & GF_BEGIN) { + // Send a zoom start event + + // The low 32 bits are the distance in pixels. + mZoomIntermediate = (float)gi.ullArguments; + + evt.mMessage = eMagnifyGestureStart; + evt.mDelta = 0.0; + } + else if (gi.dwFlags & GF_END) { + // Send a zoom end event, the delta is the change + // in touch points. + evt.mMessage = eMagnifyGesture; + // (positive for a "zoom in") + evt.mDelta = -1.0 * (mZoomIntermediate - (float)gi.ullArguments); + mZoomIntermediate = (float)gi.ullArguments; + } + else { + // Send a zoom intermediate event, the delta is the change + // in touch points. + evt.mMessage = eMagnifyGestureUpdate; + // (positive for a "zoom in") + evt.mDelta = -1.0 * (mZoomIntermediate - (float)gi.ullArguments); + mZoomIntermediate = (float)gi.ullArguments; + } + } + break; + + case GID_ROTATE: + { + // Send a rotate start event + double radians = 0.0; + + // On GF_BEGIN, ullArguments contains the absolute rotation at the + // start of the gesture. In later events it contains the offset from + // the start angle. + if (gi.ullArguments != 0) + radians = GID_ROTATE_ANGLE_FROM_ARGUMENT(gi.ullArguments); + + double degrees = -1 * radians * (180/M_PI); + + if (gi.dwFlags & GF_BEGIN) { + // At some point we should pass the initial angle in + // along with delta. It's useful. + degrees = mRotateIntermediate = 0.0; + } + + evt.mDirection = 0; + evt.mDelta = degrees - mRotateIntermediate; + mRotateIntermediate = degrees; + + if (evt.mDelta > 0) + evt.mDirection = nsIDOMSimpleGestureEvent::ROTATION_COUNTERCLOCKWISE; + else if (evt.mDelta < 0) + evt.mDirection = nsIDOMSimpleGestureEvent::ROTATION_CLOCKWISE; + + if (gi.dwFlags & GF_BEGIN) { + evt.mMessage = eRotateGestureStart; + } else if (gi.dwFlags & GF_END) { + evt.mMessage = eRotateGesture; + } else { + evt.mMessage = eRotateGestureUpdate; + } + } + break; + + case GID_TWOFINGERTAP: + // Normally maps to "restore" from whatever you may have recently changed. + // A simple double click. + evt.mMessage = eTapGesture; + evt.mClickCount = 1; + break; + + case GID_PRESSANDTAP: + // Two finger right click. Defaults to right click if it falls through. + evt.mMessage = ePressTapGesture; + evt.mClickCount = 1; + break; + } + + return true; +} + +bool +nsWinGesture::ProcessPanMessage(HWND hWnd, WPARAM wParam, LPARAM lParam) +{ + GESTUREINFO gi; + + ZeroMemory(&gi,sizeof(GESTUREINFO)); + gi.cbSize = sizeof(GESTUREINFO); + + BOOL result = GetGestureInfo((HGESTUREINFO)lParam, &gi); + if (!result) + return false; + + // The coordinates of this event + nsPointWin coord; + coord = mPanRefPoint = gi.ptsLocation; + // We want screen coordinates in our local offsets as client coordinates will change + // when feedback is taking place. Gui events though require client coordinates. + mPanRefPoint.ScreenToClient(hWnd); + + switch(gi.dwID) + { + case GID_BEGIN: + case GID_END: + // These should always fall through to DefWndProc + return false; + break; + + // Setup pixel scroll events for both axis + case GID_PAN: + { + if (gi.dwFlags & GF_BEGIN) { + mPanIntermediate = coord; + mPixelScrollDelta = 0; + mPanActive = true; + mPanInertiaActive = false; + } + else { + +#ifdef DBG_jimm + int32_t deltaX = mPanIntermediate.x - coord.x; + int32_t deltaY = mPanIntermediate.y - coord.y; + MOZ_LOG(gWindowsLog, LogLevel::Info, + ("coordX=%d coordY=%d deltaX=%d deltaY=%d x:%d y:%d\n", coord.x, + coord.y, deltaX, deltaY, mXAxisFeedback, mYAxisFeedback)); +#endif + + mPixelScrollDelta.x = mPanIntermediate.x - coord.x; + mPixelScrollDelta.y = mPanIntermediate.y - coord.y; + mPanIntermediate = coord; + + if (gi.dwFlags & GF_INERTIA) + mPanInertiaActive = true; + + if (gi.dwFlags & GF_END) { + mPanActive = false; + mPanInertiaActive = false; + PanFeedbackFinalize(hWnd, true); + } + } + } + break; + } + return true; +} + +inline bool TestTransition(int32_t a, int32_t b) +{ + // If a is zero, overflow is zero, implying the cursor has moved back to the start position. + // If b is zero, cached overscroll is zero, implying feedback just begun. + if (a == 0 || b == 0) return true; + // Test for different signs. + return (a < 0) == (b < 0); +} + +void +nsWinGesture::UpdatePanFeedbackX(HWND hWnd, int32_t scrollOverflow, bool& endFeedback) +{ + // If scroll overflow was returned indicating we panned past the bounds of + // the scrollable view port, start feeback. + if (scrollOverflow != 0) { + if (!mFeedbackActive) { + BeginPanningFeedback(hWnd); + mFeedbackActive = true; + } + endFeedback = false; + mXAxisFeedback = true; + return; + } + + if (mXAxisFeedback) { + int32_t newOverflow = mPixelScrollOverflow.x - mPixelScrollDelta.x; + + // Detect a reverse transition past the starting drag point. This tells us the user + // has panned all the way back so we can stop providing feedback for this axis. + if (!TestTransition(newOverflow, mPixelScrollOverflow.x) || newOverflow == 0) + return; + + // Cache the total over scroll in pixels. + mPixelScrollOverflow.x = newOverflow; + endFeedback = false; + } +} + +void +nsWinGesture::UpdatePanFeedbackY(HWND hWnd, int32_t scrollOverflow, bool& endFeedback) +{ + // If scroll overflow was returned indicating we panned past the bounds of + // the scrollable view port, start feeback. + if (scrollOverflow != 0) { + if (!mFeedbackActive) { + BeginPanningFeedback(hWnd); + mFeedbackActive = true; + } + endFeedback = false; + mYAxisFeedback = true; + return; + } + + if (mYAxisFeedback) { + int32_t newOverflow = mPixelScrollOverflow.y - mPixelScrollDelta.y; + + // Detect a reverse transition past the starting drag point. This tells us the user + // has panned all the way back so we can stop providing feedback for this axis. + if (!TestTransition(newOverflow, mPixelScrollOverflow.y) || newOverflow == 0) + return; + + // Cache the total over scroll in pixels. + mPixelScrollOverflow.y = newOverflow; + endFeedback = false; + } +} + +void +nsWinGesture::PanFeedbackFinalize(HWND hWnd, bool endFeedback) +{ + if (!mFeedbackActive) + return; + + if (endFeedback) { + mFeedbackActive = false; + mXAxisFeedback = false; + mYAxisFeedback = false; + mPixelScrollOverflow = 0; + EndPanningFeedback(hWnd); + return; + } + + UpdatePanningFeedback(hWnd, mPixelScrollOverflow.x, mPixelScrollOverflow.y, mPanInertiaActive); +} + +bool +nsWinGesture::PanDeltaToPixelScroll(WidgetWheelEvent& aWheelEvent) +{ + aWheelEvent.mDeltaX = aWheelEvent.mDeltaY = aWheelEvent.mDeltaZ = 0.0; + aWheelEvent.mLineOrPageDeltaX = aWheelEvent.mLineOrPageDeltaY = 0; + + aWheelEvent.mRefPoint = LayoutDeviceIntPoint(mPanRefPoint.x, mPanRefPoint.y); + aWheelEvent.mDeltaMode = nsIDOMWheelEvent::DOM_DELTA_PIXEL; + aWheelEvent.mScrollType = WidgetWheelEvent::SCROLL_SYNCHRONOUSLY; + aWheelEvent.mIsNoLineOrPageDelta = true; + + aWheelEvent.mOverflowDeltaX = 0.0; + aWheelEvent.mOverflowDeltaY = 0.0; + + // Don't scroll the view if we are currently at a bounds, or, if we are + // panning back from a max feedback position. This keeps the original drag point + // constant. + if (!mXAxisFeedback) { + aWheelEvent.mDeltaX = mPixelScrollDelta.x; + } + if (!mYAxisFeedback) { + aWheelEvent.mDeltaY = mPixelScrollDelta.y; + } + + return (aWheelEvent.mDeltaX != 0 || aWheelEvent.mDeltaY != 0); +} diff --git a/widget/windows/nsWinGesture.h b/widget/windows/nsWinGesture.h new file mode 100644 index 0000000000..24c1f6b2d8 --- /dev/null +++ b/widget/windows/nsWinGesture.h @@ -0,0 +1,288 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef WinGesture_h__ +#define WinGesture_h__ + +/* + * nsWinGesture - Touch input handling for tablet displays. + */ + +#include "nsdefs.h" +#include <winuser.h> +#include <tpcshrd.h> +#include "nsPoint.h" +#include "mozilla/EventForwards.h" +#include "mozilla/TouchEvents.h" + +// Desktop builds target apis for 502. Win8 Metro builds target 602. +#if WINVER < 0x0602 + +DECLARE_HANDLE(HGESTUREINFO); + +/* + * Gesture flags - GESTUREINFO.dwFlags + */ +#define GF_BEGIN 0x00000001 +#define GF_INERTIA 0x00000002 +#define GF_END 0x00000004 + +/* + * Gesture configuration structure + * - Used in SetGestureConfig and GetGestureConfig + * - Note that any setting not included in either GESTURECONFIG.dwWant or + * GESTURECONFIG.dwBlock will use the parent window's preferences or + * system defaults. + */ +typedef struct tagGESTURECONFIG { + DWORD dwID; // gesture ID + DWORD dwWant; // settings related to gesture ID that are to be turned on + DWORD dwBlock; // settings related to gesture ID that are to be turned off +} GESTURECONFIG, *PGESTURECONFIG; + +/* + * Gesture information structure + * - Pass the HGESTUREINFO received in the WM_GESTURE message lParam into the + * GetGestureInfo function to retrieve this information. + * - If cbExtraArgs is non-zero, pass the HGESTUREINFO received in the WM_GESTURE + * message lParam into the GetGestureExtraArgs function to retrieve extended + * argument information. + */ +typedef struct tagGESTUREINFO { + UINT cbSize; // size, in bytes, of this structure (including variable length Args field) + DWORD dwFlags; // see GF_* flags + DWORD dwID; // gesture ID, see GID_* defines + HWND hwndTarget; // handle to window targeted by this gesture + POINTS ptsLocation; // current location of this gesture + DWORD dwInstanceID; // internally used + DWORD dwSequenceID; // internally used + ULONGLONG ullArguments; // arguments for gestures whose arguments fit in 8 BYTES + UINT cbExtraArgs; // size, in bytes, of extra arguments, if any, that accompany this gesture +} GESTUREINFO, *PGESTUREINFO; +typedef GESTUREINFO const * PCGESTUREINFO; + +/* + * Gesture notification structure + * - The WM_GESTURENOTIFY message lParam contains a pointer to this structure. + * - The WM_GESTURENOTIFY message notifies a window that gesture recognition is + * in progress and a gesture will be generated if one is recognized under the + * current gesture settings. + */ +typedef struct tagGESTURENOTIFYSTRUCT { + UINT cbSize; // size, in bytes, of this structure + DWORD dwFlags; // unused + HWND hwndTarget; // handle to window targeted by the gesture + POINTS ptsLocation; // starting location + DWORD dwInstanceID; // internally used +} GESTURENOTIFYSTRUCT, *PGESTURENOTIFYSTRUCT; + + +/* + * Gesture argument helpers + * - Angle should be a double in the range of -2pi to +2pi + * - Argument should be an unsigned 16-bit value + */ +#define GID_ROTATE_ANGLE_TO_ARGUMENT(_arg_) ((USHORT)((((_arg_) + 2.0 * 3.14159265) / (4.0 * 3.14159265)) * 65535.0)) +#define GID_ROTATE_ANGLE_FROM_ARGUMENT(_arg_) ((((double)(_arg_) / 65535.0) * 4.0 * 3.14159265) - 2.0 * 3.14159265) + +/* + * Gesture configuration flags + */ +#define GC_ALLGESTURES 0x00000001 + +#define GC_ZOOM 0x00000001 + +#define GC_PAN 0x00000001 +#define GC_PAN_WITH_SINGLE_FINGER_VERTICALLY 0x00000002 +#define GC_PAN_WITH_SINGLE_FINGER_HORIZONTALLY 0x00000004 +#define GC_PAN_WITH_GUTTER 0x00000008 +#define GC_PAN_WITH_INERTIA 0x00000010 + +#define GC_ROTATE 0x00000001 + +#define GC_TWOFINGERTAP 0x00000001 + +#define GC_PRESSANDTAP 0x00000001 + +/* + * Gesture IDs + */ +#define GID_BEGIN 1 +#define GID_END 2 +#define GID_ZOOM 3 +#define GID_PAN 4 +#define GID_ROTATE 5 +#define GID_TWOFINGERTAP 6 +#define GID_PRESSANDTAP 7 + +// Maximum number of gestures that can be included +// in a single call to SetGestureConfig / GetGestureConfig +#define GESTURECONFIGMAXCOUNT 256 + +// If specified, GetGestureConfig returns consolidated configuration +// for the specified window and it's parent window chain +#define GCF_INCLUDE_ANCESTORS 0x00000001 + +// Window events we need to respond to or receive +#define WM_GESTURE 0x0119 +#define WM_GESTURENOTIFY 0x011A + +typedef struct _TOUCHINPUT { + LONG x; + LONG y; + HANDLE hSource; + DWORD dwID; + DWORD dwFlags; + DWORD dwMask; + DWORD dwTime; + ULONG_PTR dwExtraInfo; + DWORD cxContact; + DWORD cyContact; +} TOUCHINPUT, *PTOUCHINPUT; + +typedef HANDLE HTOUCHINPUT; + +#define WM_TOUCH 0x0240 + +#define TOUCHEVENTF_MOVE 0x0001 +#define TOUCHEVENTF_DOWN 0x0002 +#define TOUCHEVENTF_UP 0x0004 +#define TOUCHEVENTF_INRANGE 0x0008 +#define TOUCHEVENTF_PRIMARY 0x0010 +#define TOUCHEVENTF_NOCOALESCE 0x0020 +#define TOUCHEVENTF_PEN 0x0040 +#define TOUCHEVENTF_PALM 0x0080 + +#define TOUCHINPUTMASKF_TIMEFROMSYSTEM 0x0001 +#define TOUCHINPUTMASKF_EXTRAINFO 0x0002 +#define TOUCHINPUTMASKF_CONTACTAREA 0x0004 + +#define TOUCH_COORD_TO_PIXEL(C) (C/100) + +#define TWF_FINETOUCH 0x0001 +#define TWF_WANTPALM 0x0002 + +#endif // WINVER < 0x0602 + +// WM_TABLET_QUERYSYSTEMGESTURESTATUS return values +#define TABLET_ROTATE_GESTURE_ENABLE 0x02000000 + +class nsPointWin : public nsIntPoint +{ +public: + nsPointWin& operator=(const POINTS& aPoint) { + x = aPoint.x; y = aPoint.y; + return *this; + } + nsPointWin& operator=(const POINT& aPoint) { + x = aPoint.x; y = aPoint.y; + return *this; + } + nsPointWin& operator=(int val) { + x = y = val; + return *this; + } + void ScreenToClient(HWND hWnd) { + POINT tmp; + tmp.x = x; tmp.y = y; + ::ScreenToClient(hWnd, &tmp); + *this = tmp; + } +}; + +class nsWinGesture +{ +public: + nsWinGesture(); + +public: + bool SetWinGestureSupport(HWND hWnd, mozilla::WidgetGestureNotifyEvent::PanDirection aDirection); + bool ShutdownWinGestureSupport(); + bool RegisterTouchWindow(HWND hWnd); + bool UnregisterTouchWindow(HWND hWnd); + bool GetTouchInputInfo(HTOUCHINPUT hTouchInput, uint32_t cInputs, PTOUCHINPUT pInputs); + bool CloseTouchInputHandle(HTOUCHINPUT hTouchInput); + bool IsAvailable(); + + // Simple gesture process + bool ProcessGestureMessage(HWND hWnd, WPARAM wParam, LPARAM lParam, mozilla::WidgetSimpleGestureEvent& evt); + + // Pan processing + bool IsPanEvent(LPARAM lParam); + bool ProcessPanMessage(HWND hWnd, WPARAM wParam, LPARAM lParam); + bool PanDeltaToPixelScroll(mozilla::WidgetWheelEvent& aWheelEvent); + void UpdatePanFeedbackX(HWND hWnd, int32_t scrollOverflow, bool& endFeedback); + void UpdatePanFeedbackY(HWND hWnd, int32_t scrollOverflow, bool& endFeedback); + void PanFeedbackFinalize(HWND hWnd, bool endFeedback); + +public: + // Helpers + bool GetGestureInfo(HGESTUREINFO hGestureInfo, PGESTUREINFO pGestureInfo); + bool CloseGestureInfoHandle(HGESTUREINFO hGestureInfo); + bool GetGestureExtraArgs(HGESTUREINFO hGestureInfo, UINT cbExtraArgs, PBYTE pExtraArgs); + bool SetGestureConfig(HWND hWnd, UINT cIDs, PGESTURECONFIG pGestureConfig); + bool GetGestureConfig(HWND hWnd, DWORD dwFlags, PUINT pcIDs, PGESTURECONFIG pGestureConfig); + bool BeginPanningFeedback(HWND hWnd); + bool EndPanningFeedback(HWND hWnd); + bool UpdatePanningFeedback(HWND hWnd, LONG offsetX, LONG offsetY, BOOL fInInertia); + +protected: + +private: + // Function prototypes + typedef BOOL (WINAPI * GetGestureInfoPtr)(HGESTUREINFO hGestureInfo, PGESTUREINFO pGestureInfo); + typedef BOOL (WINAPI * CloseGestureInfoHandlePtr)(HGESTUREINFO hGestureInfo); + typedef BOOL (WINAPI * GetGestureExtraArgsPtr)(HGESTUREINFO hGestureInfo, UINT cbExtraArgs, PBYTE pExtraArgs); + typedef BOOL (WINAPI * SetGestureConfigPtr)(HWND hwnd, DWORD dwReserved, UINT cIDs, PGESTURECONFIG pGestureConfig, UINT cbSize); + typedef BOOL (WINAPI * GetGestureConfigPtr)(HWND hwnd, DWORD dwReserved, DWORD dwFlags, PUINT pcIDs, PGESTURECONFIG pGestureConfig, UINT cbSize); + typedef BOOL (WINAPI * BeginPanningFeedbackPtr)(HWND hWnd); + typedef BOOL (WINAPI * EndPanningFeedbackPtr)(HWND hWnd, BOOL fAnimateBack); + typedef BOOL (WINAPI * UpdatePanningFeedbackPtr)(HWND hWnd, LONG offsetX, LONG offsetY, BOOL fInInertia); + typedef BOOL (WINAPI * RegisterTouchWindowPtr)(HWND hWnd, ULONG flags); + typedef BOOL (WINAPI * UnregisterTouchWindowPtr)(HWND hWnd); + typedef BOOL (WINAPI * GetTouchInputInfoPtr)(HTOUCHINPUT hTouchInput, uint32_t cInputs, PTOUCHINPUT pInputs, int32_t cbSize); + typedef BOOL (WINAPI * CloseTouchInputHandlePtr)(HTOUCHINPUT hTouchInput); + + // Static function pointers + static GetGestureInfoPtr getGestureInfo; + static CloseGestureInfoHandlePtr closeGestureInfoHandle; + static GetGestureExtraArgsPtr getGestureExtraArgs; + static SetGestureConfigPtr setGestureConfig; + static GetGestureConfigPtr getGestureConfig; + static BeginPanningFeedbackPtr beginPanningFeedback; + static EndPanningFeedbackPtr endPanningFeedback; + static UpdatePanningFeedbackPtr updatePanningFeedback; + static RegisterTouchWindowPtr registerTouchWindow; + static UnregisterTouchWindowPtr unregisterTouchWindow; + static GetTouchInputInfoPtr getTouchInputInfo; + static CloseTouchInputHandlePtr closeTouchInputHandle; + + // Delay load info + bool InitLibrary(); + + static HMODULE sLibraryHandle; + static const wchar_t kGestureLibraryName[]; + + // Pan and feedback state + nsPointWin mPanIntermediate; + nsPointWin mPanRefPoint; + nsPointWin mPixelScrollDelta; + bool mPanActive; + bool mFeedbackActive; + bool mXAxisFeedback; + bool mYAxisFeedback; + bool mPanInertiaActive; + nsPointWin mPixelScrollOverflow; + + // Zoom state + double mZoomIntermediate; + + // Rotate state + double mRotateIntermediate; +}; + +#endif /* WinGesture_h__ */ + + diff --git a/widget/windows/nsWindow.cpp b/widget/windows/nsWindow.cpp new file mode 100644 index 0000000000..2172f2aa0b --- /dev/null +++ b/widget/windows/nsWindow.cpp @@ -0,0 +1,8139 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sts=2 sw=2 et cin: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * nsWindow - Native window management and event handling. + * + * nsWindow is organized into a set of major blocks and + * block subsections. The layout is as follows: + * + * Includes + * Variables + * nsIWidget impl. + * nsIWidget methods and utilities + * nsSwitchToUIThread impl. + * nsSwitchToUIThread methods and utilities + * Moz events + * Event initialization + * Event dispatching + * Native events + * Wndproc(s) + * Event processing + * OnEvent event handlers + * IME management and accessibility + * Transparency + * Popup hook handling + * Misc. utilities + * Child window impl. + * + * Search for "BLOCK:" to find major blocks. + * Search for "SECTION:" to find specific sections. + * + * Blocks should be split out into separate files if they + * become unmanageable. + * + * Related source: + * + * nsWindowDefs.h - Definitions, macros, structs, enums + * and general setup. + * nsWindowDbg.h/.cpp - Debug related code and directives. + * nsWindowGfx.h/.cpp - Graphics and painting. + * + */ + +/************************************************************** + ************************************************************** + ** + ** BLOCK: Includes + ** + ** Include headers. + ** + ************************************************************** + **************************************************************/ + +#include "gfx2DGlue.h" +#include "gfxEnv.h" +#include "gfxPlatform.h" +#include "gfxPrefs.h" +#include "mozilla/Logging.h" +#include "mozilla/MathAlgorithms.h" +#include "mozilla/MiscEvents.h" +#include "mozilla/MouseEvents.h" +#include "mozilla/TouchEvents.h" + +#include "mozilla/ipc/MessageChannel.h" +#include <algorithm> +#include <limits> + +#include "nsWindow.h" + +#include <shellapi.h> +#include <windows.h> +#include <wtsapi32.h> +#include <process.h> +#include <commctrl.h> +#include <unknwn.h> +#include <psapi.h> + +#include "mozilla/Logging.h" +#include "prtime.h" +#include "prprf.h" +#include "prmem.h" +#include "prenv.h" + +#include "mozilla/WidgetTraceEvent.h" +#include "nsIAppShell.h" +#include "nsISupportsPrimitives.h" +#include "nsIDOMMouseEvent.h" +#include "nsIKeyEventInPluginCallback.h" +#include "nsITheme.h" +#include "nsIObserverService.h" +#include "nsIScreenManager.h" +#include "imgIContainer.h" +#include "nsIFile.h" +#include "nsIRollupListener.h" +#include "nsIServiceManager.h" +#include "nsIClipboard.h" +#include "WinMouseScrollHandler.h" +#include "nsFontMetrics.h" +#include "nsIFontEnumerator.h" +#include "nsFont.h" +#include "nsRect.h" +#include "nsThreadUtils.h" +#include "nsNativeCharsetUtils.h" +#include "nsGkAtoms.h" +#include "nsCRT.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsXPIDLString.h" +#include "nsWidgetsCID.h" +#include "nsTHashtable.h" +#include "nsHashKeys.h" +#include "nsString.h" +#include "mozilla/Services.h" +#include "nsNativeThemeWin.h" +#include "nsWindowsDllInterceptor.h" +#include "nsLayoutUtils.h" +#include "nsView.h" +#include "nsIWindowMediator.h" +#include "nsIServiceManager.h" +#include "nsWindowGfx.h" +#include "gfxWindowsPlatform.h" +#include "Layers.h" +#include "nsPrintfCString.h" +#include "mozilla/Preferences.h" +#include "nsISound.h" +#include "SystemTimeConverter.h" +#include "WinTaskbar.h" +#include "WidgetUtils.h" +#include "nsIWidgetListener.h" +#include "mozilla/dom/Touch.h" +#include "mozilla/gfx/2D.h" +#include "nsToolkitCompsCID.h" +#include "nsIAppStartup.h" +#include "mozilla/WindowsVersion.h" +#include "mozilla/TextEvents.h" // For WidgetKeyboardEvent +#include "mozilla/TextEventDispatcherListener.h" +#include "mozilla/widget/WinNativeEventData.h" +#include "mozilla/widget/PlatformWidgetTypes.h" +#include "nsThemeConstants.h" +#include "nsBidiKeyboard.h" +#include "nsThemeConstants.h" +#include "gfxConfig.h" +#include "InProcessWinCompositorWidget.h" + +#include "nsIGfxInfo.h" +#include "nsUXThemeConstants.h" +#include "KeyboardLayout.h" +#include "nsNativeDragTarget.h" +#include <mmsystem.h> // needed for WIN32_LEAN_AND_MEAN +#include <zmouse.h> +#include <richedit.h> + +#if defined(ACCESSIBILITY) + +#ifdef DEBUG +#include "mozilla/a11y/Logging.h" +#endif + +#include "oleidl.h" +#include <winuser.h> +#include "nsAccessibilityService.h" +#include "mozilla/a11y/DocAccessible.h" +#include "mozilla/a11y/Platform.h" +#if !defined(WINABLEAPI) +#include <winable.h> +#endif // !defined(WINABLEAPI) +#endif // defined(ACCESSIBILITY) + +#include "nsIWinTaskbar.h" +#define NS_TASKBAR_CONTRACTID "@mozilla.org/windows-taskbar;1" + +#include "nsIWindowsUIUtils.h" + +#include "nsWindowDefs.h" + +#include "nsCrashOnException.h" +#include "nsIXULRuntime.h" + +#include "nsIContent.h" + +#include "mozilla/HangMonitor.h" +#include "WinIMEHandler.h" + +#include "npapi.h" + +#include <d3d11.h> + +#include "InkCollector.h" + +// ERROR from wingdi.h (below) gets undefined by some code. +// #define ERROR 0 +// #define RGN_ERROR ERROR +#define ERROR 0 + +#if !defined(SM_CONVERTIBLESLATEMODE) +#define SM_CONVERTIBLESLATEMODE 0x2003 +#endif + +#if !defined(WM_DPICHANGED) +#define WM_DPICHANGED 0x02E0 +#endif + +#include "mozilla/gfx/DeviceManagerDx.h" +#include "mozilla/layers/APZCTreeManager.h" +#include "mozilla/layers/InputAPZContext.h" +#include "mozilla/layers/ScrollInputMethods.h" +#include "ClientLayerManager.h" +#include "InputData.h" + +#include "mozilla/Telemetry.h" + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::gfx; +using namespace mozilla::layers; +using namespace mozilla::widget; + +/************************************************************** + ************************************************************** + ** + ** BLOCK: Variables + ** + ** nsWindow Class static initializations and global variables. + ** + ************************************************************** + **************************************************************/ + +/************************************************************** + * + * SECTION: nsWindow statics + * + **************************************************************/ + +bool nsWindow::sDropShadowEnabled = true; +uint32_t nsWindow::sInstanceCount = 0; +bool nsWindow::sSwitchKeyboardLayout = false; +BOOL nsWindow::sIsOleInitialized = FALSE; +HCURSOR nsWindow::sHCursor = nullptr; +imgIContainer* nsWindow::sCursorImgContainer = nullptr; +nsWindow* nsWindow::sCurrentWindow = nullptr; +bool nsWindow::sJustGotDeactivate = false; +bool nsWindow::sJustGotActivate = false; +bool nsWindow::sIsInMouseCapture = false; + +// imported in nsWidgetFactory.cpp +TriStateBool nsWindow::sCanQuit = TRI_UNKNOWN; + +// Hook Data Memebers for Dropdowns. sProcessHook Tells the +// hook methods whether they should be processing the hook +// messages. +HHOOK nsWindow::sMsgFilterHook = nullptr; +HHOOK nsWindow::sCallProcHook = nullptr; +HHOOK nsWindow::sCallMouseHook = nullptr; +bool nsWindow::sProcessHook = false; +UINT nsWindow::sRollupMsgId = 0; +HWND nsWindow::sRollupMsgWnd = nullptr; +UINT nsWindow::sHookTimerId = 0; + +// Mouse Clicks - static variable definitions for figuring +// out 1 - 3 Clicks. +POINT nsWindow::sLastMousePoint = {0}; +POINT nsWindow::sLastMouseMovePoint = {0}; +LONG nsWindow::sLastMouseDownTime = 0L; +LONG nsWindow::sLastClickCount = 0L; +BYTE nsWindow::sLastMouseButton = 0; + +// Trim heap on minimize. (initialized, but still true.) +int nsWindow::sTrimOnMinimize = 2; + +TriStateBool nsWindow::sHasBogusPopupsDropShadowOnMultiMonitor = TRI_UNKNOWN; + +WPARAM nsWindow::sMouseExitwParam = 0; +LPARAM nsWindow::sMouseExitlParamScreen = 0; + +static SystemTimeConverter<DWORD>& +TimeConverter() { + static SystemTimeConverter<DWORD> timeConverterSingleton; + return timeConverterSingleton; +} + +namespace mozilla { + +class CurrentWindowsTimeGetter { +public: + CurrentWindowsTimeGetter(HWND aWnd) + : mWnd(aWnd) + { + } + + DWORD GetCurrentTime() const + { + return ::GetTickCount(); + } + + void GetTimeAsyncForPossibleBackwardsSkew(const TimeStamp& aNow) + { + DWORD currentTime = GetCurrentTime(); + if (sBackwardsSkewStamp && currentTime == sLastPostTime) { + // There's already one inflight with this timestamp. Don't + // send a duplicate. + return; + } + sBackwardsSkewStamp = Some(aNow); + sLastPostTime = currentTime; + static_assert(sizeof(WPARAM) >= sizeof(DWORD), "Can't fit a DWORD in a WPARAM"); + ::PostMessage(mWnd, MOZ_WM_SKEWFIX, sLastPostTime, 0); + } + + static bool GetAndClearBackwardsSkewStamp(DWORD aPostTime, TimeStamp* aOutSkewStamp) + { + if (aPostTime != sLastPostTime) { + // The SKEWFIX message is stale; we've sent a new one since then. + // Ignore this one. + return false; + } + MOZ_ASSERT(sBackwardsSkewStamp); + *aOutSkewStamp = sBackwardsSkewStamp.value(); + sBackwardsSkewStamp = Nothing(); + return true; + } + +private: + static Maybe<TimeStamp> sBackwardsSkewStamp; + static DWORD sLastPostTime; + HWND mWnd; +}; + +Maybe<TimeStamp> CurrentWindowsTimeGetter::sBackwardsSkewStamp; +DWORD CurrentWindowsTimeGetter::sLastPostTime = 0; + +} // namespace mozilla + +/************************************************************** + * + * SECTION: globals variables + * + **************************************************************/ + +static const char *sScreenManagerContractID = "@mozilla.org/gfx/screenmanager;1"; + +extern mozilla::LazyLogModule gWindowsLog; + +// Global used in Show window enumerations. +static bool gWindowsVisible = false; + +// True if we have sent a notification that we are suspending/sleeping. +static bool gIsSleepMode = false; + +static NS_DEFINE_CID(kCClipboardCID, NS_CLIPBOARD_CID); + +// General purpose user32.dll hook object +static WindowsDllInterceptor sUser32Intercept; + +// 2 pixel offset for eTransparencyBorderlessGlass which equals the size of +// the default window border Windows paints. Glass will be extended inward +// this distance to remove the border. +static const int32_t kGlassMarginAdjustment = 2; + +// When the client area is extended out into the default window frame area, +// this is the minimum amount of space along the edge of resizable windows +// we will always display a resize cursor in, regardless of the underlying +// content. +static const int32_t kResizableBorderMinSize = 3; + +// Cached pointer events enabler value, True if pointer events are enabled. +static bool gIsPointerEventsEnabled = false; + +// We should never really try to accelerate windows bigger than this. In some +// cases this might lead to no D3D9 acceleration where we could have had it +// but D3D9 does not reliably report when it supports bigger windows. 8192 +// is as safe as we can get, we know at least D3D10 hardware always supports +// this, other hardware we expect to report correctly in D3D9. +#define MAX_ACCELERATED_DIMENSION 8192 + +// On window open (as well as after), Windows has an unfortunate habit of +// sending rather a lot of WM_NCHITTEST messages. Because we have to do point +// to DOM target conversions for these, we cache responses for a given +// coordinate this many milliseconds: +#define HITTEST_CACHE_LIFETIME_MS 50 + +#if defined(ACCESSIBILITY) && defined(_M_IX86) + +namespace mozilla { + +/** + * Windows touchscreen code works by setting a global WH_GETMESSAGE hook and + * injecting tiptsf.dll. The touchscreen process then posts registered messages + * to our main thread. The tiptsf hook picks up those registered messages and + * uses them as commands, some of which call into UIA, which then calls into + * MSAA, which then sends WM_GETOBJECT to us. + * + * We can get ahead of this by installing our own thread-local WH_GETMESSAGE + * hook. Since thread-local hooks are called ahead of global hooks, we will + * see these registered messages before tiptsf does. At this point we can then + * raise a flag that blocks a11y before invoking CallNextHookEx which will then + * invoke the global tiptsf hook. Then when we see WM_GETOBJECT, we check the + * flag by calling TIPMessageHandler::IsA11yBlocked(). + * + * For Windows 8, we also hook tiptsf!ProcessCaretEvents, which is an a11y hook + * function that also calls into UIA. + */ +class TIPMessageHandler +{ +public: + ~TIPMessageHandler() + { + if (mHook) { + ::UnhookWindowsHookEx(mHook); + } + } + + static void Initialize() + { + if (!IsWin8OrLater()) { + return; + } + + if (sInstance) { + return; + } + + sInstance = new TIPMessageHandler(); + ClearOnShutdown(&sInstance); + } + + static bool IsA11yBlocked() + { + if (!sInstance) { + return false; + } + + return sInstance->mA11yBlockCount > 0; + } + +private: + TIPMessageHandler() + : mHook(nullptr) + , mA11yBlockCount(0) + { + MOZ_ASSERT(NS_IsMainThread()); + + // Registered messages used by tiptsf + mMessages[0] = ::RegisterWindowMessage(L"ImmersiveFocusNotification"); + mMessages[1] = ::RegisterWindowMessage(L"TipCloseMenus"); + mMessages[2] = ::RegisterWindowMessage(L"TabletInputPanelOpening"); + mMessages[3] = ::RegisterWindowMessage(L"IHM Pen or Touch Event noticed"); + mMessages[4] = ::RegisterWindowMessage(L"ProgrammabilityCaretVisibility"); + mMessages[5] = ::RegisterWindowMessage(L"CaretTrackingUpdateIPHidden"); + mMessages[6] = ::RegisterWindowMessage(L"CaretTrackingUpdateIPInfo"); + + mHook = ::SetWindowsHookEx(WH_GETMESSAGE, &TIPHook, nullptr, + ::GetCurrentThreadId()); + MOZ_ASSERT(mHook); + + // On touchscreen devices, tiptsf.dll will have been loaded when STA COM was + // first initialized. + if (!IsWin10OrLater() && GetModuleHandle(L"tiptsf.dll") && + !sProcessCaretEventsStub) { + sTipTsfInterceptor.Init("tiptsf.dll"); + DebugOnly<bool> ok = sTipTsfInterceptor.AddHook("ProcessCaretEvents", + reinterpret_cast<intptr_t>(&ProcessCaretEventsHook), + (void**) &sProcessCaretEventsStub); + MOZ_ASSERT(ok); + } + + if (!sSendMessageTimeoutWStub) { + sUser32Intercept.Init("user32.dll"); + DebugOnly<bool> hooked = sUser32Intercept.AddHook("SendMessageTimeoutW", + reinterpret_cast<intptr_t>(&SendMessageTimeoutWHook), + (void**) &sSendMessageTimeoutWStub); + MOZ_ASSERT(hooked); + } + } + + class MOZ_RAII A11yInstantiationBlocker + { + public: + A11yInstantiationBlocker() + { + if (!TIPMessageHandler::sInstance) { + return; + } + ++TIPMessageHandler::sInstance->mA11yBlockCount; + } + + ~A11yInstantiationBlocker() + { + if (!TIPMessageHandler::sInstance) { + return; + } + MOZ_ASSERT(TIPMessageHandler::sInstance->mA11yBlockCount > 0); + --TIPMessageHandler::sInstance->mA11yBlockCount; + } + }; + + friend class A11yInstantiationBlocker; + + static LRESULT CALLBACK TIPHook(int aCode, WPARAM aWParam, LPARAM aLParam) + { + if (aCode < 0 || !sInstance) { + return ::CallNextHookEx(nullptr, aCode, aWParam, aLParam); + } + + MSG* msg = reinterpret_cast<MSG*>(aLParam); + UINT& msgCode = msg->message; + + for (uint32_t i = 0; i < ArrayLength(sInstance->mMessages); ++i) { + if (msgCode == sInstance->mMessages[i]) { + A11yInstantiationBlocker block; + return ::CallNextHookEx(nullptr, aCode, aWParam, aLParam); + } + } + + return ::CallNextHookEx(nullptr, aCode, aWParam, aLParam); + } + + static void CALLBACK ProcessCaretEventsHook(HWINEVENTHOOK aWinEventHook, + DWORD aEvent, HWND aHwnd, + LONG aObjectId, LONG aChildId, + DWORD aGeneratingTid, + DWORD aEventTime) + { + A11yInstantiationBlocker block; + sProcessCaretEventsStub(aWinEventHook, aEvent, aHwnd, aObjectId, aChildId, + aGeneratingTid, aEventTime); + } + + static LRESULT WINAPI SendMessageTimeoutWHook(HWND aHwnd, UINT aMsgCode, + WPARAM aWParam, LPARAM aLParam, + UINT aFlags, UINT aTimeout, + PDWORD_PTR aMsgResult) + { + // We don't want to handle this unless the message is a WM_GETOBJECT that we + // want to block, and the aHwnd is a nsWindow that belongs to the current + // thread. + if (!aMsgResult || aMsgCode != WM_GETOBJECT || aLParam != OBJID_CLIENT || + !WinUtils::GetNSWindowPtr(aHwnd) || + ::GetWindowThreadProcessId(aHwnd, nullptr) != ::GetCurrentThreadId() || + !IsA11yBlocked()) { + return sSendMessageTimeoutWStub(aHwnd, aMsgCode, aWParam, aLParam, + aFlags, aTimeout, aMsgResult); + } + + // In this case we want to fake the result that would happen if we had + // decided not to handle WM_GETOBJECT in our WndProc. We hand the message + // off to DefWindowProc to accomplish this. + *aMsgResult = static_cast<DWORD_PTR>(::DefWindowProcW(aHwnd, aMsgCode, + aWParam, aLParam)); + + return static_cast<LRESULT>(TRUE); + } + + static WindowsDllInterceptor sTipTsfInterceptor; + static WINEVENTPROC sProcessCaretEventsStub; + static decltype(&SendMessageTimeoutW) sSendMessageTimeoutWStub; + static StaticAutoPtr<TIPMessageHandler> sInstance; + + HHOOK mHook; + UINT mMessages[7]; + uint32_t mA11yBlockCount; +}; + +WindowsDllInterceptor TIPMessageHandler::sTipTsfInterceptor; +WINEVENTPROC TIPMessageHandler::sProcessCaretEventsStub; +decltype(&SendMessageTimeoutW) TIPMessageHandler::sSendMessageTimeoutWStub; +StaticAutoPtr<TIPMessageHandler> TIPMessageHandler::sInstance; + +} // namespace mozilla + +#endif // defined(ACCESSIBILITY) && defined(_M_IX86) + +/************************************************************** + ************************************************************** + ** + ** BLOCK: nsIWidget impl. + ** + ** nsIWidget interface implementation, broken down into + ** sections. + ** + ************************************************************** + **************************************************************/ + +/************************************************************** + * + * SECTION: nsWindow construction and destruction + * + **************************************************************/ + +nsWindow::nsWindow() + : nsWindowBase() + , mResizeState(NOT_RESIZING) +{ + mIconSmall = nullptr; + mIconBig = nullptr; + mWnd = nullptr; + mTransitionWnd = nullptr; + mPaintDC = nullptr; + mPrevWndProc = nullptr; + mNativeDragTarget = nullptr; + mInDtor = false; + mIsVisible = false; + mIsTopWidgetWindow = false; + mUnicodeWidget = true; + mDisplayPanFeedback = false; + mTouchWindow = false; + mFutureMarginsToUse = false; + mCustomNonClient = false; + mHideChrome = false; + mFullscreenMode = false; + mMousePresent = false; + mDestroyCalled = false; + mHasTaskbarIconBeenCreated = false; + mMouseTransparent = false; + mPickerDisplayCount = 0; + mWindowType = eWindowType_child; + mBorderStyle = eBorderStyle_default; + mOldSizeMode = nsSizeMode_Normal; + mLastSizeMode = nsSizeMode_Normal; + mLastSize.width = 0; + mLastSize.height = 0; + mOldStyle = 0; + mOldExStyle = 0; + mPainting = 0; + mLastKeyboardLayout = 0; + mBlurSuppressLevel = 0; + mLastPaintEndTime = TimeStamp::Now(); + mCachedHitTestPoint.x = 0; + mCachedHitTestPoint.y = 0; + mCachedHitTestTime = TimeStamp::Now(); + mCachedHitTestResult = 0; +#ifdef MOZ_XUL + mTransparencyMode = eTransparencyOpaque; + memset(&mGlassMargins, 0, sizeof mGlassMargins); +#endif + DWORD background = ::GetSysColor(COLOR_BTNFACE); + mBrush = ::CreateSolidBrush(NSRGB_2_COLOREF(background)); + mSendingSetText = false; + mDefaultScale = -1.0; // not yet set, will be calculated on first use + + mTaskbarPreview = nullptr; + + // Global initialization + if (!sInstanceCount) { + // Global app registration id for Win7 and up. See + // WinTaskbar.cpp for details. + mozilla::widget::WinTaskbar::RegisterAppUserModelID(); + KeyboardLayout::GetInstance()->OnLayoutChange(::GetKeyboardLayout(0)); +#if defined(ACCESSIBILITY) && defined(_M_IX86) + mozilla::TIPMessageHandler::Initialize(); +#endif // defined(ACCESSIBILITY) && defined(_M_IX86) + IMEHandler::Initialize(); + if (SUCCEEDED(::OleInitialize(nullptr))) { + sIsOleInitialized = TRUE; + } + NS_ASSERTION(sIsOleInitialized, "***** OLE is not initialized!\n"); + MouseScrollHandler::Initialize(); + // Init titlebar button info for custom frames. + nsUXThemeData::InitTitlebarInfo(); + // Init theme data + nsUXThemeData::UpdateNativeThemeInfo(); + RedirectedKeyDownMessageManager::Forget(); + InkCollector::sInkCollector = new InkCollector(); + + Preferences::AddBoolVarCache(&gIsPointerEventsEnabled, + "dom.w3c_pointer_events.enabled", + gIsPointerEventsEnabled); + } // !sInstanceCount + + mIdleService = nullptr; + + mSizeConstraintsScale = GetDefaultScale().scale; + + sInstanceCount++; +} + +nsWindow::~nsWindow() +{ + mInDtor = true; + + // If the widget was released without calling Destroy() then the native window still + // exists, and we need to destroy it. + // Destroy() will early-return if it was already called. In any case it is important + // to call it before destroying mPresentLock (cf. 1156182). + Destroy(); + + // Free app icon resources. This must happen after `OnDestroy` (see bug 708033). + if (mIconSmall) + ::DestroyIcon(mIconSmall); + + if (mIconBig) + ::DestroyIcon(mIconBig); + + sInstanceCount--; + + // Global shutdown + if (sInstanceCount == 0) { + InkCollector::sInkCollector->Shutdown(); + InkCollector::sInkCollector = nullptr; + IMEHandler::Terminate(); + NS_IF_RELEASE(sCursorImgContainer); + if (sIsOleInitialized) { + ::OleFlushClipboard(); + ::OleUninitialize(); + sIsOleInitialized = FALSE; + } + } + + NS_IF_RELEASE(mNativeDragTarget); +} + +NS_IMPL_ISUPPORTS_INHERITED0(nsWindow, nsBaseWidget) + +/************************************************************** + * + * SECTION: nsIWidget::Create, nsIWidget::Destroy + * + * Creating and destroying windows for this widget. + * + **************************************************************/ + +// Allow Derived classes to modify the height that is passed +// when the window is created or resized. +int32_t nsWindow::GetHeight(int32_t aProposedHeight) +{ + return aProposedHeight; +} + +static bool +ShouldCacheTitleBarInfo(nsWindowType aWindowType, nsBorderStyle aBorderStyle) +{ + return (aWindowType == eWindowType_toplevel) && + (aBorderStyle == eBorderStyle_default || + aBorderStyle == eBorderStyle_all) && + (!nsUXThemeData::sTitlebarInfoPopulatedThemed || + !nsUXThemeData::sTitlebarInfoPopulatedAero); +} + +// Create the proper widget +nsresult +nsWindow::Create(nsIWidget* aParent, + nsNativeWidget aNativeParent, + const LayoutDeviceIntRect& aRect, + nsWidgetInitData* aInitData) +{ + nsWidgetInitData defaultInitData; + if (!aInitData) + aInitData = &defaultInitData; + + mUnicodeWidget = aInitData->mUnicode; + + nsIWidget *baseParent = aInitData->mWindowType == eWindowType_dialog || + aInitData->mWindowType == eWindowType_toplevel || + aInitData->mWindowType == eWindowType_invisible ? + nullptr : aParent; + + mIsTopWidgetWindow = (nullptr == baseParent); + mBounds = aRect; + + // Ensure that the toolkit is created. + nsToolkit::GetToolkit(); + + BaseCreate(baseParent, aInitData); + + HWND parent; + if (aParent) { // has a nsIWidget parent + parent = aParent ? (HWND)aParent->GetNativeData(NS_NATIVE_WINDOW) : nullptr; + mParent = aParent; + } else { // has a nsNative parent + parent = (HWND)aNativeParent; + mParent = aNativeParent ? + WinUtils::GetNSWindowPtr((HWND)aNativeParent) : nullptr; + } + + mIsRTL = aInitData->mRTL; + + DWORD style = WindowStyle(); + DWORD extendedStyle = WindowExStyle(); + + if (mWindowType == eWindowType_popup) { + if (!aParent) { + parent = nullptr; + } + + if (IsVistaOrLater() && !IsWin8OrLater() && + HasBogusPopupsDropShadowOnMultiMonitor()) { + extendedStyle |= WS_EX_COMPOSITED; + } + + if (aInitData->mMouseTransparent) { + // This flag makes the window transparent to mouse events + mMouseTransparent = true; + extendedStyle |= WS_EX_TRANSPARENT; + } + } else if (mWindowType == eWindowType_invisible) { + // Make sure CreateWindowEx succeeds at creating a toplevel window + style &= ~0x40000000; // WS_CHILDWINDOW + } else { + // See if the caller wants to explictly set clip children and clip siblings + if (aInitData->clipChildren) { + style |= WS_CLIPCHILDREN; + } else { + style &= ~WS_CLIPCHILDREN; + } + if (aInitData->clipSiblings) { + style |= WS_CLIPSIBLINGS; + } + } + + const wchar_t* className; + if (aInitData->mDropShadow) { + className = GetWindowPopupClass(); + } else { + className = GetWindowClass(); + } + // Plugins are created in the disabled state so that they can't + // steal focus away from our main window. This is especially + // important if the plugin has loaded in a background tab. + if (aInitData->mWindowType == eWindowType_plugin || + aInitData->mWindowType == eWindowType_plugin_ipc_chrome || + aInitData->mWindowType == eWindowType_plugin_ipc_content) { + style |= WS_DISABLED; + } + mWnd = ::CreateWindowExW(extendedStyle, + className, + L"", + style, + aRect.x, + aRect.y, + aRect.width, + GetHeight(aRect.height), + parent, + nullptr, + nsToolkit::mDllInstance, + nullptr); + + if (!mWnd) { + NS_WARNING("nsWindow CreateWindowEx failed."); + return NS_ERROR_FAILURE; + } + + if (mIsRTL && WinUtils::dwmSetWindowAttributePtr) { + DWORD dwAttribute = TRUE; + WinUtils::dwmSetWindowAttributePtr(mWnd, DWMWA_NONCLIENT_RTL_LAYOUT, &dwAttribute, sizeof dwAttribute); + } + + if (!IsPlugin() && + mWindowType != eWindowType_invisible && + MouseScrollHandler::Device::IsFakeScrollableWindowNeeded()) { + // Ugly Thinkpad Driver Hack (Bugs 507222 and 594977) + // + // We create two zero-sized windows as descendants of the top-level window, + // like so: + // + // Top-level window (MozillaWindowClass) + // FAKETRACKPOINTSCROLLCONTAINER (MozillaWindowClass) + // FAKETRACKPOINTSCROLLABLE (MozillaWindowClass) + // + // We need to have the middle window, otherwise the Trackpoint driver + // will fail to deliver scroll messages. WM_MOUSEWHEEL messages are + // sent to the FAKETRACKPOINTSCROLLABLE, which then propagate up the + // window hierarchy until they are handled by nsWindow::WindowProc. + // WM_HSCROLL messages are also sent to the FAKETRACKPOINTSCROLLABLE, + // but these do not propagate automatically, so we have the window + // procedure pretend that they were dispatched to the top-level window + // instead. + // + // The FAKETRACKPOINTSCROLLABLE needs to have the specific window styles it + // is given below so that it catches the Trackpoint driver's heuristics. + HWND scrollContainerWnd = ::CreateWindowW + (className, L"FAKETRACKPOINTSCROLLCONTAINER", + WS_CHILD | WS_VISIBLE, + 0, 0, 0, 0, mWnd, nullptr, nsToolkit::mDllInstance, nullptr); + HWND scrollableWnd = ::CreateWindowW + (className, L"FAKETRACKPOINTSCROLLABLE", + WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_TABSTOP | 0x30, + 0, 0, 0, 0, scrollContainerWnd, nullptr, nsToolkit::mDllInstance, + nullptr); + + // Give the FAKETRACKPOINTSCROLLABLE window a specific ID so that + // WindowProcInternal can distinguish it from the top-level window + // easily. + ::SetWindowLongPtrW(scrollableWnd, GWLP_ID, eFakeTrackPointScrollableID); + + // Make FAKETRACKPOINTSCROLLABLE use nsWindow::WindowProc, and store the + // old window procedure in its "user data". + WNDPROC oldWndProc; + if (mUnicodeWidget) + oldWndProc = (WNDPROC)::SetWindowLongPtrW(scrollableWnd, GWLP_WNDPROC, + (LONG_PTR)nsWindow::WindowProc); + else + oldWndProc = (WNDPROC)::SetWindowLongPtrA(scrollableWnd, GWLP_WNDPROC, + (LONG_PTR)nsWindow::WindowProc); + ::SetWindowLongPtrW(scrollableWnd, GWLP_USERDATA, (LONG_PTR)oldWndProc); + } + + SubclassWindow(TRUE); + + // Starting with Windows XP, a process always runs within a terminal services + // session. In order to play nicely with RDP, fast user switching, and the + // lock screen, we should be handling WM_WTSSESSION_CHANGE. We must register + // our HWND in order to receive this message. + DebugOnly<BOOL> wtsRegistered = ::WTSRegisterSessionNotification(mWnd, + NOTIFY_FOR_THIS_SESSION); + NS_ASSERTION(wtsRegistered, "WTSRegisterSessionNotification failed!\n"); + + mDefaultIMC.Init(this); + IMEHandler::InitInputContext(this, mInputContext); + + // If the internal variable set by the config.trim_on_minimize pref has not + // been initialized, and if this is the hidden window (conveniently created + // before any visible windows, and after the profile has been initialized), + // do some initialization work. + if (sTrimOnMinimize == 2 && mWindowType == eWindowType_invisible) { + // Our internal trim prevention logic is effective on 2K/XP at maintaining + // the working set when windows are minimized, but on Vista and up it has + // little to no effect. Since this feature has been the source of numerous + // bugs over the years, disable it (sTrimOnMinimize=1) on Vista and up. + sTrimOnMinimize = + Preferences::GetBool("config.trim_on_minimize", + IsVistaOrLater() ? 1 : 0); + sSwitchKeyboardLayout = + Preferences::GetBool("intl.keyboard.per_window_layout", false); + } + + // Query for command button metric data for rendering the titlebar. We + // only do this once on the first window that has an actual titlebar + if (ShouldCacheTitleBarInfo(mWindowType, mBorderStyle)) { + nsUXThemeData::UpdateTitlebarInfo(mWnd); + } + + return NS_OK; +} + +// Close this nsWindow +void nsWindow::Destroy() +{ + // WM_DESTROY has already fired, avoid calling it twice + if (mOnDestroyCalled) + return; + + // Don't destroy windows that have file pickers open, we'll tear these down + // later once the picker is closed. + mDestroyCalled = true; + if (mPickerDisplayCount) + return; + + // During the destruction of all of our children, make sure we don't get deleted. + nsCOMPtr<nsIWidget> kungFuDeathGrip(this); + + /** + * On windows the LayerManagerOGL destructor wants the widget to be around for + * cleanup. It also would like to have the HWND intact, so we nullptr it here. + */ + DestroyLayerManager(); + + /* We should clear our cached resources now and not wait for the GC to + * delete the nsWindow. */ + ClearCachedResources(); + + // The DestroyWindow function destroys the specified window. The function sends WM_DESTROY + // and WM_NCDESTROY messages to the window to deactivate it and remove the keyboard focus + // from it. The function also destroys the window's menu, flushes the thread message queue, + // destroys timers, removes clipboard ownership, and breaks the clipboard viewer chain (if + // the window is at the top of the viewer chain). + // + // If the specified window is a parent or owner window, DestroyWindow automatically destroys + // the associated child or owned windows when it destroys the parent or owner window. The + // function first destroys child or owned windows, and then it destroys the parent or owner + // window. + VERIFY(::DestroyWindow(mWnd)); + + // Our windows can be subclassed which may prevent us receiving WM_DESTROY. If OnDestroy() + // didn't get called, call it now. + if (false == mOnDestroyCalled) { + MSGResult msgResult; + mWindowHook.Notify(mWnd, WM_DESTROY, 0, 0, msgResult); + OnDestroy(); + } +} + +/************************************************************** + * + * SECTION: Window class utilities + * + * Utilities for calculating the proper window class name for + * Create window. + * + **************************************************************/ + +const wchar_t* +nsWindow::RegisterWindowClass(const wchar_t* aClassName, + UINT aExtraStyle, LPWSTR aIconID) const +{ + WNDCLASSW wc; + if (::GetClassInfoW(nsToolkit::mDllInstance, aClassName, &wc)) { + // already registered + return aClassName; + } + + wc.style = CS_DBLCLKS | aExtraStyle; + wc.lpfnWndProc = WinUtils::NonClientDpiScalingDefWindowProcW; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hInstance = nsToolkit::mDllInstance; + wc.hIcon = aIconID ? ::LoadIconW(::GetModuleHandleW(nullptr), aIconID) : nullptr; + wc.hCursor = nullptr; + wc.hbrBackground = mBrush; + wc.lpszMenuName = nullptr; + wc.lpszClassName = aClassName; + + if (!::RegisterClassW(&wc)) { + // For older versions of Win32 (i.e., not XP), the registration may + // fail with aExtraStyle, so we have to re-register without it. + wc.style = CS_DBLCLKS; + ::RegisterClassW(&wc); + } + return aClassName; +} + +static LPWSTR const gStockApplicationIcon = MAKEINTRESOURCEW(32512); + +// Return the proper window class for everything except popups. +const wchar_t* +nsWindow::GetWindowClass() const +{ + switch (mWindowType) { + case eWindowType_invisible: + return RegisterWindowClass(kClassNameHidden, 0, gStockApplicationIcon); + case eWindowType_dialog: + return RegisterWindowClass(kClassNameDialog, 0, 0); + default: + return RegisterWindowClass(GetMainWindowClass(), 0, gStockApplicationIcon); + } +} + +// Return the proper popup window class +const wchar_t* +nsWindow::GetWindowPopupClass() const +{ + return RegisterWindowClass(kClassNameDropShadow, + CS_XP_DROPSHADOW, gStockApplicationIcon); +} + +/************************************************************** + * + * SECTION: Window styles utilities + * + * Return the proper windows styles and extended styles. + * + **************************************************************/ + +// Return nsWindow styles +DWORD nsWindow::WindowStyle() +{ + DWORD style; + + switch (mWindowType) { + case eWindowType_plugin: + case eWindowType_plugin_ipc_chrome: + case eWindowType_plugin_ipc_content: + case eWindowType_child: + style = WS_OVERLAPPED; + break; + + case eWindowType_dialog: + style = WS_OVERLAPPED | WS_BORDER | WS_DLGFRAME | WS_SYSMENU | DS_3DLOOK | + DS_MODALFRAME | WS_CLIPCHILDREN; + if (mBorderStyle != eBorderStyle_default) + style |= WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX; + break; + + case eWindowType_popup: + style = WS_POPUP; + if (!HasGlass()) { + style |= WS_OVERLAPPED; + } + break; + + default: + NS_ERROR("unknown border style"); + // fall through + + case eWindowType_toplevel: + case eWindowType_invisible: + style = WS_OVERLAPPED | WS_BORDER | WS_DLGFRAME | WS_SYSMENU | + WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_CLIPCHILDREN; + break; + } + + if (mBorderStyle != eBorderStyle_default && mBorderStyle != eBorderStyle_all) { + if (mBorderStyle == eBorderStyle_none || !(mBorderStyle & eBorderStyle_border)) + style &= ~WS_BORDER; + + if (mBorderStyle == eBorderStyle_none || !(mBorderStyle & eBorderStyle_title)) { + style &= ~WS_DLGFRAME; + style |= WS_POPUP; + style &= ~WS_CHILD; + } + + if (mBorderStyle == eBorderStyle_none || !(mBorderStyle & eBorderStyle_close)) + style &= ~0; + // XXX The close box can only be removed by changing the window class, + // as far as I know --- roc+moz@cs.cmu.edu + + if (mBorderStyle == eBorderStyle_none || + !(mBorderStyle & (eBorderStyle_menu | eBorderStyle_close))) + style &= ~WS_SYSMENU; + // Looks like getting rid of the system menu also does away with the + // close box. So, we only get rid of the system menu if you want neither it + // nor the close box. How does the Windows "Dialog" window class get just + // closebox and no sysmenu? Who knows. + + if (mBorderStyle == eBorderStyle_none || !(mBorderStyle & eBorderStyle_resizeh)) + style &= ~WS_THICKFRAME; + + if (mBorderStyle == eBorderStyle_none || !(mBorderStyle & eBorderStyle_minimize)) + style &= ~WS_MINIMIZEBOX; + + if (mBorderStyle == eBorderStyle_none || !(mBorderStyle & eBorderStyle_maximize)) + style &= ~WS_MAXIMIZEBOX; + + if (IsPopupWithTitleBar()) { + style |= WS_CAPTION; + if (mBorderStyle & eBorderStyle_close) { + style |= WS_SYSMENU; + } + } + } + + VERIFY_WINDOW_STYLE(style); + return style; +} + +// Return nsWindow extended styles +DWORD nsWindow::WindowExStyle() +{ + switch (mWindowType) + { + case eWindowType_plugin: + case eWindowType_plugin_ipc_chrome: + case eWindowType_plugin_ipc_content: + case eWindowType_child: + return 0; + + case eWindowType_dialog: + return WS_EX_WINDOWEDGE | WS_EX_DLGMODALFRAME; + + case eWindowType_popup: + { + DWORD extendedStyle = WS_EX_TOOLWINDOW; + if (mPopupLevel == ePopupLevelTop) + extendedStyle |= WS_EX_TOPMOST; + return extendedStyle; + } + default: + NS_ERROR("unknown border style"); + // fall through + + case eWindowType_toplevel: + case eWindowType_invisible: + return WS_EX_WINDOWEDGE; + } +} + +/************************************************************** + * + * SECTION: Window subclassing utilities + * + * Set or clear window subclasses on native windows. Used in + * Create and Destroy. + * + **************************************************************/ + +// Subclass (or remove the subclass from) this component's nsWindow +void nsWindow::SubclassWindow(BOOL bState) +{ + if (bState) { + if (!mWnd || !IsWindow(mWnd)) { + NS_ERROR("Invalid window handle"); + } + + if (mUnicodeWidget) { + mPrevWndProc = + reinterpret_cast<WNDPROC>( + SetWindowLongPtrW(mWnd, + GWLP_WNDPROC, + reinterpret_cast<LONG_PTR>(nsWindow::WindowProc))); + } else { + mPrevWndProc = + reinterpret_cast<WNDPROC>( + SetWindowLongPtrA(mWnd, + GWLP_WNDPROC, + reinterpret_cast<LONG_PTR>(nsWindow::WindowProc))); + } + NS_ASSERTION(mPrevWndProc, "Null standard window procedure"); + // connect the this pointer to the nsWindow handle + WinUtils::SetNSWindowBasePtr(mWnd, this); + } else { + if (IsWindow(mWnd)) { + if (mUnicodeWidget) { + SetWindowLongPtrW(mWnd, + GWLP_WNDPROC, + reinterpret_cast<LONG_PTR>(mPrevWndProc)); + } else { + SetWindowLongPtrA(mWnd, + GWLP_WNDPROC, + reinterpret_cast<LONG_PTR>(mPrevWndProc)); + } + } + WinUtils::SetNSWindowBasePtr(mWnd, nullptr); + mPrevWndProc = nullptr; + } +} + +/************************************************************** + * + * SECTION: nsIWidget::SetParent, nsIWidget::GetParent + * + * Set or clear the parent widgets using window properties, and + * handles calculating native parent handles. + * + **************************************************************/ + +// Get and set parent widgets +NS_IMETHODIMP nsWindow::SetParent(nsIWidget *aNewParent) +{ + nsCOMPtr<nsIWidget> kungFuDeathGrip(this); + nsIWidget* parent = GetParent(); + if (parent) { + parent->RemoveChild(this); + } + + mParent = aNewParent; + + if (aNewParent) { + ReparentNativeWidget(aNewParent); + aNewParent->AddChild(this); + return NS_OK; + } + if (mWnd) { + // If we have no parent, SetParent should return the desktop. + VERIFY(::SetParent(mWnd, nullptr)); + } + return NS_OK; +} + +void +nsWindow::ReparentNativeWidget(nsIWidget* aNewParent) +{ + NS_PRECONDITION(aNewParent, ""); + + mParent = aNewParent; + if (mWindowType == eWindowType_popup) { + return; + } + HWND newParent = (HWND)aNewParent->GetNativeData(NS_NATIVE_WINDOW); + NS_ASSERTION(newParent, "Parent widget has a null native window handle"); + if (newParent && mWnd) { + ::SetParent(mWnd, newParent); + } +} + +nsIWidget* nsWindow::GetParent(void) +{ + return GetParentWindow(false); +} + +static int32_t RoundDown(double aDouble) +{ + return aDouble > 0 ? static_cast<int32_t>(floor(aDouble)) : + static_cast<int32_t>(ceil(aDouble)); +} + +float nsWindow::GetDPI() +{ + HDC dc = ::GetDC(mWnd); + if (!dc) + return 96.0f; + + double heightInches = ::GetDeviceCaps(dc, VERTSIZE)/MM_PER_INCH_FLOAT; + int heightPx = ::GetDeviceCaps(dc, VERTRES); + ::ReleaseDC(mWnd, dc); + if (heightInches < 0.25) { + // Something's broken + return 96.0f; + } + return float(heightPx/heightInches); +} + +double nsWindow::GetDefaultScaleInternal() +{ + if (mDefaultScale <= 0.0) { + mDefaultScale = WinUtils::LogToPhysFactor(mWnd); + } + return mDefaultScale; +} + +int32_t nsWindow::LogToPhys(double aValue) +{ + return WinUtils::LogToPhys(::MonitorFromWindow(mWnd, + MONITOR_DEFAULTTOPRIMARY), + aValue); +} + +nsWindow* +nsWindow::GetParentWindow(bool aIncludeOwner) +{ + return static_cast<nsWindow*>(GetParentWindowBase(aIncludeOwner)); +} + +nsWindowBase* +nsWindow::GetParentWindowBase(bool aIncludeOwner) +{ + if (mIsTopWidgetWindow) { + // Must use a flag instead of mWindowType to tell if the window is the + // owned by the topmost widget, because a child window can be embedded inside + // a HWND which is not associated with a nsIWidget. + return nullptr; + } + + // If this widget has already been destroyed, pretend we have no parent. + // This corresponds to code in Destroy which removes the destroyed + // widget from its parent's child list. + if (mInDtor || mOnDestroyCalled) + return nullptr; + + + // aIncludeOwner set to true implies walking the parent chain to retrieve the + // root owner. aIncludeOwner set to false implies the search will stop at the + // true parent (default). + nsWindow* widget = nullptr; + if (mWnd) { + HWND parent = nullptr; + if (aIncludeOwner) + parent = ::GetParent(mWnd); + else + parent = ::GetAncestor(mWnd, GA_PARENT); + + if (parent) { + widget = WinUtils::GetNSWindowPtr(parent); + if (widget) { + // If the widget is in the process of being destroyed then + // do NOT return it + if (widget->mInDtor) { + widget = nullptr; + } + } + } + } + + return static_cast<nsWindowBase*>(widget); +} + +BOOL CALLBACK +nsWindow::EnumAllChildWindProc(HWND aWnd, LPARAM aParam) +{ + nsWindow *wnd = WinUtils::GetNSWindowPtr(aWnd); + if (wnd) { + ((nsWindow::WindowEnumCallback*)aParam)(wnd); + } + return TRUE; +} + +BOOL CALLBACK +nsWindow::EnumAllThreadWindowProc(HWND aWnd, LPARAM aParam) +{ + nsWindow *wnd = WinUtils::GetNSWindowPtr(aWnd); + if (wnd) { + ((nsWindow::WindowEnumCallback*)aParam)(wnd); + } + EnumChildWindows(aWnd, EnumAllChildWindProc, aParam); + return TRUE; +} + +void +nsWindow::EnumAllWindows(WindowEnumCallback aCallback) +{ + EnumThreadWindows(GetCurrentThreadId(), + EnumAllThreadWindowProc, + (LPARAM)aCallback); +} + +static already_AddRefed<SourceSurface> +CreateSourceSurfaceForGfxSurface(gfxASurface* aSurface) +{ + MOZ_ASSERT(aSurface); + return Factory::CreateSourceSurfaceForCairoSurface( + aSurface->CairoSurface(), aSurface->GetSize(), + aSurface->GetSurfaceFormat()); +} + +nsWindow::ScrollSnapshot* +nsWindow::EnsureSnapshotSurface(ScrollSnapshot& aSnapshotData, + const mozilla::gfx::IntSize& aSize) +{ + // If the surface doesn't exist or is the wrong size then create new one. + if (!aSnapshotData.surface || aSnapshotData.surface->GetSize() != aSize) { + aSnapshotData.surface = new gfxWindowsSurface(aSize, kScrollCaptureFormat); + aSnapshotData.surfaceHasSnapshot = false; + } + + return &aSnapshotData; +} + +already_AddRefed<SourceSurface> +nsWindow::CreateScrollSnapshot() +{ + RECT clip = { 0 }; + int rgnType = ::GetWindowRgnBox(mWnd, &clip); + if (rgnType == RGN_ERROR) { + // We failed to get the clip assume that we need a full fallback. + clip.left = 0; + clip.top = 0; + clip.right = mBounds.width; + clip.bottom = mBounds.height; + return GetFallbackScrollSnapshot(clip); + } + + // Check that the window is in a position to snapshot. We don't check for + // clipped width as that doesn't currently matter for APZ scrolling. + if (clip.top || clip.bottom != mBounds.height) { + return GetFallbackScrollSnapshot(clip); + } + + HDC windowDC = ::GetDC(mWnd); + if (!windowDC) { + return GetFallbackScrollSnapshot(clip); + } + auto releaseDC = MakeScopeExit([&] { + ::ReleaseDC(mWnd, windowDC); + }); + + gfx::IntSize snapshotSize(mBounds.width, mBounds.height); + ScrollSnapshot* snapshot; + if (clip.left || clip.right != mBounds.width) { + // Can't do a full snapshot, so use the partial snapshot. + snapshot = EnsureSnapshotSurface(mPartialSnapshot, snapshotSize); + } else { + snapshot = EnsureSnapshotSurface(mFullSnapshot, snapshotSize); + } + + // Note that we know that the clip is full height. + if (!::BitBlt(snapshot->surface->GetDC(), clip.left, 0, clip.right - clip.left, + clip.bottom, windowDC, clip.left, 0, SRCCOPY)) { + return GetFallbackScrollSnapshot(clip); + } + ::GdiFlush(); + snapshot->surface->Flush(); + snapshot->surfaceHasSnapshot = true; + snapshot->clip = clip; + mCurrentSnapshot = snapshot; + + return CreateSourceSurfaceForGfxSurface(mCurrentSnapshot->surface); +} + +already_AddRefed<SourceSurface> +nsWindow::GetFallbackScrollSnapshot(const RECT& aRequiredClip) +{ + gfx::IntSize snapshotSize(mBounds.width, mBounds.height); + + // If the current snapshot is the correct size and covers the required clip, + // just keep that by returning null. + // Note: we know the clip is always full height. + if (mCurrentSnapshot && + mCurrentSnapshot->surface->GetSize() == snapshotSize && + mCurrentSnapshot->clip.left <= aRequiredClip.left && + mCurrentSnapshot->clip.right >= aRequiredClip.right) { + return nullptr; + } + + // Otherwise we'll use the full snapshot, making sure it is big enough first. + mCurrentSnapshot = EnsureSnapshotSurface(mFullSnapshot, snapshotSize); + + // If there is no snapshot, create a default. + if (!mCurrentSnapshot->surfaceHasSnapshot) { + gfx::SurfaceFormat format = mCurrentSnapshot->surface->GetSurfaceFormat(); + RefPtr<DrawTarget> dt = Factory::CreateDrawTargetForCairoSurface( + mCurrentSnapshot->surface->CairoSurface(), + mCurrentSnapshot->surface->GetSize(), &format); + + DefaultFillScrollCapture(dt); + } + + return CreateSourceSurfaceForGfxSurface(mCurrentSnapshot->surface); +} + +/************************************************************** + * + * SECTION: nsIWidget::Show + * + * Hide or show this component. + * + **************************************************************/ + +NS_IMETHODIMP nsWindow::Show(bool bState) +{ + if (mWindowType == eWindowType_popup) { + // See bug 603793. When we try to draw D3D9/10 windows with a drop shadow + // without the DWM on a secondary monitor, windows fails to composite + // our windows correctly. We therefor switch off the drop shadow for + // pop-up windows when the DWM is disabled and two monitors are + // connected. + if (HasBogusPopupsDropShadowOnMultiMonitor() && + WinUtils::GetMonitorCount() > 1 && + !nsUXThemeData::CheckForCompositor()) + { + if (sDropShadowEnabled) { + ::SetClassLongA(mWnd, GCL_STYLE, 0); + sDropShadowEnabled = false; + } + } else { + if (!sDropShadowEnabled) { + ::SetClassLongA(mWnd, GCL_STYLE, CS_DROPSHADOW); + sDropShadowEnabled = true; + } + } + + // WS_EX_COMPOSITED conflicts with the WS_EX_LAYERED style and causes + // some popup menus to become invisible. + LONG_PTR exStyle = ::GetWindowLongPtrW(mWnd, GWL_EXSTYLE); + if (exStyle & WS_EX_LAYERED) { + ::SetWindowLongPtrW(mWnd, GWL_EXSTYLE, exStyle & ~WS_EX_COMPOSITED); + } + } + + bool syncInvalidate = false; + + bool wasVisible = mIsVisible; + // Set the status now so that anyone asking during ShowWindow or + // SetWindowPos would get the correct answer. + mIsVisible = bState; + + // We may have cached an out of date visible state. This can happen + // when session restore sets the full screen mode. + if (mIsVisible) + mOldStyle |= WS_VISIBLE; + else + mOldStyle &= ~WS_VISIBLE; + + if (!mIsVisible && wasVisible) { + ClearCachedResources(); + } + + if (mWnd) { + if (bState) { + if (!wasVisible && mWindowType == eWindowType_toplevel) { + // speed up the initial paint after show for + // top level windows: + syncInvalidate = true; + switch (mSizeMode) { + case nsSizeMode_Fullscreen: + ::ShowWindow(mWnd, SW_SHOW); + break; + case nsSizeMode_Maximized : + ::ShowWindow(mWnd, SW_SHOWMAXIMIZED); + break; + case nsSizeMode_Minimized : + ::ShowWindow(mWnd, SW_SHOWMINIMIZED); + break; + default: + if (CanTakeFocus()) { + ::ShowWindow(mWnd, SW_SHOWNORMAL); + } else { + ::ShowWindow(mWnd, SW_SHOWNOACTIVATE); + GetAttention(2); + } + break; + } + } else { + DWORD flags = SWP_NOSIZE | SWP_NOMOVE | SWP_SHOWWINDOW; + if (wasVisible) + flags |= SWP_NOZORDER; + + if (mWindowType == eWindowType_popup) { + // ensure popups are the topmost of the TOPMOST + // layer. Remember not to set the SWP_NOZORDER + // flag as that might allow the taskbar to overlap + // the popup. + flags |= SWP_NOACTIVATE; + HWND owner = ::GetWindow(mWnd, GW_OWNER); + ::SetWindowPos(mWnd, owner ? 0 : HWND_TOPMOST, 0, 0, 0, 0, flags); + } else { + if (mWindowType == eWindowType_dialog && !CanTakeFocus()) + flags |= SWP_NOACTIVATE; + + ::SetWindowPos(mWnd, HWND_TOP, 0, 0, 0, 0, flags); + } + } + + if (!wasVisible && (mWindowType == eWindowType_toplevel || mWindowType == eWindowType_dialog)) { + // when a toplevel window or dialog is shown, initialize the UI state + ::SendMessageW(mWnd, WM_CHANGEUISTATE, MAKEWPARAM(UIS_INITIALIZE, UISF_HIDEFOCUS | UISF_HIDEACCEL), 0); + } + } else { + // Clear contents to avoid ghosting of old content if we display + // this window again. + if (wasVisible && mTransparencyMode == eTransparencyTransparent) { + if (mCompositorWidgetDelegate) { + mCompositorWidgetDelegate->ClearTransparentWindow(); + } + } + if (mWindowType != eWindowType_dialog) { + ::ShowWindow(mWnd, SW_HIDE); + } else { + ::SetWindowPos(mWnd, 0, 0, 0, 0, 0, SWP_HIDEWINDOW | SWP_NOSIZE | SWP_NOMOVE | + SWP_NOZORDER | SWP_NOACTIVATE); + } + } + } + +#ifdef MOZ_XUL + if (!wasVisible && bState) { + Invalidate(); + if (syncInvalidate && !mInDtor && !mOnDestroyCalled) { + ::UpdateWindow(mWnd); + } + } +#endif + + return NS_OK; +} + +/************************************************************** + * + * SECTION: nsIWidget::IsVisible + * + * Returns the visibility state. + * + **************************************************************/ + +// Return true if the whether the component is visible, false otherwise +bool nsWindow::IsVisible() const +{ + return mIsVisible; +} + +/************************************************************** + * + * SECTION: Window clipping utilities + * + * Used in Size and Move operations for setting the proper + * window clipping regions for window transparency. + * + **************************************************************/ + +// XP and Vista visual styles sometimes require window clipping regions to be applied for proper +// transparency. These routines are called on size and move operations. +void nsWindow::ClearThemeRegion() +{ + if (IsVistaOrLater() && !HasGlass() && + (mWindowType == eWindowType_popup && !IsPopupWithTitleBar() && + (mPopupType == ePopupTypeTooltip || mPopupType == ePopupTypePanel))) { + SetWindowRgn(mWnd, nullptr, false); + } +} + +void nsWindow::SetThemeRegion() +{ + // Popup types that have a visual styles region applied (bug 376408). This can be expanded + // for other window types as needed. The regions are applied generically to the base window + // so default constants are used for part and state. At some point we might need part and + // state values from nsNativeThemeWin's GetThemePartAndState, but currently windows that + // change shape based on state haven't come up. + if (IsVistaOrLater() && !HasGlass() && + (mWindowType == eWindowType_popup && !IsPopupWithTitleBar() && + (mPopupType == ePopupTypeTooltip || mPopupType == ePopupTypePanel))) { + HRGN hRgn = nullptr; + RECT rect = {0,0,mBounds.width,mBounds.height}; + + HDC dc = ::GetDC(mWnd); + GetThemeBackgroundRegion(nsUXThemeData::GetTheme(eUXTooltip), dc, TTP_STANDARD, TS_NORMAL, &rect, &hRgn); + if (hRgn) { + if (!SetWindowRgn(mWnd, hRgn, false)) // do not delete or alter hRgn if accepted. + DeleteObject(hRgn); + } + ::ReleaseDC(mWnd, dc); + } +} + +/************************************************************** + * + * SECTION: Touch and APZ-related functions + * + **************************************************************/ + +void nsWindow::RegisterTouchWindow() { + mTouchWindow = true; + mGesture.RegisterTouchWindow(mWnd); + ::EnumChildWindows(mWnd, nsWindow::RegisterTouchForDescendants, 0); +} + +BOOL CALLBACK nsWindow::RegisterTouchForDescendants(HWND aWnd, LPARAM aMsg) { + nsWindow* win = WinUtils::GetNSWindowPtr(aWnd); + if (win) + win->mGesture.RegisterTouchWindow(aWnd); + return TRUE; +} + +/************************************************************** + * + * SECTION: nsIWidget::Move, nsIWidget::Resize, + * nsIWidget::Size, nsIWidget::BeginResizeDrag + * + * Repositioning and sizing a window. + * + **************************************************************/ + +void +nsWindow::SetSizeConstraints(const SizeConstraints& aConstraints) +{ + SizeConstraints c = aConstraints; + if (mWindowType != eWindowType_popup) { + c.mMinSize.width = std::max(int32_t(::GetSystemMetrics(SM_CXMINTRACK)), c.mMinSize.width); + c.mMinSize.height = std::max(int32_t(::GetSystemMetrics(SM_CYMINTRACK)), c.mMinSize.height); + } + ClientLayerManager *clientLayerManager = GetLayerManager()->AsClientLayerManager(); + + if (clientLayerManager) { + int32_t maxSize = clientLayerManager->GetMaxTextureSize(); + // We can't make ThebesLayers bigger than this anyway.. no point it letting + // a window grow bigger as we won't be able to draw content there in + // general. + c.mMaxSize.width = std::min(c.mMaxSize.width, maxSize); + c.mMaxSize.height = std::min(c.mMaxSize.height, maxSize); + } + + mSizeConstraintsScale = GetDefaultScale().scale; + + nsBaseWidget::SetSizeConstraints(c); +} + +const SizeConstraints +nsWindow::GetSizeConstraints() +{ + double scale = GetDefaultScale().scale; + if (mSizeConstraintsScale == scale || mSizeConstraintsScale == 0.0) { + return mSizeConstraints; + } + scale /= mSizeConstraintsScale; + SizeConstraints c = mSizeConstraints; + if (c.mMinSize.width != NS_MAXSIZE) { + c.mMinSize.width = NSToIntRound(c.mMinSize.width * scale); + } + if (c.mMinSize.height != NS_MAXSIZE) { + c.mMinSize.height = NSToIntRound(c.mMinSize.height * scale); + } + if (c.mMaxSize.width != NS_MAXSIZE) { + c.mMaxSize.width = NSToIntRound(c.mMaxSize.width * scale); + } + if (c.mMaxSize.height != NS_MAXSIZE) { + c.mMaxSize.height = NSToIntRound(c.mMaxSize.height * scale); + } + return c; +} + +// Move this component +NS_IMETHODIMP nsWindow::Move(double aX, double aY) +{ + if (mWindowType == eWindowType_toplevel || + mWindowType == eWindowType_dialog) { + SetSizeMode(nsSizeMode_Normal); + } + + // for top-level windows only, convert coordinates from desktop pixels + // (the "parent" coordinate space) to the window's device pixel space + double scale = BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0; + int32_t x = NSToIntRound(aX * scale); + int32_t y = NSToIntRound(aY * scale); + + // Check to see if window needs to be moved first + // to avoid a costly call to SetWindowPos. This check + // can not be moved to the calling code in nsView, because + // some platforms do not position child windows correctly + + // Only perform this check for non-popup windows, since the positioning can + // in fact change even when the x/y do not. We always need to perform the + // check. See bug #97805 for details. + if (mWindowType != eWindowType_popup && (mBounds.x == x) && (mBounds.y == y)) + { + // Nothing to do, since it is already positioned correctly. + return NS_OK; + } + + mBounds.x = x; + mBounds.y = y; + + if (mWnd) { +#ifdef DEBUG + // complain if a window is moved offscreen (legal, but potentially worrisome) + if (mIsTopWidgetWindow) { // only a problem for top-level windows + // Make sure this window is actually on the screen before we move it + // XXX: Needs multiple monitor support + HDC dc = ::GetDC(mWnd); + if (dc) { + if (::GetDeviceCaps(dc, TECHNOLOGY) == DT_RASDISPLAY) { + RECT workArea; + ::SystemParametersInfo(SPI_GETWORKAREA, 0, &workArea, 0); + // no annoying assertions. just mention the issue. + if (x < 0 || x >= workArea.right || y < 0 || y >= workArea.bottom) { + MOZ_LOG(gWindowsLog, LogLevel::Info, + ("window moved to offscreen position\n")); + } + } + ::ReleaseDC(mWnd, dc); + } + } +#endif + ClearThemeRegion(); + + UINT flags = SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOSIZE; + // Workaround SetWindowPos bug with D3D9. If our window has a clip + // region, some drivers or OSes may incorrectly copy into the clipped-out + // area. + if (IsPlugin() && + (!mLayerManager || mLayerManager->GetBackendType() == LayersBackend::LAYERS_D3D9) && + mClipRects && + (mClipRectCount != 1 || !mClipRects[0].IsEqualInterior(LayoutDeviceIntRect(0, 0, mBounds.width, mBounds.height)))) { + flags |= SWP_NOCOPYBITS; + } + double oldScale = mDefaultScale; + mResizeState = IN_SIZEMOVE; + VERIFY(::SetWindowPos(mWnd, nullptr, x, y, 0, 0, flags)); + mResizeState = NOT_RESIZING; + if (WinUtils::LogToPhysFactor(mWnd) != oldScale) { + ChangedDPI(); + } + + SetThemeRegion(); + } + NotifyRollupGeometryChange(); + return NS_OK; +} + +// Resize this component +NS_IMETHODIMP nsWindow::Resize(double aWidth, double aHeight, bool aRepaint) +{ + // for top-level windows only, convert coordinates from desktop pixels + // (the "parent" coordinate space) to the window's device pixel space + double scale = BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0; + int32_t width = NSToIntRound(aWidth * scale); + int32_t height = NSToIntRound(aHeight * scale); + + NS_ASSERTION((width >= 0) , "Negative width passed to nsWindow::Resize"); + NS_ASSERTION((height >= 0), "Negative height passed to nsWindow::Resize"); + + ConstrainSize(&width, &height); + + // Avoid unnecessary resizing calls + if (mBounds.width == width && mBounds.height == height) { + if (aRepaint) { + Invalidate(); + } + return NS_OK; + } + + // Set cached value for lightweight and printing + mBounds.width = width; + mBounds.height = height; + + if (mWnd) { + UINT flags = SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE; + + if (!aRepaint) { + flags |= SWP_NOREDRAW; + } + + ClearThemeRegion(); + double oldScale = mDefaultScale; + VERIFY(::SetWindowPos(mWnd, nullptr, 0, 0, + width, GetHeight(height), flags)); + if (WinUtils::LogToPhysFactor(mWnd) != oldScale) { + ChangedDPI(); + } + SetThemeRegion(); + } + + if (aRepaint) + Invalidate(); + + NotifyRollupGeometryChange(); + return NS_OK; +} + +// Resize this component +NS_IMETHODIMP nsWindow::Resize(double aX, double aY, double aWidth, + double aHeight, bool aRepaint) +{ + // for top-level windows only, convert coordinates from desktop pixels + // (the "parent" coordinate space) to the window's device pixel space + double scale = BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0; + int32_t x = NSToIntRound(aX * scale); + int32_t y = NSToIntRound(aY * scale); + int32_t width = NSToIntRound(aWidth * scale); + int32_t height = NSToIntRound(aHeight * scale); + + NS_ASSERTION((width >= 0), "Negative width passed to nsWindow::Resize"); + NS_ASSERTION((height >= 0), "Negative height passed to nsWindow::Resize"); + + ConstrainSize(&width, &height); + + // Avoid unnecessary resizing calls + if (mBounds.x == x && mBounds.y == y && + mBounds.width == width && mBounds.height == height) { + if (aRepaint) { + Invalidate(); + } + return NS_OK; + } + + // Set cached value for lightweight and printing + mBounds.x = x; + mBounds.y = y; + mBounds.width = width; + mBounds.height = height; + + if (mWnd) { + UINT flags = SWP_NOZORDER | SWP_NOACTIVATE; + if (!aRepaint) { + flags |= SWP_NOREDRAW; + } + + ClearThemeRegion(); + double oldScale = mDefaultScale; + VERIFY(::SetWindowPos(mWnd, nullptr, x, y, + width, GetHeight(height), flags)); + if (WinUtils::LogToPhysFactor(mWnd) != oldScale) { + ChangedDPI(); + } + if (mTransitionWnd) { + // If we have a fullscreen transition window, we need to make + // it topmost again, otherwise the taskbar may be raised by + // the system unexpectedly when we leave fullscreen state. + ::SetWindowPos(mTransitionWnd, HWND_TOPMOST, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); + // Every transition window is only used once. + mTransitionWnd = nullptr; + } + SetThemeRegion(); + } + + if (aRepaint) + Invalidate(); + + NotifyRollupGeometryChange(); + return NS_OK; +} + +NS_IMETHODIMP +nsWindow::BeginResizeDrag(WidgetGUIEvent* aEvent, + int32_t aHorizontal, + int32_t aVertical) +{ + NS_ENSURE_ARG_POINTER(aEvent); + + if (aEvent->mClass != eMouseEventClass) { + // you can only begin a resize drag with a mouse event + return NS_ERROR_INVALID_ARG; + } + + if (aEvent->AsMouseEvent()->button != WidgetMouseEvent::eLeftButton) { + // you can only begin a resize drag with the left mouse button + return NS_ERROR_INVALID_ARG; + } + + // work out what sizemode we're talking about + WPARAM syscommand; + if (aVertical < 0) { + if (aHorizontal < 0) { + syscommand = SC_SIZE | WMSZ_TOPLEFT; + } else if (aHorizontal == 0) { + syscommand = SC_SIZE | WMSZ_TOP; + } else { + syscommand = SC_SIZE | WMSZ_TOPRIGHT; + } + } else if (aVertical == 0) { + if (aHorizontal < 0) { + syscommand = SC_SIZE | WMSZ_LEFT; + } else if (aHorizontal == 0) { + return NS_ERROR_INVALID_ARG; + } else { + syscommand = SC_SIZE | WMSZ_RIGHT; + } + } else { + if (aHorizontal < 0) { + syscommand = SC_SIZE | WMSZ_BOTTOMLEFT; + } else if (aHorizontal == 0) { + syscommand = SC_SIZE | WMSZ_BOTTOM; + } else { + syscommand = SC_SIZE | WMSZ_BOTTOMRIGHT; + } + } + + // resizing doesn't work if the mouse is already captured + CaptureMouse(false); + + // find the top-level window + HWND toplevelWnd = WinUtils::GetTopLevelHWND(mWnd, true); + + // tell Windows to start the resize + ::PostMessage(toplevelWnd, WM_SYSCOMMAND, syscommand, + POINTTOPOINTS(aEvent->mRefPoint)); + + return NS_OK; +} + +/************************************************************** + * + * SECTION: Window Z-order and state. + * + * nsIWidget::PlaceBehind, nsIWidget::SetSizeMode, + * nsIWidget::ConstrainPosition + * + * Z-order, positioning, restore, minimize, and maximize. + * + **************************************************************/ + +// Position the window behind the given window +void +nsWindow::PlaceBehind(nsTopLevelWidgetZPlacement aPlacement, + nsIWidget *aWidget, bool aActivate) +{ + HWND behind = HWND_TOP; + if (aPlacement == eZPlacementBottom) + behind = HWND_BOTTOM; + else if (aPlacement == eZPlacementBelow && aWidget) + behind = (HWND)aWidget->GetNativeData(NS_NATIVE_WINDOW); + UINT flags = SWP_NOMOVE | SWP_NOREPOSITION | SWP_NOSIZE; + if (!aActivate) + flags |= SWP_NOACTIVATE; + + if (!CanTakeFocus() && behind == HWND_TOP) + { + // Can't place the window to top so place it behind the foreground window + // (as long as it is not topmost) + HWND wndAfter = ::GetForegroundWindow(); + if (!wndAfter) + behind = HWND_BOTTOM; + else if (!(GetWindowLongPtrW(wndAfter, GWL_EXSTYLE) & WS_EX_TOPMOST)) + behind = wndAfter; + flags |= SWP_NOACTIVATE; + } + + ::SetWindowPos(mWnd, behind, 0, 0, 0, 0, flags); +} + +static UINT +GetCurrentShowCmd(HWND aWnd) +{ + WINDOWPLACEMENT pl; + pl.length = sizeof(pl); + ::GetWindowPlacement(aWnd, &pl); + return pl.showCmd; +} + +// Maximize, minimize or restore the window. +void +nsWindow::SetSizeMode(nsSizeMode aMode) +{ + // Let's not try and do anything if we're already in that state. + // (This is needed to prevent problems when calling window.minimize(), which + // calls us directly, and then the OS triggers another call to us.) + if (aMode == mSizeMode) + return; + + // save the requested state + mLastSizeMode = mSizeMode; + nsBaseWidget::SetSizeMode(aMode); + if (mIsVisible) { + int mode; + + switch (aMode) { + case nsSizeMode_Fullscreen : + mode = SW_SHOW; + break; + + case nsSizeMode_Maximized : + mode = SW_MAXIMIZE; + break; + + case nsSizeMode_Minimized : + // Using SW_SHOWMINIMIZED prevents the working set from being trimmed but + // keeps the window active in the tray. So after the window is minimized, + // windows will fire WM_WINDOWPOSCHANGED (OnWindowPosChanged) at which point + // we will do some additional processing to get the active window set right. + // If sTrimOnMinimize is set, we let windows handle minimization normally + // using SW_MINIMIZE. + mode = sTrimOnMinimize ? SW_MINIMIZE : SW_SHOWMINIMIZED; + break; + + default : + mode = SW_RESTORE; + } + + // Don't call ::ShowWindow if we're trying to "restore" a window that is + // already in a normal state. Prevents a bug where snapping to one side + // of the screen and then minimizing would cause Windows to forget our + // window's correct restored position/size. + if(!(GetCurrentShowCmd(mWnd) == SW_SHOWNORMAL && mode == SW_RESTORE)) { + ::ShowWindow(mWnd, mode); + } + // we activate here to ensure that the right child window is focused + if (mode == SW_MAXIMIZE || mode == SW_SHOW) + DispatchFocusToTopLevelWindow(true); + } +} + +// Constrain a potential move to fit onscreen +// Position (aX, aY) is specified in Windows screen (logical) pixels, +// except when using per-monitor DPI, in which case it's device pixels. +void +nsWindow::ConstrainPosition(bool aAllowSlop, int32_t *aX, int32_t *aY) +{ + if (!mIsTopWidgetWindow) // only a problem for top-level windows + return; + + double dpiScale = GetDesktopToDeviceScale().scale; + + // We need to use the window size in the kind of pixels used for window- + // manipulation APIs. + int32_t logWidth = std::max<int32_t>(NSToIntRound(mBounds.width / dpiScale), 1); + int32_t logHeight = std::max<int32_t>(NSToIntRound(mBounds.height / dpiScale), 1); + + /* get our playing field. use the current screen, or failing that + for any reason, use device caps for the default screen. */ + RECT screenRect; + + nsCOMPtr<nsIScreenManager> screenmgr = do_GetService(sScreenManagerContractID); + if (!screenmgr) { + return; + } + nsCOMPtr<nsIScreen> screen; + int32_t left, top, width, height; + + screenmgr->ScreenForRect(*aX, *aY, logWidth, logHeight, + getter_AddRefs(screen)); + if (mSizeMode != nsSizeMode_Fullscreen) { + // For normalized windows, use the desktop work area. + nsresult rv = screen->GetAvailRectDisplayPix(&left, &top, &width, &height); + if (NS_FAILED(rv)) { + return; + } + } else { + // For full screen windows, use the desktop. + nsresult rv = screen->GetRectDisplayPix(&left, &top, &width, &height); + if (NS_FAILED(rv)) { + return; + } + } + screenRect.left = left; + screenRect.right = left + width; + screenRect.top = top; + screenRect.bottom = top + height; + + if (aAllowSlop) { + if (*aX < screenRect.left - logWidth + kWindowPositionSlop) + *aX = screenRect.left - logWidth + kWindowPositionSlop; + else if (*aX >= screenRect.right - kWindowPositionSlop) + *aX = screenRect.right - kWindowPositionSlop; + + if (*aY < screenRect.top - logHeight + kWindowPositionSlop) + *aY = screenRect.top - logHeight + kWindowPositionSlop; + else if (*aY >= screenRect.bottom - kWindowPositionSlop) + *aY = screenRect.bottom - kWindowPositionSlop; + + } else { + + if (*aX < screenRect.left) + *aX = screenRect.left; + else if (*aX >= screenRect.right - logWidth) + *aX = screenRect.right - logWidth; + + if (*aY < screenRect.top) + *aY = screenRect.top; + else if (*aY >= screenRect.bottom - logHeight) + *aY = screenRect.bottom - logHeight; + } +} + +/************************************************************** + * + * SECTION: nsIWidget::Enable, nsIWidget::IsEnabled + * + * Enabling and disabling the widget. + * + **************************************************************/ + +// Enable/disable this component +NS_IMETHODIMP nsWindow::Enable(bool bState) +{ + if (mWnd) { + ::EnableWindow(mWnd, bState); + } + return NS_OK; +} + +// Return the current enable state +bool nsWindow::IsEnabled() const +{ + return !mWnd || + (::IsWindowEnabled(mWnd) && + ::IsWindowEnabled(::GetAncestor(mWnd, GA_ROOT))); +} + + +/************************************************************** + * + * SECTION: nsIWidget::SetFocus + * + * Give the focus to this widget. + * + **************************************************************/ + +NS_IMETHODIMP nsWindow::SetFocus(bool aRaise) +{ + if (mWnd) { +#ifdef WINSTATE_DEBUG_OUTPUT + if (mWnd == WinUtils::GetTopLevelHWND(mWnd)) { + MOZ_LOG(gWindowsLog, LogLevel::Info, + ("*** SetFocus: [ top] raise=%d\n", aRaise)); + } else { + MOZ_LOG(gWindowsLog, LogLevel::Info, + ("*** SetFocus: [child] raise=%d\n", aRaise)); + } +#endif + // Uniconify, if necessary + HWND toplevelWnd = WinUtils::GetTopLevelHWND(mWnd); + if (aRaise && ::IsIconic(toplevelWnd)) { + ::ShowWindow(toplevelWnd, SW_RESTORE); + } + ::SetFocus(mWnd); + } + return NS_OK; +} + + +/************************************************************** + * + * SECTION: Bounds + * + * GetBounds, GetClientBounds, GetScreenBounds, + * GetRestoredBounds, GetClientOffset + * SetDrawsInTitlebar, SetNonClientMargins + * + * Bound calculations. + * + **************************************************************/ + +// Return the window's full dimensions in screen coordinates. +// If the window has a parent, converts the origin to an offset +// of the parent's screen origin. +LayoutDeviceIntRect +nsWindow::GetBounds() +{ + if (!mWnd) { + return mBounds; + } + + RECT r; + VERIFY(::GetWindowRect(mWnd, &r)); + + LayoutDeviceIntRect rect; + + // assign size + rect.width = r.right - r.left; + rect.height = r.bottom - r.top; + + // popup window bounds' are in screen coordinates, not relative to parent + // window + if (mWindowType == eWindowType_popup) { + rect.x = r.left; + rect.y = r.top; + return rect; + } + + // chrome on parent: + // ___ 5,5 (chrome start) + // | ____ 10,10 (client start) + // | | ____ 20,20 (child start) + // | | | + // 20,20 - 5,5 = 15,15 (??) + // minus GetClientOffset: + // 15,15 - 5,5 = 10,10 + // + // no chrome on parent: + // ______ 10,10 (win start) + // | ____ 20,20 (child start) + // | | + // 20,20 - 10,10 = 10,10 + // + // walking the chain: + // ___ 5,5 (chrome start) + // | ___ 10,10 (client start) + // | | ___ 20,20 (child start) + // | | | __ 30,30 (child start) + // | | | | + // 30,30 - 20,20 = 10,10 (offset from second child to first) + // 20,20 - 5,5 = 15,15 + 10,10 = 25,25 (??) + // minus GetClientOffset: + // 25,25 - 5,5 = 20,20 (offset from second child to parent client) + + // convert coordinates if parent exists + HWND parent = ::GetParent(mWnd); + if (parent) { + RECT pr; + VERIFY(::GetWindowRect(parent, &pr)); + r.left -= pr.left; + r.top -= pr.top; + // adjust for chrome + nsWindow* pWidget = static_cast<nsWindow*>(GetParent()); + if (pWidget && pWidget->IsTopLevelWidget()) { + LayoutDeviceIntPoint clientOffset = pWidget->GetClientOffset(); + r.left -= clientOffset.x; + r.top -= clientOffset.y; + } + } + rect.x = r.left; + rect.y = r.top; + return rect; +} + +// Get this component dimension +LayoutDeviceIntRect +nsWindow::GetClientBounds() +{ + if (!mWnd) { + return LayoutDeviceIntRect(0, 0, 0, 0); + } + + RECT r; + VERIFY(::GetClientRect(mWnd, &r)); + + LayoutDeviceIntRect bounds = GetBounds(); + LayoutDeviceIntRect rect; + rect.MoveTo(bounds.TopLeft() + GetClientOffset()); + rect.width = r.right - r.left; + rect.height = r.bottom - r.top; + return rect; +} + +// Like GetBounds, but don't offset by the parent +LayoutDeviceIntRect +nsWindow::GetScreenBounds() +{ + if (!mWnd) { + return mBounds; + } + + RECT r; + VERIFY(::GetWindowRect(mWnd, &r)); + + LayoutDeviceIntRect rect; + rect.x = r.left; + rect.y = r.top; + rect.width = r.right - r.left; + rect.height = r.bottom - r.top; + return rect; +} + +nsresult +nsWindow::GetRestoredBounds(LayoutDeviceIntRect &aRect) +{ + if (SizeMode() == nsSizeMode_Normal) { + aRect = GetScreenBounds(); + return NS_OK; + } + if (!mWnd) { + return NS_ERROR_FAILURE; + } + + WINDOWPLACEMENT pl = { sizeof(WINDOWPLACEMENT) }; + VERIFY(::GetWindowPlacement(mWnd, &pl)); + const RECT& r = pl.rcNormalPosition; + + HMONITOR monitor = ::MonitorFromWindow(mWnd, MONITOR_DEFAULTTONULL); + if (!monitor) { + return NS_ERROR_FAILURE; + } + MONITORINFO mi = { sizeof(MONITORINFO) }; + VERIFY(::GetMonitorInfo(monitor, &mi)); + + aRect.SetRect(r.left, r.top, r.right - r.left, r.bottom - r.top); + aRect.MoveBy(mi.rcWork.left - mi.rcMonitor.left, + mi.rcWork.top - mi.rcMonitor.top); + return NS_OK; +} + +// Return the x,y offset of the client area from the origin of the window. If +// the window is borderless returns (0,0). +LayoutDeviceIntPoint +nsWindow::GetClientOffset() +{ + if (!mWnd) { + return LayoutDeviceIntPoint(0, 0); + } + + RECT r1; + GetWindowRect(mWnd, &r1); + LayoutDeviceIntPoint pt = WidgetToScreenOffset(); + return LayoutDeviceIntPoint(pt.x - r1.left, pt.y - r1.top); +} + +void +nsWindow::SetDrawsInTitlebar(bool aState) +{ + nsWindow * window = GetTopLevelWindow(true); + if (window && window != this) { + return window->SetDrawsInTitlebar(aState); + } + + if (aState) { + // top, right, bottom, left for nsIntMargin + LayoutDeviceIntMargin margins(0, -1, -1, -1); + SetNonClientMargins(margins); + } + else { + LayoutDeviceIntMargin margins(-1, -1, -1, -1); + SetNonClientMargins(margins); + } +} + +void +nsWindow::ResetLayout() +{ + // This will trigger a frame changed event, triggering + // nc calc size and a sizemode gecko event. + SetWindowPos(mWnd, 0, 0, 0, 0, 0, + SWP_FRAMECHANGED|SWP_NOACTIVATE|SWP_NOMOVE| + SWP_NOOWNERZORDER|SWP_NOSIZE|SWP_NOZORDER); + + // If hidden, just send the frame changed event for now. + if (!mIsVisible) + return; + + // Send a gecko size event to trigger reflow. + RECT clientRc = {0}; + GetClientRect(mWnd, &clientRc); + nsIntRect evRect(WinUtils::ToIntRect(clientRc)); + OnResize(evRect); + + // Invalidate and update + Invalidate(); +} + +// Internally track the caption status via a window property. Required +// due to our internal handling of WM_NCACTIVATE when custom client +// margins are set. +static const wchar_t kManageWindowInfoProperty[] = L"ManageWindowInfoProperty"; +typedef BOOL (WINAPI *GetWindowInfoPtr)(HWND hwnd, PWINDOWINFO pwi); +static GetWindowInfoPtr sGetWindowInfoPtrStub = nullptr; + +BOOL WINAPI +GetWindowInfoHook(HWND hWnd, PWINDOWINFO pwi) +{ + if (!sGetWindowInfoPtrStub) { + NS_ASSERTION(FALSE, "Something is horribly wrong in GetWindowInfoHook!"); + return FALSE; + } + int windowStatus = + reinterpret_cast<LONG_PTR>(GetPropW(hWnd, kManageWindowInfoProperty)); + // No property set, return the default data. + if (!windowStatus) + return sGetWindowInfoPtrStub(hWnd, pwi); + // Call GetWindowInfo and update dwWindowStatus with our + // internally tracked value. + BOOL result = sGetWindowInfoPtrStub(hWnd, pwi); + if (result && pwi) + pwi->dwWindowStatus = (windowStatus == 1 ? 0 : WS_ACTIVECAPTION); + return result; +} + +void +nsWindow::UpdateGetWindowInfoCaptionStatus(bool aActiveCaption) +{ + if (!mWnd) + return; + + if (!sGetWindowInfoPtrStub) { + sUser32Intercept.Init("user32.dll"); + if (!sUser32Intercept.AddHook("GetWindowInfo", reinterpret_cast<intptr_t>(GetWindowInfoHook), + (void**) &sGetWindowInfoPtrStub)) + return; + } + // Update our internally tracked caption status + SetPropW(mWnd, kManageWindowInfoProperty, + reinterpret_cast<HANDLE>(static_cast<INT_PTR>(aActiveCaption) + 1)); +} + +/** + * Called when the window layout changes: full screen mode transitions, + * theme changes, and composition changes. Calculates the new non-client + * margins and fires off a frame changed event, which triggers an nc calc + * size windows event, kicking the changes in. + * + * The offsets calculated here are based on the value of `mNonClientMargins` + * which is specified in the "chromemargins" attribute of the window. For + * each margin, the value specified has the following meaning: + * -1 - leave the default frame in place + * 0 - remove the frame + * >0 - frame size equals min(0, (default frame size - margin value)) + * + * This function calculates and populates `mNonClientOffset`. + * In our processing of `WM_NCCALCSIZE`, the frame size will be calculated + * as (default frame size - offset). For example, if the left frame should + * be 1 pixel narrower than the default frame size, `mNonClientOffset.left` + * will equal 1. + * + * For maximized, fullscreen, and minimized windows, the values stored in + * `mNonClientMargins` are ignored, and special processing takes place. + * + * For non-glass windows, we only allow frames to be their default size + * or removed entirely. + */ +bool +nsWindow::UpdateNonClientMargins(int32_t aSizeMode, bool aReflowWindow) +{ + if (!mCustomNonClient) + return false; + + if (aSizeMode == -1) { + aSizeMode = mSizeMode; + } + + bool hasCaption = (mBorderStyle + & (eBorderStyle_all + | eBorderStyle_title + | eBorderStyle_menu + | eBorderStyle_default)); + + // mCaptionHeight is the default size of the NC area at + // the top of the window. If the window has a caption, + // the size is calculated as the sum of: + // SM_CYFRAME - The thickness of the sizing border + // around a resizable window + // SM_CXPADDEDBORDER - The amount of border padding + // for captioned windows + // SM_CYCAPTION - The height of the caption area + // + // If the window does not have a caption, mCaptionHeight will be equal to + // `GetSystemMetrics(SM_CYFRAME)` + mCaptionHeight = GetSystemMetrics(SM_CYFRAME) + + (hasCaption ? GetSystemMetrics(SM_CYCAPTION) + + GetSystemMetrics(SM_CXPADDEDBORDER) + : 0); + + // mHorResizeMargin is the size of the default NC areas on the + // left and right sides of our window. It is calculated as + // the sum of: + // SM_CXFRAME - The thickness of the sizing border + // SM_CXPADDEDBORDER - The amount of border padding + // for captioned windows + // + // If the window does not have a caption, mHorResizeMargin will be equal to + // `GetSystemMetrics(SM_CXFRAME)` + mHorResizeMargin = GetSystemMetrics(SM_CXFRAME) + + (hasCaption ? GetSystemMetrics(SM_CXPADDEDBORDER) : 0); + + // mVertResizeMargin is the size of the default NC area at the + // bottom of the window. It is calculated as the sum of: + // SM_CYFRAME - The thickness of the sizing border + // SM_CXPADDEDBORDER - The amount of border padding + // for captioned windows. + // + // If the window does not have a caption, mVertResizeMargin will be equal to + // `GetSystemMetrics(SM_CYFRAME)` + mVertResizeMargin = GetSystemMetrics(SM_CYFRAME) + + (hasCaption ? GetSystemMetrics(SM_CXPADDEDBORDER) : 0); + + if (aSizeMode == nsSizeMode_Minimized) { + // Use default frame size for minimized windows + mNonClientOffset.top = 0; + mNonClientOffset.left = 0; + mNonClientOffset.right = 0; + mNonClientOffset.bottom = 0; + } else if (aSizeMode == nsSizeMode_Fullscreen) { + // Remove the default frame from the top of our fullscreen window. This + // makes the whole caption part of our client area, allowing us to draw + // in the whole caption area. Additionally remove the default frame from + // the left, right, and bottom. + mNonClientOffset.top = mCaptionHeight; + mNonClientOffset.bottom = mVertResizeMargin; + mNonClientOffset.left = mHorResizeMargin; + mNonClientOffset.right = mHorResizeMargin; + } else if (aSizeMode == nsSizeMode_Maximized) { + // Remove the default frame from the top of our maximized window. This + // makes the whole caption part of our client area, allowing us to draw + // in the whole caption area. Use default frame size on left, right, and + // bottom. The reason this works is that, for maximized windows, + // Windows positions them so that their frames fall off the screen. + // This gives the illusion of windows having no frames when they are + // maximized. If we try to mess with the frame sizes by setting these + // offsets to positive values, our client area will fall off the screen. + mNonClientOffset.top = mCaptionHeight; + mNonClientOffset.bottom = 0; + mNonClientOffset.left = 0; + mNonClientOffset.right = 0; + + APPBARDATA appBarData; + appBarData.cbSize = sizeof(appBarData); + UINT taskbarState = SHAppBarMessage(ABM_GETSTATE, &appBarData); + if (ABS_AUTOHIDE & taskbarState) { + UINT edge = -1; + appBarData.hWnd = FindWindow(L"Shell_TrayWnd", nullptr); + if (appBarData.hWnd) { + HMONITOR taskbarMonitor = ::MonitorFromWindow(appBarData.hWnd, + MONITOR_DEFAULTTOPRIMARY); + HMONITOR windowMonitor = ::MonitorFromWindow(mWnd, + MONITOR_DEFAULTTONEAREST); + if (taskbarMonitor == windowMonitor) { + SHAppBarMessage(ABM_GETTASKBARPOS, &appBarData); + edge = appBarData.uEdge; + } + } + + if (ABE_LEFT == edge) { + mNonClientOffset.left -= 1; + } else if (ABE_RIGHT == edge) { + mNonClientOffset.right -= 1; + } else if (ABE_BOTTOM == edge || ABE_TOP == edge) { + mNonClientOffset.bottom -= 1; + } + } + } else { + bool glass = nsUXThemeData::CheckForCompositor(); + + // We're dealing with a "normal" window (not maximized, minimized, or + // fullscreen), so process `mNonClientMargins` and set `mNonClientOffset` + // accordingly. + // + // Setting `mNonClientOffset` to 0 has the effect of leaving the default + // frame intact. Setting it to a value greater than 0 reduces the frame + // size by that amount. + + if (mNonClientMargins.top > 0 && glass) { + mNonClientOffset.top = std::min(mCaptionHeight, mNonClientMargins.top); + } else if (mNonClientMargins.top == 0) { + mNonClientOffset.top = mCaptionHeight; + } else { + mNonClientOffset.top = 0; + } + + if (mNonClientMargins.bottom > 0 && glass) { + mNonClientOffset.bottom = std::min(mVertResizeMargin, mNonClientMargins.bottom); + } else if (mNonClientMargins.bottom == 0) { + mNonClientOffset.bottom = mVertResizeMargin; + } else { + mNonClientOffset.bottom = 0; + } + + if (mNonClientMargins.left > 0 && glass) { + mNonClientOffset.left = std::min(mHorResizeMargin, mNonClientMargins.left); + } else if (mNonClientMargins.left == 0) { + mNonClientOffset.left = mHorResizeMargin; + } else { + mNonClientOffset.left = 0; + } + + if (mNonClientMargins.right > 0 && glass) { + mNonClientOffset.right = std::min(mHorResizeMargin, mNonClientMargins.right); + } else if (mNonClientMargins.right == 0) { + mNonClientOffset.right = mHorResizeMargin; + } else { + mNonClientOffset.right = 0; + } + } + + if (aReflowWindow) { + // Force a reflow of content based on the new client + // dimensions. + ResetLayout(); + } + + return true; +} + +NS_IMETHODIMP +nsWindow::SetNonClientMargins(LayoutDeviceIntMargin &margins) +{ + if (!mIsTopWidgetWindow || + mBorderStyle == eBorderStyle_none) + return NS_ERROR_INVALID_ARG; + + if (mHideChrome) { + mFutureMarginsOnceChromeShows = margins; + mFutureMarginsToUse = true; + return NS_OK; + } + mFutureMarginsToUse = false; + + // Request for a reset + if (margins.top == -1 && margins.left == -1 && + margins.right == -1 && margins.bottom == -1) { + mCustomNonClient = false; + mNonClientMargins = margins; + // Force a reflow of content based on the new client + // dimensions. + ResetLayout(); + + int windowStatus = + reinterpret_cast<LONG_PTR>(GetPropW(mWnd, kManageWindowInfoProperty)); + if (windowStatus) { + ::SendMessageW(mWnd, WM_NCACTIVATE, 1 != windowStatus, 0); + } + + return NS_OK; + } + + if (margins.top < -1 || margins.bottom < -1 || + margins.left < -1 || margins.right < -1) + return NS_ERROR_INVALID_ARG; + + mNonClientMargins = margins; + mCustomNonClient = true; + if (!UpdateNonClientMargins()) { + NS_WARNING("UpdateNonClientMargins failed!"); + return NS_OK; + } + + return NS_OK; +} + +void +nsWindow::InvalidateNonClientRegion() +{ + // +-+-----------------------+-+ + // | | app non-client chrome | | + // | +-----------------------+ | + // | | app client chrome | | } + // | +-----------------------+ | } + // | | app content | | } area we don't want to invalidate + // | +-----------------------+ | } + // | | app client chrome | | } + // | +-----------------------+ | + // +---------------------------+ < + // ^ ^ windows non-client chrome + // client area = app * + RECT rect; + GetWindowRect(mWnd, &rect); + MapWindowPoints(nullptr, mWnd, (LPPOINT)&rect, 2); + HRGN winRgn = CreateRectRgnIndirect(&rect); + + // Subtract app client chrome and app content leaving + // windows non-client chrome and app non-client chrome + // in winRgn. + GetWindowRect(mWnd, &rect); + rect.top += mCaptionHeight; + rect.right -= mHorResizeMargin; + rect.bottom -= mHorResizeMargin; + rect.left += mVertResizeMargin; + MapWindowPoints(nullptr, mWnd, (LPPOINT)&rect, 2); + HRGN clientRgn = CreateRectRgnIndirect(&rect); + CombineRgn(winRgn, winRgn, clientRgn, RGN_DIFF); + DeleteObject(clientRgn); + + // triggers ncpaint and paint events for the two areas + RedrawWindow(mWnd, nullptr, winRgn, RDW_FRAME | RDW_INVALIDATE); + DeleteObject(winRgn); +} + +HRGN +nsWindow::ExcludeNonClientFromPaintRegion(HRGN aRegion) +{ + RECT rect; + HRGN rgn = nullptr; + if (aRegion == (HRGN)1) { // undocumented value indicating a full refresh + GetWindowRect(mWnd, &rect); + rgn = CreateRectRgnIndirect(&rect); + } else { + rgn = aRegion; + } + GetClientRect(mWnd, &rect); + MapWindowPoints(mWnd, nullptr, (LPPOINT)&rect, 2); + HRGN nonClientRgn = CreateRectRgnIndirect(&rect); + CombineRgn(rgn, rgn, nonClientRgn, RGN_DIFF); + DeleteObject(nonClientRgn); + return rgn; +} + +/************************************************************** + * + * SECTION: nsIWidget::SetBackgroundColor + * + * Sets the window background paint color. + * + **************************************************************/ + +void nsWindow::SetBackgroundColor(const nscolor &aColor) +{ + if (mBrush) + ::DeleteObject(mBrush); + + mBrush = ::CreateSolidBrush(NSRGB_2_COLOREF(aColor)); + if (mWnd != nullptr) { + ::SetClassLongPtrW(mWnd, GCLP_HBRBACKGROUND, (LONG_PTR)mBrush); + } +} + +/************************************************************** + * + * SECTION: nsIWidget::SetCursor + * + * SetCursor and related utilities for manging cursor state. + * + **************************************************************/ + +// Set this component cursor +NS_IMETHODIMP nsWindow::SetCursor(nsCursor aCursor) +{ + // Only change cursor if it's changing + + //XXX mCursor isn't always right. Scrollbars and others change it, too. + //XXX If we want this optimization we need a better way to do it. + //if (aCursor != mCursor) { + HCURSOR newCursor = nullptr; + + switch (aCursor) { + case eCursor_select: + newCursor = ::LoadCursor(nullptr, IDC_IBEAM); + break; + + case eCursor_wait: + newCursor = ::LoadCursor(nullptr, IDC_WAIT); + break; + + case eCursor_hyperlink: + { + newCursor = ::LoadCursor(nullptr, IDC_HAND); + break; + } + + case eCursor_standard: + case eCursor_context_menu: // XXX See bug 258960. + newCursor = ::LoadCursor(nullptr, IDC_ARROW); + break; + + case eCursor_n_resize: + case eCursor_s_resize: + newCursor = ::LoadCursor(nullptr, IDC_SIZENS); + break; + + case eCursor_w_resize: + case eCursor_e_resize: + newCursor = ::LoadCursor(nullptr, IDC_SIZEWE); + break; + + case eCursor_nw_resize: + case eCursor_se_resize: + newCursor = ::LoadCursor(nullptr, IDC_SIZENWSE); + break; + + case eCursor_ne_resize: + case eCursor_sw_resize: + newCursor = ::LoadCursor(nullptr, IDC_SIZENESW); + break; + + case eCursor_crosshair: + newCursor = ::LoadCursor(nullptr, IDC_CROSS); + break; + + case eCursor_move: + newCursor = ::LoadCursor(nullptr, IDC_SIZEALL); + break; + + case eCursor_help: + newCursor = ::LoadCursor(nullptr, IDC_HELP); + break; + + case eCursor_copy: // CSS3 + newCursor = ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_COPY)); + break; + + case eCursor_alias: + newCursor = ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_ALIAS)); + break; + + case eCursor_cell: + newCursor = ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_CELL)); + break; + + case eCursor_grab: + newCursor = ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_GRAB)); + break; + + case eCursor_grabbing: + newCursor = ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_GRABBING)); + break; + + case eCursor_spinning: + newCursor = ::LoadCursor(nullptr, IDC_APPSTARTING); + break; + + case eCursor_zoom_in: + newCursor = ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_ZOOMIN)); + break; + + case eCursor_zoom_out: + newCursor = ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_ZOOMOUT)); + break; + + case eCursor_not_allowed: + case eCursor_no_drop: + newCursor = ::LoadCursor(nullptr, IDC_NO); + break; + + case eCursor_col_resize: + newCursor = ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_COLRESIZE)); + break; + + case eCursor_row_resize: + newCursor = ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_ROWRESIZE)); + break; + + case eCursor_vertical_text: + newCursor = ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_VERTICALTEXT)); + break; + + case eCursor_all_scroll: + // XXX not 100% appropriate perhaps + newCursor = ::LoadCursor(nullptr, IDC_SIZEALL); + break; + + case eCursor_nesw_resize: + newCursor = ::LoadCursor(nullptr, IDC_SIZENESW); + break; + + case eCursor_nwse_resize: + newCursor = ::LoadCursor(nullptr, IDC_SIZENWSE); + break; + + case eCursor_ns_resize: + newCursor = ::LoadCursor(nullptr, IDC_SIZENS); + break; + + case eCursor_ew_resize: + newCursor = ::LoadCursor(nullptr, IDC_SIZEWE); + break; + + case eCursor_none: + newCursor = ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_NONE)); + break; + + default: + NS_ERROR("Invalid cursor type"); + break; + } + + if (nullptr != newCursor) { + mCursor = aCursor; + HCURSOR oldCursor = ::SetCursor(newCursor); + + if (sHCursor == oldCursor) { + NS_IF_RELEASE(sCursorImgContainer); + if (sHCursor != nullptr) + ::DestroyIcon(sHCursor); + sHCursor = nullptr; + } + } + + return NS_OK; +} + +// Setting the actual cursor +NS_IMETHODIMP nsWindow::SetCursor(imgIContainer* aCursor, + uint32_t aHotspotX, uint32_t aHotspotY) +{ + if (sCursorImgContainer == aCursor && sHCursor) { + ::SetCursor(sHCursor); + return NS_OK; + } + + int32_t width; + int32_t height; + + nsresult rv; + rv = aCursor->GetWidth(&width); + NS_ENSURE_SUCCESS(rv, rv); + rv = aCursor->GetHeight(&height); + NS_ENSURE_SUCCESS(rv, rv); + + // Reject cursors greater than 128 pixels in either direction, to prevent + // spoofing. + // XXX ideally we should rescale. Also, we could modify the API to + // allow trusted content to set larger cursors. + if (width > 128 || height > 128) + return NS_ERROR_NOT_AVAILABLE; + + HCURSOR cursor; + double scale = GetDefaultScale().scale; + IntSize size = RoundedToInt(Size(width * scale, height * scale)); + rv = nsWindowGfx::CreateIcon(aCursor, true, aHotspotX, aHotspotY, size, &cursor); + NS_ENSURE_SUCCESS(rv, rv); + + mCursor = nsCursor(-1); + ::SetCursor(cursor); + + NS_IF_RELEASE(sCursorImgContainer); + sCursorImgContainer = aCursor; + NS_ADDREF(sCursorImgContainer); + + if (sHCursor != nullptr) + ::DestroyIcon(sHCursor); + sHCursor = cursor; + + return NS_OK; +} + +/************************************************************** + * + * SECTION: nsIWidget::Get/SetTransparencyMode + * + * Manage the transparency mode of the top-level window + * containing this widget. + * + **************************************************************/ + +#ifdef MOZ_XUL +nsTransparencyMode nsWindow::GetTransparencyMode() +{ + return GetTopLevelWindow(true)->GetWindowTranslucencyInner(); +} + +void nsWindow::SetTransparencyMode(nsTransparencyMode aMode) +{ + GetTopLevelWindow(true)->SetWindowTranslucencyInner(aMode); +} + +void nsWindow::UpdateOpaqueRegion(const LayoutDeviceIntRegion& aOpaqueRegion) +{ + if (!HasGlass() || GetParent()) + return; + + // If there is no opaque region or hidechrome=true, set margins + // to support a full sheet of glass. Comments in MSDN indicate + // all values must be set to -1 to get a full sheet of glass. + MARGINS margins = { -1, -1, -1, -1 }; + if (!aOpaqueRegion.IsEmpty()) { + LayoutDeviceIntRect pluginBounds; + for (nsIWidget* child = GetFirstChild(); child; child = child->GetNextSibling()) { + if (child->IsPlugin()) { + // Collect the bounds of all plugins for GetLargestRectangle. + LayoutDeviceIntRect childBounds = child->GetBounds(); + pluginBounds.UnionRect(pluginBounds, childBounds); + } + } + + LayoutDeviceIntRect clientBounds = GetClientBounds(); + + // Find the largest rectangle and use that to calculate the inset. Our top + // priority is to include the bounds of all plugins. + LayoutDeviceIntRect largest = + aOpaqueRegion.GetLargestRectangle(pluginBounds); + margins.cxLeftWidth = largest.x; + margins.cxRightWidth = clientBounds.width - largest.XMost(); + margins.cyBottomHeight = clientBounds.height - largest.YMost(); + if (mCustomNonClient) { + // The minimum glass height must be the caption buttons height, + // otherwise the buttons are drawn incorrectly. + largest.y = std::max<uint32_t>(largest.y, + nsUXThemeData::sCommandButtons[CMDBUTTONIDX_BUTTONBOX].cy); + } + margins.cyTopHeight = largest.y; + } + + // Only update glass area if there are changes + if (memcmp(&mGlassMargins, &margins, sizeof mGlassMargins)) { + mGlassMargins = margins; + UpdateGlass(); + } +} + +/************************************************************** +* +* SECTION: nsIWidget::UpdateWindowDraggingRegion +* +* For setting the draggable titlebar region from CSS +* with -moz-window-dragging: drag. +* +**************************************************************/ + +void +nsWindow::UpdateWindowDraggingRegion(const LayoutDeviceIntRegion& aRegion) +{ + if (mDraggableRegion != aRegion) { + mDraggableRegion = aRegion; + } +} + +void nsWindow::UpdateGlass() +{ + MARGINS margins = mGlassMargins; + + // DWMNCRP_USEWINDOWSTYLE - The non-client rendering area is + // rendered based on the window style. + // DWMNCRP_ENABLED - The non-client area rendering is + // enabled; the window style is ignored. + DWMNCRENDERINGPOLICY policy = DWMNCRP_USEWINDOWSTYLE; + switch (mTransparencyMode) { + case eTransparencyBorderlessGlass: + // Only adjust if there is some opaque rectangle + if (margins.cxLeftWidth >= 0) { + margins.cxLeftWidth += kGlassMarginAdjustment; + margins.cyTopHeight += kGlassMarginAdjustment; + margins.cxRightWidth += kGlassMarginAdjustment; + margins.cyBottomHeight += kGlassMarginAdjustment; + } + // Fall through + case eTransparencyGlass: + policy = DWMNCRP_ENABLED; + break; + default: + break; + } + + MOZ_LOG(gWindowsLog, LogLevel::Info, + ("glass margins: left:%d top:%d right:%d bottom:%d\n", + margins.cxLeftWidth, margins.cyTopHeight, + margins.cxRightWidth, margins.cyBottomHeight)); + + // Extends the window frame behind the client area + if (nsUXThemeData::CheckForCompositor()) { + WinUtils::dwmExtendFrameIntoClientAreaPtr(mWnd, &margins); + WinUtils::dwmSetWindowAttributePtr(mWnd, DWMWA_NCRENDERING_POLICY, &policy, sizeof policy); + } +} +#endif + +/************************************************************** + * + * SECTION: nsIWidget::HideWindowChrome + * + * Show or hide window chrome. + * + **************************************************************/ + +NS_IMETHODIMP nsWindow::HideWindowChrome(bool aShouldHide) +{ + HWND hwnd = WinUtils::GetTopLevelHWND(mWnd, true); + if (!WinUtils::GetNSWindowPtr(hwnd)) + { + NS_WARNING("Trying to hide window decorations in an embedded context"); + return NS_ERROR_FAILURE; + } + + if (mHideChrome == aShouldHide) + return NS_OK; + + DWORD_PTR style, exStyle; + mHideChrome = aShouldHide; + if (aShouldHide) { + DWORD_PTR tempStyle = ::GetWindowLongPtrW(hwnd, GWL_STYLE); + DWORD_PTR tempExStyle = ::GetWindowLongPtrW(hwnd, GWL_EXSTYLE); + + style = tempStyle & ~(WS_CAPTION | WS_THICKFRAME); + exStyle = tempExStyle & ~(WS_EX_DLGMODALFRAME | WS_EX_WINDOWEDGE | + WS_EX_CLIENTEDGE | WS_EX_STATICEDGE); + + mOldStyle = tempStyle; + mOldExStyle = tempExStyle; + } + else { + if (!mOldStyle || !mOldExStyle) { + mOldStyle = ::GetWindowLongPtrW(hwnd, GWL_STYLE); + mOldExStyle = ::GetWindowLongPtrW(hwnd, GWL_EXSTYLE); + } + + style = mOldStyle; + exStyle = mOldExStyle; + if (mFutureMarginsToUse) { + SetNonClientMargins(mFutureMarginsOnceChromeShows); + } + } + + VERIFY_WINDOW_STYLE(style); + ::SetWindowLongPtrW(hwnd, GWL_STYLE, style); + ::SetWindowLongPtrW(hwnd, GWL_EXSTYLE, exStyle); + + return NS_OK; +} + +/************************************************************** + * + * SECTION: nsWindow::Invalidate + * + * Invalidate an area of the client for painting. + * + **************************************************************/ + +// Invalidate this component visible area +NS_IMETHODIMP nsWindow::Invalidate(bool aEraseBackground, + bool aUpdateNCArea, + bool aIncludeChildren) +{ + if (!mWnd) { + return NS_OK; + } + +#ifdef WIDGET_DEBUG_OUTPUT + debug_DumpInvalidate(stdout, + this, + nullptr, + "noname", + (int32_t) mWnd); +#endif // WIDGET_DEBUG_OUTPUT + + DWORD flags = RDW_INVALIDATE; + if (aEraseBackground) { + flags |= RDW_ERASE; + } + if (aUpdateNCArea) { + flags |= RDW_FRAME; + } + if (aIncludeChildren) { + flags |= RDW_ALLCHILDREN; + } + + VERIFY(::RedrawWindow(mWnd, nullptr, nullptr, flags)); + return NS_OK; +} + +// Invalidate this component visible area +NS_IMETHODIMP nsWindow::Invalidate(const LayoutDeviceIntRect& aRect) +{ + if (mWnd) { +#ifdef WIDGET_DEBUG_OUTPUT + debug_DumpInvalidate(stdout, + this, + &aRect, + "noname", + (int32_t) mWnd); +#endif // WIDGET_DEBUG_OUTPUT + + RECT rect; + + rect.left = aRect.x; + rect.top = aRect.y; + rect.right = aRect.x + aRect.width; + rect.bottom = aRect.y + aRect.height; + + VERIFY(::InvalidateRect(mWnd, &rect, FALSE)); + } + return NS_OK; +} + +static LRESULT CALLBACK +FullscreenTransitionWindowProc(HWND hWnd, UINT uMsg, + WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) { + case WM_FULLSCREEN_TRANSITION_BEFORE: + case WM_FULLSCREEN_TRANSITION_AFTER: { + DWORD duration = (DWORD)lParam; + DWORD flags = AW_BLEND; + if (uMsg == WM_FULLSCREEN_TRANSITION_AFTER) { + flags |= AW_HIDE; + } + ::AnimateWindow(hWnd, duration, flags); + // The message sender should have added ref for us. + NS_DispatchToMainThread( + already_AddRefed<nsIRunnable>((nsIRunnable*)wParam)); + break; + } + case WM_DESTROY: + ::PostQuitMessage(0); + break; + default: + return ::DefWindowProcW(hWnd, uMsg, wParam, lParam); + } + return 0; +} + +struct FullscreenTransitionInitData +{ + nsIntRect mBounds; + HANDLE mSemaphore; + HANDLE mThread; + HWND mWnd; + + FullscreenTransitionInitData() + : mSemaphore(nullptr) + , mThread(nullptr) + , mWnd(nullptr) { } + + ~FullscreenTransitionInitData() + { + if (mSemaphore) { + ::CloseHandle(mSemaphore); + } + if (mThread) { + ::CloseHandle(mThread); + } + } +}; + +static DWORD WINAPI +FullscreenTransitionThreadProc(LPVOID lpParam) +{ + // Initialize window class + static bool sInitialized = false; + if (!sInitialized) { + WNDCLASSW wc = {}; + wc.lpfnWndProc = ::FullscreenTransitionWindowProc; + wc.hInstance = nsToolkit::mDllInstance; + wc.hbrBackground = ::CreateSolidBrush(RGB(0, 0, 0)); + wc.lpszClassName = kClassNameTransition; + ::RegisterClassW(&wc); + sInitialized = true; + } + + auto data = static_cast<FullscreenTransitionInitData*>(lpParam); + HWND wnd = ::CreateWindowW( + kClassNameTransition, L"", 0, 0, 0, 0, 0, + nullptr, nullptr, nsToolkit::mDllInstance, nullptr); + if (!wnd) { + ::ReleaseSemaphore(data->mSemaphore, 1, nullptr); + return 0; + } + + // Since AnimateWindow blocks the thread of the transition window, + // we need to hide the cursor for that window, otherwise the system + // would show the busy pointer to the user. + ::ShowCursor(false); + ::SetWindowLongW(wnd, GWL_STYLE, 0); + ::SetWindowLongW(wnd, GWL_EXSTYLE, WS_EX_LAYERED | + WS_EX_TRANSPARENT | WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE); + ::SetWindowPos(wnd, HWND_TOPMOST, data->mBounds.x, data->mBounds.y, + data->mBounds.width, data->mBounds.height, 0); + data->mWnd = wnd; + ::ReleaseSemaphore(data->mSemaphore, 1, nullptr); + // The initialization data may no longer be valid + // after we release the semaphore. + data = nullptr; + + MSG msg; + while (::GetMessageW(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + ::ShowCursor(true); + ::DestroyWindow(wnd); + return 0; +} + +class FullscreenTransitionData final : public nsISupports +{ +public: + NS_DECL_ISUPPORTS + + explicit FullscreenTransitionData(HWND aWnd) + : mWnd(aWnd) + { + MOZ_ASSERT(NS_IsMainThread(), "FullscreenTransitionData " + "should be constructed in the main thread"); + } + + const HWND mWnd; + +private: + ~FullscreenTransitionData() + { + MOZ_ASSERT(NS_IsMainThread(), "FullscreenTransitionData " + "should be deconstructed in the main thread"); + ::PostMessageW(mWnd, WM_DESTROY, 0, 0); + } +}; + +NS_IMPL_ISUPPORTS0(FullscreenTransitionData) + +/* virtual */ bool +nsWindow::PrepareForFullscreenTransition(nsISupports** aData) +{ + // We don't support fullscreen transition when composition is not + // enabled, which could make the transition broken and annoying. + // See bug 1184201. + if (!nsUXThemeData::CheckForCompositor()) { + return false; + } + + FullscreenTransitionInitData initData; + nsCOMPtr<nsIScreen> screen = GetWidgetScreen(); + int32_t x, y, width, height; + screen->GetRectDisplayPix(&x, &y, &width, &height); + MOZ_ASSERT(BoundsUseDesktopPixels(), + "Should only be called on top-level window"); + double scale = GetDesktopToDeviceScale().scale; // XXX or GetDefaultScale() ? + initData.mBounds.x = NSToIntRound(x * scale); + initData.mBounds.y = NSToIntRound(y * scale); + initData.mBounds.width = NSToIntRound(width * scale); + initData.mBounds.height = NSToIntRound(height * scale); + + // Create a semaphore for synchronizing the window handle which will + // be created by the transition thread and used by the main thread for + // posting the transition messages. + initData.mSemaphore = ::CreateSemaphore(nullptr, 0, 1, nullptr); + if (initData.mSemaphore) { + initData.mThread = ::CreateThread( + nullptr, 0, FullscreenTransitionThreadProc, &initData, 0, nullptr); + if (initData.mThread) { + ::WaitForSingleObject(initData.mSemaphore, INFINITE); + } + } + if (!initData.mWnd) { + return false; + } + + mTransitionWnd = initData.mWnd; + auto data = new FullscreenTransitionData(initData.mWnd); + *aData = data; + NS_ADDREF(data); + return true; +} + +/* virtual */ void +nsWindow::PerformFullscreenTransition(FullscreenTransitionStage aStage, + uint16_t aDuration, nsISupports* aData, + nsIRunnable* aCallback) +{ + auto data = static_cast<FullscreenTransitionData*>(aData); + nsCOMPtr<nsIRunnable> callback = aCallback; + UINT msg = aStage == eBeforeFullscreenToggle ? + WM_FULLSCREEN_TRANSITION_BEFORE : WM_FULLSCREEN_TRANSITION_AFTER; + WPARAM wparam = (WPARAM)callback.forget().take(); + ::PostMessage(data->mWnd, msg, wparam, (LPARAM)aDuration); +} + +nsresult +nsWindow::MakeFullScreen(bool aFullScreen, nsIScreen* aTargetScreen) +{ + // taskbarInfo will be nullptr pre Windows 7 until Bug 680227 is resolved. + nsCOMPtr<nsIWinTaskbar> taskbarInfo = + do_GetService(NS_TASKBAR_CONTRACTID); + + mFullscreenMode = aFullScreen; + if (aFullScreen) { + if (mSizeMode == nsSizeMode_Fullscreen) + return NS_OK; + mOldSizeMode = mSizeMode; + SetSizeMode(nsSizeMode_Fullscreen); + + // Notify the taskbar that we will be entering full screen mode. + if (taskbarInfo) { + taskbarInfo->PrepareFullScreenHWND(mWnd, TRUE); + } + } else { + if (mSizeMode != nsSizeMode_Fullscreen) + return NS_OK; + SetSizeMode(mOldSizeMode); + } + + // If we are going fullscreen, the window size continues to change + // and the window will be reflow again then. + UpdateNonClientMargins(mSizeMode, /* Reflow */ !aFullScreen); + + // Will call hide chrome, reposition window. Note this will + // also cache dimensions for restoration, so it should only + // be called once per fullscreen request. + nsBaseWidget::InfallibleMakeFullScreen(aFullScreen, aTargetScreen); + + if (mIsVisible && !aFullScreen && mOldSizeMode == nsSizeMode_Normal) { + // Ensure the window exiting fullscreen get activated. Window + // activation might be bypassed in SetSizeMode. + DispatchFocusToTopLevelWindow(true); + } + + // Notify the taskbar that we have exited full screen mode. + if (!aFullScreen && taskbarInfo) { + taskbarInfo->PrepareFullScreenHWND(mWnd, FALSE); + } + + if (mWidgetListener) { + mWidgetListener->SizeModeChanged(mSizeMode); + mWidgetListener->FullscreenChanged(aFullScreen); + } + + // Send a eMouseEnterIntoWidget event since Windows has already sent + // a WM_MOUSELEAVE that caused us to send a eMouseExitFromWidget event. + if (aFullScreen && !sCurrentWindow) { + sCurrentWindow = this; + LPARAM pos = sCurrentWindow->lParamToClient(sMouseExitlParamScreen); + sCurrentWindow->DispatchMouseEvent(eMouseEnterIntoWidget, + sMouseExitwParam, pos, false, + WidgetMouseEvent::eLeftButton, + MOUSE_INPUT_SOURCE(), MOUSE_POINTERID()); + } + + return NS_OK; +} + +/************************************************************** + * + * SECTION: Native data storage + * + * nsIWidget::GetNativeData + * nsIWidget::FreeNativeData + * + * Set or clear native data based on a constant. + * + **************************************************************/ + +// Return some native data according to aDataType +void* nsWindow::GetNativeData(uint32_t aDataType) +{ + switch (aDataType) { + case NS_NATIVE_TMP_WINDOW: + return (void*)::CreateWindowExW(mIsRTL ? WS_EX_LAYOUTRTL : 0, + GetWindowClass(), + L"", + WS_CHILD, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + mWnd, + nullptr, + nsToolkit::mDllInstance, + nullptr); + case NS_NATIVE_PLUGIN_ID: + case NS_NATIVE_PLUGIN_PORT: + case NS_NATIVE_WIDGET: + case NS_NATIVE_WINDOW: + return (void*)mWnd; + case NS_NATIVE_SHAREABLE_WINDOW: + return (void*) WinUtils::GetTopLevelHWND(mWnd); + case NS_NATIVE_GRAPHIC: + MOZ_ASSERT_UNREACHABLE("Not supported on Windows:"); + return nullptr; + case NS_RAW_NATIVE_IME_CONTEXT: { + void* pseudoIMEContext = GetPseudoIMEContext(); + if (pseudoIMEContext) { + return pseudoIMEContext; + } + MOZ_FALLTHROUGH; + } + case NS_NATIVE_TSF_THREAD_MGR: + case NS_NATIVE_TSF_CATEGORY_MGR: + case NS_NATIVE_TSF_DISPLAY_ATTR_MGR: + return IMEHandler::GetNativeData(this, aDataType); + + default: + break; + } + + return nullptr; +} + +static void +SetChildStyleAndParent(HWND aChildWindow, HWND aParentWindow) +{ + // Make sure the window is styled to be a child window. + LONG_PTR style = GetWindowLongPtr(aChildWindow, GWL_STYLE); + style |= WS_CHILD; + style &= ~WS_POPUP; + SetWindowLongPtr(aChildWindow, GWL_STYLE, style); + + // Do the reparenting. Note that this call will probably cause a sync native + // message to the process that owns the child window. + ::SetParent(aChildWindow, aParentWindow); +} + +void +nsWindow::SetNativeData(uint32_t aDataType, uintptr_t aVal) +{ + switch (aDataType) { + case NS_NATIVE_CHILD_WINDOW: + SetChildStyleAndParent(reinterpret_cast<HWND>(aVal), mWnd); + break; + case NS_NATIVE_CHILD_OF_SHAREABLE_WINDOW: + SetChildStyleAndParent(reinterpret_cast<HWND>(aVal), + WinUtils::GetTopLevelHWND(mWnd)); + break; + default: + NS_ERROR("SetNativeData called with unsupported data type."); + } +} + +// Free some native data according to aDataType +void nsWindow::FreeNativeData(void * data, uint32_t aDataType) +{ + switch (aDataType) + { + case NS_NATIVE_GRAPHIC: + case NS_NATIVE_WIDGET: + case NS_NATIVE_WINDOW: + case NS_NATIVE_PLUGIN_PORT: + break; + default: + break; + } +} + +/************************************************************** + * + * SECTION: nsIWidget::SetTitle + * + * Set the main windows title text. + * + **************************************************************/ + +NS_IMETHODIMP nsWindow::SetTitle(const nsAString& aTitle) +{ + const nsString& strTitle = PromiseFlatString(aTitle); + AutoRestore<bool> sendingText(mSendingSetText); + mSendingSetText = true; + ::SendMessageW(mWnd, WM_SETTEXT, (WPARAM)0, (LPARAM)(LPCWSTR)strTitle.get()); + return NS_OK; +} + +/************************************************************** + * + * SECTION: nsIWidget::SetIcon + * + * Set the main windows icon. + * + **************************************************************/ + +NS_IMETHODIMP nsWindow::SetIcon(const nsAString& aIconSpec) +{ + // Assume the given string is a local identifier for an icon file. + + nsCOMPtr<nsIFile> iconFile; + ResolveIconName(aIconSpec, NS_LITERAL_STRING(".ico"), + getter_AddRefs(iconFile)); + if (!iconFile) + return NS_OK; // not an error if icon is not found + + nsAutoString iconPath; + iconFile->GetPath(iconPath); + + // XXX this should use MZLU (see bug 239279) + + ::SetLastError(0); + + HICON bigIcon = (HICON)::LoadImageW(nullptr, + (LPCWSTR)iconPath.get(), + IMAGE_ICON, + ::GetSystemMetrics(SM_CXICON), + ::GetSystemMetrics(SM_CYICON), + LR_LOADFROMFILE ); + HICON smallIcon = (HICON)::LoadImageW(nullptr, + (LPCWSTR)iconPath.get(), + IMAGE_ICON, + ::GetSystemMetrics(SM_CXSMICON), + ::GetSystemMetrics(SM_CYSMICON), + LR_LOADFROMFILE ); + + if (bigIcon) { + HICON icon = (HICON) ::SendMessageW(mWnd, WM_SETICON, (WPARAM)ICON_BIG, (LPARAM)bigIcon); + if (icon) + ::DestroyIcon(icon); + mIconBig = bigIcon; + } +#ifdef DEBUG_SetIcon + else { + NS_LossyConvertUTF16toASCII cPath(iconPath); + MOZ_LOG(gWindowsLog, LogLevel::Info, + ("\nIcon load error; icon=%s, rc=0x%08X\n\n", + cPath.get(), ::GetLastError())); + } +#endif + if (smallIcon) { + HICON icon = (HICON) ::SendMessageW(mWnd, WM_SETICON, (WPARAM)ICON_SMALL, (LPARAM)smallIcon); + if (icon) + ::DestroyIcon(icon); + mIconSmall = smallIcon; + } +#ifdef DEBUG_SetIcon + else { + NS_LossyConvertUTF16toASCII cPath(iconPath); + MOZ_LOG(gWindowsLog, LogLevel::Info, + ("\nSmall icon load error; icon=%s, rc=0x%08X\n\n", + cPath.get(), ::GetLastError())); + } +#endif + return NS_OK; +} + +/************************************************************** + * + * SECTION: nsIWidget::WidgetToScreenOffset + * + * Return this widget's origin in screen coordinates. + * + **************************************************************/ + +LayoutDeviceIntPoint nsWindow::WidgetToScreenOffset() +{ + POINT point; + point.x = 0; + point.y = 0; + ::ClientToScreen(mWnd, &point); + return LayoutDeviceIntPoint(point.x, point.y); +} + +LayoutDeviceIntSize +nsWindow::ClientToWindowSize(const LayoutDeviceIntSize& aClientSize) +{ + if (mWindowType == eWindowType_popup && !IsPopupWithTitleBar()) + return aClientSize; + + // just use (200, 200) as the position + RECT r; + r.left = 200; + r.top = 200; + r.right = 200 + aClientSize.width; + r.bottom = 200 + aClientSize.height; + ::AdjustWindowRectEx(&r, WindowStyle(), false, WindowExStyle()); + + return LayoutDeviceIntSize(r.right - r.left, r.bottom - r.top); +} + +/************************************************************** + * + * SECTION: nsIWidget::EnableDragDrop + * + * Enables/Disables drag and drop of files on this widget. + * + **************************************************************/ + +void +nsWindow::EnableDragDrop(bool aEnable) +{ + NS_ASSERTION(mWnd, "nsWindow::EnableDragDrop() called after Destroy()"); + + nsresult rv = NS_ERROR_FAILURE; + if (aEnable) { + if (!mNativeDragTarget) { + mNativeDragTarget = new nsNativeDragTarget(this); + mNativeDragTarget->AddRef(); + if (SUCCEEDED(::CoLockObjectExternal((LPUNKNOWN)mNativeDragTarget, + TRUE, FALSE))) { + ::RegisterDragDrop(mWnd, (LPDROPTARGET)mNativeDragTarget); + } + } + } else { + if (mWnd && mNativeDragTarget) { + ::RevokeDragDrop(mWnd); + ::CoLockObjectExternal((LPUNKNOWN)mNativeDragTarget, FALSE, TRUE); + mNativeDragTarget->DragCancel(); + NS_RELEASE(mNativeDragTarget); + } + } +} + +/************************************************************** + * + * SECTION: nsIWidget::CaptureMouse + * + * Enables/Disables system mouse capture. + * + **************************************************************/ + +void nsWindow::CaptureMouse(bool aCapture) +{ + TRACKMOUSEEVENT mTrack; + mTrack.cbSize = sizeof(TRACKMOUSEEVENT); + mTrack.dwHoverTime = 0; + mTrack.hwndTrack = mWnd; + if (aCapture) { + mTrack.dwFlags = TME_CANCEL | TME_LEAVE; + ::SetCapture(mWnd); + } else { + mTrack.dwFlags = TME_LEAVE; + ::ReleaseCapture(); + } + sIsInMouseCapture = aCapture; + TrackMouseEvent(&mTrack); +} + +/************************************************************** + * + * SECTION: nsIWidget::CaptureRollupEvents + * + * Dealing with event rollup on destroy for popups. Enables & + * Disables system capture of any and all events that would + * cause a dropdown to be rolled up. + * + **************************************************************/ + +void +nsWindow::CaptureRollupEvents(nsIRollupListener* aListener, bool aDoCapture) +{ + if (aDoCapture) { + gRollupListener = aListener; + if (!sMsgFilterHook && !sCallProcHook && !sCallMouseHook) { + RegisterSpecialDropdownHooks(); + } + sProcessHook = true; + } else { + gRollupListener = nullptr; + sProcessHook = false; + UnregisterSpecialDropdownHooks(); + } +} + +/************************************************************** + * + * SECTION: nsIWidget::GetAttention + * + * Bring this window to the user's attention. + * + **************************************************************/ + +// Draw user's attention to this window until it comes to foreground. +NS_IMETHODIMP +nsWindow::GetAttention(int32_t aCycleCount) +{ + // Got window? + if (!mWnd) + return NS_ERROR_NOT_INITIALIZED; + + HWND flashWnd = WinUtils::GetTopLevelHWND(mWnd, false, false); + HWND fgWnd = ::GetForegroundWindow(); + // Don't flash if the flash count is 0 or if the foreground window is our + // window handle or that of our owned-most window. + if (aCycleCount == 0 || + flashWnd == fgWnd || + flashWnd == WinUtils::GetTopLevelHWND(fgWnd, false, false)) { + return NS_OK; + } + + DWORD defaultCycleCount = 0; + ::SystemParametersInfo(SPI_GETFOREGROUNDFLASHCOUNT, 0, &defaultCycleCount, 0); + + FLASHWINFO flashInfo = { sizeof(FLASHWINFO), flashWnd, + FLASHW_ALL, aCycleCount > 0 ? aCycleCount : defaultCycleCount, 0 }; + ::FlashWindowEx(&flashInfo); + + return NS_OK; +} + +void nsWindow::StopFlashing() +{ + HWND flashWnd = mWnd; + while (HWND ownerWnd = ::GetWindow(flashWnd, GW_OWNER)) { + flashWnd = ownerWnd; + } + + FLASHWINFO flashInfo = { sizeof(FLASHWINFO), flashWnd, + FLASHW_STOP, 0, 0 }; + ::FlashWindowEx(&flashInfo); +} + +/************************************************************** + * + * SECTION: nsIWidget::HasPendingInputEvent + * + * Ask whether there user input events pending. All input events are + * included, including those not targeted at this nsIwidget instance. + * + **************************************************************/ + +bool +nsWindow::HasPendingInputEvent() +{ + // If there is pending input or the user is currently + // moving the window then return true. + // Note: When the user is moving the window WIN32 spins + // a separate event loop and input events are not + // reported to the application. + if (HIWORD(GetQueueStatus(QS_INPUT))) + return true; + GUITHREADINFO guiInfo; + guiInfo.cbSize = sizeof(GUITHREADINFO); + if (!GetGUIThreadInfo(GetCurrentThreadId(), &guiInfo)) + return false; + return GUI_INMOVESIZE == (guiInfo.flags & GUI_INMOVESIZE); +} + +/************************************************************** + * + * SECTION: nsIWidget::GetLayerManager + * + * Get the layer manager associated with this widget. + * + **************************************************************/ + +LayerManager* +nsWindow::GetLayerManager(PLayerTransactionChild* aShadowManager, + LayersBackend aBackendHint, + LayerManagerPersistence aPersistence) +{ + RECT windowRect; + ::GetClientRect(mWnd, &windowRect); + + // Try OMTC first. + if (!mLayerManager && ShouldUseOffMainThreadCompositing()) { + gfxWindowsPlatform::GetPlatform()->UpdateRenderMode(); + + // e10s uses the parameter to pass in the shadow manager from the TabChild + // so we don't expect to see it there since this doesn't support e10s. + NS_ASSERTION(aShadowManager == nullptr, "Async Compositor not supported with e10s"); + CreateCompositor(); + } + + if (!mLayerManager) { + MOZ_ASSERT(!mCompositorSession && !mCompositorBridgeChild); + MOZ_ASSERT(!mCompositorWidgetDelegate); + + // Ensure we have a widget proxy even if we're not using the compositor, + // since all our transparent window handling lives there. + CompositorWidgetInitData initData( + reinterpret_cast<uintptr_t>(mWnd), + reinterpret_cast<uintptr_t>(static_cast<nsIWidget*>(this)), + mTransparencyMode); + mBasicLayersSurface = new InProcessWinCompositorWidget(initData, this); + mCompositorWidgetDelegate = mBasicLayersSurface; + mLayerManager = CreateBasicLayerManager(); + } + + NS_ASSERTION(mLayerManager, "Couldn't provide a valid layer manager."); + + return mLayerManager; +} + +/************************************************************** + * + * SECTION: nsIWidget::OnDefaultButtonLoaded + * + * Called after the dialog is loaded and it has a default button. + * + **************************************************************/ + +NS_IMETHODIMP +nsWindow::OnDefaultButtonLoaded(const LayoutDeviceIntRect& aButtonRect) +{ + if (aButtonRect.IsEmpty()) + return NS_OK; + + // Don't snap when we are not active. + HWND activeWnd = ::GetActiveWindow(); + if (activeWnd != ::GetForegroundWindow() || + WinUtils::GetTopLevelHWND(mWnd, true) != + WinUtils::GetTopLevelHWND(activeWnd, true)) { + return NS_OK; + } + + bool isAlwaysSnapCursor = + Preferences::GetBool("ui.cursor_snapping.always_enabled", false); + + if (!isAlwaysSnapCursor) { + BOOL snapDefaultButton; + if (!::SystemParametersInfo(SPI_GETSNAPTODEFBUTTON, 0, + &snapDefaultButton, 0) || !snapDefaultButton) + return NS_OK; + } + + LayoutDeviceIntRect widgetRect = GetScreenBounds(); + LayoutDeviceIntRect buttonRect(aButtonRect + widgetRect.TopLeft()); + + LayoutDeviceIntPoint centerOfButton(buttonRect.x + buttonRect.width / 2, + buttonRect.y + buttonRect.height / 2); + // The center of the button can be outside of the widget. + // E.g., it could be hidden by scrolling. + if (!widgetRect.Contains(centerOfButton)) { + return NS_OK; + } + + if (!::SetCursorPos(centerOfButton.x, centerOfButton.y)) { + NS_ERROR("SetCursorPos failed"); + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +void +nsWindow::UpdateThemeGeometries(const nsTArray<ThemeGeometry>& aThemeGeometries) +{ + RefPtr<LayerManager> layerManager = GetLayerManager(); + if (!layerManager) { + return; + } + + nsIntRegion clearRegion; + if (!HasGlass() || !nsUXThemeData::CheckForCompositor()) { + // Make sure and clear old regions we've set previously. Note HasGlass can be false + // for glass desktops if the window we are rendering to doesn't make use of glass + // (e.g. fullscreen browsing). + layerManager->SetRegionToClear(clearRegion); + return; + } + + // On Win10, force show the top border: + if (IsWin10OrLater() && mCustomNonClient && mSizeMode == nsSizeMode_Normal) { + RECT rect; + ::GetWindowRect(mWnd, &rect); + // We want 1 pixel of border for every whole 100% of scaling + double borderSize = std::min(1, RoundDown(GetDesktopToDeviceScale().scale)); + clearRegion.Or(clearRegion, gfx::IntRect::Truncate(0, 0, rect.right - rect.left, borderSize)); + } + + if (!IsWin10OrLater()) { + for (size_t i = 0; i < aThemeGeometries.Length(); i++) { + if (aThemeGeometries[i].mType == nsNativeThemeWin::eThemeGeometryTypeWindowButtons) { + LayoutDeviceIntRect bounds = aThemeGeometries[i].mRect; + clearRegion.Or(clearRegion, gfx::IntRect::Truncate(bounds.X(), bounds.Y(), bounds.Width(), bounds.Height() - 2.0)); + clearRegion.Or(clearRegion, gfx::IntRect::Truncate(bounds.X() + 1.0, bounds.YMost() - 2.0, bounds.Width() - 1.0, 1.0)); + clearRegion.Or(clearRegion, gfx::IntRect::Truncate(bounds.X() + 2.0, bounds.YMost() - 1.0, bounds.Width() - 3.0, 1.0)); + } + } + } + + layerManager->SetRegionToClear(clearRegion); +} + +uint32_t +nsWindow::GetMaxTouchPoints() const +{ + return WinUtils::GetMaxTouchPoints(); +} + +/************************************************************** + ************************************************************** + ** + ** BLOCK: Moz Events + ** + ** Moz GUI event management. + ** + ************************************************************** + **************************************************************/ + +/************************************************************** + * + * SECTION: Mozilla event initialization + * + * Helpers for initializing moz events. + * + **************************************************************/ + +// Event initialization +void nsWindow::InitEvent(WidgetGUIEvent& event, LayoutDeviceIntPoint* aPoint) +{ + if (nullptr == aPoint) { // use the point from the event + // get the message position in client coordinates + if (mWnd != nullptr) { + DWORD pos = ::GetMessagePos(); + POINT cpos; + + cpos.x = GET_X_LPARAM(pos); + cpos.y = GET_Y_LPARAM(pos); + + ::ScreenToClient(mWnd, &cpos); + event.mRefPoint = LayoutDeviceIntPoint(cpos.x, cpos.y); + } else { + event.mRefPoint = LayoutDeviceIntPoint(0, 0); + } + } else { + // use the point override if provided + event.mRefPoint = *aPoint; + } + + event.AssignEventTime(CurrentMessageWidgetEventTime()); +} + +WidgetEventTime +nsWindow::CurrentMessageWidgetEventTime() const +{ + LONG messageTime = ::GetMessageTime(); + return WidgetEventTime(messageTime, GetMessageTimeStamp(messageTime)); +} + +/************************************************************** + * + * SECTION: Moz event dispatch helpers + * + * Helpers for dispatching different types of moz events. + * + **************************************************************/ + +// Main event dispatch. Invokes callback and ProcessEvent method on +// Event Listener object. Part of nsIWidget. +NS_IMETHODIMP nsWindow::DispatchEvent(WidgetGUIEvent* event, + nsEventStatus& aStatus) +{ +#ifdef WIDGET_DEBUG_OUTPUT + debug_DumpEvent(stdout, + event->mWidget, + event, + "something", + (int32_t) mWnd); +#endif // WIDGET_DEBUG_OUTPUT + + aStatus = nsEventStatus_eIgnore; + + // Top level windows can have a view attached which requires events be sent + // to the underlying base window and the view. Added when we combined the + // base chrome window with the main content child for nc client area (title + // bar) rendering. + if (mAttachedWidgetListener) { + aStatus = mAttachedWidgetListener->HandleEvent(event, mUseAttachedEvents); + } + else if (mWidgetListener) { + aStatus = mWidgetListener->HandleEvent(event, mUseAttachedEvents); + } + + // the window can be destroyed during processing of seemingly innocuous events like, say, + // mousedowns due to the magic of scripting. mousedowns will return nsEventStatus_eIgnore, + // which causes problems with the deleted window. therefore: + if (mOnDestroyCalled) + aStatus = nsEventStatus_eConsumeNoDefault; + return NS_OK; +} + +bool nsWindow::DispatchStandardEvent(EventMessage aMsg) +{ + WidgetGUIEvent event(true, aMsg, this); + InitEvent(event); + + bool result = DispatchWindowEvent(&event); + return result; +} + +bool nsWindow::DispatchKeyboardEvent(WidgetKeyboardEvent* event) +{ + nsEventStatus status = DispatchInputEvent(event); + return ConvertStatus(status); +} + +bool nsWindow::DispatchContentCommandEvent(WidgetContentCommandEvent* aEvent) +{ + nsEventStatus status; + DispatchEvent(aEvent, status); + return ConvertStatus(status); +} + +bool nsWindow::DispatchWheelEvent(WidgetWheelEvent* aEvent) +{ + nsEventStatus status = DispatchInputEvent(aEvent->AsInputEvent()); + return ConvertStatus(status); +} + +bool nsWindow::DispatchWindowEvent(WidgetGUIEvent* event) +{ + nsEventStatus status; + DispatchEvent(event, status); + return ConvertStatus(status); +} + +bool nsWindow::DispatchWindowEvent(WidgetGUIEvent* event, + nsEventStatus& aStatus) +{ + DispatchEvent(event, aStatus); + return ConvertStatus(aStatus); +} + +// Recursively dispatch synchronous paints for nsIWidget +// descendants with invalidated rectangles. +BOOL CALLBACK nsWindow::DispatchStarvedPaints(HWND aWnd, LPARAM aMsg) +{ + LONG_PTR proc = ::GetWindowLongPtrW(aWnd, GWLP_WNDPROC); + if (proc == (LONG_PTR)&nsWindow::WindowProc) { + // its one of our windows so check to see if it has a + // invalidated rect. If it does. Dispatch a synchronous + // paint. + if (GetUpdateRect(aWnd, nullptr, FALSE)) + VERIFY(::UpdateWindow(aWnd)); + } + return TRUE; +} + +// Check for pending paints and dispatch any pending paint +// messages for any nsIWidget which is a descendant of the +// top-level window that *this* window is embedded within. +// +// Note: We do not dispatch pending paint messages for non +// nsIWidget managed windows. +void nsWindow::DispatchPendingEvents() +{ + if (mPainting) { + NS_WARNING("We were asked to dispatch pending events during painting, " + "denying since that's unsafe."); + return; + } + + // We need to ensure that reflow events do not get starved. + // At the same time, we don't want to recurse through here + // as that would prevent us from dispatching starved paints. + static int recursionBlocker = 0; + if (recursionBlocker++ == 0) { + NS_ProcessPendingEvents(nullptr, PR_MillisecondsToInterval(100)); + --recursionBlocker; + } + + // Quickly check to see if there are any paint events pending, + // but only dispatch them if it has been long enough since the + // last paint completed. + if (::GetQueueStatus(QS_PAINT) && + ((TimeStamp::Now() - mLastPaintEndTime).ToMilliseconds() >= 50)) { + // Find the top level window. + HWND topWnd = WinUtils::GetTopLevelHWND(mWnd); + + // Dispatch pending paints for topWnd and all its descendant windows. + // Note: EnumChildWindows enumerates all descendant windows not just + // the children (but not the window itself). + nsWindow::DispatchStarvedPaints(topWnd, 0); + ::EnumChildWindows(topWnd, nsWindow::DispatchStarvedPaints, 0); + } +} + +bool nsWindow::DispatchPluginEvent(UINT aMessage, + WPARAM aWParam, + LPARAM aLParam, + bool aDispatchPendingEvents) +{ + bool ret = nsWindowBase::DispatchPluginEvent( + WinUtils::InitMSG(aMessage, aWParam, aLParam, mWnd)); + if (aDispatchPendingEvents && !Destroyed()) { + DispatchPendingEvents(); + } + return ret; +} + +// Deal with all sort of mouse event +bool +nsWindow::DispatchMouseEvent(EventMessage aEventMessage, WPARAM wParam, + LPARAM lParam, bool aIsContextMenuKey, + int16_t aButton, uint16_t aInputSource, + uint16_t aPointerId) +{ + bool result = false; + + UserActivity(); + + if (!mWidgetListener) { + return result; + } + + LayoutDeviceIntPoint eventPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); + LayoutDeviceIntPoint mpScreen = eventPoint + WidgetToScreenOffset(); + + // Suppress mouse moves caused by widget creation. Make sure to do this early + // so that we update sLastMouseMovePoint even for touch-induced mousemove events. + if (aEventMessage == eMouseMove) { + if ((sLastMouseMovePoint.x == mpScreen.x) && (sLastMouseMovePoint.y == mpScreen.y)) { + return result; + } + sLastMouseMovePoint.x = mpScreen.x; + sLastMouseMovePoint.y = mpScreen.y; + } + + if (WinUtils::GetIsMouseFromTouch(aEventMessage)) { + if (aEventMessage == eMouseDown) { + Telemetry::Accumulate(Telemetry::FX_TOUCH_USED, 1); + } + + if (mTouchWindow) { + // If mTouchWindow is true, then we must have APZ enabled and be + // feeding it raw touch events. In that case we don't need to + // send touch-generated mouse events to content. The only exception is + // the touch-generated mouse double-click, which is used to start off the + // touch-based drag-and-drop gesture. + MOZ_ASSERT(mAPZC); + if (aEventMessage == eMouseDoubleClick) { + aEventMessage = eMouseTouchDrag; + } else { + return result; + } + } + } + + // Since it is unclear whether a user will use the digitizer, + // Postpone initialization until first PEN message will be found. + if (nsIDOMMouseEvent::MOZ_SOURCE_PEN == aInputSource + // Messages should be only at topLevel window. + && nsWindowType::eWindowType_toplevel == mWindowType + // Currently this scheme is used only when pointer events is enabled. + && gfxPrefs::PointerEventsEnabled()) { + InkCollector::sInkCollector->SetTarget(mWnd); + InkCollector::sInkCollector->SetPointerId(aPointerId); + } + + switch (aEventMessage) { + case eMouseDown: + CaptureMouse(true); + break; + + // eMouseMove and eMouseExitFromWidget are here because we need to make + // sure capture flag isn't left on after a drag where we wouldn't see a + // button up message (see bug 324131). + case eMouseUp: + case eMouseMove: + case eMouseExitFromWidget: + if (!(wParam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON)) && sIsInMouseCapture) + CaptureMouse(false); + break; + + default: + break; + + } // switch + + WidgetMouseEvent event(true, aEventMessage, this, WidgetMouseEvent::eReal, + aIsContextMenuKey ? WidgetMouseEvent::eContextMenuKey : + WidgetMouseEvent::eNormal); + if (aEventMessage == eContextMenu && aIsContextMenuKey) { + LayoutDeviceIntPoint zero(0, 0); + InitEvent(event, &zero); + } else { + InitEvent(event, &eventPoint); + } + + ModifierKeyState modifierKeyState; + modifierKeyState.InitInputEvent(event); + event.button = aButton; + event.inputSource = aInputSource; + event.pointerId = aPointerId; + // If we get here the mouse events must be from non-touch sources, so + // convert it to pointer events as well + event.convertToPointer = true; + + bool insideMovementThreshold = (DeprecatedAbs(sLastMousePoint.x - eventPoint.x) < (short)::GetSystemMetrics(SM_CXDOUBLECLK)) && + (DeprecatedAbs(sLastMousePoint.y - eventPoint.y) < (short)::GetSystemMetrics(SM_CYDOUBLECLK)); + + BYTE eventButton; + switch (aButton) { + case WidgetMouseEvent::eLeftButton: + eventButton = VK_LBUTTON; + break; + case WidgetMouseEvent::eMiddleButton: + eventButton = VK_MBUTTON; + break; + case WidgetMouseEvent::eRightButton: + eventButton = VK_RBUTTON; + break; + default: + eventButton = 0; + break; + } + + // Doubleclicks are used to set the click count, then changed to mousedowns + // We're going to time double-clicks from mouse *up* to next mouse *down* + LONG curMsgTime = ::GetMessageTime(); + + switch (aEventMessage) { + case eMouseDoubleClick: + event.mMessage = eMouseDown; + event.button = aButton; + sLastClickCount = 2; + sLastMouseDownTime = curMsgTime; + break; + case eMouseUp: + // remember when this happened for the next mouse down + sLastMousePoint.x = eventPoint.x; + sLastMousePoint.y = eventPoint.y; + sLastMouseButton = eventButton; + break; + case eMouseDown: + // now look to see if we want to convert this to a double- or triple-click + if (((curMsgTime - sLastMouseDownTime) < (LONG)::GetDoubleClickTime()) && + insideMovementThreshold && + eventButton == sLastMouseButton) { + sLastClickCount ++; + } else { + // reset the click count, to count *this* click + sLastClickCount = 1; + } + // Set last Click time on MouseDown only + sLastMouseDownTime = curMsgTime; + break; + case eMouseMove: + if (!insideMovementThreshold) { + sLastClickCount = 0; + } + break; + case eMouseExitFromWidget: + event.mExitFrom = + IsTopLevelMouseExit(mWnd) ? WidgetMouseEvent::eTopLevel : + WidgetMouseEvent::eChild; + break; + default: + break; + } + event.mClickCount = sLastClickCount; + +#ifdef NS_DEBUG_XX + MOZ_LOG(gWindowsLog, LogLevel::Info, + ("Msg Time: %d Click Count: %d\n", curMsgTime, event.mClickCount)); +#endif + + NPEvent pluginEvent; + + switch (aEventMessage) { + case eMouseDown: + switch (aButton) { + case WidgetMouseEvent::eLeftButton: + pluginEvent.event = WM_LBUTTONDOWN; + break; + case WidgetMouseEvent::eMiddleButton: + pluginEvent.event = WM_MBUTTONDOWN; + break; + case WidgetMouseEvent::eRightButton: + pluginEvent.event = WM_RBUTTONDOWN; + break; + default: + break; + } + break; + case eMouseUp: + switch (aButton) { + case WidgetMouseEvent::eLeftButton: + pluginEvent.event = WM_LBUTTONUP; + break; + case WidgetMouseEvent::eMiddleButton: + pluginEvent.event = WM_MBUTTONUP; + break; + case WidgetMouseEvent::eRightButton: + pluginEvent.event = WM_RBUTTONUP; + break; + default: + break; + } + break; + case eMouseDoubleClick: + switch (aButton) { + case WidgetMouseEvent::eLeftButton: + pluginEvent.event = WM_LBUTTONDBLCLK; + break; + case WidgetMouseEvent::eMiddleButton: + pluginEvent.event = WM_MBUTTONDBLCLK; + break; + case WidgetMouseEvent::eRightButton: + pluginEvent.event = WM_RBUTTONDBLCLK; + break; + default: + break; + } + break; + case eMouseMove: + pluginEvent.event = WM_MOUSEMOVE; + break; + case eMouseExitFromWidget: + pluginEvent.event = WM_MOUSELEAVE; + break; + default: + pluginEvent.event = WM_NULL; + break; + } + + pluginEvent.wParam = wParam; // plugins NEED raw OS event flags! + pluginEvent.lParam = lParam; + + event.mPluginEvent.Copy(pluginEvent); + + // call the event callback + if (mWidgetListener) { + if (aEventMessage == eMouseMove) { + LayoutDeviceIntRect rect = GetBounds(); + rect.x = 0; + rect.y = 0; + + if (rect.Contains(event.mRefPoint)) { + if (sCurrentWindow == nullptr || sCurrentWindow != this) { + if ((nullptr != sCurrentWindow) && (!sCurrentWindow->mInDtor)) { + LPARAM pos = sCurrentWindow->lParamToClient(lParamToScreen(lParam)); + sCurrentWindow->DispatchMouseEvent(eMouseExitFromWidget, + wParam, pos, false, + WidgetMouseEvent::eLeftButton, + aInputSource, aPointerId); + } + sCurrentWindow = this; + if (!mInDtor) { + LPARAM pos = sCurrentWindow->lParamToClient(lParamToScreen(lParam)); + sCurrentWindow->DispatchMouseEvent(eMouseEnterIntoWidget, + wParam, pos, false, + WidgetMouseEvent::eLeftButton, + aInputSource, aPointerId); + } + } + } + } else if (aEventMessage == eMouseExitFromWidget) { + sMouseExitwParam = wParam; + sMouseExitlParamScreen = lParamToScreen(lParam); + if (sCurrentWindow == this) { + sCurrentWindow = nullptr; + } + } + + result = ConvertStatus(DispatchInputEvent(&event)); + + // Release the widget with NS_IF_RELEASE() just in case + // the context menu key code in EventListenerManager::HandleEvent() + // released it already. + return result; + } + + return result; +} + +void nsWindow::DispatchFocusToTopLevelWindow(bool aIsActivate) +{ + if (aIsActivate) + sJustGotActivate = false; + sJustGotDeactivate = false; + + // retrive the toplevel window or dialog + HWND curWnd = mWnd; + HWND toplevelWnd = nullptr; + while (curWnd) { + toplevelWnd = curWnd; + + nsWindow *win = WinUtils::GetNSWindowPtr(curWnd); + if (win) { + nsWindowType wintype = win->WindowType(); + if (wintype == eWindowType_toplevel || wintype == eWindowType_dialog) + break; + } + + curWnd = ::GetParent(curWnd); // Parent or owner (if has no parent) + } + + if (toplevelWnd) { + nsWindow *win = WinUtils::GetNSWindowPtr(toplevelWnd); + if (win && win->mWidgetListener) { + if (aIsActivate) { + win->mWidgetListener->WindowActivated(); + } else { + if (!win->BlurEventsSuppressed()) { + win->mWidgetListener->WindowDeactivated(); + } + } + } + } +} + +bool nsWindow::IsTopLevelMouseExit(HWND aWnd) +{ + DWORD pos = ::GetMessagePos(); + POINT mp; + mp.x = GET_X_LPARAM(pos); + mp.y = GET_Y_LPARAM(pos); + HWND mouseWnd = ::WindowFromPoint(mp); + + // WinUtils::GetTopLevelHWND() will return a HWND for the window frame + // (which includes the non-client area). If the mouse has moved into + // the non-client area, we should treat it as a top-level exit. + HWND mouseTopLevel = WinUtils::GetTopLevelHWND(mouseWnd); + if (mouseWnd == mouseTopLevel) + return true; + + return WinUtils::GetTopLevelHWND(aWnd) != mouseTopLevel; +} + +bool nsWindow::BlurEventsSuppressed() +{ + // are they suppressed in this window? + if (mBlurSuppressLevel > 0) + return true; + + // are they suppressed by any container widget? + HWND parentWnd = ::GetParent(mWnd); + if (parentWnd) { + nsWindow *parent = WinUtils::GetNSWindowPtr(parentWnd); + if (parent) + return parent->BlurEventsSuppressed(); + } + return false; +} + +// In some circumstances (opening dependent windows) it makes more sense +// (and fixes a crash bug) to not blur the parent window. Called from +// nsFilePicker. +void nsWindow::SuppressBlurEvents(bool aSuppress) +{ + if (aSuppress) + ++mBlurSuppressLevel; // for this widget + else { + NS_ASSERTION(mBlurSuppressLevel > 0, "unbalanced blur event suppression"); + if (mBlurSuppressLevel > 0) + --mBlurSuppressLevel; + } +} + +bool nsWindow::ConvertStatus(nsEventStatus aStatus) +{ + return aStatus == nsEventStatus_eConsumeNoDefault; +} + +/************************************************************** + * + * SECTION: IPC + * + * IPC related helpers. + * + **************************************************************/ + +// static +bool +nsWindow::IsAsyncResponseEvent(UINT aMsg, LRESULT& aResult) +{ + switch(aMsg) { + case WM_SETFOCUS: + case WM_KILLFOCUS: + case WM_ENABLE: + case WM_WINDOWPOSCHANGING: + case WM_WINDOWPOSCHANGED: + case WM_PARENTNOTIFY: + case WM_ACTIVATEAPP: + case WM_NCACTIVATE: + case WM_ACTIVATE: + case WM_CHILDACTIVATE: + case WM_IME_SETCONTEXT: + case WM_IME_NOTIFY: + case WM_SHOWWINDOW: + case WM_CANCELMODE: + case WM_MOUSEACTIVATE: + case WM_CONTEXTMENU: + aResult = 0; + return true; + + case WM_SETTINGCHANGE: + case WM_SETCURSOR: + return false; + } + +#ifdef DEBUG + char szBuf[200]; + sprintf(szBuf, + "An unhandled ISMEX_SEND message was received during spin loop! (%X)", aMsg); + NS_WARNING(szBuf); +#endif + + return false; +} + +void +nsWindow::IPCWindowProcHandler(UINT& msg, WPARAM& wParam, LPARAM& lParam) +{ + MOZ_ASSERT_IF(msg != WM_GETOBJECT, + !mozilla::ipc::MessageChannel::IsPumpingMessages() || + mozilla::ipc::SuppressedNeuteringRegion::IsNeuteringSuppressed()); + + // Modal UI being displayed in windowless plugins. + if (mozilla::ipc::MessageChannel::IsSpinLoopActive() && + (InSendMessageEx(nullptr) & (ISMEX_REPLIED|ISMEX_SEND)) == ISMEX_SEND) { + LRESULT res; + if (IsAsyncResponseEvent(msg, res)) { + ReplyMessage(res); + } + return; + } + + // Handle certain sync plugin events sent to the parent which + // trigger ipc calls that result in deadlocks. + + DWORD dwResult = 0; + bool handled = false; + + switch(msg) { + // Windowless flash sending WM_ACTIVATE events to the main window + // via calls to ShowWindow. + case WM_ACTIVATE: + if (lParam != 0 && LOWORD(wParam) == WA_ACTIVE && + IsWindow((HWND)lParam)) { + // Check for Adobe Reader X sync activate message from their + // helper window and ignore. Fixes an annoying focus problem. + if ((InSendMessageEx(nullptr) & (ISMEX_REPLIED|ISMEX_SEND)) == ISMEX_SEND) { + wchar_t szClass[10]; + HWND focusWnd = (HWND)lParam; + if (IsWindowVisible(focusWnd) && + GetClassNameW(focusWnd, szClass, + sizeof(szClass)/sizeof(char16_t)) && + !wcscmp(szClass, L"Edit") && + !WinUtils::IsOurProcessWindow(focusWnd)) { + break; + } + } + handled = true; + } + break; + // Plugins taking or losing focus triggering focus app messages. + case WM_SETFOCUS: + case WM_KILLFOCUS: + // Windowed plugins that pass sys key events to defwndproc generate + // WM_SYSCOMMAND events to the main window. + case WM_SYSCOMMAND: + // Windowed plugins that fire context menu selection events to parent + // windows. + case WM_CONTEXTMENU: + // IME events fired as a result of synchronous focus changes + case WM_IME_SETCONTEXT: + handled = true; + break; + } + + if (handled && + (InSendMessageEx(nullptr) & (ISMEX_REPLIED|ISMEX_SEND)) == ISMEX_SEND) { + ReplyMessage(dwResult); + } +} + +/************************************************************** + ************************************************************** + ** + ** BLOCK: Native events + ** + ** Main Windows message handlers and OnXXX handlers for + ** Windows event handling. + ** + ************************************************************** + **************************************************************/ + +/************************************************************** + * + * SECTION: Wind proc. + * + * The main Windows event procedures and associated + * message processing methods. + * + **************************************************************/ + +static bool +DisplaySystemMenu(HWND hWnd, nsSizeMode sizeMode, bool isRtl, int32_t x, int32_t y) +{ + HMENU hMenu = GetSystemMenu(hWnd, FALSE); + if (hMenu) { + MENUITEMINFO mii; + mii.cbSize = sizeof(MENUITEMINFO); + mii.fMask = MIIM_STATE; + mii.fType = 0; + + // update the options + mii.fState = MF_ENABLED; + SetMenuItemInfo(hMenu, SC_RESTORE, FALSE, &mii); + SetMenuItemInfo(hMenu, SC_SIZE, FALSE, &mii); + SetMenuItemInfo(hMenu, SC_MOVE, FALSE, &mii); + SetMenuItemInfo(hMenu, SC_MAXIMIZE, FALSE, &mii); + SetMenuItemInfo(hMenu, SC_MINIMIZE, FALSE, &mii); + + mii.fState = MF_GRAYED; + switch(sizeMode) { + case nsSizeMode_Fullscreen: + // intentional fall through + case nsSizeMode_Maximized: + SetMenuItemInfo(hMenu, SC_SIZE, FALSE, &mii); + SetMenuItemInfo(hMenu, SC_MOVE, FALSE, &mii); + SetMenuItemInfo(hMenu, SC_MAXIMIZE, FALSE, &mii); + break; + case nsSizeMode_Minimized: + SetMenuItemInfo(hMenu, SC_MINIMIZE, FALSE, &mii); + break; + case nsSizeMode_Normal: + SetMenuItemInfo(hMenu, SC_RESTORE, FALSE, &mii); + break; + } + LPARAM cmd = + TrackPopupMenu(hMenu, + (TPM_LEFTBUTTON|TPM_RIGHTBUTTON| + TPM_RETURNCMD|TPM_TOPALIGN| + (isRtl ? TPM_RIGHTALIGN : TPM_LEFTALIGN)), + x, y, 0, hWnd, nullptr); + if (cmd) { + PostMessage(hWnd, WM_SYSCOMMAND, cmd, 0); + return true; + } + } + return false; +} + +inline static mozilla::HangMonitor::ActivityType ActivityTypeForMessage(UINT msg) +{ + if ((msg >= WM_KEYFIRST && msg <= WM_IME_KEYLAST) || + (msg >= WM_MOUSEFIRST && msg <= WM_MOUSELAST) || + (msg >= MOZ_WM_MOUSEWHEEL_FIRST && msg <= MOZ_WM_MOUSEWHEEL_LAST) || + (msg >= NS_WM_IMEFIRST && msg <= NS_WM_IMELAST)) { + return mozilla::HangMonitor::kUIActivity; + } + + // This may not actually be right, but we don't want to reset the timer if + // we're not actually processing a UI message. + return mozilla::HangMonitor::kActivityUIAVail; +} + +// The WndProc procedure for all nsWindows in this toolkit. This merely catches +// exceptions and passes the real work to WindowProcInternal. See bug 587406 +// and http://msdn.microsoft.com/en-us/library/ms633573%28VS.85%29.aspx +LRESULT CALLBACK nsWindow::WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + mozilla::ipc::CancelCPOWs(); + + HangMonitor::NotifyActivity(ActivityTypeForMessage(msg)); + + return mozilla::CallWindowProcCrashProtected(WindowProcInternal, hWnd, msg, wParam, lParam); +} + +LRESULT CALLBACK nsWindow::WindowProcInternal(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + if (::GetWindowLongPtrW(hWnd, GWLP_ID) == eFakeTrackPointScrollableID) { + // This message was sent to the FAKETRACKPOINTSCROLLABLE. + if (msg == WM_HSCROLL) { + // Route WM_HSCROLL messages to the main window. + hWnd = ::GetParent(::GetParent(hWnd)); + } else { + // Handle all other messages with its original window procedure. + WNDPROC prevWindowProc = (WNDPROC)::GetWindowLongPtr(hWnd, GWLP_USERDATA); + return ::CallWindowProcW(prevWindowProc, hWnd, msg, wParam, lParam); + } + } + + if (msg == MOZ_WM_TRACE) { + // This is a tracer event for measuring event loop latency. + // See WidgetTraceEvent.cpp for more details. + mozilla::SignalTracerThread(); + return 0; + } + + // Get the window which caused the event and ask it to process the message + nsWindow *targetWindow = WinUtils::GetNSWindowPtr(hWnd); + NS_ASSERTION(targetWindow, "nsWindow* is null!"); + if (!targetWindow) + return ::DefWindowProcW(hWnd, msg, wParam, lParam); + + // Hold the window for the life of this method, in case it gets + // destroyed during processing, unless we're in the dtor already. + nsCOMPtr<nsIWidget> kungFuDeathGrip; + if (!targetWindow->mInDtor) + kungFuDeathGrip = targetWindow; + + targetWindow->IPCWindowProcHandler(msg, wParam, lParam); + + // Create this here so that we store the last rolled up popup until after + // the event has been processed. + nsAutoRollup autoRollup; + + LRESULT popupHandlingResult; + if (DealWithPopups(hWnd, msg, wParam, lParam, &popupHandlingResult)) + return popupHandlingResult; + + // Call ProcessMessage + LRESULT retValue; + if (targetWindow->ProcessMessage(msg, wParam, lParam, &retValue)) { + return retValue; + } + + LRESULT res = ::CallWindowProcW(targetWindow->GetPrevWindowProc(), + hWnd, msg, wParam, lParam); + + return res; +} + +// The main windows message processing method for plugins. +// The result means whether this method processed the native +// event for plugin. If false, the native event should be +// processed by the caller self. +bool +nsWindow::ProcessMessageForPlugin(const MSG &aMsg, + MSGResult& aResult) +{ + aResult.mResult = 0; + aResult.mConsumed = true; + + bool eventDispatched = false; + switch (aMsg.message) { + case WM_CHAR: + case WM_SYSCHAR: + aResult.mResult = ProcessCharMessage(aMsg, &eventDispatched); + break; + + case WM_KEYUP: + case WM_SYSKEYUP: + aResult.mResult = ProcessKeyUpMessage(aMsg, &eventDispatched); + break; + + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + aResult.mResult = ProcessKeyDownMessage(aMsg, &eventDispatched); + break; + + case WM_DEADCHAR: + case WM_SYSDEADCHAR: + + case WM_CUT: + case WM_COPY: + case WM_PASTE: + case WM_CLEAR: + case WM_UNDO: + break; + + default: + return false; + } + + if (!eventDispatched) { + aResult.mConsumed = nsWindowBase::DispatchPluginEvent(aMsg); + } + if (!Destroyed()) { + DispatchPendingEvents(); + } + return true; +} + +static void ForceFontUpdate() +{ + // update device context font cache + // Dirty but easiest way: + // Changing nsIPrefBranch entry which triggers callbacks + // and flows into calling mDeviceContext->FlushFontCache() + // to update the font cache in all the instance of Browsers + static const char kPrefName[] = "font.internaluseonly.changed"; + bool fontInternalChange = + Preferences::GetBool(kPrefName, false); + Preferences::SetBool(kPrefName, !fontInternalChange); +} + +static bool CleartypeSettingChanged() +{ + static int currentQuality = -1; + BYTE quality = cairo_win32_get_system_text_quality(); + + if (currentQuality == quality) + return false; + + if (currentQuality < 0) { + currentQuality = quality; + return false; + } + currentQuality = quality; + return true; +} + +bool +nsWindow::ExternalHandlerProcessMessage(UINT aMessage, + WPARAM& aWParam, + LPARAM& aLParam, + MSGResult& aResult) +{ + if (mWindowHook.Notify(mWnd, aMessage, aWParam, aLParam, aResult)) { + return true; + } + + if (IMEHandler::ProcessMessage(this, aMessage, aWParam, aLParam, aResult)) { + return true; + } + + if (MouseScrollHandler::ProcessMessage(this, aMessage, aWParam, aLParam, + aResult)) { + return true; + } + + if (PluginHasFocus()) { + MSG nativeMsg = WinUtils::InitMSG(aMessage, aWParam, aLParam, mWnd); + if (ProcessMessageForPlugin(nativeMsg, aResult)) { + return true; + } + } + + return false; +} + +// The main windows message processing method. +bool +nsWindow::ProcessMessage(UINT msg, WPARAM& wParam, LPARAM& lParam, + LRESULT *aRetValue) +{ +#if defined(EVENT_DEBUG_OUTPUT) + // First param shows all events, second param indicates whether + // to show mouse move events. See nsWindowDbg for details. + PrintEvent(msg, SHOW_REPEAT_EVENTS, SHOW_MOUSEMOVE_EVENTS); +#endif + + MSGResult msgResult(aRetValue); + if (ExternalHandlerProcessMessage(msg, wParam, lParam, msgResult)) { + return (msgResult.mConsumed || !mWnd); + } + + bool result = false; // call the default nsWindow proc + *aRetValue = 0; + + // Glass hit testing w/custom transparent margins + LRESULT dwmHitResult; + if (mCustomNonClient && + nsUXThemeData::CheckForCompositor() && + /* We don't do this for win10 glass with a custom titlebar, + * in order to avoid the caption buttons breaking. */ + !(IsWin10OrLater() && HasGlass()) && + WinUtils::dwmDwmDefWindowProcPtr(mWnd, msg, wParam, lParam, &dwmHitResult)) { + *aRetValue = dwmHitResult; + return true; + } + + // (Large blocks of code should be broken out into OnEvent handlers.) + switch (msg) { + // WM_QUERYENDSESSION must be handled by all windows. + // Otherwise Windows thinks the window can just be killed at will. + case WM_QUERYENDSESSION: + if (sCanQuit == TRI_UNKNOWN) + { + // Ask if it's ok to quit, and store the answer until we + // get WM_ENDSESSION signaling the round is complete. + nsCOMPtr<nsIObserverService> obsServ = + mozilla::services::GetObserverService(); + nsCOMPtr<nsISupportsPRBool> cancelQuit = + do_CreateInstance(NS_SUPPORTS_PRBOOL_CONTRACTID); + cancelQuit->SetData(false); + obsServ->NotifyObservers(cancelQuit, "quit-application-requested", nullptr); + + bool abortQuit; + cancelQuit->GetData(&abortQuit); + sCanQuit = abortQuit ? TRI_FALSE : TRI_TRUE; + } + *aRetValue = sCanQuit ? TRUE : FALSE; + result = true; + break; + + case WM_ENDSESSION: + case MOZ_WM_APP_QUIT: + if (msg == MOZ_WM_APP_QUIT || (wParam == TRUE && sCanQuit == TRI_TRUE)) + { + // Let's fake a shutdown sequence without actually closing windows etc. + // to avoid Windows killing us in the middle. A proper shutdown would + // require having a chance to pump some messages. Unfortunately + // Windows won't let us do that. Bug 212316. + nsCOMPtr<nsIObserverService> obsServ = + mozilla::services::GetObserverService(); + NS_NAMED_LITERAL_STRING(context, "shutdown-persist"); + NS_NAMED_LITERAL_STRING(syncShutdown, "syncShutdown"); + obsServ->NotifyObservers(nullptr, "quit-application-granted", syncShutdown.get()); + obsServ->NotifyObservers(nullptr, "quit-application-forced", nullptr); + obsServ->NotifyObservers(nullptr, "quit-application", nullptr); + obsServ->NotifyObservers(nullptr, "profile-change-net-teardown", context.get()); + obsServ->NotifyObservers(nullptr, "profile-change-teardown", context.get()); + obsServ->NotifyObservers(nullptr, "profile-before-change", context.get()); + obsServ->NotifyObservers(nullptr, "profile-before-change-qm", context.get()); + obsServ->NotifyObservers(nullptr, "profile-before-change-telemetry", context.get()); + // Then a controlled but very quick exit. + _exit(0); + } + sCanQuit = TRI_UNKNOWN; + result = true; + break; + + case WM_SYSCOLORCHANGE: + OnSysColorChanged(); + break; + + case WM_THEMECHANGED: + { + // Update non-client margin offsets + UpdateNonClientMargins(); + nsUXThemeData::InitTitlebarInfo(); + nsUXThemeData::UpdateNativeThemeInfo(); + + NotifyThemeChanged(); + + // Invalidate the window so that the repaint will + // pick up the new theme. + Invalidate(true, true, true); + } + break; + + case WM_WTSSESSION_CHANGE: + { + switch (wParam) { + case WTS_CONSOLE_CONNECT: + case WTS_REMOTE_CONNECT: + case WTS_SESSION_UNLOCK: + // When a session becomes visible, we should invalidate. + Invalidate(true, true, true); + break; + default: + break; + } + } + break; + + case WM_FONTCHANGE: + { + // We only handle this message for the hidden window, + // as we only need to update the (global) font list once + // for any given change, not once per window! + if (mWindowType != eWindowType_invisible) { + break; + } + + nsresult rv; + bool didChange = false; + + // update the global font list + nsCOMPtr<nsIFontEnumerator> fontEnum = do_GetService("@mozilla.org/gfx/fontenumerator;1", &rv); + if (NS_SUCCEEDED(rv)) { + fontEnum->UpdateFontList(&didChange); + ForceFontUpdate(); + } //if (NS_SUCCEEDED(rv)) + } + break; + + case WM_SETTINGCHANGE: + { + if (IsWin10OrLater() && mWindowType == eWindowType_invisible && lParam) { + auto lParamString = reinterpret_cast<const wchar_t*>(lParam); + if (!wcscmp(lParamString, L"UserInteractionMode")) { + nsCOMPtr<nsIWindowsUIUtils> uiUtils(do_GetService("@mozilla.org/windows-ui-utils;1")); + if (uiUtils) { + uiUtils->UpdateTabletModeState(); + } + } + } + } + break; + + case WM_NCCALCSIZE: + { + if (mCustomNonClient) { + // If `wParam` is `FALSE`, `lParam` points to a `RECT` that contains + // the proposed window rectangle for our window. During our + // processing of the `WM_NCCALCSIZE` message, we are expected to + // modify the `RECT` that `lParam` points to, so that its value upon + // our return is the new client area. We must return 0 if `wParam` + // is `FALSE`. + // + // If `wParam` is `TRUE`, `lParam` points to a `NCCALCSIZE_PARAMS` + // struct. This struct contains an array of 3 `RECT`s, the first of + // which has the exact same meaning as the `RECT` that is pointed to + // by `lParam` when `wParam` is `FALSE`. The remaining `RECT`s, in + // conjunction with our return value, can + // be used to specify portions of the source and destination window + // rectangles that are valid and should be preserved. We opt not to + // implement an elaborate client-area preservation technique, and + // simply return 0, which means "preserve the entire old client area + // and align it with the upper-left corner of our new client area". + RECT *clientRect = wParam + ? &(reinterpret_cast<NCCALCSIZE_PARAMS*>(lParam))->rgrc[0] + : (reinterpret_cast<RECT*>(lParam)); + double scale = WinUtils::IsPerMonitorDPIAware() + ? WinUtils::LogToPhysFactor(mWnd) / WinUtils::SystemScaleFactor() + : 1.0; + clientRect->top += + NSToIntRound((mCaptionHeight - mNonClientOffset.top) * scale); + clientRect->left += + NSToIntRound((mHorResizeMargin - mNonClientOffset.left) * scale); + clientRect->right -= + NSToIntRound((mHorResizeMargin - mNonClientOffset.right) * scale); + clientRect->bottom -= + NSToIntRound((mVertResizeMargin - mNonClientOffset.bottom) * scale); + + result = true; + *aRetValue = 0; + } + break; + } + + case WM_NCHITTEST: + { + if (mMouseTransparent) { + // Treat this window as transparent. + *aRetValue = HTTRANSPARENT; + result = true; + break; + } + + /* + * If an nc client area margin has been moved, we are responsible + * for calculating where the resize margins are and returning the + * appropriate set of hit test constants. DwmDefWindowProc (above) + * will handle hit testing on it's command buttons if we are on a + * composited desktop. + */ + + if (!mCustomNonClient) + break; + + *aRetValue = + ClientMarginHitTestPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); + result = true; + break; + } + + case WM_SETTEXT: + /* + * WM_SETTEXT paints the titlebar area. Avoid this if we have a + * custom titlebar we paint ourselves, or if we're the ones + * sending the message with an updated title + */ + + if ((mSendingSetText && nsUXThemeData::CheckForCompositor()) || + !mCustomNonClient || mNonClientMargins.top == -1) + break; + + { + // From msdn, the way around this is to disable the visible state + // temporarily. We need the text to be set but we don't want the + // redraw to occur. However, we need to make sure that we don't + // do this at the same time that a Present is happening. + // + // To do this we take mPresentLock in nsWindow::PreRender and + // if that lock is taken we wait before doing WM_SETTEXT + if (mCompositorWidgetDelegate) { + mCompositorWidgetDelegate->EnterPresentLock(); + } + DWORD style = GetWindowLong(mWnd, GWL_STYLE); + SetWindowLong(mWnd, GWL_STYLE, style & ~WS_VISIBLE); + *aRetValue = CallWindowProcW(GetPrevWindowProc(), mWnd, + msg, wParam, lParam); + SetWindowLong(mWnd, GWL_STYLE, style); + if (mCompositorWidgetDelegate) { + mCompositorWidgetDelegate->LeavePresentLock(); + } + + return true; + } + + case WM_NCACTIVATE: + { + /* + * WM_NCACTIVATE paints nc areas. Avoid this and re-route painting + * through WM_NCPAINT via InvalidateNonClientRegion. + */ + UpdateGetWindowInfoCaptionStatus(FALSE != wParam); + + if (!mCustomNonClient) + break; + + // There is a case that rendered result is not kept. Bug 1237617 + if (wParam == TRUE && + !gfxEnv::DisableForcePresent() && + gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled()) { + NS_DispatchToMainThread(NewRunnableMethod(this, &nsWindow::ForcePresent)); + } + + // let the dwm handle nc painting on glass + // Never allow native painting if we are on fullscreen + if(mSizeMode != nsSizeMode_Fullscreen && + nsUXThemeData::CheckForCompositor()) + break; + + if (wParam == TRUE) { + // going active + *aRetValue = FALSE; // ignored + result = true; + // invalidate to trigger a paint + InvalidateNonClientRegion(); + break; + } else { + // going inactive + *aRetValue = TRUE; // go ahead and deactive + result = true; + // invalidate to trigger a paint + InvalidateNonClientRegion(); + break; + } + } + + case WM_NCPAINT: + { + /* + * Reset the non-client paint region so that it excludes the + * non-client areas we paint manually. Then call defwndproc + * to do the actual painting. + */ + + if (!mCustomNonClient) + break; + + // let the dwm handle nc painting on glass + if(nsUXThemeData::CheckForCompositor()) + break; + + HRGN paintRgn = ExcludeNonClientFromPaintRegion((HRGN)wParam); + LRESULT res = CallWindowProcW(GetPrevWindowProc(), mWnd, + msg, (WPARAM)paintRgn, lParam); + if (paintRgn != (HRGN)wParam) + DeleteObject(paintRgn); + *aRetValue = res; + result = true; + } + break; + + case WM_POWERBROADCAST: + switch (wParam) + { + case PBT_APMSUSPEND: + PostSleepWakeNotification(true); + break; + case PBT_APMRESUMEAUTOMATIC: + case PBT_APMRESUMECRITICAL: + case PBT_APMRESUMESUSPEND: + PostSleepWakeNotification(false); + break; + } + break; + + case WM_CLOSE: // close request + if (mWidgetListener) + mWidgetListener->RequestWindowClose(this); + result = true; // abort window closure + break; + + case WM_DESTROY: + // clean up. + OnDestroy(); + result = true; + break; + + case WM_PAINT: + if (CleartypeSettingChanged()) { + ForceFontUpdate(); + gfxFontCache *fc = gfxFontCache::GetCache(); + if (fc) { + fc->Flush(); + } + } + *aRetValue = (int) OnPaint(nullptr, 0); + result = true; + break; + + case WM_PRINTCLIENT: + result = OnPaint((HDC) wParam, 0); + break; + + case WM_HOTKEY: + result = OnHotKey(wParam, lParam); + break; + + case WM_SYSCHAR: + case WM_CHAR: + { + MSG nativeMsg = WinUtils::InitMSG(msg, wParam, lParam, mWnd); + result = ProcessCharMessage(nativeMsg, nullptr); + DispatchPendingEvents(); + } + break; + + case WM_SYSKEYUP: + case WM_KEYUP: + { + MSG nativeMsg = WinUtils::InitMSG(msg, wParam, lParam, mWnd); + nativeMsg.time = ::GetMessageTime(); + result = ProcessKeyUpMessage(nativeMsg, nullptr); + DispatchPendingEvents(); + } + break; + + case WM_SYSKEYDOWN: + case WM_KEYDOWN: + { + MSG nativeMsg = WinUtils::InitMSG(msg, wParam, lParam, mWnd); + result = ProcessKeyDownMessage(nativeMsg, nullptr); + DispatchPendingEvents(); + } + break; + + // say we've dealt with erase background if widget does + // not need auto-erasing + case WM_ERASEBKGND: + if (!AutoErase((HDC)wParam)) { + *aRetValue = 1; + result = true; + } + break; + + case WM_MOUSEMOVE: + { + if (!mMousePresent && !sIsInMouseCapture) { + // First MOUSEMOVE over the client area. Ask for MOUSELEAVE + TRACKMOUSEEVENT mTrack; + mTrack.cbSize = sizeof(TRACKMOUSEEVENT); + mTrack.dwFlags = TME_LEAVE; + mTrack.dwHoverTime = 0; + mTrack.hwndTrack = mWnd; + TrackMouseEvent(&mTrack); + } + mMousePresent = true; + + // Suppress dispatch of pending events + // when mouse moves are generated by widget + // creation instead of user input. + LPARAM lParamScreen = lParamToScreen(lParam); + POINT mp; + mp.x = GET_X_LPARAM(lParamScreen); + mp.y = GET_Y_LPARAM(lParamScreen); + bool userMovedMouse = false; + if ((sLastMouseMovePoint.x != mp.x) || (sLastMouseMovePoint.y != mp.y)) { + userMovedMouse = true; + } + + result = DispatchMouseEvent(eMouseMove, wParam, lParam, + false, WidgetMouseEvent::eLeftButton, + MOUSE_INPUT_SOURCE(), MOUSE_POINTERID()); + if (userMovedMouse) { + DispatchPendingEvents(); + } + } + break; + + case WM_NCMOUSEMOVE: + // If we receive a mouse move event on non-client chrome, make sure and + // send an eMouseExitFromWidget event as well. + if (mMousePresent && !sIsInMouseCapture) + SendMessage(mWnd, WM_MOUSELEAVE, 0, 0); + break; + + case WM_LBUTTONDOWN: + { + result = DispatchMouseEvent(eMouseDown, wParam, lParam, + false, WidgetMouseEvent::eLeftButton, + MOUSE_INPUT_SOURCE(), MOUSE_POINTERID()); + DispatchPendingEvents(); + } + break; + + case WM_LBUTTONUP: + { + result = DispatchMouseEvent(eMouseUp, wParam, lParam, + false, WidgetMouseEvent::eLeftButton, + MOUSE_INPUT_SOURCE(), MOUSE_POINTERID()); + DispatchPendingEvents(); + } + break; + + case WM_MOUSELEAVE: + { + if (!mMousePresent) + break; + mMousePresent = false; + + // We need to check mouse button states and put them in for + // wParam. + WPARAM mouseState = (GetKeyState(VK_LBUTTON) ? MK_LBUTTON : 0) + | (GetKeyState(VK_MBUTTON) ? MK_MBUTTON : 0) + | (GetKeyState(VK_RBUTTON) ? MK_RBUTTON : 0); + // Synthesize an event position because we don't get one from + // WM_MOUSELEAVE. + LPARAM pos = lParamToClient(::GetMessagePos()); + DispatchMouseEvent(eMouseExitFromWidget, mouseState, pos, false, + WidgetMouseEvent::eLeftButton, + MOUSE_INPUT_SOURCE(), MOUSE_POINTERID()); + } + break; + + case MOZ_WM_PEN_LEAVES_HOVER_OF_DIGITIZER: + { + LPARAM pos = lParamToClient(::GetMessagePos()); + uint16_t pointerId = InkCollector::sInkCollector->GetPointerId(); + if (pointerId != 0) { + DispatchMouseEvent(eMouseExitFromWidget, wParam, pos, false, + WidgetMouseEvent::eLeftButton, + nsIDOMMouseEvent::MOZ_SOURCE_PEN, pointerId); + InkCollector::sInkCollector->ClearTarget(); + InkCollector::sInkCollector->ClearPointerId(); + } + } + break; + + case WM_CONTEXTMENU: + { + // If the context menu is brought up by a touch long-press, then + // the APZ code is responsible for dealing with this, so we don't + // need to do anything. + if (mTouchWindow && MOUSE_INPUT_SOURCE() == nsIDOMMouseEvent::MOZ_SOURCE_TOUCH) { + MOZ_ASSERT(mAPZC); // since mTouchWindow is true, APZ must be enabled + result = true; + break; + } + + // if the context menu is brought up from the keyboard, |lParam| + // will be -1. + LPARAM pos; + bool contextMenukey = false; + if (lParam == -1) + { + contextMenukey = true; + pos = lParamToClient(GetMessagePos()); + } + else + { + pos = lParamToClient(lParam); + } + + result = DispatchMouseEvent(eContextMenu, wParam, pos, contextMenukey, + contextMenukey ? + WidgetMouseEvent::eLeftButton : + WidgetMouseEvent::eRightButton, + MOUSE_INPUT_SOURCE(), MOUSE_POINTERID()); + if (lParam != -1 && !result && mCustomNonClient && + mDraggableRegion.Contains(GET_X_LPARAM(pos), GET_Y_LPARAM(pos))) { + // Blank area hit, throw up the system menu. + DisplaySystemMenu(mWnd, mSizeMode, mIsRTL, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); + result = true; + } + } + break; + + case WM_LBUTTONDBLCLK: + result = DispatchMouseEvent(eMouseDoubleClick, wParam, + lParam, false, + WidgetMouseEvent::eLeftButton, + MOUSE_INPUT_SOURCE(), MOUSE_POINTERID()); + DispatchPendingEvents(); + break; + + case WM_MBUTTONDOWN: + result = DispatchMouseEvent(eMouseDown, wParam, + lParam, false, + WidgetMouseEvent::eMiddleButton, + MOUSE_INPUT_SOURCE(), MOUSE_POINTERID()); + DispatchPendingEvents(); + break; + + case WM_MBUTTONUP: + result = DispatchMouseEvent(eMouseUp, wParam, + lParam, false, + WidgetMouseEvent::eMiddleButton, + MOUSE_INPUT_SOURCE(), MOUSE_POINTERID()); + DispatchPendingEvents(); + break; + + case WM_MBUTTONDBLCLK: + result = DispatchMouseEvent(eMouseDoubleClick, wParam, + lParam, false, + WidgetMouseEvent::eMiddleButton, + MOUSE_INPUT_SOURCE(), MOUSE_POINTERID()); + DispatchPendingEvents(); + break; + + case WM_NCMBUTTONDOWN: + result = DispatchMouseEvent(eMouseDown, 0, + lParamToClient(lParam), false, + WidgetMouseEvent::eMiddleButton, + MOUSE_INPUT_SOURCE(), MOUSE_POINTERID()); + DispatchPendingEvents(); + break; + + case WM_NCMBUTTONUP: + result = DispatchMouseEvent(eMouseUp, 0, + lParamToClient(lParam), false, + WidgetMouseEvent::eMiddleButton, + MOUSE_INPUT_SOURCE(), MOUSE_POINTERID()); + DispatchPendingEvents(); + break; + + case WM_NCMBUTTONDBLCLK: + result = DispatchMouseEvent(eMouseDoubleClick, 0, + lParamToClient(lParam), false, + WidgetMouseEvent::eMiddleButton, + MOUSE_INPUT_SOURCE(), MOUSE_POINTERID()); + DispatchPendingEvents(); + break; + + case WM_RBUTTONDOWN: + result = DispatchMouseEvent(eMouseDown, wParam, + lParam, false, + WidgetMouseEvent::eRightButton, + MOUSE_INPUT_SOURCE(), MOUSE_POINTERID()); + DispatchPendingEvents(); + break; + + case WM_RBUTTONUP: + result = DispatchMouseEvent(eMouseUp, wParam, + lParam, false, + WidgetMouseEvent::eRightButton, + MOUSE_INPUT_SOURCE(), MOUSE_POINTERID()); + DispatchPendingEvents(); + break; + + case WM_RBUTTONDBLCLK: + result = DispatchMouseEvent(eMouseDoubleClick, wParam, + lParam, false, + WidgetMouseEvent::eRightButton, + MOUSE_INPUT_SOURCE(), MOUSE_POINTERID()); + DispatchPendingEvents(); + break; + + case WM_NCRBUTTONDOWN: + result = DispatchMouseEvent(eMouseDown, 0, + lParamToClient(lParam), false, + WidgetMouseEvent::eRightButton, + MOUSE_INPUT_SOURCE(), MOUSE_POINTERID()); + DispatchPendingEvents(); + break; + + case WM_NCRBUTTONUP: + result = DispatchMouseEvent(eMouseUp, 0, + lParamToClient(lParam), false, + WidgetMouseEvent::eRightButton, + MOUSE_INPUT_SOURCE(), MOUSE_POINTERID()); + DispatchPendingEvents(); + break; + + case WM_NCRBUTTONDBLCLK: + result = DispatchMouseEvent(eMouseDoubleClick, 0, + lParamToClient(lParam), false, + WidgetMouseEvent::eRightButton, + MOUSE_INPUT_SOURCE(), MOUSE_POINTERID()); + DispatchPendingEvents(); + break; + + // Windows doesn't provide to customize the behavior of 4th nor 5th button + // of mouse. If 5-button mouse works with standard mouse deriver of + // Windows, users cannot disable 4th button (browser back) nor 5th button + // (browser forward). We should allow to do it with our prefs since we can + // prevent Windows to generate WM_APPCOMMAND message if WM_XBUTTONUP + // messages are not sent to DefWindowProc. + case WM_XBUTTONDOWN: + case WM_XBUTTONUP: + case WM_NCXBUTTONDOWN: + case WM_NCXBUTTONUP: + *aRetValue = TRUE; + switch (GET_XBUTTON_WPARAM(wParam)) { + case XBUTTON1: + result = !Preferences::GetBool("mousebutton.4th.enabled", true); + break; + case XBUTTON2: + result = !Preferences::GetBool("mousebutton.5th.enabled", true); + break; + default: + break; + } + break; + + case WM_SIZING: + { + // When we get WM_ENTERSIZEMOVE we don't know yet if we're in a live + // resize or move event. Instead we wait for first VM_SIZING message + // within a ENTERSIZEMOVE to consider this a live resize event. + if (mResizeState == IN_SIZEMOVE) { + mResizeState = RESIZING; + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + + if (observerService) { + observerService->NotifyObservers(nullptr, "live-resize-start", + nullptr); + } + } + break; + } + + case WM_MOVING: + FinishLiveResizing(MOVING); + if (WinUtils::IsPerMonitorDPIAware()) { + // Sometimes, we appear to miss a WM_DPICHANGED message while moving + // a window around. Therefore, call ChangedDPI and ResetLayout here, + // which causes the prescontext and appshell window management code to + // check the appUnitsPerDevPixel value and current widget size, and + // refresh them if necessary. If nothing has changed, these calls will + // return without actually triggering any extra reflow or painting. + ChangedDPI(); + ResetLayout(); + } + break; + + case WM_ENTERSIZEMOVE: + { + if (mResizeState == NOT_RESIZING) { + mResizeState = IN_SIZEMOVE; + } + break; + } + + case WM_EXITSIZEMOVE: + { + FinishLiveResizing(NOT_RESIZING); + + if (!sIsInMouseCapture) { + NotifySizeMoveDone(); + } + + break; + } + + case WM_NCLBUTTONDBLCLK: + DispatchMouseEvent(eMouseDoubleClick, 0, lParamToClient(lParam), + false, WidgetMouseEvent::eLeftButton, + MOUSE_INPUT_SOURCE(), MOUSE_POINTERID()); + result = + DispatchMouseEvent(eMouseUp, 0, lParamToClient(lParam), + false, WidgetMouseEvent::eLeftButton, + MOUSE_INPUT_SOURCE(), MOUSE_POINTERID()); + DispatchPendingEvents(); + break; + + case WM_APPCOMMAND: + { + MSG nativeMsg = WinUtils::InitMSG(msg, wParam, lParam, mWnd); + result = HandleAppCommandMsg(nativeMsg, aRetValue); + break; + } + + // The WM_ACTIVATE event is fired when a window is raised or lowered, + // and the loword of wParam specifies which. But we don't want to tell + // the focus system about this until the WM_SETFOCUS or WM_KILLFOCUS + // events are fired. Instead, set either the sJustGotActivate or + // gJustGotDeactivate flags and activate/deactivate once the focus + // events arrive. + case WM_ACTIVATE: + if (mWidgetListener) { + int32_t fActive = LOWORD(wParam); + + if (WA_INACTIVE == fActive) { + // when minimizing a window, the deactivation and focus events will + // be fired in the reverse order. Instead, just deactivate right away. + if (HIWORD(wParam)) + DispatchFocusToTopLevelWindow(false); + else + sJustGotDeactivate = true; + + if (mIsTopWidgetWindow) + mLastKeyboardLayout = KeyboardLayout::GetInstance()->GetLayout(); + + } else { + StopFlashing(); + + sJustGotActivate = true; + WidgetMouseEvent event(true, eMouseActivate, this, + WidgetMouseEvent::eReal); + InitEvent(event); + ModifierKeyState modifierKeyState; + modifierKeyState.InitInputEvent(event); + DispatchInputEvent(&event); + if (sSwitchKeyboardLayout && mLastKeyboardLayout) + ActivateKeyboardLayout(mLastKeyboardLayout, 0); + } + } + break; + + case WM_MOUSEACTIVATE: + // A popup with a parent owner should not be activated when clicked but + // should still allow the mouse event to be fired, so the return value + // is set to MA_NOACTIVATE. But if the owner isn't the frontmost window, + // just use default processing so that the window is activated. + if (IsPopup() && IsOwnerForegroundWindow()) { + *aRetValue = MA_NOACTIVATE; + result = true; + } + break; + + case WM_WINDOWPOSCHANGING: + { + LPWINDOWPOS info = (LPWINDOWPOS)lParam; + OnWindowPosChanging(info); + result = true; + } + break; + + case WM_GETMINMAXINFO: + { + MINMAXINFO* mmi = (MINMAXINFO*)lParam; + // Set the constraints. The minimum size should also be constrained to the + // default window maximum size so that it fits on screen. + mmi->ptMinTrackSize.x = + std::min((int32_t)mmi->ptMaxTrackSize.x, + std::max((int32_t)mmi->ptMinTrackSize.x, mSizeConstraints.mMinSize.width)); + mmi->ptMinTrackSize.y = + std::min((int32_t)mmi->ptMaxTrackSize.y, + std::max((int32_t)mmi->ptMinTrackSize.y, mSizeConstraints.mMinSize.height)); + mmi->ptMaxTrackSize.x = std::min((int32_t)mmi->ptMaxTrackSize.x, mSizeConstraints.mMaxSize.width); + mmi->ptMaxTrackSize.y = std::min((int32_t)mmi->ptMaxTrackSize.y, mSizeConstraints.mMaxSize.height); + } + break; + + case WM_SETFOCUS: + // If previous focused window isn't ours, it must have received the + // redirected message. So, we should forget it. + if (!WinUtils::IsOurProcessWindow(HWND(wParam))) { + RedirectedKeyDownMessageManager::Forget(); + } + if (sJustGotActivate) { + DispatchFocusToTopLevelWindow(true); + } + break; + + case WM_KILLFOCUS: + if (sJustGotDeactivate) { + DispatchFocusToTopLevelWindow(false); + } + break; + + case WM_WINDOWPOSCHANGED: + { + WINDOWPOS* wp = (LPWINDOWPOS)lParam; + OnWindowPosChanged(wp); + result = true; + } + break; + + case WM_INPUTLANGCHANGEREQUEST: + *aRetValue = TRUE; + result = false; + break; + + case WM_INPUTLANGCHANGE: + KeyboardLayout::GetInstance()-> + OnLayoutChange(reinterpret_cast<HKL>(lParam)); + nsBidiKeyboard::OnLayoutChange(); + result = false; // always pass to child window + break; + + case WM_DESTROYCLIPBOARD: + { + nsIClipboard* clipboard; + nsresult rv = CallGetService(kCClipboardCID, &clipboard); + if(NS_SUCCEEDED(rv)) { + clipboard->EmptyClipboard(nsIClipboard::kGlobalClipboard); + NS_RELEASE(clipboard); + } + } + break; + +#ifdef ACCESSIBILITY + case WM_GETOBJECT: + { + *aRetValue = 0; + // Do explicit casting to make it working on 64bit systems (see bug 649236 + // for details). + int32_t objId = static_cast<DWORD>(lParam); + if (objId == OBJID_CLIENT) { // oleacc.dll will be loaded dynamically + a11y::Accessible* rootAccessible = GetAccessible(); // Held by a11y cache + if (rootAccessible) { + IAccessible *msaaAccessible = nullptr; + rootAccessible->GetNativeInterface((void**)&msaaAccessible); // does an addref + if (msaaAccessible) { + *aRetValue = LresultFromObject(IID_IAccessible, wParam, msaaAccessible); // does an addref + msaaAccessible->Release(); // release extra addref + result = true; // We handled the WM_GETOBJECT message + } + } + } + } + break; +#endif + + case WM_SYSCOMMAND: + { + WPARAM filteredWParam = (wParam &0xFFF0); + // prevent Windows from trimming the working set. bug 76831 + if (!sTrimOnMinimize && filteredWParam == SC_MINIMIZE) { + ::ShowWindow(mWnd, SW_SHOWMINIMIZED); + result = true; + } + + if (mSizeMode == nsSizeMode_Fullscreen && + filteredWParam == SC_RESTORE && + GetCurrentShowCmd(mWnd) != SW_SHOWMINIMIZED) { + MakeFullScreen(false); + result = true; + } + + // Handle the system menu manually when we're in full screen mode + // so we can set the appropriate options. + if (filteredWParam == SC_KEYMENU && lParam == VK_SPACE && + mSizeMode == nsSizeMode_Fullscreen) { + DisplaySystemMenu(mWnd, mSizeMode, mIsRTL, + MOZ_SYSCONTEXT_X_POS, + MOZ_SYSCONTEXT_Y_POS); + result = true; + } + } + break; + + case WM_DWMCOMPOSITIONCHANGED: + // First, update the compositor state to latest one. All other methods + // should use same state as here for consistency painting. + nsUXThemeData::CheckForCompositor(true); + + UpdateNonClientMargins(); + BroadcastMsg(mWnd, WM_DWMCOMPOSITIONCHANGED); + NotifyThemeChanged(); + UpdateGlass(); + Invalidate(true, true, true); + break; + + case WM_DPICHANGED: + { + LPRECT rect = (LPRECT) lParam; + OnDPIChanged(rect->left, rect->top, rect->right - rect->left, + rect->bottom - rect->top); + break; + } + + case WM_UPDATEUISTATE: + { + // If the UI state has changed, fire an event so the UI updates the + // keyboard cues based on the system setting and how the window was + // opened. For example, a dialog opened via a keyboard press on a button + // should enable cues, whereas the same dialog opened via a mouse click of + // the button should not. + if (mWindowType == eWindowType_toplevel || + mWindowType == eWindowType_dialog) { + int32_t action = LOWORD(wParam); + if (action == UIS_SET || action == UIS_CLEAR) { + int32_t flags = HIWORD(wParam); + UIStateChangeType showAccelerators = UIStateChangeType_NoChange; + UIStateChangeType showFocusRings = UIStateChangeType_NoChange; + if (flags & UISF_HIDEACCEL) + showAccelerators = (action == UIS_SET) ? UIStateChangeType_Clear : UIStateChangeType_Set; + if (flags & UISF_HIDEFOCUS) + showFocusRings = (action == UIS_SET) ? UIStateChangeType_Clear : UIStateChangeType_Set; + + NotifyUIStateChanged(showAccelerators, showFocusRings); + } + } + + break; + } + + /* Gesture support events */ + case WM_TABLET_QUERYSYSTEMGESTURESTATUS: + // According to MS samples, this must be handled to enable + // rotational support in multi-touch drivers. + result = true; + *aRetValue = TABLET_ROTATE_GESTURE_ENABLE; + break; + + case WM_TOUCH: + result = OnTouch(wParam, lParam); + if (result) { + *aRetValue = 0; + } + break; + + case WM_GESTURE: + result = OnGesture(wParam, lParam); + break; + + case WM_GESTURENOTIFY: + { + if (mWindowType != eWindowType_invisible && + !IsPlugin()) { + // A GestureNotify event is dispatched to decide which single-finger panning + // direction should be active (including none) and if pan feedback should + // be displayed. Java and plugin windows can make their own calls. + + GESTURENOTIFYSTRUCT * gestureinfo = (GESTURENOTIFYSTRUCT*)lParam; + nsPointWin touchPoint; + touchPoint = gestureinfo->ptsLocation; + touchPoint.ScreenToClient(mWnd); + WidgetGestureNotifyEvent gestureNotifyEvent(true, eGestureNotify, this); + gestureNotifyEvent.mRefPoint = + LayoutDeviceIntPoint::FromUnknownPoint(touchPoint); + nsEventStatus status; + DispatchEvent(&gestureNotifyEvent, status); + mDisplayPanFeedback = gestureNotifyEvent.mDisplayPanFeedback; + if (!mTouchWindow) + mGesture.SetWinGestureSupport(mWnd, gestureNotifyEvent.mPanDirection); + } + result = false; //should always bubble to DefWindowProc + } + break; + + case WM_CLEAR: + { + WidgetContentCommandEvent command(true, eContentCommandDelete, this); + DispatchWindowEvent(&command); + result = true; + } + break; + + case WM_CUT: + { + WidgetContentCommandEvent command(true, eContentCommandCut, this); + DispatchWindowEvent(&command); + result = true; + } + break; + + case WM_COPY: + { + WidgetContentCommandEvent command(true, eContentCommandCopy, this); + DispatchWindowEvent(&command); + result = true; + } + break; + + case WM_PASTE: + { + WidgetContentCommandEvent command(true, eContentCommandPaste, this); + DispatchWindowEvent(&command); + result = true; + } + break; + + case EM_UNDO: + { + WidgetContentCommandEvent command(true, eContentCommandUndo, this); + DispatchWindowEvent(&command); + *aRetValue = (LRESULT)(command.mSucceeded && command.mIsEnabled); + result = true; + } + break; + + case EM_REDO: + { + WidgetContentCommandEvent command(true, eContentCommandRedo, this); + DispatchWindowEvent(&command); + *aRetValue = (LRESULT)(command.mSucceeded && command.mIsEnabled); + result = true; + } + break; + + case EM_CANPASTE: + { + // Support EM_CANPASTE message only when wParam isn't specified or + // is plain text format. + if (wParam == 0 || wParam == CF_TEXT || wParam == CF_UNICODETEXT) { + WidgetContentCommandEvent command(true, eContentCommandPaste, + this, true); + DispatchWindowEvent(&command); + *aRetValue = (LRESULT)(command.mSucceeded && command.mIsEnabled); + result = true; + } + } + break; + + case EM_CANUNDO: + { + WidgetContentCommandEvent command(true, eContentCommandUndo, this, true); + DispatchWindowEvent(&command); + *aRetValue = (LRESULT)(command.mSucceeded && command.mIsEnabled); + result = true; + } + break; + + case EM_CANREDO: + { + WidgetContentCommandEvent command(true, eContentCommandRedo, this, true); + DispatchWindowEvent(&command); + *aRetValue = (LRESULT)(command.mSucceeded && command.mIsEnabled); + result = true; + } + break; + + case MOZ_WM_SKEWFIX: + { + TimeStamp skewStamp; + if (CurrentWindowsTimeGetter::GetAndClearBackwardsSkewStamp(wParam, &skewStamp)) { + TimeConverter().CompensateForBackwardsSkew(::GetMessageTime(), skewStamp); + } + } + break; + + default: + { + if (msg == nsAppShell::GetTaskbarButtonCreatedMessage()) { + SetHasTaskbarIconBeenCreated(); + } + } + break; + + } + + //*aRetValue = result; + if (mWnd) { + return result; + } + else { + //Events which caused mWnd destruction and aren't consumed + //will crash during the Windows default processing. + return true; + } +} + +void +nsWindow::FinishLiveResizing(ResizeState aNewState) +{ + if (mResizeState == RESIZING) { + nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService(); + if (observerService) { + observerService->NotifyObservers(nullptr, "live-resize-end", nullptr); + } + } + mResizeState = aNewState; + ForcePresent(); +} + +/************************************************************** + * + * SECTION: Broadcast messaging + * + * Broadcast messages to all windows. + * + **************************************************************/ + +// Enumerate all child windows sending aMsg to each of them +BOOL CALLBACK nsWindow::BroadcastMsgToChildren(HWND aWnd, LPARAM aMsg) +{ + WNDPROC winProc = (WNDPROC)::GetWindowLongPtrW(aWnd, GWLP_WNDPROC); + if (winProc == &nsWindow::WindowProc) { + // it's one of our windows so go ahead and send a message to it + ::CallWindowProcW(winProc, aWnd, aMsg, 0, 0); + } + return TRUE; +} + +// Enumerate all top level windows specifying that the children of each +// top level window should be enumerated. Do *not* send the message to +// each top level window since it is assumed that the toolkit will send +// aMsg to them directly. +BOOL CALLBACK nsWindow::BroadcastMsg(HWND aTopWindow, LPARAM aMsg) +{ + // Iterate each of aTopWindows child windows sending the aMsg + // to each of them. + ::EnumChildWindows(aTopWindow, nsWindow::BroadcastMsgToChildren, aMsg); + return TRUE; +} + +/************************************************************** + * + * SECTION: Event processing helpers + * + * Special processing for certain event types and + * synthesized events. + * + **************************************************************/ + +int32_t +nsWindow::ClientMarginHitTestPoint(int32_t mx, int32_t my) +{ + if (mSizeMode == nsSizeMode_Minimized || + mSizeMode == nsSizeMode_Fullscreen) { + return HTCLIENT; + } + + // Calculations are done in screen coords + RECT winRect; + GetWindowRect(mWnd, &winRect); + + // hit return constants: + // HTBORDER - non-resizable border + // HTBOTTOM, HTLEFT, HTRIGHT, HTTOP - resizable border + // HTBOTTOMLEFT, HTBOTTOMRIGHT - resizable corner + // HTTOPLEFT, HTTOPRIGHT - resizable corner + // HTCAPTION - general title bar area + // HTCLIENT - area considered the client + // HTCLOSE - hovering over the close button + // HTMAXBUTTON - maximize button + // HTMINBUTTON - minimize button + + int32_t testResult = HTCLIENT; + + bool isResizable = (mBorderStyle & (eBorderStyle_all | + eBorderStyle_resizeh | + eBorderStyle_default)) > 0 ? true : false; + if (mSizeMode == nsSizeMode_Maximized) + isResizable = false; + + // Ensure being accessible to borders of window. Even if contents are in + // this area, the area must behave as border. + nsIntMargin nonClientSize(std::max(mCaptionHeight - mNonClientOffset.top, + kResizableBorderMinSize), + std::max(mHorResizeMargin - mNonClientOffset.right, + kResizableBorderMinSize), + std::max(mVertResizeMargin - mNonClientOffset.bottom, + kResizableBorderMinSize), + std::max(mHorResizeMargin - mNonClientOffset.left, + kResizableBorderMinSize)); + + bool allowContentOverride = mSizeMode == nsSizeMode_Maximized || + (mx >= winRect.left + nonClientSize.left && + mx <= winRect.right - nonClientSize.right && + my >= winRect.top + nonClientSize.top && + my <= winRect.bottom - nonClientSize.bottom); + + // The border size. If there is no content under mouse cursor, the border + // size should be larger than the values in system settings. Otherwise, + // contents under the mouse cursor should be able to override the behavior. + // E.g., user must expect that Firefox button always opens the popup menu + // even when the user clicks on the above edge of it. + nsIntMargin borderSize(std::max(nonClientSize.top, mVertResizeMargin), + std::max(nonClientSize.right, mHorResizeMargin), + std::max(nonClientSize.bottom, mVertResizeMargin), + std::max(nonClientSize.left, mHorResizeMargin)); + + bool top = false; + bool bottom = false; + bool left = false; + bool right = false; + + if (my >= winRect.top && my < winRect.top + borderSize.top) { + top = true; + } else if (my <= winRect.bottom && my > winRect.bottom - borderSize.bottom) { + bottom = true; + } + + // (the 2x case here doubles the resize area for corners) + int multiplier = (top || bottom) ? 2 : 1; + if (mx >= winRect.left && + mx < winRect.left + (multiplier * borderSize.left)) { + left = true; + } else if (mx <= winRect.right && + mx > winRect.right - (multiplier * borderSize.right)) { + right = true; + } + + if (isResizable) { + if (top) { + testResult = HTTOP; + if (left) + testResult = HTTOPLEFT; + else if (right) + testResult = HTTOPRIGHT; + } else if (bottom) { + testResult = HTBOTTOM; + if (left) + testResult = HTBOTTOMLEFT; + else if (right) + testResult = HTBOTTOMRIGHT; + } else { + if (left) + testResult = HTLEFT; + if (right) + testResult = HTRIGHT; + } + } else { + if (top) + testResult = HTCAPTION; + else if (bottom || left || right) + testResult = HTBORDER; + } + + if (!sIsInMouseCapture && allowContentOverride) { + POINT pt = { mx, my }; + ::ScreenToClient(mWnd, &pt); + if (pt.x == mCachedHitTestPoint.x && pt.y == mCachedHitTestPoint.y && + TimeStamp::Now() - mCachedHitTestTime < TimeDuration::FromMilliseconds(HITTEST_CACHE_LIFETIME_MS)) { + return mCachedHitTestResult; + } + if (mDraggableRegion.Contains(pt.x, pt.y)) { + testResult = HTCAPTION; + } else { + testResult = HTCLIENT; + } + mCachedHitTestPoint = pt; + mCachedHitTestTime = TimeStamp::Now(); + mCachedHitTestResult = testResult; + } + + return testResult; +} + +TimeStamp +nsWindow::GetMessageTimeStamp(LONG aEventTime) const +{ + CurrentWindowsTimeGetter getCurrentTime(mWnd); + return TimeConverter().GetTimeStampFromSystemTime(aEventTime, + getCurrentTime); +} + +void nsWindow::PostSleepWakeNotification(const bool aIsSleepMode) +{ + if (aIsSleepMode == gIsSleepMode) + return; + + gIsSleepMode = aIsSleepMode; + + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + if (observerService) + observerService->NotifyObservers(nullptr, + aIsSleepMode ? NS_WIDGET_SLEEP_OBSERVER_TOPIC : + NS_WIDGET_WAKE_OBSERVER_TOPIC, nullptr); +} + +LRESULT nsWindow::ProcessCharMessage(const MSG &aMsg, bool *aEventDispatched) +{ + if (IMEHandler::IsComposingOn(this)) { + IMEHandler::NotifyIME(this, REQUEST_TO_COMMIT_COMPOSITION); + } + // These must be checked here too as a lone WM_CHAR could be received + // if a child window didn't handle it (for example Alt+Space in a content + // window) + ModifierKeyState modKeyState; + NativeKey nativeKey(this, aMsg, modKeyState); + return static_cast<LRESULT>(nativeKey.HandleCharMessage(aEventDispatched)); +} + +LRESULT nsWindow::ProcessKeyUpMessage(const MSG &aMsg, bool *aEventDispatched) +{ + ModifierKeyState modKeyState; + NativeKey nativeKey(this, aMsg, modKeyState); + return static_cast<LRESULT>(nativeKey.HandleKeyUpMessage(aEventDispatched)); +} + +LRESULT nsWindow::ProcessKeyDownMessage(const MSG &aMsg, + bool *aEventDispatched) +{ + // If this method doesn't call NativeKey::HandleKeyDownMessage(), this method + // must clean up the redirected message information itself. For more + // information, see above comment of + // RedirectedKeyDownMessageManager::AutoFlusher class definition in + // KeyboardLayout.h. + RedirectedKeyDownMessageManager::AutoFlusher redirectedMsgFlusher(this, aMsg); + + ModifierKeyState modKeyState; + + NativeKey nativeKey(this, aMsg, modKeyState); + LRESULT result = + static_cast<LRESULT>(nativeKey.HandleKeyDownMessage(aEventDispatched)); + // HandleKeyDownMessage cleaned up the redirected message information + // itself, so, we should do nothing. + redirectedMsgFlusher.Cancel(); + + if (aMsg.wParam == VK_MENU || + (aMsg.wParam == VK_F10 && !modKeyState.IsShift())) { + // We need to let Windows handle this keypress, + // by returning false, if there's a native menu + // bar somewhere in our containing window hierarchy. + // Otherwise we handle the keypress and don't pass + // it on to Windows, by returning true. + bool hasNativeMenu = false; + HWND hWnd = mWnd; + while (hWnd) { + if (::GetMenu(hWnd)) { + hasNativeMenu = true; + break; + } + hWnd = ::GetParent(hWnd); + } + result = !hasNativeMenu; + } + + return result; +} + +nsresult +nsWindow::SynthesizeNativeKeyEvent(int32_t aNativeKeyboardLayout, + int32_t aNativeKeyCode, + uint32_t aModifierFlags, + const nsAString& aCharacters, + const nsAString& aUnmodifiedCharacters, + nsIObserver* aObserver) +{ + AutoObserverNotifier notifier(aObserver, "keyevent"); + + KeyboardLayout* keyboardLayout = KeyboardLayout::GetInstance(); + return keyboardLayout->SynthesizeNativeKeyEvent( + this, aNativeKeyboardLayout, aNativeKeyCode, aModifierFlags, + aCharacters, aUnmodifiedCharacters); +} + +nsresult +nsWindow::SynthesizeNativeMouseEvent(LayoutDeviceIntPoint aPoint, + uint32_t aNativeMessage, + uint32_t aModifierFlags, + nsIObserver* aObserver) +{ + AutoObserverNotifier notifier(aObserver, "mouseevent"); + + if (aNativeMessage == MOUSEEVENTF_MOVE) { + // Reset sLastMouseMovePoint so that even if we're moving the mouse + // to the position it's already at, we still dispatch a mousemove + // event, because the callers of this function expect that. + sLastMouseMovePoint = {0}; + } + ::SetCursorPos(aPoint.x, aPoint.y); + + INPUT input; + memset(&input, 0, sizeof(input)); + + input.type = INPUT_MOUSE; + input.mi.dwFlags = aNativeMessage; + ::SendInput(1, &input, sizeof(INPUT)); + + return NS_OK; +} + +nsresult +nsWindow::SynthesizeNativeMouseScrollEvent(LayoutDeviceIntPoint aPoint, + uint32_t aNativeMessage, + double aDeltaX, + double aDeltaY, + double aDeltaZ, + uint32_t aModifierFlags, + uint32_t aAdditionalFlags, + nsIObserver* aObserver) +{ + AutoObserverNotifier notifier(aObserver, "mousescrollevent"); + return MouseScrollHandler::SynthesizeNativeMouseScrollEvent( + this, aPoint, aNativeMessage, + (aNativeMessage == WM_MOUSEWHEEL || aNativeMessage == WM_VSCROLL) ? + static_cast<int32_t>(aDeltaY) : static_cast<int32_t>(aDeltaX), + aModifierFlags, aAdditionalFlags); +} + +/************************************************************** + * + * SECTION: OnXXX message handlers + * + * For message handlers that need to be broken out or + * implemented in specific platform code. + * + **************************************************************/ + +void nsWindow::OnWindowPosChanged(WINDOWPOS* wp) +{ + if (wp == nullptr) + return; + +#ifdef WINSTATE_DEBUG_OUTPUT + if (mWnd == WinUtils::GetTopLevelHWND(mWnd)) { + MOZ_LOG(gWindowsLog, LogLevel::Info, ("*** OnWindowPosChanged: [ top] ")); + } else { + MOZ_LOG(gWindowsLog, LogLevel::Info, ("*** OnWindowPosChanged: [child] ")); + } + MOZ_LOG(gWindowsLog, LogLevel::Info, ("WINDOWPOS flags:")); + if (wp->flags & SWP_FRAMECHANGED) { + MOZ_LOG(gWindowsLog, LogLevel::Info, ("SWP_FRAMECHANGED ")); + } + if (wp->flags & SWP_SHOWWINDOW) { + MOZ_LOG(gWindowsLog, LogLevel::Info, ("SWP_SHOWWINDOW ")); + } + if (wp->flags & SWP_NOSIZE) { + MOZ_LOG(gWindowsLog, LogLevel::Info, ("SWP_NOSIZE ")); + } + if (wp->flags & SWP_HIDEWINDOW) { + MOZ_LOG(gWindowsLog, LogLevel::Info, ("SWP_HIDEWINDOW ")); + } + if (wp->flags & SWP_NOZORDER) { + MOZ_LOG(gWindowsLog, LogLevel::Info, ("SWP_NOZORDER ")); + } + if (wp->flags & SWP_NOACTIVATE) { + MOZ_LOG(gWindowsLog, LogLevel::Info, ("SWP_NOACTIVATE ")); + } + MOZ_LOG(gWindowsLog, LogLevel::Info, ("\n")); +#endif + + // Handle window size mode changes + if (wp->flags & SWP_FRAMECHANGED && mSizeMode != nsSizeMode_Fullscreen) { + + // Bug 566135 - Windows theme code calls show window on SW_SHOWMINIMIZED + // windows when fullscreen games disable desktop composition. If we're + // minimized and not being activated, ignore the event and let windows + // handle it. + if (mSizeMode == nsSizeMode_Minimized && (wp->flags & SWP_NOACTIVATE)) + return; + + WINDOWPLACEMENT pl; + pl.length = sizeof(pl); + ::GetWindowPlacement(mWnd, &pl); + + nsSizeMode previousSizeMode = mSizeMode; + + // Windows has just changed the size mode of this window. The call to + // SizeModeChanged will trigger a call into SetSizeMode where we will + // set the min/max window state again or for nsSizeMode_Normal, call + // SetWindow with a parameter of SW_RESTORE. There's no need however as + // this window's mode has already changed. Updating mSizeMode here + // insures the SetSizeMode call is a no-op. Addresses a bug on Win7 related + // to window docking. (bug 489258) + if (pl.showCmd == SW_SHOWMAXIMIZED) + mSizeMode = (mFullscreenMode ? nsSizeMode_Fullscreen : nsSizeMode_Maximized); + else if (pl.showCmd == SW_SHOWMINIMIZED) + mSizeMode = nsSizeMode_Minimized; + else if (mFullscreenMode) + mSizeMode = nsSizeMode_Fullscreen; + else + mSizeMode = nsSizeMode_Normal; + + // If !sTrimOnMinimize, we minimize windows using SW_SHOWMINIMIZED (See + // SetSizeMode for internal calls, and WM_SYSCOMMAND for external). This + // prevents the working set from being trimmed but keeps the window active. + // After the window is minimized, we need to do some touch up work on the + // active window. (bugs 76831 & 499816) + if (!sTrimOnMinimize && nsSizeMode_Minimized == mSizeMode) + ActivateOtherWindowHelper(mWnd); + +#ifdef WINSTATE_DEBUG_OUTPUT + switch (mSizeMode) { + case nsSizeMode_Normal: + MOZ_LOG(gWindowsLog, LogLevel::Info, + ("*** mSizeMode: nsSizeMode_Normal\n")); + break; + case nsSizeMode_Minimized: + MOZ_LOG(gWindowsLog, LogLevel::Info, + ("*** mSizeMode: nsSizeMode_Minimized\n")); + break; + case nsSizeMode_Maximized: + MOZ_LOG(gWindowsLog, LogLevel::Info, + ("*** mSizeMode: nsSizeMode_Maximized\n")); + break; + default: + MOZ_LOG(gWindowsLog, LogLevel::Info, ("*** mSizeMode: ??????\n")); + break; + } +#endif + + if (mWidgetListener && mSizeMode != previousSizeMode) + mWidgetListener->SizeModeChanged(mSizeMode); + + // If window was restored, window activation was bypassed during the + // SetSizeMode call originating from OnWindowPosChanging to avoid saving + // pre-restore attributes. Force activation now to get correct attributes. + if (mLastSizeMode != nsSizeMode_Normal && mSizeMode == nsSizeMode_Normal) + DispatchFocusToTopLevelWindow(true); + + mLastSizeMode = mSizeMode; + + // Skip window size change events below on minimization. + if (mSizeMode == nsSizeMode_Minimized) + return; + } + + // Handle window position changes + if (!(wp->flags & SWP_NOMOVE)) { + mBounds.x = wp->x; + mBounds.y = wp->y; + + NotifyWindowMoved(wp->x, wp->y); + } + + // Handle window size changes + if (!(wp->flags & SWP_NOSIZE)) { + RECT r; + int32_t newWidth, newHeight; + + ::GetWindowRect(mWnd, &r); + + newWidth = r.right - r.left; + newHeight = r.bottom - r.top; + nsIntRect rect(wp->x, wp->y, newWidth, newHeight); + + if (newWidth > mLastSize.width) + { + RECT drect; + + // getting wider + drect.left = wp->x + mLastSize.width; + drect.top = wp->y; + drect.right = drect.left + (newWidth - mLastSize.width); + drect.bottom = drect.top + newHeight; + + ::RedrawWindow(mWnd, &drect, nullptr, + RDW_INVALIDATE | + RDW_NOERASE | + RDW_NOINTERNALPAINT | + RDW_ERASENOW | + RDW_ALLCHILDREN); + } + if (newHeight > mLastSize.height) + { + RECT drect; + + // getting taller + drect.left = wp->x; + drect.top = wp->y + mLastSize.height; + drect.right = drect.left + newWidth; + drect.bottom = drect.top + (newHeight - mLastSize.height); + + ::RedrawWindow(mWnd, &drect, nullptr, + RDW_INVALIDATE | + RDW_NOERASE | + RDW_NOINTERNALPAINT | + RDW_ERASENOW | + RDW_ALLCHILDREN); + } + + mBounds.width = newWidth; + mBounds.height = newHeight; + mLastSize.width = newWidth; + mLastSize.height = newHeight; + +#ifdef WINSTATE_DEBUG_OUTPUT + MOZ_LOG(gWindowsLog, LogLevel::Info, + ("*** Resize window: %d x %d x %d x %d\n", wp->x, wp->y, + newWidth, newHeight)); +#endif + + // If a maximized window is resized, recalculate the non-client margins. + if (mSizeMode == nsSizeMode_Maximized) { + if (UpdateNonClientMargins(nsSizeMode_Maximized, true)) { + // gecko resize event already sent by UpdateNonClientMargins. + return; + } + } + + // Recalculate the width and height based on the client area for gecko events. + if (::GetClientRect(mWnd, &r)) { + rect.width = r.right - r.left; + rect.height = r.bottom - r.top; + } + + // Send a gecko resize event + OnResize(rect); + } +} + +// static +void nsWindow::ActivateOtherWindowHelper(HWND aWnd) +{ + // Find the next window that is enabled, visible, and not minimized. + HWND hwndBelow = ::GetNextWindow(aWnd, GW_HWNDNEXT); + while (hwndBelow && (!::IsWindowEnabled(hwndBelow) || !::IsWindowVisible(hwndBelow) || + ::IsIconic(hwndBelow))) { + hwndBelow = ::GetNextWindow(hwndBelow, GW_HWNDNEXT); + } + + // Push ourselves to the bottom of the stack, then activate the + // next window. + ::SetWindowPos(aWnd, HWND_BOTTOM, 0, 0, 0, 0, + SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE); + if (hwndBelow) + ::SetForegroundWindow(hwndBelow); + + // Play the minimize sound while we're here, since that is also + // forgotten when we use SW_SHOWMINIMIZED. + nsCOMPtr<nsISound> sound(do_CreateInstance("@mozilla.org/sound;1")); + if (sound) { + sound->PlaySystemSound(NS_LITERAL_STRING("Minimize")); + } +} + +void nsWindow::OnWindowPosChanging(LPWINDOWPOS& info) +{ + // Update non-client margins if the frame size is changing, and let the + // browser know we are changing size modes, so alternative css can kick in. + // If we're going into fullscreen mode, ignore this, since it'll reset + // margins to normal mode. + if ((info->flags & SWP_FRAMECHANGED && !(info->flags & SWP_NOSIZE)) && + mSizeMode != nsSizeMode_Fullscreen) { + WINDOWPLACEMENT pl; + pl.length = sizeof(pl); + ::GetWindowPlacement(mWnd, &pl); + nsSizeMode sizeMode; + if (pl.showCmd == SW_SHOWMAXIMIZED) + sizeMode = (mFullscreenMode ? nsSizeMode_Fullscreen : nsSizeMode_Maximized); + else if (pl.showCmd == SW_SHOWMINIMIZED) + sizeMode = nsSizeMode_Minimized; + else if (mFullscreenMode) + sizeMode = nsSizeMode_Fullscreen; + else + sizeMode = nsSizeMode_Normal; + + if (mWidgetListener) + mWidgetListener->SizeModeChanged(sizeMode); + + UpdateNonClientMargins(sizeMode, false); + } + + // enforce local z-order rules + if (!(info->flags & SWP_NOZORDER)) { + HWND hwndAfter = info->hwndInsertAfter; + + nsWindow *aboveWindow = 0; + nsWindowZ placement; + + if (hwndAfter == HWND_BOTTOM) + placement = nsWindowZBottom; + else if (hwndAfter == HWND_TOP || hwndAfter == HWND_TOPMOST || hwndAfter == HWND_NOTOPMOST) + placement = nsWindowZTop; + else { + placement = nsWindowZRelative; + aboveWindow = WinUtils::GetNSWindowPtr(hwndAfter); + } + + if (mWidgetListener) { + nsCOMPtr<nsIWidget> actualBelow = nullptr; + if (mWidgetListener->ZLevelChanged(false, &placement, + aboveWindow, getter_AddRefs(actualBelow))) { + if (placement == nsWindowZBottom) + info->hwndInsertAfter = HWND_BOTTOM; + else if (placement == nsWindowZTop) + info->hwndInsertAfter = HWND_TOP; + else { + info->hwndInsertAfter = (HWND)actualBelow->GetNativeData(NS_NATIVE_WINDOW); + } + } + } + } + // prevent rude external programs from making hidden window visible + if (mWindowType == eWindowType_invisible) + info->flags &= ~SWP_SHOWWINDOW; +} + +void nsWindow::UserActivity() +{ + // Check if we have the idle service, if not we try to get it. + if (!mIdleService) { + mIdleService = do_GetService("@mozilla.org/widget/idleservice;1"); + } + + // Check that we now have the idle service. + if (mIdleService) { + mIdleService->ResetIdleTimeOut(0); + } +} + +bool nsWindow::OnTouch(WPARAM wParam, LPARAM lParam) +{ + uint32_t cInputs = LOWORD(wParam); + PTOUCHINPUT pInputs = new TOUCHINPUT[cInputs]; + + if (mGesture.GetTouchInputInfo((HTOUCHINPUT)lParam, cInputs, pInputs)) { + MultiTouchInput touchInput, touchEndInput; + + // Walk across the touch point array processing each contact point. + for (uint32_t i = 0; i < cInputs; i++) { + bool addToEvent = false, addToEndEvent = false; + + // N.B.: According with MS documentation + // https://msdn.microsoft.com/en-us/library/windows/desktop/dd317334(v=vs.85).aspx + // TOUCHEVENTF_DOWN cannot be combined with TOUCHEVENTF_MOVE or TOUCHEVENTF_UP. + // Possibly, it means that TOUCHEVENTF_MOVE and TOUCHEVENTF_UP can be combined together. + + if (pInputs[i].dwFlags & (TOUCHEVENTF_DOWN | TOUCHEVENTF_MOVE)) { + if (touchInput.mTimeStamp.IsNull()) { + // Initialize a touch event to send. + touchInput.mType = MultiTouchInput::MULTITOUCH_MOVE; + touchInput.mTime = ::GetMessageTime(); + touchInput.mTimeStamp = GetMessageTimeStamp(touchInput.mTime); + ModifierKeyState modifierKeyState; + touchInput.modifiers = modifierKeyState.GetModifiers(); + } + // Pres shell expects this event to be a eTouchStart + // if any new contact points have been added since the last event sent. + if (pInputs[i].dwFlags & TOUCHEVENTF_DOWN) { + touchInput.mType = MultiTouchInput::MULTITOUCH_START; + } + addToEvent = true; + } + if (pInputs[i].dwFlags & TOUCHEVENTF_UP) { + // Pres shell expects removed contacts points to be delivered in a separate + // eTouchEnd event containing only the contact points that were removed. + if (touchEndInput.mTimeStamp.IsNull()) { + // Initialize a touch event to send. + touchEndInput.mType = MultiTouchInput::MULTITOUCH_END; + touchEndInput.mTime = ::GetMessageTime(); + touchEndInput.mTimeStamp = GetMessageTimeStamp(touchEndInput.mTime); + ModifierKeyState modifierKeyState; + touchEndInput.modifiers = modifierKeyState.GetModifiers(); + } + addToEndEvent = true; + } + if (!addToEvent && !addToEndEvent) { + // Filter out spurious Windows events we don't understand, like palm contact. + continue; + } + + // Setup the touch point we'll append to the touch event array. + nsPointWin touchPoint; + touchPoint.x = TOUCH_COORD_TO_PIXEL(pInputs[i].x); + touchPoint.y = TOUCH_COORD_TO_PIXEL(pInputs[i].y); + touchPoint.ScreenToClient(mWnd); + + // Initialize the touch data. + SingleTouchData touchData(pInputs[i].dwID, // aIdentifier + ScreenIntPoint::FromUnknownPoint(touchPoint), // aScreenPoint + /* radius, if known */ + pInputs[i].dwFlags & TOUCHINPUTMASKF_CONTACTAREA + ? ScreenSize( + TOUCH_COORD_TO_PIXEL(pInputs[i].cxContact) / 2, + TOUCH_COORD_TO_PIXEL(pInputs[i].cyContact) / 2) + : ScreenSize(1, 1), // aRadius + 0.0f, // aRotationAngle + 0.0f); // aForce + + // Append touch data to the appropriate event. + if (addToEvent) { + touchInput.mTouches.AppendElement(touchData); + } + if (addToEndEvent) { + touchEndInput.mTouches.AppendElement(touchData); + } + } + + // Dispatch touch start and touch move event if we have one. + if (!touchInput.mTimeStamp.IsNull()) { + DispatchTouchInput(touchInput); + } + // Dispatch touch end event if we have one. + if (!touchEndInput.mTimeStamp.IsNull()) { + DispatchTouchInput(touchEndInput); + } + } + + delete [] pInputs; + mGesture.CloseTouchInputHandle((HTOUCHINPUT)lParam); + return true; +} + +// Gesture event processing. Handles WM_GESTURE events. +bool nsWindow::OnGesture(WPARAM wParam, LPARAM lParam) +{ + // Treatment for pan events which translate into scroll events: + if (mGesture.IsPanEvent(lParam)) { + if ( !mGesture.ProcessPanMessage(mWnd, wParam, lParam) ) + return false; // ignore + + nsEventStatus status; + + WidgetWheelEvent wheelEvent(true, eWheel, this); + + ModifierKeyState modifierKeyState; + modifierKeyState.InitInputEvent(wheelEvent); + + wheelEvent.button = 0; + wheelEvent.mTime = ::GetMessageTime(); + wheelEvent.mTimeStamp = GetMessageTimeStamp(wheelEvent.mTime); + wheelEvent.inputSource = nsIDOMMouseEvent::MOZ_SOURCE_TOUCH; + + bool endFeedback = true; + + if (mGesture.PanDeltaToPixelScroll(wheelEvent)) { + mozilla::Telemetry::Accumulate(mozilla::Telemetry::SCROLL_INPUT_METHODS, + (uint32_t) ScrollInputMethod::MainThreadTouch); + DispatchEvent(&wheelEvent, status); + } + + if (mDisplayPanFeedback) { + mGesture.UpdatePanFeedbackX( + mWnd, + DeprecatedAbs(RoundDown(wheelEvent.mOverflowDeltaX)), + endFeedback); + mGesture.UpdatePanFeedbackY( + mWnd, + DeprecatedAbs(RoundDown(wheelEvent.mOverflowDeltaY)), + endFeedback); + mGesture.PanFeedbackFinalize(mWnd, endFeedback); + } + + mGesture.CloseGestureInfoHandle((HGESTUREINFO)lParam); + + return true; + } + + // Other gestures translate into simple gesture events: + WidgetSimpleGestureEvent event(true, eVoidEvent, this); + if ( !mGesture.ProcessGestureMessage(mWnd, wParam, lParam, event) ) { + return false; // fall through to DefWndProc + } + + // Polish up and send off the new event + ModifierKeyState modifierKeyState; + modifierKeyState.InitInputEvent(event); + event.button = 0; + event.mTime = ::GetMessageTime(); + event.mTimeStamp = GetMessageTimeStamp(event.mTime); + event.inputSource = nsIDOMMouseEvent::MOZ_SOURCE_TOUCH; + + nsEventStatus status; + DispatchEvent(&event, status); + if (status == nsEventStatus_eIgnore) { + return false; // Ignored, fall through + } + + // Only close this if we process and return true. + mGesture.CloseGestureInfoHandle((HGESTUREINFO)lParam); + + return true; // Handled +} + +nsresult +nsWindow::ConfigureChildren(const nsTArray<Configuration>& aConfigurations) +{ + // If this is a remotely updated widget we receive clipping, position, and + // size information from a source other than our owner. Don't let our parent + // update this information. + if (mWindowType == eWindowType_plugin_ipc_chrome) { + return NS_OK; + } + + // XXXroc we could use BeginDeferWindowPos/DeferWindowPos/EndDeferWindowPos + // here, if that helps in some situations. So far I haven't seen a + // need. + for (uint32_t i = 0; i < aConfigurations.Length(); ++i) { + const Configuration& configuration = aConfigurations[i]; + nsWindow* w = static_cast<nsWindow*>(configuration.mChild.get()); + NS_ASSERTION(w->GetParent() == this, + "Configured widget is not a child"); + nsresult rv = w->SetWindowClipRegion(configuration.mClipRegion, true); + NS_ENSURE_SUCCESS(rv, rv); + LayoutDeviceIntRect bounds = w->GetBounds(); + if (bounds.Size() != configuration.mBounds.Size()) { + w->Resize(configuration.mBounds.x, configuration.mBounds.y, + configuration.mBounds.width, configuration.mBounds.height, + true); + } else if (bounds.TopLeft() != configuration.mBounds.TopLeft()) { + w->Move(configuration.mBounds.x, configuration.mBounds.y); + + + if (gfxWindowsPlatform::GetPlatform()->IsDirect2DBackend() || + GetLayerManager()->GetBackendType() != LayersBackend::LAYERS_BASIC) { + // XXX - Workaround for Bug 587508. This will invalidate the part of the + // plugin window that might be touched by moving content somehow. The + // underlying problem should be found and fixed! + LayoutDeviceIntRegion r; + r.Sub(bounds, configuration.mBounds); + r.MoveBy(-bounds.x, + -bounds.y); + LayoutDeviceIntRect toInvalidate = r.GetBounds(); + + WinUtils::InvalidatePluginAsWorkaround(w, toInvalidate); + } + } + rv = w->SetWindowClipRegion(configuration.mClipRegion, false); + NS_ENSURE_SUCCESS(rv, rv); + } + return NS_OK; +} + +static HRGN +CreateHRGNFromArray(const nsTArray<LayoutDeviceIntRect>& aRects) +{ + int32_t size = sizeof(RGNDATAHEADER) + sizeof(RECT)*aRects.Length(); + AutoTArray<uint8_t,100> buf; + buf.SetLength(size); + RGNDATA* data = reinterpret_cast<RGNDATA*>(buf.Elements()); + RECT* rects = reinterpret_cast<RECT*>(data->Buffer); + data->rdh.dwSize = sizeof(data->rdh); + data->rdh.iType = RDH_RECTANGLES; + data->rdh.nCount = aRects.Length(); + LayoutDeviceIntRect bounds; + for (uint32_t i = 0; i < aRects.Length(); ++i) { + const LayoutDeviceIntRect& r = aRects[i]; + bounds.UnionRect(bounds, r); + ::SetRect(&rects[i], r.x, r.y, r.XMost(), r.YMost()); + } + ::SetRect(&data->rdh.rcBound, bounds.x, bounds.y, bounds.XMost(), bounds.YMost()); + return ::ExtCreateRegion(nullptr, buf.Length(), data); +} + +nsresult +nsWindow::SetWindowClipRegion(const nsTArray<LayoutDeviceIntRect>& aRects, + bool aIntersectWithExisting) +{ + if (IsWindowClipRegionEqual(aRects)) { + return NS_OK; + } + + nsBaseWidget::SetWindowClipRegion(aRects, aIntersectWithExisting); + + HRGN dest = CreateHRGNFromArray(aRects); + if (!dest) + return NS_ERROR_OUT_OF_MEMORY; + + if (aIntersectWithExisting) { + HRGN current = ::CreateRectRgn(0, 0, 0, 0); + if (current) { + if (::GetWindowRgn(mWnd, current) != 0 /*ERROR*/) { + ::CombineRgn(dest, dest, current, RGN_AND); + } + ::DeleteObject(current); + } + } + + // If a plugin is not visible, especially if it is in a background tab, + // it should not be able to steal keyboard focus. This code checks whether + // the region that the plugin is being clipped to is NULLREGION. If it is, + // the plugin window gets disabled. + if (IsPlugin()) { + if (NULLREGION == ::CombineRgn(dest, dest, dest, RGN_OR)) { + ::ShowWindow(mWnd, SW_HIDE); + ::EnableWindow(mWnd, FALSE); + } else { + ::EnableWindow(mWnd, TRUE); + ::ShowWindow(mWnd, SW_SHOW); + } + } + if (!::SetWindowRgn(mWnd, dest, TRUE)) { + ::DeleteObject(dest); + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +// WM_DESTROY event handler +void nsWindow::OnDestroy() +{ + mOnDestroyCalled = true; + + // Make sure we don't get destroyed in the process of tearing down. + nsCOMPtr<nsIWidget> kungFuDeathGrip(this); + + // Dispatch the destroy notification. + if (!mInDtor) + NotifyWindowDestroyed(); + + // Prevent the widget from sending additional events. + mWidgetListener = nullptr; + mAttachedWidgetListener = nullptr; + + // Unregister notifications from terminal services + ::WTSUnRegisterSessionNotification(mWnd); + + // Free our subclass and clear |this| stored in the window props. We will no longer + // receive events from Windows after this point. + SubclassWindow(FALSE); + + // Once mWidgetListener is cleared and the subclass is reset, sCurrentWindow can be + // cleared. (It's used in tracking windows for mouse events.) + if (sCurrentWindow == this) + sCurrentWindow = nullptr; + + // Disconnects us from our parent, will call our GetParent(). + nsBaseWidget::Destroy(); + + // Release references to children, device context, toolkit, and app shell. + nsBaseWidget::OnDestroy(); + + // Clear our native parent handle. + // XXX Windows will take care of this in the proper order, and SetParent(nullptr)'s + // remove child on the parent already took place in nsBaseWidget's Destroy call above. + //SetParent(nullptr); + mParent = nullptr; + + // We have to destroy the native drag target before we null out our window pointer. + EnableDragDrop(false); + + // If we're going away and for some reason we're still the rollup widget, rollup and + // turn off capture. + nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener(); + nsCOMPtr<nsIWidget> rollupWidget; + if (rollupListener) { + rollupWidget = rollupListener->GetRollupWidget(); + } + if (this == rollupWidget) { + if ( rollupListener ) + rollupListener->Rollup(0, false, nullptr, nullptr); + CaptureRollupEvents(nullptr, false); + } + + IMEHandler::OnDestroyWindow(this); + + // Free GDI window class objects + if (mBrush) { + VERIFY(::DeleteObject(mBrush)); + mBrush = nullptr; + } + + // Destroy any custom cursor resources. + if (mCursor == -1) + SetCursor(eCursor_standard); + + if (mCompositorWidgetDelegate) { + mCompositorWidgetDelegate->OnDestroyWindow(); + } + mBasicLayersSurface = nullptr; + + // Finalize panning feedback to possibly restore window displacement + mGesture.PanFeedbackFinalize(mWnd, true); + + // Clear the main HWND. + mWnd = nullptr; +} + +// Send a resize message to the listener +bool nsWindow::OnResize(nsIntRect &aWindowRect) +{ + bool result = mWidgetListener ? + mWidgetListener->WindowResized(this, aWindowRect.width, aWindowRect.height) : false; + + // If there is an attached view, inform it as well as the normal widget listener. + if (mAttachedWidgetListener) { + return mAttachedWidgetListener->WindowResized(this, aWindowRect.width, aWindowRect.height); + } + + return result; +} + +bool nsWindow::OnHotKey(WPARAM wParam, LPARAM lParam) +{ + return true; +} + +// Can be overriden. Controls auto-erase of background. +bool nsWindow::AutoErase(HDC dc) +{ + return false; +} + +bool +nsWindow::IsPopup() +{ + return mWindowType == eWindowType_popup; +} + +bool +nsWindow::ShouldUseOffMainThreadCompositing() +{ + // We don't currently support using an accelerated layer manager with + // transparent windows so don't even try. I'm also not sure if we even + // want to support this case. See bug 593471 + if (mTransparencyMode == eTransparencyTransparent) { + return false; + } + + return nsBaseWidget::ShouldUseOffMainThreadCompositing(); +} + +void +nsWindow::WindowUsesOMTC() +{ + ULONG_PTR style = ::GetClassLongPtr(mWnd, GCL_STYLE); + if (!style) { + NS_WARNING("Could not get window class style"); + return; + } + style |= CS_HREDRAW | CS_VREDRAW; + DebugOnly<ULONG_PTR> result = ::SetClassLongPtr(mWnd, GCL_STYLE, style); + NS_WARNING_ASSERTION(result, "Could not reset window class style"); +} + +bool +nsWindow::HasBogusPopupsDropShadowOnMultiMonitor() { + if (sHasBogusPopupsDropShadowOnMultiMonitor == TRI_UNKNOWN) { + // Since any change in the preferences requires a restart, this can be + // done just once. + // Check for Direct2D first. + sHasBogusPopupsDropShadowOnMultiMonitor = + gfxWindowsPlatform::GetPlatform()->IsDirect2DBackend() ? TRI_TRUE : TRI_FALSE; + if (!sHasBogusPopupsDropShadowOnMultiMonitor) { + // Otherwise check if Direct3D 9 may be used. + if (gfxConfig::IsEnabled(Feature::HW_COMPOSITING) && + !gfxConfig::IsEnabled(Feature::OPENGL_COMPOSITING)) + { + nsCOMPtr<nsIGfxInfo> gfxInfo = services::GetGfxInfo(); + if (gfxInfo) { + int32_t status; + nsCString discardFailureId; + if (NS_SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_DIRECT3D_9_LAYERS, + discardFailureId, &status))) { + if (status == nsIGfxInfo::FEATURE_STATUS_OK || + gfxConfig::IsForcedOnByUser(Feature::HW_COMPOSITING)) + { + sHasBogusPopupsDropShadowOnMultiMonitor = TRI_TRUE; + } + } + } + } + } + } + return !!sHasBogusPopupsDropShadowOnMultiMonitor; +} + +void +nsWindow::OnSysColorChanged() +{ + if (mWindowType == eWindowType_invisible) { + ::EnumThreadWindows(GetCurrentThreadId(), nsWindow::BroadcastMsg, WM_SYSCOLORCHANGE); + } + else { + // Note: This is sent for child windows as well as top-level windows. + // The Win32 toolkit normally only sends these events to top-level windows. + // But we cycle through all of the childwindows and send it to them as well + // so all presentations get notified properly. + // See nsWindow::GlobalMsgWindowProc. + NotifySysColorChanged(); + } +} + +void +nsWindow::OnDPIChanged(int32_t x, int32_t y, int32_t width, int32_t height) +{ + // Don't try to handle WM_DPICHANGED for popup windows (see bug 1239353); + // they remain tied to their original parent's resolution. + if (mWindowType == eWindowType_popup) { + return; + } + if (DefaultScaleOverride() > 0.0) { + return; + } + double oldScale = mDefaultScale; + mDefaultScale = -1.0; // force recomputation of scale factor + double newScale = GetDefaultScaleInternal(); + + if (mResizeState != RESIZING && mSizeMode == nsSizeMode_Normal) { + // Limit the position (if not in the middle of a drag-move) & size, + // if it would overflow the destination screen + nsCOMPtr<nsIScreenManager> sm = do_GetService(sScreenManagerContractID); + if (sm) { + nsCOMPtr<nsIScreen> screen; + sm->ScreenForRect(x, y, width, height, getter_AddRefs(screen)); + if (screen) { + int32_t availLeft, availTop, availWidth, availHeight; + screen->GetAvailRect(&availLeft, &availTop, &availWidth, &availHeight); + if (mResizeState != MOVING) { + x = std::max(x, availLeft); + y = std::max(y, availTop); + } + width = std::min(width, availWidth); + height = std::min(height, availHeight); + } + } + + Resize(x, y, width, height, true); + } + ChangedDPI(); + ResetLayout(); +} + +/************************************************************** + ************************************************************** + ** + ** BLOCK: IME management and accessibility + ** + ** Handles managing IME input and accessibility. + ** + ************************************************************** + **************************************************************/ + +NS_IMETHODIMP_(void) +nsWindow::SetInputContext(const InputContext& aContext, + const InputContextAction& aAction) +{ + InputContext newInputContext = aContext; + IMEHandler::SetInputContext(this, newInputContext, aAction); + mInputContext = newInputContext; +} + +NS_IMETHODIMP_(InputContext) +nsWindow::GetInputContext() +{ + mInputContext.mIMEState.mOpen = IMEState::CLOSED; + if (WinUtils::IsIMEEnabled(mInputContext) && IMEHandler::GetOpenState(this)) { + mInputContext.mIMEState.mOpen = IMEState::OPEN; + } else { + mInputContext.mIMEState.mOpen = IMEState::CLOSED; + } + return mInputContext; +} + +nsIMEUpdatePreference +nsWindow::GetIMEUpdatePreference() +{ + return IMEHandler::GetUpdatePreference(); +} + +NS_IMETHODIMP_(TextEventDispatcherListener*) +nsWindow::GetNativeTextEventDispatcherListener() +{ + return IMEHandler::GetNativeTextEventDispatcherListener(); +} + +#ifdef ACCESSIBILITY +#ifdef DEBUG +#define NS_LOG_WMGETOBJECT(aWnd, aHwnd, aAcc) \ + if (a11y::logging::IsEnabled(a11y::logging::ePlatforms)) { \ + printf("Get the window:\n {\n HWND: %p, parent HWND: %p, wndobj: %p,\n",\ + aHwnd, ::GetParent(aHwnd), aWnd); \ + printf(" acc: %p", aAcc); \ + if (aAcc) { \ + nsAutoString name; \ + aAcc->Name(name); \ + printf(", accname: %s", NS_ConvertUTF16toUTF8(name).get()); \ + } \ + printf("\n }\n"); \ + } + +#else +#define NS_LOG_WMGETOBJECT(aWnd, aHwnd, aAcc) +#endif + +a11y::Accessible* +nsWindow::GetAccessible() +{ + // If the pref was ePlatformIsDisabled, return null here, disabling a11y. + if (a11y::PlatformDisabledState() == a11y::ePlatformIsDisabled) + return nullptr; + + if (mInDtor || mOnDestroyCalled || mWindowType == eWindowType_invisible) { + return nullptr; + } + + // In case of popup window return a popup accessible. + nsView* view = nsView::GetViewFor(this); + if (view) { + nsIFrame* frame = view->GetFrame(); + if (frame && nsLayoutUtils::IsPopup(frame)) { + nsAccessibilityService* accService = GetOrCreateAccService(); + if (accService) { + a11y::DocAccessible* docAcc = + GetAccService()->GetDocAccessible(frame->PresContext()->PresShell()); + if (docAcc) { + NS_LOG_WMGETOBJECT(this, mWnd, + docAcc->GetAccessibleOrDescendant(frame->GetContent())); + return docAcc->GetAccessibleOrDescendant(frame->GetContent()); + } + } + } + } + + // otherwise root document accessible. + NS_LOG_WMGETOBJECT(this, mWnd, GetRootAccessible()); + return GetRootAccessible(); +} +#endif + +/************************************************************** + ************************************************************** + ** + ** BLOCK: Transparency + ** + ** Window transparency helpers. + ** + ************************************************************** + **************************************************************/ + +#ifdef MOZ_XUL + +void nsWindow::SetWindowTranslucencyInner(nsTransparencyMode aMode) +{ + if (aMode == mTransparencyMode) + return; + + // stop on dialogs and popups! + HWND hWnd = WinUtils::GetTopLevelHWND(mWnd, true); + nsWindow* parent = WinUtils::GetNSWindowPtr(hWnd); + + if (!parent) + { + NS_WARNING("Trying to use transparent chrome in an embedded context"); + return; + } + + if (parent != this) { + NS_WARNING("Setting SetWindowTranslucencyInner on a parent this is not us!"); + } + + if (aMode == eTransparencyTransparent) { + // If we're switching to the use of a transparent window, hide the chrome + // on our parent. + HideWindowChrome(true); + } else if (mHideChrome && mTransparencyMode == eTransparencyTransparent) { + // if we're switching out of transparent, re-enable our parent's chrome. + HideWindowChrome(false); + } + + LONG_PTR style = ::GetWindowLongPtrW(hWnd, GWL_STYLE), + exStyle = ::GetWindowLongPtr(hWnd, GWL_EXSTYLE); + + if (parent->mIsVisible) + style |= WS_VISIBLE; + if (parent->mSizeMode == nsSizeMode_Maximized) + style |= WS_MAXIMIZE; + else if (parent->mSizeMode == nsSizeMode_Minimized) + style |= WS_MINIMIZE; + + if (aMode == eTransparencyTransparent) + exStyle |= WS_EX_LAYERED; + else + exStyle &= ~WS_EX_LAYERED; + + VERIFY_WINDOW_STYLE(style); + ::SetWindowLongPtrW(hWnd, GWL_STYLE, style); + ::SetWindowLongPtrW(hWnd, GWL_EXSTYLE, exStyle); + + if (HasGlass()) + memset(&mGlassMargins, 0, sizeof mGlassMargins); + mTransparencyMode = aMode; + + if (mCompositorWidgetDelegate) { + mCompositorWidgetDelegate->UpdateTransparency(aMode); + } + UpdateGlass(); +} + +#endif //MOZ_XUL + +/************************************************************** + ************************************************************** + ** + ** BLOCK: Popup rollup hooks + ** + ** Deals with CaptureRollup on popup windows. + ** + ************************************************************** + **************************************************************/ + +// Schedules a timer for a window, so we can rollup after processing the hook event +void nsWindow::ScheduleHookTimer(HWND aWnd, UINT aMsgId) +{ + // In some cases multiple hooks may be scheduled + // so ignore any other requests once one timer is scheduled + if (sHookTimerId == 0) { + // Remember the window handle and the message ID to be used later + sRollupMsgId = aMsgId; + sRollupMsgWnd = aWnd; + // Schedule native timer for doing the rollup after + // this event is done being processed + sHookTimerId = ::SetTimer(nullptr, 0, 0, (TIMERPROC)HookTimerForPopups); + NS_ASSERTION(sHookTimerId, "Timer couldn't be created."); + } +} + +#ifdef POPUP_ROLLUP_DEBUG_OUTPUT +int gLastMsgCode = 0; +extern MSGFEventMsgInfo gMSGFEvents[]; +#endif + +// Process Menu messages, rollup when popup is clicked. +LRESULT CALLBACK nsWindow::MozSpecialMsgFilter(int code, WPARAM wParam, LPARAM lParam) +{ +#ifdef POPUP_ROLLUP_DEBUG_OUTPUT + if (sProcessHook) { + MSG* pMsg = (MSG*)lParam; + + int inx = 0; + while (gMSGFEvents[inx].mId != code && gMSGFEvents[inx].mStr != nullptr) { + inx++; + } + if (code != gLastMsgCode) { + if (gMSGFEvents[inx].mId == code) { +#ifdef DEBUG + MOZ_LOG(gWindowsLog, LogLevel::Info, + ("MozSpecialMessageProc - code: 0x%X - %s hw: %p\n", + code, gMSGFEvents[inx].mStr, pMsg->hwnd)); +#endif + } else { +#ifdef DEBUG + MOZ_LOG(gWindowsLog, LogLevel::Info, + ("MozSpecialMessageProc - code: 0x%X - %d hw: %p\n", + code, gMSGFEvents[inx].mId, pMsg->hwnd)); +#endif + } + gLastMsgCode = code; + } + PrintEvent(pMsg->message, FALSE, FALSE); + } +#endif // #ifdef POPUP_ROLLUP_DEBUG_OUTPUT + + if (sProcessHook && code == MSGF_MENU) { + MSG* pMsg = (MSG*)lParam; + ScheduleHookTimer( pMsg->hwnd, pMsg->message); + } + + return ::CallNextHookEx(sMsgFilterHook, code, wParam, lParam); +} + +// Process all mouse messages. Roll up when a click is in a native window +// that doesn't have an nsIWidget. +LRESULT CALLBACK nsWindow::MozSpecialMouseProc(int code, WPARAM wParam, LPARAM lParam) +{ + if (sProcessHook) { + switch (WinUtils::GetNativeMessage(wParam)) { + case WM_LBUTTONDOWN: + case WM_RBUTTONDOWN: + case WM_MBUTTONDOWN: + case WM_MOUSEWHEEL: + case WM_MOUSEHWHEEL: + { + MOUSEHOOKSTRUCT* ms = (MOUSEHOOKSTRUCT*)lParam; + nsIWidget* mozWin = WinUtils::GetNSWindowPtr(ms->hwnd); + if (mozWin) { + // If this window is windowed plugin window, the mouse events are not + // sent to us. + if (static_cast<nsWindow*>(mozWin)->IsPlugin()) + ScheduleHookTimer(ms->hwnd, (UINT)wParam); + } else { + ScheduleHookTimer(ms->hwnd, (UINT)wParam); + } + break; + } + } + } + return ::CallNextHookEx(sCallMouseHook, code, wParam, lParam); +} + +// Process all messages. Roll up when the window is moving, or +// is resizing or when maximized or mininized. +LRESULT CALLBACK nsWindow::MozSpecialWndProc(int code, WPARAM wParam, LPARAM lParam) +{ +#ifdef POPUP_ROLLUP_DEBUG_OUTPUT + if (sProcessHook) { + CWPSTRUCT* cwpt = (CWPSTRUCT*)lParam; + PrintEvent(cwpt->message, FALSE, FALSE); + } +#endif + + if (sProcessHook) { + CWPSTRUCT* cwpt = (CWPSTRUCT*)lParam; + if (cwpt->message == WM_MOVING || + cwpt->message == WM_SIZING || + cwpt->message == WM_GETMINMAXINFO) { + ScheduleHookTimer(cwpt->hwnd, (UINT)cwpt->message); + } + } + + return ::CallNextHookEx(sCallProcHook, code, wParam, lParam); +} + +// Register the special "hooks" for dropdown processing. +void nsWindow::RegisterSpecialDropdownHooks() +{ + NS_ASSERTION(!sMsgFilterHook, "sMsgFilterHook must be NULL!"); + NS_ASSERTION(!sCallProcHook, "sCallProcHook must be NULL!"); + + DISPLAY_NMM_PRT("***************** Installing Msg Hooks ***************\n"); + + // Install msg hook for moving the window and resizing + if (!sMsgFilterHook) { + DISPLAY_NMM_PRT("***** Hooking sMsgFilterHook!\n"); + sMsgFilterHook = SetWindowsHookEx(WH_MSGFILTER, MozSpecialMsgFilter, + nullptr, GetCurrentThreadId()); +#ifdef POPUP_ROLLUP_DEBUG_OUTPUT + if (!sMsgFilterHook) { + MOZ_LOG(gWindowsLog, LogLevel::Info, + ("***** SetWindowsHookEx is NOT installed for WH_MSGFILTER!\n")); + } +#endif + } + + // Install msg hook for menus + if (!sCallProcHook) { + DISPLAY_NMM_PRT("***** Hooking sCallProcHook!\n"); + sCallProcHook = SetWindowsHookEx(WH_CALLWNDPROC, MozSpecialWndProc, + nullptr, GetCurrentThreadId()); +#ifdef POPUP_ROLLUP_DEBUG_OUTPUT + if (!sCallProcHook) { + MOZ_LOG(gWindowsLog, LogLevel::Info, + ("***** SetWindowsHookEx is NOT installed for WH_CALLWNDPROC!\n")); + } +#endif + } + + // Install msg hook for the mouse + if (!sCallMouseHook) { + DISPLAY_NMM_PRT("***** Hooking sCallMouseHook!\n"); + sCallMouseHook = SetWindowsHookEx(WH_MOUSE, MozSpecialMouseProc, + nullptr, GetCurrentThreadId()); +#ifdef POPUP_ROLLUP_DEBUG_OUTPUT + if (!sCallMouseHook) { + MOZ_LOG(gWindowsLog, LogLevel::Info, + ("***** SetWindowsHookEx is NOT installed for WH_MOUSE!\n")); + } +#endif + } +} + +// Unhook special message hooks for dropdowns. +void nsWindow::UnregisterSpecialDropdownHooks() +{ + DISPLAY_NMM_PRT("***************** De-installing Msg Hooks ***************\n"); + + if (sCallProcHook) { + DISPLAY_NMM_PRT("***** Unhooking sCallProcHook!\n"); + if (!::UnhookWindowsHookEx(sCallProcHook)) { + DISPLAY_NMM_PRT("***** UnhookWindowsHookEx failed for sCallProcHook!\n"); + } + sCallProcHook = nullptr; + } + + if (sMsgFilterHook) { + DISPLAY_NMM_PRT("***** Unhooking sMsgFilterHook!\n"); + if (!::UnhookWindowsHookEx(sMsgFilterHook)) { + DISPLAY_NMM_PRT("***** UnhookWindowsHookEx failed for sMsgFilterHook!\n"); + } + sMsgFilterHook = nullptr; + } + + if (sCallMouseHook) { + DISPLAY_NMM_PRT("***** Unhooking sCallMouseHook!\n"); + if (!::UnhookWindowsHookEx(sCallMouseHook)) { + DISPLAY_NMM_PRT("***** UnhookWindowsHookEx failed for sCallMouseHook!\n"); + } + sCallMouseHook = nullptr; + } +} + +// This timer is designed to only fire one time at most each time a "hook" function +// is used to rollup the dropdown. In some cases, the timer may be scheduled from the +// hook, but that hook event or a subsequent event may roll up the dropdown before +// this timer function is executed. +// +// For example, if an MFC control takes focus, the combobox will lose focus and rollup +// before this function fires. +VOID CALLBACK nsWindow::HookTimerForPopups(HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime) +{ + if (sHookTimerId != 0) { + // if the window is nullptr then we need to use the ID to kill the timer + BOOL status = ::KillTimer(nullptr, sHookTimerId); + NS_ASSERTION(status, "Hook Timer was not killed."); + sHookTimerId = 0; + } + + if (sRollupMsgId != 0) { + // Note: DealWithPopups does the check to make sure that the rollup widget is set. + LRESULT popupHandlingResult; + nsAutoRollup autoRollup; + DealWithPopups(sRollupMsgWnd, sRollupMsgId, 0, 0, &popupHandlingResult); + sRollupMsgId = 0; + sRollupMsgWnd = nullptr; + } +} + +BOOL CALLBACK nsWindow::ClearResourcesCallback(HWND aWnd, LPARAM aMsg) +{ + nsWindow *window = WinUtils::GetNSWindowPtr(aWnd); + if (window) { + window->ClearCachedResources(); + } + return TRUE; +} + +void +nsWindow::ClearCachedResources() +{ + if (mLayerManager && + mLayerManager->GetBackendType() == LayersBackend::LAYERS_BASIC) { + mLayerManager->ClearCachedResources(); + } + ::EnumChildWindows(mWnd, nsWindow::ClearResourcesCallback, 0); +} + +static bool IsDifferentThreadWindow(HWND aWnd) +{ + return ::GetCurrentThreadId() != ::GetWindowThreadProcessId(aWnd, nullptr); +} + +// static +bool +nsWindow::EventIsInsideWindow(nsWindow* aWindow) +{ + RECT r; + ::GetWindowRect(aWindow->mWnd, &r); + DWORD pos = ::GetMessagePos(); + POINT mp; + mp.x = GET_X_LPARAM(pos); + mp.y = GET_Y_LPARAM(pos); + + // was the event inside this window? + return static_cast<bool>(::PtInRect(&r, mp)); +} + +// static +bool +nsWindow::GetPopupsToRollup(nsIRollupListener* aRollupListener, + uint32_t* aPopupsToRollup) +{ + // If we're dealing with menus, we probably have submenus and we don't want + // to rollup some of them if the click is in a parent menu of the current + // submenu. + *aPopupsToRollup = UINT32_MAX; + AutoTArray<nsIWidget*, 5> widgetChain; + uint32_t sameTypeCount = + aRollupListener->GetSubmenuWidgetChain(&widgetChain); + for (uint32_t i = 0; i < widgetChain.Length(); ++i) { + nsIWidget* widget = widgetChain[i]; + if (EventIsInsideWindow(static_cast<nsWindow*>(widget))) { + // Don't roll up if the mouse event occurred within a menu of the + // same type. If the mouse event occurred in a menu higher than that, + // roll up, but pass the number of popups to Rollup so that only those + // of the same type close up. + if (i < sameTypeCount) { + return false; + } + + *aPopupsToRollup = sameTypeCount; + break; + } + } + return true; +} + +// static +bool +nsWindow::NeedsToHandleNCActivateDelayed(HWND aWnd) +{ + // While popup is open, popup window might be activated by other application. + // At this time, we need to take back focus to the previous window but it + // causes flickering its nonclient area because WM_NCACTIVATE comes before + // WM_ACTIVATE and we cannot know which window will take focus at receiving + // WM_NCACTIVATE. Therefore, we need a hack for preventing the flickerling. + // + // If non-popup window receives WM_NCACTIVATE at deactivating, default + // wndproc shouldn't handle it as deactivating. Instead, at receiving + // WM_ACTIVIATE after that, WM_NCACTIVATE should be sent again manually. + // This returns true if the window needs to handle WM_NCACTIVATE later. + + nsWindow* window = WinUtils::GetNSWindowPtr(aWnd); + return window && !window->IsPopup(); +} + +static bool +IsTouchSupportEnabled(HWND aWnd) +{ + nsWindow* topWindow = WinUtils::GetNSWindowPtr(WinUtils::GetTopLevelHWND(aWnd, true)); + return topWindow ? topWindow->IsTouchWindow() : false; +} + +// static +bool +nsWindow::DealWithPopups(HWND aWnd, UINT aMessage, + WPARAM aWParam, LPARAM aLParam, LRESULT* aResult) +{ + NS_ASSERTION(aResult, "Bad outResult"); + + // XXX Why do we use the return value of WM_MOUSEACTIVATE for all messages? + *aResult = MA_NOACTIVATE; + + if (!::IsWindowVisible(aWnd)) { + return false; + } + + nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener(); + NS_ENSURE_TRUE(rollupListener, false); + + nsCOMPtr<nsIWidget> popup = rollupListener->GetRollupWidget(); + if (!popup) { + return false; + } + + static bool sSendingNCACTIVATE = false; + static bool sPendingNCACTIVATE = false; + uint32_t popupsToRollup = UINT32_MAX; + + bool consumeRollupEvent = false; + + nsWindow* popupWindow = static_cast<nsWindow*>(popup.get()); + UINT nativeMessage = WinUtils::GetNativeMessage(aMessage); + switch (nativeMessage) { + case WM_TOUCH: + if (!IsTouchSupportEnabled(aWnd)) { + // If APZ is disabled, don't allow touch inputs to dismiss popups. The + // compatibility mouse events will do it instead. + return false; + } + MOZ_FALLTHROUGH; + case WM_LBUTTONDOWN: + case WM_RBUTTONDOWN: + case WM_MBUTTONDOWN: + case WM_NCLBUTTONDOWN: + case WM_NCRBUTTONDOWN: + case WM_NCMBUTTONDOWN: + if (nativeMessage != WM_TOUCH && + IsTouchSupportEnabled(aWnd) && + MOUSE_INPUT_SOURCE() == nsIDOMMouseEvent::MOZ_SOURCE_TOUCH) { + // If any of these mouse events are really compatibility events that + // Windows is sending for touch inputs, then don't allow them to dismiss + // popups when APZ is enabled (instead we do the dismissing as part of + // WM_TOUCH handling which is more correct). + // If we don't do this, then when the user lifts their finger after a + // long-press, the WM_RBUTTONDOWN compatibility event that Windows sends + // us will dismiss the contextmenu popup that we displayed as part of + // handling the long-tap-up. + return false; + } + if (!EventIsInsideWindow(popupWindow) && + GetPopupsToRollup(rollupListener, &popupsToRollup)) { + break; + } + return false; + + case WM_MOUSEWHEEL: + case WM_MOUSEHWHEEL: + // We need to check if the popup thinks that it should cause closing + // itself when mouse wheel events are fired outside the rollup widget. + if (!EventIsInsideWindow(popupWindow)) { + // Check if we should consume this event even if we don't roll-up: + consumeRollupEvent = + rollupListener->ShouldConsumeOnMouseWheelEvent(); + *aResult = MA_ACTIVATE; + if (rollupListener->ShouldRollupOnMouseWheelEvent() && + GetPopupsToRollup(rollupListener, &popupsToRollup)) { + break; + } + } + return consumeRollupEvent; + + case WM_ACTIVATEAPP: + break; + + case WM_ACTIVATE: + // NOTE: Don't handle WA_INACTIVE for preventing popup taking focus + // because we cannot distinguish it's caused by mouse or not. + if (LOWORD(aWParam) == WA_ACTIVE && aLParam) { + nsWindow* window = WinUtils::GetNSWindowPtr(aWnd); + if (window && window->IsPopup()) { + // Cancel notifying widget listeners of deactivating the previous + // active window (see WM_KILLFOCUS case in ProcessMessage()). + sJustGotDeactivate = false; + // Reactivate the window later. + ::PostMessageW(aWnd, MOZ_WM_REACTIVATE, aWParam, aLParam); + return true; + } + // Don't rollup the popup when focus moves back to the parent window + // from a popup because such case is caused by strange mouse drivers. + nsWindow* prevWindow = + WinUtils::GetNSWindowPtr(reinterpret_cast<HWND>(aLParam)); + if (prevWindow && prevWindow->IsPopup()) { + return false; + } + } else if (LOWORD(aWParam) == WA_INACTIVE) { + nsWindow* activeWindow = + WinUtils::GetNSWindowPtr(reinterpret_cast<HWND>(aLParam)); + if (sPendingNCACTIVATE && NeedsToHandleNCActivateDelayed(aWnd)) { + // If focus moves to non-popup widget or focusable popup, the window + // needs to update its nonclient area. + if (!activeWindow || !activeWindow->IsPopup()) { + sSendingNCACTIVATE = true; + ::SendMessageW(aWnd, WM_NCACTIVATE, false, 0); + sSendingNCACTIVATE = false; + } + sPendingNCACTIVATE = false; + } + // If focus moves from/to popup, we don't need to rollup the popup + // because such case is caused by strange mouse drivers. + if (activeWindow) { + if (activeWindow->IsPopup()) { + return false; + } + nsWindow* deactiveWindow = WinUtils::GetNSWindowPtr(aWnd); + if (deactiveWindow && deactiveWindow->IsPopup()) { + return false; + } + } + } else if (LOWORD(aWParam) == WA_CLICKACTIVE) { + // If the WM_ACTIVATE message is caused by a click in a popup, + // we should not rollup any popups. + if (EventIsInsideWindow(popupWindow) || + !GetPopupsToRollup(rollupListener, &popupsToRollup)) { + return false; + } + } + break; + + case MOZ_WM_REACTIVATE: + // The previous active window should take back focus. + if (::IsWindow(reinterpret_cast<HWND>(aLParam))) { + ::SetForegroundWindow(reinterpret_cast<HWND>(aLParam)); + } + return true; + + case WM_NCACTIVATE: + if (!aWParam && !sSendingNCACTIVATE && + NeedsToHandleNCActivateDelayed(aWnd)) { + // Don't just consume WM_NCACTIVATE. It doesn't handle only the + // nonclient area state change. + ::DefWindowProcW(aWnd, aMessage, TRUE, aLParam); + // Accept the deactivating because it's necessary to receive following + // WM_ACTIVATE. + *aResult = TRUE; + sPendingNCACTIVATE = true; + return true; + } + return false; + + case WM_MOUSEACTIVATE: + if (!EventIsInsideWindow(popupWindow) && + GetPopupsToRollup(rollupListener, &popupsToRollup)) { + // WM_MOUSEACTIVATE may be caused by moving the mouse (e.g., X-mouse + // of TweakUI is enabled. Then, check if the popup should be rolled up + // with rollup listener. If not, just consume the message. + if (HIWORD(aLParam) == WM_MOUSEMOVE && + !rollupListener->ShouldRollupOnMouseActivate()) { + return true; + } + // Otherwise, it should be handled by wndproc. + return false; + } + + // Prevent the click inside the popup from causing a change in window + // activation. Since the popup is shown non-activated, we need to eat any + // requests to activate the window while it is displayed. Windows will + // automatically activate the popup on the mousedown otherwise. + return true; + + case WM_SHOWWINDOW: + // If the window is being minimized, close popups. + if (aLParam == SW_PARENTCLOSING) { + break; + } + return false; + + case WM_KILLFOCUS: + // If focus moves to other window created in different process/thread, + // e.g., a plugin window, popups should be rolled up. + if (IsDifferentThreadWindow(reinterpret_cast<HWND>(aWParam))) { + break; + } + return false; + + case WM_MOVING: + case WM_MENUSELECT: + break; + + default: + return false; + } + + // Only need to deal with the last rollup for left mouse down events. + NS_ASSERTION(!mLastRollup, "mLastRollup is null"); + + if (nativeMessage == WM_LBUTTONDOWN) { + POINT pt; + pt.x = GET_X_LPARAM(aLParam); + pt.y = GET_Y_LPARAM(aLParam); + ::ClientToScreen(aWnd, &pt); + nsIntPoint pos(pt.x, pt.y); + + consumeRollupEvent = + rollupListener->Rollup(popupsToRollup, true, &pos, &mLastRollup); + NS_IF_ADDREF(mLastRollup); + } else { + consumeRollupEvent = + rollupListener->Rollup(popupsToRollup, true, nullptr, nullptr); + } + + // Tell hook to stop processing messages + sProcessHook = false; + sRollupMsgId = 0; + sRollupMsgWnd = nullptr; + + // If we are NOT supposed to be consuming events, let it go through + if (consumeRollupEvent && nativeMessage != WM_RBUTTONDOWN) { + *aResult = MA_ACTIVATE; + return true; + } + + return false; +} + +/************************************************************** + ************************************************************** + ** + ** BLOCK: Misc. utility methods and functions. + ** + ** General use. + ** + ************************************************************** + **************************************************************/ + +// Note that the result of GetTopLevelWindow method can be different from the +// result of WinUtils::GetTopLevelHWND(). The result can be non-floating +// window. Because our top level window may be contained in another window +// which is not managed by us. +nsWindow* nsWindow::GetTopLevelWindow(bool aStopOnDialogOrPopup) +{ + nsWindow* curWindow = this; + + while (true) { + if (aStopOnDialogOrPopup) { + switch (curWindow->mWindowType) { + case eWindowType_dialog: + case eWindowType_popup: + return curWindow; + default: + break; + } + } + + // Retrieve the top level parent or owner window + nsWindow* parentWindow = curWindow->GetParentWindow(true); + + if (!parentWindow) + return curWindow; + + curWindow = parentWindow; + } +} + +static BOOL CALLBACK gEnumWindowsProc(HWND hwnd, LPARAM lParam) +{ + DWORD pid; + ::GetWindowThreadProcessId(hwnd, &pid); + if (pid == GetCurrentProcessId() && ::IsWindowVisible(hwnd)) + { + gWindowsVisible = true; + return FALSE; + } + return TRUE; +} + +bool nsWindow::CanTakeFocus() +{ + gWindowsVisible = false; + EnumWindows(gEnumWindowsProc, 0); + if (!gWindowsVisible) { + return true; + } else { + HWND fgWnd = ::GetForegroundWindow(); + if (!fgWnd) { + return true; + } + DWORD pid; + GetWindowThreadProcessId(fgWnd, &pid); + if (pid == GetCurrentProcessId()) { + return true; + } + } + return false; +} + +/* static */ const wchar_t* +nsWindow::GetMainWindowClass() +{ + static const wchar_t* sMainWindowClass = nullptr; + if (!sMainWindowClass) { + nsAdoptingString className = + Preferences::GetString("ui.window_class_override"); + if (!className.IsEmpty()) { + sMainWindowClass = wcsdup(className.get()); + } else { + sMainWindowClass = kClassNameGeneral; + } + } + return sMainWindowClass; +} + +LPARAM nsWindow::lParamToScreen(LPARAM lParam) +{ + POINT pt; + pt.x = GET_X_LPARAM(lParam); + pt.y = GET_Y_LPARAM(lParam); + ::ClientToScreen(mWnd, &pt); + return MAKELPARAM(pt.x, pt.y); +} + +LPARAM nsWindow::lParamToClient(LPARAM lParam) +{ + POINT pt; + pt.x = GET_X_LPARAM(lParam); + pt.y = GET_Y_LPARAM(lParam); + ::ScreenToClient(mWnd, &pt); + return MAKELPARAM(pt.x, pt.y); +} + +void nsWindow::PickerOpen() +{ + mPickerDisplayCount++; +} + +void nsWindow::PickerClosed() +{ + NS_ASSERTION(mPickerDisplayCount > 0, "mPickerDisplayCount out of sync!"); + if (!mPickerDisplayCount) + return; + mPickerDisplayCount--; + if (!mPickerDisplayCount && mDestroyCalled) { + Destroy(); + } +} + +bool +nsWindow::WidgetTypeSupportsAcceleration() +{ + // We don't currently support using an accelerated layer manager with + // transparent windows so don't even try. I'm also not sure if we even + // want to support this case. See bug 593471. + // + // Also see bug 1150376, D3D11 composition can cause issues on some devices + // on Windows 7 where presentation fails randomly for windows with drop + // shadows. + return mTransparencyMode != eTransparencyTransparent && + !(IsPopup() && DeviceManagerDx::Get()->IsWARP()); +} + +void +nsWindow::SetCandidateWindowForPlugin(const CandidateWindowPosition& aPosition) +{ + CANDIDATEFORM form; + form.dwIndex = 0; + if (aPosition.mExcludeRect) { + form.dwStyle = CFS_EXCLUDE; + form.rcArea.left = aPosition.mRect.x; + form.rcArea.top = aPosition.mRect.y; + form.rcArea.right = aPosition.mRect.x + aPosition.mRect.width; + form.rcArea.bottom = aPosition.mRect.y + aPosition.mRect.height; + } else { + form.dwStyle = CFS_CANDIDATEPOS; + } + form.ptCurrentPos.x = aPosition.mPoint.x; + form.ptCurrentPos.y = aPosition.mPoint.y; + + IMEHandler::SetCandidateWindow(this, &form); +} + +void +nsWindow::DefaultProcOfPluginEvent(const WidgetPluginEvent& aEvent) +{ + const NPEvent* pPluginEvent = + static_cast<const NPEvent*>(aEvent.mPluginEvent); + + if (NS_WARN_IF(!pPluginEvent)) { + return; + } + + if (!mWnd) { + return; + } + + // For WM_IME_*COMPOSITION + IMEHandler::DefaultProcOfPluginEvent(this, pPluginEvent); + + CallWindowProcW(GetPrevWindowProc(), mWnd, pPluginEvent->event, + pPluginEvent->wParam, pPluginEvent->lParam); +} + +nsresult +nsWindow::OnWindowedPluginKeyEvent(const NativeEventData& aKeyEventData, + nsIKeyEventInPluginCallback* aCallback) +{ + if (NS_WARN_IF(!mWnd)) { + return NS_OK; + } + const WinNativeKeyEventData* eventData = + static_cast<const WinNativeKeyEventData*>(aKeyEventData); + switch (eventData->mMessage) { + case WM_KEYDOWN: + case WM_SYSKEYDOWN: { + MSG mozMsg = + WinUtils::InitMSG(MOZ_WM_KEYDOWN, eventData->mWParam, + eventData->mLParam, mWnd); + ModifierKeyState modifierKeyState(eventData->mModifiers); + NativeKey nativeKey(this, mozMsg, modifierKeyState, + eventData->GetKeyboardLayout()); + return nativeKey.HandleKeyDownMessage() ? NS_SUCCESS_EVENT_CONSUMED : + NS_OK; + } + case WM_KEYUP: + case WM_SYSKEYUP: { + MSG mozMsg = + WinUtils::InitMSG(MOZ_WM_KEYUP, eventData->mWParam, + eventData->mLParam, mWnd); + ModifierKeyState modifierKeyState(eventData->mModifiers); + NativeKey nativeKey(this, mozMsg, modifierKeyState, + eventData->GetKeyboardLayout()); + return nativeKey.HandleKeyUpMessage() ? NS_SUCCESS_EVENT_CONSUMED : NS_OK; + } + default: + // We shouldn't consume WM_*CHAR messages here even if the preceding + // keydown or keyup event on the plugin is consumed. It should be + // managed in each plugin window rather than top level window. + return NS_OK; + } +} + +/************************************************************** + ************************************************************** + ** + ** BLOCK: ChildWindow impl. + ** + ** Child window overrides. + ** + ************************************************************** + **************************************************************/ + +// return the style for a child nsWindow +DWORD ChildWindow::WindowStyle() +{ + DWORD style = WS_CLIPCHILDREN | nsWindow::WindowStyle(); + if (!(style & WS_POPUP)) + style |= WS_CHILD; // WS_POPUP and WS_CHILD are mutually exclusive. + VERIFY_WINDOW_STYLE(style); + return style; +} + +void +nsWindow::GetCompositorWidgetInitData(mozilla::widget::CompositorWidgetInitData* aInitData) +{ + aInitData->hWnd() = reinterpret_cast<uintptr_t>(mWnd); + aInitData->widgetKey() = reinterpret_cast<uintptr_t>(static_cast<nsIWidget*>(this)); + aInitData->transparencyMode() = mTransparencyMode; +} diff --git a/widget/windows/nsWindow.h b/widget/windows/nsWindow.h new file mode 100644 index 0000000000..248978bd79 --- /dev/null +++ b/widget/windows/nsWindow.h @@ -0,0 +1,671 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef Window_h__ +#define Window_h__ + +/* + * nsWindow - Native window management and event handling. + */ + +#include "mozilla/RefPtr.h" +#include "nsBaseWidget.h" +#include "nsWindowBase.h" +#include "nsdefs.h" +#include "nsIdleService.h" +#include "nsToolkit.h" +#include "nsString.h" +#include "nsTArray.h" +#include "gfxWindowsPlatform.h" +#include "gfxWindowsSurface.h" +#include "nsWindowDbg.h" +#include "cairo.h" +#include "nsITimer.h" +#include "nsRegion.h" +#include "mozilla/EventForwards.h" +#include "mozilla/MouseEvents.h" +#include "mozilla/TimeStamp.h" +#include "nsMargin.h" +#include "nsRegionFwd.h" + +#include "nsWinGesture.h" +#include "WinUtils.h" +#include "WindowHook.h" +#include "TaskbarWindowPreview.h" + +#ifdef ACCESSIBILITY +#include "oleacc.h" +#include "mozilla/a11y/Accessible.h" +#endif + +#include "nsUXThemeData.h" +#include "nsIDOMMouseEvent.h" +#include "nsIIdleServiceInternal.h" + +#include "IMMHandler.h" + +/** + * Forward class definitions + */ + +class nsNativeDragTarget; +class nsIRollupListener; +class imgIContainer; + +namespace mozilla { +namespace widget { +class NativeKey; +class WinCompositorWidget; +struct MSGResult; +} // namespace widget +} // namespacw mozilla; + +/** + * Native WIN32 window wrapper. + */ + +class nsWindow : public nsWindowBase +{ + typedef mozilla::TimeStamp TimeStamp; + typedef mozilla::TimeDuration TimeDuration; + typedef mozilla::widget::WindowHook WindowHook; + typedef mozilla::widget::TaskbarWindowPreview TaskbarWindowPreview; + typedef mozilla::widget::NativeKey NativeKey; + typedef mozilla::widget::MSGResult MSGResult; + typedef mozilla::widget::IMEContext IMEContext; + +public: + nsWindow(); + + NS_DECL_ISUPPORTS_INHERITED + + friend class nsWindowGfx; + + // nsWindowBase + virtual void InitEvent(mozilla::WidgetGUIEvent& aEvent, + LayoutDeviceIntPoint* aPoint = nullptr) override; + virtual WidgetEventTime CurrentMessageWidgetEventTime() const override; + virtual bool DispatchWindowEvent(mozilla::WidgetGUIEvent* aEvent) override; + virtual bool DispatchKeyboardEvent(mozilla::WidgetKeyboardEvent* aEvent) override; + virtual bool DispatchWheelEvent(mozilla::WidgetWheelEvent* aEvent) override; + virtual bool DispatchContentCommandEvent(mozilla::WidgetContentCommandEvent* aEvent) override; + virtual nsWindowBase* GetParentWindowBase(bool aIncludeOwner) override; + virtual bool IsTopLevelWidget() override { return mIsTopWidgetWindow; } + + using nsWindowBase::DispatchPluginEvent; + + // nsIWidget interface + using nsWindowBase::Create; // for Create signature not overridden here + virtual MOZ_MUST_USE nsresult Create(nsIWidget* aParent, + nsNativeWidget aNativeParent, + const LayoutDeviceIntRect& aRect, + nsWidgetInitData* aInitData = nullptr) + override; + virtual void Destroy() override; + NS_IMETHOD SetParent(nsIWidget *aNewParent) override; + virtual nsIWidget* GetParent(void) override; + virtual float GetDPI() override; + double GetDefaultScaleInternal() final; + int32_t LogToPhys(double aValue) final; + mozilla::DesktopToLayoutDeviceScale GetDesktopToDeviceScale() final + { + if (mozilla::widget::WinUtils::IsPerMonitorDPIAware()) { + return mozilla::DesktopToLayoutDeviceScale(1.0); + } else { + return mozilla::DesktopToLayoutDeviceScale(GetDefaultScaleInternal()); + } + } + + NS_IMETHOD Show(bool bState) override; + virtual bool IsVisible() const override; + virtual void ConstrainPosition(bool aAllowSlop, int32_t *aX, int32_t *aY) override; + virtual void SetSizeConstraints(const SizeConstraints& aConstraints) override; + virtual const SizeConstraints GetSizeConstraints() override; + NS_IMETHOD Move(double aX, double aY) override; + NS_IMETHOD Resize(double aWidth, double aHeight, bool aRepaint) override; + NS_IMETHOD Resize(double aX, double aY, double aWidth, double aHeight, bool aRepaint) override; + NS_IMETHOD BeginResizeDrag(mozilla::WidgetGUIEvent* aEvent, + int32_t aHorizontal, + int32_t aVertical) override; + virtual void PlaceBehind(nsTopLevelWidgetZPlacement aPlacement, nsIWidget *aWidget, bool aActivate) override; + virtual void SetSizeMode(nsSizeMode aMode) override; + NS_IMETHOD Enable(bool aState) override; + virtual bool IsEnabled() const override; + NS_IMETHOD SetFocus(bool aRaise) override; + virtual LayoutDeviceIntRect GetBounds() override; + virtual LayoutDeviceIntRect GetScreenBounds() override; + virtual MOZ_MUST_USE nsresult GetRestoredBounds(LayoutDeviceIntRect& aRect) override; + virtual LayoutDeviceIntRect GetClientBounds() override; + virtual LayoutDeviceIntPoint GetClientOffset() override; + void SetBackgroundColor(const nscolor& aColor) override; + NS_IMETHOD SetCursor(imgIContainer* aCursor, + uint32_t aHotspotX, uint32_t aHotspotY) override; + NS_IMETHOD SetCursor(nsCursor aCursor) override; + virtual nsresult ConfigureChildren(const nsTArray<Configuration>& aConfigurations) override; + virtual bool PrepareForFullscreenTransition(nsISupports** aData) override; + virtual void PerformFullscreenTransition(FullscreenTransitionStage aStage, + uint16_t aDuration, + nsISupports* aData, + nsIRunnable* aCallback) override; + virtual nsresult MakeFullScreen(bool aFullScreen, + nsIScreen* aScreen = nullptr) override; + NS_IMETHOD HideWindowChrome(bool aShouldHide) override; + NS_IMETHOD Invalidate(bool aEraseBackground = false, + bool aUpdateNCArea = false, + bool aIncludeChildren = false); + NS_IMETHOD Invalidate(const LayoutDeviceIntRect& aRect); + virtual void* GetNativeData(uint32_t aDataType) override; + void SetNativeData(uint32_t aDataType, uintptr_t aVal) override; + virtual void FreeNativeData(void * data, uint32_t aDataType) override; + NS_IMETHOD SetTitle(const nsAString& aTitle) override; + NS_IMETHOD SetIcon(const nsAString& aIconSpec) override; + virtual LayoutDeviceIntPoint WidgetToScreenOffset() override; + virtual LayoutDeviceIntSize ClientToWindowSize(const LayoutDeviceIntSize& aClientSize) override; + NS_IMETHOD DispatchEvent(mozilla::WidgetGUIEvent* aEvent, + nsEventStatus& aStatus) override; + virtual void EnableDragDrop(bool aEnable) override; + virtual void CaptureMouse(bool aCapture) override; + virtual void CaptureRollupEvents(nsIRollupListener* aListener, + bool aDoCapture) override; + NS_IMETHOD GetAttention(int32_t aCycleCount) override; + virtual bool HasPendingInputEvent() override; + virtual LayerManager* GetLayerManager(PLayerTransactionChild* aShadowManager = nullptr, + LayersBackend aBackendHint = mozilla::layers::LayersBackend::LAYERS_NONE, + LayerManagerPersistence aPersistence = LAYER_MANAGER_CURRENT) override; + NS_IMETHOD OnDefaultButtonLoaded(const LayoutDeviceIntRect& aButtonRect) override; + virtual nsresult SynthesizeNativeKeyEvent(int32_t aNativeKeyboardLayout, + int32_t aNativeKeyCode, + uint32_t aModifierFlags, + const nsAString& aCharacters, + const nsAString& aUnmodifiedCharacters, + nsIObserver* aObserver) override; + virtual nsresult SynthesizeNativeMouseEvent(LayoutDeviceIntPoint aPoint, + uint32_t aNativeMessage, + uint32_t aModifierFlags, + nsIObserver* aObserver) override; + + virtual nsresult SynthesizeNativeMouseMove(LayoutDeviceIntPoint aPoint, + nsIObserver* aObserver) override + { return SynthesizeNativeMouseEvent(aPoint, MOUSEEVENTF_MOVE, 0, aObserver); } + + virtual nsresult SynthesizeNativeMouseScrollEvent(LayoutDeviceIntPoint aPoint, + uint32_t aNativeMessage, + double aDeltaX, + double aDeltaY, + double aDeltaZ, + uint32_t aModifierFlags, + uint32_t aAdditionalFlags, + nsIObserver* aObserver) override; + NS_IMETHOD_(void) SetInputContext(const InputContext& aContext, + const InputContextAction& aAction) override; + NS_IMETHOD_(InputContext) GetInputContext() override; + NS_IMETHOD_(TextEventDispatcherListener*) + GetNativeTextEventDispatcherListener() override; +#ifdef MOZ_XUL + virtual void SetTransparencyMode(nsTransparencyMode aMode) override; + virtual nsTransparencyMode GetTransparencyMode() override; + virtual void UpdateOpaqueRegion(const LayoutDeviceIntRegion& aOpaqueRegion) override; +#endif // MOZ_XUL + virtual nsIMEUpdatePreference GetIMEUpdatePreference() override; + NS_IMETHOD SetNonClientMargins(LayoutDeviceIntMargin& aMargins) override; + void SetDrawsInTitlebar(bool aState) override; + virtual void UpdateWindowDraggingRegion(const LayoutDeviceIntRegion& aRegion) override; + + virtual void UpdateThemeGeometries(const nsTArray<ThemeGeometry>& aThemeGeometries) override; + virtual uint32_t GetMaxTouchPoints() const override; + + /** + * Event helpers + */ + virtual bool DispatchMouseEvent( + mozilla::EventMessage aEventMessage, + WPARAM wParam, + LPARAM lParam, + bool aIsContextMenuKey = false, + int16_t aButton = + mozilla::WidgetMouseEvent::eLeftButton, + uint16_t aInputSource = + nsIDOMMouseEvent::MOZ_SOURCE_MOUSE, + uint16_t aPointerId = 0); + virtual bool DispatchWindowEvent(mozilla::WidgetGUIEvent* aEvent, + nsEventStatus& aStatus); + void DispatchPendingEvents(); + bool DispatchPluginEvent(UINT aMessage, + WPARAM aWParam, + LPARAM aLParam, + bool aDispatchPendingEvents); + + void SuppressBlurEvents(bool aSuppress); // Called from nsFilePicker + bool BlurEventsSuppressed(); +#ifdef ACCESSIBILITY + /** + * Return an accessible associated with the window. + */ + mozilla::a11y::Accessible* GetAccessible(); +#endif // ACCESSIBILITY + + /** + * Window utilities + */ + nsWindow* GetTopLevelWindow(bool aStopOnDialogOrPopup); + WNDPROC GetPrevWindowProc() { return mPrevWndProc; } + WindowHook& GetWindowHook() { return mWindowHook; } + nsWindow* GetParentWindow(bool aIncludeOwner); + // Get an array of all nsWindow*s on the main thread. + typedef void (WindowEnumCallback)(nsWindow*); + static void EnumAllWindows(WindowEnumCallback aCallback); + + /** + * Misc. + */ + virtual bool AutoErase(HDC dc); + bool WidgetTypeSupportsAcceleration() override; + + void ForcePresent(); + + /** + * AssociateDefaultIMC() associates or disassociates the default IMC for + * the window. + * + * @param aAssociate TRUE, associates the default IMC with the window. + * Otherwise, disassociates the default IMC from the + * window. + * @return TRUE if this method associated the default IMC with + * disassociated window or disassociated the default IMC + * from associated window. + * Otherwise, i.e., if this method did nothing actually, + * FALSE. + */ + bool AssociateDefaultIMC(bool aAssociate); + + bool HasTaskbarIconBeenCreated() { return mHasTaskbarIconBeenCreated; } + // Called when either the nsWindow or an nsITaskbarTabPreview receives the noticiation that this window + // has its icon placed on the taskbar. + void SetHasTaskbarIconBeenCreated(bool created = true) { mHasTaskbarIconBeenCreated = created; } + + // Getter/setter for the nsITaskbarWindowPreview for this nsWindow + already_AddRefed<nsITaskbarWindowPreview> GetTaskbarPreview() { + nsCOMPtr<nsITaskbarWindowPreview> preview(do_QueryReferent(mTaskbarPreview)); + return preview.forget(); + } + void SetTaskbarPreview(nsITaskbarWindowPreview *preview) { mTaskbarPreview = do_GetWeakReference(preview); } + + virtual void ReparentNativeWidget(nsIWidget* aNewParent) override; + + // Open file picker tracking + void PickerOpen(); + void PickerClosed(); + + bool const DestroyCalled() { return mDestroyCalled; } + + bool IsPopup(); + virtual bool ShouldUseOffMainThreadCompositing() override; + + const IMEContext& DefaultIMC() const { return mDefaultIMC; } + + virtual void SetCandidateWindowForPlugin( + const mozilla::widget::CandidateWindowPosition& + aPosition) override; + virtual void DefaultProcOfPluginEvent( + const mozilla::WidgetPluginEvent& aEvent) override; + virtual nsresult OnWindowedPluginKeyEvent( + const mozilla::NativeEventData& aKeyEventData, + nsIKeyEventInPluginCallback* aCallback) override; + + void GetCompositorWidgetInitData(mozilla::widget::CompositorWidgetInitData* aInitData) override; + bool IsTouchWindow() const { return mTouchWindow; } + +protected: + virtual ~nsWindow(); + + virtual void WindowUsesOMTC() override; + virtual void RegisterTouchWindow() override; + + // A magic number to identify the FAKETRACKPOINTSCROLLABLE window created + // when the trackpoint hack is enabled. + enum { eFakeTrackPointScrollableID = 0x46545053 }; + + // Used for displayport suppression during window resize + enum ResizeState { + NOT_RESIZING, + IN_SIZEMOVE, + RESIZING, + MOVING + }; + + /** + * Callbacks + */ + static LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); + static LRESULT CALLBACK WindowProcInternal(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); + + static BOOL CALLBACK BroadcastMsgToChildren(HWND aWnd, LPARAM aMsg); + static BOOL CALLBACK BroadcastMsg(HWND aTopWindow, LPARAM aMsg); + static BOOL CALLBACK DispatchStarvedPaints(HWND aTopWindow, LPARAM aMsg); + static BOOL CALLBACK RegisterTouchForDescendants(HWND aTopWindow, LPARAM aMsg); + static BOOL CALLBACK UnregisterTouchForDescendants(HWND aTopWindow, LPARAM aMsg); + static LRESULT CALLBACK MozSpecialMsgFilter(int code, WPARAM wParam, LPARAM lParam); + static LRESULT CALLBACK MozSpecialWndProc(int code, WPARAM wParam, LPARAM lParam); + static LRESULT CALLBACK MozSpecialMouseProc(int code, WPARAM wParam, LPARAM lParam); + static VOID CALLBACK HookTimerForPopups( HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime ); + static BOOL CALLBACK ClearResourcesCallback(HWND aChild, LPARAM aParam); + static BOOL CALLBACK EnumAllChildWindProc(HWND aWnd, LPARAM aParam); + static BOOL CALLBACK EnumAllThreadWindowProc(HWND aWnd, LPARAM aParam); + + /** + * Window utilities + */ + LPARAM lParamToScreen(LPARAM lParam); + LPARAM lParamToClient(LPARAM lParam); + virtual void SubclassWindow(BOOL bState); + bool CanTakeFocus(); + bool UpdateNonClientMargins(int32_t aSizeMode = -1, bool aReflowWindow = true); + void UpdateGetWindowInfoCaptionStatus(bool aActiveCaption); + void ResetLayout(); + void InvalidateNonClientRegion(); + HRGN ExcludeNonClientFromPaintRegion(HRGN aRegion); + static const wchar_t* GetMainWindowClass(); + bool HasGlass() const { + return mTransparencyMode == eTransparencyGlass || + mTransparencyMode == eTransparencyBorderlessGlass; + } + HWND GetOwnerWnd() const + { + return ::GetWindow(mWnd, GW_OWNER); + } + bool IsOwnerForegroundWindow() const + { + HWND owner = GetOwnerWnd(); + return owner && owner == ::GetForegroundWindow(); + } + bool IsPopup() const + { + return mWindowType == eWindowType_popup; + } + + + /** + * Event processing helpers + */ + void DispatchFocusToTopLevelWindow(bool aIsActivate); + bool DispatchStandardEvent(mozilla::EventMessage aMsg); + void RelayMouseEvent(UINT aMsg, WPARAM wParam, LPARAM lParam); + virtual bool ProcessMessage(UINT msg, WPARAM &wParam, + LPARAM &lParam, LRESULT *aRetValue); + bool ExternalHandlerProcessMessage( + UINT aMessage, WPARAM& aWParam, + LPARAM& aLParam, MSGResult& aResult); + bool ProcessMessageForPlugin(const MSG &aMsg, + MSGResult& aResult); + LRESULT ProcessCharMessage(const MSG &aMsg, + bool *aEventDispatched); + LRESULT ProcessKeyUpMessage(const MSG &aMsg, + bool *aEventDispatched); + LRESULT ProcessKeyDownMessage(const MSG &aMsg, + bool *aEventDispatched); + static bool EventIsInsideWindow(nsWindow* aWindow); + // Convert nsEventStatus value to a windows boolean + static bool ConvertStatus(nsEventStatus aStatus); + static void PostSleepWakeNotification(const bool aIsSleepMode); + int32_t ClientMarginHitTestPoint(int32_t mx, int32_t my); + TimeStamp GetMessageTimeStamp(LONG aEventTime) const; + static void UpdateFirstEventTime(DWORD aEventTime); + void FinishLiveResizing(ResizeState aNewState); + + /** + * Event handlers + */ + virtual void OnDestroy() override; + virtual bool OnResize(nsIntRect &aWindowRect); + bool OnGesture(WPARAM wParam, LPARAM lParam); + bool OnTouch(WPARAM wParam, LPARAM lParam); + bool OnHotKey(WPARAM wParam, LPARAM lParam); + bool OnPaint(HDC aDC, uint32_t aNestingLevel); + void OnWindowPosChanged(WINDOWPOS* wp); + void OnWindowPosChanging(LPWINDOWPOS& info); + void OnSysColorChanged(); + void OnDPIChanged(int32_t x, int32_t y, + int32_t width, int32_t height); + + /** + * Function that registers when the user has been active (used for detecting + * when the user is idle). + */ + void UserActivity(); + + int32_t GetHeight(int32_t aProposedHeight); + const wchar_t* GetWindowClass() const; + const wchar_t* GetWindowPopupClass() const; + virtual DWORD WindowStyle(); + DWORD WindowExStyle(); + + // This method registers the given window class, and returns the class name. + const wchar_t* RegisterWindowClass(const wchar_t* aClassName, + UINT aExtraStyle, + LPWSTR aIconID) const; + + /** + * XP and Vista theming support for windows with rounded edges + */ + void ClearThemeRegion(); + void SetThemeRegion(); + + /** + * Popup hooks + */ + static void ScheduleHookTimer(HWND aWnd, UINT aMsgId); + static void RegisterSpecialDropdownHooks(); + static void UnregisterSpecialDropdownHooks(); + static bool GetPopupsToRollup(nsIRollupListener* aRollupListener, + uint32_t* aPopupsToRollup); + static bool NeedsToHandleNCActivateDelayed(HWND aWnd); + static bool DealWithPopups(HWND inWnd, UINT inMsg, WPARAM inWParam, LPARAM inLParam, LRESULT* outResult); + + /** + * Window transparency helpers + */ +#ifdef MOZ_XUL +private: + void SetWindowTranslucencyInner(nsTransparencyMode aMode); + nsTransparencyMode GetWindowTranslucencyInner() const { return mTransparencyMode; } + void UpdateGlass(); +protected: +#endif // MOZ_XUL + + static bool IsAsyncResponseEvent(UINT aMsg, LRESULT& aResult); + void IPCWindowProcHandler(UINT& msg, WPARAM& wParam, LPARAM& lParam); + + /** + * Misc. + */ + void StopFlashing(); + static bool IsTopLevelMouseExit(HWND aWnd); + virtual nsresult SetWindowClipRegion(const nsTArray<LayoutDeviceIntRect>& aRects, + bool aIntersectWithExisting) override; + nsIntRegion GetRegionToPaint(bool aForceFullRepaint, + PAINTSTRUCT ps, HDC aDC); + static void ActivateOtherWindowHelper(HWND aWnd); + void ClearCachedResources(); + nsIWidgetListener* GetPaintListener(); + + already_AddRefed<SourceSurface> CreateScrollSnapshot() override; + + struct ScrollSnapshot + { + RefPtr<gfxWindowsSurface> surface; + bool surfaceHasSnapshot = false; + RECT clip; + }; + + ScrollSnapshot* EnsureSnapshotSurface(ScrollSnapshot& aSnapshotData, + const mozilla::gfx::IntSize& aSize); + + ScrollSnapshot mFullSnapshot; + ScrollSnapshot mPartialSnapshot; + ScrollSnapshot* mCurrentSnapshot = nullptr; + + already_AddRefed<SourceSurface> + GetFallbackScrollSnapshot(const RECT& aRequiredClip); + +protected: + nsCOMPtr<nsIWidget> mParent; + nsIntSize mLastSize; + nsIntPoint mLastPoint; + HWND mWnd; + HWND mTransitionWnd; + WNDPROC mPrevWndProc; + HBRUSH mBrush; + IMEContext mDefaultIMC; + bool mIsTopWidgetWindow; + bool mInDtor; + bool mIsVisible; + bool mUnicodeWidget; + bool mPainting; + bool mTouchWindow; + bool mDisplayPanFeedback; + bool mHideChrome; + bool mIsRTL; + bool mFullscreenMode; + bool mMousePresent; + bool mDestroyCalled; + uint32_t mBlurSuppressLevel; + DWORD_PTR mOldStyle; + DWORD_PTR mOldExStyle; + nsNativeDragTarget* mNativeDragTarget; + HKL mLastKeyboardLayout; + nsSizeMode mOldSizeMode; + nsSizeMode mLastSizeMode; + WindowHook mWindowHook; + uint32_t mPickerDisplayCount; + HICON mIconSmall; + HICON mIconBig; + static bool sDropShadowEnabled; + static uint32_t sInstanceCount; + static TriStateBool sCanQuit; + static nsWindow* sCurrentWindow; + static BOOL sIsOleInitialized; + static HCURSOR sHCursor; + static imgIContainer* sCursorImgContainer; + static bool sSwitchKeyboardLayout; + static bool sJustGotDeactivate; + static bool sJustGotActivate; + static bool sIsInMouseCapture; + static int sTrimOnMinimize; + + // Always use the helper method to read this property. See bug 603793. + static TriStateBool sHasBogusPopupsDropShadowOnMultiMonitor; + static bool HasBogusPopupsDropShadowOnMultiMonitor(); + + // Non-client margin settings + // Pre-calculated outward offset applied to default frames + LayoutDeviceIntMargin mNonClientOffset; + // Margins set by the owner + LayoutDeviceIntMargin mNonClientMargins; + // Margins we'd like to set once chrome is reshown: + LayoutDeviceIntMargin mFutureMarginsOnceChromeShows; + // Indicates we need to apply margins once toggling chrome into showing: + bool mFutureMarginsToUse; + + // Indicates custom frames are enabled + bool mCustomNonClient; + // Cached copy of L&F's resize border + int32_t mHorResizeMargin; + int32_t mVertResizeMargin; + // Height of the caption plus border + int32_t mCaptionHeight; + + double mDefaultScale; + + nsCOMPtr<nsIIdleServiceInternal> mIdleService; + + // Draggable titlebar region maintained by UpdateWindowDraggingRegion + LayoutDeviceIntRegion mDraggableRegion; + + // Hook Data Memebers for Dropdowns. sProcessHook Tells the + // hook methods whether they should be processing the hook + // messages. + static HHOOK sMsgFilterHook; + static HHOOK sCallProcHook; + static HHOOK sCallMouseHook; + static bool sProcessHook; + static UINT sRollupMsgId; + static HWND sRollupMsgWnd; + static UINT sHookTimerId; + + // Mouse Clicks - static variable definitions for figuring + // out 1 - 3 Clicks. + static POINT sLastMousePoint; + static POINT sLastMouseMovePoint; + static LONG sLastMouseDownTime; + static LONG sLastClickCount; + static BYTE sLastMouseButton; + + // Graphics + HDC mPaintDC; // only set during painting + + LayoutDeviceIntRect mLastPaintBounds; + + ResizeState mResizeState; + + // Transparency +#ifdef MOZ_XUL + nsTransparencyMode mTransparencyMode; + nsIntRegion mPossiblyTransparentRegion; + MARGINS mGlassMargins; +#endif // MOZ_XUL + + // Win7 Gesture processing and management + nsWinGesture mGesture; + + // Weak ref to the nsITaskbarWindowPreview associated with this window + nsWeakPtr mTaskbarPreview; + // True if the taskbar (possibly through the tab preview) tells us that the + // icon has been created on the taskbar. + bool mHasTaskbarIconBeenCreated; + + // Indicates that mouse events should be ignored and pass through to the + // window below. This is currently only used for popups. + bool mMouseTransparent; + + // Whether we're in the process of sending a WM_SETTEXT ourselves + bool mSendingSetText; + + // The point in time at which the last paint completed. We use this to avoid + // painting too rapidly in response to frequent input events. + TimeStamp mLastPaintEndTime; + + // Caching for hit test results + POINT mCachedHitTestPoint; + TimeStamp mCachedHitTestTime; + int32_t mCachedHitTestResult; + + RefPtr<mozilla::widget::WinCompositorWidget> mBasicLayersSurface; + + static bool sNeedsToInitMouseWheelSettings; + static void InitMouseWheelScrollData(); + + double mSizeConstraintsScale; // scale in effect when setting constraints + + // Used to remember the wParam (i.e. currently pressed modifier keys) + // and lParam (i.e. last mouse position) in screen coordinates from + // the previous mouse-exit. Static since it is not + // associated with a particular widget (since we exited the widget). + static WPARAM sMouseExitwParam; + static LPARAM sMouseExitlParamScreen; +}; + +/** + * A child window is a window with different style. + */ +class ChildWindow : public nsWindow { + +public: + ChildWindow() {} + +protected: + virtual DWORD WindowStyle(); +}; + +#endif // Window_h__ diff --git a/widget/windows/nsWindowBase.cpp b/widget/windows/nsWindowBase.cpp new file mode 100644 index 0000000000..a374b36359 --- /dev/null +++ b/widget/windows/nsWindowBase.cpp @@ -0,0 +1,243 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsWindowBase.h" + +#include "mozilla/MiscEvents.h" +#include "KeyboardLayout.h" +#include "WinUtils.h" +#include "npapi.h" +#include "nsAutoPtr.h" + +using namespace mozilla; +using namespace mozilla::widget; + +static const wchar_t kUser32LibName[] = L"user32.dll"; +bool nsWindowBase::sTouchInjectInitialized = false; +InjectTouchInputPtr nsWindowBase::sInjectTouchFuncPtr; + +bool +nsWindowBase::DispatchPluginEvent(const MSG& aMsg) +{ + if (!ShouldDispatchPluginEvent()) { + return false; + } + WidgetPluginEvent pluginEvent(true, ePluginInputEvent, this); + LayoutDeviceIntPoint point(0, 0); + InitEvent(pluginEvent, &point); + NPEvent npEvent; + npEvent.event = aMsg.message; + npEvent.wParam = aMsg.wParam; + npEvent.lParam = aMsg.lParam; + pluginEvent.mPluginEvent.Copy(npEvent); + pluginEvent.mRetargetToFocusedDocument = true; + return DispatchWindowEvent(&pluginEvent); +} + +bool +nsWindowBase::ShouldDispatchPluginEvent() +{ + return PluginHasFocus(); +} + +// static +bool +nsWindowBase::InitTouchInjection() +{ + if (!sTouchInjectInitialized) { + // Initialize touch injection on the first call + HMODULE hMod = LoadLibraryW(kUser32LibName); + if (!hMod) { + return false; + } + + InitializeTouchInjectionPtr func = + (InitializeTouchInjectionPtr)GetProcAddress(hMod, "InitializeTouchInjection"); + if (!func) { + WinUtils::Log("InitializeTouchInjection not available."); + return false; + } + + if (!func(TOUCH_INJECT_MAX_POINTS, TOUCH_FEEDBACK_DEFAULT)) { + WinUtils::Log("InitializeTouchInjection failure. GetLastError=%d", GetLastError()); + return false; + } + + sInjectTouchFuncPtr = + (InjectTouchInputPtr)GetProcAddress(hMod, "InjectTouchInput"); + if (!sInjectTouchFuncPtr) { + WinUtils::Log("InjectTouchInput not available."); + return false; + } + sTouchInjectInitialized = true; + } + return true; +} + +bool +nsWindowBase::InjectTouchPoint(uint32_t aId, LayoutDeviceIntPoint& aPoint, + POINTER_FLAGS aFlags, uint32_t aPressure, + uint32_t aOrientation) +{ + if (aId > TOUCH_INJECT_MAX_POINTS) { + WinUtils::Log("Pointer ID exceeds maximum. See TOUCH_INJECT_MAX_POINTS."); + return false; + } + + POINTER_TOUCH_INFO info; + memset(&info, 0, sizeof(POINTER_TOUCH_INFO)); + + info.touchFlags = TOUCH_FLAG_NONE; + info.touchMask = TOUCH_MASK_CONTACTAREA|TOUCH_MASK_ORIENTATION|TOUCH_MASK_PRESSURE; + info.pressure = aPressure; + info.orientation = aOrientation; + + info.pointerInfo.pointerFlags = aFlags; + info.pointerInfo.pointerType = PT_TOUCH; + info.pointerInfo.pointerId = aId; + info.pointerInfo.ptPixelLocation.x = aPoint.x; + info.pointerInfo.ptPixelLocation.y = aPoint.y; + + info.rcContact.top = info.pointerInfo.ptPixelLocation.y - 2; + info.rcContact.bottom = info.pointerInfo.ptPixelLocation.y + 2; + info.rcContact.left = info.pointerInfo.ptPixelLocation.x - 2; + info.rcContact.right = info.pointerInfo.ptPixelLocation.x + 2; + + if (!sInjectTouchFuncPtr(1, &info)) { + WinUtils::Log("InjectTouchInput failure. GetLastError=%d", GetLastError()); + return false; + } + return true; +} + +void nsWindowBase::ChangedDPI() +{ + if (mWidgetListener) { + nsIPresShell* presShell = mWidgetListener->GetPresShell(); + if (presShell) { + presShell->BackingScaleFactorChanged(); + } + mWidgetListener->UIResolutionChanged(); + } +} + +nsresult +nsWindowBase::SynthesizeNativeTouchPoint(uint32_t aPointerId, + nsIWidget::TouchPointerState aPointerState, + LayoutDeviceIntPoint aPoint, + double aPointerPressure, + uint32_t aPointerOrientation, + nsIObserver* aObserver) +{ + AutoObserverNotifier notifier(aObserver, "touchpoint"); + + if (gfxPrefs::APZTestFailsWithNativeInjection() || !InitTouchInjection()) { + // If we don't have touch injection from the OS, or if we are running a test + // that cannot properly inject events to satisfy the OS requirements (see bug + // 1313170) we can just fake it and synthesize the events from here. + MOZ_ASSERT(NS_IsMainThread()); + if (aPointerState == TOUCH_HOVER) { + return NS_ERROR_UNEXPECTED; + } + + if (!mSynthesizedTouchInput) { + mSynthesizedTouchInput = MakeUnique<MultiTouchInput>(); + } + + WidgetEventTime time = CurrentMessageWidgetEventTime(); + LayoutDeviceIntPoint pointInWindow = aPoint - WidgetToScreenOffset(); + MultiTouchInput inputToDispatch = UpdateSynthesizedTouchState( + mSynthesizedTouchInput.get(), time.mTime, time.mTimeStamp, + aPointerId, aPointerState, pointInWindow, aPointerPressure, + aPointerOrientation); + DispatchTouchInput(inputToDispatch); + return NS_OK; + } + + bool hover = aPointerState & TOUCH_HOVER; + bool contact = aPointerState & TOUCH_CONTACT; + bool remove = aPointerState & TOUCH_REMOVE; + bool cancel = aPointerState & TOUCH_CANCEL; + + // win api expects a value from 0 to 1024. aPointerPressure is a value + // from 0.0 to 1.0. + uint32_t pressure = (uint32_t)ceil(aPointerPressure * 1024); + + // If we already know about this pointer id get it's record + PointerInfo* info = mActivePointers.Get(aPointerId); + + // We know about this pointer, send an update + if (info) { + POINTER_FLAGS flags = POINTER_FLAG_UPDATE; + if (hover) { + flags |= POINTER_FLAG_INRANGE; + } else if (contact) { + flags |= POINTER_FLAG_INCONTACT|POINTER_FLAG_INRANGE; + } else if (remove) { + flags = POINTER_FLAG_UP; + // Remove the pointer from our tracking list. This is nsAutPtr wrapped, + // so shouldn't leak. + mActivePointers.Remove(aPointerId); + } + + if (cancel) { + flags |= POINTER_FLAG_CANCELED; + } + + return !InjectTouchPoint(aPointerId, aPoint, flags, + pressure, aPointerOrientation) ? + NS_ERROR_UNEXPECTED : NS_OK; + } + + // Missing init state, error out + if (remove || cancel) { + return NS_ERROR_INVALID_ARG; + } + + // Create a new pointer + info = new PointerInfo(aPointerId, aPoint); + + POINTER_FLAGS flags = POINTER_FLAG_INRANGE; + if (contact) { + flags |= POINTER_FLAG_INCONTACT|POINTER_FLAG_DOWN; + } + + mActivePointers.Put(aPointerId, info); + return !InjectTouchPoint(aPointerId, aPoint, flags, + pressure, aPointerOrientation) ? + NS_ERROR_UNEXPECTED : NS_OK; +} + +nsresult +nsWindowBase::ClearNativeTouchSequence(nsIObserver* aObserver) +{ + AutoObserverNotifier notifier(aObserver, "cleartouch"); + if (!sTouchInjectInitialized) { + return NS_OK; + } + + // cancel all input points + for (auto iter = mActivePointers.Iter(); !iter.Done(); iter.Next()) { + nsAutoPtr<PointerInfo>& info = iter.Data(); + InjectTouchPoint(info.get()->mPointerId, info.get()->mPosition, + POINTER_FLAG_CANCELED); + iter.Remove(); + } + + nsBaseWidget::ClearNativeTouchSequence(nullptr); + + return NS_OK; +} + +bool +nsWindowBase::HandleAppCommandMsg(const MSG& aAppCommandMsg, + LRESULT *aRetValue) +{ + ModifierKeyState modKeyState; + NativeKey nativeKey(this, aAppCommandMsg, modKeyState); + bool consumed = nativeKey.HandleAppCommandMessage(); + *aRetValue = consumed ? 1 : 0; + return consumed; +} diff --git a/widget/windows/nsWindowBase.h b/widget/windows/nsWindowBase.h new file mode 100644 index 0000000000..405f5b359e --- /dev/null +++ b/widget/windows/nsWindowBase.h @@ -0,0 +1,145 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsWindowBase_h_ +#define nsWindowBase_h_ + +#include "mozilla/EventForwards.h" +#include "nsBaseWidget.h" +#include "nsClassHashtable.h" + +#include <windows.h> +#include "touchinjection_sdk80.h" + +/* + * nsWindowBase - Base class of common methods other classes need to access + * in both win32 and winrt window classes. + */ +class nsWindowBase : public nsBaseWidget +{ +public: + typedef mozilla::WidgetEventTime WidgetEventTime; + + /* + * Return the HWND or null for this widget. + */ + virtual HWND GetWindowHandle() final { + return static_cast<HWND>(GetNativeData(NS_NATIVE_WINDOW)); + } + + /* + * Return the parent window, if it exists. + */ + virtual nsWindowBase* GetParentWindowBase(bool aIncludeOwner) = 0; + + /* + * Return true if this is a top level widget. + */ + virtual bool IsTopLevelWidget() = 0; + + /* + * Init a standard gecko event for this widget. + * @param aEvent the event to initialize. + * @param aPoint message position in physical coordinates. + */ + virtual void InitEvent(mozilla::WidgetGUIEvent& aEvent, + LayoutDeviceIntPoint* aPoint = nullptr) = 0; + + /* + * Returns WidgetEventTime instance which is initialized with current message + * time. + */ + virtual WidgetEventTime CurrentMessageWidgetEventTime() const = 0; + + /* + * Dispatch a gecko event for this widget. + * Returns true if it's consumed. Otherwise, false. + */ + virtual bool DispatchWindowEvent(mozilla::WidgetGUIEvent* aEvent) = 0; + + /* + * Dispatch a gecko keyboard event for this widget. This + * is called by KeyboardLayout to dispatch gecko events. + * Returns true if it's consumed. Otherwise, false. + */ + virtual bool DispatchKeyboardEvent(mozilla::WidgetKeyboardEvent* aEvent) = 0; + + /* + * Dispatch a gecko wheel event for this widget. This + * is called by ScrollHandler to dispatch gecko events. + * Returns true if it's consumed. Otherwise, false. + */ + virtual bool DispatchWheelEvent(mozilla::WidgetWheelEvent* aEvent) = 0; + + /* + * Dispatch a gecko content command event for this widget. This + * is called by ScrollHandler to dispatch gecko events. + * Returns true if it's consumed. Otherwise, false. + */ + virtual bool DispatchContentCommandEvent(mozilla::WidgetContentCommandEvent* aEvent) = 0; + + /* + * Default dispatch of a plugin event. + */ + virtual bool DispatchPluginEvent(const MSG& aMsg); + + /* + * Returns true if this should dispatch a plugin event. + */ + bool ShouldDispatchPluginEvent(); + + /* + * Touch input injection apis + */ + virtual nsresult SynthesizeNativeTouchPoint(uint32_t aPointerId, + TouchPointerState aPointerState, + LayoutDeviceIntPoint aPoint, + double aPointerPressure, + uint32_t aPointerOrientation, + nsIObserver* aObserver) override; + virtual nsresult ClearNativeTouchSequence(nsIObserver* aObserver) override; + + /* + * WM_APPCOMMAND common handler. + * Sends events via NativeKey::HandleAppCommandMessage(). + */ + virtual bool HandleAppCommandMsg(const MSG& aAppCommandMsg, + LRESULT *aRetValue); + +protected: + virtual int32_t LogToPhys(double aValue) = 0; + void ChangedDPI(); + + static bool InitTouchInjection(); + bool InjectTouchPoint(uint32_t aId, LayoutDeviceIntPoint& aPoint, + POINTER_FLAGS aFlags, uint32_t aPressure = 1024, + uint32_t aOrientation = 90); + + class PointerInfo + { + public: + PointerInfo(int32_t aPointerId, LayoutDeviceIntPoint& aPoint) : + mPointerId(aPointerId), + mPosition(aPoint) + { + } + + int32_t mPointerId; + LayoutDeviceIntPoint mPosition; + }; + + nsClassHashtable<nsUint32HashKey, PointerInfo> mActivePointers; + static bool sTouchInjectInitialized; + static InjectTouchInputPtr sInjectTouchFuncPtr; + + // This is used by SynthesizeNativeTouchPoint to maintain state between + // multiple synthesized points, in the case where we can't call InjectTouch + // directly. + mozilla::UniquePtr<mozilla::MultiTouchInput> mSynthesizedTouchInput; +protected: + InputContext mInputContext; +}; + +#endif // nsWindowBase_h_ diff --git a/widget/windows/nsWindowDbg.cpp b/widget/windows/nsWindowDbg.cpp new file mode 100644 index 0000000000..d5bfcddd9f --- /dev/null +++ b/widget/windows/nsWindowDbg.cpp @@ -0,0 +1,67 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * nsWindowDbg - Debug related utilities for nsWindow. + */ + +#include "mozilla/Logging.h" +#include "nsWindowDbg.h" +#include "WinUtils.h" + +using namespace mozilla::widget; + +extern mozilla::LazyLogModule gWindowsLog; + +#if defined(POPUP_ROLLUP_DEBUG_OUTPUT) +MSGFEventMsgInfo gMSGFEvents[] = { + "MSGF_DIALOGBOX", 0, + "MSGF_MESSAGEBOX", 1, + "MSGF_MENU", 2, + "MSGF_SCROLLBAR", 5, + "MSGF_NEXTWINDOW", 6, + "MSGF_MAX", 8, + "MSGF_USER", 4096, + nullptr, 0}; +#endif + +static long gEventCounter = 0; +static long gLastEventMsg = 0; + +void PrintEvent(UINT msg, bool aShowAllEvents, bool aShowMouseMoves) +{ + int inx = 0; + while (gAllEvents[inx].mId != msg && gAllEvents[inx].mStr != nullptr) { + inx++; + } + if (aShowAllEvents || (!aShowAllEvents && gLastEventMsg != (long)msg)) { + if (aShowMouseMoves || (!aShowMouseMoves && msg != 0x0020 && msg != 0x0200 && msg != 0x0084)) { + MOZ_LOG(gWindowsLog, LogLevel::Info, + ("%6d - 0x%04X %s\n", gEventCounter++, msg, + gAllEvents[inx].mStr ? gAllEvents[inx].mStr : "Unknown")); + gLastEventMsg = msg; + } + } +} + +#ifdef DEBUG +void DDError(const char *msg, HRESULT hr) +{ + /*XXX make nicer */ + MOZ_LOG(gWindowsLog, LogLevel::Error, + ("direct draw error %s: 0x%08lx\n", msg, hr)); +} +#endif + +#ifdef DEBUG_VK +bool is_vk_down(int vk) +{ + SHORT st = GetKeyState(vk); +#ifdef DEBUG + MOZ_LOG(gWindowsLog, LogLevel::Info, ("is_vk_down vk=%x st=%x\n",vk, st)); +#endif + return (st < 0); +} +#endif diff --git a/widget/windows/nsWindowDbg.h b/widget/windows/nsWindowDbg.h new file mode 100644 index 0000000000..6607699527 --- /dev/null +++ b/widget/windows/nsWindowDbg.h @@ -0,0 +1,61 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef WindowDbg_h__ +#define WindowDbg_h__ + +/* + * nsWindowDbg - Debug related utilities for nsWindow. + */ + +#include "nsWindowDefs.h" + +// Enabled main event loop debug event output +//#define EVENT_DEBUG_OUTPUT + +// Enables debug output for popup rollup hooks +//#define POPUP_ROLLUP_DEBUG_OUTPUT + +// Enable window size and state debug output +//#define WINSTATE_DEBUG_OUTPUT + +// nsIWidget defines a set of debug output statements +// that are called in various places within the code. +//#define WIDGET_DEBUG_OUTPUT + +// Enable IS_VK_DOWN debug output +//#define DEBUG_VK + +// Main event loop debug output flags +#if defined(EVENT_DEBUG_OUTPUT) +#define SHOW_REPEAT_EVENTS true +#define SHOW_MOUSEMOVE_EVENTS false +#endif // defined(EVENT_DEBUG_OUTPUT) + +void PrintEvent(UINT msg, bool aShowAllEvents, bool aShowMouseMoves); + +#if defined(POPUP_ROLLUP_DEBUG_OUTPUT) +typedef struct { + char * mStr; + int mId; +} MSGFEventMsgInfo; + +#define DISPLAY_NMM_PRT(_arg) MOZ_LOG(gWindowsLog, mozilla::LogLevel::Info, ((_arg))); +#else +#define DISPLAY_NMM_PRT(_arg) +#endif // defined(POPUP_ROLLUP_DEBUG_OUTPUT) + +#if defined(DEBUG) +void DDError(const char *msg, HRESULT hr); +#endif // defined(DEBUG) + +#if defined(DEBUG_VK) +bool is_vk_down(int vk); +#define IS_VK_DOWN is_vk_down +#else +#define IS_VK_DOWN(a) (GetKeyState(a) < 0) +#endif // defined(DEBUG_VK) + +#endif /* WindowDbg_h__ */ diff --git a/widget/windows/nsWindowDefs.h b/widget/windows/nsWindowDefs.h new file mode 100644 index 0000000000..48a9356d28 --- /dev/null +++ b/widget/windows/nsWindowDefs.h @@ -0,0 +1,147 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef WindowDefs_h__ +#define WindowDefs_h__ + +/* + * nsWindowDefs - nsWindow related definitions, consts, and macros. + */ + +#include "mozilla/widget/WinMessages.h" +#include "nsBaseWidget.h" +#include "nsdefs.h" +#include "resource.h" + +/************************************************************** + * + * SECTION: defines + * + **************************************************************/ + +// ConstrainPosition window positioning slop value +#define kWindowPositionSlop 20 + +// Origin of the system context menu when displayed in full screen mode +#define MOZ_SYSCONTEXT_X_POS 20 +#define MOZ_SYSCONTEXT_Y_POS 20 + +// Don't put more than this many rects in the dirty region, just fluff +// out to the bounding-box if there are more +#define MAX_RECTS_IN_REGION 100 + +//Tablet PC Mouse Input Source +#define TABLET_INK_SIGNATURE 0xFFFFFF00 +#define TABLET_INK_CHECK 0xFF515700 +#define TABLET_INK_TOUCH 0x00000080 +#define TABLET_INK_ID_MASK 0x0000007F +#define MOUSE_INPUT_SOURCE() WinUtils::GetMouseInputSource() +#define MOUSE_POINTERID() WinUtils::GetMousePointerID() + +/************************************************************** + * + * SECTION: enums + * + **************************************************************/ + +// nsWindow::sCanQuit +typedef enum +{ + TRI_UNKNOWN = -1, + TRI_FALSE = 0, + TRI_TRUE = 1 +} TriStateBool; + +/************************************************************** + * + * SECTION: constants + * + **************************************************************/ + +/* + * Native windows class names + * + * ::: IMPORTANT ::: + * + * External apps and drivers depend on window class names. + * For example, changing the window classes could break + * touchpad scrolling or screen readers. + */ +const uint32_t kMaxClassNameLength = 40; +const wchar_t kClassNameHidden[] = L"MozillaHiddenWindowClass"; +const wchar_t kClassNameGeneral[] = L"MozillaWindowClass"; +const wchar_t kClassNameDialog[] = L"MozillaDialogClass"; +const wchar_t kClassNameDropShadow[] = L"MozillaDropShadowWindowClass"; +const wchar_t kClassNameTemp[] = L"MozillaTempWindowClass"; +const wchar_t kClassNameTransition[] = L"MozillaTransitionWindowClass"; + +/************************************************************** + * + * SECTION: structs + * + **************************************************************/ + +// Used for synthesizing events +struct KeyPair +{ + uint8_t mGeneral; + uint8_t mSpecific; + uint16_t mScanCode; + KeyPair(uint32_t aGeneral, uint32_t aSpecific) + : mGeneral(aGeneral & 0xFF) + , mSpecific(aSpecific & 0xFF) + , mScanCode((aGeneral & 0xFFFF0000) >> 16) + { + } +}; + +#if (WINVER < 0x0600) +struct TITLEBARINFOEX +{ + DWORD cbSize; + RECT rcTitleBar; + DWORD rgstate[CCHILDREN_TITLEBAR + 1]; + RECT rgrect[CCHILDREN_TITLEBAR + 1]; +}; +#endif + +namespace mozilla { +namespace widget { + +struct MSGResult +{ + // Result for the message. + LRESULT& mResult; + // If mConsumed is true, the caller shouldn't call next wndproc. + bool mConsumed; + + MSGResult(LRESULT* aResult = nullptr) : + mResult(aResult ? *aResult : mDefaultResult), mConsumed(false) + { + } + +private: + LRESULT mDefaultResult; +}; + +} // namespace widget +} // namespace mozilla + +/************************************************************** + * + * SECTION: macros + * + **************************************************************/ + +#define NSRGB_2_COLOREF(color) \ + RGB(NS_GET_R(color),NS_GET_G(color),NS_GET_B(color)) +#define COLOREF_2_NSRGB(color) \ + NS_RGB(GetRValue(color), GetGValue(color), GetBValue(color)) + +#define VERIFY_WINDOW_STYLE(s) \ + NS_ASSERTION(((s) & (WS_CHILD | WS_POPUP)) != (WS_CHILD | WS_POPUP), \ + "WS_POPUP and WS_CHILD are mutually exclusive") + +#endif /* WindowDefs_h__ */ diff --git a/widget/windows/nsWindowGfx.cpp b/widget/windows/nsWindowGfx.cpp new file mode 100644 index 0000000000..a88631f89e --- /dev/null +++ b/widget/windows/nsWindowGfx.cpp @@ -0,0 +1,686 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * nsWindowGfx - Painting and aceleration. + */ + +/************************************************************** + ************************************************************** + ** + ** BLOCK: Includes + ** + ** Include headers. + ** + ************************************************************** + **************************************************************/ + +#include "mozilla/dom/ContentParent.h" +#include "mozilla/plugins/PluginInstanceParent.h" +using mozilla::plugins::PluginInstanceParent; + +#include "nsWindowGfx.h" +#include "nsAppRunner.h" +#include <windows.h> +#include "gfxEnv.h" +#include "gfxImageSurface.h" +#include "gfxUtils.h" +#include "gfxWindowsSurface.h" +#include "gfxWindowsPlatform.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/DataSurfaceHelpers.h" +#include "mozilla/gfx/Tools.h" +#include "mozilla/RefPtr.h" +#include "mozilla/UniquePtrExtensions.h" +#include "nsGfxCIID.h" +#include "gfxContext.h" +#include "prmem.h" +#include "WinUtils.h" +#include "nsIWidgetListener.h" +#include "mozilla/Unused.h" +#include "nsDebug.h" +#include "nsIXULRuntime.h" + +#include "mozilla/layers/CompositorBridgeParent.h" +#include "mozilla/layers/CompositorBridgeChild.h" +#include "ClientLayerManager.h" + +#include "nsUXThemeData.h" +#include "nsUXThemeConstants.h" + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::layers; +using namespace mozilla::widget; +using namespace mozilla::plugins; + +/************************************************************** + ************************************************************** + ** + ** BLOCK: Variables + ** + ** nsWindow Class static initializations and global variables. + ** + ************************************************************** + **************************************************************/ + +/************************************************************** + * + * SECTION: nsWindow statics + * + **************************************************************/ + +static UniquePtr<uint8_t[]> sSharedSurfaceData; +static IntSize sSharedSurfaceSize; + +struct IconMetrics { + int32_t xMetric; + int32_t yMetric; + int32_t defaultSize; +}; + +// Corresponds 1:1 to the IconSizeType enum +static IconMetrics sIconMetrics[] = { + {SM_CXSMICON, SM_CYSMICON, 16}, // small icon + {SM_CXICON, SM_CYICON, 32} // regular icon +}; + +/************************************************************** + ************************************************************** + ** + ** BLOCK: nsWindow impl. + ** + ** Paint related nsWindow methods. + ** + ************************************************************** + **************************************************************/ + +// GetRegionToPaint returns the invalidated region that needs to be painted +nsIntRegion nsWindow::GetRegionToPaint(bool aForceFullRepaint, + PAINTSTRUCT ps, HDC aDC) +{ + if (aForceFullRepaint) { + RECT paintRect; + ::GetClientRect(mWnd, &paintRect); + return nsIntRegion(WinUtils::ToIntRect(paintRect)); + } + + HRGN paintRgn = ::CreateRectRgn(0, 0, 0, 0); + if (paintRgn != nullptr) { + int result = GetRandomRgn(aDC, paintRgn, SYSRGN); + if (result == 1) { + POINT pt = {0,0}; + ::MapWindowPoints(nullptr, mWnd, &pt, 1); + ::OffsetRgn(paintRgn, pt.x, pt.y); + } + nsIntRegion rgn(WinUtils::ConvertHRGNToRegion(paintRgn)); + ::DeleteObject(paintRgn); + return rgn; + } + return nsIntRegion(WinUtils::ToIntRect(ps.rcPaint)); +} + +#define WORDSSIZE(x) ((x).width * (x).height) +static bool +EnsureSharedSurfaceSize(IntSize size) +{ + IntSize screenSize; + screenSize.height = GetSystemMetrics(SM_CYSCREEN); + screenSize.width = GetSystemMetrics(SM_CXSCREEN); + + if (WORDSSIZE(screenSize) > WORDSSIZE(size)) + size = screenSize; + + if (WORDSSIZE(screenSize) < WORDSSIZE(size)) + NS_WARNING("Trying to create a shared surface larger than the screen"); + + if (!sSharedSurfaceData || (WORDSSIZE(size) > WORDSSIZE(sSharedSurfaceSize))) { + sSharedSurfaceSize = size; + sSharedSurfaceData = + MakeUniqueFallible<uint8_t[]>(WORDSSIZE(sSharedSurfaceSize) * 4); + } + + return !sSharedSurfaceData; +} + +nsIWidgetListener* nsWindow::GetPaintListener() +{ + if (mDestroyCalled) + return nullptr; + return mAttachedWidgetListener ? mAttachedWidgetListener : mWidgetListener; +} + +void nsWindow::ForcePresent() +{ + if (mResizeState != RESIZING) { + if (CompositorBridgeChild* remoteRenderer = GetRemoteRenderer()) { + remoteRenderer->SendForcePresent(); + } + } +} + +bool nsWindow::OnPaint(HDC aDC, uint32_t aNestingLevel) +{ + // We never have reentrant paint events, except when we're running our RPC + // windows event spin loop. If we don't trap for this, we'll try to paint, + // but view manager will refuse to paint the surface, resulting is black + // flashes on the plugin rendering surface. + if (mozilla::ipc::MessageChannel::IsSpinLoopActive() && mPainting) + return false; + + DeviceResetReason resetReason = DeviceResetReason::OK; + if (gfxWindowsPlatform::GetPlatform()->DidRenderingDeviceReset(&resetReason)) { + + gfxCriticalNote << "(nsWindow) Detected device reset: " << (int)resetReason; + + gfxWindowsPlatform::GetPlatform()->UpdateRenderMode(); + EnumAllWindows([] (nsWindow* aWindow) -> void { + aWindow->OnRenderingDeviceReset(); + }); + + gfxCriticalNote << "(nsWindow) Finished device reset."; + + return false; + } + + // After we CallUpdateWindow to the child, occasionally a WM_PAINT message + // is posted to the parent event loop with an empty update rect. Do a + // dummy paint so that Windows stops dispatching WM_PAINT in an inifinite + // loop. See bug 543788. + if (IsPlugin()) { + RECT updateRect; + if (!GetUpdateRect(mWnd, &updateRect, FALSE) || + (updateRect.left == updateRect.right && + updateRect.top == updateRect.bottom)) { + PAINTSTRUCT ps; + BeginPaint(mWnd, &ps); + EndPaint(mWnd, &ps); + return true; + } + + if (mWindowType == eWindowType_plugin_ipc_chrome) { + // Fire off an async request to the plugin to paint its window + mozilla::dom::ContentParent::SendAsyncUpdate(this); + ValidateRect(mWnd, nullptr); + return true; + } + + PluginInstanceParent* instance = reinterpret_cast<PluginInstanceParent*>( + ::GetPropW(mWnd, L"PluginInstanceParentProperty")); + if (instance) { + Unused << instance->CallUpdateWindow(); + } else { + // We should never get here since in-process plugins should have + // subclassed our HWND and handled WM_PAINT, but in some cases that + // could fail. Return without asserting since it's not our fault. + NS_WARNING("Plugin failed to subclass our window"); + } + + ValidateRect(mWnd, nullptr); + return true; + } + + ClientLayerManager *clientLayerManager = GetLayerManager()->AsClientLayerManager(); + + if (clientLayerManager && !mBounds.IsEqualEdges(mLastPaintBounds)) { + // Do an early async composite so that we at least have something on the + // screen in the right place, even if the content is out of date. + clientLayerManager->Composite(); + } + mLastPaintBounds = mBounds; + + PAINTSTRUCT ps; + +#ifdef MOZ_XUL + if (!aDC && (eTransparencyTransparent == mTransparencyMode)) + { + // For layered translucent windows all drawing should go to memory DC and no + // WM_PAINT messages are normally generated. To support asynchronous painting + // we force generation of WM_PAINT messages by invalidating window areas with + // RedrawWindow, InvalidateRect or InvalidateRgn function calls. + // BeginPaint/EndPaint must be called to make Windows think that invalid area + // is painted. Otherwise it will continue sending the same message endlessly. + ::BeginPaint(mWnd, &ps); + ::EndPaint(mWnd, &ps); + + // We're guaranteed to have a widget proxy since we called GetLayerManager(). + aDC = mCompositorWidgetDelegate->GetTransparentDC(); + } +#endif + + mPainting = true; + +#ifdef WIDGET_DEBUG_OUTPUT + HRGN debugPaintFlashRegion = nullptr; + HDC debugPaintFlashDC = nullptr; + + if (debug_WantPaintFlashing()) + { + debugPaintFlashRegion = ::CreateRectRgn(0, 0, 0, 0); + ::GetUpdateRgn(mWnd, debugPaintFlashRegion, TRUE); + debugPaintFlashDC = ::GetDC(mWnd); + } +#endif // WIDGET_DEBUG_OUTPUT + + HDC hDC = aDC ? aDC : (::BeginPaint(mWnd, &ps)); + mPaintDC = hDC; + +#ifdef MOZ_XUL + bool forceRepaint = aDC || (eTransparencyTransparent == mTransparencyMode); +#else + bool forceRepaint = nullptr != aDC; +#endif + nsIntRegion region = GetRegionToPaint(forceRepaint, ps, hDC); + + if (clientLayerManager) { + // We need to paint to the screen even if nothing changed, since if we + // don't have a compositing window manager, our pixels could be stale. + clientLayerManager->SetNeedsComposite(true); + clientLayerManager->SendInvalidRegion(region); + } + + RefPtr<nsWindow> strongThis(this); + + nsIWidgetListener* listener = GetPaintListener(); + if (listener) { + listener->WillPaintWindow(this); + } + // Re-get the listener since the will paint notification may have killed it. + listener = GetPaintListener(); + if (!listener) { + return false; + } + + if (clientLayerManager && clientLayerManager->NeedsComposite()) { + clientLayerManager->Composite(); + clientLayerManager->SetNeedsComposite(false); + } + + bool result = true; + if (!region.IsEmpty() && listener) + { + // Should probably pass in a real region here, using GetRandomRgn + // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/gdi/clipping_4q0e.asp + +#ifdef WIDGET_DEBUG_OUTPUT + debug_DumpPaintEvent(stdout, + this, + region, + "noname", + (int32_t) mWnd); +#endif // WIDGET_DEBUG_OUTPUT + + switch (GetLayerManager()->GetBackendType()) { + case LayersBackend::LAYERS_BASIC: + { + RefPtr<gfxASurface> targetSurface; + +#if defined(MOZ_XUL) + // don't support transparency for non-GDI rendering, for now + if (eTransparencyTransparent == mTransparencyMode) { + targetSurface = mBasicLayersSurface->EnsureTransparentSurface(); + } +#endif + + RefPtr<gfxWindowsSurface> targetSurfaceWin; + if (!targetSurface) + { + uint32_t flags = (mTransparencyMode == eTransparencyOpaque) ? 0 : + gfxWindowsSurface::FLAG_IS_TRANSPARENT; + targetSurfaceWin = new gfxWindowsSurface(hDC, flags); + targetSurface = targetSurfaceWin; + } + + if (!targetSurface) { + NS_ERROR("Invalid RenderMode!"); + return false; + } + + RECT paintRect; + ::GetClientRect(mWnd, &paintRect); + RefPtr<DrawTarget> dt = + gfxPlatform::GetPlatform()->CreateDrawTargetForSurface(targetSurface, + IntSize(paintRect.right - paintRect.left, + paintRect.bottom - paintRect.top)); + if (!dt || !dt->IsValid()) { + gfxWarning() << "nsWindow::OnPaint failed in CreateDrawTargetForSurface"; + return false; + } + + // don't need to double buffer with anything but GDI + BufferMode doubleBuffering = mozilla::layers::BufferMode::BUFFER_NONE; +#ifdef MOZ_XUL + switch (mTransparencyMode) { + case eTransparencyGlass: + case eTransparencyBorderlessGlass: + default: + // If we're not doing translucency, then double buffer + doubleBuffering = mozilla::layers::BufferMode::BUFFERED; + break; + case eTransparencyTransparent: + // If we're rendering with translucency, we're going to be + // rendering the whole window; make sure we clear it first + dt->ClearRect(Rect(0.f, 0.f, + dt->GetSize().width, dt->GetSize().height)); + break; + } +#else + doubleBuffering = mozilla::layers::BufferMode::BUFFERED; +#endif + + RefPtr<gfxContext> thebesContext = gfxContext::CreateOrNull(dt); + MOZ_ASSERT(thebesContext); // already checked draw target above + + { + AutoLayerManagerSetup + setupLayerManager(this, thebesContext, doubleBuffering); + result = listener->PaintWindow( + this, LayoutDeviceIntRegion::FromUnknownRegion(region)); + } + +#ifdef MOZ_XUL + if (eTransparencyTransparent == mTransparencyMode) { + // Data from offscreen drawing surface was copied to memory bitmap of transparent + // bitmap. Now it can be read from memory bitmap to apply alpha channel and after + // that displayed on the screen. + mBasicLayersSurface->RedrawTransparentWindow(); + } +#endif + } + break; + case LayersBackend::LAYERS_CLIENT: + { + result = listener->PaintWindow( + this, LayoutDeviceIntRegion::FromUnknownRegion(region)); + if (!gfxEnv::DisableForcePresent() && gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled()) { + nsCOMPtr<nsIRunnable> event = + NewRunnableMethod(this, &nsWindow::ForcePresent); + NS_DispatchToMainThread(event); + } + } + break; + default: + NS_ERROR("Unknown layers backend used!"); + break; + } + } + + if (!aDC) { + ::EndPaint(mWnd, &ps); + } + + mPaintDC = nullptr; + mLastPaintEndTime = TimeStamp::Now(); + +#if defined(WIDGET_DEBUG_OUTPUT) + if (debug_WantPaintFlashing()) + { + // Only flash paint events which have not ignored the paint message. + // Those that ignore the paint message aren't painting anything so there + // is only the overhead of the dispatching the paint event. + if (result) { + ::InvertRgn(debugPaintFlashDC, debugPaintFlashRegion); + PR_Sleep(PR_MillisecondsToInterval(30)); + ::InvertRgn(debugPaintFlashDC, debugPaintFlashRegion); + PR_Sleep(PR_MillisecondsToInterval(30)); + } + ::ReleaseDC(mWnd, debugPaintFlashDC); + ::DeleteObject(debugPaintFlashRegion); + } +#endif // WIDGET_DEBUG_OUTPUT + + mPainting = false; + + // Re-get the listener since painting may have killed it. + listener = GetPaintListener(); + if (listener) + listener->DidPaintWindow(); + + if (aNestingLevel == 0 && ::GetUpdateRect(mWnd, nullptr, false)) { + OnPaint(aDC, 1); + } + + return result; +} + +IntSize nsWindowGfx::GetIconMetrics(IconSizeType aSizeType) { + int32_t width = ::GetSystemMetrics(sIconMetrics[aSizeType].xMetric); + int32_t height = ::GetSystemMetrics(sIconMetrics[aSizeType].yMetric); + + if (width == 0 || height == 0) { + width = height = sIconMetrics[aSizeType].defaultSize; + } + + return IntSize(width, height); +} + +nsresult nsWindowGfx::CreateIcon(imgIContainer *aContainer, + bool aIsCursor, + uint32_t aHotspotX, + uint32_t aHotspotY, + IntSize aScaledSize, + HICON *aIcon) { + + MOZ_ASSERT((aScaledSize.width > 0 && aScaledSize.height > 0) || + (aScaledSize.width == 0 && aScaledSize.height == 0)); + + // Get the image data + RefPtr<SourceSurface> surface = + aContainer->GetFrame(imgIContainer::FRAME_CURRENT, + imgIContainer::FLAG_SYNC_DECODE); + NS_ENSURE_TRUE(surface, NS_ERROR_NOT_AVAILABLE); + + IntSize frameSize = surface->GetSize(); + if (frameSize.IsEmpty()) { + return NS_ERROR_FAILURE; + } + + IntSize iconSize(aScaledSize.width, aScaledSize.height); + if (iconSize == IntSize(0, 0)) { // use frame's intrinsic size + iconSize = frameSize; + } + + RefPtr<DataSourceSurface> dataSurface; + bool mappedOK; + DataSourceSurface::MappedSurface map; + + if (iconSize != frameSize) { + // Scale the surface + dataSurface = Factory::CreateDataSourceSurface(iconSize, + SurfaceFormat::B8G8R8A8); + NS_ENSURE_TRUE(dataSurface, NS_ERROR_FAILURE); + mappedOK = dataSurface->Map(DataSourceSurface::MapType::READ_WRITE, &map); + NS_ENSURE_TRUE(mappedOK, NS_ERROR_FAILURE); + + RefPtr<DrawTarget> dt = + Factory::CreateDrawTargetForData(BackendType::CAIRO, + map.mData, + dataSurface->GetSize(), + map.mStride, + SurfaceFormat::B8G8R8A8); + if (!dt) { + gfxWarning() << "nsWindowGfx::CreatesIcon failed in CreateDrawTargetForData"; + return NS_ERROR_OUT_OF_MEMORY; + } + dt->DrawSurface(surface, + Rect(0, 0, iconSize.width, iconSize.height), + Rect(0, 0, frameSize.width, frameSize.height), + DrawSurfaceOptions(), + DrawOptions(1.0f, CompositionOp::OP_SOURCE)); + } else if (surface->GetFormat() != SurfaceFormat::B8G8R8A8) { + // Convert format to SurfaceFormat::B8G8R8A8 + dataSurface = gfxUtils:: + CopySurfaceToDataSourceSurfaceWithFormat(surface, + SurfaceFormat::B8G8R8A8); + NS_ENSURE_TRUE(dataSurface, NS_ERROR_FAILURE); + mappedOK = dataSurface->Map(DataSourceSurface::MapType::READ, &map); + } else { + dataSurface = surface->GetDataSurface(); + NS_ENSURE_TRUE(dataSurface, NS_ERROR_FAILURE); + mappedOK = dataSurface->Map(DataSourceSurface::MapType::READ, &map); + } + NS_ENSURE_TRUE(dataSurface && mappedOK, NS_ERROR_FAILURE); + MOZ_ASSERT(dataSurface->GetFormat() == SurfaceFormat::B8G8R8A8); + + uint8_t* data = nullptr; + UniquePtr<uint8_t[]> autoDeleteArray; + if (map.mStride == BytesPerPixel(dataSurface->GetFormat()) * iconSize.width) { + // Mapped data is already packed + data = map.mData; + } else { + // We can't use map.mData since the pixels are not packed (as required by + // CreateDIBitmap, which is called under the DataToBitmap call below). + // + // We must unmap before calling SurfaceToPackedBGRA because it needs access + // to the pixel data. + dataSurface->Unmap(); + map.mData = nullptr; + + autoDeleteArray = SurfaceToPackedBGRA(dataSurface); + data = autoDeleteArray.get(); + NS_ENSURE_TRUE(data, NS_ERROR_FAILURE); + } + + HBITMAP bmp = DataToBitmap(data, iconSize.width, -iconSize.height, 32); + uint8_t* a1data = Data32BitTo1Bit(data, iconSize.width, iconSize.height); + if (map.mData) { + dataSurface->Unmap(); + } + if (!a1data) { + return NS_ERROR_FAILURE; + } + + HBITMAP mbmp = DataToBitmap(a1data, iconSize.width, -iconSize.height, 1); + PR_Free(a1data); + + ICONINFO info = {0}; + info.fIcon = !aIsCursor; + info.xHotspot = aHotspotX; + info.yHotspot = aHotspotY; + info.hbmMask = mbmp; + info.hbmColor = bmp; + + HCURSOR icon = ::CreateIconIndirect(&info); + ::DeleteObject(mbmp); + ::DeleteObject(bmp); + if (!icon) + return NS_ERROR_FAILURE; + *aIcon = icon; + return NS_OK; +} + +// Adjust cursor image data +uint8_t* nsWindowGfx::Data32BitTo1Bit(uint8_t* aImageData, + uint32_t aWidth, uint32_t aHeight) +{ + // We need (aWidth + 7) / 8 bytes plus zero-padding up to a multiple of + // 4 bytes for each row (HBITMAP requirement). Bug 353553. + uint32_t outBpr = ((aWidth + 31) / 8) & ~3; + + // Allocate and clear mask buffer + uint8_t* outData = (uint8_t*)PR_Calloc(outBpr, aHeight); + if (!outData) + return nullptr; + + int32_t *imageRow = (int32_t*)aImageData; + for (uint32_t curRow = 0; curRow < aHeight; curRow++) { + uint8_t *outRow = outData + curRow * outBpr; + uint8_t mask = 0x80; + for (uint32_t curCol = 0; curCol < aWidth; curCol++) { + // Use sign bit to test for transparency, as alpha byte is highest byte + if (*imageRow++ < 0) + *outRow |= mask; + + mask >>= 1; + if (!mask) { + outRow ++; + mask = 0x80; + } + } + } + + return outData; +} + +/** + * Convert the given image data to a HBITMAP. If the requested depth is + * 32 bit, a bitmap with an alpha channel will be returned. + * + * @param aImageData The image data to convert. Must use the format accepted + * by CreateDIBitmap. + * @param aWidth With of the bitmap, in pixels. + * @param aHeight Height of the image, in pixels. + * @param aDepth Image depth, in bits. Should be one of 1, 24 and 32. + * + * @return The HBITMAP representing the image. Caller should call + * DeleteObject when done with the bitmap. + * On failure, nullptr will be returned. + */ +HBITMAP nsWindowGfx::DataToBitmap(uint8_t* aImageData, + uint32_t aWidth, + uint32_t aHeight, + uint32_t aDepth) +{ + HDC dc = ::GetDC(nullptr); + + if (aDepth == 32) { + // Alpha channel. We need the new header. + BITMAPV4HEADER head = { 0 }; + head.bV4Size = sizeof(head); + head.bV4Width = aWidth; + head.bV4Height = aHeight; + head.bV4Planes = 1; + head.bV4BitCount = aDepth; + head.bV4V4Compression = BI_BITFIELDS; + head.bV4SizeImage = 0; // Uncompressed + head.bV4XPelsPerMeter = 0; + head.bV4YPelsPerMeter = 0; + head.bV4ClrUsed = 0; + head.bV4ClrImportant = 0; + + head.bV4RedMask = 0x00FF0000; + head.bV4GreenMask = 0x0000FF00; + head.bV4BlueMask = 0x000000FF; + head.bV4AlphaMask = 0xFF000000; + + HBITMAP bmp = ::CreateDIBitmap(dc, + reinterpret_cast<CONST BITMAPINFOHEADER*>(&head), + CBM_INIT, + aImageData, + reinterpret_cast<CONST BITMAPINFO*>(&head), + DIB_RGB_COLORS); + ::ReleaseDC(nullptr, dc); + return bmp; + } + + char reserved_space[sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * 2]; + BITMAPINFOHEADER& head = *(BITMAPINFOHEADER*)reserved_space; + + head.biSize = sizeof(BITMAPINFOHEADER); + head.biWidth = aWidth; + head.biHeight = aHeight; + head.biPlanes = 1; + head.biBitCount = (WORD)aDepth; + head.biCompression = BI_RGB; + head.biSizeImage = 0; // Uncompressed + head.biXPelsPerMeter = 0; + head.biYPelsPerMeter = 0; + head.biClrUsed = 0; + head.biClrImportant = 0; + + BITMAPINFO& bi = *(BITMAPINFO*)reserved_space; + + if (aDepth == 1) { + RGBQUAD black = { 0, 0, 0, 0 }; + RGBQUAD white = { 255, 255, 255, 0 }; + + bi.bmiColors[0] = white; + bi.bmiColors[1] = black; + } + + HBITMAP bmp = ::CreateDIBitmap(dc, &head, CBM_INIT, aImageData, &bi, DIB_RGB_COLORS); + ::ReleaseDC(nullptr, dc); + return bmp; +} diff --git a/widget/windows/nsWindowGfx.h b/widget/windows/nsWindowGfx.h new file mode 100644 index 0000000000..3f4d763621 --- /dev/null +++ b/widget/windows/nsWindowGfx.h @@ -0,0 +1,35 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef WindowGfx_h__ +#define WindowGfx_h__ + +/* + * nsWindowGfx - Painting and aceleration. + */ + +#include "nsWindow.h" +#include <imgIContainer.h> + +class nsWindowGfx { +public: + enum IconSizeType { + kSmallIcon, + kRegularIcon + }; + static mozilla::gfx::IntSize GetIconMetrics(IconSizeType aSizeType); + static nsresult CreateIcon(imgIContainer *aContainer, bool aIsCursor, + uint32_t aHotspotX, uint32_t aHotspotY, + mozilla::gfx::IntSize aScaledSize, HICON *aIcon); + +private: + /** + * Cursor helpers + */ + static uint8_t* Data32BitTo1Bit(uint8_t* aImageData, uint32_t aWidth, uint32_t aHeight); + static HBITMAP DataToBitmap(uint8_t* aImageData, uint32_t aWidth, uint32_t aHeight, uint32_t aDepth); +}; + +#endif // WindowGfx_h__ diff --git a/widget/windows/nsdefs.h b/widget/windows/nsdefs.h new file mode 100644 index 0000000000..4e0c1c3973 --- /dev/null +++ b/widget/windows/nsdefs.h @@ -0,0 +1,45 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef NSDEFS_H +#define NSDEFS_H + +#include <windows.h> + +#ifdef _DEBUG + #define BREAK_TO_DEBUGGER DebugBreak() +#else + #define BREAK_TO_DEBUGGER +#endif + +#ifdef _DEBUG + #define VERIFY(exp) if (!(exp)) { GetLastError(); BREAK_TO_DEBUGGER; } +#else // !_DEBUG + #define VERIFY(exp) (exp) +#endif // !_DEBUG + +// Win32 logging modules: +// nsWindow, nsSound, and nsClipboard +// +// Logging can be changed at runtime without recompiling in the General +// property page of Visual Studio under the "Environment" property. +// +// Two variables are of importance to be set: +// MOZ_LOG and MOZ_LOG_FILE +// +// MOZ_LOG: +// MOZ_LOG=all:5 (To log everything completely) +// MOZ_LOG=nsWindow:5,nsSound:5,nsClipboard:5 +// (To log windows widget stuff) +// MOZ_LOG= (To turn off logging) +// +// MOZ_LOG_FILE: +// MOZ_LOG_FILE=C:\log.txt (To a file on disk) +// MOZ_LOG_FILE=WinDebug (To the debug window) +// MOZ_LOG_FILE= (To stdout/stderr) + +#endif // NSDEFS_H + + diff --git a/widget/windows/res/aliasb.cur b/widget/windows/res/aliasb.cur 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..45258ab2be --- /dev/null +++ b/widget/windows/resource.h @@ -0,0 +1,16 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#define IDC_GRAB 4101 +#define IDC_GRABBING 4102 +#define IDC_CELL 4103 +#define IDC_COPY 4104 +#define IDC_ALIAS 4105 +#define IDC_ZOOMIN 4106 +#define IDC_ZOOMOUT 4107 +#define IDC_COLRESIZE 4108 +#define IDC_ROWRESIZE 4109 +#define IDC_VERTICALTEXT 4110 +#define IDC_DUMMY_CE_MENUBAR 4111 +#define IDC_NONE 4112 diff --git a/widget/windows/tests/TestWinDND.cpp b/widget/windows/tests/TestWinDND.cpp new file mode 100644 index 0000000000..096633b098 --- /dev/null +++ b/widget/windows/tests/TestWinDND.cpp @@ -0,0 +1,716 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#define MOZILLA_INTERNAL_API + +#include <ole2.h> +#include <shlobj.h> + +#include "TestHarness.h" +#include "nsArray.h" +#include "nsIFile.h" +#include "nsNetUtil.h" +#include "nsISupportsPrimitives.h" +#include "nsIFileURL.h" +#include "nsITransferable.h" +#include "nsClipboard.h" +#include "nsDataObjCollection.h" + +nsIFile* xferFile; + +nsresult CheckValidHDROP(STGMEDIUM* pSTG) +{ + if (pSTG->tymed != TYMED_HGLOBAL) { + fail("Received data is not in an HGLOBAL"); + return NS_ERROR_UNEXPECTED; + } + + HGLOBAL hGlobal = pSTG->hGlobal; + DROPFILES* pDropFiles; + pDropFiles = (DROPFILES*)GlobalLock(hGlobal); + if (!pDropFiles) { + fail("There is no data at the given HGLOBAL"); + return NS_ERROR_UNEXPECTED; + } + + if (pDropFiles->pFiles != sizeof(DROPFILES)) + fail("DROPFILES struct has wrong size"); + + if (pDropFiles->fWide != true) { + fail("Received data is not Unicode"); + return NS_ERROR_UNEXPECTED; + } + nsString s; + unsigned long offset = 0; + while (1) { + s = (char16_t*)((char*)pDropFiles + pDropFiles->pFiles + offset); + if (s.IsEmpty()) + break; + nsresult rv; + nsCOMPtr<nsIFile> localFile( + do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv)); + rv = localFile->InitWithPath(s); + if (NS_FAILED(rv)) { + fail("File could not be opened"); + return NS_ERROR_UNEXPECTED; + } + offset += sizeof(char16_t) * (s.Length() + 1); + } + return NS_OK; +} + +nsresult CheckValidTEXT(STGMEDIUM* pSTG) +{ + if (pSTG->tymed != TYMED_HGLOBAL) { + fail("Received data is not in an HGLOBAL"); + return NS_ERROR_UNEXPECTED; + } + + HGLOBAL hGlobal = pSTG->hGlobal; + char* pText; + pText = (char*)GlobalLock(hGlobal); + if (!pText) { + fail("There is no data at the given HGLOBAL"); + return NS_ERROR_UNEXPECTED; + } + + nsCString string; + string = pText; + + if (!string.EqualsLiteral("Mozilla can drag and drop")) { + fail("Text passed through drop object wrong"); + return NS_ERROR_UNEXPECTED; + } + return NS_OK; +} + +nsresult CheckValidTEXTTwo(STGMEDIUM* pSTG) +{ + if (pSTG->tymed != TYMED_HGLOBAL) { + fail("Received data is not in an HGLOBAL"); + return NS_ERROR_UNEXPECTED; + } + + HGLOBAL hGlobal = pSTG->hGlobal; + char* pText; + pText = (char*)GlobalLock(hGlobal); + if (!pText) { + fail("There is no data at the given HGLOBAL"); + return NS_ERROR_UNEXPECTED; + } + + nsCString string; + string = pText; + + if (!string.EqualsLiteral("Mozilla can drag and drop twice over")) { + fail("Text passed through drop object wrong"); + return NS_ERROR_UNEXPECTED; + } + return NS_OK; +} + +nsresult CheckValidUNICODE(STGMEDIUM* pSTG) +{ + if (pSTG->tymed != TYMED_HGLOBAL) { + fail("Received data is not in an HGLOBAL"); + return NS_ERROR_UNEXPECTED; + } + + HGLOBAL hGlobal = pSTG->hGlobal; + char16_t* pText; + pText = (char16_t*)GlobalLock(hGlobal); + if (!pText) { + fail("There is no data at the given HGLOBAL"); + return NS_ERROR_UNEXPECTED; + } + + nsString string; + string = pText; + + if (!string.EqualsLiteral("Mozilla can drag and drop")) { + fail("Text passed through drop object wrong"); + return NS_ERROR_UNEXPECTED; + } + return NS_OK; +} + +nsresult CheckValidUNICODETwo(STGMEDIUM* pSTG) +{ + if (pSTG->tymed != TYMED_HGLOBAL) { + fail("Received data is not in an HGLOBAL"); + return NS_ERROR_UNEXPECTED; + } + + HGLOBAL hGlobal = pSTG->hGlobal; + char16_t* pText; + pText = (char16_t*)GlobalLock(hGlobal); + if (!pText) { + fail("There is no data at the given HGLOBAL"); + return NS_ERROR_UNEXPECTED; + } + + nsString string; + string = pText; + + if (!string.EqualsLiteral("Mozilla can drag and drop twice over")) { + fail("Text passed through drop object wrong"); + return NS_ERROR_UNEXPECTED; + } + return NS_OK; +} + +nsresult GetTransferableFile(nsCOMPtr<nsITransferable>& pTransferable) +{ + nsresult rv; + + nsCOMPtr<nsISupports> genericWrapper = do_QueryInterface(xferFile); + + pTransferable = do_CreateInstance("@mozilla.org/widget/transferable;1"); + pTransferable->Init(nullptr); + rv = pTransferable->SetTransferData("application/x-moz-file", genericWrapper, + 0); + return rv; +} + +nsresult GetTransferableText(nsCOMPtr<nsITransferable>& pTransferable) +{ + nsresult rv; + NS_NAMED_LITERAL_STRING(mozString, "Mozilla can drag and drop"); + nsCOMPtr<nsISupportsString> xferString = + do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID); + rv = xferString->SetData(mozString); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsISupports> genericWrapper = do_QueryInterface(xferString); + + pTransferable = do_CreateInstance("@mozilla.org/widget/transferable;1"); + pTransferable->Init(nullptr); + rv = pTransferable->SetTransferData("text/unicode", genericWrapper, + mozString.Length() * sizeof(char16_t)); + return rv; +} + +nsresult GetTransferableTextTwo(nsCOMPtr<nsITransferable>& pTransferable) +{ + nsresult rv; + NS_NAMED_LITERAL_STRING(mozString, " twice over"); + nsCOMPtr<nsISupportsString> xferString = + do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID); + rv = xferString->SetData(mozString); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsISupports> genericWrapper = do_QueryInterface(xferString); + + pTransferable = do_CreateInstance("@mozilla.org/widget/transferable;1"); + pTransferable->Init(nullptr); + rv = pTransferable->SetTransferData("text/unicode", genericWrapper, + mozString.Length() * sizeof(char16_t)); + return rv; +} + +nsresult GetTransferableURI(nsCOMPtr<nsITransferable>& pTransferable) +{ + nsresult rv; + + nsCOMPtr<nsIURI> xferURI; + + rv = NS_NewURI(getter_AddRefs(xferURI), "http://www.mozilla.org"); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsISupports> genericWrapper = do_QueryInterface(xferURI); + + pTransferable = do_CreateInstance("@mozilla.org/widget/transferable;1"); + pTransferable->Init(nullptr); + rv = pTransferable->SetTransferData("text/x-moz-url", genericWrapper, 0); + return rv; +} + +nsresult MakeDataObject(nsIArray* transferableArray, + RefPtr<IDataObject>& itemToDrag) +{ + nsresult rv; + uint32_t itemCount = 0; + + nsCOMPtr<nsIURI> uri; + rv = NS_NewURI(getter_AddRefs(uri), "http://www.mozilla.org"); + NS_ENSURE_SUCCESS(rv, rv); + + rv = transferableArray->GetLength(&itemCount); + NS_ENSURE_SUCCESS(rv, rv); + + // Copied more or less exactly from nsDragService::InvokeDragSession + // This is what lets us play fake Drag Service for the test + if (itemCount > 1) { + nsDataObjCollection * dataObjCollection = new nsDataObjCollection(); + if (!dataObjCollection) + return NS_ERROR_OUT_OF_MEMORY; + itemToDrag = dataObjCollection; + for (uint32_t i=0; i<itemCount; ++i) { + nsCOMPtr<nsITransferable> trans = do_QueryElementAt(transferableArray, i); + if (trans) { + RefPtr<IDataObject> dataObj; + rv = nsClipboard::CreateNativeDataObject(trans, + getter_AddRefs(dataObj), uri); + NS_ENSURE_SUCCESS(rv, rv); + // Add the flavors to the collection object too + rv = nsClipboard::SetupNativeDataObject(trans, dataObjCollection); + NS_ENSURE_SUCCESS(rv, rv); + + dataObjCollection->AddDataObject(dataObj); + } + } + } // if dragging multiple items + else { + nsCOMPtr<nsITransferable> trans = do_QueryElementAt(transferableArray, 0); + if (trans) { + rv = nsClipboard::CreateNativeDataObject(trans, + getter_AddRefs(itemToDrag), + uri); + NS_ENSURE_SUCCESS(rv, rv); + } + } // else dragging a single object + return rv; +} + +nsresult Do_CheckOneFile() +{ + nsresult rv; + nsCOMPtr<nsITransferable> transferable; + nsCOMPtr<nsIMutableArray> transferableArray = nsArray::Create(); + nsCOMPtr<nsISupports> genericWrapper; + RefPtr<IDataObject> dataObj; + + rv = GetTransferableFile(transferable); + if (NS_FAILED(rv)) { + fail("Could not create the proper nsITransferable!"); + return rv; + } + genericWrapper = do_QueryInterface(transferable); + rv = transferableArray->AppendElement(genericWrapper); + if (NS_FAILED(rv)) { + fail("Could not append element to transferable array"); + return rv; + } + + rv = MakeDataObject(transferableArray, dataObj); + if (NS_FAILED(rv)) { + fail("Could not create data object"); + return rv; + } + + FORMATETC fe; + SET_FORMATETC(fe, CF_HDROP, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL); + if (dataObj->QueryGetData(&fe) != S_OK) { + fail("File data object does not support the file data type!"); + return NS_ERROR_UNEXPECTED; + } + + STGMEDIUM* stg; + stg = (STGMEDIUM*)CoTaskMemAlloc(sizeof(STGMEDIUM)); + if (dataObj->GetData(&fe, stg) != S_OK) { + fail("File data object did not provide data on request"); + return NS_ERROR_UNEXPECTED; + } + + rv = CheckValidHDROP(stg); + if (NS_FAILED(rv)) { + fail("HDROP was invalid"); + return rv; + } + + ReleaseStgMedium(stg); + + return NS_OK; +} + +nsresult Do_CheckTwoFiles() +{ + nsresult rv; + nsCOMPtr<nsITransferable> transferable; + nsCOMPtr<nsIMutableArray> transferableArray = nsArray::Create(); + nsCOMPtr<nsISupports> genericWrapper; + RefPtr<IDataObject> dataObj; + + rv = GetTransferableFile(transferable); + if (NS_FAILED(rv)) { + fail("Could not create the proper nsITransferable!"); + return rv; + } + genericWrapper = do_QueryInterface(transferable); + rv = transferableArray->AppendElement(genericWrapper); + if (NS_FAILED(rv)) { + fail("Could not append element to transferable array"); + return rv; + } + + rv = GetTransferableFile(transferable); + if (NS_FAILED(rv)) { + fail("Could not create the proper nsITransferable!"); + return rv; + } + genericWrapper = do_QueryInterface(transferable); + rv = transferableArray->AppendElement(genericWrapper); + if (NS_FAILED(rv)) { + fail("Could not append element to transferable array"); + return rv; + } + + rv = MakeDataObject(transferableArray, dataObj); + if (NS_FAILED(rv)) { + fail("Could not create data object"); + return rv; + } + + FORMATETC fe; + SET_FORMATETC(fe, CF_HDROP, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL); + if (dataObj->QueryGetData(&fe) != S_OK) { + fail("File data object does not support the file data type!"); + return NS_ERROR_UNEXPECTED; + } + + STGMEDIUM* stg; + stg = (STGMEDIUM*)CoTaskMemAlloc(sizeof(STGMEDIUM)); + if (dataObj->GetData(&fe, stg) != S_OK) { + fail("File data object did not provide data on request"); + return NS_ERROR_UNEXPECTED; + } + + rv = CheckValidHDROP(stg); + if (NS_FAILED(rv)) { + fail("HDROP was invalid"); + return rv; + } + + ReleaseStgMedium(stg); + + return NS_OK; +} + +nsresult Do_CheckOneString() +{ + nsresult rv; + nsCOMPtr<nsITransferable> transferable; + nsCOMPtr<nsIMutableArray> transferableArray = nsArray::Create(); + nsCOMPtr<nsISupports> genericWrapper; + RefPtr<IDataObject> dataObj; + + rv = GetTransferableText(transferable); + if (NS_FAILED(rv)) { + fail("Could not create the proper nsITransferable!"); + return rv; + } + genericWrapper = do_QueryInterface(transferable); + rv = transferableArray->AppendElement(genericWrapper); + if (NS_FAILED(rv)) { + fail("Could not append element to transferable array"); + return rv; + } + + rv = MakeDataObject(transferableArray, dataObj); + if (NS_FAILED(rv)) { + fail("Could not create data object"); + return rv; + } + + FORMATETC fe; + SET_FORMATETC(fe, CF_TEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL); + if (dataObj->QueryGetData(&fe) != S_OK) { + fail("String data object does not support the ASCII text data type!"); + return NS_ERROR_UNEXPECTED; + } + + STGMEDIUM* stg; + stg = (STGMEDIUM*)CoTaskMemAlloc(sizeof(STGMEDIUM)); + if (dataObj->GetData(&fe, stg) != S_OK) { + fail("String data object did not provide ASCII data on request"); + return NS_ERROR_UNEXPECTED; + } + + rv = CheckValidTEXT(stg); + if (NS_FAILED(rv)) { + fail("TEXT was invalid"); + return rv; + } + + ReleaseStgMedium(stg); + + SET_FORMATETC(fe, CF_UNICODETEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL); + if (dataObj->QueryGetData(&fe) != S_OK) { + fail("String data object does not support the wide text data type!"); + return NS_ERROR_UNEXPECTED; + } + + if (dataObj->GetData(&fe, stg) != S_OK) { + fail("String data object did not provide wide data on request"); + return NS_ERROR_UNEXPECTED; + } + + rv = CheckValidUNICODE(stg); + if (NS_FAILED(rv)) { + fail("UNICODE was invalid"); + return rv; + } + + return NS_OK; +} + +nsresult Do_CheckTwoStrings() +{ + nsresult rv; + nsCOMPtr<nsITransferable> transferable; + nsCOMPtr<nsIMutableArray> transferableArray = nsArray::Create(); + nsCOMPtr<nsISupports> genericWrapper; + RefPtr<IDataObject> dataObj; + + rv = GetTransferableText(transferable); + if (NS_FAILED(rv)) { + fail("Could not create the proper nsITransferable!"); + return rv; + } + genericWrapper = do_QueryInterface(transferable); + rv = transferableArray->AppendElement(genericWrapper); + if (NS_FAILED(rv)) { + fail("Could not append element to transferable array"); + return rv; + } + + rv = GetTransferableTextTwo(transferable); + if (NS_FAILED(rv)) { + fail("Could not create the proper nsITransferable!"); + return rv; + } + genericWrapper = do_QueryInterface(transferable); + rv = transferableArray->AppendElement(genericWrapper); + if (NS_FAILED(rv)) { + fail("Could not append element to transferable array"); + return rv; + } + + rv = MakeDataObject(transferableArray, dataObj); + if (NS_FAILED(rv)) { + fail("Could not create data object"); + return rv; + } + + FORMATETC fe; + SET_FORMATETC(fe, CF_TEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL); + if (dataObj->QueryGetData(&fe) != S_OK) { + fail("String data object does not support the ASCII text data type!"); + return NS_ERROR_UNEXPECTED; + } + + STGMEDIUM* stg; + stg = (STGMEDIUM*)CoTaskMemAlloc(sizeof(STGMEDIUM)); + if (dataObj->GetData(&fe, stg) != S_OK) { + fail("String data object did not provide ASCII data on request"); + return NS_ERROR_UNEXPECTED; + } + + rv = CheckValidTEXTTwo(stg); + if (NS_FAILED(rv)) { + fail("TEXT was invalid"); + return rv; + } + + ReleaseStgMedium(stg); + + SET_FORMATETC(fe, CF_UNICODETEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL); + if (dataObj->QueryGetData(&fe) != S_OK) { + fail("String data object does not support the wide text data type!"); + return NS_ERROR_UNEXPECTED; + } + + if (dataObj->GetData(&fe, stg) != S_OK) { + fail("String data object did not provide wide data on request"); + return NS_ERROR_UNEXPECTED; + } + + rv = CheckValidUNICODETwo(stg); + if (NS_FAILED(rv)) { + fail("UNICODE was invalid"); + return rv; + } + + return NS_OK; +} + +nsresult Do_CheckSetArbitraryData(bool aMultiple) +{ + nsresult rv; + nsCOMPtr<nsITransferable> transferable; + nsCOMPtr<nsIMutableArray> transferableArray = nsArray::Create(); + nsCOMPtr<nsISupports> genericWrapper; + RefPtr<IDataObject> dataObj; + + rv = GetTransferableText(transferable); + if (NS_FAILED(rv)) { + fail("Could not create the proper nsITransferable!"); + return rv; + } + genericWrapper = do_QueryInterface(transferable); + rv = transferableArray->AppendElement(genericWrapper); + if (NS_FAILED(rv)) { + fail("Could not append element to transferable array"); + return rv; + } + + if (aMultiple) { + rv = GetTransferableText(transferable); + if (NS_FAILED(rv)) { + fail("Could not create the proper nsITransferable!"); + return rv; + } + genericWrapper = do_QueryInterface(transferable); + rv = transferableArray->AppendElement(genericWrapper); + if (NS_FAILED(rv)) { + fail("Could not append element to transferable array"); + return rv; + } + } + + rv = MakeDataObject(transferableArray, dataObj); + if (NS_FAILED(rv)) { + fail("Could not create data object"); + return rv; + } + + static CLIPFORMAT mozArbitraryFormat = + ::RegisterClipboardFormatW(L"MozillaTestFormat"); + FORMATETC fe; + STGMEDIUM stg; + SET_FORMATETC(fe, mozArbitraryFormat, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL); + + HGLOBAL hg = GlobalAlloc(GPTR, 1024); + stg.tymed = TYMED_HGLOBAL; + stg.hGlobal = hg; + stg.pUnkForRelease = nullptr; + + if (dataObj->SetData(&fe, &stg, true) != S_OK) { + if (aMultiple) { + fail("Unable to set arbitrary data type on data object collection!"); + } else { + fail("Unable to set arbitrary data type on data object!"); + } + return NS_ERROR_UNEXPECTED; + } + + if (dataObj->QueryGetData(&fe) != S_OK) { + fail("Arbitrary data set on data object is not advertised!"); + return NS_ERROR_UNEXPECTED; + } + + STGMEDIUM* stg2; + stg2 = (STGMEDIUM*)CoTaskMemAlloc(sizeof(STGMEDIUM)); + if (dataObj->GetData(&fe, stg2) != S_OK) { + fail("Data object did not provide arbitrary data upon request!"); + return NS_ERROR_UNEXPECTED; + } + + if (stg2->hGlobal != hg) { + fail("Arbitrary data was not returned properly!"); + return rv; + } + ReleaseStgMedium(stg2); + + return NS_OK; +} + +// This function performs basic drop tests, testing a data object consisting +// of one transferable +nsresult Do_Test1() +{ + nsresult rv = NS_OK; + nsresult workingrv; + + workingrv = Do_CheckOneFile(); + if (NS_FAILED(workingrv)) { + fail("Drag object tests failed on a single file"); + rv = NS_ERROR_UNEXPECTED; + } else { + passed("Successfully created a working file drag object!"); + } + + workingrv = Do_CheckOneString(); + if (NS_FAILED(workingrv)) { + fail("Drag object tests failed on a single string"); + rv = NS_ERROR_UNEXPECTED; + } else { + passed("Successfully created a working string drag object!"); + } + + workingrv = Do_CheckSetArbitraryData(false); + if (NS_FAILED(workingrv)) { + fail("Drag object tests failed on setting arbitrary data"); + rv = NS_ERROR_UNEXPECTED; + } else { + passed("Successfully set arbitrary data on a drag object"); + } + + return rv; +} + +// This function performs basic drop tests, testing a data object consisting of +// two transferables. +nsresult Do_Test2() +{ + nsresult rv = NS_OK; + nsresult workingrv; + + workingrv = Do_CheckTwoFiles(); + if (NS_FAILED(workingrv)) { + fail("Drag object tests failed on multiple files"); + rv = NS_ERROR_UNEXPECTED; + } else { + passed("Successfully created a working multiple file drag object!"); + } + + workingrv = Do_CheckTwoStrings(); + if (NS_FAILED(workingrv)) { + fail("Drag object tests failed on multiple strings"); + rv = NS_ERROR_UNEXPECTED; + } else { + passed("Successfully created a working multiple string drag object!"); + } + + workingrv = Do_CheckSetArbitraryData(true); + if (NS_FAILED(workingrv)) { + fail("Drag object tests failed on setting arbitrary data"); + rv = NS_ERROR_UNEXPECTED; + } else { + passed("Successfully set arbitrary data on a drag object"); + } + + return rv; +} + +// This function performs advanced drag and drop tests, testing a data object +// consisting of multiple transferables that have different data types +nsresult Do_Test3() +{ + nsresult rv = NS_OK; + nsresult workingrv; + + // XXX TODO Write more advanced tests in Bug 535860 + return rv; +} + +int main(int argc, char** argv) +{ + ScopedXPCOM xpcom("Test Windows Drag and Drop"); + + nsCOMPtr<nsIFile> file; + file = xpcom.GetProfileDirectory(); + xferFile = file; + + if (NS_SUCCEEDED(Do_Test1())) + passed("Basic Drag and Drop data type tests (single transferable) succeeded!"); + + if (NS_SUCCEEDED(Do_Test2())) + passed("Basic Drag and Drop data type tests (multiple transferables) succeeded!"); + +//if (NS_SUCCEEDED(Do_Test3())) +// passed("Advanced Drag and Drop data type tests succeeded!"); + + return gFailCount; +} diff --git a/widget/windows/tests/moz.build b/widget/windows/tests/moz.build new file mode 100644 index 0000000000..28919c271d --- /dev/null +++ b/widget/windows/tests/moz.build @@ -0,0 +1,6 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + diff --git a/widget/windows/touchinjection_sdk80.h b/widget/windows/touchinjection_sdk80.h new file mode 100644 index 0000000000..e93f62089b --- /dev/null +++ b/widget/windows/touchinjection_sdk80.h @@ -0,0 +1,107 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef touchinjection_sdk80_h +#define touchinjection_sdk80_h + +// Note, this isn't inclusive of all touch injection header info. +// You may need to add more to expand on current apis. + +#ifndef TOUCH_FEEDBACK_DEFAULT + +#define TOUCH_FEEDBACK_DEFAULT 0x1 +#define TOUCH_FEEDBACK_INDIRECT 0x2 +#define TOUCH_FEEDBACK_NONE 0x3 + +enum { + PT_POINTER = 0x00000001, // Generic pointer + PT_TOUCH = 0x00000002, // Touch + PT_PEN = 0x00000003, // Pen + PT_MOUSE = 0x00000004, // Mouse +}; + +typedef DWORD POINTER_INPUT_TYPE; +typedef UINT32 POINTER_FLAGS; + +typedef enum { + POINTER_CHANGE_NONE, + POINTER_CHANGE_FIRSTBUTTON_DOWN, + POINTER_CHANGE_FIRSTBUTTON_UP, + POINTER_CHANGE_SECONDBUTTON_DOWN, + POINTER_CHANGE_SECONDBUTTON_UP, + POINTER_CHANGE_THIRDBUTTON_DOWN, + POINTER_CHANGE_THIRDBUTTON_UP, + POINTER_CHANGE_FOURTHBUTTON_DOWN, + POINTER_CHANGE_FOURTHBUTTON_UP, + POINTER_CHANGE_FIFTHBUTTON_DOWN, + POINTER_CHANGE_FIFTHBUTTON_UP, +} POINTER_BUTTON_CHANGE_TYPE; + +typedef struct { + POINTER_INPUT_TYPE pointerType; + UINT32 pointerId; + UINT32 frameId; + POINTER_FLAGS pointerFlags; + HANDLE sourceDevice; + HWND hwndTarget; + POINT ptPixelLocation; + POINT ptHimetricLocation; + POINT ptPixelLocationRaw; + POINT ptHimetricLocationRaw; + DWORD dwTime; + UINT32 historyCount; + INT32 InputData; + DWORD dwKeyStates; + UINT64 PerformanceCount; + POINTER_BUTTON_CHANGE_TYPE ButtonChangeType; +} POINTER_INFO; + +typedef UINT32 TOUCH_FLAGS; +typedef UINT32 TOUCH_MASK; + +typedef struct { + POINTER_INFO pointerInfo; + TOUCH_FLAGS touchFlags; + TOUCH_MASK touchMask; + RECT rcContact; + RECT rcContactRaw; + UINT32 orientation; + UINT32 pressure; +} POINTER_TOUCH_INFO; + +#define TOUCH_FLAG_NONE 0x00000000 // Default + +#define TOUCH_MASK_NONE 0x00000000 // Default - none of the optional fields are valid +#define TOUCH_MASK_CONTACTAREA 0x00000001 // The rcContact field is valid +#define TOUCH_MASK_ORIENTATION 0x00000002 // The orientation field is valid +#define TOUCH_MASK_PRESSURE 0x00000004 // The pressure field is valid + +#define POINTER_FLAG_NONE 0x00000000 // Default +#define POINTER_FLAG_NEW 0x00000001 // New pointer +#define POINTER_FLAG_INRANGE 0x00000002 // Pointer has not departed +#define POINTER_FLAG_INCONTACT 0x00000004 // Pointer is in contact +#define POINTER_FLAG_FIRSTBUTTON 0x00000010 // Primary action +#define POINTER_FLAG_SECONDBUTTON 0x00000020 // Secondary action +#define POINTER_FLAG_THIRDBUTTON 0x00000040 // Third button +#define POINTER_FLAG_FOURTHBUTTON 0x00000080 // Fourth button +#define POINTER_FLAG_FIFTHBUTTON 0x00000100 // Fifth button +#define POINTER_FLAG_PRIMARY 0x00002000 // Pointer is primary +#define POINTER_FLAG_CONFIDENCE 0x00004000 // Pointer is considered unlikely to be accidental +#define POINTER_FLAG_CANCELED 0x00008000 // Pointer is departing in an abnormal manner +#define POINTER_FLAG_DOWN 0x00010000 // Pointer transitioned to down state (made contact) +#define POINTER_FLAG_UPDATE 0x00020000 // Pointer update +#define POINTER_FLAG_UP 0x00040000 // Pointer transitioned from down state (broke contact) +#define POINTER_FLAG_WHEEL 0x00080000 // Vertical wheel +#define POINTER_FLAG_HWHEEL 0x00100000 // Horizontal wheel +#define POINTER_FLAG_CAPTURECHANGED 0x00200000 // Lost capture + +#endif // TOUCH_FEEDBACK_DEFAULT + +#define TOUCH_FLAGS_CONTACTUPDATE (POINTER_FLAG_UPDATE|POINTER_FLAG_INRANGE|POINTER_FLAG_INCONTACT) +#define TOUCH_FLAGS_CONTACTDOWN (POINTER_FLAG_DOWN|POINTER_FLAG_INRANGE|POINTER_FLAG_INCONTACT) + +typedef BOOL (WINAPI* InitializeTouchInjectionPtr)(UINT32 maxCount, DWORD dwMode); +typedef BOOL (WINAPI* InjectTouchInputPtr)(UINT32 count, CONST POINTER_TOUCH_INFO *info); + +#endif // touchinjection_sdk80_h
\ No newline at end of file diff --git a/widget/windows/widget.rc b/widget/windows/widget.rc new file mode 100644 index 0000000000..9361f9e48c --- /dev/null +++ b/widget/windows/widget.rc @@ -0,0 +1,30 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "resource.h" +#include <winresrc.h> +#include <dlgs.h> + +IDC_GRAB CURSOR DISCARDABLE "res/grab.cur" +IDC_GRABBING CURSOR DISCARDABLE "res/grabbing.cur" +IDC_CELL CURSOR DISCARDABLE "res/cell.cur" +IDC_COPY CURSOR DISCARDABLE "res/copy.cur" +IDC_ALIAS CURSOR DISCARDABLE "res/aliasb.cur" +IDC_ZOOMIN CURSOR DISCARDABLE "res/zoom_in.cur" +IDC_ZOOMOUT CURSOR DISCARDABLE "res/zoom_out.cur" +IDC_COLRESIZE CURSOR DISCARDABLE "res/col_resize.cur" +IDC_ROWRESIZE CURSOR DISCARDABLE "res/row_resize.cur" +IDC_VERTICALTEXT CURSOR DISCARDABLE "res/vertical_text.cur" +IDC_NONE CURSOR DISCARDABLE "res/none.cur" + +OPTPROPSHEET DIALOG DISCARDABLE 32, 32, 288, 226 +STYLE DS_MODALFRAME | DS_3DLOOK | DS_CONTEXTHELP | WS_POPUP | WS_VISIBLE | + WS_CAPTION | WS_SYSMENU +CAPTION "Options" +FONT 8, "MS Sans Serif" +BEGIN + +END |