/* -*- 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* TouchManager::gCaptureTouchList; /*static*/ void TouchManager::InitializeStatics() { NS_ASSERTION(!gCaptureTouchList, "InitializeStatics called multiple times!"); gCaptureTouchList = new nsRefPtrHashtable; } /*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& aTouch, nsIDocument* aLimitToDocument = nullptr) { nsCOMPtr 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 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& aData, void *aTouchList) { WidgetTouchEvent::TouchArray* touches = static_cast(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& 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 oldTouch = gCaptureTouchList->GetWeak(id); if (!oldTouch) { touches.RemoveElementAt(i); continue; } if (!touch->Equals(oldTouch)) { touch->mChanged = true; haveChanged = true; } nsCOMPtr 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 oldTouch = gCaptureTouchList->GetWeak(id); if (!oldTouch) { continue; } nsCOMPtr 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; }