From 4b49370e149173247f13cf732e5933d7d5c079d0 Mon Sep 17 00:00:00 2001 From: Maksim Lebedev Date: Fri, 4 Apr 2014 10:09:10 -0700 Subject: [PATCH] Bug 968148 - Implement PointerCapture for pointer events. r=smaug --HG-- extra : rebase_source : 331cf187194c8827e4b75835e85b2d79fdc419c7 --- content/base/public/Element.h | 26 +++ dom/base/DOMException.cpp | 1 + dom/base/domerr.msg | 1 + dom/events/PointerEvent.cpp | 38 +++-- dom/events/PointerEvent.h | 5 + dom/interfaces/core/nsIDOMDOMException.idl | 1 + dom/webidl/Element.webidl | 7 + dom/webidl/EventHandler.webidl | 4 + layout/base/nsIPresShell.h | 27 ++++ layout/base/nsPresShell.cpp | 175 ++++++++++++++++++++- layout/base/nsPresShell.h | 1 + widget/MouseEvents.h | 8 +- widget/windows/winrt/MetroInput.cpp | 1 + xpcom/base/ErrorList.h | 1 + 14 files changed, 273 insertions(+), 23 deletions(-) diff --git a/content/base/public/Element.h b/content/base/public/Element.h index 1f62964f2d4..197bffda951 100644 --- a/content/base/public/Element.h +++ b/content/base/public/Element.h @@ -632,6 +632,32 @@ public: GetElementsByClassName(const nsAString& aClassNames); bool MozMatchesSelector(const nsAString& aSelector, ErrorResult& aError); + void SetPointerCapture(int32_t aPointerId, ErrorResult& aError) + { + bool activeState = false; + if (!nsIPresShell::GetPointerInfo(aPointerId, activeState)) { + aError.Throw(NS_ERROR_DOM_INVALID_POINTER_ERR); + return; + } + if (!activeState) { + return; + } + nsIPresShell::SetPointerCapturingContent(aPointerId, this); + } + void ReleasePointerCapture(int32_t aPointerId, ErrorResult& aError) + { + bool activeState = false; + if (!nsIPresShell::GetPointerInfo(aPointerId, activeState)) { + aError.Throw(NS_ERROR_DOM_INVALID_POINTER_ERR); + return; + } + + // Ignoring ReleasePointerCapture call on incorrect element (on element + // that didn't have capture before). + if (nsIPresShell::GetPointerCapturingContent(aPointerId) == this) { + nsIPresShell::ReleasePointerCapturingContent(aPointerId, this); + } + } void SetCapture(bool aRetargetToElement) { // If there is already an active capture, ignore this request. This would diff --git a/dom/base/DOMException.cpp b/dom/base/DOMException.cpp index af5b6ed7b7d..1c2f07a00f0 100644 --- a/dom/base/DOMException.cpp +++ b/dom/base/DOMException.cpp @@ -50,6 +50,7 @@ enum DOM4ErrorTypeCodeMap { TimeoutError = nsIDOMDOMException::TIMEOUT_ERR, InvalidNodeTypeError = nsIDOMDOMException::INVALID_NODE_TYPE_ERR, DataCloneError = nsIDOMDOMException::DATA_CLONE_ERR, + InvalidPointerId = nsIDOMDOMException::INVALID_POINTER_ERR, EncodingError = 0, /* XXX Should be JavaScript native errors */ diff --git a/dom/base/domerr.msg b/dom/base/domerr.msg index ab59ce5845b..e5bae9e7b2d 100644 --- a/dom/base/domerr.msg +++ b/dom/base/domerr.msg @@ -30,6 +30,7 @@ DOM4_MSG_DEF(QuotaExceededError, "The quota has been exceeded.", NS_ERROR_DOM_QU DOM4_MSG_DEF(TimeoutError, "The operation timed out.", NS_ERROR_DOM_TIMEOUT_ERR) DOM4_MSG_DEF(InvalidNodeTypeError, "The supplied node is incorrect or has an incorrect ancestor for this operation.", NS_ERROR_DOM_INVALID_NODE_TYPE_ERR) DOM4_MSG_DEF(DataCloneError, "The object could not be cloned.", NS_ERROR_DOM_DATA_CLONE_ERR) +DOM4_MSG_DEF(InvalidPointerId, "Invalid pointer id.", NS_ERROR_DOM_INVALID_POINTER_ERR) /* XXX Should be JavaScript native TypeError */ DOM4_MSG_DEF(TypeError, "The method parameter is missing or invalid.", NS_ERROR_TYPE_ERR) diff --git a/dom/events/PointerEvent.cpp b/dom/events/PointerEvent.cpp index 987277186da..9478be4c4b5 100644 --- a/dom/events/PointerEvent.cpp +++ b/dom/events/PointerEvent.cpp @@ -48,26 +48,21 @@ ConvertStringToPointerType(const nsAString& aPointerTypeArg) return nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN; } -//static +// static already_AddRefed -PointerEvent::Constructor(const GlobalObject& aGlobal, +PointerEvent::Constructor(EventTarget* aOwner, const nsAString& aType, - const PointerEventInit& aParam, - ErrorResult& aRv) + const PointerEventInit& aParam) { - nsCOMPtr t = do_QueryInterface(aGlobal.GetAsSupports()); - nsRefPtr e = new PointerEvent(t, nullptr, nullptr); - bool trusted = e->Init(t); + nsRefPtr e = new PointerEvent(aOwner, nullptr, nullptr); + bool trusted = e->Init(aOwner); - aRv = e->InitMouseEvent(aType, aParam.mBubbles, aParam.mCancelable, - aParam.mView, aParam.mDetail, aParam.mScreenX, - aParam.mScreenY, aParam.mClientX, aParam.mClientY, - aParam.mCtrlKey, aParam.mAltKey, aParam.mShiftKey, - aParam.mMetaKey, aParam.mButton, - aParam.mRelatedTarget); - if (aRv.Failed()) { - return nullptr; - } + e->InitMouseEvent(aType, aParam.mBubbles, aParam.mCancelable, + aParam.mView, aParam.mDetail, aParam.mScreenX, + aParam.mScreenY, aParam.mClientX, aParam.mClientY, + aParam.mCtrlKey, aParam.mAltKey, aParam.mShiftKey, + aParam.mMetaKey, aParam.mButton, + aParam.mRelatedTarget); WidgetPointerEvent* widgetEvent = e->mEvent->AsPointerEvent(); widgetEvent->pointerId = aParam.mPointerId; @@ -84,6 +79,17 @@ PointerEvent::Constructor(const GlobalObject& aGlobal, return e.forget(); } +// static +already_AddRefed +PointerEvent::Constructor(const GlobalObject& aGlobal, + const nsAString& aType, + const PointerEventInit& aParam, + ErrorResult& aRv) +{ + nsCOMPtr owner = do_QueryInterface(aGlobal.GetAsSupports()); + return Constructor(owner, aType, aParam); +} + void PointerEvent::GetPointerType(nsAString& aPointerType) { diff --git a/dom/events/PointerEvent.h b/dom/events/PointerEvent.h index 172838d21ca..e3fff26f418 100644 --- a/dom/events/PointerEvent.h +++ b/dom/events/PointerEvent.h @@ -34,6 +34,11 @@ public: const PointerEventInit& aParam, ErrorResult& aRv); + static already_AddRefed + Constructor(EventTarget* aOwner, + const nsAString& aType, + const PointerEventInit& aParam); + int32_t PointerId(); int32_t Width(); int32_t Height(); diff --git a/dom/interfaces/core/nsIDOMDOMException.idl b/dom/interfaces/core/nsIDOMDOMException.idl index b000e2e081c..50183769f3b 100644 --- a/dom/interfaces/core/nsIDOMDOMException.idl +++ b/dom/interfaces/core/nsIDOMDOMException.idl @@ -45,6 +45,7 @@ interface nsIDOMDOMException : nsISupports const unsigned short TIMEOUT_ERR = 23; const unsigned short INVALID_NODE_TYPE_ERR = 24; const unsigned short DATA_CLONE_ERR = 25; + const unsigned short INVALID_POINTER_ERR = 26; readonly attribute unsigned short code; }; diff --git a/dom/webidl/Element.webidl b/dom/webidl/Element.webidl index dfd04a78aab..60ece2d0216 100644 --- a/dom/webidl/Element.webidl +++ b/dom/webidl/Element.webidl @@ -89,6 +89,13 @@ interface Element : Node { [Throws, Pure] boolean mozMatchesSelector(DOMString selector); + // Pointer events methods. + [Throws, Pref="dom.w3c_pointer_events.enabled"] + void setPointerCapture(long pointerId); + + [Throws, Pref="dom.w3c_pointer_events.enabled"] + void releasePointerCapture(long pointerId); + // Proprietary extensions /** * Set this during a mousedown event to grab and retarget all mouse events diff --git a/dom/webidl/EventHandler.webidl b/dom/webidl/EventHandler.webidl index 32fcdcfaec0..216ddd686da 100644 --- a/dom/webidl/EventHandler.webidl +++ b/dom/webidl/EventHandler.webidl @@ -105,6 +105,10 @@ interface GlobalEventHandlers { attribute EventHandler onpointerenter; [Pref="dom.w3c_pointer_events.enabled"] attribute EventHandler onpointerleave; + [Pref="dom.w3c_pointer_events.enabled"] + attribute EventHandler ongotpointercapture; + [Pref="dom.w3c_pointer_events.enabled"] + attribute EventHandler onlostpointercapture; // Mozilla-specific handlers attribute EventHandler onmozfullscreenchange; diff --git a/layout/base/nsIPresShell.h b/layout/base/nsIPresShell.h index bd9e46c4be7..42909cdbd4c 100644 --- a/layout/base/nsIPresShell.h +++ b/layout/base/nsIPresShell.h @@ -37,6 +37,7 @@ #include // for FILE definition #include "nsChangeHint.h" #include "nsRefPtrHashtable.h" +#include "nsClassHashtable.h" #include "nsPresArena.h" #include "nsIImageLoadingContent.h" #include "nsMargin.h" @@ -1163,6 +1164,32 @@ public: static nsRefPtrHashtable* gCaptureTouchList; static bool gPreventMouseEvents; + // Keeps a map between pointerId and element that currently capturing pointer + // with such pointerId. If pointerId is absent in this map then nobody is + // capturing it. + static nsRefPtrHashtable* gPointerCaptureList; + + struct PointerInfo + { + bool mActiveState; + uint16_t mPointerType; + PointerInfo(bool aActiveState, uint16_t aPointerType) : + mActiveState(aActiveState), mPointerType(aPointerType) {} + }; + // Keeps information about pointers such as pointerId, activeState, pointerType + static nsClassHashtable* gActivePointersIds; + + static void DispatchGotOrLostPointerCaptureEvent(bool aIsGotCapture, + uint32_t aPointerId, + nsIContent* aCaptureTarget); + static void SetPointerCapturingContent(uint32_t aPointerId, nsIContent* aContent); + static void ReleasePointerCapturingContent(uint32_t aPointerId, nsIContent* aContent); + static nsIContent* GetPointerCapturingContent(uint32_t aPointerId); + + // GetPointerInfo returns true if pointer with aPointerId is situated in device, false otherwise. + // aActiveState is additional information, which shows state of pointer like button state for mouse. + static bool GetPointerInfo(uint32_t aPointerId, bool& aActiveState); + /** * When capturing content is set, it traps all mouse events and retargets * them at this content node. If capturing is not allowed diff --git a/layout/base/nsPresShell.cpp b/layout/base/nsPresShell.cpp index eaa59ff3ee5..582ff68a0a4 100644 --- a/layout/base/nsPresShell.cpp +++ b/layout/base/nsPresShell.cpp @@ -46,6 +46,7 @@ #include "mozilla/dom/Element.h" #include "mozilla/dom/Event.h" // for Event::GetEventPopupControlState() #include "mozilla/dom/ShadowRoot.h" +#include "mozilla/dom/PointerEvent.h" #include "nsIDocument.h" #include "nsCSSStyleSheet.h" #include "nsAnimationManager.h" @@ -81,6 +82,7 @@ #include "nsILineIterator.h" // for ScrollContentIntoView #include "pldhash.h" #include "mozilla/dom/Touch.h" +#include "mozilla/dom/PointerEventBinding.h" #include "nsIObserverService.h" #include "nsDocShell.h" // for reflow observation #include "nsIBaseWindow.h" @@ -188,6 +190,8 @@ CapturingContentInfo nsIPresShell::gCaptureInfo = false /* mPreventDrag */, nullptr /* mContent */ }; nsIContent* nsIPresShell::gKeyDownTarget; nsRefPtrHashtable* nsIPresShell::gCaptureTouchList; +nsRefPtrHashtable* nsIPresShell::gPointerCaptureList; +nsClassHashtable* nsIPresShell::gActivePointersIds; bool nsIPresShell::gPreventMouseEvents = false; // convert a color value to a string, in the CSS format #RRGGBB @@ -5957,6 +5961,98 @@ nsIPresShell::SetCapturingContent(nsIContent* aContent, uint8_t aFlags) } } +/* static */ void +nsIPresShell::SetPointerCapturingContent(uint32_t aPointerId, nsIContent* aContent) +{ + nsIContent* content = GetPointerCapturingContent(aPointerId); + + PointerInfo* pointerInfo = nullptr; + if (!content && gActivePointersIds->Get(aPointerId, &pointerInfo) && + pointerInfo && + nsIDOMMouseEvent::MOZ_SOURCE_MOUSE == pointerInfo->mPointerType) { + SetCapturingContent(aContent, CAPTURE_PREVENTDRAG); + } + + if (content) { + // Releasing capture for given pointer. + gPointerCaptureList->Remove(aPointerId); + DispatchGotOrLostPointerCaptureEvent(false, aPointerId, content); + // Need to check the state because a lostpointercapture listener + // may have called SetPointerCapture + if (GetPointerCapturingContent(aPointerId)) { + return; + } + } + + gPointerCaptureList->Put(aPointerId, aContent); + DispatchGotOrLostPointerCaptureEvent(true, aPointerId, aContent); +} + +/* static */ void +nsIPresShell::ReleasePointerCapturingContent(uint32_t aPointerId, nsIContent* aContent) +{ + if (gActivePointersIds->Get(aPointerId)) { + SetCapturingContent(nullptr, CAPTURE_PREVENTDRAG); + } + + // Releasing capture for given pointer. + gPointerCaptureList->Remove(aPointerId); + + DispatchGotOrLostPointerCaptureEvent(false, aPointerId, aContent); +} + +/* static */ nsIContent* +nsIPresShell::GetPointerCapturingContent(uint32_t aPointerId) +{ + return gPointerCaptureList->GetWeak(aPointerId); +} + +/* static */ bool +nsIPresShell::GetPointerInfo(uint32_t aPointerId, bool& aActiveState) +{ + PointerInfo* pointerInfo = nullptr; + if (gActivePointersIds->Get(aPointerId, &pointerInfo) && pointerInfo) { + aActiveState = pointerInfo->mActiveState; + return true; + } + return false; +} + +void +PresShell::UpdateActivePointerState(WidgetGUIEvent* aEvent) +{ + switch (aEvent->message) { + case NS_MOUSE_ENTER: + // In this case we have to know information about available mouse pointers + if (WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent()) { + gActivePointersIds->Put(mouseEvent->pointerId, new PointerInfo(false, mouseEvent->inputSource)); + } + break; + case NS_POINTER_DOWN: + // In this case we switch pointer to active state + if (WidgetPointerEvent* pointerEvent = aEvent->AsPointerEvent()) { + gActivePointersIds->Put(pointerEvent->pointerId, new PointerInfo(true, pointerEvent->inputSource)); + } + break; + case NS_POINTER_UP: + // In this case we remove information about pointer or turn off active state + if (WidgetPointerEvent* pointerEvent = aEvent->AsPointerEvent()) { + if(pointerEvent->inputSource != nsIDOMMouseEvent::MOZ_SOURCE_TOUCH) { + gActivePointersIds->Put(pointerEvent->pointerId, new PointerInfo(false, pointerEvent->inputSource)); + } else { + gActivePointersIds->Remove(pointerEvent->pointerId); + } + } + break; + case NS_MOUSE_EXIT: + // In this case we have to remove information about disappeared mouse pointers + if (WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent()) { + gActivePointersIds->Remove(mouseEvent->pointerId); + } + break; + } +} + nsIContent* PresShell::GetCurrentEventContent() { @@ -6368,6 +6464,31 @@ DispatchPointerFromMouseOrTouch(PresShell* aShell, return NS_OK; } +class ReleasePointerCaptureCaller +{ +public: + ReleasePointerCaptureCaller() : + mPointerId(0), + mContent(nullptr) + { + } + ~ReleasePointerCaptureCaller() + { + if (mContent) { + nsIPresShell::ReleasePointerCapturingContent(mPointerId, mContent); + } + } + void SetTarget(uint32_t aPointerId, nsIContent* aContent) + { + mPointerId = aPointerId; + mContent = aContent; + } + +private: + int32_t mPointerId; + nsCOMPtr mContent; +}; + nsresult PresShell::HandleEvent(nsIFrame* aFrame, WidgetGUIEvent* aEvent, @@ -6387,6 +6508,9 @@ PresShell::HandleEvent(nsIFrame* aFrame, } RecordMouseLocation(aEvent); + if (sPointerEventEnabled) { + UpdateActivePointerState(aEvent); + } if (!nsContentUtils::IsSafeToRunScript()) return NS_OK; @@ -6468,8 +6592,8 @@ PresShell::HandleEvent(nsIFrame* aFrame, nsIFrame* frame = aFrame; - bool dispatchUsingCoordinates = aEvent->IsUsingCoordinates(); - if (dispatchUsingCoordinates) { + if (aEvent->IsUsingCoordinates()) { + ReleasePointerCaptureCaller releasePointerCaptureCaller; if (nsLayoutUtils::AreAsyncAnimationsEnabled() && mDocument) { if (aEvent->eventStructType == NS_TOUCH_EVENT) { nsIDocument::UnlockPointer(); @@ -6679,6 +6803,27 @@ PresShell::HandleEvent(nsIFrame* aFrame, } } + if (aEvent->eventStructType == NS_POINTER_EVENT && + aEvent->message != NS_POINTER_DOWN) { + if (WidgetPointerEvent* pointerEvent = aEvent->AsPointerEvent()) { + uint32_t pointerId = pointerEvent->pointerId; + nsIContent* pointerCapturingContent = GetPointerCapturingContent(pointerId); + + if (pointerCapturingContent) { + if (nsIFrame* capturingFrame = pointerCapturingContent->GetPrimaryFrame()) { + frame = capturingFrame; + } + + if (pointerEvent->message == NS_POINTER_UP || + pointerEvent->message == NS_POINTER_CANCEL) { + // Implicitly releasing capture for given pointer. + // LOST_POINTER_CAPTURE should be send after NS_POINTER_UP or NS_POINTER_CANCEL. + releasePointerCaptureCaller.SetTarget(pointerId, pointerCapturingContent); + } + } + } + } + // Suppress mouse event if it's being targeted at an element inside // a document which needs events suppressed if (aEvent->eventStructType == NS_MOUSE_EVENT && @@ -7267,6 +7412,26 @@ PresShell::HandleEventInternal(WidgetEvent* aEvent, nsEventStatus* aStatus) return rv; } +void +nsIPresShell::DispatchGotOrLostPointerCaptureEvent(bool aIsGotCapture, + uint32_t aPointerId, + nsIContent* aCaptureTarget) +{ + PointerEventInit init; + init.mPointerId = aPointerId; + init.mBubbles = true; + nsRefPtr event; + event = PointerEvent::Constructor(aCaptureTarget, + aIsGotCapture + ? NS_LITERAL_STRING("gotpointercapture") + : NS_LITERAL_STRING("lostpointercapture"), + init); + if (event) { + bool dummy; + aCaptureTarget->DispatchEvent(event->InternalDOMEvent(), &dummy); + } +} + void PresShell::DispatchTouchEvent(WidgetEvent* aEvent, nsEventStatus* aStatus, @@ -9814,6 +9979,8 @@ void nsIPresShell::InitializeStatics() { NS_ASSERTION(!gCaptureTouchList, "InitializeStatics called multiple times!"); gCaptureTouchList = new nsRefPtrHashtable; + gPointerCaptureList = new nsRefPtrHashtable; + gActivePointersIds = new nsClassHashtable; } void nsIPresShell::ReleaseStatics() @@ -9821,6 +9988,10 @@ void nsIPresShell::ReleaseStatics() NS_ASSERTION(gCaptureTouchList, "ReleaseStatics called without Initialize!"); delete gCaptureTouchList; gCaptureTouchList = nullptr; + delete gPointerCaptureList; + gPointerCaptureList = nullptr; + delete gActivePointersIds; + gActivePointersIds = nullptr; } // Asks our docshell whether we're active. diff --git a/layout/base/nsPresShell.h b/layout/base/nsPresShell.h index b59e6b36220..523dea76e24 100644 --- a/layout/base/nsPresShell.h +++ b/layout/base/nsPresShell.h @@ -700,6 +700,7 @@ protected: virtual void ResumePainting() MOZ_OVERRIDE; void UpdateImageVisibility(); + void UpdateActivePointerState(mozilla::WidgetGUIEvent* aEvent); nsRevocableEventPtr > mUpdateImageVisibilityEvent; diff --git a/widget/MouseEvents.h b/widget/MouseEvents.h index d664b6835a9..d209cea9d54 100644 --- a/widget/MouseEvents.h +++ b/widget/MouseEvents.h @@ -44,14 +44,16 @@ class WidgetPointerHelper { public: bool convertToPointer; + uint32_t pointerId; uint32_t tiltX; uint32_t tiltY; - WidgetPointerHelper() : convertToPointer(true), tiltX(0), tiltY(0) {} + WidgetPointerHelper() : convertToPointer(true), pointerId(0), tiltX(0), tiltY(0) {} void AssignPointerHelperData(const WidgetPointerHelper& aEvent) { convertToPointer = aEvent.convertToPointer; + pointerId = aEvent.pointerId; tiltX = aEvent.tiltX; tiltY = aEvent.tiltY; } @@ -560,7 +562,6 @@ public: WidgetPointerEvent(bool aIsTrusted, uint32_t aMsg, nsIWidget* w) : WidgetMouseEvent(aIsTrusted, aMsg, w, NS_POINTER_EVENT, eReal) - , pointerId(0) , width(0) , height(0) , isPrimary(true) @@ -570,7 +571,6 @@ public: WidgetPointerEvent(const WidgetMouseEvent& aEvent) : WidgetMouseEvent(aEvent) - , pointerId(0) , width(0) , height(0) , isPrimary(true) @@ -609,7 +609,6 @@ public: return result; } - uint32_t pointerId; uint32_t width; uint32_t height; bool isPrimary; @@ -620,7 +619,6 @@ public: { AssignMouseEventData(aEvent, aCopyTargets); - pointerId = aEvent.pointerId; width = aEvent.width; height = aEvent.height; isPrimary = aEvent.isPrimary; diff --git a/widget/windows/winrt/MetroInput.cpp b/widget/windows/winrt/MetroInput.cpp index 3cd0da48dfd..9587d7027d8 100644 --- a/widget/windows/winrt/MetroInput.cpp +++ b/widget/windows/winrt/MetroInput.cpp @@ -808,6 +808,7 @@ MetroInput::InitGeckoMouseEventFromPointerPoint( aPointerPoint->get_PointerDevice(device.GetAddressOf()); device->get_PointerDeviceType(&deviceType); aPointerPoint->get_Properties(props.GetAddressOf()); + aPointerPoint->get_PointerId(&aEvent->pointerId); props->get_Pressure(&pressure); props->get_XTilt(&tiltX); props->get_YTilt(&tiltY); diff --git a/xpcom/base/ErrorList.h b/xpcom/base/ErrorList.h index 20765bc903b..f7651ca5d22 100644 --- a/xpcom/base/ErrorList.h +++ b/xpcom/base/ErrorList.h @@ -486,6 +486,7 @@ ERROR(NS_ERROR_DOM_ENCODING_NOT_SUPPORTED_ERR, FAILURE(28)), ERROR(NS_ERROR_DOM_ENCODING_NOT_UTF_ERR, FAILURE(29)), ERROR(NS_ERROR_DOM_ENCODING_DECODE_ERR, FAILURE(30)), + ERROR(NS_ERROR_DOM_INVALID_POINTER_ERR, FAILURE(31)), /* DOM error codes defined by us */ ERROR(NS_ERROR_DOM_SECMAN_ERR, FAILURE(1001)), ERROR(NS_ERROR_DOM_WRONG_TYPE_ERR, FAILURE(1002)),