0

[Event Timing] Post launch cleanup for 2 Experiments

* Remove EventTimingKeypressAndCompositionInteractionId flag
* Remove EventTimingFallbackToModalDialogStart flag
* Update Key_interaction_state_machine doc
* Fix FlushKeydown to cover key_code_to_interaction_info_map_

Preview of the state machine doc: https://chromium.googlesource.com/chromium/src/+/refs/changes/50/5854350/3/third_party/blink/renderer/core/timing/Key_interaction_state_machine.md

Bug: 1435448,1456384
Change-Id: I862d444a2adcd5dc0d77e09ed49cc660e0b3e86d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5854350
Commit-Queue: Aoyuan Zuo <zuoaoyuan@chromium.org>
Reviewed-by: Michal Mocny <mmocny@chromium.org>
Reviewed-by: Kentaro Hara <haraken@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1354832}
This commit is contained in:
Aoyuan Zuo
2024-09-12 21:45:37 +00:00
committed by Chromium LUCI CQ
parent e80fa82591
commit 97db5bc75e
10 changed files with 214 additions and 414 deletions

@ -876,13 +876,6 @@ public final class ProductionSupportedFlagList {
Flag.baseFeature(
BlinkFeatures.BACK_FORWARD_CACHE_SEND_NOT_RESTORED_REASONS,
"Expose NotRestoredReasons via PerformanceNavigationTiming API."),
Flag.baseFeature(
BlinkFeatures.EVENT_TIMING_FALLBACK_TO_MODAL_DIALOG_START,
"Enable reporting the modal dialog start time as an alternative end time for"
+ " duration measurement in performance event timing."),
Flag.baseFeature(
BlinkFeatures.EVENT_TIMING_KEYPRESS_AND_COMPOSITION_INTERACTION_ID,
"Exposes Event Timing keyboard InteractionId of composition and keypress events."),
Flag.baseFeature("SkipUnnecessaryThreadHopsForParseHeaders"),
Flag.commandLine(
AwSwitches.WEBVIEW_FPS_COMPONENT,

Binary file not shown.

After

(image error) Size: 182 KiB

File diff suppressed because one or more lines are too long

Before

(image error) Size: 12 KiB

@ -786,12 +786,6 @@ BASE_FEATURE(kEstablishGpuChannelAsync,
#endif
);
// Exposes Event Timing keyboard InteractionId of composition and keypress
// events.
BASE_FEATURE(kEventTimingKeypressAndCompositionInteractionId,
"EventTimingKeypressAndCompositionInteractionId",
base::FEATURE_ENABLED_BY_DEFAULT);
// Enables unload handler deprecation via Permissions-Policy.
// https://crbug.com/1324111
BASE_FEATURE(kDeprecateUnload,
@ -816,12 +810,6 @@ BASE_FEATURE(kDeprecateUnloadByAllowList,
const base::FeatureParam<std::string> kDeprecateUnloadAllowlist{
&kDeprecateUnloadByAllowList, "allowlist", ""};
// Enable reporting the modal dialog start time as an alternative end time for
// duration measurement in performance event timing.
BASE_FEATURE(kEventTimingFallbackToModalDialogStart,
"EventTimingFallbackToModalDialogStart",
base::FEATURE_ENABLED_BY_DEFAULT);
// Enable not reporting orphan pointerup (pointerup not accompanied by
// pointerdown) as an interaction in performance event timing.
BASE_FEATURE(kEventTimingHandleOrphanPointerup,

@ -458,11 +458,6 @@ BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kDropInputEventsBeforeFirstPaint);
// layer tree frame sink.
BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kEstablishGpuChannelAsync);
// Exposes Event Timing interactionId of keypress/keyboard events under
// composition.
BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(
kEventTimingKeypressAndCompositionInteractionId);
BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kDeprecateUnload);
BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kDeprecateUnloadByAllowList);
BLINK_COMMON_EXPORT extern const base::FeatureParam<int>
@ -471,9 +466,6 @@ BLINK_COMMON_EXPORT extern const base::FeatureParam<int> kDeprecateUnloadBucket;
BLINK_COMMON_EXPORT extern const base::FeatureParam<std::string>
kDeprecateUnloadAllowlist;
BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(
kEventTimingFallbackToModalDialogStart);
BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kEventTimingHandleOrphanPointerup);
BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kExcludeLowEntropyImagesFromLCP);

