tor-browser

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

PerformanceInteractionMetrics.cpp (9675B)


      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 #include "PerformanceInteractionMetrics.h"
      8 
      9 #include "mozilla/EventForwards.h"
     10 #include "mozilla/Maybe.h"
     11 #include "mozilla/MouseEvents.h"
     12 #include "mozilla/RandomNum.h"
     13 #include "mozilla/TextEvents.h"
     14 
     15 // Interaction ID increment. We increase this value by an integer greater than 1
     16 // to discourage developers from using the value to 'count' the number of user
     17 // interactions. This is consistent with the spec, which allows the increasing
     18 // the user interaction value by a small number chosen by the user agent.
     19 constexpr uint32_t kInteractionIdIncrement = 7;
     20 // Minimum potential value for the first Interaction ID.
     21 constexpr uint32_t kMinFirstInteractionID = 100;
     22 // Maximum potential value for the first Interaction ID.
     23 constexpr uint32_t kMaxFirstInteractionID = 10000;
     24 
     25 constexpr uint32_t kNonPointerId = -1;
     26 
     27 namespace mozilla::dom {
     28 
     29 PerformanceInteractionMetrics::PerformanceInteractionMetrics() {
     30  uint64_t randVal = RandomUint64().valueOr(kMinFirstInteractionID);
     31  // Choose a random integer as the initial value to discourage developers from
     32  // using interactionId to counter the number of interactions.
     33  // https://wicg.github.io/event-timing/#user-interaction-value
     34  mCurrentInteractionValue =
     35      kMinFirstInteractionID +
     36      (randVal % (kMaxFirstInteractionID - kMinFirstInteractionID + 1));
     37 }
     38 
     39 // https://w3c.github.io/event-timing/#sec-increasing-interaction-count
     40 uint64_t PerformanceInteractionMetrics::IncreaseInteractionValueAndCount() {
     41  mCurrentInteractionValue += kInteractionIdIncrement;
     42  mInteractionCount++;
     43  return mCurrentInteractionValue;
     44 }
     45 
     46 // https://w3c.github.io/event-timing/#sec-computing-interactionid
     47 Maybe<uint64_t> PerformanceInteractionMetrics::ComputeInteractionId(
     48    PerformanceEventTiming* aEventTiming, const WidgetEvent* aEvent) {
     49  // Step 1. If event’s isTrusted attribute value is false, return 0.
     50  if (!aEvent->IsTrusted()) {
     51    return Some(0);
     52  }
     53 
     54  // Step 2. Let type be event’s type attribute value.
     55  const EventMessage eventType = aEvent->mMessage;
     56 
     57  // Step 3. If type is not one among keyup, compositionstart, input,
     58  // pointercancel, pointerup, or click, return 0.
     59  // Note: keydown and pointerdown are handled in finalize event timing.
     60  switch (eventType) {
     61    case eKeyDown:
     62    case eKeyPress:
     63    case eKeyUp:
     64    case eCompositionStart:
     65    case eEditorInput:
     66    case ePointerCancel:
     67    case ePointerDown:
     68    case ePointerUp:
     69    case eContextMenu:
     70    case ePointerClick:
     71      break;
     72    default:
     73      return Some(0);
     74  }
     75 
     76  // Step 4-8. Happens in the class constructor.
     77 
     78  if (eventType == ePointerDown) {
     79    uint32_t pointerId = aEvent->AsPointerEvent()->pointerId;
     80 
     81    // If after the previously buffered pointerdown we haven't encountered any
     82    // matching pointerup, we need to set it as zero so it properly flushes the
     83    // events.
     84    auto bufferedPointerdown = mPendingPointerDowns.MaybeGet(pointerId);
     85    if (NS_WARN_IF(bufferedPointerdown &&
     86                   !(*bufferedPointerdown)->HasKnownInteractionId())) {
     87      (*bufferedPointerdown)->SetInteractionId(0);
     88    }
     89    mPendingPointerDowns.InsertOrUpdate(pointerId, aEventTiming);
     90    mContextMenuTriggered = false;
     91    // InteractionId for this will be assigned by pointerup or pointercancel
     92    // later.
     93    return Nothing();
     94  }
     95 
     96  if (eventType == eKeyDown) {
     97    const WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent();
     98 
     99    if (keyEvent->mIsComposing) {
    100      return Some(0);
    101    }
    102 
    103    auto code = keyEvent->mKeyCode;
    104 
    105    // This is not part of the spec yet, but it's being discussed and will be
    106    // added to the spec soon.
    107    // See: https://github.com/w3c/event-timing/issues/153
    108    mPendingKeyDowns.InsertOrUpdate(code, aEventTiming);
    109    uint64_t interactionId = IncreaseInteractionValueAndCount();
    110    mLastKeydownInteractionValue = Some(interactionId);
    111    return Some(interactionId);
    112  }
    113 
    114  // keypress is not a part of the spec yet, but it's being worked on:
    115  // https://github.com/w3c/event-timing/issues/149
    116  if (eventType == eKeyPress) {
    117    // Set the interaction id generated by the previous keydown entry.
    118    return Some(mCurrentInteractionValue);
    119  }
    120 
    121  // Step 8. If type is keyup:
    122  if (eventType == eKeyUp) {
    123    // Step 8.1. If event’s isComposing attribute value is true, return 0.
    124    const WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent();
    125    if (keyEvent->mIsComposing) {
    126      return Some(0);
    127    }
    128 
    129    // Step 8.2. Let code be event’s keyCode attribute value.
    130    const uint32_t code = keyEvent->mKeyCode;
    131 
    132    // Step 8.4. Let entry be pendingKeyDowns[code].
    133    auto entry = mPendingKeyDowns.MaybeGet(code);
    134    // Step 8.3. If pendingKeyDowns[code] does not exist, return 0.
    135    if (!entry) {
    136      return Some(0);
    137    }
    138 
    139    uint64_t interactionId = (*entry)->InteractionId();
    140 
    141    // Step 8.9. Remove pendingKeyDowns[code].
    142    mPendingKeyDowns.Remove(code);
    143 
    144    // Step 8.10. Return interactionId.
    145    return Some(interactionId);
    146  }
    147 
    148  // Step 9. If type is compositionstart:
    149  if (eventType == eCompositionStart) {
    150    // Step 9.1 For each entry in the values of pendingKeyDowns:
    151    for (auto iter = mPendingKeyDowns.Iter(); !iter.Done(); iter.Next()) {
    152      PerformanceEventTiming* entry = iter.Data();
    153      // Step 9.1.1. Append entry to window’s entries to be queued.
    154      entry->SetInteractionId(0);
    155    }
    156 
    157    // Step 9.2. Clear pendingKeyDowns.
    158    mPendingKeyDowns.Clear();
    159    // Step 9.3. Return 0
    160    return Some(0);
    161  }
    162 
    163  // Step 10. If type is input:
    164  if (eventType == eEditorInput) {
    165    // Step 10.1. If event is not an instance of InputEvent, return 0.
    166    const auto* inputEvent = aEvent->AsEditorInputEvent();
    167    if (!inputEvent) {
    168      return Some(0);
    169    }
    170 
    171    // Step 10.2. If event’s isComposing attribute value is false, return 0.
    172    if (!inputEvent->mIsComposing) {
    173      return Some(0);
    174    }
    175 
    176    mLastKeydownInteractionValue = Nothing();
    177    return Some(IncreaseInteractionValueAndCount());
    178  }
    179 
    180  // Step 11. Otherwise (type is pointercancel, pointerup, or click):
    181 
    182  MOZ_ASSERT(eventType == ePointerCancel || eventType == ePointerUp ||
    183                 eventType == ePointerClick || eventType == eContextMenu,
    184             "Unexpected event type");
    185  const auto* mouseEvent = aEvent->AsMouseEvent();
    186  // Step 11.1. Let pointerId be event’s pointerId attribute value.
    187  auto pointerId = mouseEvent->pointerId;
    188 
    189  // Step 11.2. If type is click:
    190  if (eventType == ePointerClick) {
    191    if (pointerId == kNonPointerId) {
    192      // -1 pointerId is a reserved value to indicate events that were generated
    193      // by something other than a pointer device, like keydown.
    194      // Return the interaction value of the keydown event instead.
    195      return Some(mLastKeydownInteractionValue.valueOr(0));
    196    }
    197 
    198    // Step 11.2.2. Let value be pointerMap[pointerId].
    199    auto value = mPointerInteractionValueMap.MaybeGet(pointerId);
    200    // Step 11.2.1. If pointerMap[pointerId] does not exist, return 0.
    201    if (!value) {
    202      return Some(0);
    203    }
    204 
    205    // Step 11.2.3. Remove pointerMap[pointerId].
    206    mPointerInteractionValueMap.Remove(pointerId);
    207    // Step 11.2.4. Return value.
    208    return Some(*value);
    209  }
    210 
    211  // Step 11.3. Assert that type is pointerup or pointercancel.
    212  MOZ_RELEASE_ASSERT(eventType == ePointerUp || eventType == ePointerCancel ||
    213                     eventType == eContextMenu);
    214 
    215  // Step 11.5. Let pointerDownEntry be pendingPointerDowns[pointerId].
    216  auto entry = mPendingPointerDowns.MaybeGet(pointerId);
    217  // Step 11.4. If pendingPointerDowns[pointerId] does not exist, return 0.
    218  if (!entry) {
    219    // This is the case where we have seen a pointerup before a contextmenu
    220    // event. We return the same interactionId for the contextmenu.
    221    // See https://github.com/w3c/event-timing/issues/155.
    222    if (eventType == eContextMenu) {
    223      return Some(mCurrentInteractionValue);
    224    }
    225 
    226    // This is the case where we have seen a contextmenu before a pointerup
    227    // event. Similarly, we return the same interactionId, but also we reset the
    228    // "is contextmenu triggered" flag to make sure that the next events are
    229    // handled correctly. See https://github.com/w3c/event-timing/issues/155.
    230    if (eventType == ePointerUp && mContextMenuTriggered) {
    231      mContextMenuTriggered = false;
    232      return Some(mCurrentInteractionValue);
    233    }
    234    return Some(0);
    235  }
    236 
    237  // Step 11.7. If type is pointerup:
    238  if (eventType == ePointerUp || eventType == eContextMenu) {
    239    // Step 11.7.1. Increase interaction count on window.
    240    uint64_t interactionId = IncreaseInteractionValueAndCount();
    241 
    242    // Step 11.7.2. Set pointerMap[pointerId] to window’s user interaction
    243    // value.
    244    mPointerInteractionValueMap.InsertOrUpdate(pointerId, interactionId);
    245 
    246    // Step 11.7.3. Set pointerDownEntry’s interactionId to
    247    // pointerMap[pointerId].
    248    (*entry)->SetInteractionId(interactionId);
    249  } else {
    250    (*entry)->SetInteractionId(0);
    251  }
    252 
    253  // Step 11.9. Remove pendingPointerDowns[pointerId].
    254  mPendingPointerDowns.Remove(pointerId);
    255 
    256  if (eventType == eContextMenu) {
    257    mContextMenuTriggered = true;
    258  }
    259 
    260  // Step 11.10. If type is pointercancel, return 0.
    261  if (eventType == ePointerCancel) {
    262    return Some(0);
    263  }
    264 
    265  return Some(mPointerInteractionValueMap.Get(pointerId));
    266 }
    267 
    268 }  // namespace mozilla::dom