tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

TouchManager.cpp (20615B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
      6 */
      7 
      8 #include "TouchManager.h"
      9 
     10 #include "PositionedEventTargeting.h"
     11 #include "Units.h"
     12 #include "mozilla/EventForwards.h"
     13 #include "mozilla/PresShell.h"
     14 #include "mozilla/StaticPrefs_apz.h"
     15 #include "mozilla/StaticPrefs_test.h"
     16 #include "mozilla/TimeStamp.h"
     17 #include "mozilla/dom/Document.h"
     18 #include "mozilla/dom/EventTarget.h"
     19 #include "mozilla/dom/PointerEventHandler.h"
     20 #include "mozilla/layers/InputAPZContext.h"
     21 #include "nsIContent.h"
     22 #include "nsIFrame.h"
     23 #include "nsLayoutUtils.h"
     24 
     25 using namespace mozilla::dom;
     26 
     27 namespace mozilla {
     28 
     29 StaticAutoPtr<nsTHashMap<nsUint32HashKey, TouchManager::TouchInfo>>
     30    TouchManager::sCaptureTouchList;
     31 layers::LayersId TouchManager::sCaptureTouchLayersId;
     32 TimeStamp TouchManager::sSingleTouchStartTimeStamp;
     33 LayoutDeviceIntPoint TouchManager::sSingleTouchStartPoint;
     34 bool TouchManager::sPrecedingTouchPointerDownConsumedByContent = false;
     35 
     36 /*static*/
     37 void TouchManager::InitializeStatics() {
     38  NS_ASSERTION(!sCaptureTouchList, "InitializeStatics called multiple times!");
     39  sCaptureTouchList = new nsTHashMap<nsUint32HashKey, TouchManager::TouchInfo>;
     40  sCaptureTouchLayersId = layers::LayersId{0};
     41 }
     42 
     43 /*static*/
     44 void TouchManager::ReleaseStatics() {
     45  NS_ASSERTION(sCaptureTouchList, "ReleaseStatics called without Initialize!");
     46  sCaptureTouchList = nullptr;
     47 }
     48 
     49 void TouchManager::Init(PresShell* aPresShell, Document* aDocument) {
     50  mPresShell = aPresShell;
     51  mDocument = aDocument;
     52 }
     53 
     54 void TouchManager::Destroy() {
     55  EvictTouches(mDocument);
     56  mDocument = nullptr;
     57  mPresShell = nullptr;
     58 }
     59 
     60 static nsIContent* GetNonAnonymousAncestor(EventTarget* aTarget) {
     61  nsIContent* content = nsIContent::FromEventTargetOrNull(aTarget);
     62  if (content && content->IsInNativeAnonymousSubtree()) {
     63    content = content->FindFirstNonChromeOnlyAccessContent();
     64  }
     65  return content;
     66 }
     67 
     68 /*static*/
     69 void TouchManager::EvictTouchPoint(RefPtr<Touch>& aTouch,
     70                                   Document* aLimitToDocument) {
     71  nsCOMPtr<nsINode> node(
     72      nsINode::FromEventTargetOrNull(aTouch->mOriginalTarget));
     73  if (node) {
     74    Document* doc = node->GetComposedDoc();
     75    if (doc && (!aLimitToDocument || aLimitToDocument == doc)) {
     76      if (PresShell* presShell = doc->GetPresShell()) {
     77        if (nsIFrame* frame = presShell->GetRootFrame()) {
     78          if (nsCOMPtr<nsIWidget> widget = frame->GetNearestWidget()) {
     79            WidgetTouchEvent event(true, eTouchEnd, widget);
     80            event.mTouches.AppendElement(aTouch);
     81            widget->DispatchEvent(&event);
     82          }
     83        }
     84      }
     85    }
     86  }
     87  if (!node || !aLimitToDocument || node->OwnerDoc() == aLimitToDocument) {
     88    sCaptureTouchList->Remove(aTouch->Identifier());
     89  }
     90 }
     91 
     92 /*static*/
     93 void TouchManager::AppendToTouchList(
     94    WidgetTouchEvent::TouchArrayBase* aTouchList) {
     95  for (const auto& data : sCaptureTouchList->Values()) {
     96    const RefPtr<Touch>& touch = data.mTouch;
     97    touch->mChanged = false;
     98    aTouchList->AppendElement(touch);
     99  }
    100 }
    101 
    102 void TouchManager::EvictTouches(Document* aLimitToDocument) {
    103  WidgetTouchEvent::AutoTouchArray touches;
    104  AppendToTouchList(&touches);
    105  for (uint32_t i = 0; i < touches.Length(); ++i) {
    106    EvictTouchPoint(touches[i], aLimitToDocument);
    107  }
    108  sCaptureTouchLayersId = layers::LayersId{0};
    109 }
    110 
    111 /* static */
    112 nsIFrame* TouchManager::SetupTarget(WidgetTouchEvent* aEvent,
    113                                    nsIFrame* aFrame) {
    114  MOZ_ASSERT(aEvent);
    115 
    116  if (!aEvent || aEvent->mMessage != eTouchStart) {
    117    // All touch events except for touchstart use a captured target.
    118    return aFrame;
    119  }
    120 
    121  nsIFrame* target = aFrame;
    122  for (int32_t i = aEvent->mTouches.Length(); i;) {
    123    --i;
    124    dom::Touch* touch = aEvent->mTouches[i];
    125 
    126    int32_t id = touch->Identifier();
    127    if (!TouchManager::HasCapturedTouch(id)) {
    128      // find the target for this touch
    129      RelativeTo relativeTo{aFrame};
    130      nsPoint eventPoint = nsLayoutUtils::GetEventCoordinatesRelativeTo(
    131          aEvent, touch->mRefPoint, relativeTo);
    132      target = FindFrameTargetedByInputEvent(aEvent, relativeTo, eventPoint);
    133      if (target) {
    134        nsIContent* targetContent = target->GetContentForEvent(aEvent);
    135        touch->SetTouchTarget(targetContent
    136                                  ? targetContent->GetAsElementOrParentElement()
    137                                  : nullptr);
    138      } else {
    139        aEvent->mTouches.RemoveElementAt(i);
    140      }
    141    } else {
    142      // This touch is an old touch, we need to ensure that is not
    143      // marked as changed and set its target correctly
    144      touch->mChanged = false;
    145      RefPtr<dom::Touch> oldTouch = TouchManager::GetCapturedTouch(id);
    146      if (oldTouch) {
    147        touch->SetTouchTarget(oldTouch->mOriginalTarget);
    148      }
    149    }
    150  }
    151  return target;
    152 }
    153 
    154 /* static */
    155 nsIFrame* TouchManager::SuppressInvalidPointsAndGetTargetedFrame(
    156    WidgetTouchEvent* aEvent) {
    157  MOZ_ASSERT(aEvent);
    158 
    159  if (!aEvent || aEvent->mMessage != eTouchStart) {
    160    // All touch events except for touchstart use a captured target.
    161    return nullptr;
    162  }
    163 
    164  // if this is a continuing session, ensure that all these events are
    165  // in the same document by taking the target of the events already in
    166  // the capture list
    167  nsCOMPtr<nsIContent> anyTarget;
    168  if (aEvent->mTouches.Length() > 1) {
    169    anyTarget = TouchManager::GetAnyCapturedTouchTarget();
    170  }
    171 
    172  nsIFrame* frame = nullptr;
    173  for (uint32_t i = aEvent->mTouches.Length(); i;) {
    174    --i;
    175    dom::Touch* touch = aEvent->mTouches[i];
    176    if (TouchManager::HasCapturedTouch(touch->Identifier())) {
    177      continue;
    178    }
    179 
    180    MOZ_ASSERT(touch->mOriginalTarget);
    181    nsIContent* const targetContent =
    182        nsIContent::FromEventTargetOrNull(touch->GetTarget());
    183    if (MOZ_UNLIKELY(!targetContent)) {
    184      touch->mIsTouchEventSuppressed = true;
    185      continue;
    186    }
    187 
    188    // Even if the target content is not connected, we should dispatch the touch
    189    // start event except when the target content is owned by different
    190    // document.
    191    if (MOZ_UNLIKELY(!targetContent->IsInComposedDoc())) {
    192      if (anyTarget && anyTarget->OwnerDoc() != targetContent->OwnerDoc()) {
    193        touch->mIsTouchEventSuppressed = true;
    194        continue;
    195      }
    196      if (!anyTarget) {
    197        anyTarget = targetContent;
    198      }
    199      touch->SetTouchTarget(targetContent->GetAsElementOrParentElement());
    200      if (PresShell* const presShell =
    201              targetContent->OwnerDoc()->GetPresShell()) {
    202        if (nsIFrame* rootFrame = presShell->GetRootFrame()) {
    203          frame = rootFrame;
    204        }
    205      }
    206      continue;
    207    }
    208 
    209    nsIFrame* targetFrame = targetContent->GetPrimaryFrame();
    210    if (targetFrame && !anyTarget) {
    211      anyTarget = targetContent;
    212    } else {
    213      nsIFrame* newTargetFrame = nullptr;
    214      for (nsIFrame* f = targetFrame; f;
    215           f = nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(f)) {
    216        if (f->PresContext()->Document() == anyTarget->OwnerDoc()) {
    217          newTargetFrame = f;
    218          break;
    219        }
    220        // We must be in a subdocument so jump directly to the root frame.
    221        // GetParentOrPlaceholderForCrossDoc gets called immediately to
    222        // jump up to the containing document.
    223        f = f->PresShell()->GetRootFrame();
    224      }
    225      // if we couldn't find a target frame in the same document as
    226      // anyTarget, remove the touch from the capture touch list, as
    227      // well as the event->mTouches array. touchmove events that aren't
    228      // in the captured touch list will be discarded
    229      if (!newTargetFrame) {
    230        touch->mIsTouchEventSuppressed = true;
    231      } else {
    232        targetFrame = newTargetFrame;
    233        nsIContent* newTargetContent = targetFrame->GetContentForEvent(aEvent);
    234        touch->SetTouchTarget(
    235            newTargetContent ? newTargetContent->GetAsElementOrParentElement()
    236                             : nullptr);
    237      }
    238    }
    239    if (targetFrame) {
    240      frame = targetFrame;
    241    }
    242  }
    243  return frame;
    244 }
    245 
    246 bool TouchManager::PreHandleEvent(WidgetEvent* aEvent, nsEventStatus* aStatus,
    247                                  bool& aTouchIsNew,
    248                                  nsCOMPtr<nsIContent>& aCurrentEventContent) {
    249  MOZ_DIAGNOSTIC_ASSERT(aEvent->IsTrusted());
    250 
    251  // NOTE: If you need to handle new event messages here, you need to add new
    252  //       cases in PresShell::EventHandler::PrepareToDispatchEvent().
    253  switch (aEvent->mMessage) {
    254    case eTouchStart: {
    255      WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
    256      // if there is only one touch in this touchstart event, assume that it is
    257      // the start of a new touch session and evict any old touches in the
    258      // queue
    259      if (touchEvent->mTouches.Length() == 1) {
    260        EvictTouches();
    261        // Per
    262        // https://w3c.github.io/touch-events/#touchevent-implementer-s-note,
    263        // all touch event should be dispatched to the same document that first
    264        // touch event associated to. We cache layers id of the first touchstart
    265        // event, all subsequent touch events will use the same layers id.
    266        sCaptureTouchLayersId = aEvent->mLayersId;
    267        sSingleTouchStartTimeStamp = aEvent->mTimeStamp;
    268        sSingleTouchStartPoint = touchEvent->mTouches[0]->mRefPoint;
    269        const PointerInfo* pointerInfo = PointerEventHandler::GetPointerInfo(
    270            touchEvent->mTouches[0]->Identifier());
    271        sPrecedingTouchPointerDownConsumedByContent =
    272            pointerInfo && pointerInfo->mPreventMouseEventByContent;
    273      } else {
    274        touchEvent->mLayersId = sCaptureTouchLayersId;
    275        sSingleTouchStartTimeStamp = TimeStamp();
    276      }
    277      // Add any new touches to the queue
    278      WidgetTouchEvent::TouchArray& touches = touchEvent->mTouches;
    279      for (int32_t i = touches.Length(); i;) {
    280        --i;
    281        Touch* touch = touches[i];
    282        int32_t id = touch->Identifier();
    283        if (!sCaptureTouchList->Get(id, nullptr)) {
    284          // If it is not already in the queue, it is a new touch
    285          touch->mChanged = true;
    286        }
    287        touch->mMessage = aEvent->mMessage;
    288        TouchInfo info = {
    289            touch, GetNonAnonymousAncestor(touch->mOriginalTarget), true};
    290        sCaptureTouchList->InsertOrUpdate(id, info);
    291        if (touch->mIsTouchEventSuppressed) {
    292          // We're going to dispatch touch event. Remove this touch instance if
    293          // it is suppressed.
    294          touches.RemoveElementAt(i);
    295          continue;
    296        }
    297      }
    298      break;
    299    }
    300    case eTouchRawUpdate:
    301      MOZ_ASSERT_UNREACHABLE("eTouchRawUpdate shouldn't be handled as a touch");
    302      break;
    303    case eTouchMove: {
    304      // Check for touches that changed. Mark them add to queue
    305      WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
    306      WidgetTouchEvent::TouchArray& touches = touchEvent->mTouches;
    307      touchEvent->mLayersId = sCaptureTouchLayersId;
    308      bool haveChanged = false;
    309      for (int32_t i = touches.Length(); i;) {
    310        --i;
    311        Touch* touch = touches[i];
    312        if (!touch) {
    313          continue;
    314        }
    315        int32_t id = touch->Identifier();
    316        touch->mMessage = aEvent->mMessage;
    317 
    318        TouchInfo info;
    319        if (!sCaptureTouchList->Get(id, &info)) {
    320          touches.RemoveElementAt(i);
    321          continue;
    322        }
    323        const RefPtr<Touch> oldTouch = info.mTouch;
    324        if (!oldTouch->Equals(touch)) {
    325          touch->mChanged = true;
    326          haveChanged = true;
    327        }
    328 
    329        nsCOMPtr<EventTarget> targetPtr = oldTouch->mOriginalTarget;
    330        if (!targetPtr) {
    331          touches.RemoveElementAt(i);
    332          continue;
    333        }
    334        nsCOMPtr<nsINode> targetNode(do_QueryInterface(targetPtr));
    335        if (!targetNode->IsInComposedDoc()) {
    336          targetPtr = info.mNonAnonymousTarget;
    337        }
    338        touch->SetTouchTarget(targetPtr);
    339 
    340        info.mTouch = touch;
    341        // info.mNonAnonymousTarget is still valid from above
    342        sCaptureTouchList->InsertOrUpdate(id, info);
    343        // if we're moving from touchstart to touchmove for this touch
    344        // we allow preventDefault to prevent mouse events
    345        if (oldTouch->mMessage != touch->mMessage) {
    346          aTouchIsNew = true;
    347        }
    348        if (oldTouch->mIsTouchEventSuppressed) {
    349          touch->mIsTouchEventSuppressed = true;
    350          touches.RemoveElementAt(i);
    351          continue;
    352        }
    353      }
    354      // is nothing has changed, we should just return
    355      if (!haveChanged) {
    356        if (aTouchIsNew) {
    357          // however, if this is the first touchmove after a touchstart,
    358          // it is special in that preventDefault is allowed on it, so
    359          // we must dispatch it to content even if nothing changed. we
    360          // arbitrarily pick the first touch point to be the "changed"
    361          // touch because firing an event with no changed events doesn't
    362          // work.
    363          for (uint32_t i = 0; i < touchEvent->mTouches.Length(); ++i) {
    364            if (touchEvent->mTouches[i]) {
    365              touchEvent->mTouches[i]->mChanged = true;
    366              break;
    367            }
    368          }
    369        } else {
    370          // This touch event isn't going to be dispatched on the main-thread,
    371          // we need to tell it to APZ because returned nsEventStatus is
    372          // unreliable to tell whether the event was preventDefaulted or not.
    373          layers::InputAPZContext::SetDropped();
    374          return false;
    375        }
    376      }
    377      break;
    378    }
    379    case eTouchEnd:
    380    case eTouchCancel: {
    381      // Remove the changed touches
    382      // need to make sure we only remove touches that are ending here
    383      WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
    384      WidgetTouchEvent::TouchArray& touches = touchEvent->mTouches;
    385      touchEvent->mLayersId = sCaptureTouchLayersId;
    386      for (int32_t i = touches.Length(); i;) {
    387        --i;
    388        Touch* touch = touches[i];
    389        if (!touch) {
    390          continue;
    391        }
    392        touch->mMessage = aEvent->mMessage;
    393        touch->mChanged = true;
    394 
    395        int32_t id = touch->Identifier();
    396        TouchInfo info;
    397        if (!sCaptureTouchList->Get(id, &info)) {
    398          continue;
    399        }
    400        nsCOMPtr<EventTarget> targetPtr = info.mTouch->mOriginalTarget;
    401        nsCOMPtr<nsINode> targetNode(do_QueryInterface(targetPtr));
    402        if (targetNode && !targetNode->IsInComposedDoc()) {
    403          targetPtr = info.mNonAnonymousTarget;
    404        }
    405 
    406        aCurrentEventContent = do_QueryInterface(targetPtr);
    407        touch->SetTouchTarget(targetPtr);
    408        sCaptureTouchList->Remove(id);
    409        if (info.mTouch->mIsTouchEventSuppressed) {
    410          touches.RemoveElementAt(i);
    411          continue;
    412        }
    413      }
    414      // add any touches left in the touch list, but ensure changed=false
    415      AppendToTouchList(&touches);
    416      break;
    417    }
    418    case eTouchPointerCancel: {
    419      // Don't generate pointer events by touch events after eTouchPointerCancel
    420      // is received.
    421      WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
    422      WidgetTouchEvent::TouchArray& touches = touchEvent->mTouches;
    423      touchEvent->mLayersId = sCaptureTouchLayersId;
    424      for (uint32_t i = 0; i < touches.Length(); ++i) {
    425        Touch* touch = touches[i];
    426        if (!touch) {
    427          continue;
    428        }
    429        int32_t id = touch->Identifier();
    430        TouchInfo info;
    431        if (!sCaptureTouchList->Get(id, &info)) {
    432          continue;
    433        }
    434        info.mConvertToPointer = false;
    435        sCaptureTouchList->InsertOrUpdate(id, info);
    436      }
    437      break;
    438    }
    439    default:
    440      break;
    441  }
    442  return true;
    443 }
    444 
    445 void TouchManager::PostHandleEvent(const WidgetEvent* aEvent,
    446                                   const nsEventStatus* aStatus) {
    447  switch (aEvent->mMessage) {
    448    case eTouchRawUpdate:
    449      MOZ_ASSERT_UNREACHABLE("eTouchRawUpdate shouldn't be handled as a touch");
    450      break;
    451    case eTouchMove: {
    452      if (sSingleTouchStartTimeStamp.IsNull()) {
    453        break;
    454      }
    455      if (*aStatus == nsEventStatus_eConsumeNoDefault) {
    456        sSingleTouchStartTimeStamp = TimeStamp();
    457        break;
    458      }
    459      const WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
    460      if (touchEvent->mTouches.Length() > 1) {
    461        sSingleTouchStartTimeStamp = TimeStamp();
    462        break;
    463      }
    464      if (touchEvent->mTouches.Length() == 1) {
    465        // If the touch moved too far from the start point, don't treat the
    466        // touch as a tap.
    467        const float distance =
    468            static_cast<float>((sSingleTouchStartPoint -
    469                                aEvent->AsTouchEvent()->mTouches[0]->mRefPoint)
    470                                   .Length());
    471        const float maxDistance =
    472            StaticPrefs::apz_touch_start_tolerance() *
    473            (MOZ_LIKELY(touchEvent->mWidget) ? touchEvent->mWidget->GetDPI()
    474                                             : 96.0f);
    475        if (distance > maxDistance) {
    476          sSingleTouchStartTimeStamp = TimeStamp();
    477        }
    478      }
    479      break;
    480    }
    481    case eTouchStart:
    482    case eTouchEnd:
    483      if (*aStatus == nsEventStatus_eConsumeNoDefault &&
    484          !sSingleTouchStartTimeStamp.IsNull()) {
    485        sSingleTouchStartTimeStamp = TimeStamp();
    486      }
    487      break;
    488    case eTouchCancel:
    489    case eTouchPointerCancel:
    490    case eMouseLongTap:
    491    case eContextMenu: {
    492      if (!sSingleTouchStartTimeStamp.IsNull()) {
    493        sSingleTouchStartTimeStamp = TimeStamp();
    494      }
    495      break;
    496    }
    497    default:
    498      break;
    499  }
    500 }
    501 
    502 /*static*/
    503 already_AddRefed<nsIContent> TouchManager::GetAnyCapturedTouchTarget() {
    504  nsCOMPtr<nsIContent> result = nullptr;
    505  if (sCaptureTouchList->Count() == 0) {
    506    return result.forget();
    507  }
    508  for (const auto& data : sCaptureTouchList->Values()) {
    509    const RefPtr<Touch>& touch = data.mTouch;
    510    if (touch) {
    511      EventTarget* target = touch->GetTarget();
    512      if (target) {
    513        result = nsIContent::FromEventTargetOrNull(target);
    514        break;
    515      }
    516    }
    517  }
    518  return result.forget();
    519 }
    520 
    521 /*static*/
    522 bool TouchManager::HasCapturedTouch(int32_t aId) {
    523  return sCaptureTouchList->Contains(aId);
    524 }
    525 
    526 /*static*/
    527 already_AddRefed<Touch> TouchManager::GetCapturedTouch(int32_t aId) {
    528  RefPtr<Touch> touch;
    529  TouchInfo info;
    530  if (sCaptureTouchList->Get(aId, &info)) {
    531    touch = info.mTouch;
    532  }
    533  return touch.forget();
    534 }
    535 
    536 /*static*/
    537 bool TouchManager::ShouldConvertTouchToPointer(const Touch* aTouch,
    538                                               const WidgetTouchEvent* aEvent) {
    539  if (!aTouch || !aTouch->convertToPointer) {
    540    return false;
    541  }
    542  TouchInfo info;
    543  if (!sCaptureTouchList->Get(aTouch->Identifier(), &info)) {
    544    // This check runs before the TouchManager has the touch registered in its
    545    // touch list. It's because we dispatching pointer events before handling
    546    // touch events. So we convert eTouchStart to pointerdown even it's not
    547    // registered.
    548    // Check WidgetTouchEvent::mMessage because Touch::mMessage is assigned when
    549    // pre-handling touch events.
    550    return aEvent->mMessage == eTouchStart;
    551  }
    552 
    553  if (!info.mConvertToPointer) {
    554    return false;
    555  }
    556 
    557  switch (aEvent->mMessage) {
    558    case eTouchStart: {
    559      // We don't want to fire duplicated pointerdown.
    560      return false;
    561    }
    562    case eTouchMove:
    563    case eTouchRawUpdate: {
    564      return !aTouch->Equals(info.mTouch);
    565    }
    566    default:
    567      break;
    568  }
    569  return true;
    570 }
    571 
    572 /* static */
    573 bool TouchManager::IsSingleTapEndToDoDefault(
    574    const WidgetTouchEvent* aTouchEndEvent) {
    575  MOZ_ASSERT(aTouchEndEvent);
    576  MOZ_ASSERT(aTouchEndEvent->mFlags.mIsSynthesizedForTests);
    577  MOZ_ASSERT(!StaticPrefs::test_events_async_enabled());
    578  if (sSingleTouchStartTimeStamp.IsNull() ||
    579      aTouchEndEvent->mTouches.Length() != 1) {
    580    return false;
    581  }
    582  // If it's pressed long time, we should not treat it as a single tap because
    583  // a long press should cause opening context menu by default.
    584  if ((aTouchEndEvent->mTimeStamp - sSingleTouchStartTimeStamp)
    585          .ToMilliseconds() > StaticPrefs::apz_max_tap_time()) {
    586    return false;
    587  }
    588  NS_WARNING_ASSERTION(aTouchEndEvent->mTouches[0]->mChanged,
    589                       "The single tap end should be changed");
    590  return true;
    591 }
    592 
    593 /* static */
    594 bool TouchManager::IsPrecedingTouchPointerDownConsumedByContent() {
    595  return sPrecedingTouchPointerDownConsumedByContent;
    596 }
    597 
    598 }  // namespace mozilla