[go: nahoru, domu]

[Event Timing] Expose interactionId to Keypress & keydown/up under composition

This CL changes the logic of generating interaction Id for keyboard
events in performance event timing.

Non-composition:
* Previous behaviour: Interaction id is generated on keyups, and will
  match the related keydown backwards.
* New behaviour: Interaction id is now generated on the keydown entry,
  and match forward with related keypress and keyup.
Composition(IME, virtual keyboard, etc.):
* Previous behaviour: Interaction id is assigned to each input event
  only.
* New behaviour: Interaction id is generated on the first input or
  keydown event (whichever comes first) before the compositionupdate,
  and any further keydown or input event before the compositionupdate
  and any number of keyups after compositionupdate will be matched
  with same interaction id.

This may affect the total duration of INP since more events are now
being meatured, eg. keypress.

Low-Coverage-Reason: OTHER This CL is also covered by wpt manual test.
Bug: 1456384
Change-Id: Ie736f3f189e9a349badbe03c4a65627ac491eff4
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4632521
Reviewed-by: Robert Flack <flackr@chromium.org>
Commit-Queue: Aoyuan Zuo <zuoaoyuan@chromium.org>
Reviewed-by: Michal Mocny <mmocny@chromium.org>
Reviewed-by: Aoyuan Zuo <zuoaoyuan@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1261339}
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 863e39d..c064862 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -7267,6 +7267,29 @@
             ]
         }
     ],
