mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-31 06:05:44 +00:00
229 lines
7.4 KiB
C++
229 lines
7.4 KiB
C++
/* -*- 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 "TouchManager.h"
|
|
#include "nsPresShell.h"
|
|
|
|
bool TouchManager::gPreventMouseEvents = false;
|
|
nsRefPtrHashtable<nsUint32HashKey, dom::Touch>* TouchManager::gCaptureTouchList;
|
|
|
|
/*static*/ void
|
|
TouchManager::InitializeStatics()
|
|
{
|
|
NS_ASSERTION(!gCaptureTouchList, "InitializeStatics called multiple times!");
|
|
gCaptureTouchList = new nsRefPtrHashtable<nsUint32HashKey, dom::Touch>;
|
|
}
|
|
|
|
/*static*/ void
|
|
TouchManager::ReleaseStatics()
|
|
{
|
|
NS_ASSERTION(gCaptureTouchList, "ReleaseStatics called without Initialize!");
|
|
delete gCaptureTouchList;
|
|
gCaptureTouchList = nullptr;
|
|
}
|
|
|
|
void
|
|
TouchManager::Init(PresShell* aPresShell, nsIDocument* aDocument)
|
|
{
|
|
mPresShell = aPresShell;
|
|
mDocument = aDocument;
|
|
}
|
|
|
|
void
|
|
TouchManager::Destroy()
|
|
{
|
|
EvictTouches();
|
|
mDocument = nullptr;
|
|
mPresShell = nullptr;
|
|
}
|
|
|
|
static void
|
|
EvictTouchPoint(nsRefPtr<dom::Touch>& aTouch,
|
|
nsIDocument* aLimitToDocument = nullptr)
|
|
{
|
|
nsCOMPtr<nsINode> node(do_QueryInterface(aTouch->mTarget));
|
|
if (node) {
|
|
nsIDocument* doc = node->GetCurrentDoc();
|
|
if (doc && (!aLimitToDocument || aLimitToDocument == doc)) {
|
|
nsIPresShell* presShell = doc->GetShell();
|
|
if (presShell) {
|
|
nsIFrame* frame = presShell->GetRootFrame();
|
|
if (frame) {
|
|
nsPoint pt(aTouch->mRefPoint.x, aTouch->mRefPoint.y);
|
|
nsCOMPtr<nsIWidget> widget = frame->GetView()->GetNearestWidget(&pt);
|
|
if (widget) {
|
|
WidgetTouchEvent event(true, NS_TOUCH_END, widget);
|
|
event.widget = widget;
|
|
event.time = PR_IntervalNow();
|
|
event.touches.AppendElement(aTouch);
|
|
nsEventStatus status;
|
|
widget->DispatchEvent(&event, status);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!node || !aLimitToDocument || node->OwnerDoc() == aLimitToDocument) {
|
|
// We couldn't dispatch touchend. Remove the touch from gCaptureTouchList explicitly.
|
|
TouchManager::gCaptureTouchList->Remove(aTouch->Identifier());
|
|
}
|
|
}
|
|
|
|
static PLDHashOperator
|
|
AppendToTouchList(const uint32_t& aKey, nsRefPtr<dom::Touch>& aData, void *aTouchList)
|
|
{
|
|
WidgetTouchEvent::TouchArray* touches =
|
|
static_cast<WidgetTouchEvent::TouchArray*>(aTouchList);
|
|
aData->mChanged = false;
|
|
touches->AppendElement(aData);
|
|
return PL_DHASH_NEXT;
|
|
}
|
|
|
|
void
|
|
TouchManager::EvictTouches()
|
|
{
|
|
WidgetTouchEvent::AutoTouchArray touches;
|
|
gCaptureTouchList->Enumerate(&AppendToTouchList, &touches);
|
|
for (uint32_t i = 0; i < touches.Length(); ++i) {
|
|
EvictTouchPoint(touches[i], mDocument);
|
|
}
|
|
}
|
|
|
|
bool
|
|
TouchManager::PreHandleEvent(WidgetEvent* aEvent,
|
|
nsEventStatus* aStatus,
|
|
bool& aTouchIsNew,
|
|
bool& aIsHandlingUserInput,
|
|
nsCOMPtr<nsIContent>& aCurrentEventContent)
|
|
{
|
|
switch (aEvent->message) {
|
|
case NS_TOUCH_START: {
|
|
aIsHandlingUserInput = true;
|
|
WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
|
|
// if there is only one touch in this touchstart event, assume that it is
|
|
// the start of a new touch session and evict any old touches in the
|
|
// queue
|
|
if (touchEvent->touches.Length() == 1) {
|
|
WidgetTouchEvent::AutoTouchArray touches;
|
|
gCaptureTouchList->Enumerate(&AppendToTouchList, (void *)&touches);
|
|
for (uint32_t i = 0; i < touches.Length(); ++i) {
|
|
EvictTouchPoint(touches[i]);
|
|
}
|
|
}
|
|
// Add any new touches to the queue
|
|
for (uint32_t i = 0; i < touchEvent->touches.Length(); ++i) {
|
|
dom::Touch* touch = touchEvent->touches[i];
|
|
int32_t id = touch->Identifier();
|
|
if (!gCaptureTouchList->Get(id, nullptr)) {
|
|
// If it is not already in the queue, it is a new touch
|
|
touch->mChanged = true;
|
|
}
|
|
touch->mMessage = aEvent->message;
|
|
gCaptureTouchList->Put(id, touch);
|
|
}
|
|
break;
|
|
}
|
|
case NS_TOUCH_MOVE: {
|
|
// Check for touches that changed. Mark them add to queue
|
|
WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
|
|
WidgetTouchEvent::TouchArray& touches = touchEvent->touches;
|
|
bool haveChanged = false;
|
|
for (int32_t i = touches.Length(); i; ) {
|
|
--i;
|
|
dom::Touch* touch = touches[i];
|
|
if (!touch) {
|
|
continue;
|
|
}
|
|
int32_t id = touch->Identifier();
|
|
touch->mMessage = aEvent->message;
|
|
|
|
nsRefPtr<dom::Touch> oldTouch = gCaptureTouchList->GetWeak(id);
|
|
if (!oldTouch) {
|
|
touches.RemoveElementAt(i);
|
|
continue;
|
|
}
|
|
if (!touch->Equals(oldTouch)) {
|
|
touch->mChanged = true;
|
|
haveChanged = true;
|
|
}
|
|
|
|
nsCOMPtr<dom::EventTarget> targetPtr = oldTouch->mTarget;
|
|
if (!targetPtr) {
|
|
touches.RemoveElementAt(i);
|
|
continue;
|
|
}
|
|
touch->SetTarget(targetPtr);
|
|
|
|
gCaptureTouchList->Put(id, touch);
|
|
// if we're moving from touchstart to touchmove for this touch
|
|
// we allow preventDefault to prevent mouse events
|
|
if (oldTouch->mMessage != touch->mMessage) {
|
|
aTouchIsNew = true;
|
|
}
|
|
}
|
|
// is nothing has changed, we should just return
|
|
if (!haveChanged) {
|
|
if (aTouchIsNew) {
|
|
// however, if this is the first touchmove after a touchstart,
|
|
// it is special in that preventDefault is allowed on it, so
|
|
// we must dispatch it to content even if nothing changed. we
|
|
// arbitrarily pick the first touch point to be the "changed"
|
|
// touch because firing an event with no changed events doesn't
|
|
// work.
|
|
for (uint32_t i = 0; i < touchEvent->touches.Length(); ++i) {
|
|
if (touchEvent->touches[i]) {
|
|
touchEvent->touches[i]->mChanged = true;
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
if (gPreventMouseEvents) {
|
|
*aStatus = nsEventStatus_eConsumeNoDefault;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case NS_TOUCH_END:
|
|
aIsHandlingUserInput = true;
|
|
// Fall through to touchcancel code
|
|
case NS_TOUCH_CANCEL: {
|
|
// Remove the changed touches
|
|
// need to make sure we only remove touches that are ending here
|
|
WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
|
|
WidgetTouchEvent::TouchArray& touches = touchEvent->touches;
|
|
for (uint32_t i = 0; i < touches.Length(); ++i) {
|
|
dom::Touch* touch = touches[i];
|
|
if (!touch) {
|
|
continue;
|
|
}
|
|
touch->mMessage = aEvent->message;
|
|
touch->mChanged = true;
|
|
|
|
int32_t id = touch->Identifier();
|
|
nsRefPtr<dom::Touch> oldTouch = gCaptureTouchList->GetWeak(id);
|
|
if (!oldTouch) {
|
|
continue;
|
|
}
|
|
nsCOMPtr<EventTarget> targetPtr = oldTouch->mTarget;
|
|
|
|
aCurrentEventContent = do_QueryInterface(targetPtr);
|
|
touch->SetTarget(targetPtr);
|
|
gCaptureTouchList->Remove(id);
|
|
}
|
|
// add any touches left in the touch list, but ensure changed=false
|
|
gCaptureTouchList->Enumerate(&AppendToTouchList, (void *)&touches);
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
return true;
|
|
}
|