229 lines
7.8 KiB
C++
229 lines
7.8 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||
/* 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 "PerformanceInteractionMetrics.h"
|
||
#include "mozilla/EventForwards.h"
|
||
#include "mozilla/Maybe.h"
|
||
#include "mozilla/MouseEvents.h"
|
||
#include "mozilla/RandomNum.h"
|
||
#include "mozilla/TextEvents.h"
|
||
|
||
// Interaction ID increment. We increase this value by an integer greater than 1
|
||
// to discourage developers from using the value to 'count' the number of user
|
||
// interactions. This is consistent with the spec, which allows the increasing
|
||
// the user interaction value by a small number chosen by the user agent.
|
||
constexpr uint32_t kInteractionIdIncrement = 7;
|
||
// Minimum potential value for the first Interaction ID.
|
||
constexpr uint32_t kMinFirstInteractionID = 100;
|
||
// Maximum potential value for the first Interaction ID.
|
||
constexpr uint32_t kMaxFirstInteractionID = 10000;
|
||
|
||
constexpr uint32_t kNonPointerId = -1;
|
||
|
||
namespace mozilla::dom {
|
||
|
||
PerformanceInteractionMetrics::PerformanceInteractionMetrics() {
|
||
uint64_t randVal = RandomUint64().valueOr(kMinFirstInteractionID);
|
||
// Choose a random integer as the initial value to discourage developers from
|
||
// using interactionId to counter the number of interactions.
|
||
// https://wicg.github.io/event-timing/#user-interaction-value
|
||
mCurrentInteractionValue =
|
||
kMinFirstInteractionID +
|
||
(randVal % (kMaxFirstInteractionID - kMinFirstInteractionID + 1));
|
||
}
|
||
|
||
// https://w3c.github.io/event-timing/#sec-increasing-interaction-count
|
||
uint64_t PerformanceInteractionMetrics::IncreaseInteractionValueAndCount() {
|
||
mCurrentInteractionValue += kInteractionIdIncrement;
|
||
mInteractionCount++;
|
||
return mCurrentInteractionValue;
|
||
}
|
||
|
||
// https://w3c.github.io/event-timing/#sec-computing-interactionid
|
||
Maybe<uint64_t> PerformanceInteractionMetrics::ComputeInteractionId(
|
||
PerformanceEventTiming* aEventTiming, const WidgetEvent* aEvent) {
|
||
// Step 1. If event’s isTrusted attribute value is false, return 0.
|
||
if (!aEvent->IsTrusted()) {
|
||
return Some(0);
|
||
}
|
||
|
||
// Step 2. Let type be event’s type attribute value.
|
||
const EventMessage eventType = aEvent->mMessage;
|
||
|
||
// Step 3. If type is not one among keyup, compositionstart, input,
|
||
// pointercancel, pointerup, or click, return 0.
|
||
// Note: keydown and pointerdown are handled in finalize event timing.
|
||
switch (eventType) {
|
||
case eKeyDown:
|
||
case eKeyUp:
|
||
case eCompositionStart:
|
||
case eEditorInput:
|
||
case ePointerCancel:
|
||
case ePointerDown:
|
||
case ePointerUp:
|
||
case ePointerClick:
|
||
break;
|
||
default:
|
||
return Some(0);
|
||
}
|
||
|
||
// Step 4-8. Happens in the class constructor.
|
||
|
||
if (eventType == ePointerDown) {
|
||
uint32_t pointerId = aEvent->AsPointerEvent()->pointerId;
|
||
|
||
mPendingPointerDowns.InsertOrUpdate(pointerId, aEventTiming);
|
||
// InteractionId for this will be assigned by pointerup or pointercancel
|
||
// later.
|
||
return Nothing();
|
||
}
|
||
|
||
if (eventType == eKeyDown) {
|
||
const WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent();
|
||
|
||
if (keyEvent->mIsComposing) {
|
||
return Some(0);
|
||
}
|
||
|
||
auto code = keyEvent->mKeyCode;
|
||
|
||
// This is not part of the spec yet, but it's being discussed and will be
|
||
// added to the spec soon.
|
||
// See: https://github.com/w3c/event-timing/issues/153
|
||
mPendingKeyDowns.InsertOrUpdate(code, aEventTiming);
|
||
uint64_t interactionId = IncreaseInteractionValueAndCount();
|
||
return Some(interactionId);
|
||
}
|
||
|
||
// Step 8. If type is keyup:
|
||
if (eventType == eKeyUp) {
|
||
// Step 8.1. If event’s isComposing attribute value is true, return 0.
|
||
const WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent();
|
||
if (keyEvent->mIsComposing) {
|
||
return Some(0);
|
||
}
|
||
|
||
// Step 8.2. Let code be event’s keyCode attribute value.
|
||
const uint32_t code = keyEvent->mKeyCode;
|
||
|
||
// Step 8.4. Let entry be pendingKeyDowns[code].
|
||
auto entry = mPendingKeyDowns.MaybeGet(code);
|
||
// Step 8.3. If pendingKeyDowns[code] does not exist, return 0.
|
||
if (!entry) {
|
||
return Some(0);
|
||
}
|
||
|
||
uint64_t interactionId = (*entry)->InteractionId();
|
||
|
||
// Step 8.9. Remove pendingKeyDowns[code].
|
||
mPendingKeyDowns.Remove(code);
|
||
mLastKeydownInteractionValue = Some(interactionId);
|
||
|
||
// Step 8.10. Return interactionId.
|
||
return Some(interactionId);
|
||
}
|
||
|
||
// Step 9. If type is compositionstart:
|
||
if (eventType == eCompositionStart) {
|
||
// Step 9.1 For each entry in the values of pendingKeyDowns:
|
||
for (auto iter = mPendingKeyDowns.Iter(); !iter.Done(); iter.Next()) {
|
||
PerformanceEventTiming* entry = iter.Data();
|
||
// Step 9.1.1. Append entry to window’s entries to be queued.
|
||
entry->SetInteractionId(0);
|
||
}
|
||
|
||
// Step 9.2. Clear pendingKeyDowns.
|
||
mPendingKeyDowns.Clear();
|
||
// Step 9.3. Return 0
|
||
return Some(0);
|
||
}
|
||
|
||
// Step 10. If type is input:
|
||
if (eventType == eEditorInput) {
|
||
// Step 10.1. If event is not an instance of InputEvent, return 0.
|
||
const auto* inputEvent = aEvent->AsEditorInputEvent();
|
||
if (!inputEvent) {
|
||
return Some(0);
|
||
}
|
||
|
||
// Step 10.2. If event’s isComposing attribute value is false, return 0.
|
||
if (!inputEvent->mIsComposing) {
|
||
return Some(0);
|
||
}
|
||
|
||
mLastKeydownInteractionValue = Nothing();
|
||
return Some(IncreaseInteractionValueAndCount());
|
||
}
|
||
|
||
// Step 11. Otherwise (type is pointercancel, pointerup, or click):
|
||
|
||
MOZ_ASSERT(eventType == ePointerCancel || eventType == ePointerUp ||
|
||
eventType == ePointerClick,
|
||
"Unexpected event type");
|
||
const auto* mouseEvent = aEvent->AsMouseEvent();
|
||
// Step 11.1. Let pointerId be event’s pointerId attribute value.
|
||
auto pointerId = mouseEvent->pointerId;
|
||
|
||
// Step 11.2. If type is click:
|
||
if (eventType == ePointerClick) {
|
||
if (pointerId == kNonPointerId) {
|
||
// -1 pointerId is a reserved value to indicate events that were generated
|
||
// by something other than a pointer device, like keydown.
|
||
// Return the interaction value of the keydown event instead.
|
||
return Some(mLastKeydownInteractionValue.valueOr(0));
|
||
}
|
||
|
||
// Step 11.2.2. Let value be pointerMap[pointerId].
|
||
auto value = mPointerInteractionValueMap.MaybeGet(pointerId);
|
||
// Step 11.2.1. If pointerMap[pointerId] does not exist, return 0.
|
||
if (!value) {
|
||
return Some(0);
|
||
}
|
||
|
||
// Step 11.2.3. Remove pointerMap[pointerId].
|
||
mPointerInteractionValueMap.Remove(pointerId);
|
||
// Step 11.2.4. Return value.
|
||
return Some(*value);
|
||
}
|
||
|
||
// Step 11.3. Assert that type is pointerup or pointercancel.
|
||
MOZ_RELEASE_ASSERT(eventType == ePointerUp || eventType == ePointerCancel);
|
||
|
||
// Step 11.5. Let pointerDownEntry be pendingPointerDowns[pointerId].
|
||
auto entry = mPendingPointerDowns.MaybeGet(pointerId);
|
||
// Step 11.4. If pendingPointerDowns[pointerId] does not exist, return 0.
|
||
if (!entry) {
|
||
return Some(0);
|
||
}
|
||
|
||
// Step 11.7. If type is pointerup:
|
||
if (eventType == ePointerUp) {
|
||
// Step 11.7.1. Increase interaction count on window.
|
||
uint64_t interactionId = IncreaseInteractionValueAndCount();
|
||
|
||
// Step 11.7.2. Set pointerMap[pointerId] to window’s user interaction
|
||
// value.
|
||
mPointerInteractionValueMap.InsertOrUpdate(pointerId, interactionId);
|
||
|
||
// Step 11.7.3. Set pointerDownEntry’s interactionId to
|
||
// pointerMap[pointerId].
|
||
(*entry)->SetInteractionId(interactionId);
|
||
} else {
|
||
(*entry)->SetInteractionId(0);
|
||
}
|
||
|
||
// Step 11.9. Remove pendingPointerDowns[pointerId].
|
||
mPendingPointerDowns.Remove(pointerId);
|
||
|
||
// Step 11.10. If type is pointercancel, return 0.
|
||
if (eventType == ePointerCancel) {
|
||
return Some(0);
|
||
}
|
||
|
||
return Some(mPointerInteractionValueMap.Get(pointerId));
|
||
}
|
||
|
||
} // namespace mozilla::dom
|