+    "EventTimingKeypressAndCompositionInteractionId": [
+        {
+            "platforms": [
+                "android",
+                "chromeos",
+                "chromeos_lacros",
+                "fuchsia",
+                "linux",
+                "mac",
+                "windows",
+                "android_webview",
+                "android_weblayer"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "EventTimingKeypressAndCompositionInteractionId"
+                    ]
+                }
+            ]
+        }
+    ],
     "EvictionUnlocksResources": [
         {
             "platforms": [
diff --git a/third_party/blink/common/features.cc b/third_party/blink/common/features.cc
index fcbe28f8..ca8f050 100644
--- a/third_party/blink/common/features.cc
+++ b/third_party/blink/common/features.cc
@@ -809,6 +809,12 @@
 #endif
 );
 
+// Exposes Event Timing keyboard InteractionId of composition and keypress
+// events.
+BASE_FEATURE(kEventTimingKeypressAndCompositionInteractionId,
+             "EventTimingKeypressAndCompositionInteractionId",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 // Enables unload handler deprecation via Permissions-Policy.
 // https://crbug.com/1324111
 BASE_FEATURE(kDeprecateUnload,
diff --git a/third_party/blink/public/common/features.h b/third_party/blink/public/common/features.h
index bb33f5c..7f4efbf 100644
--- a/third_party/blink/public/common/features.h
+++ b/third_party/blink/public/common/features.h
@@ -426,6 +426,11 @@
 // 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 BASE_DECLARE_FEATURE(kDeprecateUnloadByUserAndOrigin);
diff --git a/third_party/blink/renderer/core/timing/event_timing.cc b/third_party/blink/renderer/core/timing/event_timing.cc
index 4d9ad8d..39ec75e 100644
--- a/third_party/blink/renderer/core/timing/event_timing.cc
+++ b/third_party/blink/renderer/core/timing/event_timing.cc
@@ -83,8 +83,10 @@
   // since EventCounts cannot be used to properly computed percentiles on those.
   // See spec: https://wicg.github.io/event-timing/#sec-events-exposed.
   // Need to be kept in sync with IsWebInteractionEvent
-  // (widget_event_handler.cc).
-  return event.isTrusted() &&
+  // (widget_event_handler.cc) except non-raw web input event types, for example
+  // kCompositionend.
+  return (event.isTrusted() ||
+          event.type() == event_type_names::kCompositionend) &&
          (IsA<MouseEvent>(event) || IsA<PointerEvent>(event) ||
           IsA<TouchEvent>(event) || IsA<KeyboardEvent>(event) ||
           IsA<WheelEvent>(event) || event.IsInputEvent() ||
@@ -103,10 +105,10 @@
     const Event& event,
     EventTarget* original_event_target) {
   auto* performance = DOMWindowPerformance::performance(*window);
-  if (!performance || !event.isTrusted() ||
-      (!IsEventTypeForEventTiming(event) &&
-       event.type() != event_type_names::kPointermove))
+  if (!performance || (!IsEventTypeForEventTiming(event) &&
+                       event.type() != event_type_names::kPointermove)) {
     return nullptr;
+  }
 
   // Most events track their performance in EventDispatcher::Dispatch but
   // some event types which can be filtered are tracked at the point
diff --git a/third_party/blink/renderer/core/timing/responsiveness_metrics.cc b/third_party/blink/renderer/core/timing/responsiveness_metrics.cc
index cc040e5..5d8a8ff9 100644
--- a/third_party/blink/renderer/core/timing/responsiveness_metrics.cc
+++ b/third_party/blink/renderer/core/timing/responsiveness_metrics.cc
@@ -13,6 +13,7 @@
 #include "base/trace_event/trace_id_helper.h"
 #include "services/metrics/public/cpp/ukm_builders.h"
 #include "services/metrics/public/cpp/ukm_recorder.h"
+#include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/common/responsiveness_metrics/user_interaction_latency.h"
 #include "third_party/blink/renderer/core/dom/dom_high_res_time_stamp.h"
 #include "third_party/blink/renderer/core/event_type_names.h"
@@ -20,6 +21,7 @@
 #include "third_party/blink/renderer/core/frame/local_dom_window.h"
 #include "third_party/blink/renderer/core/frame/local_frame.h"
 #include "third_party/blink/renderer/core/frame/local_frame_client.h"
+#include "third_party/blink/renderer/core/timing/performance.h"
 #include "third_party/blink/renderer/core/timing/performance_event_timing.h"
 #include "third_party/blink/renderer/core/timing/window_performance.h"
 #include "third_party/blink/renderer/platform/instrumentation/tracing/traced_value.h"
@@ -151,6 +153,10 @@
           window_performance_->task_runner_,
           this,
           &ResponsivenessMetrics::ContextmenuFlushTimerFired),
+      composition_end_flush_timer_(
+          window_performance_->task_runner_,
+          this,
+          &ResponsivenessMetrics::FlushCompositionEndTimerFired),
       current_interaction_id_for_event_timing_(
           // Follow the spec by choosing a random integer as the initial value
           // to discourage developers from using interactionId to count the
@@ -165,8 +171,9 @@
     UserInteractionType interaction_type,
     const WTF::Vector<EventTimestamps>& timestamps,
     uint32_t interaction_offset) {
-  if (!window)
+  if (!window) {
     return;
+  }
 
   for (EventTimestamps timestamp : timestamps) {
     if (timestamp.start_time == base::TimeTicks()) {
@@ -397,6 +404,12 @@
     PerformanceEventTiming* entry,
     std::optional<int> key_code,
     EventTimestamps event_timestamps) {
+  if (base::FeatureList::IsEnabled(
+          features::kEventTimingKeypressAndCompositionInteractionId)) {
+    return SetKeyIdAndRecordLatencyExperimental(entry, key_code,
+                                                event_timestamps);
+  }
+
   last_pointer_id_ = std::nullopt;
   auto event_type = entry->name();
   if (event_type == event_type_names::kKeydown) {
@@ -408,21 +421,22 @@
       FlushKeydown();
     }
     // During compositions, we ignore keydowns/keyups and look at input events.
-    if (composition_started_)
+    if (composition_started_) {
       return true;
+    }
 
     DCHECK(key_code.has_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.
+      // 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.
@@ -446,8 +460,9 @@
       contextmenu_flush_timer_.Stop();
     }
     DCHECK(key_code.has_value());
-    if (composition_started_ || !key_code_entry_map_.Contains(*key_code))
+    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.
@@ -477,7 +492,8 @@
     }
     // 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.
+    // TODO(crbug.com/1252856): fix counts in ChromeOS due to duplicate
+    // events.
     UpdateInteractionId();
     entry->SetInteractionIdAndOffset(GetCurrentInteractionId(),
                                      GetInteractionCount());
@@ -487,17 +503,149 @@
   return true;
 }
 
-void ResponsivenessMetrics::FlushExpiredKeydown(DOMHighResTimeStamp end_time) {
-  // 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 (end_time - key_down->processingEnd() > kMaxDelayForEntries) {
-      window_performance_->NotifyAndAddEventTimingBuffer(key_down);
-      key_codes_to_remove.push_back(entry.key);
+bool ResponsivenessMetrics::SetKeyIdAndRecordLatencyExperimental(
+    PerformanceEventTiming* entry,
+    std::optional<int> key_code,
+    EventTimestamps 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();
     }
+
+    CHECK(key_code.has_value());
+    if (composition_state_ == kNonComposition) {
+      if (IsHoldingKey(key_code)) {
+        FlushSequenceBasedKeyboardEvents();
+      }
+      UpdateInteractionId();
+    } else if (composition_state_ == kCompositionContinueOngoingInteraction) {
+      // Continue interaction; Do not update Interaction Id
+    } else if (composition_state_ == kCompositionStartNewInteractionOnKeydown) {
+      FlushSequenceBasedKeyboardEvents();
+      UpdateInteractionId();
+      composition_state_ = kCompositionContinueOngoingInteraction;
+    } else if (composition_state_ == kEndCompositionOnKeydown) {
+      FlushSequenceBasedKeyboardEvents();
+      UpdateInteractionId();
+      composition_state_ = kNonComposition;
+    }
+
+    entry->SetInteractionIdAndOffset(GetCurrentInteractionId(),
+                                     GetInteractionCount());
+    sequence_based_keyboard_interaction_info_.SetInteractionIdAndOffset(
+        GetCurrentInteractionId(), GetInteractionCount());
+    sequence_based_keyboard_interaction_info_.AddTimestamps(event_timestamps);
+
+    if (composition_state_ == kNonComposition) {
+      InteractionInfo keydown_entry(GetCurrentInteractionId(),
+                                    GetInteractionCount(), event_timestamps);
+      key_code_to_interaction_info_map_.Set(*key_code,
+                                            std::move(keydown_entry));
+    }
+    last_keydown_keycode_ = key_code;
+  } else if (event_type == event_type_names::kKeyup) {
+    if (composition_state_ == kNonComposition) {
+      CHECK(key_code.has_value());
+      if (!key_code_to_interaction_info_map_.Contains(*key_code)) {
+        return true;
+      }
+
+      // Match the keydown entry with the keyup entry using keycode.
+      auto& key_entry =
+          key_code_to_interaction_info_map_.find(*key_code)->value;
+      entry->SetInteractionIdAndOffset(key_entry.GetInteractionId(),
+                                       key_entry.GetInteractionOffset());
+      key_entry.AddTimestamps(event_timestamps);
+      RecordKeyboardUKM(window_performance_->DomWindow(),
+                        key_entry.GetTimeStamps(),
+                        key_entry.GetInteractionOffset());
+      // Remove keycode from the map and reset other values
+      key_code_to_interaction_info_map_.erase(*key_code);
+      sequence_based_keyboard_interaction_info_.Clear();
+    } else {
+      entry->SetInteractionIdAndOffset(GetCurrentInteractionId(),
+                                       GetInteractionCount());
+      sequence_based_keyboard_interaction_info_.SetInteractionIdAndOffset(
+          GetCurrentInteractionId(), GetInteractionCount());
+      sequence_based_keyboard_interaction_info_.AddTimestamps(event_timestamps);
+    }
+    last_keydown_keycode_.reset();
+  } else if (event_type == event_type_names::kKeypress) {
+    if (composition_state_ == kNonComposition &&
+        last_keydown_keycode_.has_value()) {
+      // Set a interaction id generated by previous keydown entry
+      entry->SetInteractionIdAndOffset(GetCurrentInteractionId(),
+                                       GetInteractionCount());
+      key_code_to_interaction_info_map_.find(*last_keydown_keycode_)
+          ->value.AddTimestamps(event_timestamps);
+    }
+  } else if (event_type == event_type_names::kCompositionstart) {
+    composition_state_ = kCompositionContinueOngoingInteraction;
+    key_code_to_interaction_info_map_.clear();
+  } else if (event_type == event_type_names::kCompositionend) {
+    composition_state_ = kEndCompositionOnKeydown;
+    composition_end_flush_timer_.StartOneShot(kFlushTimerLength, FROM_HERE);
+  } else if (event_type == event_type_names::kCompositionupdate) {
+    if (!last_keydown_keycode_.has_value()) {
+      composition_state_ = kCompositionStartNewInteractionOnInput;
+    } else {
+      composition_state_ = kCompositionStartNewInteractionOnKeydown;
+    }
+  } else if (event_type == event_type_names::kInput) {
+    // Expose interactionId for Input events only under composition
+    if (composition_state_ == kNonComposition) {
+      return true;
+    }
+    // Update Interaction Id when input is selected using IME suggestion without
+    // pressing a key. In this case Input event starts and finishes interaction
+    if (composition_state_ == kCompositionStartNewInteractionOnInput) {
+      FlushSequenceBasedKeyboardEvents();
+      UpdateInteractionId();
+      entry->SetInteractionIdAndOffset(GetCurrentInteractionId(),
+                                       GetInteractionCount());
+      sequence_based_keyboard_interaction_info_.SetInteractionIdAndOffset(
+          GetCurrentInteractionId(), GetInteractionCount());
+      sequence_based_keyboard_interaction_info_.AddTimestamps(event_timestamps);
+      FlushSequenceBasedKeyboardEvents();
+      composition_state_ = kCompositionStartNewInteractionOnKeydown;
+    } else {
+      // TODO(crbug.com/1252856): fix counts in ChromeOS due to duplicate
+      // events.
+      entry->SetInteractionIdAndOffset(GetCurrentInteractionId(),
+                                       GetInteractionCount());
+      sequence_based_keyboard_interaction_info_.SetInteractionIdAndOffset(
+          GetCurrentInteractionId(), GetInteractionCount());
+      sequence_based_keyboard_interaction_info_.AddTimestamps(event_timestamps);
+    }
+    last_keydown_keycode_.reset();
   }
-  key_code_entry_map_.RemoveAll(key_codes_to_remove);
+  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() {
@@ -540,6 +688,37 @@
   return current_interaction_event_queued_timestamp_;
 }
 
+void ResponsivenessMetrics::FlushCompositionEndTimerFired(TimerBase*) {
+  FlushSequenceBasedKeyboardEvents();
+}
+
+void ResponsivenessMetrics::FlushSequenceBasedKeyboardEvents() {
+  LocalDOMWindow* window = window_performance_->DomWindow();
+  if (!window) {
+    return;
+  }
+
+  if (composition_end_flush_timer_.IsActive()) {
+    composition_end_flush_timer_.Stop();
+  }
+
+  if (!sequence_based_keyboard_interaction_info_.Empty()) {
+    RecordKeyboardUKM(
+        window, sequence_based_keyboard_interaction_info_.GetTimeStamps(),
+        sequence_based_keyboard_interaction_info_.GetInteractionOffset());
+    sequence_based_keyboard_interaction_info_.Clear();
+  }
+}
+
+// Determines if the key is is being held (pressed) for a sustained period of
+// time. It is used when keyup does not appear in the end of a interaction (e.g
+// Windows). In such cases the interaction is reported using
+// sequence_based_keyboard_interaction_info_.
+bool ResponsivenessMetrics::IsHoldingKey(std::optional<int> key_code) {
+  return last_keydown_keycode_.has_value() &&
+         *last_keydown_keycode_ == *key_code;
+}
+
 void ResponsivenessMetrics::FlushPointerTimerFired(TimerBase*) {
   FlushPointerup();
 }
@@ -612,9 +791,15 @@
   // We only delay dispatching entries when they are pointerdown.
   if (entry->name() != event_type_names::kPointerdown)
     return;
+
   window_performance_->NotifyAndAddEventTimingBuffer(entry);
 }
 
+// Flush UKM timestamps of composition events for testing.
+void ResponsivenessMetrics::FlushAllEventsForTesting() {
+  FlushSequenceBasedKeyboardEvents();
+}
+
 void ResponsivenessMetrics::KeyboardEntryAndTimestamps::Trace(
     Visitor* visitor) const {
   visitor->Trace(entry_);
@@ -627,9 +812,10 @@
 void ResponsivenessMetrics::Trace(Visitor* visitor) const {
   visitor->Trace(window_performance_);
   visitor->Trace(pointer_id_entry_map_);
-  visitor->Trace(key_code_entry_map_);
   visitor->Trace(pointer_flush_timer_);
   visitor->Trace(contextmenu_flush_timer_);
+  visitor->Trace(key_code_entry_map_);
+  visitor->Trace(composition_end_flush_timer_);
 }
 
 perfetto::protos::pbzero::WebContentInteraction::Type
diff --git a/third_party/blink/renderer/core/timing/responsiveness_metrics.h b/third_party/blink/renderer/core/timing/responsiveness_metrics.h
index e87494f..7aa710e 100644
--- a/third_party/blink/renderer/core/timing/responsiveness_metrics.h
+++ b/third_party/blink/renderer/core/timing/responsiveness_metrics.h
@@ -15,6 +15,7 @@
 #include "third_party/blink/renderer/core/events/pointer_event.h"
 #include "third_party/blink/renderer/core/frame/local_dom_window.h"
 #include "third_party/blink/renderer/platform/heap/collection_support/heap_hash_map.h"
+#include "third_party/blink/renderer/platform/wtf/vector_traits.h"
 #include "third_party/perfetto/include/perfetto/tracing/event_context.h"
 
 namespace blink {
@@ -22,7 +23,8 @@
 class PerformanceEventTiming;
 class WindowPerformance;
 
-class ResponsivenessMetrics : public GarbageCollected<ResponsivenessMetrics> {
+class CORE_EXPORT ResponsivenessMetrics
+    : public GarbageCollected<ResponsivenessMetrics> {
  public:
   // Timestamps for input events.
   struct EventTimestamps {
@@ -38,6 +40,46 @@
     base::TimeTicks main_thread_queued_time;
   };
 
+  // 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)
+  class InteractionInfo {
+   public:
+    InteractionInfo(uint32_t interaction_id,
+                    uint32_t interaction_offset,
+                    EventTimestamps timestamps)
+        : interaction_id_(interaction_id),
+          interaction_offset_(interaction_offset),
+          timestamps_({timestamps}) {}
+
+    InteractionInfo() = default;
+    ~InteractionInfo() = default;
+    uint32_t GetInteractionId() const { return interaction_id_; }
+    uint32_t GetInteractionOffset() const { return interaction_offset_; }
+    void SetInteractionIdAndOffset(uint32_t interaction_id,
+                                   uint32_t interaction_offset) {
+      interaction_id_ = interaction_id;
+      interaction_offset_ = interaction_offset;
+    }
+    Vector<EventTimestamps> const& GetTimeStamps() { return timestamps_; }
+    bool Empty() { return timestamps_.empty(); }
+    void AddTimestamps(EventTimestamps timestamp) {
+      timestamps_.push_back(timestamp);
+    }
+    void Clear() {
+      interaction_id_ = 0;
+      interaction_offset_ = 0;
+      timestamps_.clear();
+    }
+
+   private:
+    // InteractionId associated with the entry.
+    uint32_t interaction_id_ = 0;
+    uint32_t interaction_offset_ = 0;
+    // Timestamps associated with the entries of the same interaction.
+    Vector<EventTimestamps> timestamps_;
+  };
+
   // Wrapper class to store PerformanceEventTiming and timestamps
   // on a HeapHashMap.
   class KeyboardEntryAndTimestamps
@@ -103,6 +145,9 @@
   explicit ResponsivenessMetrics(WindowPerformance*);
   ~ResponsivenessMetrics();
 
+  // Flush UKM timestamps of composition events for testing.
+  void FlushAllEventsForTesting();
+
   // Stop UKM sampling for testing.
   void StopUkmSamplingForTesting() { sampling_ = false; }
 
@@ -126,8 +171,13 @@
                                 std::optional<int> key_code,
                                 EventTimestamps event_timestamps);
 
-  // Clear keydowns in |key_codes_to_remove| if we have stored them for a
-  // while.
+  // Experimental function that in addition to SetKeyIdAndRecordLatency()
+  // exposes interactionId for keypress and keyup/keydown under composition.
+  bool SetKeyIdAndRecordLatencyExperimental(PerformanceEventTiming* entry,
+                                            std::optional<int> key_code,
+                                            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.
@@ -185,20 +235,55 @@
   // Used to flush all entries in |pointer_id_entry_map_|.
   void FlushPointerdownAndPointerup();
 
+  // Method called when |composition_end_| fires. Ensures that the last
+  // interaction of compositoin events is reported, even if
+  // there is no following keydown.
+  void FlushCompositionEndTimerFired(TimerBase*);
+
+  // Used to flush any entries in |keyboard_sequence_based_timestamps_to_UKM_|
+  void FlushSequenceBasedKeyboardEvents();
+
   void NotifyPointerdown(PerformanceEventTiming* entry) const;
 
+  // Indicates if a key is being held for a sustained period of time
+  bool IsHoldingKey(std::optional<int> key_code);
+
   Member<WindowPerformance> window_performance_;
 
+  // Map from keyCodes to interaction info (ID, offset, and timestamps).
+  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.
   bool composition_started_ = false;
 
+  enum CompositionState {
+    kNonComposition,
+    kCompositionContinueOngoingInteraction,
+    kCompositionStartNewInteractionOnKeydown,
+    kCompositionStartNewInteractionOnInput,
+    kEndCompositionOnKeydown
+  };
+
+  CompositionState composition_state_ = kNonComposition;
+
+  std::optional<int> last_keydown_keycode_;
+
+  // InteractionInfo storing interactionId, interaction offset, and timestamps
+  // of entries for reporting them to UKM in 3 main cases:
+  //  1) Pressing a key under composition.
+  //  2) Holding a key under composition.
+  //  3) Holding a key under no composition.
+  InteractionInfo sequence_based_keyboard_interaction_info_;
+
   // Map from pointerId to the first pointer event entry seen for the user
   // interaction, and other information.
   HeapHashMap<PointerId,
@@ -207,6 +292,7 @@
       pointer_id_entry_map_;
   HeapTaskRunnerTimer<ResponsivenessMetrics> pointer_flush_timer_;
   HeapTaskRunnerTimer<ResponsivenessMetrics> contextmenu_flush_timer_;
+  HeapTaskRunnerTimer<ResponsivenessMetrics> composition_end_flush_timer_;
   // The PointerId of the last pointerdown or pointerup event processed. Used to
   // know which interactionId to use for click events. If pointecancel or
   // keyboard events are seen, the value is reset. TODO(crbug.com/1264930):
diff --git a/third_party/blink/renderer/core/timing/window_performance.cc b/third_party/blink/renderer/core/timing/window_performance.cc
index 946036b..8cd4bf2f 100644
--- a/third_party/blink/renderer/core/timing/window_performance.cc
+++ b/third_party/blink/renderer/core/timing/window_performance.cc
@@ -175,8 +175,10 @@
          type == event_type_names::kPointerup ||
          type == event_type_names::kClick ||
          type == event_type_names::kKeydown ||
+         type == event_type_names::kKeypress ||
          type == event_type_names::kKeyup ||
          type == event_type_names::kCompositionstart ||
+         type == event_type_names::kCompositionupdate ||
          type == event_type_names::kCompositionend ||
          type == event_type_names::kInput;
 }
@@ -410,7 +412,10 @@
   const PointerEvent* pointer_event = DynamicTo<PointerEvent>(event);
   if (event_type == event_type_names::kPointermove) {
     // A trusted pointermove must be a PointerEvent.
-    DCHECK(event.IsPointerEvent());
+    if (!event.IsPointerEvent()) {
+      return;
+    }
+
     NotifyPotentialDrag(pointer_event->pointerId());
     SetCurrentEventTimingEvent(nullptr);
     return;
diff --git a/third_party/blink/renderer/core/timing/window_performance_test.cc b/third_party/blink/renderer/core/timing/window_performance_test.cc
index 3bfb14f..f318b36 100644
--- a/third_party/blink/renderer/core/timing/window_performance_test.cc
+++ b/third_party/blink/renderer/core/timing/window_performance_test.cc
@@ -7,6 +7,7 @@
 
 #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"
@@ -58,9 +59,14 @@
 
 }  // namespace
 
-class WindowPerformanceTest : public testing::Test {
+class WindowPerformanceTest : public testing::Test,
+                              public ::testing::WithParamInterface<bool> {
  protected:
   void SetUp() override {
+    if (GetParam()) {
+      features_.InitAndEnableFeature(
+          blink::features::kEventTimingKeypressAndCompositionInteractionId);
+    }
     test_task_runner_ = base::MakeRefCounted<base::TestMockTimeTaskRunner>();
     ResetPerformance();
   }
@@ -190,9 +196,10 @@
   scoped_refptr<base::TestMockTimeTaskRunner> test_task_runner_;
   ScopedFakeUkmRecorder scoped_fake_ukm_recorder_;
   base::HistogramTester histogram_tester_;
+  base::test::ScopedFeatureList features_;
 };
 
-TEST_F(WindowPerformanceTest, SanitizedLongTaskName) {
+TEST_P(WindowPerformanceTest, SanitizedLongTaskName) {
   // Unable to attribute, when no execution contents are available.
   EXPECT_EQ("unknown", SanitizedAttribution(nullptr, false, GetFrame()));
 
@@ -204,7 +211,7 @@
             SanitizedAttribution(GetWindow(), true, GetFrame()));
 }
 
-TEST_F(WindowPerformanceTest, SanitizedLongTaskName_CrossOrigin) {
+TEST_P(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"));
@@ -221,7 +228,7 @@
 // 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_F(WindowPerformanceTest, NavigateAway) {
+TEST_P(WindowPerformanceTest, NavigateAway) {
   AddLongTaskObserver();
 
   // Simulate navigation commit.
@@ -277,7 +284,7 @@
 
 // Make sure the output entries with the same timestamps follow the insertion
 // order. (http://crbug.com/767560)
-TEST_F(WindowPerformanceTest, EnsureEntryListOrder) {
+TEST_P(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();
@@ -310,7 +317,7 @@
   }
 }
 
-TEST_F(WindowPerformanceTest, EventTimingEntryBuffering) {
+TEST_P(WindowPerformanceTest, EventTimingEntryBuffering) {
   EXPECT_TRUE(page_holder_->GetFrame().Loader().GetDocumentLoader());
 
   base::TimeTicks start_time = GetTimeOrigin() + base::Seconds(1.1);
@@ -347,7 +354,7 @@
                     .size());
 }
 
-TEST_F(WindowPerformanceTest, Expose100MsEvents) {
+TEST_P(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);
@@ -369,7 +376,7 @@
   EXPECT_EQ(event_type_names::kMousedown, entries.at(0)->name());
 }
 
-TEST_F(WindowPerformanceTest, EventTimingDuration) {
+TEST_P(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);
@@ -404,7 +411,7 @@
 
 // Test the case where multiple events are registered and then their
 // presentation promise is resolved.
-TEST_F(WindowPerformanceTest, MultipleEventsThenPresent) {
+TEST_P(WindowPerformanceTest, MultipleEventsThenPresent) {
   size_t num_events = 10;
   for (size_t i = 0; i < num_events; ++i) {
     base::TimeTicks start_time = GetTimeOrigin() + base::Seconds(i);
@@ -427,7 +434,7 @@
 }
 
 // Test for existence of 'first-input' given different types of first events.
-TEST_F(WindowPerformanceTest, FirstInput) {
+TEST_P(WindowPerformanceTest, FirstInput) {
   struct {
     AtomicString event_type;
     bool should_report;
@@ -461,7 +468,7 @@
 
 // Test that the 'first-input' is populated after some irrelevant events are
 // ignored.
-TEST_F(WindowPerformanceTest, FirstInputAfterIgnored) {
+TEST_P(WindowPerformanceTest, FirstInputAfterIgnored) {
   AtomicString several_events[] = {event_type_names::kMouseover,
                                    event_type_names::kMousedown,
                                    event_type_names::kPointerup};
@@ -482,7 +489,7 @@
 }
 
 // Test that pointerdown followed by pointerup works as a 'firstInput'.
-TEST_F(WindowPerformanceTest, FirstPointerUp) {
+TEST_P(WindowPerformanceTest, FirstPointerUp) {
   base::TimeTicks start_time = GetTimeStamp(0);
   base::TimeTicks processing_start = GetTimeStamp(1);
   base::TimeTicks processing_end = GetTimeStamp(2);
@@ -508,7 +515,7 @@
 
 // When the pointerdown is optimized out, the mousedown works as a
 // 'first-input'.
-TEST_F(WindowPerformanceTest, PointerdownOptimizedOut) {
+TEST_P(WindowPerformanceTest, PointerdownOptimizedOut) {
   base::TimeTicks start_time = GetTimeStamp(0);
   base::TimeTicks processing_start = GetTimeStamp(1);
   base::TimeTicks processing_end = GetTimeStamp(2);
@@ -528,7 +535,7 @@
 
 // Test that pointerdown followed by mousedown, pointerup works as a
 // 'first-input'.
-TEST_F(WindowPerformanceTest, PointerdownOnDesktop) {
+TEST_P(WindowPerformanceTest, PointerdownOnDesktop) {
   base::TimeTicks start_time = GetTimeStamp(0);
   base::TimeTicks processing_start = GetTimeStamp(1);
   base::TimeTicks processing_end = GetTimeStamp(2);
@@ -558,7 +565,7 @@
                     .size());
 }
 
-TEST_F(WindowPerformanceTest, OneKeyboardInteraction) {
+TEST_P(WindowPerformanceTest, OneKeyboardInteraction) {
   base::TimeTicks keydown_timestamp = GetTimeStamp(0);
   // Keydown
   base::TimeTicks processing_start_keydown = GetTimeStamp(1);
@@ -608,7 +615,7 @@
       "Blink.Responsiveness.UserInteraction.MaxEventDuration.Drag", 0);
 }
 
-TEST_F(WindowPerformanceTest, HoldingDownAKey) {
+TEST_P(WindowPerformanceTest, HoldingDownAKey) {
   auto entries = GetUkmRecorder()->GetEntriesByName(
       ukm::builders::Responsiveness_UserInteraction::kEntryName);
   EXPECT_EQ(0u, entries.size());
@@ -688,7 +695,7 @@
       "Blink.Responsiveness.UserInteraction.MaxEventDuration.Drag", 0);
 }
 
-TEST_F(WindowPerformanceTest, PressMultipleKeys) {
+TEST_P(WindowPerformanceTest, PressMultipleKeys) {
   auto entries = GetUkmRecorder()->GetEntriesByName(
       ukm::builders::Responsiveness_UserInteraction::kEntryName);
   EXPECT_EQ(0u, entries.size());
@@ -762,7 +769,7 @@
 // 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_F(WindowPerformanceTest, KeyupFinishLastButCallbackInvokedFirst) {
+TEST_P(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;
@@ -823,7 +830,7 @@
       "Blink.Responsiveness.UserInteraction.MaxEventDuration.Drag", 0);
 }
 
-TEST_F(WindowPerformanceTest, TapOrClick) {
+TEST_P(WindowPerformanceTest, TapOrClick) {
   // Pointerdown
   base::TimeTicks pointerdown_timestamp = GetTimeOrigin();
   base::TimeTicks processing_start_pointerdown = GetTimeStamp(1);
@@ -883,7 +890,7 @@
       "Blink.Responsiveness.UserInteraction.MaxEventDuration.Drag", 0);
 }
 
-TEST_F(WindowPerformanceTest, PageVisibilityChanged) {
+TEST_P(WindowPerformanceTest, PageVisibilityChanged) {
   // The page visibility gets changed.
   PageVisibilityChanged(GetTimeStamp(18));
 
@@ -945,7 +952,7 @@
       ukm::builders::Responsiveness_UserInteraction::kInteractionTypeName, 1);
 }
 
-TEST_F(WindowPerformanceTest, Drag) {
+TEST_P(WindowPerformanceTest, Drag) {
   // Pointerdown
   base::TimeTicks pointerdwon_timestamp = GetTimeOrigin();
   base::TimeTicks processing_start_pointerdown = GetTimeStamp(1);
@@ -1007,7 +1014,7 @@
       "Blink.Responsiveness.UserInteraction.MaxEventDuration.Drag", 1);
 }
 
-TEST_F(WindowPerformanceTest, Scroll) {
+TEST_P(WindowPerformanceTest, Scroll) {
   // Pointerdown
   base::TimeTicks pointerdown_timestamp = GetTimeOrigin();
   base::TimeTicks processing_start_keydown = GetTimeStamp(1);
@@ -1047,7 +1054,7 @@
       "Blink.Responsiveness.UserInteraction.MaxEventDuration.Drag", 0);
 }
 
-TEST_F(WindowPerformanceTest, TouchesWithoutClick) {
+TEST_P(WindowPerformanceTest, TouchesWithoutClick) {
   base::TimeTicks pointerdown_timestamp = GetTimeOrigin();
   // First Pointerdown
   base::TimeTicks processing_start_pointerdown = GetTimeStamp(1);
@@ -1082,7 +1089,7 @@
 //  Test artificial pointerup and click on MacOS fall back to use processingEnd
 //  as event duration ending time.
 //  See crbug.com/1321819
-TEST_F(WindowPerformanceTest, ArtificialPointerupOrClick) {
+TEST_P(WindowPerformanceTest, ArtificialPointerupOrClick) {
   // Arbitrary pointerId picked for testing
   PointerId pointer_id = 4;
 
@@ -1148,7 +1155,7 @@
 #if BUILDFLAG(USE_PERFETTO_CLIENT_LIBRARY)
 // The trace_analyzer does not work on platforms on which the migration of
 // tracing into Perfetto has not completed.
-TEST_F(WindowPerformanceTest, PerformanceMarkTraceEvent) {
+TEST_P(WindowPerformanceTest, PerformanceMarkTraceEvent) {
   v8::HandleScope handle_scope(GetScriptState()->GetIsolate());
   v8::Local<v8::Context> context = GetScriptState()->GetContext();
   v8::Context::Scope context_scope(context);
@@ -1184,7 +1191,7 @@
 }
 #endif  // BUILDFLAG(USE_PERFETTO_CLIENT_LIBRARY)
 
-TEST_F(WindowPerformanceTest, ElementTimingTraceEvent) {
+TEST_P(WindowPerformanceTest, ElementTimingTraceEvent) {
   using trace_analyzer::Query;
   trace_analyzer::Start("*");
   // |element| needs to be non-null to prevent a crash.
@@ -1225,7 +1232,7 @@
   EXPECT_EQ(*url, "url");
 }
 
-TEST_F(WindowPerformanceTest, EventTimingTraceEvents) {
+TEST_P(WindowPerformanceTest, EventTimingTraceEvents) {
   using trace_analyzer::Query;
   trace_analyzer::Start("*");
   base::TimeTicks start_time = GetTimeOrigin() + base::Seconds(1);
@@ -1287,7 +1294,7 @@
   std::string* frame_trace_value = arg_dict.FindString("frame");
   EXPECT_EQ(*frame_trace_value, GetFrameIdForTracing(GetFrame()));
   EXPECT_EQ(arg_dict.FindInt("nodeId"),
-            GetWindow()->document()->GetDomNodeId());
+            DOMNodeIds::IdForNode(GetWindow()->document()));
   ASSERT_TRUE(pointerdown_begin->has_other_event());
   EXPECT_EQ(base::ClampRound(pointerdown_begin->GetAbsTimeToOtherEvent()),
             25000);
@@ -1303,7 +1310,7 @@
   frame_trace_value = arg_dict.FindString("frame");
   EXPECT_EQ(*frame_trace_value, GetFrameIdForTracing(GetFrame()));
   EXPECT_EQ(arg_dict.FindInt("nodeId"),
-            GetWindow()->document()->GetDomNodeId());
+            DOMNodeIds::IdForNode(GetWindow()->document()));
   ASSERT_TRUE(pointerup_begin->has_other_event());
   EXPECT_EQ(base::ClampRound(pointerup_begin->GetAbsTimeToOtherEvent()), 30000);
   EXPECT_FALSE(pointerup_begin->other_event->HasDictArg("data"));
@@ -1318,13 +1325,13 @@
   frame_trace_value = arg_dict.FindString("frame");
   EXPECT_EQ(*frame_trace_value, GetFrameIdForTracing(GetFrame()));
   EXPECT_EQ(arg_dict.FindInt("nodeId"),
-            GetWindow()->document()->GetDomNodeId());
+            DOMNodeIds::IdForNode(GetWindow()->document()));
   ASSERT_TRUE(click_begin->has_other_event());
   EXPECT_EQ(base::ClampRound(click_begin->GetAbsTimeToOtherEvent()), 30000);
   EXPECT_FALSE(click_begin->other_event->HasDictArg("data"));
 }
 
-TEST_F(WindowPerformanceTest, SlowInteractionToNextPaintTraceEvents) {
+TEST_P(WindowPerformanceTest, SlowInteractionToNextPaintTraceEvents) {
   using trace_analyzer::Query;
   trace_analyzer::Start("*");
 
@@ -1442,7 +1449,7 @@
   EXPECT_EQ(base::ClampRound(events[2]->GetAbsTimeToOtherEvent()), 600000);
 }
 
-TEST_F(WindowPerformanceTest, InteractionID) {
+TEST_P(WindowPerformanceTest, InteractionID) {
   // Keyboard with max duration 25, total duration 40.
   PerformanceEventTiming* keydown_entry =
       CreatePerformanceEventTiming(event_type_names::kKeydown);
@@ -1531,6 +1538,8 @@
   }
 }
 
+INSTANTIATE_TEST_SUITE_P(All, WindowPerformanceTest, ::testing::Bool());
+
 class InteractionIdTest : public WindowPerformanceTest {
  public:
   struct EventForInteraction {
@@ -1604,7 +1613,7 @@
 };
 
 // Tests English typing.
-TEST_F(InteractionIdTest, InputOutsideComposition) {
+TEST_P(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),
@@ -1656,96 +1665,137 @@
 }
 
 // Tests Japanese on Mac.
-TEST_F(InteractionIdTest, CompositionSingleKeydown) {
+TEST_P(InteractionIdTest, CompositionSingleKeydown) {
   // Insert "a" with a duration of 20.
   std::vector<EventForInteraction> events1 = {
       {event_type_names::kKeydown, 229, std::nullopt, GetTimeStamp(100),
        GetTimeStamp(200)},
       {event_type_names::kCompositionstart, std::nullopt, std::nullopt},
+      {event_type_names::kCompositionupdate, std::nullopt, std::nullopt},
       {event_type_names::kInput, std::nullopt, std::nullopt, GetTimeStamp(120),
        GetTimeStamp(140)},
       {event_type_names::kKeyup, 65, std::nullopt, GetTimeStamp(120),
        GetTimeStamp(220)}};
   std::vector<uint32_t> ids1 = SimulateInteractionIds(events1);
-  EXPECT_EQ(ids1[0], 0u) << "Keydown interactionId was zero";
-  EXPECT_EQ(ids1[1], 0u) << "Compositionstart interactionId was zero";
-  EXPECT_GT(ids1[2], 0u) << "Input interactionId was nonzero";
-  EXPECT_EQ(ids1[3], 0u) << "Keyup interactionId was zero";
 
   // Insert "b" and finish composition with a duration of 30.
   std::vector<EventForInteraction> events2 = {
       {event_type_names::kKeydown, 229, std::nullopt, GetTimeStamp(200),
        GetTimeStamp(300)},
+      {event_type_names::kCompositionupdate, std::nullopt, std::nullopt},
       {event_type_names::kInput, std::nullopt, std::nullopt, GetTimeStamp(230),
        GetTimeStamp(260)},
       {event_type_names::kKeyup, 66, std::nullopt, GetTimeStamp(270),
        GetTimeStamp(370)},
       {event_type_names::kCompositionend, std::nullopt, std::nullopt}};
   std::vector<uint32_t> ids2 = SimulateInteractionIds(events2);
-  EXPECT_EQ(ids2[0], 0u) << "Second keydown interactionId was zero";
-  EXPECT_GT(ids2[1], 0u) << "Second input interactionId was nonzero";
-  EXPECT_EQ(ids2[2], 0u) << "Second keyup interactionId was zero";
-  EXPECT_EQ(ids2[3], 0u) << "Compositionend interactionId was zero";
-  EXPECT_NE(ids1[2], ids2[1])
-      << "First and second inputs have different interactionIds";
 
-  CheckUKMValues({{20, 20, UserInteractionType::kKeyboard},
-                  {30, 30, UserInteractionType::kKeyboard}});
+  if (base::FeatureList::IsEnabled(
+          features::kEventTimingKeypressAndCompositionInteractionId)) {
+    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(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}});
+  }
 }
 
 // Tests Chinese on Mac. Windows is similar, but has more keyups inside the
 // composition.
-TEST_F(InteractionIdTest, CompositionToFinalInput) {
+TEST_P(InteractionIdTest, CompositionToFinalInput) {
   // Insert "a" with a duration of 25.
   std::vector<EventForInteraction> events1 = {
       {event_type_names::kKeydown, 229, std::nullopt, GetTimeStamp(100),
        GetTimeStamp(190)},
       {event_type_names::kCompositionstart, std::nullopt, std::nullopt},
+      {event_type_names::kCompositionupdate, std::nullopt, std::nullopt},
       {event_type_names::kInput, std::nullopt, std::nullopt, GetTimeStamp(100),
        GetTimeStamp(125)},
       {event_type_names::kKeyup, 65, std::nullopt, GetTimeStamp(110),
        GetTimeStamp(190)}};
   std::vector<uint32_t> ids1 = SimulateInteractionIds(events1);
-  EXPECT_GT(ids1[2], 0u) << "First input nonzero";
+  EXPECT_GT(ids1[3], 0u) << "First input nonzero";
 
   // Insert "b" with a duration of 35.
   std::vector<EventForInteraction> events2 = {
       {event_type_names::kKeydown, 229, std::nullopt, GetTimeStamp(200),
        GetTimeStamp(290)},
+      {event_type_names::kCompositionupdate, std::nullopt, std::nullopt},
       {event_type_names::kInput, std::nullopt, std::nullopt, GetTimeStamp(220),
        GetTimeStamp(255)},
       {event_type_names::kKeyup, 66, std::nullopt, GetTimeStamp(210),
        GetTimeStamp(290)}};
   std::vector<uint32_t> ids2 = SimulateInteractionIds(events2);
-  EXPECT_GT(ids2[1], 0u) << "Second input nonzero";
-  EXPECT_NE(ids1[2], ids2[1])
+  EXPECT_GT(ids2[2], 0u) << "Second input nonzero";
+  EXPECT_NE(ids1[3], ids2[2])
       << "First and second input have different interactionIds";
 
   // Select a composed input and finish, with a duration of 140.
   std::vector<EventForInteraction> events3 = {
+      {event_type_names::kCompositionupdate, std::nullopt, std::nullopt},
       {event_type_names::kInput, std::nullopt, std::nullopt, GetTimeStamp(300),
        GetTimeStamp(440)},
       {event_type_names::kCompositionend, std::nullopt, std::nullopt}};
   std::vector<uint32_t> ids3 = SimulateInteractionIds(events3);
-  EXPECT_EQ(ids3[1], 0u) << "Compositionend has zero interactionId";
-  EXPECT_GT(ids3[0], 0u) << "Third input has nonzero interactionId";
-  EXPECT_NE(ids1[2], ids3[0])
+  EXPECT_EQ(ids3[2], 0u) << "Compositionend 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[1], ids3[0])
+  EXPECT_NE(ids2[2], ids3[1])
       << "Second and third inputs have different interactionIds";
 
-  CheckUKMValues({{25, 25, UserInteractionType::kKeyboard},
-                  {35, 35, UserInteractionType::kKeyboard},
-                  {140, 140, UserInteractionType::kKeyboard}});
+  if (base::FeatureList::IsEnabled(
+          features::kEventTimingKeypressAndCompositionInteractionId)) {
+    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}});
+  }
 }
 
 // Tests Chinese on Windows.
-TEST_F(InteractionIdTest, CompositionToFinalInputMultipleKeyUps) {
+TEST_P(InteractionIdTest, CompositionToFinalInputMultipleKeyUps) {
   // Insert "a" with a duration of 66.
   std::vector<EventForInteraction> events1 = {
       {event_type_names::kKeydown, 229, std::nullopt, GetTimeStamp(0),
        GetTimeStamp(100)},
       {event_type_names::kCompositionstart, std::nullopt, std::nullopt},
+      {event_type_names::kCompositionupdate, std::nullopt, std::nullopt},
       {event_type_names::kInput, std::nullopt, std::nullopt, GetTimeStamp(0),
        GetTimeStamp(66)},
       {event_type_names::kKeyup, 229, std::nullopt, GetTimeStamp(0),
@@ -1753,14 +1803,12 @@
       {event_type_names::kKeyup, 65, std::nullopt, GetTimeStamp(0),
        GetTimeStamp(100)}};
   std::vector<uint32_t> ids1 = SimulateInteractionIds(events1);
-  EXPECT_GT(ids1[2], 0u) << "First input nonzero";
-  EXPECT_EQ(ids1[3], 0u) << "First keyup has zero interactionId";
-  EXPECT_EQ(ids1[4], 0u) << "Second keyup has zero interactionId";
 
   // Insert "b" with a duration of 51.
   std::vector<EventForInteraction> events2 = {
       {event_type_names::kKeydown, 229, std::nullopt, GetTimeStamp(200),
        GetTimeStamp(300)},
+      {event_type_names::kCompositionupdate, std::nullopt, std::nullopt},
       {event_type_names::kInput, std::nullopt, std::nullopt, GetTimeStamp(200),
        GetTimeStamp(251)},
       {event_type_names::kKeyup, 229, std::nullopt, GetTimeStamp(200),
@@ -1768,52 +1816,80 @@
       {event_type_names::kKeyup, 66, std::nullopt, GetTimeStamp(200),
        GetTimeStamp(300)}};
   std::vector<uint32_t> ids2 = SimulateInteractionIds(events2);
-  EXPECT_GT(ids2[1], 0u) << "Second input nonzero";
-  EXPECT_NE(ids1[2], ids2[1])
-      << "First and second input have different interactionIds";
-  EXPECT_EQ(ids2[2], 0u) << "Third keyup has zero interactionId";
-  EXPECT_EQ(ids2[3], 0u) << "Fourth keyup has zero interactionId";
 
   // Select a composed input and finish, with duration of 85.
   std::vector<EventForInteraction> events3 = {
+      {event_type_names::kCompositionupdate, std::nullopt, std::nullopt},
       {event_type_names::kInput, std::nullopt, std::nullopt, GetTimeStamp(300),
        GetTimeStamp(385)},
       {event_type_names::kCompositionend, std::nullopt, std::nullopt}};
   std::vector<uint32_t> ids3 = SimulateInteractionIds(events3);
-  EXPECT_GT(ids3[0], 0u) << "Third input has nonzero interactionId";
-  EXPECT_NE(ids1[2], ids3[0])
-      << "First and third inputs have different interactionIds";
-  EXPECT_NE(ids2[1], ids3[0])
-      << "Second and third inputs have different interactionIds";
 
-  CheckUKMValues({{66, 66, UserInteractionType::kKeyboard},
-                  {51, 51, UserInteractionType::kKeyboard},
-                  {85, 85, UserInteractionType::kKeyboard}});
+  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";
+
+    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}});
+  }
 }
 
 // Tests Android smart suggestions (similar to Android Chinese).
-TEST_F(InteractionIdTest, SmartSuggestion) {
+TEST_P(InteractionIdTest, SmartSuggestion) {
   // Insert "A" with a duration of 9.
   std::vector<EventForInteraction> events1 = {
       {event_type_names::kKeydown, 229, std::nullopt, GetTimeStamp(0),
        GetTimeStamp(16)},
       {event_type_names::kCompositionstart, std::nullopt, std::nullopt},
+      {event_type_names::kCompositionupdate, std::nullopt, std::nullopt},
       {event_type_names::kInput, std::nullopt, std::nullopt, GetTimeStamp(0),
        GetTimeStamp(9)},
       {event_type_names::kKeyup, 229, std::nullopt, GetTimeStamp(0),
        GetTimeStamp(16)}};
   std::vector<uint32_t> ids1 = SimulateInteractionIds(events1);
-  EXPECT_GT(ids1[2], 0u) << "First input nonzero";
 
   // Compose to "At" with a duration of 14.
   std::vector<EventForInteraction> events2 = {
+      {event_type_names::kCompositionupdate, std::nullopt, std::nullopt},
       {event_type_names::kInput, std::nullopt, std::nullopt, GetTimeStamp(100),
        GetTimeStamp(114)},
       {event_type_names::kCompositionend, std::nullopt, std::nullopt}};
   std::vector<uint32_t> ids2 = SimulateInteractionIds(events2);
-  EXPECT_GT(ids2[0], 0u) << "Second input nonzero";
-  EXPECT_NE(ids1[2], ids2[1])
-      << "First and second input have different interactionIds";
 
   // Add "the". No composition so need to consider the keydown and keyup.
   // Max duration of 43 and total duration of 70
@@ -1825,16 +1901,43 @@
       {event_type_names::kKeyup, 229, std::nullopt, GetTimeStamp(235),
        GetTimeStamp(270)}};
   std::vector<uint32_t> ids3 = SimulateInteractionIds(events3);
-  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}});
+  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";
+
+    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}});
+  }
 }
 
-TEST_F(InteractionIdTest, TapWithoutClick) {
+TEST_P(InteractionIdTest, TapWithoutClick) {
   std::vector<EventForInteraction> events = {
       {event_type_names::kPointerdown, std::nullopt, 1, GetTimeStamp(100),
        GetTimeStamp(140)},
@@ -1855,7 +1958,7 @@
   CheckUKMValues({{40, 50, UserInteractionType::kTapOrClick}});
 }
 
-TEST_F(InteractionIdTest, PointerupClick) {
+TEST_P(InteractionIdTest, PointerupClick) {
   std::vector<EventForInteraction> events = {
       {event_type_names::kPointerup, std::nullopt, 1, GetTimeStamp(100),
        GetTimeStamp(140)},
@@ -1869,7 +1972,7 @@
   CheckUKMValues({{40, 50, UserInteractionType::kTapOrClick}});
 }
 
-TEST_F(InteractionIdTest, JustClick) {
+TEST_P(InteractionIdTest, JustClick) {
   // Hitting enter on a keyboard may cause just a trusted click event.
   std::vector<EventForInteraction> events = {
       {event_type_names::kClick, std::nullopt, -1, GetTimeStamp(120),
@@ -1881,7 +1984,7 @@
   CheckUKMValues({{30, 30, UserInteractionType::kTapOrClick}});
 }
 
-TEST_F(InteractionIdTest, PointerdownClick) {
+TEST_P(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),
@@ -1896,7 +1999,7 @@
   CheckUKMValues({{40, 50, UserInteractionType::kTapOrClick}});
 }
 
-TEST_F(InteractionIdTest, MultiTouch) {
+TEST_P(InteractionIdTest, MultiTouch) {
   // In multitouch, we report an interaction per pointerId. We do not see
   // clicks.
   std::vector<EventForInteraction> events = {
@@ -1921,7 +2024,7 @@
                   {50, 60, UserInteractionType::kTapOrClick}});
 }
 
-TEST_F(InteractionIdTest, ClickIncorrectPointerId) {
+TEST_P(InteractionIdTest, ClickIncorrectPointerId) {
   // On mobile, in cases where touchstart is skipped, click does not get the
   // correct pointerId. See crbug.com/1264930 for more details.
   std::vector<EventForInteraction> events = {
@@ -1937,4 +2040,6 @@
   CheckUKMValues({{40, 60, UserInteractionType::kTapOrClick}});
 }
 
+INSTANTIATE_TEST_SUITE_P(All, InteractionIdTest, ::testing::Bool());
+
 }  // namespace blink
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index acdcb5c..7c39bf64 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -5435,6 +5435,7 @@
 crbug.com/1454956 [ Linux ] external/wpt/mediacapture-record/MediaRecorder-canvas-media-source.https.html [ Failure ]
 crbug.com/1454956 [ Mac ] virtual/gpu/external/wpt/mediacapture-record/MediaRecorder-canvas-media-source.https.html [ Failure ]
 crbug.com/1466578 external/wpt/event-timing/event-click-visibilitychange.html [ Failure Pass ]
+crbug.com/1456384 external/wpt/event-timing/interactionid-keypress.html [ Failure Pass ]
 crbug.com/1446711 [ Mac11 Release ] media/controls/playback-speed-menu.html [ Timeout ]
 crbug.com/1446711 [ Mac12 Release ] media/controls/playback-speed-menu.html [ Timeout ]
 crbug.com/1446711 [ Win ] virtual/media-foundation-for-clear-dcomp/media/controls/accessibility-playback-speed-button.html [ Timeout ]
diff --git a/third_party/blink/web_tests/external/wpt/event-timing/interactionid-composition-manual.html b/third_party/blink/web_tests/external/wpt/event-timing/interactionid-composition-manual.html
new file mode 100644
index 0000000..2b4f2aa
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/event-timing/interactionid-composition-manual.html
@@ -0,0 +1,162 @@
+<!doctype html>
+<html>
+
+<head>
+    <style>
+        table,
+        td {
+            padding: 8px;
+            border: 1px solid black;
+        }
+    </style>
+</head>
+
+<body>
+    <title>Event Timing: interactionId composition events.</title>
+    <form>
+        <b> Select your Operating System from the list</b>
+        <select id="option" >
+            <option> ---Choose OS--- </option>
+            <option> Linux </option>
+            <option> Windows </option>
+        </select>
+        <p> Your selected OS is:
+            <input type="text" id="os" size="20">
+        </p>
+    </form>
+    <pre>
+    Steps:
+    1) Open <b id = "IMEtype"></b> IME and select Hiragana input method.
+    2) Click at the above textbox and then type 'a' using keyboard.
+    3) Press the '{Enter}' key to complete the IME composition.
+    4) <a href="interactionid-composition-manual.html">Click here</a> to test again if not following the steps exactly.
+
+    <textarea id='test' placeholder="enter 'a'"></textarea>
+
+Expected Result:
+    The test is successful when the sentence "PASS Event Timing: interactionId composition events" is displayed
+    at the bottom of the page after completing all the steps. If there is an indicated Harness Error next to the sentence, the test failed.
+    Moreover, the event log table below provides a summary of the keyboard events processed throughout the test.
+    Here is a breakdown of the columns in the table:
+
+    1. <strong>InteractionId</strong>: Identifies the specific interaction to which an event belongs.
+    2. <strong>EventType</strong>: Specifies the type of event that occurred during a particular interaction. There are
+    seven possible event types:
+    - 'keydown'
+    - 'keypress'
+    - 'input'
+    - 'keyup'
+    - 'compositionupdate'
+    - 'compositionstart'
+    - 'compositionend'
+    3. <strong>NumberOfEvents</strong>: Indicates the number of times a particular type of event was recorded in a single interaction.
+
+</pre>
+
+    <table id="eventLogTable">
+        <tr>
+            <td>InteractionId</td>
+            <td>Event Type</td>
+            <td>Number of Events</td>
+        </tr>
+    </table>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src=resources/event-timing-test-utils.js></script>
+    <script>
+        setup({ explicit_timeout: true, explicit_done: true });
+
+        function dropdownMenu() {
+            var list = document.getElementById("option");
+            document.getElementById("os").value = list.options[list.selectedIndex].text;
+            if (document.getElementById("os").value == "Linux") {
+                document.getElementById("IMEtype").textContent = "Japanese - Mozc";
+            }
+            else if (document.getElementById("os").value == "Windows") {
+                document.getElementById("IMEtype").textContent = "Japanese Microsoft";
+            }
+        }
+
+        function logEventSummary(interactionId, eventType, nrOfEvents) {
+
+            var table = document.getElementById("eventLogTable");
+            var row = table.insertRow();
+
+            // Add values to the table
+            var cell = row.insertCell();
+            cell.innerHTML = interactionId;
+            cell = row.insertCell();
+            cell.innerHTML = eventType;
+            cell = row.insertCell();
+            cell.innerHTML = nrOfEvents;
+        }
+
+        let observedEntries = [];
+        let map = new Map();
+        const events = ['keydown', 'keypress', 'input', 'keyup', 'compositionupdate', 'compositionstart', 'compositionend'];
+
+
+        function eventsForCheck(entry) {
+            if (events.includes(entry.name)) {
+                if (map.has(entry.name)) {
+                    map.get(entry.name).push({ interactionId: entry.interactionId, startTime: entry.startTime });
+                    return true;
+                } else {
+                    map.set(entry.name, [{ interactionId: entry.interactionId, startTime: entry.startTime }]);
+                    return true;
+                }
+            }
+
+            return false;
+        }
+
+        test(function () {
+            assert_implements(window.PerformanceEventTiming, 'Event Timing is not supported.');
+            new PerformanceObserver(entryList => {
+                observedEntries = observedEntries.concat(entryList.getEntries().filter(eventsForCheck));
+
+                if (!observedEntries.find(entry => entry.name === "compositionend"))
+                    return;
+
+                assert_equals(map.get('compositionstart')[0].interactionId, 0, 'Compositionstart should not have an interactionId');
+                logEventSummary(map.get('compositionstart')[0].interactionId, "compositionstart", 1);
+                assert_equals(map.get("input").length, map.get("compositionupdate").length, "For every input there should be exactly one compositionupdate");
+
+                // Create a Set to track seen input values
+                const seenInteractionIds = new Set();
+
+                map.get("input").forEach(value => {
+                    assert_false(seenInteractionIds.has(value.interactionId), "All Inputs shall have unique InteractionIds.");
+                    seenInteractionIds.add(value);
+                    assert_greater_than(value.interactionId, 0, 'Input should have an interactionId greater than 0');
+                    const filteredArrayKeydowns = map.get('keydown').filter(interactionId => interactionId === value.interactionId);
+                    const countKeydowns = filteredArrayKeydowns.length;
+                    logEventSummary(value.interactionId, "keydown", countKeydowns);
+                    assert_true((countKeydowns <= 1), "For each input there should be no more than 1 keydown.");
+
+                    logEventSummary(value.interactionId, "compositionupdate", 1);
+                    logEventSummary(value.interactionId, "input", 1);
+
+                    const filteredArrayKeyups = map.get('keyup').filter(interactionId => interactionId === value.interactionId);
+                    const countKeyups = filteredArrayKeyups.length;
+                    logEventSummary(value.interactionId, "keyup", countKeyups);
+
+                    filteredArrayKeyups.forEach(keyupEntry => {
+                        assert_true(keyupEntry.startTime > value.startTime, 'Keyup start time should be greater than input start time');
+                    });
+
+                });
+
+                assert_equals(map.get('compositionend')[0].interactionId, 0, 'Compositionend should not have an interactionId');
+                logEventSummary(map.get('compositionstart')[0].interactionId, "compositionend", 1);
+                done();
+                observedEntries = [];
+            }).observe({ type: "event" });
+
+            addListeners(document.getElementById('test'), events);
+        });
+
+    </script>
+</body>
+
+</html>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/event-timing/interactionid-keypress.html b/third_party/blink/web_tests/external/wpt/event-timing/interactionid-keypress.html
new file mode 100644
index 0000000..a6f2009
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/event-timing/interactionid-keypress.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html>
+<meta charset=utf-8 />
+<meta name="timeout" content="long">
+<title>Event Timing: interactionId-keypress.</title>
+<textarea id='test'></textarea>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=/resources/testdriver.js></script>
+<script src=/resources/testdriver-actions.js></script>
+<script src=/resources/testdriver-vendor.js></script>
+<script src=resources/event-timing-test-utils.js></script>
+
+<script>
+  let observedEntries = [];
+  let map = new Map();
+          const events = ['keydown','keypress','keyup'];
+
+  async_test(function (t) {
+    assert_implements(window.PerformanceEventTiming, 'Event Timing is not supported.');
+
+    new PerformanceObserver(t.step_func(entryList => {
+      observedEntries = observedEntries.concat(entryList.getEntries().filter(filterAndAddToMap(events, map)));
+      if (observedEntries.length < 3)
+        return;
+      assert_greater_than(map.get('keypress'), 0, 'Should have a non-trivial interactionId');
+      assert_equals(map.get('keydown'), map.get('keypress'), 'The keydown and the keypress should have the same interactionId');
+      assert_equals(map.get('keyup'), map.get('keypress'), 'The keyup and the keypress should have the same interactionId');
+      assert_equals('t', document.getElementById('test').value);
+      t.done();
+    })).observe({ type: "event" });
+
+    addListenersAndPress(document.getElementById('test'), 't', events);
+  }, "Event Timing: compare event timing interactionId for keypress.");
+
+
+</script>
+
+</html>
\ No newline at end of file