Skip to content

Commit

Permalink
LibWeb: Implement PerformanceEventTiming computeInteractionId
Browse files Browse the repository at this point in the history
Implement the method and also add the extensions to the Window
class.
  • Loading branch information
noahmbright committed Oct 10, 2024
1 parent 8d25cf1 commit 7a40c9a
Show file tree
Hide file tree
Showing 3 changed files with 272 additions and 13 deletions.
244 changes: 239 additions & 5 deletions Userland/Libraries/LibWeb/EventTiming/PerformanceEventTiming.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,47 @@
#include <LibWeb/Bindings/PerformanceEventTimingPrototype.h>
#include <LibWeb/DOM/Event.h>
#include <LibWeb/EventTiming/PerformanceEventTiming.h>
#include <LibWeb/HTML/Window.h>
#include <LibWeb/Page/InputEvent.h>
#include <LibWeb/PerformanceTimeline/EntryTypes.h>
#include <LibWeb/PerformanceTimeline/PerformanceObserver.h>
#include <LibWeb/UIEvents/InputEvent.h>
#include <LibWeb/UIEvents/KeyboardEvent.h>
#include <LibWeb/UIEvents/PointerEvent.h>

namespace Web::EventTiming {

static unsigned long long compute_interaction_id(DOM::Event const&);

JS_DEFINE_ALLOCATOR(PerformanceEventTiming);

// https://www.w3.org/TR/event-timing/#sec-init-event-timing
PerformanceEventTiming::PerformanceEventTiming(JS::Realm& realm, String const& name, HighResolutionTime::DOMHighResTimeStamp start_time, HighResolutionTime::DOMHighResTimeStamp duration,
DOM::Event const& event, HighResolutionTime::DOMHighResTimeStamp processing_start, unsigned long long interaction_id)
DOM::Event const& event, HighResolutionTime::DOMHighResTimeStamp processing_start)
: PerformanceTimeline::PerformanceEntry(realm, name, start_time, duration)
, m_entry_type(PerformanceTimeline::EntryTypes::event)
, m_start_time(event.time_stamp())
, m_processing_start(processing_start)
, m_cancelable(event.cancelable())
, m_interaction_id(interaction_id)
//, m_interaction_id(compute_interaction_id(event))
{
m_interaction_id = compute_interaction_id(event);
}

// https://www.w3.org/TR/event-timing/#sec-increasing-interaction-count
static void increase_interaction_count(HTML::Window& window)
{
// 1. Increase window’s user interaction value value by a small number chosen by the user agent.
//
// picking lucky number 7 arbitrarily
// interaction value is initialized to a random between 100 and 10000, so I guess 7 is "small"?
window.increase_user_interaction_value(7);

// 2. Let interactionCount be window’s interactionCount.
auto interaction_count = window.user_interaction_value();

// 3. Set interactionCount to interactionCount + 1.
window.set_user_interaction_value(interaction_count + 1);
}

PerformanceEventTiming::~PerformanceEventTiming() = default;
Expand All @@ -37,12 +60,21 @@ FlyString const& PerformanceEventTiming::entry_type() const

HighResolutionTime::DOMHighResTimeStamp PerformanceEventTiming::processing_end() const
{
// The processingEnd attribute’s getter returns a timestamp captured at the end of the event dispatch
// algorithm. This is when event handlers have finished executing. It’s equal to processingStart when
// there are no such event handlers.
dbgln("FIXME: Implement PeformanceEventTiming processing_end()");
return 0;
}

HighResolutionTime::DOMHighResTimeStamp PerformanceEventTiming::processing_start() const
{
// The processingStart attribute’s getter returns a timestamp captured at the beginning of
// the event dispatch algorithm. This is when event handlers are about to be executed.
//
// https://dom.spec.whatwg.org/#concept-event-dispatch
// This is implemented in EventDispatcher::dispatch but not obvious
// where it captures a timestamp before launching the handlers
dbgln("FIXME: Implement PeformanceEventTiming processing_start()");
return 0;
}
Expand All @@ -54,14 +86,15 @@ bool PerformanceEventTiming::cancelable() const

JS::ThrowCompletionOr<JS::GCPtr<DOM::Node>> PerformanceEventTiming::target()
{
// The target attribute’s getter returns the associated event’s last target when such
// Node is not disconnected nor in the shadow DOM.
dbgln("FIXME: Implement PerformanceEventTiming::PeformanceEventTiming target()");
return nullptr;
}

