diff --git a/gfx/thebes/gfxPrefs.h b/gfx/thebes/gfxPrefs.h index 0dfc074e4867..ed470e083aa5 100644 --- a/gfx/thebes/gfxPrefs.h +++ b/gfx/thebes/gfxPrefs.h @@ -197,6 +197,11 @@ private: // Use vsync events generated by hardware DECL_GFX_PREF(Once, "gfx.frameuniformity.hw-vsync", FrameUniformityHWVsyncEnabled, bool, false); + DECL_GFX_PREF(Once, "gfx.touch.resample", TouchResampling, bool, false); + // These times should be in nanoseconds + DECL_GFX_PREF(Once, "gfx.touch.resample.max-predict", TouchResampleMaxPredict, int32_t, 8000000); + DECL_GFX_PREF(Once, "gfx.touch.resample.vsync-adjust", TouchVsyncSampleAdjust, int32_t, 5000000); + DECL_GFX_PREF(Once, "gfx.touch.resample.min-resample", TouchResampleMinTime, int32_t, 2000000); DECL_GFX_PREF(Live, "gl.msaa-level", MSAALevel, uint32_t, 2); diff --git a/widget/gonk/GeckoTouchDispatcher.cpp b/widget/gonk/GeckoTouchDispatcher.cpp new file mode 100644 index 000000000000..3a64f506a96a --- /dev/null +++ b/widget/gonk/GeckoTouchDispatcher.cpp @@ -0,0 +1,451 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 sts=2 et sw=2 tw=80: */ +/* Copyright 2014 Mozilla Foundation and Mozilla contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "FrameMetrics.h" +#include "GeckoProfiler.h" +#include "GeckoTouchDispatcher.h" +#include "InputData.h" +#include "base/basictypes.h" +#include "gfxPrefs.h" +#include "libui/Input.h" +#include "mozilla/MouseEvents.h" +#include "mozilla/Mutex.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/TouchEvents.h" +#include "mozilla/dom/Touch.h" +#include "nsAppShell.h" +#include "nsDebug.h" +#include "nsThreadUtils.h" +#include "nsWindow.h" +#include +#include +#include + +#define LOG(args...) \ + __android_log_print(ANDROID_LOG_INFO, "Gonk" , ## args) + +// uncomment to print log resample data +// #define LOG_RESAMPLE_DATA 1 + +namespace mozilla { + +// Amount of time in MS before an input is considered expired. +static const uint64_t kInputExpirationThresholdMs = 1000; +static int32_t nanosecToMillisec(int64_t nanosec) { return nanosec / 1000000; } + +static StaticRefPtr sTouchDispatcher; + +GeckoTouchDispatcher::GeckoTouchDispatcher() + : mTouchQueueLock("GeckoTouchDispatcher::mTouchQueueLock") + , mTouchEventsFiltered(false) + , mTouchDownCount(0) + , mTouchTimeDiff(0) + , mLastTouchTime(0) +{ + // Since GeckoTouchDispatcher is initialized when input is initialized + // and reads gfxPrefs, it is the first thing to touch gfxPrefs. + // The first thing to touch gfxPrefs MUST occur on the main thread and init + // the singleton + MOZ_ASSERT(sTouchDispatcher == nullptr); + MOZ_ASSERT(NS_IsMainThread()); + gfxPrefs::GetSingleton(); + + mEnabledUniformityInfo = gfxPrefs::UniformityInfo(); + mResamplingEnabled = gfxPrefs::TouchResampling() && + gfxPrefs::FrameUniformityHWVsyncEnabled(); + mVsyncAdjust = gfxPrefs::TouchVsyncSampleAdjust(); + mMaxPredict = gfxPrefs::TouchResampleMaxPredict(); + mMinResampleTime = gfxPrefs::TouchResampleMinTime(); + sTouchDispatcher = this; +} + +class DispatchTouchEventsMainThread : public nsRunnable +{ +public: + DispatchTouchEventsMainThread(GeckoTouchDispatcher* aTouchDispatcher, + uint64_t aVsyncTime) + : mTouchDispatcher(aTouchDispatcher) + , mVsyncTime(aVsyncTime) + { + } + + NS_IMETHOD Run() + { + mTouchDispatcher->DispatchTouchMoveEvents(mVsyncTime); + return NS_OK; + } + +private: + nsRefPtr mTouchDispatcher; + uint64_t mVsyncTime; +}; + +class DispatchSingleTouchMainThread : public nsRunnable +{ +public: + DispatchSingleTouchMainThread(GeckoTouchDispatcher* aTouchDispatcher, + MultiTouchInput& aTouch) + : mTouchDispatcher(aTouchDispatcher) + , mTouch(aTouch) + { + } + + NS_IMETHOD Run() + { + mTouchDispatcher->DispatchTouchEvent(mTouch); + return NS_OK; + } + +private: + nsRefPtr mTouchDispatcher; + MultiTouchInput mTouch; +}; + +// Timestamp is in nanoseconds +/* static */ bool +GeckoTouchDispatcher::NotifyVsync(uint64_t aVsyncTimestamp) +{ + if (sTouchDispatcher == nullptr) { + return false; + } + + MOZ_ASSERT(sTouchDispatcher->mResamplingEnabled); + bool haveTouchData = false; + { + MutexAutoLock lock(sTouchDispatcher->mTouchQueueLock); + haveTouchData = !sTouchDispatcher->mTouchMoveEvents.empty(); + } + + if (haveTouchData) { + NS_DispatchToMainThread(new DispatchTouchEventsMainThread(sTouchDispatcher, aVsyncTimestamp)); + } + + return haveTouchData; +} + +// Touch data timestamps are in milliseconds, aEventTime is in nanoseconds +void +GeckoTouchDispatcher::NotifyTouch(MultiTouchInput& aData, uint64_t aEventTime) +{ + if (mResamplingEnabled) { + switch (aData.mType) { + case MultiTouchInput::MULTITOUCH_MOVE: + { + MutexAutoLock lock(mTouchQueueLock); + mTouchMoveEvents.push_back(aData); + mTouchTimeDiff = aEventTime - mLastTouchTime; + mLastTouchTime = aEventTime; + return; + } + default: + break; + } + } + + NS_DispatchToMainThread(new DispatchSingleTouchMainThread(this, aData)); +} + +void +GeckoTouchDispatcher::DispatchTouchMoveEvents(uint64_t aVsyncTime) +{ + MultiTouchInput touchMove; + + { + MutexAutoLock lock(mTouchQueueLock); + if (mTouchMoveEvents.empty()) { + return; + } + + int touchCount = mTouchMoveEvents.size(); + // Both aVsynctime and mLastTouchTime are uint64_t + // Need to store as a signed int. + int64_t vsyncTouchDiff = aVsyncTime - mLastTouchTime; + bool resample = (touchCount > 1) && + (vsyncTouchDiff > mMinResampleTime); + + if (!resample) { + touchMove = mTouchMoveEvents.back(); + mTouchMoveEvents.clear(); + mTouchMoveEvents.push_back(touchMove); + } else { + ResampleTouchMoves(touchMove, aVsyncTime); + } + } + + DispatchTouchEvent(touchMove); +} + +static int +Interpolate(int start, int end, int64_t aFrameDiff, int64_t aTouchDiff) +{ + return start + (((end - start) * aFrameDiff) / aTouchDiff); +} + +static const SingleTouchData& +GetTouchByID(const SingleTouchData& aCurrentTouch, MultiTouchInput& aOtherTouch) +{ + int32_t id = aCurrentTouch.mIdentifier; + for (size_t i = 0; i < aOtherTouch.mTouches.Length(); i++) { + SingleTouchData& touch = aOtherTouch.mTouches[i]; + if (touch.mIdentifier == id) { + return touch; + } + } + + // We can have situations where a previous touch event had 2 fingers + // and we lift 1 finger off. In those cases, we won't find the touch event + // with given id, so just return the current touch, which will be resampled + // without modification and dispatched. + return aCurrentTouch; +} + +static void +ResampleTouch(MultiTouchInput& aOutTouch, MultiTouchInput& aCurrent, + MultiTouchInput& aOther, int64_t aFrameDiff, + int64_t aTouchDiff, bool aInterpolate) +{ + aOutTouch = aCurrent; + + // Make sure we only resample the correct finger. + for (size_t i = 0; i < aOutTouch.mTouches.Length(); i++) { + const SingleTouchData& current = aCurrent.mTouches[i]; + const SingleTouchData& other = GetTouchByID(current, aOther); + + const ScreenIntPoint& currentTouchPoint = current.mScreenPoint; + const ScreenIntPoint& otherTouchPoint = other.mScreenPoint; + + ScreenIntPoint newSamplePoint; + newSamplePoint.x = Interpolate(currentTouchPoint.x, otherTouchPoint.x, aFrameDiff, aTouchDiff); + newSamplePoint.y = Interpolate(currentTouchPoint.y, otherTouchPoint.y, aFrameDiff, aTouchDiff); + + aOutTouch.mTouches[i].mScreenPoint = newSamplePoint; + +#ifdef LOG_RESAMPLE_DATA + const char* type = "extrapolate"; + if (aInterpolate) { + type = "interpolate"; + } + + float alpha = (double) aFrameDiff / (double) aTouchDiff; + LOG("%s current (%d, %d), other (%d, %d) to (%d, %d) alpha %f, touch diff %llu, frame diff %lld\n", + type, + currentTouchPoint.x, currentTouchPoint.y, + otherTouchPoint.x, otherTouchPoint.y, + newSamplePoint.x, newSamplePoint.y, + alpha, aTouchDiff, aFrameDiff); +#endif + } +} + +// Interpolates with the touch event prior to SampleTime +// and with the future touch event past sample time +int32_t +GeckoTouchDispatcher::InterpolateTouch(MultiTouchInput& aOutTouch, uint64_t aSampleTime) +{ + MOZ_RELEASE_ASSERT(mTouchMoveEvents.size() >= 2); + mTouchQueueLock.AssertCurrentThreadOwns(); + + // currentTouch < SampleTime < futureTouch + MultiTouchInput futureTouch = mTouchMoveEvents.back(); + mTouchMoveEvents.pop_back(); + MultiTouchInput currentTouch = mTouchMoveEvents.back(); + + mTouchMoveEvents.clear(); + mTouchMoveEvents.push_back(futureTouch); + + uint64_t currentTouchTime = mLastTouchTime - mTouchTimeDiff; + int64_t frameDiff = aSampleTime - currentTouchTime; + ResampleTouch(aOutTouch, currentTouch, futureTouch, frameDiff, mTouchTimeDiff, true); + + return nanosecToMillisec(frameDiff); +} + +// Extrapolates from the previous two touch events before sample time +// and extrapolates them to sample time. +int32_t +GeckoTouchDispatcher::ExtrapolateTouch(MultiTouchInput& aOutTouch, uint64_t aSampleTime) +{ + MOZ_RELEASE_ASSERT(mTouchMoveEvents.size() >= 2); + mTouchQueueLock.AssertCurrentThreadOwns(); + + // prevTouch < currentTouch < SampleTime + MultiTouchInput currentTouch = mTouchMoveEvents.back(); + mTouchMoveEvents.pop_back(); + MultiTouchInput prevTouch = mTouchMoveEvents.back(); + mTouchMoveEvents.clear(); + mTouchMoveEvents.push_back(currentTouch); + + uint64_t currentTouchTime = mLastTouchTime; + int64_t maxResampleTime = std::min(mTouchTimeDiff / 2, (int64_t) mMaxPredict); + uint64_t maxTimestamp = currentTouchTime + maxResampleTime; + + if (aSampleTime > maxTimestamp) { + aSampleTime = maxTimestamp; + #ifdef LOG_RESAMPLE_DATA + LOG("Overshot extrapolation time, adjusting sample time\n"); + #endif + } + + // This has to be signed int since it is negative + int64_t frameDiff = currentTouchTime - aSampleTime; + ResampleTouch(aOutTouch, currentTouch, prevTouch, frameDiff, mTouchTimeDiff, false); + return -nanosecToMillisec(frameDiff); +} + +void +GeckoTouchDispatcher::ResampleTouchMoves(MultiTouchInput& aOutTouch, uint64_t aVsyncTime) +{ + uint64_t sampleTime = aVsyncTime - mVsyncAdjust; + int32_t touchTimeAdjust = 0; + + if (mLastTouchTime > sampleTime) { + touchTimeAdjust = InterpolateTouch(aOutTouch, sampleTime); + } else { + touchTimeAdjust = ExtrapolateTouch(aOutTouch, sampleTime); + } + + aOutTouch.mTimeStamp += TimeDuration::FromMilliseconds(touchTimeAdjust); + aOutTouch.mTime += touchTimeAdjust; +} + +// Some touch events get sent as mouse events. If APZ doesn't capture the event +// and if a touch only has 1 touch input, we can send a mouse event. +void +GeckoTouchDispatcher::DispatchMouseEvent(MultiTouchInput& aMultiTouch, + bool aForwardToChildren) +{ + WidgetMouseEvent mouseEvent = ToWidgetMouseEvent(aMultiTouch, nullptr); + if (mouseEvent.message == NS_EVENT_NULL) { + return; + } + + mouseEvent.mFlags.mNoCrossProcessBoundaryForwarding = !aForwardToChildren; + nsWindow::DispatchInputEvent(mouseEvent); +} + +static bool +IsExpired(const MultiTouchInput& aTouch) +{ + // No pending events, the filter state can be updated. + uint64_t timeNowMs = systemTime(SYSTEM_TIME_MONOTONIC) / 1000000; + return (timeNowMs - aTouch.mTime) > kInputExpirationThresholdMs; +} +void +GeckoTouchDispatcher::DispatchTouchEvent(MultiTouchInput& aMultiTouch) +{ + if (!mTouchDownCount) { + mTouchEventsFiltered = IsExpired(aMultiTouch); + } + + switch (aMultiTouch.mType) { + case MultiTouchInput::MULTITOUCH_START: + mTouchDownCount++; + break; + case MultiTouchInput::MULTITOUCH_MOVE: + break; + case MultiTouchInput::MULTITOUCH_END: + case MultiTouchInput::MULTITOUCH_CANCEL: + mTouchDownCount--; + if (mTouchDownCount == 0) { + MutexAutoLock lock(mTouchQueueLock); + mTouchMoveEvents.clear(); + } + break; + default: + break; + } + + if (mTouchEventsFiltered) { + return; + } + + bool captured = false; + WidgetTouchEvent event = aMultiTouch.ToWidgetTouchEvent(nullptr); + nsEventStatus status = nsWindow::DispatchInputEvent(event, &captured); + + if (mEnabledUniformityInfo) { + const char* touchAction = "Invalid"; + switch (aMultiTouch.mType) { + case MultiTouchInput::MULTITOUCH_START: + touchAction = "Touch_Event_Down"; + break; + case MultiTouchInput::MULTITOUCH_MOVE: + touchAction = "Touch_Event_Move"; + break; + case MultiTouchInput::MULTITOUCH_END: + case MultiTouchInput::MULTITOUCH_CANCEL: + touchAction = "Touch_Event_Up"; + break; + } + + const SingleTouchData& firstTouch = aMultiTouch.mTouches[0]; + const ScreenIntPoint& touchPoint = firstTouch.mScreenPoint; + + LOG("UniformityInfo %s %llu %d %d", touchAction, systemTime(SYSTEM_TIME_MONOTONIC), + touchPoint.x, touchPoint.y); + } + + if (!captured && (aMultiTouch.mTouches.Length() == 1)) { + bool forwardToChildren = status != nsEventStatus_eConsumeNoDefault; + DispatchMouseEvent(aMultiTouch, forwardToChildren); + } +} + +WidgetMouseEvent +GeckoTouchDispatcher::ToWidgetMouseEvent(const MultiTouchInput& aMultiTouch, + nsIWidget* aWidget) const +{ + NS_ABORT_IF_FALSE(NS_IsMainThread(), + "Can only convert To WidgetMouseEvent on main thread"); + + uint32_t mouseEventType = NS_EVENT_NULL; + switch (aMultiTouch.mType) { + case MultiTouchInput::MULTITOUCH_START: + mouseEventType = NS_MOUSE_BUTTON_DOWN; + break; + case MultiTouchInput::MULTITOUCH_MOVE: + mouseEventType = NS_MOUSE_MOVE; + break; + case MultiTouchInput::MULTITOUCH_CANCEL: + case MultiTouchInput::MULTITOUCH_END: + mouseEventType = NS_MOUSE_BUTTON_UP; + break; + default: + MOZ_ASSERT_UNREACHABLE("Did not assign a type to WidgetMouseEvent"); + break; + } + + WidgetMouseEvent event(true, mouseEventType, aWidget, + WidgetMouseEvent::eReal, WidgetMouseEvent::eNormal); + + const SingleTouchData& firstTouch = aMultiTouch.mTouches[0]; + event.refPoint.x = firstTouch.mScreenPoint.x; + event.refPoint.y = firstTouch.mScreenPoint.y; + + event.time = aMultiTouch.mTime; + event.button = WidgetMouseEvent::eLeftButton; + event.inputSource = nsIDOMMouseEvent::MOZ_SOURCE_TOUCH; + event.modifiers = aMultiTouch.modifiers; + + if (mouseEventType != NS_MOUSE_MOVE) { + event.clickCount = 1; + } + + return event; +} + +} // namespace mozilla diff --git a/widget/gonk/GeckoTouchDispatcher.h b/widget/gonk/GeckoTouchDispatcher.h new file mode 100644 index 000000000000..d2a9bab320b1 --- /dev/null +++ b/widget/gonk/GeckoTouchDispatcher.h @@ -0,0 +1,87 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 sts=2 et sw=2 tw=80: */ +/* Copyright 2014 Mozilla Foundation and Mozilla contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GECKO_TOUCH_INPUT_DISPATCHER_h +#define GECKO_TOUCH_INPUT_DISPATCHER_h + +#include "InputData.h" +#include "Units.h" +#include "mozilla/Mutex.h" +#include + +class nsIWidget; + +namespace mozilla { +class WidgetMouseEvent; + +// Used to resample touch events whenever a vsync event occurs. It batches +// touch moves and on every vsync, resamples the touch position to create smooth +// scrolls. We use the Android touch resample algorithm. It uses a combination of +// extrapolation and interpolation. The algorithm takes the vsync time and +// subtracts mVsyncAdjust time in ms and creates a sample time. All touch events are +// relative to this sample time. If the last touch event occurs AFTER this +// sample time, interpolate the last two touch events. If the last touch event occurs BEFORE +// this sample time, we extrapolate the last two touch events to the sample +// time. The magic numbers defined as constants are taken from android +// InputTransport.cpp. +class GeckoTouchDispatcher +{ + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GeckoTouchDispatcher) + +public: + GeckoTouchDispatcher(); + void NotifyTouch(MultiTouchInput& aData, uint64_t aEventTime); + void DispatchTouchEvent(MultiTouchInput& aMultiTouch); + void DispatchTouchMoveEvents(uint64_t aVsyncTime); + static bool NotifyVsync(uint64_t aVsyncTimestamp); + +private: + int32_t InterpolateTouch(MultiTouchInput& aOutTouch, uint64_t aSampleTime); + int32_t ExtrapolateTouch(MultiTouchInput& aOutTouch, uint64_t aSampleTime); + void ResampleTouchMoves(MultiTouchInput& aOutTouch, uint64_t vsyncTime); + void SendTouchEvent(MultiTouchInput& aData); + void DispatchMouseEvent(MultiTouchInput& aMultiTouch, + bool aForwardToChildren); + WidgetMouseEvent ToWidgetMouseEvent(const MultiTouchInput& aData, nsIWidget* aWidget) const; + + // mTouchQueueLock are used to protect the vector below + // as it is accessed on the vsync thread and main thread + Mutex mTouchQueueLock; + std::vector mTouchMoveEvents; + + bool mResamplingEnabled; + bool mTouchEventsFiltered; + bool mEnabledUniformityInfo; + int mTouchDownCount; + + // All times below are in nanoseconds + int32_t mVsyncAdjust; // Time from vsync we create sample times from + int32_t mMaxPredict; // How far into the future we're allowed to extrapolate + + // Amount of time between vsync and the last event that is required before we + // resample + int32_t mMinResampleTime; + + // The time difference between the last two touch move events + int64_t mTouchTimeDiff; + + // The system time at which the last touch event occured + uint64_t mLastTouchTime; +}; + +} // namespace mozilla +#endif // GECKO_TOUCH_INPUT_DISPATCHER_h diff --git a/widget/gonk/HwcComposer2D.cpp b/widget/gonk/HwcComposer2D.cpp index 165f3c3a4564..6dc779db8cdc 100644 --- a/widget/gonk/HwcComposer2D.cpp +++ b/widget/gonk/HwcComposer2D.cpp @@ -28,6 +28,7 @@ #include "mozilla/StaticPtr.h" #include "cutils/properties.h" #include "gfx2DGlue.h" +#include "GeckoTouchDispatcher.h" #if ANDROID_VERSION >= 17 #include "libdisplay/FramebufferSurface.h" @@ -149,6 +150,10 @@ HwcComposer2D::Init(hwc_display_t dpy, hwc_surface_t sur, gl::GLContext* aGLCont mColorFill = false; mRBSwapSupport = false; } + + if (RegisterHwcEventCallback()) { + EnableVsync(true); + } #else char propValue[PROPERTY_VALUE_MAX]; property_get("ro.display.colorfill", propValue, "0"); @@ -223,7 +228,7 @@ HwcComposer2D::RunVsyncEventControl(bool aEnable) void HwcComposer2D::Vsync(int aDisplay, int64_t aTimestamp) { - // TODO: Handle Vsync event here + GeckoTouchDispatcher::NotifyVsync(aTimestamp); } #endif diff --git a/widget/gonk/moz.build b/widget/gonk/moz.build index 14f9d128d7c3..52b3452af8a4 100644 --- a/widget/gonk/moz.build +++ b/widget/gonk/moz.build @@ -44,6 +44,7 @@ SOURCES += ['libui/' + src for src in [ ]] SOURCES += [ + 'GeckoTouchDispatcher.cpp', 'GfxInfo.cpp', 'GonkMemoryPressureMonitoring.cpp', 'GonkPermission.cpp', diff --git a/widget/gonk/nsAppShell.cpp b/widget/gonk/nsAppShell.cpp index d0d9bc18d72f..133dd54b3912 100644 --- a/widget/gonk/nsAppShell.cpp +++ b/widget/gonk/nsAppShell.cpp @@ -72,6 +72,7 @@ // Defines kKeyMapping and GetKeyNameIndex() #include "GonkKeyMapping.h" +#include "GeckoTouchDispatcher.h" #define LOG(args...) \ __android_log_print(ANDROID_LOG_INFO, "Gonk" , ## args) @@ -157,14 +158,12 @@ struct UserInputData { ::Touch touches[MAX_POINTERS]; } motion; }; - - Modifiers DOMModifiers() const; }; -Modifiers -UserInputData::DOMModifiers() const +static mozilla::Modifiers +getDOMModifiers(int32_t metaState) { - Modifiers result = 0; + mozilla::Modifiers result = 0; if (metaState & (AMETA_ALT_ON | AMETA_ALT_LEFT_ON | AMETA_ALT_RIGHT_ON)) { result |= MODIFIER_ALT; } @@ -195,106 +194,6 @@ UserInputData::DOMModifiers() const return result; } -static void -sendMouseEvent(uint32_t msg, UserInputData& data, bool forwardToChildren) -{ - WidgetMouseEvent event(true, msg, nullptr, - WidgetMouseEvent::eReal, WidgetMouseEvent::eNormal); - - event.refPoint.x = data.motion.touches[0].coords.getX(); - event.refPoint.y = data.motion.touches[0].coords.getY(); - event.time = data.timeMs; - event.button = WidgetMouseEvent::eLeftButton; - event.inputSource = nsIDOMMouseEvent::MOZ_SOURCE_TOUCH; - if (msg != NS_MOUSE_MOVE) - event.clickCount = 1; - event.modifiers = data.DOMModifiers(); - - event.mFlags.mNoCrossProcessBoundaryForwarding = !forwardToChildren; - - nsWindow::DispatchInputEvent(event); -} - -static void -addDOMTouch(UserInputData& data, WidgetTouchEvent& event, int i) -{ - const ::Touch& touch = data.motion.touches[i]; - event.touches.AppendElement( - new dom::Touch(touch.id, - nsIntPoint(floor(touch.coords.getX() + 0.5), floor(touch.coords.getY() + 0.5)), - nsIntPoint(touch.coords.getAxisValue(AMOTION_EVENT_AXIS_SIZE), - touch.coords.getAxisValue(AMOTION_EVENT_AXIS_SIZE)), - 0, - touch.coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE)) - ); -} - -static void -printUniformityInfo(UserInputData& aData) -{ - char* touchAction; - const ::Touch& touch = aData.motion.touches[0]; - int32_t action = aData.action & AMOTION_EVENT_ACTION_MASK; - switch (action) { - case AMOTION_EVENT_ACTION_DOWN: - touchAction = "Touch_Event_Down"; - break; - case AMOTION_EVENT_ACTION_MOVE: - touchAction = "Touch_Event_Move"; - break; - case AMOTION_EVENT_ACTION_UP: - touchAction = "Touch_Event_Up"; - break; - default : - return; - } - LOG("UniformityInfo %s %llu %f %f", touchAction, systemTime(SYSTEM_TIME_MONOTONIC), - touch.coords.getX(), touch.coords.getY() ); -} - -static nsEventStatus -sendTouchEvent(UserInputData& data, bool* captured) -{ - uint32_t msg; - int32_t action = data.action & AMOTION_EVENT_ACTION_MASK; - switch (action) { - case AMOTION_EVENT_ACTION_DOWN: - case AMOTION_EVENT_ACTION_POINTER_DOWN: - msg = NS_TOUCH_START; - break; - case AMOTION_EVENT_ACTION_MOVE: - msg = NS_TOUCH_MOVE; - break; - case AMOTION_EVENT_ACTION_UP: - case AMOTION_EVENT_ACTION_POINTER_UP: - msg = NS_TOUCH_END; - break; - case AMOTION_EVENT_ACTION_OUTSIDE: - case AMOTION_EVENT_ACTION_CANCEL: - msg = NS_TOUCH_CANCEL; - break; - default: - return nsEventStatus_eIgnore; - } - - WidgetTouchEvent event(true, msg, nullptr); - - event.time = data.timeMs; - event.modifiers = data.DOMModifiers(); - - int32_t i; - if (msg == NS_TOUCH_END) { - i = data.action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK; - i >>= AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; - addDOMTouch(data, event, i); - } else { - for (i = 0; i < data.motion.touchCount; ++i) - addDOMTouch(data, event, i); - } - - return nsWindow::DispatchInputEvent(event, captured); -} - class MOZ_STACK_CLASS KeyEventDispatcher { public: @@ -405,7 +304,7 @@ KeyEventDispatcher::DispatchKeyEventInternal(uint32_t aEventMessage) event.mKeyValue = mDOMPrintableKeyValue; } event.mCodeNameIndex = mDOMCodeNameIndex; - event.modifiers = mData.DOMModifiers(); + event.modifiers = getDOMModifiers(mData.metaState); event.location = nsIDOMKeyEvent::DOM_KEY_LOCATION_MOBILE; event.time = mData.timeMs; return nsWindow::DispatchInputEvent(event); @@ -594,12 +493,10 @@ public: GeckoInputDispatcher(sp &aEventHub) : mQueueLock("GeckoInputDispatcher::mQueueMutex") , mEventHub(aEventHub) - , mTouchDownCount(0) , mKeyDownCount(0) - , mTouchEventsFiltered(false) , mKeyEventsFiltered(false) { - mEnabledUniformityInfo = Preferences::GetBool("layers.uniformity-info", false); + mTouchDispatcher = new GeckoTouchDispatcher(); } virtual void dump(String8& dump); @@ -644,13 +541,10 @@ private: mozilla::Mutex mQueueLock; std::queue mEventQueue; sp mEventHub; + nsRefPtr mTouchDispatcher; - int mTouchDownCount; int mKeyDownCount; - bool mTouchEventsFiltered; bool mKeyEventsFiltered; - BitSet32 mTouchDown; - bool mEnabledUniformityInfo; }; // GeckoInputReaderPolicy @@ -722,80 +616,7 @@ GeckoInputDispatcher::dispatchOnce() switch (data.type) { case UserInputData::MOTION_DATA: { - if (!mTouchDownCount) { - // No pending events, the filter state can be updated. - mTouchEventsFiltered = isExpired(data); - } - - int32_t action = data.action & AMOTION_EVENT_ACTION_MASK; - int32_t index = data.action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK; - index >>= AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; - int32_t id = data.motion.touches[index].id; - switch (action) { - case AMOTION_EVENT_ACTION_DOWN: - case AMOTION_EVENT_ACTION_POINTER_DOWN: - if (!mTouchDown.hasBit(id)) { - mTouchDown.markBit(id); - mTouchDownCount++; - } - break; - case AMOTION_EVENT_ACTION_MOVE: - case AMOTION_EVENT_ACTION_HOVER_MOVE: - // No need to update the count on move. - break; - case AMOTION_EVENT_ACTION_UP: - case AMOTION_EVENT_ACTION_POINTER_UP: - case AMOTION_EVENT_ACTION_OUTSIDE: - case AMOTION_EVENT_ACTION_CANCEL: - if (mTouchDown.hasBit(id)) { - mTouchDown.clearBit(id); - mTouchDownCount--; - } - break; - default: - break; - } - - if (mTouchEventsFiltered) { - return; - } - - nsEventStatus status = nsEventStatus_eIgnore; - if (action != AMOTION_EVENT_ACTION_HOVER_MOVE) { - bool captured; - status = sendTouchEvent(data, &captured); - if (mEnabledUniformityInfo) { - printUniformityInfo(data); - } - if (captured) { - return; - } - } - - uint32_t msg; - switch (action) { - case AMOTION_EVENT_ACTION_DOWN: - msg = NS_MOUSE_BUTTON_DOWN; - break; - case AMOTION_EVENT_ACTION_POINTER_DOWN: - case AMOTION_EVENT_ACTION_POINTER_UP: - case AMOTION_EVENT_ACTION_MOVE: - case AMOTION_EVENT_ACTION_HOVER_MOVE: - msg = NS_MOUSE_MOVE; - break; - case AMOTION_EVENT_ACTION_OUTSIDE: - case AMOTION_EVENT_ACTION_CANCEL: - case AMOTION_EVENT_ACTION_UP: - msg = NS_MOUSE_BUTTON_UP; - break; - default: - msg = NS_EVENT_NULL; - break; - } - if (msg != NS_EVENT_NULL) { - sendMouseEvent(msg, data, - status != nsEventStatus_eConsumeNoDefault); - } + MOZ_ASSERT_UNREACHABLE("Should not dispatch touch events here anymore"); break; } case UserInputData::KEY_DATA: { @@ -841,41 +662,72 @@ GeckoInputDispatcher::notifyKey(const NotifyKeyArgs* args) gAppShell->NotifyNativeEvent(); } +static void +addMultiTouch(MultiTouchInput& aMultiTouch, + const NotifyMotionArgs* args, int aIndex) +{ + int32_t id = args->pointerProperties[aIndex].id; + PointerCoords coords = args->pointerCoords[aIndex]; + float force = coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE); + ScreenIntPoint point(floor(coords.getX() + 0.5), + floor(coords.getY() + 0.5)); + + SingleTouchData touchData(id, point, ScreenSize(0, 0), + 0, force); + + aMultiTouch.mTouches.AppendElement(touchData); +} void GeckoInputDispatcher::notifyMotion(const NotifyMotionArgs* args) { - UserInputData data; - data.timeMs = nanosecsToMillisecs(args->eventTime); - data.type = UserInputData::MOTION_DATA; - data.action = args->action; - data.flags = args->flags; - data.metaState = args->metaState; - data.deviceId = args->deviceId; - MOZ_ASSERT(args->pointerCount <= MAX_POINTERS); - data.motion.touchCount = args->pointerCount; - for (uint32_t i = 0; i < args->pointerCount; ++i) { - ::Touch& touch = data.motion.touches[i]; - touch.id = args->pointerProperties[i].id; - memcpy(&touch.coords, &args->pointerCoords[i], sizeof(*args->pointerCoords)); + uint32_t time = nanosecsToMillisecs(args->eventTime); + int32_t action = args->action & AMOTION_EVENT_ACTION_MASK; + int touchCount = args->pointerCount; + MOZ_ASSERT(touchCount <= MAX_POINTERS); + TimeStamp timestamp = TimeStamp::Now(); + Modifiers modifiers = getDOMModifiers(args->metaState); + + MultiTouchInput::MultiTouchType touchType = MultiTouchInput::MULTITOUCH_CANCEL; + switch (action) { + case AMOTION_EVENT_ACTION_DOWN: + case AMOTION_EVENT_ACTION_POINTER_DOWN: + touchType = MultiTouchInput::MULTITOUCH_START; + break; + case AMOTION_EVENT_ACTION_MOVE: + touchType = MultiTouchInput::MULTITOUCH_MOVE; + break; + case AMOTION_EVENT_ACTION_UP: + case AMOTION_EVENT_ACTION_POINTER_UP: + touchType = MultiTouchInput::MULTITOUCH_END; + break; + case AMOTION_EVENT_ACTION_OUTSIDE: + case AMOTION_EVENT_ACTION_CANCEL: + touchType = MultiTouchInput::MULTITOUCH_CANCEL; + break; + default: + MOZ_ASSERT_UNREACHABLE("Could not assign a touch type"); + break; } - { - MutexAutoLock lock(mQueueLock); - if (!mEventQueue.empty() && - mEventQueue.back().type == UserInputData::MOTION_DATA && - ((mEventQueue.back().action & AMOTION_EVENT_ACTION_MASK) == - AMOTION_EVENT_ACTION_MOVE || - (mEventQueue.back().action & AMOTION_EVENT_ACTION_MASK) == - AMOTION_EVENT_ACTION_HOVER_MOVE)) - mEventQueue.back() = data; - else - mEventQueue.push(data); + + MultiTouchInput touchData(touchType, time, timestamp, modifiers); + + // For touch ends, we have to filter out which finger is actually + // the touch end since the touch array has all fingers, not just the touch + // that we want to end + if (touchType == MultiTouchInput::MULTITOUCH_END) { + int touchIndex = args->action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK; + touchIndex >>= AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; + addMultiTouch(touchData, args, touchIndex); + } else { + for (int32_t i = 0; i < touchCount; ++i) { + addMultiTouch(touchData, args, i); + } } - gAppShell->NotifyNativeEvent(); + + mTouchDispatcher->NotifyTouch(touchData, args->eventTime); } - - void GeckoInputDispatcher::notifySwitch(const NotifySwitchArgs* args) { if (!sDevInputAudioJack)