1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-31 15:38:10 +00:00

LibWeb: Implement PerformanceObserver

This commit is contained in:
Luke Wilde 2023-08-25 01:26:47 +01:00 committed by Andreas Kling
parent 5055883b9f
commit af2886449a
22 changed files with 793 additions and 57 deletions

View file

@ -23,8 +23,11 @@
#include <LibWeb/HTML/Timer.h>
#include <LibWeb/HTML/Window.h>
#include <LibWeb/HTML/WindowOrWorkerGlobalScope.h>
#include <LibWeb/HighResolutionTime/SupportedPerformanceTypes.h>
#include <LibWeb/Infra/Base64.h>
#include <LibWeb/PerformanceTimeline/EntryTypes.h>
#include <LibWeb/PerformanceTimeline/PerformanceObserver.h>
#include <LibWeb/PerformanceTimeline/PerformanceObserverEntryList.h>
#include <LibWeb/UserTiming/PerformanceMark.h>
#include <LibWeb/UserTiming/PerformanceMeasure.h>
#include <LibWeb/WebIDL/AbstractOperations.h>
@ -35,11 +38,6 @@ namespace Web::HTML {
WindowOrWorkerGlobalScopeMixin::~WindowOrWorkerGlobalScopeMixin() = default;
// Please keep these in alphabetical order based on the entry type :^)
#define ENUMERATE_SUPPORTED_PERFORMANCE_ENTRY_TYPES \
__ENUMERATE_SUPPORTED_PERFORMANCE_ENTRY_TYPES(PerformanceTimeline::EntryTypes::mark, UserTiming::PerformanceMark) \
__ENUMERATE_SUPPORTED_PERFORMANCE_ENTRY_TYPES(PerformanceTimeline::EntryTypes::measure, UserTiming::PerformanceMeasure)
void WindowOrWorkerGlobalScopeMixin::initialize(JS::Realm&)
{
#define __ENUMERATE_SUPPORTED_PERFORMANCE_ENTRY_TYPES(entry_type, cpp_class) \
@ -58,6 +56,10 @@ void WindowOrWorkerGlobalScopeMixin::visit_edges(JS::Cell::Visitor& visitor)
{
for (auto& it : m_timers)
visitor.visit(it.value);
for (auto& observer : m_registered_performance_observer_objects)
visitor.visit(observer);
for (auto& entry : m_performance_entry_buffer_map)
entry.value.visit_edges(visitor);
}
// https://html.spec.whatwg.org/multipage/webappapis.html#dom-origin
@ -301,11 +303,10 @@ PerformanceTimeline::PerformanceEntryTuple& WindowOrWorkerGlobalScopeMixin::rele
}
// https://www.w3.org/TR/performance-timeline/#dfn-queue-a-performanceentry
WebIDL::ExceptionOr<void> WindowOrWorkerGlobalScopeMixin::queue_performance_entry(JS::NonnullGCPtr<PerformanceTimeline::PerformanceEntry> new_entry)
void WindowOrWorkerGlobalScopeMixin::queue_performance_entry(JS::NonnullGCPtr<PerformanceTimeline::PerformanceEntry> new_entry)
{
auto& vm = new_entry->vm();
// FIXME: 1. Let interested observers be an initially empty set of PerformanceObserver objects.
// 1. Let interested observers be an initially empty set of PerformanceObserver objects.
Vector<JS::Handle<PerformanceTimeline::PerformanceObserver>> interested_observers;
// 2. Let entryType be newEntrys entryType value.
auto const& entry_type = new_entry->entry_type();
@ -313,14 +314,31 @@ WebIDL::ExceptionOr<void> WindowOrWorkerGlobalScopeMixin::queue_performance_entr
// 3. Let relevantGlobal be newEntry's relevant global object.
// NOTE: Already is `this`.
// FIXME: 4. For each registered performance observer regObs in relevantGlobal's list of registered performance observer
// objects:
// 1. If regObs's options list contains a PerformanceObserverInit options whose entryTypes member includes entryType
// or whose type member equals to entryType:
// 1. If should add entry with newEntry and options returns true, append regObs's observer to interested observers.
// 4. For each registered performance observer regObs in relevantGlobal's list of registered performance observer
// objects:
for (auto const& registered_observer : m_registered_performance_observer_objects) {
// 1. If regObs's options list contains a PerformanceObserverInit options whose entryTypes member includes entryType
// or whose type member equals to entryType:
auto iterator = registered_observer->options_list().find_if([&entry_type](PerformanceTimeline::PerformanceObserverInit const& entry) {
if (entry.entry_types.has_value())
return entry.entry_types->contains_slow(String::from_utf8(entry_type).release_value_but_fixme_should_propagate_errors());
// FIXME: 5. For each observer in interested observers:
// 1. Append newEntry to observer's observer buffer.
VERIFY(entry.type.has_value());
return entry.type.value() == entry_type;
});
if (!iterator.is_end()) {
// 1. If should add entry with newEntry and options returns true, append regObs's observer to interested observers.
if (new_entry->should_add_entry(*iterator) == PerformanceTimeline::ShouldAddEntry::Yes)
interested_observers.append(registered_observer);
}
}
// 5. For each observer in interested observers:
for (auto const& observer : interested_observers) {
// 1. Append newEntry to observer's observer buffer.
observer->append_to_observer_buffer({}, new_entry);
}
// 6. Let tuple be the relevant performance entry tuple of entryType and relevantGlobal.
auto& tuple = relevant_performance_entry_tuple(entry_type);
@ -334,10 +352,10 @@ WebIDL::ExceptionOr<void> WindowOrWorkerGlobalScopeMixin::queue_performance_entr
// 9. If isBufferFull is false and shouldAdd is true, append newEntry to tuple's performance entry buffer.
if (!is_buffer_full && should_add == PerformanceTimeline::ShouldAddEntry::Yes)
TRY_OR_THROW_OOM(vm, tuple.performance_entry_buffer.try_append(JS::make_handle(new_entry)));
tuple.performance_entry_buffer.append(new_entry);
// FIXME: 10. Queue the PerformanceObserver task with relevantGlobal as input.
return {};
// 10. Queue the PerformanceObserver task with relevantGlobal as input.
queue_the_performance_observer_task();
}
void WindowOrWorkerGlobalScopeMixin::clear_performance_entry_buffer(Badge<HighResolutionTime::Performance>, FlyString const& entry_type)
@ -354,35 +372,6 @@ void WindowOrWorkerGlobalScopeMixin::remove_entries_from_performance_entry_buffe
});
}
// https://www.w3.org/TR/performance-timeline/#dfn-filter-buffer-by-name-and-type
static ErrorOr<Vector<JS::Handle<PerformanceTimeline::PerformanceEntry>>> filter_buffer_by_name_and_type(Vector<JS::Handle<PerformanceTimeline::PerformanceEntry>> const& buffer, Optional<String> name, Optional<String> type)
{
// 1. Let result be an initially empty list.
Vector<JS::Handle<PerformanceTimeline::PerformanceEntry>> result;
// 2. For each PerformanceEntry entry in buffer, run the following steps:
for (auto const& entry : buffer) {
// 1. If type is not null and if type is not identical to entry's entryType attribute, continue to next entry.
if (type.has_value() && type.value() != entry->entry_type())
continue;
// 2. If name is not null and if name is not identical to entry's name attribute, continue to next entry.
if (name.has_value() && name.value() != entry->name())
continue;
// 3. append entry to result.
TRY(result.try_append(entry));
}
// 3. Sort results's entries in chronological order with respect to startTime
quick_sort(result, [](auto const& left_entry, auto const& right_entry) {
return left_entry->start_time() < right_entry->start_time();
});
// 4. Return result.
return result;
}
// https://www.w3.org/TR/performance-timeline/#dfn-filter-buffer-map-by-name-and-type
ErrorOr<Vector<JS::Handle<PerformanceTimeline::PerformanceEntry>>> WindowOrWorkerGlobalScopeMixin::filter_buffer_map_by_name_and_type(Optional<String> name, Optional<String> type) const
{
@ -431,4 +420,111 @@ ErrorOr<Vector<JS::Handle<PerformanceTimeline::PerformanceEntry>>> WindowOrWorke
return result;
}
void WindowOrWorkerGlobalScopeMixin::register_performance_observer(Badge<PerformanceTimeline::PerformanceObserver>, JS::NonnullGCPtr<PerformanceTimeline::PerformanceObserver> observer)
{
m_registered_performance_observer_objects.set(observer, AK::HashSetExistingEntryBehavior::Keep);
}
void WindowOrWorkerGlobalScopeMixin::unregister_performance_observer(Badge<PerformanceTimeline::PerformanceObserver>, JS::NonnullGCPtr<PerformanceTimeline::PerformanceObserver> observer)
{
m_registered_performance_observer_objects.remove(observer);
}
bool WindowOrWorkerGlobalScopeMixin::has_registered_performance_observer(JS::NonnullGCPtr<PerformanceTimeline::PerformanceObserver> observer)
{
return m_registered_performance_observer_objects.contains(observer);
}
// https://w3c.github.io/performance-timeline/#dfn-queue-the-performanceobserver-task
void WindowOrWorkerGlobalScopeMixin::queue_the_performance_observer_task()
{
// 1. If relevantGlobal's performance observer task queued flag is set, terminate these steps.
if (m_performance_observer_task_queued)
return;
// 2. Set relevantGlobal's performance observer task queued flag.
m_performance_observer_task_queued = true;
// 3. Queue a task that consists of running the following substeps. The task source for the queued task is the performance
// timeline task source.
queue_global_task(Task::Source::PerformanceTimeline, this_impl(), [this]() {
auto& realm = this_impl().realm();
// 1. Unset performance observer task queued flag of relevantGlobal.
m_performance_observer_task_queued = false;
// 2. Let notifyList be a copy of relevantGlobal's list of registered performance observer objects.
auto notify_list = m_registered_performance_observer_objects;
// 3. For each registered performance observer object registeredObserver in notifyList, run these steps:
for (auto& registered_observer : notify_list) {
// 1. Let po be registeredObserver's observer.
// 2. Let entries be a copy of pos observer buffer.
// 4. Empty pos observer buffer.
auto entries = registered_observer->take_records();
// 3. If entries is empty, return.
// FIXME: Do they mean `continue`?
if (entries.is_empty())
continue;
Vector<JS::NonnullGCPtr<PerformanceTimeline::PerformanceEntry>> entries_as_gc_ptrs;
for (auto& entry : entries)
entries_as_gc_ptrs.append(*entry);
// 5. Let observerEntryList be a new PerformanceObserverEntryList, with its entry list set to entries.
auto observer_entry_list = realm.heap().allocate<PerformanceTimeline::PerformanceObserverEntryList>(realm, realm, move(entries_as_gc_ptrs));
// 6. Let droppedEntriesCount be null.
Optional<u64> dropped_entries_count;
// 7. If po's requires dropped entries is set, perform the following steps:
if (registered_observer->requires_dropped_entries()) {
// 1. Set droppedEntriesCount to 0.
dropped_entries_count = 0;
// 2. For each PerformanceObserverInit item in registeredObserver's options list:
for (auto const& item : registered_observer->options_list()) {
// 1. For each DOMString entryType that appears either as item's type or in item's entryTypes:
auto increment_dropped_entries_count = [this, &dropped_entries_count](FlyString const& type) {
// 1. Let map be relevantGlobal's performance entry buffer map.
auto const& map = m_performance_entry_buffer_map;
// 2. Let tuple be the result of getting the value of entry on map given entryType as key.
auto const& tuple = map.get(type);
VERIFY(tuple.has_value());
// 3. Increase droppedEntriesCount by tuple's dropped entries count.
dropped_entries_count.value() += tuple->dropped_entries_count;
};
if (item.type.has_value()) {
increment_dropped_entries_count(item.type.value());
} else {
VERIFY(item.entry_types.has_value());
for (auto const& type : item.entry_types.value())
increment_dropped_entries_count(type);
}
}
// 3. Set po's requires dropped entries to false.
registered_observer->unset_requires_dropped_entries({});
}
// 8. Let callbackOptions be a PerformanceObserverCallbackOptions with its droppedEntriesCount set to
// droppedEntriesCount if droppedEntriesCount is not null, otherwise unset.
auto callback_options = JS::Object::create(realm, realm.intrinsics().object_prototype());
if (dropped_entries_count.has_value())
MUST(callback_options->create_data_property("droppedEntriesCount", JS::Value(dropped_entries_count.value())));
// 9. Call pos observer callback with observerEntryList as the first argument, with po as the second
// argument and as callback this value, and with callbackOptions as the third argument.
// If this throws an exception, report the exception.
auto completion = WebIDL::invoke_callback(registered_observer->callback(), registered_observer, observer_entry_list, registered_observer, callback_options);
if (completion.is_abrupt())
HTML::report_exception(completion, realm);
}
});
}
}