unsigned long long PerformanceEventTiming::interaction_id()
unsigned long long PerformanceEventTiming::interaction_id() const
{
dbgln("FIXME: Implement PeformanceEventTiming interaction_id()");
return 0;
return m_interaction_id;
}

// https://www.w3.org/TR/event-timing/#sec-should-add-performanceeventtiming
Expand Down Expand Up @@ -113,6 +146,207 @@ Optional<u64> PerformanceEventTiming::max_buffer_size()
// else return 150;
}

static unsigned long long compute_interaction_id(DOM::Event const& event)
{
// some ad hoc verify casts are thrown in for each branch depending on the
// event type

// 1. If event’s isTrusted attribute value is false, return 0.
if (!event.is_trusted())
return 0;

// 2. Let type be event’s type attribute value.
auto const& type = event.type();

// 3. If type is not one among keyup, compositionstart, input, pointercancel, pointermove, pointerup, or click, return 0.
// FIXME: Any reason not to wrap these in an enum?
if (type != "keyup"
|| type != "compositionstart"
|| type != "input"
|| type != "pointercancel"
|| type != "pointermove"
|| type != "pointerup"
|| type != "click")
return 0;

// Note: keydown and pointerdown are handled in finalize event timing.

// 4. Let window be event’s relevant global object.
auto& object = event.realm().global_object();

// FIXME: Get the global object, but then run a bunch of algorithms this spec
// gives for windows. Throwing in an ad hoc cast
if (!is<HTML::Window>(object))
return 0;
auto& window = verify_cast<HTML::Window>(object);

// 5. Let pendingKeyDowns be window’s pending key downs.
auto& pending_key_downs = window.pending_key_downs();

// 6. Let pointerMap be window’s pointer interaction value map.
auto& pointer_map = window.pointer_interaction_value_map();

// 7. Let pointerIsDragSet be window’s pointer is drag set.
auto& pointer_is_drag_set = window.pointer_is_drag_set();

// 8. Let pendingPointerDowns be window’s pending pointer downs.
auto& pending_pointer_downs = window.pending_pointer_downs();

// 9. If type is keyup:
if (type == "keyup") {
// https://www.w3.org/TR/uievents/#dom-keyboardevent-iscomposing
auto const& keyup_event = verify_cast<UIEvents::KeyboardEvent>(event);

// 9.1. If event’s isComposing attribute value is true, return 0.
if (keyup_event.is_composing())
return 0;

// 9.2. Let code be event’s keyCode attribute value.
auto code = static_cast<int>(keyup_event.key_code());

// 9.3. If pendingKeyDowns[code] does not exist, return 0.
if (!pending_key_downs.contains(code))
return 0;

// 9.4. Let entry be pendingKeyDowns[code].
auto entry = pending_key_downs.get(code);

// 9.5. Increase interaction count on window.
increase_interaction_count(window);

// 9.6. Let interactionId be window’s user interaction value value.
auto interaction_id = window.user_interaction_value();

// 9.7. Set entry’s interactionId to interactionId.
entry->set_interaction_id(interaction_id);

// 9.8. Add entry to window’s entries to be queued.
window.entries_to_be_queued().append(entry.value());

// 9.9. Remove pendingKeyDowns[code].
pending_key_downs.remove(code);

// 9.10. Return interactionId.
return interaction_id;
}

// 10. If type is compositionstart:
if (type == "compositionstart") {
// 10.1. For each entry in the values of pendingKeyDowns:
for (auto& [_, entry] : pending_key_downs)
// 10.1.1 Append entry to window’s entries to be queued.
window.entries_to_be_queued().append(entry);

// 10.2. Clear pendingKeyDowns.
pending_key_downs.clear();

// 10.3. Return 0.
return 0;
}

// 11. If type is input:
if (type == "input") {

// 11.1 If event is not an instance of InputEvent, return 0.
// Note: this check is done to exclude Events for which the type is input but that are not about modified text content.
if (!is<UIEvents::InputEvent>(event))
return 0;

auto const& input_event = verify_cast<UIEvents::InputEvent>(event);

// 11.2 If event’s isComposing attribute value is false, return 0.
if (!input_event.is_composing())
return 0;

// 11.3 Increase interaction count on window.
increase_interaction_count(window);

// 11.4 Return window’s user interaction value.
return window.user_interaction_value();
}

// 12. Otherwise (type is pointercancel, pointermove, pointerup, or click):
auto const& pointer_event = verify_cast<UIEvents::PointerEvent>(event);

// 12.1. Let pointerId be event’s pointerId attribute value.
auto pointer_id = pointer_event.pointer_id();

// 12.2. If type is click:
if (pointer_event.type() == "click") {
// 12.1.1. If pointerMap[pointerId] does not exist, return 0.
if (pointer_map.find(pointer_id) == pointer_map.end())
return 0;

// 12.1.2. Let value be pointerMap[pointerId].
auto value = pointer_map.get(pointer_id);

// 12.1.3. Remove pointerMap[pointerId].
pointer_map.remove(pointer_id);

// 12.1.4. Remove [pointerId] from pointerIsDragSet.
pointer_is_drag_set.remove(pointer_id);

// 12.1.5. Return value.
return value.value();
}

// 12.3. If type is pointermove:
if (pointer_event.type() == "pointermove") {
// 12.1. Add pointerId to pointerIsDragSet.
pointer_is_drag_set.set(pointer_id);

// 12.1. Return 0.
return 0;
}

// 12.4. Assert that type is pointerup or pointercancel.
VERIFY(type == "pointerup" || type == "pointercancel");

// 12.5. If pendingPointerDowns[pointerId] does not exist, return 0.
if (pending_pointer_downs.find(pointer_id) == pending_pointer_downs.end())
return 0;

// 12.6. Let pointerDownEntry be pendingPointerDowns[pointerId].
// 12.7. Assert that pointerDownEntry is a PerformanceEventTiming entry.
auto pointer_down_entry = pending_pointer_downs.get(pointer_id);

// 12.8. If type is pointerup:
if (type == "pointerup") {

// 12.8.1. Let interactionType be "tap".
auto const* interaction_type = "tap";

// 12.8.2. If pointerIsDragSet contains [pointerId] exists, set interactionType to "drag".
if (pointer_is_drag_set.find(pointer_id) != pointer_is_drag_set.end())
interaction_type = "drag";

// This is unused?
(void)interaction_type;

// 12.8.3. Increase interaction count on window.
increase_interaction_count(window);

// 12.8.4. Set pointerMap[pointerId] to window’s user interaction value.
pointer_map.set(pointer_id, window.user_interaction_value());

// 12.8.5. Set pointerDownEntry’s interactionId to pointerMap[pointerId].
pointer_down_entry.value().set_interaction_id(pointer_map.get(pointer_id).value());
}

// 12.9. Append pointerDownEntry to window’s entries to be queued.
window.entries_to_be_queued().append(pointer_down_entry.value());

// 12.10. Remove pendingPointerDowns[pointerId].
pending_pointer_downs.remove(pointer_id);

// 12.11. If type is pointercancel, return 0.
if (type == "pointercancel")
return 0;

// 12.12. Return pointerMap[pointerId].
return pointer_map.get(pointer_id).value();
}

