gecko-dev/widget/windows/nsWindowBase.cpp
Kartikaya Gupta cb026d2536 Bug 1313170 - Use the widget-level touch injection instead of the OS-level touch injection for some tests. r=dvander
When using the InjectTouchInput API on Windows, the API requires that the caller
keep providing input frames (by calling the API function) at least every 100ms.
If the caller fails to do so, Windows can return an ERROR_TIMEOUT and throw
away the touch sequence. In some tests, it is hard for us to make this guarantee,
because we need to wait for other events between the touchdown and touchup. For
these tests, we can use the widget-level touch injection code that we have as
a fallback for the OS-level touch injection code. The widget-level touch
injection is less representative of real-world usage but allows us to bypass
the timeout problem.

MozReview-Commit-ID: EoVUSZmERUw

--HG--
extra : rebase_source : 4f7ca1a1e6da4479d51526e8f132f5cb23fceb49
2016-10-31 10:05:15 -04:00

244 lines
7.4 KiB
C++

/* -*- 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;
}