@ -1,15 +1,18 @@
# Event Timing - Key Interaction State Machine
Patricija Cerkaite, May 29 2023
Patricija Cerkaite, created on May 29 2023
Aoyuan Zuo, updated on Sept. 09 2024
[TOC]
## Background
A keyboard interaction is a group of event handlers that fire when pressing a key on a keyboard. For example, A single "key pressed" interaction should include set order of events, such as `keydown`, `input`, and `keyup`. [EventTiming](https://w3c.github.io/event-timing/) group up certain events as interactions by assigning the same & non-trivial [interactionId](https://www.w3.org/TR/2022/WD-event-timing-20220524/#dom-performanceeventtiming-interactionid) following a state machine logic located in [`responsiveness_metrics.cc -> ResponsivenessMetrics::SetKeyIdAndRecordLatency()`](https://chromium.googlesource.com/chromium/src/+/main/third_party/blink/renderer/core/timing/responsiveness_metrics.cc#327). This doc visualizes this state machine to help people understand its logic.
A keyboard interaction is a group of event handlers that fire when pressing a key on a keyboard. For example, A single "key pressed" interaction should include set order of events, such as `keydown`, `keypress`, `input`, and `keyup`. [EventTiming](https://w3c.github.io/event-timing/) group up certain events as interactions by assigning the same & non-trivial [interactionId](https://www.w3.org/TR/2022/WD-event-timing-20220524/#dom-performanceeventtiming-interactionid) following a state machine logic located in [`responsiveness_metrics.cc -> ResponsivenessMetrics::SetKeyIdAndRecordLatency()`](https://chromium.googlesource.com/chromium/src/+/main/third_party/blink/renderer/core/timing/responsiveness_metrics.cc#327). This doc visualizes this state machine to help people understand its logic.
- - - -
## Diagram
![state diagram](/docs/images/Key_interaction_state_machine_diagram.svg)
![state diagram](/docs/images/Key_interaction_state_machine_diagram.png)
*** note
### Note:
@ -21,46 +24,92 @@ A keyboard interaction is a group of event handlers that fire when pressing a ke
## States
### `[1]` No entry
The initial state. Either no entry of the key_code has been seen or previous ones have been cancelled.
`key_code_entry_map_` does not contain any entry with a key equal to key_code.
### `[1]` Non Composition
The initial state. No entry of the key_code has been seen. `key_code_to_interaction_info_map_` does not contain any entry with a key equal to key_code.
### `[2]` Have keydown entry
An intermediate state. In this state, we have seen the `keydown` entry for the current interaction, and are waiting for the matching `keyup` entry.
`key_code_entry_map_` currently contains the `keydown` entry of the interaction that this state machine represent. In this state, `keydown` entry waiting for a **matching** `keyup` entry to finish the current interaction.
### `[2]` Have Keydown
An intermediate state. In this state, we have seen the `keydown` entry for the current interaction, and are waiting for potentially `keypress` or `contextmenu` events, and a matching `keyup` entry.
`key_code_to_interaction_info_map_` and `sequence_based_keyboard_interaction_info_` currently contains the `keydown` entry.
### `[3]` Composition Event
The state indicates that `keydown` has initiated the composition. Since the composition events are not part of the keyboard interactions this intermediate state holds until the interactionId is produced.
### `[3]` Have Keypress
An intermediate state. In this state, we have seen the `keydown` and `keypress` entries for the current interaction, and are waiting for the matching `keyup` entry.
`key_code_to_interaction_info_map_` currently contains the timestamps of both `keydown` and `keypress` entries of the interaction that this state machine represent. In this state, we are waiting for the **matching** `keyup` entry to finish the current interaction.
### `[4]` Interaction finished
This is the end of an interaction lifecycle. The `keydown` entry was paired with the corresponding `keyup` entry and the key_code from the `key_code_entry_map_` was errased.
### `[4]` Have Contextmenu
When pressing the menu key on a keyboard, a `contextmenu` event would get dispatched right after the `keydown` event. This is a valid user interaction, but the `keyup` event could possibly be dropped due to the showing of contextmenu overlay. Thus, while we waiting for the potentially coming `keyup` event, we also setup a 1 second timer to wrap up this interaction in case `keyup` is not coming.
### `[5]` Composition Continue Ongoing Interaction
The state indicates that `compositionstart` has initiated the composition. Since the composition events (`compositionstart`, `compositionupdate`, `compositionend`) are not part of the keyboard interactions this intermediate state holds until the interactionId is produced.
### `[6]` Composition Start New Interaction On Keydown
This state means we're currently under composition, and we're ready to start recording a new interaction upon next `keydown`.
### `[7]` Composition Start New Interaction On Input
This state means we're currently under composition, and we have not seen `keydown` thus no interactionId has been generated yet for the current interaction. As a result, we will generate one on `input`.
### `[8]` End Composition On Keydown
We have get out of the composition session, but are still waiting for potentially coming `keyup`s to wrap up the last interaction in composition.
### `[9]` Interaction finished
This is the end of an interaction lifecycle. The `keydown` entry was paired with the corresponding `keyup` entry and the key_code from the `key_code_to_interaction_info_map_` was erased.
- - - -
## Transitions
### `[5]` keydown
### `[10]` Keydown
Generate a new interactionId. Assign it to the `keydown` entry. Save the `keydown`'s key_code, interactionId, timestamps into `key_code_to_interaction_info_map_` for UKM reporting later once the interaction is fully finished..
Save the `keydown` key_code value to the `key_code_entry_map_`.
### `[11]` Key Holding
When holding a key down for an extended period. A serious of `keydown` events with the same key_code will be dispatched. We treat each individual `keydown` event as a valid interaction and assign a unique interactionId. Only the last one will be matched with `keyup`.
### `[6]` keydown
### `[12]` Keypress
A `keypress` event following a `keydown` event belongs to the same interaction as the `keydown`. We assign them the same interactionId and also save `keypress`'s timestamp into `key_code_to_interaction_info_map_` for UKM reporting later.
If key_code value is not equal to 229, then generate a new interactionId for the [`|previous_entry|`](https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:third_party/blink/renderer/core/timing/responsiveness_metrics.cc;l=329;drc=2425fac374aaa944c34b2340b8f53c9c7fc49533#:~:text=if%20(key_code_entry_map_.Contains(*key_code))%20%7B). This transition could be triggered by holding a key down for an extended period.
### `[13]` Keyup
A `keyup` event always finish up an interaction by assigning the same interactionId as `keydown`'s and report interaction duration calculated by all events belongs to the interaction to UKM. The `key_code` will be removed from `key_code_to_interaction_info_map_` after reporting, and `sequence_based_keyboard_interaction_info_` will be cleared.
### `[7]` compositionstart
The keydown event initiates the composition session. The `isComposition` parameter is set to true.
### `[14]` Contextmenu
When pressing the menu key on a keyboard, a `contextmenu` event would get dispatched right after the `keydown` event. This is a valid user interaction, but the `keyup` event could possibly be dropped due to the showing of contextmenu overlay. Thus, while we waiting for the potentially coming `keyup` event, we also setup a 1 second timer to wrap up this interaction in case `keyup` is not coming.
### `[8]` input
The input event within the composition finishes the interaction and produces the interactionId.
### `[15]` Keyup (after contextmenu)
A `keyup` after contextmenu stops the timer and wraps up the interaction immediately, which means getting the same interactionId assigned; reporting the interaction to UKM; erasing the interaction info from `key_code_to_interaction_info_map_` and clears `sequence_based_keyboard_interaction_info_`.
### `[9]` [keyup key_code = keydown key_code] keyup
### `[16]` Compositionstart
The `compositionstart` event following a `keydown` event initiates the composition session.
The transition occurs if the keyup event is fired and there is a matching key_code of a keydown event. In this transition the following steps are executed:
1. Generate a new interactionId for the keydown-keyup pair (`keydown` and `keyup`).
2. Delete the key_code of the pair from the `key_code_entry_map_`.
### `[17]` Keydown (under composition)
Some IME (Input Method Editor) could repeatedly dispatch keydowns for the same user interaction. When it happens, we will assign the same interactionId to all of them.
### `[10]` MaybeFlushKeyboardEntries
[MaybeFlushKeyboardEntries](https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:third_party/blink/renderer/core/timing/window_performance.cc;l=677;drc=66941d1f0cfe9155b400aef887fe39a403c1f518;bpv=1;bpt=1) free up `keydown` entries stuck in `key_code_entry_map_` upon the first event timing entry after 500ms. `Keydown` gets stuck in the map for reasons like [crbug/1428603](https://bugs.chromium.org/p/chromium/issues/detail?id=1428603). This flush mechanism has a known [issue](https://bugs.chromium.org/p/chromium/issues/detail?id=1420716).
### `[18]` Compositionupdate
`Compositionupdate` is the center of a keyboard user interaction under composition. Once we see it, we'll start wrapping up this interaction with matching the interactionId to the rest of events including `Input` and `keyup`.
### `[19]` Compositionstart
This is an edge case that exists for some IMEs where `keydown` events are missing. `Compositionstart` would initiate the composition session without a `keydown`.
### `[20]` Compositionupdate (keydown missing)
This is an edge case that exists for some IMEs where `keydown` events are missing. Since interactionIds are usually generated with `keydown` events, we'll fallback to generate a new interactionId on `input` to make sure we can still capture and report this interaction.
### `[21]` Input (keydown missing)
A new interactionId is generated since one wasn't generated before due to the missing `keydown`.
### `[22]` Input
Assign the current interactionId (previously generated from `keydown`) to the `input` event.
### `[23]` Keyup
Assign the current interactionId (previously generated from `keydown`) to the `keyup` event.
### `[24]` Keydown (next interaction)
Under composition session, we interpret a new keydown as the start of a new discrete user interaction on a keyboard, which also marks the end of the current interaction.
### `[25]` Compositionend
`Compositionend` marks the end of an user interaction under composition through a one second timer, in case when more than one `keyup` events are dispatched we can still group them up into the same user interaction, which could happen to some IMEs as an edge case.
### `[26]` Keyup
A `keyup` from the same user interaction could come after `compositionend`, and it will get the same interactionId assigned.
### `[27]` Keydown (next interaction) / timer
Either after 1 second by a timer, or the next `keydown` event coming in before the timer times up, we'll wrap up the current interaction and report to UKM.
- - - -
## Mermaid diagram source file
@ -77,18 +126,35 @@ date as well.
```
stateDiagram-v2
no_entry : No entry [1]
have_key_down : Have keydown entry [2]
have_composition : Composition Event [3]
interaction_finished: Interaction finished [4]
non_composition : Non Composition [1]
have_keydown : Have keydown [2]
have_keypress : Have Keypress [3]
have_contextmenu : Have Contextmenu [4]
composition_continue_ongoing_interaction : Composition Continue Ongoing Interaction [5]
composition_start_new_interaction_on_keydown : Composition Start New Interaction On Keydown [6]
composition_start_new_interaction_on_input : Composition Start New Interaction On Input [7]
end_composition_on_keydown : End Composition On Keydown [8]
interaction_finished: Interaction finished [9]
[*] --> no_entry
no_entry --> have_key_down : keydown [5]
have_key_down --> have_key_down : keydown [6]
no_entry --> have_composition: compositionstart [7]
have_composition --> interaction_finished : input [8]
have_key_down --> interaction_finished : [keyup key_code = keydown key_code] keyup [9]
have_key_down --> interaction_finished : MaybeFlushKeyboardEntries [10]
[*] --> non_composition
non_composition --> have_keydown : Keydown [10]
have_keydown --> have_keydown : Key Holding [11]
have_keydown --> have_keypress : Keypress [12]
have_keypress --> interaction_finished : Keyup [13]
have_keydown --> have_contextmenu : Contextmenu [14]
have_contextmenu --> interaction_finished : Keyup [15]
have_keydown --> composition_continue_ongoing_interaction : Compositionstart [16]
composition_continue_ongoing_interaction --> composition_continue_ongoing_interaction : Keydown [17]
composition_continue_ongoing_interaction --> composition_start_new_interaction_on_keydown : Compositionupdate [18]
non_composition --> composition_continue_ongoing_interaction : Compositionstart [19]
composition_continue_ongoing_interaction --> composition_start_new_interaction_on_input : Compositionupdate (keydown missing) [20]
composition_start_new_interaction_on_input --> composition_start_new_interaction_on_keydown : Input [21]
composition_start_new_interaction_on_keydown --> composition_start_new_interaction_on_keydown : Input [22]
composition_start_new_interaction_on_keydown --> composition_start_new_interaction_on_keydown : Keyup [23]
composition_start_new_interaction_on_keydown --> interaction_finished : Keydown (next interaction) [24]
composition_start_new_interaction_on_keydown --> end_composition_on_keydown : Compositionend [25]
end_composition_on_keydown --> end_composition_on_keydown : Keyup [26]
end_composition_on_keydown --> interaction_finished : Keydown (next interaction) / timer [27]
interaction_finished --> [*]
```

@ -48,9 +48,6 @@ constexpr uint32_t kMaxFirstInteractionID = 10000;
// 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;
// The maximum tap delay we can handle for assigning interaction id.
constexpr blink::DOMHighResTimeStamp kMaxDelayForEntries =
blink::DOMHighResTimeStamp(500);
// The length of the timer to flush entries from the time pointerup occurs.
constexpr base::TimeDelta kFlushTimerLength = base::Seconds(1);
// The name for the histogram which records interaction timings, and the names
@ -440,9 +437,7 @@ bool ResponsivenessMetrics::SetPointerIdAndRecordLatency(
}
}
if (base::FeatureList::IsEnabled(
features::kEventTimingKeypressAndCompositionInteractionId) &&
RuntimeEnabledFeaturesBase::
if (RuntimeEnabledFeaturesBase::
EventTimingHandleKeyboardEventSimulatedClickEnabled()) {
// Try handle keyboard event simulated click.
if (TryHandleKeyboardEventSimulatedClick(entry, pointer_id)) {
@ -529,112 +524,7 @@ void ResponsivenessMetrics::RecordKeyboardUKM(
// (https://chromium.googlesource.com/chromium/src/+/main/third_party/blink/renderer/core/timing/Key_interaction_state_machine.md)
// to help understand the logic below that how event timing group up keyboard
// events as interactions.
bool ResponsivenessMetrics::SetKeyIdAndRecordLatency(
PerformanceEventTiming* entry,
EventTimestamps event_timestamps) {
if (base::FeatureList::IsEnabled(
features::kEventTimingKeypressAndCompositionInteractionId)) {
return SetKeyIdAndRecordLatencyExperimental(entry, event_timestamps);
}
last_pointer_id_ = std::nullopt;
auto event_type = entry->name();
if (event_type == event_type_names::kKeydown) {
// If we were waiting for matching pointerup/keyup after a contextmenu, they
// won't show up at this point.
if (contextmenu_flush_timer_.IsActive()) {
contextmenu_flush_timer_.Stop();
FlushPointerdownAndPointerup();
FlushKeydown();
}
// During compositions, we ignore keydowns/keyups and look at input events.
if (composition_started_) {
return true;
}
DCHECK(entry->GetEventTimingReportingInfo()->key_code.has_value());
auto key_code = entry->GetEventTimingReportingInfo()->key_code.value();
if (key_code_entry_map_.Contains(key_code)) {
auto* previous_entry = key_code_entry_map_.at(key_code);
// Ignore repeat IME keydowns. See
// https://w3c.github.io/uievents/#determine-keydown-keyup-keyCode.
// Reasoning: we cannot ignore all IME keydowns because on Android in
// some languages the events received are 'keydown', 'input', 'keyup',
// and since we are not composing then the 'input' event is ignored, so
// we must consider the key events with 229 keyCode as the user
// interaction. Besides this, we cannot consider repeat 229 keydowns
// because we may get those on ChromeOS when we should ignore them. This
// may be related to crbug.com/1252856.
if (key_code != 229) {
// Generate a new interaction id for |previous_entry|. This case could
// be caused by keeping a key pressed for a while.
UpdateInteractionId();
previous_entry->GetEntry()->SetInteractionIdAndOffset(
GetCurrentInteractionId(), GetInteractionCount());
RecordKeyboardUKM(window_performance_->DomWindow(),
{previous_entry->GetTimeStamps()},
previous_entry->GetEntry()->interactionOffset());
}
window_performance_->NotifyAndAddEventTimingBuffer(
previous_entry->GetEntry());
}
key_code_entry_map_.Set(
key_code, KeyboardEntryAndTimestamps::Create(entry, event_timestamps));
// Similar to pointerdown, we need to wait a bit before knowing the
// interactionId of keydowns.
return false;
} else if (event_type == event_type_names::kKeyup) {
if (contextmenu_flush_timer_.IsActive()) {
contextmenu_flush_timer_.Stop();
}
DCHECK(entry->GetEventTimingReportingInfo()->key_code.has_value());
auto key_code = entry->GetEventTimingReportingInfo()->key_code.value();
if (composition_started_ || !key_code_entry_map_.Contains(key_code)) {
return true;
}
auto* previous_entry = key_code_entry_map_.at(key_code);
// Generate a new interaction id for the keydown-keyup pair.
UpdateInteractionId();
previous_entry->GetEntry()->SetInteractionIdAndOffset(
GetCurrentInteractionId(), GetInteractionCount());
window_performance_->NotifyAndAddEventTimingBuffer(
previous_entry->GetEntry());
if (previous_entry->GetEntry()->HasKnownInteractionID()) {
entry->SetInteractionIdAndOffset(
previous_entry->GetEntry()->interactionId(),
previous_entry->GetEntry()->interactionOffset());
}
RecordKeyboardUKM(window_performance_->DomWindow(),
{previous_entry->GetTimeStamps(), event_timestamps},
entry->interactionOffset());
key_code_entry_map_.erase(key_code);
} else if (event_type == event_type_names::kCompositionstart) {
composition_started_ = true;
for (auto key_entry : key_code_entry_map_.Values()) {
window_performance_->NotifyAndAddEventTimingBuffer(key_entry->GetEntry());
}
key_code_entry_map_.clear();
} else if (event_type == event_type_names::kCompositionend) {
composition_started_ = false;
} else if (event_type == event_type_names::kInput) {
if (!composition_started_) {
return true;
}
// We are in the case of a text input event while compositing with
// non-trivial data, so we want to increase interactionId.
// TODO(crbug.com/1252856): fix counts in ChromeOS due to duplicate
// events.
UpdateInteractionId();
entry->SetInteractionIdAndOffset(GetCurrentInteractionId(),
GetInteractionCount());
RecordKeyboardUKM(window_performance_->DomWindow(), {event_timestamps},
entry->interactionOffset());
}
return true;
}
bool ResponsivenessMetrics::SetKeyIdAndRecordLatencyExperimental(
void ResponsivenessMetrics::SetKeyIdAndRecordLatency(
PerformanceEventTiming* entry,
EventTimestamps event_timestamps) {
last_pointer_id_ = std::nullopt;
@ -685,7 +575,7 @@ bool ResponsivenessMetrics::SetKeyIdAndRecordLatencyExperimental(
CHECK(entry->GetEventTimingReportingInfo()->key_code.has_value());
auto key_code = entry->GetEventTimingReportingInfo()->key_code.value();
if (!key_code_to_interaction_info_map_.Contains(key_code)) {
return true;
return;
}
// Match the keydown entry with the keyup entry using keycode.
@ -734,7 +624,7 @@ bool ResponsivenessMetrics::SetKeyIdAndRecordLatencyExperimental(
} else if (event_type == event_type_names::kInput) {
// Expose interactionId for Input events only under composition
if (composition_state_ == kNonComposition) {
return true;
return;
}
// Update Interaction Id when input is selected using IME suggestion without
// pressing a key. In this case Input event starts and finishes interaction
@ -759,44 +649,18 @@ bool ResponsivenessMetrics::SetKeyIdAndRecordLatencyExperimental(
}
last_keydown_keycode_info_.reset();
}
return true;
}
void ResponsivenessMetrics::FlushExpiredKeydown(
DOMHighResTimeStamp current_time) {
if (base::FeatureList::IsEnabled(
features::kEventTimingKeypressAndCompositionInteractionId)) {
// Do nothing. Experimenting not cleaning up keydown/keypress that was not
// successfully paired with a keyup thus get stuck in the map.
} else {
// We cannot delete from a HashMap while iterating.
Vector<int> key_codes_to_remove;
for (const auto& entry : key_code_entry_map_) {
PerformanceEventTiming* key_down = entry.value->GetEntry();
if (current_time - key_down->processingEnd() > kMaxDelayForEntries) {
window_performance_->NotifyAndAddEventTimingBuffer(key_down);
key_codes_to_remove.push_back(entry.key);
}
}
key_code_entry_map_.RemoveAll(key_codes_to_remove);
}
}
void ResponsivenessMetrics::FlushKeydown() {
for (const auto& entry : key_code_entry_map_) {
PerformanceEventTiming* key_down = entry.value->GetEntry();
for (auto& entry : key_code_to_interaction_info_map_) {
// Keydowns triggered contextmenu, though missing pairing keyups due to a
// known issue - https://github.com/w3c/pointerevents/issues/408, should
// still be counted as a valid interaction and get a valid id assigned.
UpdateInteractionId();
key_down->SetInteractionIdAndOffset(GetCurrentInteractionId(),
GetInteractionCount());
window_performance_->NotifyAndAddEventTimingBuffer(key_down);
// still be counted as a valid interaction and get reported to UKM.
RecordKeyboardUKM(window_performance_->DomWindow(),
{entry.value->GetTimeStamps()},
key_down->interactionOffset());
entry.value.GetTimeStamps(),
entry.value.GetInteractionOffset());
}
key_code_entry_map_.clear();
key_code_to_interaction_info_map_.clear();
}
void ResponsivenessMetrics::FlushAllEventsAtPageHidden() {
@ -958,7 +822,6 @@ void ResponsivenessMetrics::Trace(Visitor* visitor) const {
visitor->Trace(pointer_id_entry_map_);
visitor->Trace(pointer_flush_timer_);
visitor->Trace(contextmenu_flush_timer_);
visitor->Trace(key_code_entry_map_);
visitor->Trace(composition_end_flush_timer_);
}

@ -43,8 +43,7 @@ class CORE_EXPORT ResponsivenessMetrics
};
// Wrapper class to store interactionId, interaction offset, and timestamps
// of an entry on a HashMap. It is optimized and used only in the experimental
// SetKeyIdAndRecordLatency function. (SetKeyIdAndRecordLatencyExperimental)
// of an entry on a HashMap.
class InteractionInfo {
public:
InteractionInfo(uint32_t interaction_id,
@ -167,21 +166,12 @@ class CORE_EXPORT ResponsivenessMetrics
// Assigns interactionId and records interaction latency for keyboard events.
// We care about input, compositionstart, and compositionend events, so
// |key_code| will be std::nullopt in those cases. Returns true if the entry
// would be ready to be surfaced in PerformanceObservers and the Performance
// Timeline.
bool SetKeyIdAndRecordLatency(PerformanceEventTiming* entry,
// |key_code| will be std::nullopt in those cases.
void SetKeyIdAndRecordLatency(PerformanceEventTiming* entry,
EventTimestamps event_timestamps);
// Experimental function that in addition to SetKeyIdAndRecordLatency()
// exposes interactionId for keypress and keyup/keydown under composition.
bool SetKeyIdAndRecordLatencyExperimental(PerformanceEventTiming* entry,
EventTimestamps event_timestamps);
// Clear keydowns in |key_codes_to_remove| if we have stored them for a while.
void FlushExpiredKeydown(DOMHighResTimeStamp end_time);
// Clears all keydowns in |key_codes_to_remove| no matter how long we have
// stored them.
// Clears all keydowns in |key_code_to_interaction_info_map_| and report to
// UKM.
void FlushKeydown();
uint32_t GetInteractionCount() const;
@ -266,12 +256,6 @@ class CORE_EXPORT ResponsivenessMetrics
HashMap<int, InteractionInfo, IntWithZeroKeyHashTraits<int>>
key_code_to_interaction_info_map_;
// Map from keyCodes to keydown entries and keydown timestamps.
HeapHashMap<int,
Member<KeyboardEntryAndTimestamps>,
IntWithZeroKeyHashTraits<int>>
key_code_entry_map_;
// Whether we are composing or not. When we are not composing, we set
// interactionId for keydown and keyup events. When we are composing, we set
// interactionId for input events.

@ -627,11 +627,6 @@ void WindowPerformance::OnPresentationPromiseResolved(
pending_event_presentation_time_map_.Set(presentation_index,
presentation_timestamp);
ReportEventTimings();
// Use |end_time| as a proxy for the current time to flush expired keydowns.
DOMHighResTimeStamp end_time =
MonotonicTimeToDOMHighResTimeStamp(presentation_timestamp);
responsiveness_metrics_->FlushExpiredKeydown(end_time);
}
void WindowPerformance::FlushEventTimingsOnPageHidden() {
@ -941,10 +936,7 @@ void WindowPerformance::SetFallbackTime(PerformanceEventTiming* entry) {
if (!show_modal_dialog_timestamps_.empty() &&
show_modal_dialog_timestamps_.front() <
entry->GetEventTimingReportingInfo()->presentation_time) {
if (base::FeatureList::IsEnabled(
features::kEventTimingFallbackToModalDialogStart)) {
fallback_end_time_to_dialog_time = true;
}
fallback_end_time_to_dialog_time = true;
first_modal_dialog_timestamp = show_modal_dialog_timestamps_.front();
}
@ -982,8 +974,8 @@ bool WindowPerformance::SetInteractionIdAndRecordLatency(
return responsiveness_metrics_->SetPointerIdAndRecordLatency(
entry, event_timestamps);
}
return responsiveness_metrics_->SetKeyIdAndRecordLatency(entry,
event_timestamps);
responsiveness_metrics_->SetKeyIdAndRecordLatency(entry, event_timestamps);
return true;
}
void WindowPerformance::ReportLongAnimationFrameTiming(

@ -13,7 +13,6 @@
#include "base/numerics/safe_conversions.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_mock_time_task_runner.h"
#include "base/test/trace_event_analyzer.h"
#include "base/time/time.h"
@ -67,14 +66,9 @@ base::TimeTicks GetTimeStamp(int64_t time) {
} // namespace
class WindowPerformanceTest : public testing::Test,
public ::testing::WithParamInterface<bool> {
class WindowPerformanceTest : public testing::Test {
protected:
void SetUp() override {
if (GetParam()) {
features_.InitAndEnableFeature(
blink::features::kEventTimingKeypressAndCompositionInteractionId);
}
test_task_runner_ = base::MakeRefCounted<base::TestMockTimeTaskRunner>();
ResetPerformance();
}
@ -220,10 +214,9 @@ class WindowPerformanceTest : public testing::Test,
scoped_refptr<base::TestMockTimeTaskRunner> test_task_runner_;
ScopedFakeUkmRecorder scoped_fake_ukm_recorder_;
base::HistogramTester histogram_tester_;
base::test::ScopedFeatureList features_;
};
TEST_P(WindowPerformanceTest, SanitizedLongTaskName) {
TEST_F(WindowPerformanceTest, SanitizedLongTaskName) {
// Unable to attribute, when no execution contents are available.
EXPECT_EQ("unknown", SanitizedAttribution(nullptr, false, GetFrame()));
@ -235,7 +228,7 @@ TEST_P(WindowPerformanceTest, SanitizedLongTaskName) {
SanitizedAttribution(GetWindow(), true, GetFrame()));
}
TEST_P(WindowPerformanceTest, SanitizedLongTaskName_CrossOrigin) {
TEST_F(WindowPerformanceTest, SanitizedLongTaskName_CrossOrigin) {
// Create another dummy page holder and pretend it is an iframe.
DummyPageHolder another_page(gfx::Size(400, 300));
another_page.GetDocument().SetURL(KURL("https://iframed.com/bar"));
@ -252,7 +245,7 @@ TEST_P(WindowPerformanceTest, SanitizedLongTaskName_CrossOrigin) {
// https://crbug.com/706798: Checks that after navigation that have replaced the
// window object, calls to not garbage collected yet WindowPerformance belonging
// to the old window do not cause a crash.
TEST_P(WindowPerformanceTest, NavigateAway) {
TEST_F(WindowPerformanceTest, NavigateAway) {
AddLongTaskObserver();
// Simulate navigation commit.
@ -307,7 +300,7 @@ TEST(PerformanceLifetimeTest, SurviveContextSwitch) {
// Make sure the output entries with the same timestamps follow the insertion
// order. (http://crbug.com/767560)
TEST_P(WindowPerformanceTest, EnsureEntryListOrder) {
TEST_F(WindowPerformanceTest, EnsureEntryListOrder) {
// Need to have an active V8 context for ScriptValues to operate.
v8::HandleScope handle_scope(GetScriptState()->GetIsolate());
v8::Local<v8::Context> context = GetScriptState()->GetContext();
@ -340,7 +333,7 @@ TEST_P(WindowPerformanceTest, EnsureEntryListOrder) {
}
}
TEST_P(WindowPerformanceTest, EventTimingEntryBuffering) {
TEST_F(WindowPerformanceTest, EventTimingEntryBuffering) {
EXPECT_TRUE(page_holder_->GetFrame().Loader().GetDocumentLoader());
base::TimeTicks start_time = GetTimeOrigin() + base::Seconds(1.1);
@ -377,7 +370,7 @@ TEST_P(WindowPerformanceTest, EventTimingEntryBuffering) {
.size());
}
TEST_P(WindowPerformanceTest, Expose100MsEvents) {
TEST_F(WindowPerformanceTest, Expose100MsEvents) {
base::TimeTicks start_time = GetTimeOrigin() + base::Seconds(1);
base::TimeTicks processing_start = start_time + base::Milliseconds(10);
base::TimeTicks processing_end = processing_start + base::Milliseconds(10);
@ -399,7 +392,7 @@ TEST_P(WindowPerformanceTest, Expose100MsEvents) {
EXPECT_EQ(event_type_names::kMousedown, entries.at(0)->name());
}
TEST_P(WindowPerformanceTest, EventTimingDuration) {
TEST_F(WindowPerformanceTest, EventTimingDuration) {
base::TimeTicks start_time = GetTimeOrigin() + base::Milliseconds(1000);
base::TimeTicks processing_start = GetTimeOrigin() + base::Milliseconds(1001);
base::TimeTicks processing_end = GetTimeOrigin() + base::Milliseconds(1002);
@ -434,7 +427,7 @@ TEST_P(WindowPerformanceTest, EventTimingDuration) {
// Test the case where multiple events are registered and then their
// presentation promise is resolved.
TEST_P(WindowPerformanceTest, MultipleEventsThenPresent) {
TEST_F(WindowPerformanceTest, MultipleEventsThenPresent) {
size_t num_events = 10;
for (size_t i = 0; i < num_events; ++i) {
base::TimeTicks start_time = GetTimeOrigin() + base::Seconds(i);
@ -458,7 +451,7 @@ TEST_P(WindowPerformanceTest, MultipleEventsThenPresent) {
// Test the case where commit finish timestamps are recorded on all pending
// EventTimings.
TEST_P(WindowPerformanceTest,
TEST_F(WindowPerformanceTest,
CommitFinishTimeRecordedOnAllPendingEventTimings) {
size_t num_events = 3;
for (size_t i = 0; i < num_events; ++i) {
@ -484,7 +477,7 @@ TEST_P(WindowPerformanceTest,
// Test the case where a new commit finish timestamps does not affect previous
// EventTiming who has already seen a commit finish.
TEST_P(WindowPerformanceTest, NewCommitNotOverwritePreviousEventTimings) {
TEST_F(WindowPerformanceTest, NewCommitNotOverwritePreviousEventTimings) {
base::TimeTicks start_time = GetTimeOrigin() + base::Seconds(1);
base::TimeTicks processing_start = start_time + base::Milliseconds(100);
base::TimeTicks processing_end = start_time + base::Milliseconds(200);
@ -512,7 +505,7 @@ TEST_P(WindowPerformanceTest, NewCommitNotOverwritePreviousEventTimings) {
}
// Test for existence of 'first-input' given different types of first events.
TEST_P(WindowPerformanceTest, FirstInput) {
TEST_F(WindowPerformanceTest, FirstInput) {
struct {
AtomicString event_type;
bool should_report;
@ -546,7 +539,7 @@ TEST_P(WindowPerformanceTest, FirstInput) {
// Test that the 'first-input' is populated after some irrelevant events are
// ignored.
TEST_P(WindowPerformanceTest, FirstInputAfterIgnored) {
TEST_F(WindowPerformanceTest, FirstInputAfterIgnored) {
AtomicString several_events[] = {event_type_names::kMouseover,
event_type_names::kMousedown,
event_type_names::kPointerup};
@ -567,7 +560,7 @@ TEST_P(WindowPerformanceTest, FirstInputAfterIgnored) {
}
// Test that pointerdown followed by pointerup works as a 'firstInput'.
TEST_P(WindowPerformanceTest, FirstPointerUp) {
TEST_F(WindowPerformanceTest, FirstPointerUp) {
base::TimeTicks start_time = GetTimeStamp(0);
base::TimeTicks processing_start = GetTimeStamp(1);
base::TimeTicks processing_end = GetTimeStamp(2);
@ -593,7 +586,7 @@ TEST_P(WindowPerformanceTest, FirstPointerUp) {
// When the pointerdown is optimized out, the mousedown works as a
// 'first-input'.
TEST_P(WindowPerformanceTest, PointerdownOptimizedOut) {
TEST_F(WindowPerformanceTest, PointerdownOptimizedOut) {
base::TimeTicks start_time = GetTimeStamp(0);
base::TimeTicks processing_start = GetTimeStamp(1);
base::TimeTicks processing_end = GetTimeStamp(2);
@ -613,7 +606,7 @@ TEST_P(WindowPerformanceTest, PointerdownOptimizedOut) {
// Test that pointerdown followed by mousedown, pointerup works as a
// 'first-input'.
TEST_P(WindowPerformanceTest, PointerdownOnDesktop) {
TEST_F(WindowPerformanceTest, PointerdownOnDesktop) {
base::TimeTicks start_time = GetTimeStamp(0);
base::TimeTicks processing_start = GetTimeStamp(1);
base::TimeTicks processing_end = GetTimeStamp(2);
@ -643,7 +636,7 @@ TEST_P(WindowPerformanceTest, PointerdownOnDesktop) {
.size());
}
TEST_P(WindowPerformanceTest, OneKeyboardInteraction) {
TEST_F(WindowPerformanceTest, OneKeyboardInteraction) {
base::TimeTicks keydown_timestamp = GetTimeStamp(0);
// Keydown
base::TimeTicks processing_start_keydown = GetTimeStamp(1);
@ -693,7 +686,7 @@ TEST_P(WindowPerformanceTest, OneKeyboardInteraction) {
"Blink.Responsiveness.UserInteraction.MaxEventDuration.Drag", 0);
}
TEST_P(WindowPerformanceTest, HoldingDownAKey) {
TEST_F(WindowPerformanceTest, HoldingDownAKey) {
auto entries = GetUkmRecorder()->GetEntriesByName(
ukm::builders::Responsiveness_UserInteraction::kEntryName);
EXPECT_EQ(0u, entries.size());
@ -773,7 +766,7 @@ TEST_P(WindowPerformanceTest, HoldingDownAKey) {
"Blink.Responsiveness.UserInteraction.MaxEventDuration.Drag", 0);
}
TEST_P(WindowPerformanceTest, PressMultipleKeys) {
TEST_F(WindowPerformanceTest, PressMultipleKeys) {
auto entries = GetUkmRecorder()->GetEntriesByName(
ukm::builders::Responsiveness_UserInteraction::kEntryName);
EXPECT_EQ(0u, entries.size());
@ -847,7 +840,7 @@ TEST_P(WindowPerformanceTest, PressMultipleKeys) {
// Test a real world scenario, where keydown got presented first but its
// callback got invoked later than keyup's due to multi processes & threading
// overhead.
TEST_P(WindowPerformanceTest, KeyupFinishLastButCallbackInvokedFirst) {
TEST_F(WindowPerformanceTest, KeyupFinishLastButCallbackInvokedFirst) {
// Arbitrary keycode picked for testing from
// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode#value_of_keycode
int digit_1_key_code = 0x31;
@ -908,7 +901,7 @@ TEST_P(WindowPerformanceTest, KeyupFinishLastButCallbackInvokedFirst) {
"Blink.Responsiveness.UserInteraction.MaxEventDuration.Drag", 0);
}
TEST_P(WindowPerformanceTest, TapOrClick) {
TEST_F(WindowPerformanceTest, TapOrClick) {
// Pointerdown
base::TimeTicks pointerdown_timestamp = GetTimeOrigin();
base::TimeTicks processing_start_pointerdown = GetTimeStamp(1);
@ -968,7 +961,7 @@ TEST_P(WindowPerformanceTest, TapOrClick) {
"Blink.Responsiveness.UserInteraction.MaxEventDuration.Drag", 0);
}
TEST_P(WindowPerformanceTest, PageVisibilityChanged) {
TEST_F(WindowPerformanceTest, PageVisibilityChanged) {
// Pointerdown
base::TimeTicks pointerdown_timestamp = GetTimeOrigin();
base::TimeTicks processing_start_pointerdown = GetTimeStamp(1);
@ -1033,7 +1026,7 @@ TEST_P(WindowPerformanceTest, PageVisibilityChanged) {
EXPECT_EQ(1ul, performance_->interactionCount());
}
TEST_P(WindowPerformanceTest, Drag) {
TEST_F(WindowPerformanceTest, Drag) {
// Pointerdown
base::TimeTicks pointerdwon_timestamp = GetTimeOrigin();
base::TimeTicks processing_start_pointerdown = GetTimeStamp(1);
@ -1095,7 +1088,7 @@ TEST_P(WindowPerformanceTest, Drag) {
"Blink.Responsiveness.UserInteraction.MaxEventDuration.Drag", 1);
}
TEST_P(WindowPerformanceTest, Scroll) {
TEST_F(WindowPerformanceTest, Scroll) {
// Pointerdown
base::TimeTicks pointerdown_timestamp = GetTimeOrigin();
base::TimeTicks processing_start_keydown = GetTimeStamp(1);
@ -1135,7 +1128,7 @@ TEST_P(WindowPerformanceTest, Scroll) {
"Blink.Responsiveness.UserInteraction.MaxEventDuration.Drag", 0);
}
TEST_P(WindowPerformanceTest, TouchesWithoutClick) {
TEST_F(WindowPerformanceTest, TouchesWithoutClick) {
base::TimeTicks pointerdown_timestamp = GetTimeOrigin();
// First Pointerdown
base::TimeTicks processing_start_pointerdown = GetTimeStamp(1);
@ -1170,7 +1163,7 @@ TEST_P(WindowPerformanceTest, TouchesWithoutClick) {
// Test artificial pointerup and click on MacOS fall back to use processingEnd
// as event duration ending time.
// See crbug.com/1321819
TEST_P(WindowPerformanceTest, ArtificialPointerupOrClick) {
TEST_F(WindowPerformanceTest, ArtificialPointerupOrClick) {
// Arbitrary pointerId picked for testing
PointerId pointer_id = 4;
@ -1235,7 +1228,7 @@ TEST_P(WindowPerformanceTest, ArtificialPointerupOrClick) {
// The trace_analyzer does not work on platforms on which the migration of
// tracing into Perfetto has not completed.
TEST_P(WindowPerformanceTest, PerformanceMarkTraceEvent) {
TEST_F(WindowPerformanceTest, PerformanceMarkTraceEvent) {
v8::HandleScope handle_scope(GetScriptState()->GetIsolate());
v8::Local<v8::Context> context = GetScriptState()->GetContext();
v8::Context::Scope context_scope(context);
@ -1270,7 +1263,7 @@ TEST_P(WindowPerformanceTest, PerformanceMarkTraceEvent) {
ASSERT_TRUE(navigation_id);
}
TEST_P(WindowPerformanceTest, ElementTimingTraceEvent) {
TEST_F(WindowPerformanceTest, ElementTimingTraceEvent) {
using trace_analyzer::Query;
trace_analyzer::Start("*");
// |element| needs to be non-null to prevent a crash.
@ -1311,7 +1304,7 @@ TEST_P(WindowPerformanceTest, ElementTimingTraceEvent) {
EXPECT_EQ(*url, "url");
}
TEST_P(WindowPerformanceTest, EventTimingTraceEvents) {
TEST_F(WindowPerformanceTest, EventTimingTraceEvents) {
using trace_analyzer::Query;
trace_analyzer::Start("*");
base::TimeTicks start_time = GetTimeOrigin() + base::Seconds(1);
@ -1410,7 +1403,7 @@ TEST_P(WindowPerformanceTest, EventTimingTraceEvents) {
EXPECT_FALSE(click_begin->other_event->HasDictArg("data"));
}
TEST_P(WindowPerformanceTest, SlowInteractionToNextPaintTraceEvents) {
TEST_F(WindowPerformanceTest, SlowInteractionToNextPaintTraceEvents) {
using trace_analyzer::Query;
trace_analyzer::Start("*");
@ -1528,7 +1521,7 @@ TEST_P(WindowPerformanceTest, SlowInteractionToNextPaintTraceEvents) {
EXPECT_EQ(base::ClampRound(events[2]->GetAbsTimeToOtherEvent()), 600000);
}
TEST_P(WindowPerformanceTest, InteractionID) {
TEST_F(WindowPerformanceTest, InteractionID) {
// Keyboard with max duration 25, total duration 40.
PerformanceEventTiming* keydown_entry =
CreatePerformanceEventTiming(event_type_names::kKeydown, 1, std::nullopt,
@ -1618,8 +1611,6 @@ TEST_P(WindowPerformanceTest, InteractionID) {
}
}
INSTANTIATE_TEST_SUITE_P(All, WindowPerformanceTest, ::testing::Bool());
class InteractionIdTest : public WindowPerformanceTest {
public:
struct EventForInteraction {
@ -1693,7 +1684,7 @@ class InteractionIdTest : public WindowPerformanceTest {
};
// Tests English typing.
TEST_P(InteractionIdTest, InputOutsideComposition) {
TEST_F(InteractionIdTest, InputOutsideComposition) {
// Insert "a" with a max duration of 50 and total of 50.
std::vector<EventForInteraction> events1 = {
{event_type_names::kKeydown, 65, std::nullopt, GetTimeStamp(100),
@ -1745,7 +1736,7 @@ TEST_P(InteractionIdTest, InputOutsideComposition) {
}
// Tests Japanese on Mac.
TEST_P(InteractionIdTest, CompositionSingleKeydown) {
TEST_F(InteractionIdTest, CompositionSingleKeydown) {
// Insert "a" with a duration of 20.
std::vector<EventForInteraction> events1 = {
{event_type_names::kKeydown, 229, std::nullopt, GetTimeStamp(100),
@ -1770,49 +1761,31 @@ TEST_P(InteractionIdTest, CompositionSingleKeydown) {
{event_type_names::kCompositionend, std::nullopt, std::nullopt}};
std::vector<uint32_t> ids2 = SimulateInteractionIds(events2);
if (base::FeatureList::IsEnabled(
features::kEventTimingKeypressAndCompositionInteractionId)) {
performance_->GetResponsivenessMetrics().FlushAllEventsForTesting();
performance_->GetResponsivenessMetrics().FlushAllEventsForTesting();
EXPECT_GT(ids1[0], 0u) << "Keydown interactionId was nonzero";
EXPECT_EQ(ids1[1], 0u) << "Compositionstart interactionId was zero";
EXPECT_GT(ids1[3], 0u) << "Input interactionId was nonzero";
EXPECT_GT(ids1[4], 0u) << "Keyup interactionId was nonzero";
EXPECT_EQ(ids1[0], ids1[3])
<< "Keydown and Input have the same interactionIds";
EXPECT_GT(ids1[0], 0u) << "Keydown interactionId was nonzero";
EXPECT_EQ(ids1[1], 0u) << "Compositionstart interactionId was zero";
EXPECT_GT(ids1[3], 0u) << "Input interactionId was nonzero";
EXPECT_GT(ids1[4], 0u) << "Keyup interactionId was nonzero";
EXPECT_EQ(ids1[0], ids1[3])
<< "Keydown and Input have the same interactionIds";
EXPECT_GT(ids2[0], 0u) << "Second keydown interactionId was nonzero";
EXPECT_GT(ids2[2], 0u) << "Second input interactionId was nonzero";
EXPECT_GT(ids2[3], 0u) << "Second keyup interactionId was non zero";
EXPECT_EQ(ids2[4], 0u) << "Compositionend interactionId was zero";
EXPECT_EQ(ids2[0], ids2[2])
<< "Keydown and Input have the same interactionIds";
EXPECT_NE(ids1[3], ids2[2])
<< "First and second inputs have different interactionIds";
EXPECT_GT(ids2[0], 0u) << "Second keydown interactionId was nonzero";
EXPECT_GT(ids2[2], 0u) << "Second input interactionId was nonzero";
EXPECT_GT(ids2[3], 0u) << "Second keyup interactionId was non zero";
EXPECT_EQ(ids2[4], 0u) << "Compositionend interactionId was zero";
EXPECT_EQ(ids2[0], ids2[2])
<< "Keydown and Input have the same interactionIds";
EXPECT_NE(ids1[3], ids2[2])
<< "First and second inputs have different interactionIds";
CheckUKMValues({{100, 120, UserInteractionType::kKeyboard},
{100, 170, UserInteractionType::kKeyboard}});
} else {
EXPECT_EQ(ids1[0], 0u) << "Keydown interactionId was zero";
EXPECT_EQ(ids1[1], 0u) << "Compositionstart interactionId was zero";
EXPECT_GT(ids1[3], 0u) << "Input interactionId was nonzero";
EXPECT_EQ(ids1[4], 0u) << "Keyup interactionId was zero";
EXPECT_EQ(ids2[0], 0u) << "Second keydown interactionId was zero";
EXPECT_GT(ids2[2], 0u) << "Second input interactionId was nonzero";
EXPECT_EQ(ids2[3], 0u) << "Second keyup interactionId was zero";
EXPECT_EQ(ids2[4], 0u) << "Compositionend interactionId was zero";
EXPECT_NE(ids1[3], ids2[2])
<< "First and second inputs have different interactionIds";
CheckUKMValues({{20, 20, UserInteractionType::kKeyboard},
{30, 30, UserInteractionType::kKeyboard}});
}
CheckUKMValues({{100, 120, UserInteractionType::kKeyboard},
{100, 170, UserInteractionType::kKeyboard}});
}
// Tests Chinese on Mac. Windows is similar, but has more keyups inside the
// composition.
TEST_P(InteractionIdTest, CompositionToFinalInput) {
TEST_F(InteractionIdTest, CompositionToFinalInput) {
// Insert "a" with a duration of 25.
std::vector<EventForInteraction> events1 = {
{event_type_names::kKeydown, 229, std::nullopt, GetTimeStamp(100),
@ -1854,22 +1827,15 @@ TEST_P(InteractionIdTest, CompositionToFinalInput) {
EXPECT_NE(ids2[2], ids3[1])
<< "Second and third inputs have different interactionIds";
if (base::FeatureList::IsEnabled(
features::kEventTimingKeypressAndCompositionInteractionId)) {
performance_->GetResponsivenessMetrics().FlushAllEventsForTesting();
performance_->GetResponsivenessMetrics().FlushAllEventsForTesting();
CheckUKMValues({{90, 90, UserInteractionType::kKeyboard},
{90, 90, UserInteractionType::kKeyboard},
{140, 140, UserInteractionType::kKeyboard}});
} else {
CheckUKMValues({{25, 25, UserInteractionType::kKeyboard},
{35, 35, UserInteractionType::kKeyboard},
{140, 140, UserInteractionType::kKeyboard}});
}
CheckUKMValues({{90, 90, UserInteractionType::kKeyboard},
{90, 90, UserInteractionType::kKeyboard},
{140, 140, UserInteractionType::kKeyboard}});
}
// Tests Chinese on Windows.
TEST_P(InteractionIdTest, CompositionToFinalInputMultipleKeyUps) {
TEST_F(InteractionIdTest, CompositionToFinalInputMultipleKeyUps) {
// Insert "a" with a duration of 66.
std::vector<EventForInteraction> events1 = {
{event_type_names::kKeydown, 229, std::nullopt, GetTimeStamp(0),
@ -1905,52 +1871,29 @@ TEST_P(InteractionIdTest, CompositionToFinalInputMultipleKeyUps) {
{event_type_names::kCompositionend, std::nullopt, std::nullopt}};
std::vector<uint32_t> ids3 = SimulateInteractionIds(events3);
if (base::FeatureList::IsEnabled(
features::kEventTimingKeypressAndCompositionInteractionId)) {
performance_->GetResponsivenessMetrics().FlushAllEventsForTesting();
EXPECT_GT(ids1[3], 0u) << "First input nonzero";
EXPECT_GT(ids1[4], 0u) << "First keyup has nonzero interactionId";
EXPECT_GT(ids1[5], 0u) << "Second keyup has nonzero interactionId";
performance_->GetResponsivenessMetrics().FlushAllEventsForTesting();
EXPECT_GT(ids1[3], 0u) << "First input nonzero";
EXPECT_GT(ids1[4], 0u) << "First keyup has nonzero interactionId";
EXPECT_GT(ids1[5], 0u) << "Second keyup has nonzero interactionId";
EXPECT_GT(ids2[2], 0u) << "Second input nonzero";
EXPECT_NE(ids1[3], ids2[2])
<< "First and second input have different interactionIds";
EXPECT_GT(ids2[3], 0u) << "Third keyup has nonzero interactionId";
EXPECT_GT(ids2[4], 0u) << "Fourth keyup has nonzero interactionId";
EXPECT_GT(ids2[2], 0u) << "Second input nonzero";
EXPECT_NE(ids1[3], ids2[2])
<< "First and second input have different interactionIds";
EXPECT_GT(ids2[3], 0u) << "Third keyup has nonzero interactionId";
EXPECT_GT(ids2[4], 0u) << "Fourth keyup has nonzero interactionId";
EXPECT_GT(ids3[1], 0u) << "Third input has nonzero interactionId";
EXPECT_NE(ids1[3], ids3[1])
<< "First and third inputs have different interactionIds";
EXPECT_NE(ids2[2], ids3[1])
<< "Second and third inputs have different interactionIds";
CheckUKMValues({{100, 100, UserInteractionType::kKeyboard},
{100, 100, UserInteractionType::kKeyboard},
{85, 85, UserInteractionType::kKeyboard}});
} else {
EXPECT_GT(ids1[3], 0u) << "First input nonzero";
EXPECT_EQ(ids1[4], 0u) << "First keyup has zero interactionId";
EXPECT_EQ(ids1[5], 0u) << "Second keyup has zero interactionId";
EXPECT_GT(ids2[2], 0u) << "Second input nonzero";
EXPECT_NE(ids1[3], ids2[2])
<< "First and second input have different interactionIds";
EXPECT_EQ(ids2[3], 0u) << "Third keyup has zero interactionId";
EXPECT_EQ(ids2[4], 0u) << "Fourth keyup has zero interactionId";
EXPECT_GT(ids3[1], 0u) << "Third input has nonzero interactionId";
EXPECT_NE(ids1[3], ids3[1])
<< "First and third inputs have different interactionIds";
EXPECT_NE(ids2[2], ids3[1])
<< "Second and third inputs have different interactionIds";
CheckUKMValues({{66, 66, UserInteractionType::kKeyboard},
{51, 51, UserInteractionType::kKeyboard},
{85, 85, UserInteractionType::kKeyboard}});
}
EXPECT_GT(ids3[1], 0u) << "Third input has nonzero interactionId";
EXPECT_NE(ids1[3], ids3[1])
<< "First and third inputs have different interactionIds";
EXPECT_NE(ids2[2], ids3[1])
<< "Second and third inputs have different interactionIds";
CheckUKMValues({{100, 100, UserInteractionType::kKeyboard},
{100, 100, UserInteractionType::kKeyboard},
{85, 85, UserInteractionType::kKeyboard}});
}
// Tests Android smart suggestions (similar to Android Chinese).
TEST_P(InteractionIdTest, SmartSuggestion) {
TEST_F(InteractionIdTest, SmartSuggestion) {
// Insert "A" with a duration of 9.
std::vector<EventForInteraction> events1 = {
{event_type_names::kKeydown, 229, std::nullopt, GetTimeStamp(0),
@ -1982,42 +1925,24 @@ TEST_P(InteractionIdTest, SmartSuggestion) {
GetTimeStamp(270)}};
std::vector<uint32_t> ids3 = SimulateInteractionIds(events3);
if (base::FeatureList::IsEnabled(
features::kEventTimingKeypressAndCompositionInteractionId)) {
performance_->GetResponsivenessMetrics().FlushAllEventsForTesting();
EXPECT_GT(ids1[3], 0u) << "First input nonzero";
EXPECT_EQ(ids1[0], ids1[3]) << "Keydown and input have the same id";
EXPECT_EQ(ids1[0], ids1[3]) << "Keydown and keyup have the same id";
performance_->GetResponsivenessMetrics().FlushAllEventsForTesting();
EXPECT_GT(ids1[3], 0u) << "First input nonzero";
EXPECT_EQ(ids1[0], ids1[3]) << "Keydown and input have the same id";
EXPECT_EQ(ids1[0], ids1[3]) << "Keydown and keyup have the same id";
EXPECT_GT(ids2[1], 0u) << "Second input nonzero";
EXPECT_NE(ids1[3], ids2[1])
<< "First and second input have different interactionIds";
EXPECT_GT(ids3[0], 0u) << "Keydown nonzero";
EXPECT_EQ(ids3[0], ids3[2]) << "Keydown and keyup have some id";
EXPECT_EQ(ids3[1], 0u) << "Third input has zero id";
EXPECT_GT(ids2[1], 0u) << "Second input nonzero";
EXPECT_NE(ids1[3], ids2[1])
<< "First and second input have different interactionIds";
EXPECT_GT(ids3[0], 0u) << "Keydown nonzero";
EXPECT_EQ(ids3[0], ids3[2]) << "Keydown and keyup have some id";
EXPECT_EQ(ids3[1], 0u) << "Third input has zero id";
CheckUKMValues({{16, 16, UserInteractionType::kKeyboard},
{14, 14, UserInteractionType::kKeyboard},
{43, 70, UserInteractionType::kKeyboard}});
} else {
EXPECT_GT(ids1[3], 0u) << "First input nonzero";
EXPECT_GT(ids2[1], 0u) << "Second input nonzero";
EXPECT_NE(ids1[3], ids2[1])
<< "First and second input have different interactionIds";
EXPECT_GT(ids3[0], 0u) << "Keydown nonzero";
EXPECT_EQ(ids3[0], ids3[2]) << "Keydown and keyup have some id";
EXPECT_EQ(ids3[1], 0u) << "Third input has zero id";
CheckUKMValues({{9, 9, UserInteractionType::kKeyboard},
{14, 14, UserInteractionType::kKeyboard},
{43, 70, UserInteractionType::kKeyboard}});
}
CheckUKMValues({{16, 16, UserInteractionType::kKeyboard},
{14, 14, UserInteractionType::kKeyboard},
{43, 70, UserInteractionType::kKeyboard}});
}
TEST_P(InteractionIdTest, TapWithoutClick) {
TEST_F(InteractionIdTest, TapWithoutClick) {
std::vector<EventForInteraction> events = {
{event_type_names::kPointerdown, std::nullopt, 1, GetTimeStamp(100),
GetTimeStamp(140)},
@ -2038,7 +1963,7 @@ TEST_P(InteractionIdTest, TapWithoutClick) {
CheckUKMValues({{40, 50, UserInteractionType::kTapOrClick}});
}
TEST_P(InteractionIdTest, PointerupClick) {
TEST_F(InteractionIdTest, PointerupClick) {
std::vector<EventForInteraction> events = {
{event_type_names::kPointerup, std::nullopt, 1, GetTimeStamp(100),
GetTimeStamp(140)},
@ -2052,7 +1977,7 @@ TEST_P(InteractionIdTest, PointerupClick) {
CheckUKMValues({{30, 30, UserInteractionType::kTapOrClick}});
}
TEST_P(InteractionIdTest, JustClick) {
TEST_F(InteractionIdTest, JustClick) {
// Hitting enter on a keyboard may cause just a trusted click event.
std::vector<EventForInteraction> events = {
{event_type_names::kClick, std::nullopt, 0, GetTimeStamp(120),
@ -2064,7 +1989,7 @@ TEST_P(InteractionIdTest, JustClick) {
CheckUKMValues({{30, 30, UserInteractionType::kTapOrClick}});
}
TEST_P(InteractionIdTest, PointerdownClick) {
TEST_F(InteractionIdTest, PointerdownClick) {
// Contextmenus may cause us to only see pointerdown and click (no pointerup).
std::vector<EventForInteraction> events = {
{event_type_names::kPointerdown, std::nullopt, 1, GetTimeStamp(100),
@ -2079,7 +2004,7 @@ TEST_P(InteractionIdTest, PointerdownClick) {
CheckUKMValues({{40, 50, UserInteractionType::kTapOrClick}});
}
TEST_P(InteractionIdTest, MultiTouch) {
TEST_F(InteractionIdTest, MultiTouch) {
// In multitouch, we report an interaction per pointerId. We do not see
// clicks.
std::vector<EventForInteraction> events = {
@ -2104,7 +2029,7 @@ TEST_P(InteractionIdTest, MultiTouch) {
{50, 60, UserInteractionType::kTapOrClick}});
}
TEST_P(InteractionIdTest, ClickIncorrectPointerId) {
TEST_F(InteractionIdTest, ClickIncorrectPointerId) {
// On mobile, in cases where touchstart is skipped, click does not get the
// correct pointerId. See crbug.com/1264930 for more details.
// TODO crbug.com/359679950: remove this test and event timing workaround
@ -2122,6 +2047,4 @@ TEST_P(InteractionIdTest, ClickIncorrectPointerId) {
CheckUKMValues({{40, 40, UserInteractionType::kTapOrClick}});
}
INSTANTIATE_TEST_SUITE_P(All, InteractionIdTest, ::testing::Bool());
} // namespace blink