// https://w3c.github.io/timing-entrytypes-registry/#dfn-should-add-entry
PerformanceTimeline::ShouldAddEntry PerformanceEventTiming::should_add_entry(Optional<PerformanceTimeline::PerformanceObserverInit const&>) const
{
Expand Down
15 changes: 7 additions & 8 deletions Userland/Libraries/LibWeb/EventTiming/PerformanceEventTiming.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class PerformanceEventTiming final : public PerformanceTimeline::PerformanceEntr
HighResolutionTime::DOMHighResTimeStamp processing_end() const;
bool cancelable() const;
JS::ThrowCompletionOr<JS::GCPtr<DOM::Node>> target();
unsigned long long interaction_id();
unsigned long long interaction_id() const;

// from the registry:
// https://w3c.github.io/timing-entrytypes-registry/#dfn-availablefromtimeline
Expand All @@ -36,19 +36,20 @@ class PerformanceEventTiming final : public PerformanceTimeline::PerformanceEntr

virtual FlyString const& entry_type() const override;

void set_interaction_id(unsigned long long val) { m_interaction_id = val; }

private:
PerformanceEventTiming(
JS::Realm& realm, String const& name,
HighResolutionTime::DOMHighResTimeStamp start_time,
HighResolutionTime::DOMHighResTimeStamp duration, DOM::Event const& event,
HighResolutionTime::DOMHighResTimeStamp processing_start,
unsigned long long interaction_id);
HighResolutionTime::DOMHighResTimeStamp processing_start);

