Commit 7d25a90d authored by Nicolás Peña Moreno's avatar Nicolás Peña Moreno Committed by Commit Bot

[EventTiming] Implement EventCounts

This CL adds support for performance.eventCounts, gated behind the
EventTiming flag. EventCounts is maplike and enables computing the
number of input events that have occurred for any of the supported event
types. It is populated on WindowPerformance::RegisterEventTiming so that
it matches when timing with when the PerformanceEventTiming entry is
created for slow events. The spec for this is here:
https://wicg.github.io/event-timing/#sec-event-counts

Bug: 543598

Change-Id: I95bd8b977556557811a1eff8d9fc5d106af0d2b1
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2125019
Commit-Queue: Nicolás Peña Moreno <npm@chromium.org>
Reviewed-by: default avatarYoav Weiss <yoavweiss@chromium.org>
Cr-Commit-Position: refs/heads/master@{#755468}
parent 32ad6790
......@@ -614,6 +614,7 @@ static_idl_files_in_core = get_path_info(
"//third_party/blink/renderer/core/testing/union_types_test.idl",
"//third_party/blink/renderer/core/testing/worker_internals.idl",
"//third_party/blink/renderer/core/timing/dom_high_res_time_stamp.idl",
"//third_party/blink/renderer/core/timing/event_counts.idl",
"//third_party/blink/renderer/core/timing/internals_profiler.idl",
"//third_party/blink/renderer/core/timing/largest_contentful_paint.idl",
"//third_party/blink/renderer/core/timing/layout_shift.idl",
......
......@@ -443,6 +443,7 @@ core_idl_files =
"svg/svg_unit_types.idl",
"svg/svg_use_element.idl",
"svg/svg_view_element.idl",
"timing/event_counts.idl",
"timing/largest_contentful_paint.idl",
"timing/layout_shift.idl",
"timing/memory_info.idl",
......
......@@ -8,6 +8,8 @@ blink_core_sources("timing") {
sources = [
"dom_window_performance.cc",
"dom_window_performance.h",
"event_counts.cc",
"event_counts.h",
"event_timing.cc",
"event_timing.h",
"largest_contentful_paint.cc",
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/core/timing/event_counts.h"
namespace blink {
class EventCountsIterationSource final
: public PairIterable<AtomicString, unsigned>::IterationSource {
public:
explicit EventCountsIterationSource(const EventCounts& map)
: map_(map), iterator_(map_->Map().begin()) {}
bool Next(ScriptState* script_state,
AtomicString& map_key,
unsigned& map_value,
ExceptionState&) override {
if (iterator_ == map_->Map().end())
return false;
map_key = iterator_->key;
map_value = iterator_->value;
++iterator_;
return true;
}
void Trace(Visitor* visitor) override {
visitor->Trace(map_);
PairIterable<AtomicString, unsigned>::IterationSource::Trace(visitor);
}
private:
// Needs to be kept alive while we're iterating over it.
const Member<const EventCounts> map_;
HashMap<AtomicString, unsigned>::const_iterator iterator_;
};
void EventCounts::Add(const AtomicString& event_type) {
auto iterator = event_count_map_.find(event_type);
if (iterator == event_count_map_.end()) {
event_count_map_.insert(event_type, 1u);
} else {
iterator->value++;
}
}
PairIterable<AtomicString, unsigned>::IterationSource*
EventCounts::StartIteration(ScriptState*, ExceptionState&) {
return MakeGarbageCollected<EventCountsIterationSource>(*this);
}
bool EventCounts::GetMapEntry(ScriptState*,
const AtomicString& key,
unsigned& value,
ExceptionState&) {
auto it = event_count_map_.find(key);
if (it == event_count_map_.end())
return false;
value = it->value;
return true;
}
} // namespace blink
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_TIMING_EVENT_COUNTS_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_TIMING_EVENT_COUNTS_H_
#include "third_party/blink/renderer/bindings/core/v8/maplike.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
#include "third_party/blink/renderer/platform/wtf/text/atomic_string.h"
namespace blink {
class EventCounts final : public ScriptWrappable,
public Maplike<AtomicString, unsigned> {
DEFINE_WRAPPERTYPEINFO();
public:
EventCounts() = default;
const HashMap<AtomicString, unsigned>& Map() const {
return event_count_map_;
}
// IDL attributes / methods
uint32_t size() const { return event_count_map_.size(); }
void Add(const AtomicString& event_type);
void Trace(Visitor* visitor) override { ScriptWrappable::Trace(visitor); }
private:
// Maplike implementation.
PairIterable<AtomicString, unsigned>::IterationSource* StartIteration(
ScriptState*,
ExceptionState&) override;
bool GetMapEntry(ScriptState*,
const AtomicString& key,
unsigned& value,
ExceptionState&) override;
HashMap<AtomicString, unsigned> event_count_map_;
};
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_MODULES_KEYBOARD_KEYBOARD_event_count_map_H_
[Exposed=Window, RuntimeEnabled=EventTiming]
interface EventCounts {
readonly maplike<DOMString, unsigned long long>;
};
......@@ -16,6 +16,7 @@
#include "third_party/blink/renderer/core/timing/performance_event_timing.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
namespace blink {
namespace {
const base::TickClock* g_clock_for_testing = nullptr;
......@@ -23,9 +24,6 @@ static base::TimeTicks Now() {
return g_clock_for_testing ? g_clock_for_testing->NowTicks()
: base::TimeTicks::Now();
}
} // namespace
namespace blink {
bool ShouldLogEvent(const Event& event) {
return event.type() == event_type_names::kPointerdown ||
......@@ -36,11 +34,21 @@ bool ShouldLogEvent(const Event& event) {
}
bool IsEventTypeForEventTiming(const Event& event) {
return (IsA<MouseEvent>(event) || IsA<PointerEvent>(event) ||
// Include only trusted events of certain kinds. Explicitly excluding input
// events that are considered continuous: event types for which the user agent
// may have timer-based dispatch under certain conditions. These are excluded
// since EventCounts cannot be used to properly computed percentiles on those.
// See spec: https://wicg.github.io/event-timing/#sec-events-exposed
return event.isTrusted() &&
(IsA<MouseEvent>(event) || IsA<PointerEvent>(event) ||
IsA<TouchEvent>(event) || IsA<KeyboardEvent>(event) ||
IsA<WheelEvent>(event) || event.IsInputEvent() ||
event.IsCompositionEvent()) &&
event.isTrusted();
event.IsCompositionEvent() || event.IsDragEvent()) &&
event.type() != event_type_names::kMousemove &&
event.type() != event_type_names::kPointermove &&
event.type() != event_type_names::kTouchmove &&
event.type() != event_type_names::kWheel &&
event.type() != event_type_names::kDrag;
}
bool ShouldReportForEventTiming(WindowPerformance* performance) {
......@@ -55,6 +63,8 @@ bool ShouldReportForEventTiming(WindowPerformance* performance) {
performance->HasObserverFor(PerformanceEntry::kEvent));
}
} // namespace
EventTiming::EventTiming(base::TimeTicks processing_start,
base::TimeTicks event_timestamp,
WindowPerformance* performance)
......
......@@ -148,6 +148,10 @@ MemoryInfo* Performance::memory() const {
return nullptr;
}
EventCounts* Performance::eventCounts() {
return nullptr;
}
namespace {
bool IsMeasureMemoryAvailable(ScriptState* script_state) {
......
......@@ -58,6 +58,7 @@ class TickClock;
namespace blink {
class PerformanceMarkOptions;
class EventCounts;
class ExceptionState;
class LargestContentfulPaint;
class LayoutShift;
......@@ -97,6 +98,7 @@ class CORE_EXPORT Performance : public EventTargetWithInlineData {
virtual MemoryInfo* memory() const;
virtual ScriptPromise measureMemory(ScriptState*,
ExceptionState& exception_state) const;
virtual EventCounts* eventCounts();
// Reduce the resolution to prevent timing attacks. See:
// http://www.w3.org/TR/hr-time-2/#privacy-security
......
......@@ -77,5 +77,8 @@ interface Performance : EventTarget {
// https://github.com/WICG/js-self-profiling/
[MeasureAs=JSSelfProfiling, CallWith=ScriptState, RuntimeEnabled=ExperimentalJSProfiler, RaisesException] Promise<Profiler> profile(ProfilerInitOptions options);
// Event Timing
[Exposed=Window, SameObject, SaveSameObject, RuntimeEnabled=EventTiming] readonly attribute EventCounts eventCounts;
[CallWith=ScriptState, ImplementedAs=toJSONForBinding] object toJSON();
};
......@@ -227,6 +227,7 @@ void WindowPerformance::BuildJSONValue(V8ObjectBuilder& builder) const {
void WindowPerformance::Trace(Visitor* visitor) {
visitor->Trace(event_timings_);
visitor->Trace(first_pointer_down_event_timing_);
visitor->Trace(event_counts_);
visitor->Trace(navigation_);
visitor->Trace(timing_);
Performance::Trace(visitor);
......@@ -341,6 +342,9 @@ void WindowPerformance::RegisterEventTiming(const AtomicString& event_type,
if (!GetFrame())
return;
if (!event_counts_)
event_counts_ = MakeGarbageCollected<EventCounts>();
event_counts_->Add(event_type);
PerformanceEventTiming* entry = PerformanceEventTiming::Create(
event_type, MonotonicTimeToDOMHighResTimeStamp(start_time),
MonotonicTimeToDOMHighResTimeStamp(processing_start),
......@@ -440,6 +444,13 @@ void WindowPerformance::AddLayoutShiftValue(double value,
AddLayoutShiftBuffer(*entry);
}
EventCounts* WindowPerformance::eventCounts() {
DCHECK(RuntimeEnabledFeatures::EventTimingEnabled(GetExecutionContext()));
if (!event_counts_)
event_counts_ = MakeGarbageCollected<EventCounts>();
return event_counts_;
}
void WindowPerformance::OnLargestContentfulPaintUpdated(
base::TimeTicks paint_time,
uint64_t paint_size,
......
......@@ -36,6 +36,7 @@
#include "third_party/blink/renderer/core/core_export.h"
#include "third_party/blink/renderer/core/execution_context/execution_context_lifecycle_observer.h"
#include "third_party/blink/renderer/core/frame/performance_monitor.h"
#include "third_party/blink/renderer/core/timing/event_counts.h"
#include "third_party/blink/renderer/core/timing/memory_info.h"
#include "third_party/blink/renderer/core/timing/performance.h"
#include "third_party/blink/renderer/core/timing/performance_navigation.h"
......@@ -61,6 +62,8 @@ class CORE_EXPORT WindowPerformance final : public Performance,
MemoryInfo* memory() const override;
EventCounts* eventCounts() override;
bool FirstInputDetected() const { return !!first_input_timing_; }
// This method creates a PerformanceEventTiming and if needed creates a swap
......@@ -122,6 +125,7 @@ class CORE_EXPORT WindowPerformance final : public Performance,
// |duration| has not been resolved.
HeapVector<Member<PerformanceEventTiming>> event_timings_;
Member<PerformanceEventTiming> first_pointer_down_event_timing_;
Member<EventCounts> event_counts_;
mutable Member<PerformanceNavigation> navigation_;
mutable Member<PerformanceTiming> timing_;
};
......
......@@ -654,7 +654,6 @@
},
{
name: "EventTiming",
origin_trial_feature_name: "EventTiming",
status: "experimental",
},
{
......
<!DOCTYPE html>
<html>
<meta charset=utf-8 />
<title>Event Timing: eventCounts.</title>
<div id='div'>Click me</div>
<button id='button'>Click me</div>
<script src=/resources/testharness.js></script>
<script src=/resources/testharnessreport.js></script>
<script src=/resources/testdriver.js></script>
<script src=/resources/testdriver-vendor.js></script>
<script src=resources/event-timing-test-utils.js></script>
<script>
promise_test( t => {
assert_precondition(window.EventCounts, "Event Counts isn't supported");
function testClicks(expectedCount, resolve) {
const clickCount = performance.eventCounts.get('click');
if (!clickCount || clickCount < expectedCount) {
t.step_timeout(function() {
testClicks(expectedCount, resolve);
}, 5);
return;
}
assert_equals(clickCount, expectedCount,'Incorrect click count.');
assert_equals(performance.eventCounts.get('mousedown'), expectedCount, 'Incorrect mousedown count');
assert_equals(performance.eventCounts.get('mouseup'), expectedCount, 'Incorrect mouseup count.');
assert_equals(performance.eventCounts.get('mouseover'), expectedCount, 'Incorrect mouseover count.');
// There should be no other input type recorded.
assert_equals(performance.eventCounts.size, 4, 'There should only be 4 types observed.');
resolve();
}
function promiseClicks(expectedCount) {
return new Promise(resolve => {
testClicks(1, resolve)
});
}
assert_equals(performance.eventCounts.size, 0);
test_driver.click(document.getElementById('div'));
return promiseClicks(1).then(() => {
test_driver.click(document.getElementById('button'));
return promiseClicks(2);
}).then(() => {
test_driver.click(document.getElementById('div'));
return promiseClicks(3);
});
})
</script>
</html>
This is a testharness.js-based test.
PASS idl_test setup
PASS idl_test validation
PASS Partial interface Performance: original interface defined
PASS Partial interface Performance: valid exposure set
PASS Partial interface Performance: member names are unique
PASS Partial interface Performance[2]: member names are unique
PASS PerformanceEventTiming interface: existence and properties of interface object
PASS PerformanceEventTiming interface object length
PASS PerformanceEventTiming interface object name
PASS PerformanceEventTiming interface: existence and properties of interface prototype object
PASS PerformanceEventTiming interface: existence and properties of interface prototype object's "constructor" property
PASS PerformanceEventTiming interface: existence and properties of interface prototype object's @@unscopables property
PASS PerformanceEventTiming interface: attribute processingStart
PASS PerformanceEventTiming interface: attribute processingEnd
PASS PerformanceEventTiming interface: attribute cancelable
PASS PerformanceEventTiming interface: operation toJSON()
FAIL EventCounts interface: existence and properties of interface object assert_own_property: self does not have own property "EventCounts" expected property "EventCounts" missing
FAIL EventCounts interface object length assert_own_property: self does not have own property "EventCounts" expected property "EventCounts" missing
FAIL EventCounts interface object name assert_own_property: self does not have own property "EventCounts" expected property "EventCounts" missing
FAIL EventCounts interface: existence and properties of interface prototype object assert_own_property: self does not have own property "EventCounts" expected property "EventCounts" missing
FAIL EventCounts interface: existence and properties of interface prototype object's "constructor" property assert_own_property: self does not have own property "EventCounts" expected property "EventCounts" missing
FAIL EventCounts interface: existence and properties of interface prototype object's @@unscopables property assert_own_property: self does not have own property "EventCounts" expected property "EventCounts" missing
FAIL Performance interface: attribute eventCounts assert_true: The prototype object must have a property "eventCounts" expected true got false
FAIL Performance interface: performance must inherit property "eventCounts" with the proper type assert_inherits: property "eventCounts" not found in prototype chain
Harness: the test ran to completion.
......@@ -6,6 +6,13 @@ window.performance.clearMarks [function]
window.performance.clearMeasures [function]
window.performance.clearResourceTimings [function]
window.performance.dispatchEvent [function]
window.performance.eventCounts [object EventCounts]
window.performance.eventCounts.entries [function]
window.performance.eventCounts.forEach [function]
window.performance.eventCounts.get [function]
window.performance.eventCounts.has [function]
window.performance.eventCounts.keys [function]
window.performance.eventCounts.values [function]
window.performance.getEntries [function]
window.performance.getEntriesByName [function]
window.performance.getEntriesByType [function]
......
......@@ -2343,6 +2343,17 @@ interface Event
method stopPropagation
setter cancelBubble
setter returnValue
interface EventCounts
attribute @@toStringTag
getter size
method @@iterator
method constructor
method entries
method forEach
method get
method has
method keys
method values
interface EventSource : EventTarget
attribute @@toStringTag
attribute CLOSED
......@@ -5878,6 +5889,7 @@ interface PaymentResponse : EventTarget
setter onpayerdetailchange
interface Performance : EventTarget
attribute @@toStringTag
getter eventCounts
getter memory
getter navigation
getter onresourcetimingbufferfull
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment