/* -*- 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 "mozilla/TouchEvents.h" #include "mozilla/dom/EventTarget.h" #include "nsIFrame.h" #include "nsPresShell.h" #include "nsView.h" namespace mozilla { 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(RefPtr& aTouch, nsIDocument* aLimitToDocument = nullptr) { nsCOMPtr node(do_QueryInterface(aTouch->mTarget)); if (node) { nsIDocument* doc = node->GetUncomposedDoc(); 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, eTouchEnd, widget); event.mTime = PR_IntervalNow(); event.mTouches.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 void AppendToTouchList(WidgetTouchEvent::TouchArray* aTouchList) { for (auto iter = TouchManager::gCaptureTouchList->Iter(); !iter.Done(); iter.Next()) { RefPtr& touch = iter.Data(); touch->mChanged = false; aTouchList->AppendElement(touch); } } void TouchManager::EvictTouches() { WidgetTouchEvent::AutoTouchArray touches; 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->mMessage) { case eTouchStart: { 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->mTouches.Length() == 1) { WidgetTouchEvent::AutoTouchArray touches; AppendToTouchList(&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->mTouches.Length(); ++i) { dom::Touch* touch = touchEvent->mTouches[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->mMessage; gCaptureTouchList->Put(id, touch); } break; } case eTouchMove: { // Check for touches that changed. Mark them add to queue WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent(); WidgetTouchEvent::TouchArray& touches = touchEvent->mTouches; 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->mMessage; RefPtr 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->mTouches.Length(); ++i) { if (touchEvent->mTouches[i]) { touchEvent->mTouches[i]->mChanged = true; break; } } } else { return false; } } break; } case eTouchEnd: aIsHandlingUserInput = true; // Fall through to touchcancel code MOZ_FALLTHROUGH; case eTouchCancel: { // Remove the changed touches // need to make sure we only remove touches that are ending here WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent(); WidgetTouchEvent::TouchArray& touches = touchEvent->mTouches; for (uint32_t i = 0; i < touches.Length(); ++i) { dom::Touch* touch = touches[i]; if (!touch) { continue; } touch->mMessage = aEvent->mMessage; touch->mChanged = true; int32_t id = touch->Identifier(); RefPtr 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 AppendToTouchList(&touches); break; } default: break; } return true; } } // namespace mozilla