// m_entry_type defined here for both "event"s and "first-input"s
// this is the only PerformanceEntry that has two event types it could
// represent That complicates implementing the registry functions if they
// remain static
// this is the only PerformanceEntry that has two event types it could represent
// That complicates implementing the registry functions if they remain static
FlyString m_entry_type;

JS::GCPtr<DOM::EventTarget> m_event_target;
HighResolutionTime::DOMHighResTimeStamp m_start_time;
HighResolutionTime::DOMHighResTimeStamp m_processing_start;
Expand All @@ -63,8 +64,6 @@ class PerformanceEventTiming final : public PerformanceTimeline::PerformanceEntr
virtual void visit_edges(JS::Cell::Visitor&) override;

// FIXME: remaining algorithms described in this spec:
// https://www.w3.org/TR/event-timing/#sec-increasing-interaction-count
// https://www.w3.org/TR/event-timing/#sec-computing-interactionid
// https://www.w3.org/TR/event-timing/#sec-fin-event-timing
// https://www.w3.org/TR/event-timing/#sec-dispatch-pending
};
Expand Down
26 changes: 26 additions & 0 deletions Userland/Libraries/LibWeb/HTML/Window.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@
#pragma once

#include <AK/Badge.h>
#include <AK/QuickSelect.h>
#include <AK/RefPtr.h>
#include <AK/TypeCasts.h>
#include <LibJS/Heap/Heap.h>
#include <LibURL/URL.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/Bindings/WindowGlobalMixin.h>
#include <LibWeb/DOM/EventTarget.h>
#include <LibWeb/EventTiming/PerformanceEventTiming.h>
#include <LibWeb/Forward.h>
#include <LibWeb/HTML/AnimationFrameCallbackDriver.h>
#include <LibWeb/HTML/CrossOrigin/CrossOriginPropertyDescriptorMap.h>
Expand Down Expand Up @@ -235,6 +237,18 @@ class Window final

bool find(String const& string);

// https://www.w3.org/TR/event-timing/#sec-modifications-HTML
Vector<EventTiming::PerformanceEventTiming&>& entries_to_be_queued() { return m_entries_to_be_queued; }

void increase_user_interaction_value(int increment) { m_user_interaction_value += increment; }
void set_user_interaction_value(int value) { m_user_interaction_value = value; }
int user_interaction_value() const { return m_user_interaction_value; }

HashMap<int, EventTiming::PerformanceEventTiming>& pending_key_downs() { return m_pending_key_downs; }
HashMap<int, int>& pointer_interaction_value_map() { return m_pointer_interaction_value_map; }
HashTable<int>& pointer_is_drag_set() { return m_pointer_is_drag_set; }
HashMap<int, EventTiming::PerformanceEventTiming>& pending_pointer_downs() { return m_pending_pointer_downs; }

private:
explicit Window(JS::Realm&);

Expand Down Expand Up @@ -313,6 +327,18 @@ class Window final
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#dom-window-status
// When the Window object is created, the attribute must be set to the empty string. It does not do anything else.
String m_status;

// https://www.w3.org/TR/event-timing/#sec-modifications-HTML
Vector<EventTiming::PerformanceEventTiming&> m_entries_to_be_queued;
EventTiming::PerformanceEventTiming* m_pending_first_pointer_down { nullptr };
bool m_has_dispatched_input_event { false };
int m_user_interaction_value { (int)AK::random_int(100, 10000) };
HashMap<int, EventTiming::PerformanceEventTiming> m_pending_key_downs;
HashMap<int, int> m_pointer_interaction_value_map;
HashTable<int> m_pointer_is_drag_set;
HashMap<int, EventTiming::PerformanceEventTiming> m_pending_pointer_downs;
HashMap<FlyString, unsigned> m_event_counts;
int m_interaction_count;
};

void run_animation_frame_callbacks(DOM::Document&, double now);
Expand Down

0 comments on commit 7a40c9a

Please sign in to comment.