diff --git a/Meta/gn/secondary/Userland/Libraries/LibWeb/PerformanceTimeline/BUILD.gn b/Meta/gn/secondary/Userland/Libraries/LibWeb/PerformanceTimeline/BUILD.gn
index 4e348ee49fd..f4a1b657916 100644
--- a/Meta/gn/secondary/Userland/Libraries/LibWeb/PerformanceTimeline/BUILD.gn
+++ b/Meta/gn/secondary/Userland/Libraries/LibWeb/PerformanceTimeline/BUILD.gn
@@ -4,5 +4,7 @@ source_set("PerformanceTimeline") {
sources = [
"EntryTypes.cpp",
"PerformanceEntry.cpp",
+ "PerformanceObserver.cpp",
+ "PerformanceObserverEntryList.cpp",
]
}
diff --git a/Meta/gn/secondary/Userland/Libraries/LibWeb/idl_files.gni b/Meta/gn/secondary/Userland/Libraries/LibWeb/idl_files.gni
index 6bcc8611dcf..0cba9441758 100644
--- a/Meta/gn/secondary/Userland/Libraries/LibWeb/idl_files.gni
+++ b/Meta/gn/secondary/Userland/Libraries/LibWeb/idl_files.gni
@@ -212,6 +212,8 @@ standard_idl_files = [
"//Userland/Libraries/LibWeb/MathML/MathMLElement.idl",
"//Userland/Libraries/LibWeb/NavigationTiming/PerformanceTiming.idl",
"//Userland/Libraries/LibWeb/PerformanceTimeline/PerformanceEntry.idl",
+ "//Userland/Libraries/LibWeb/PerformanceTimeline/PerformanceObserver.idl",
+ "//Userland/Libraries/LibWeb/PerformanceTimeline/PerformanceObserverEntryList.idl",
"//Userland/Libraries/LibWeb/RequestIdleCallback/IdleDeadline.idl",
"//Userland/Libraries/LibWeb/ResizeObserver/ResizeObserver.idl",
"//Userland/Libraries/LibWeb/Streams/ByteLengthQueuingStrategy.idl",
diff --git a/Tests/LibWeb/Text/expected/PerformanceObserver/PerformanceObserver_basic.txt b/Tests/LibWeb/Text/expected/PerformanceObserver/PerformanceObserver_basic.txt
new file mode 100644
index 00000000000..946e4a8dde6
--- /dev/null
+++ b/Tests/LibWeb/Text/expected/PerformanceObserver/PerformanceObserver_basic.txt
@@ -0,0 +1,28 @@
+TypeError: Must specify one of entryTypes or type
+TypeError: Cannot specify type or buffered if entryTypes is specified
+TypeError: Cannot specify type or buffered if entryTypes is specified
+InvalidModificationError: Cannot change a PerformanceObserver from observing multiple types to observing a single type
+observer === globalObserver: true
+list instanceof PerformanceObserverEntryList: true
+allEntries instanceof Array: true
+allEntries.length === 3: true
+allEntries[0] === startMark: true
+allEntries[1] === endMark: true
+allEntries[2] === measureMark: true
+markEntries instanceof Array: true
+markEntries.length === 2: true
+markEntries[0] === startMark: true
+markEntries[1] === endMark: true
+measureEntries instanceof Array: true
+measureEntries.length === 1: true
+measureEntries[0] === measureMark: true
+startEntries instanceof Array: true
+startEntries.length === 1: true
+startEntries[0] === startMark: true
+endEntries instanceof Array: true
+endEntries.length === 1: true
+endEntries[0] === endMark: true
+measureEntriesByName instanceof Array: true
+measureEntriesByName.length === 1: true
+measureEntriesByName[0] === measureMark: true
+records.length === 0: true
diff --git a/Tests/LibWeb/Text/input/PerformanceObserver/PerformanceObserver_basic.html b/Tests/LibWeb/Text/input/PerformanceObserver/PerformanceObserver_basic.html
new file mode 100644
index 00000000000..e3d3d57e7e3
--- /dev/null
+++ b/Tests/LibWeb/Text/input/PerformanceObserver/PerformanceObserver_basic.html
@@ -0,0 +1,98 @@
+
+
diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt
index b2237256a57..60fdd97e4d6 100644
--- a/Userland/Libraries/LibWeb/CMakeLists.txt
+++ b/Userland/Libraries/LibWeb/CMakeLists.txt
@@ -509,6 +509,8 @@ set(SOURCES
Painting/ViewportPaintable.cpp
PerformanceTimeline/EntryTypes.cpp
PerformanceTimeline/PerformanceEntry.cpp
+ PerformanceTimeline/PerformanceObserver.cpp
+ PerformanceTimeline/PerformanceObserverEntryList.cpp
PermissionsPolicy/AutoplayAllowlist.cpp
PixelUnits.cpp
Platform/AudioCodecPlugin.cpp
diff --git a/Userland/Libraries/LibWeb/Forward.h b/Userland/Libraries/LibWeb/Forward.h
index 82d042e40ae..22927f65187 100644
--- a/Userland/Libraries/LibWeb/Forward.h
+++ b/Userland/Libraries/LibWeb/Forward.h
@@ -543,6 +543,9 @@ struct LinearGradientData;
namespace Web::PerformanceTimeline {
class PerformanceEntry;
+class PerformanceObserver;
+class PerformanceObserverEntryList;
+struct PerformanceObserverInit;
}
namespace Web::PermissionsPolicy {
diff --git a/Userland/Libraries/LibWeb/HTML/EventLoop/Task.h b/Userland/Libraries/LibWeb/HTML/EventLoop/Task.h
index 3ac67b256cf..1caf816ce2a 100644
--- a/Userland/Libraries/LibWeb/HTML/EventLoop/Task.h
+++ b/Userland/Libraries/LibWeb/HTML/EventLoop/Task.h
@@ -41,6 +41,9 @@ public:
// https://www.w3.org/TR/intersection-observer/#intersectionobserver-task-source
IntersectionObserver,
+ // https://w3c.github.io/performance-timeline/#dfn-performance-timeline-task-source
+ PerformanceTimeline,
+
// Some elements, such as the HTMLMediaElement, must have a unique task source per instance.
// Keep this field last, to serve as the base value of all unique task sources.
UniqueTaskSourceStart,
diff --git a/Userland/Libraries/LibWeb/HTML/WindowOrWorkerGlobalScope.cpp b/Userland/Libraries/LibWeb/HTML/WindowOrWorkerGlobalScope.cpp
index d9561e15e23..8aa1cf47ecb 100644
--- a/Userland/Libraries/LibWeb/HTML/WindowOrWorkerGlobalScope.cpp
+++ b/Userland/Libraries/LibWeb/HTML/WindowOrWorkerGlobalScope.cpp
@@ -23,8 +23,11 @@
#include
#include
#include
+#include
#include
#include
+#include
+#include
#include
#include
#include
@@ -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 WindowOrWorkerGlobalScopeMixin::queue_performance_entry(JS::NonnullGCPtr new_entry)
+void WindowOrWorkerGlobalScopeMixin::queue_performance_entry(JS::NonnullGCPtr 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> interested_observers;
// 2. Let entryType be newEntry’s entryType value.
auto const& entry_type = new_entry->entry_type();
@@ -313,14 +314,31 @@ WebIDL::ExceptionOr 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 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, 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>> filter_buffer_by_name_and_type(Vector> const& buffer, Optional name, Optional type)
-{
- // 1. Let result be an initially empty list.
- Vector> 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>> WindowOrWorkerGlobalScopeMixin::filter_buffer_map_by_name_and_type(Optional name, Optional type) const
{
@@ -431,4 +420,111 @@ ErrorOr>> WindowOrWorke
return result;
}
+void WindowOrWorkerGlobalScopeMixin::register_performance_observer(Badge, JS::NonnullGCPtr observer)
+{
+ m_registered_performance_observer_objects.set(observer, AK::HashSetExistingEntryBehavior::Keep);
+}
+
+void WindowOrWorkerGlobalScopeMixin::unregister_performance_observer(Badge, JS::NonnullGCPtr observer)
+{
+ m_registered_performance_observer_objects.remove(observer);
+}
+
+bool WindowOrWorkerGlobalScopeMixin::has_registered_performance_observer(JS::NonnullGCPtr 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 po’s observer buffer.
+ // 4. Empty po’s 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> 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(realm, realm, move(entries_as_gc_ptrs));
+
+ // 6. Let droppedEntriesCount be null.
+ Optional 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 po’s 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);
+ }
+ });
+}
+
}
diff --git a/Userland/Libraries/LibWeb/HTML/WindowOrWorkerGlobalScope.h b/Userland/Libraries/LibWeb/HTML/WindowOrWorkerGlobalScope.h
index 08211eda9be..086a6409c40 100644
--- a/Userland/Libraries/LibWeb/HTML/WindowOrWorkerGlobalScope.h
+++ b/Userland/Libraries/LibWeb/HTML/WindowOrWorkerGlobalScope.h
@@ -48,12 +48,18 @@ public:
void clear_interval(i32);
PerformanceTimeline::PerformanceEntryTuple& relevant_performance_entry_tuple(FlyString const& entry_type);
- WebIDL::ExceptionOr queue_performance_entry(JS::NonnullGCPtr new_entry);
+ void queue_performance_entry(JS::NonnullGCPtr new_entry);
void clear_performance_entry_buffer(Badge, FlyString const& entry_type);
void remove_entries_from_performance_entry_buffer(Badge, FlyString const& entry_type, String entry_name);
ErrorOr>> filter_buffer_map_by_name_and_type(Optional name, Optional type) const;
+ void register_performance_observer(Badge, JS::NonnullGCPtr);
+ void unregister_performance_observer(Badge, JS::NonnullGCPtr);
+ bool has_registered_performance_observer(JS::NonnullGCPtr);
+
+ void queue_the_performance_observer_task();
+
protected:
void initialize(JS::Realm&);
void visit_edges(JS::Cell::Visitor&);
@@ -70,8 +76,11 @@ private:
// https://www.w3.org/TR/performance-timeline/#performance-timeline
// Each global object has:
- // FIXME: - a performance observer task queued flag
- // FIXME: - a list of registered performance observer objects that is initially empty
+ // - a performance observer task queued flag
+ bool m_performance_observer_task_queued { false };
+
+ // - a list of registered performance observer objects that is initially empty
+ OrderedHashTable> m_registered_performance_observer_objects;
// https://www.w3.org/TR/performance-timeline/#dfn-performance-entry-buffer-map
// a performance entry buffer map map, keyed on a DOMString, representing the entry type to which the buffer belongs. The map's value is the following tuple:
diff --git a/Userland/Libraries/LibWeb/HighResolutionTime/Performance.cpp b/Userland/Libraries/LibWeb/HighResolutionTime/Performance.cpp
index f1fad3b3946..5ccdbc5e121 100644
--- a/Userland/Libraries/LibWeb/HighResolutionTime/Performance.cpp
+++ b/Userland/Libraries/LibWeb/HighResolutionTime/Performance.cpp
@@ -63,7 +63,7 @@ WebIDL::ExceptionOr> Performance::
// 2. Queue entry.
auto* window_or_worker = dynamic_cast(&realm.global_object());
VERIFY(window_or_worker);
- TRY(window_or_worker->queue_performance_entry(entry));
+ window_or_worker->queue_performance_entry(entry);
// 3. Add entry to the performance entry buffer.
// FIXME: This seems to be a holdover from moving to the `queue` structure for PerformanceObserver, as this would cause a double append.
@@ -293,7 +293,7 @@ WebIDL::ExceptionOr> Performanc
auto entry = realm.heap().allocate(realm, realm, measure_name, start_time, duration, detail);
// 10. Queue entry.
- TRY(window_or_worker->queue_performance_entry(entry));
+ window_or_worker->queue_performance_entry(entry);
// 11. Add entry to the performance entry buffer.
// FIXME: This seems to be a holdover from moving to the `queue` structure for PerformanceObserver, as this would cause a double append.
diff --git a/Userland/Libraries/LibWeb/HighResolutionTime/SupportedPerformanceTypes.h b/Userland/Libraries/LibWeb/HighResolutionTime/SupportedPerformanceTypes.h
new file mode 100644
index 00000000000..55e02ccf410
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HighResolutionTime/SupportedPerformanceTypes.h
@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2023, Luke Wilde
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+namespace Web::HighResolutionTime {
+
+// 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)
+
+}
diff --git a/Userland/Libraries/LibWeb/PerformanceTimeline/PerformanceEntry.h b/Userland/Libraries/LibWeb/PerformanceTimeline/PerformanceEntry.h
index 7a3572816f8..297fc1682ae 100644
--- a/Userland/Libraries/LibWeb/PerformanceTimeline/PerformanceEntry.h
+++ b/Userland/Libraries/LibWeb/PerformanceTimeline/PerformanceEntry.h
@@ -36,7 +36,7 @@ public:
HighResolutionTime::DOMHighResTimeStamp duration() const { return m_duration; }
// https://w3c.github.io/timing-entrytypes-registry/#dfn-should-add-entry
- virtual PerformanceTimeline::ShouldAddEntry should_add_entry() const = 0;
+ virtual PerformanceTimeline::ShouldAddEntry should_add_entry(Optional = {}) const = 0;
protected:
PerformanceEntry(JS::Realm&, String const& name, HighResolutionTime::DOMHighResTimeStamp start_time, HighResolutionTime::DOMHighResTimeStamp duration);
diff --git a/Userland/Libraries/LibWeb/PerformanceTimeline/PerformanceEntryTuple.h b/Userland/Libraries/LibWeb/PerformanceTimeline/PerformanceEntryTuple.h
index 938cd6bf914..18d86fa9571 100644
--- a/Userland/Libraries/LibWeb/PerformanceTimeline/PerformanceEntryTuple.h
+++ b/Userland/Libraries/LibWeb/PerformanceTimeline/PerformanceEntryTuple.h
@@ -14,7 +14,7 @@ namespace Web::PerformanceTimeline {
struct PerformanceEntryTuple {
// https://www.w3.org/TR/performance-timeline/#dfn-performance-entry-buffer
// A performance entry buffer to store PerformanceEntry objects, that is initially empty.
- Vector> performance_entry_buffer;
+ Vector> performance_entry_buffer;
// https://www.w3.org/TR/performance-timeline/#dfn-maxbuffersize
// An integer maxBufferSize, initialized to the registry value for this entry type.
@@ -45,6 +45,12 @@ struct PerformanceEntryTuple {
// 4. Return true.
return true;
}
+
+ void visit_edges(JS::Cell::Visitor& visitor)
+ {
+ for (auto& entry : performance_entry_buffer)
+ visitor.visit(entry);
+ }
};
}
diff --git a/Userland/Libraries/LibWeb/PerformanceTimeline/PerformanceObserver.cpp b/Userland/Libraries/LibWeb/PerformanceTimeline/PerformanceObserver.cpp
new file mode 100644
index 00000000000..221c6475d57
--- /dev/null
+++ b/Userland/Libraries/LibWeb/PerformanceTimeline/PerformanceObserver.cpp
@@ -0,0 +1,229 @@
+/*
+ * Copyright (c) 2023, Luke Wilde
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace Web::PerformanceTimeline {
+
+WebIDL::ExceptionOr> PerformanceObserver::construct_impl(JS::Realm& realm, JS::GCPtr callback)
+{
+ return realm.heap().allocate(realm, realm, callback);
+}
+
+PerformanceObserver::PerformanceObserver(JS::Realm& realm, JS::GCPtr callback)
+ : Bindings::PlatformObject(realm)
+ , m_callback(move(callback))
+{
+}
+
+PerformanceObserver::~PerformanceObserver() = default;
+
+void PerformanceObserver::initialize(JS::Realm& realm)
+{
+ Base::initialize(realm);
+ set_prototype(&Bindings::ensure_web_prototype(realm, "PerformanceObserver"));
+}
+
+void PerformanceObserver::visit_edges(Cell::Visitor& visitor)
+{
+ Base::visit_edges(visitor);
+ visitor.visit(m_callback.ptr());
+ for (auto& entry : m_observer_buffer)
+ visitor.visit(entry);
+}
+
+// https://w3c.github.io/performance-timeline/#dom-performanceobserver-observe
+WebIDL::ExceptionOr PerformanceObserver::observe(PerformanceObserverInit& options)
+{
+ auto& realm = this->realm();
+
+ // 1. Let relevantGlobal be this's relevant global object.
+ auto* relevant_global = dynamic_cast(&HTML::relevant_global_object(*this));
+ VERIFY(relevant_global);
+
+ // 2. If options's entryTypes and type members are both omitted, then throw a "TypeError".
+ if (!options.entry_types.has_value() && !options.type.has_value())
+ return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Must specify one of entryTypes or type"sv };
+
+ // 3. If options's entryTypes is present and any other member is also present, then throw a "TypeError".
+ if (options.entry_types.has_value() && (options.type.has_value() || options.buffered.has_value()))
+ return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Cannot specify type or buffered if entryTypes is specified"sv };
+
+ // 4. Update or check this's observer type by running these steps:
+ // 1. If this's observer type is "undefined":
+ if (m_observer_type == ObserverType::Undefined) {
+ // 1. If options's entryTypes member is present, then set this's observer type to "multiple".
+ if (options.entry_types.has_value())
+ m_observer_type = ObserverType::Multiple;
+
+ // 2. If options's type member is present, then set this's observer type to "single".
+ if (options.type.has_value())
+ m_observer_type = ObserverType::Single;
+ }
+ // 2. If this's observer type is "single" and options's entryTypes member is present, then throw an "InvalidModificationError".
+ else if (m_observer_type == ObserverType::Single) {
+ if (options.entry_types.has_value())
+ return WebIDL::InvalidModificationError::create(realm, "Cannot change a PerformanceObserver from observing a single type to observing multiple types"sv);
+ }
+ // 3. If this's observer type is "multiple" and options's type member is present, then throw an "InvalidModificationError".
+ else if (m_observer_type == ObserverType::Multiple) {
+ if (options.type.has_value())
+ return WebIDL::InvalidModificationError::create(realm, "Cannot change a PerformanceObserver from observing multiple types to observing a single type"sv);
+ }
+
+ // 5. Set this's requires dropped entries to true.
+ m_requires_dropped_entries = true;
+
+ // 6. If this's observer type is "multiple", run the following steps:
+ if (m_observer_type == ObserverType::Multiple) {
+ // 1. Let entry types be options's entryTypes sequence.
+ VERIFY(options.entry_types.has_value());
+ auto& entry_types = options.entry_types.value();
+
+ // 2. Remove all types from entry types that are not contained in relevantGlobal's frozen array of supported entry types.
+ // The user agent SHOULD notify developers if entry types is modified. For example, a console warning listing removed
+ // types might be appropriate.
+ entry_types.remove_all_matching([](String const& type) {
+#define __ENUMERATE_SUPPORTED_PERFORMANCE_ENTRY_TYPES(entry_type, cpp_class) \
+ if (entry_type == type) \
+ return false;
+ ENUMERATE_SUPPORTED_PERFORMANCE_ENTRY_TYPES
+#undef __ENUMERATE_SUPPORTED_PERFORMANCE_ENTRY_TYPES
+
+ dbgln("Potential FIXME: Removing unsupported PerformanceEntry type '{}' from list of observed types in PerformanceObserver::observe()", type);
+ return true;
+ });
+
+ // 3. If the resulting entry types sequence is an empty sequence, abort these steps.
+ // The user agent SHOULD notify developers when the steps are aborted to notify that registration has been aborted.
+ // For example, a console warning might be appropriate.
+ if (entry_types.is_empty()) {
+ dbgln("Potential FIXME: Returning from PerformanceObserver::observe() as we don't support any of the specified types (or none was specified).");
+ return {};
+ }
+
+ // 4. If the list of registered performance observer objects of relevantGlobal contains a registered performance
+ // observer whose observer is this, replace its options list with a list containing options as its only item.
+ // 5. Otherwise, create and append a registered performance observer object to the list of registered performance
+ // observer objects of relevantGlobal, with observer set to this and options list set to a list containing
+ // options as its only item.
+ // NOTE: See the comment on PerformanceObserver::options_list about why this doesn't create a separate registered
+ // performance observer object.
+ m_options_list.clear();
+ m_options_list.append(options);
+ relevant_global->register_performance_observer({}, *this);
+ }
+ // 7. Otherwise, run the following steps:
+ else {
+ // 1. Assert that this's observer type is "single".
+ VERIFY(m_observer_type == ObserverType::Single);
+
+ // 2. If options's type is not contained in the relevantGlobal's frozen array of supported entry types, abort these steps.
+ // The user agent SHOULD notify developers when this happens, for instance via a console warning.
+ VERIFY(options.type.has_value());
+ auto& type = options.type.value();
+ bool recognized_type = false;
+
+#define __ENUMERATE_SUPPORTED_PERFORMANCE_ENTRY_TYPES(entry_type, cpp_class) \
+ if (!recognized_type && entry_type == type) \
+ recognized_type = true;
+ ENUMERATE_SUPPORTED_PERFORMANCE_ENTRY_TYPES
+#undef __ENUMERATE_SUPPORTED_PERFORMANCE_ENTRY_TYPES
+
+ if (!recognized_type) {
+ dbgln("Potential FIXME: Returning from PerformanceObserver::observe() as we don't support the PerformanceEntry type '{}'", type);
+ return {};
+ }
+
+ // 3. If the list of registered performance observer objects of relevantGlobal contains a registered performance
+ // observer obs whose observer is this:
+ if (relevant_global->has_registered_performance_observer(*this)) {
+ // 1. If obs's options list contains a PerformanceObserverInit item currentOptions whose type is equal to options's type,
+ // replace currentOptions with options in obs's options list.
+ auto index = m_options_list.find_first_index_if([&options](PerformanceObserverInit const& entry) {
+ return entry.type == options.type;
+ });
+ if (index.has_value()) {
+ m_options_list[index.value()] = options;
+ } else {
+ // Otherwise, append options to obs's options list.
+ m_options_list.append(options);
+ }
+ }
+ // 4. Otherwise, create and append a registered performance observer object to the list of registered performance
+ // observer objects of relevantGlobal, with observer set to the this and options list set to a list containing
+ // options as its only item.
+ else {
+ m_options_list.clear();
+ m_options_list.append(options);
+ relevant_global->register_performance_observer({}, *this);
+ }
+
+ // 5. If options's buffered flag is set:
+ if (options.buffered.has_value() && options.buffered.value()) {
+ // 1. Let tuple be the relevant performance entry tuple of options's type and relevantGlobal.
+ auto const& tuple = relevant_global->relevant_performance_entry_tuple(type);
+
+ // 2. For each entry in tuple's performance entry buffer:
+ for (auto const& entry : tuple.performance_entry_buffer) {
+ // 1. If should add entry with entry and options as parameters returns true, append entry to the observer buffer.
+ if (entry->should_add_entry(options) == ShouldAddEntry::Yes)
+ m_observer_buffer.append(*entry);
+ }
+
+ // 3. Queue the PerformanceObserver task with relevantGlobal as input.
+ relevant_global->queue_the_performance_observer_task();
+ }
+ }
+
+ return {};
+}
+
+// https://w3c.github.io/performance-timeline/#dom-performanceobserver-disconnect
+void PerformanceObserver::disconnect()
+{
+ // 1. Remove this from the list of registered performance observer objects of relevant global object.
+ auto* relevant_global = dynamic_cast(&HTML::relevant_global_object(*this));
+ VERIFY(relevant_global);
+ relevant_global->unregister_performance_observer({}, *this);
+
+ // 2. Empty this's observer buffer.
+ m_observer_buffer.clear();
+
+ // 3. Empty this's options list.
+ m_options_list.clear();
+}
+
+// https://w3c.github.io/performance-timeline/#dom-performanceobserver-takerecords
+Vector> PerformanceObserver::take_records()
+{
+ // The takeRecords() method must return a copy of this's observer buffer, and also empty this's observer buffer.
+ Vector> records;
+ for (auto& record : m_observer_buffer)
+ records.append(*record);
+ m_observer_buffer.clear();
+ return records;
+}
+
+void PerformanceObserver::unset_requires_dropped_entries(Badge)
+{
+ m_requires_dropped_entries = false;
+}
+
+void PerformanceObserver::append_to_observer_buffer(Badge, JS::NonnullGCPtr entry)
+{
+ m_observer_buffer.append(entry);
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/PerformanceTimeline/PerformanceObserver.h b/Userland/Libraries/LibWeb/PerformanceTimeline/PerformanceObserver.h
new file mode 100644
index 00000000000..c1a0a66b76f
--- /dev/null
+++ b/Userland/Libraries/LibWeb/PerformanceTimeline/PerformanceObserver.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2023, Luke Wilde
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include
+#include
+
+namespace Web::PerformanceTimeline {
+
+// https://w3c.github.io/performance-timeline/#dom-performanceobserverinit
+struct PerformanceObserverInit {
+ Optional> entry_types;
+ Optional type;
+ Optional buffered;
+};
+
+// https://w3c.github.io/performance-timeline/#dom-performanceobserver
+class PerformanceObserver final : public Bindings::PlatformObject {
+ WEB_PLATFORM_OBJECT(PerformanceObserver, Bindings::PlatformObject);
+
+public:
+ enum class ObserverType {
+ Undefined,
+ Single,
+ Multiple,
+ };
+
+ static WebIDL::ExceptionOr> construct_impl(JS::Realm&, JS::GCPtr);
+ virtual ~PerformanceObserver() override;
+
+ WebIDL::ExceptionOr observe(PerformanceObserverInit& options);
+ void disconnect();
+ Vector> take_records();
+
+ bool requires_dropped_entries() const { return m_requires_dropped_entries; }
+ void unset_requires_dropped_entries(Badge);
+
+ Vector const& options_list() const { return m_options_list; }
+
+ WebIDL::CallbackType& callback() { return *m_callback; }
+
+ void append_to_observer_buffer(Badge, JS::NonnullGCPtr);
+
+private:
+ PerformanceObserver(JS::Realm&, JS::GCPtr);
+
+ virtual void initialize(JS::Realm&) override;
+ virtual void visit_edges(Cell::Visitor&) override;
+
+ // https://w3c.github.io/performance-timeline/#dfn-observer-callback
+ // A PerformanceObserverCallback observer callback set on creation.
+ JS::GCPtr m_callback;
+
+ // https://w3c.github.io/performance-timeline/#dfn-observer-buffer
+ // A PerformanceEntryList object called the observer buffer that is initially empty.
+ Vector> m_observer_buffer;
+
+ // https://w3c.github.io/performance-timeline/#dfn-observer-type
+ // A DOMString observer type which is initially "undefined".
+ ObserverType m_observer_type { ObserverType::Undefined };
+
+ // https://w3c.github.io/performance-timeline/#dfn-requires-dropped-entries
+ // A boolean requires dropped entries which is initially set to false.
+ bool m_requires_dropped_entries { false };
+
+ // https://w3c.github.io/performance-timeline/#dfn-options-list
+ // A registered performance observer is a struct consisting of an observer member (a PerformanceObserver object)
+ // and an options list member (a list of PerformanceObserverInit dictionaries).
+ // NOTE: This doesn't use a separate struct as methods such as disconnect() assume it can access an options list from `this`: a PerformanceObserver.
+ Vector m_options_list;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/PerformanceTimeline/PerformanceObserver.idl b/Userland/Libraries/LibWeb/PerformanceTimeline/PerformanceObserver.idl
new file mode 100644
index 00000000000..1ccb4df0781
--- /dev/null
+++ b/Userland/Libraries/LibWeb/PerformanceTimeline/PerformanceObserver.idl
@@ -0,0 +1,25 @@
+#import
+
+// https://w3c.github.io/performance-timeline/#dom-performanceobservercallbackoptions
+dictionary PerformanceObserverCallbackOptions {
+ unsigned long long droppedEntriesCount;
+};
+
+callback PerformanceObserverCallback = undefined (PerformanceObserverEntryList entries, PerformanceObserver observer, optional PerformanceObserverCallbackOptions options = {});
+
+// https://w3c.github.io/performance-timeline/#dom-performanceobserverinit
+dictionary PerformanceObserverInit {
+ sequence entryTypes;
+ DOMString type;
+ boolean buffered;
+};
+
+// https://w3c.github.io/performance-timeline/#dom-performanceobserver
+[Exposed=(Window,Worker), UseNewAKString]
+interface PerformanceObserver {
+ constructor(PerformanceObserverCallback callback);
+ undefined observe(optional PerformanceObserverInit options = {});
+ undefined disconnect();
+ PerformanceEntryList takeRecords();
+ //[SameObject] static readonly attribute sequence supportedEntryTypes;
+};
diff --git a/Userland/Libraries/LibWeb/PerformanceTimeline/PerformanceObserverEntryList.cpp b/Userland/Libraries/LibWeb/PerformanceTimeline/PerformanceObserverEntryList.cpp
new file mode 100644
index 00000000000..a78e6613564
--- /dev/null
+++ b/Userland/Libraries/LibWeb/PerformanceTimeline/PerformanceObserverEntryList.cpp
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2023, Luke Wilde
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace Web::PerformanceTimeline {
+
+PerformanceObserverEntryList::PerformanceObserverEntryList(JS::Realm& realm, Vector>&& entry_list)
+ : Bindings::PlatformObject(realm)
+ , m_entry_list(move(entry_list))
+{
+}
+
+PerformanceObserverEntryList::~PerformanceObserverEntryList() = default;
+
+void PerformanceObserverEntryList::initialize(JS::Realm& realm)
+{
+ Base::initialize(realm);
+ set_prototype(&Bindings::ensure_web_prototype(realm, "PerformanceObserverEntryList"));
+}
+
+void PerformanceObserverEntryList::visit_edges(Cell::Visitor& visitor)
+{
+ Base::visit_edges(visitor);
+ for (auto& entry : m_entry_list)
+ visitor.visit(entry);
+}
+
+// https://www.w3.org/TR/performance-timeline/#dfn-filter-buffer-by-name-and-type
+ErrorOr>> filter_buffer_by_name_and_type(Vector> const& buffer, Optional name, Optional type)
+{
+ // 1. Let result be an initially empty list.
+ Vector> 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://w3c.github.io/performance-timeline/#dom-performanceobserverentrylist-getentries
+WebIDL::ExceptionOr>> PerformanceObserverEntryList::get_entries() const
+{
+ // Returns a PerformanceEntryList object returned by filter buffer by name and type algorithm with this's entry list,
+ // name and type set to null.
+ return TRY_OR_THROW_OOM(vm(), filter_buffer_by_name_and_type(m_entry_list, /* name= */ Optional {}, /* type= */ Optional {}));
+}
+
+// https://w3c.github.io/performance-timeline/#dom-performanceobserverentrylist-getentriesbytype
+WebIDL::ExceptionOr>> PerformanceObserverEntryList::get_entries_by_type(String const& type) const
+{
+ // Returns a PerformanceEntryList object returned by filter buffer by name and type algorithm with this's entry list,
+ // name set to null, and type set to the method's input type parameter.
+ return TRY_OR_THROW_OOM(vm(), filter_buffer_by_name_and_type(m_entry_list, /* name= */ Optional {}, type));
+}
+
+// https://w3c.github.io/performance-timeline/#dom-performanceobserverentrylist-getentriesbyname
+WebIDL::ExceptionOr>> PerformanceObserverEntryList::get_entries_by_name(String const& name, Optional type) const
+{
+ // Returns a PerformanceEntryList object returned by filter buffer by name and type algorithm with this's entry list,
+ // name set to the method input name parameter, and type set to null if optional entryType is omitted, or set to the
+ // method's input type parameter otherwise.
+ return TRY_OR_THROW_OOM(vm(), filter_buffer_by_name_and_type(m_entry_list, name, type));
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/PerformanceTimeline/PerformanceObserverEntryList.h b/Userland/Libraries/LibWeb/PerformanceTimeline/PerformanceObserverEntryList.h
new file mode 100644
index 00000000000..04a9194a6a9
--- /dev/null
+++ b/Userland/Libraries/LibWeb/PerformanceTimeline/PerformanceObserverEntryList.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2023, Luke Wilde
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include
+
+namespace Web::PerformanceTimeline {
+
+// https://w3c.github.io/performance-timeline/#performanceobserverentrylist-interface
+class PerformanceObserverEntryList final : public Bindings::PlatformObject {
+ WEB_PLATFORM_OBJECT(PerformanceObserverEntryList, Bindings::PlatformObject);
+
+public:
+ virtual ~PerformanceObserverEntryList() override;
+
+ WebIDL::ExceptionOr>> get_entries() const;
+ WebIDL::ExceptionOr>> get_entries_by_type(String const& type) const;
+ WebIDL::ExceptionOr>> get_entries_by_name(String const& name, Optional type) const;
+
+private:
+ PerformanceObserverEntryList(JS::Realm&, Vector>&&);
+
+ virtual void initialize(JS::Realm&) override;
+ virtual void visit_edges(Cell::Visitor&) override;
+
+ // https://w3c.github.io/performance-timeline/#dfn-entry-list
+ // Returns a PerformanceEntryList object returned by filter buffer by name and type algorithm with this's entry list,
+ // name and type set to null.
+ Vector> m_entry_list;
+};
+
+ErrorOr>> filter_buffer_by_name_and_type(Vector> const& buffer, Optional name, Optional type);
+
+}
diff --git a/Userland/Libraries/LibWeb/PerformanceTimeline/PerformanceObserverEntryList.idl b/Userland/Libraries/LibWeb/PerformanceTimeline/PerformanceObserverEntryList.idl
new file mode 100644
index 00000000000..772b26ccf7f
--- /dev/null
+++ b/Userland/Libraries/LibWeb/PerformanceTimeline/PerformanceObserverEntryList.idl
@@ -0,0 +1,9 @@
+#import
+
+// https://w3c.github.io/performance-timeline/#performanceobserverentrylist-interface
+[Exposed=(Window,Worker), UseNewAKString]
+interface PerformanceObserverEntryList {
+ PerformanceEntryList getEntries();
+ PerformanceEntryList getEntriesByType(DOMString type);
+ PerformanceEntryList getEntriesByName(DOMString name, optional DOMString type);
+};
diff --git a/Userland/Libraries/LibWeb/UserTiming/PerformanceMark.h b/Userland/Libraries/LibWeb/UserTiming/PerformanceMark.h
index fd79baa36e9..0013546ee01 100644
--- a/Userland/Libraries/LibWeb/UserTiming/PerformanceMark.h
+++ b/Userland/Libraries/LibWeb/UserTiming/PerformanceMark.h
@@ -36,7 +36,7 @@ public:
static Optional max_buffer_size() { return OptionalNone {}; }
// https://w3c.github.io/timing-entrytypes-registry/#dfn-should-add-entry
- virtual PerformanceTimeline::ShouldAddEntry should_add_entry() const override { return PerformanceTimeline::ShouldAddEntry::Yes; }
+ virtual PerformanceTimeline::ShouldAddEntry should_add_entry(Optional = {}) const override { return PerformanceTimeline::ShouldAddEntry::Yes; }
virtual FlyString const& entry_type() const override;
diff --git a/Userland/Libraries/LibWeb/UserTiming/PerformanceMeasure.h b/Userland/Libraries/LibWeb/UserTiming/PerformanceMeasure.h
index 9c324693ef7..a8cd45e3ad7 100644
--- a/Userland/Libraries/LibWeb/UserTiming/PerformanceMeasure.h
+++ b/Userland/Libraries/LibWeb/UserTiming/PerformanceMeasure.h
@@ -38,7 +38,7 @@ public:
static Optional max_buffer_size() { return OptionalNone {}; }
// https://w3c.github.io/timing-entrytypes-registry/#dfn-should-add-entry
- virtual PerformanceTimeline::ShouldAddEntry should_add_entry() const override { return PerformanceTimeline::ShouldAddEntry::Yes; }
+ virtual PerformanceTimeline::ShouldAddEntry should_add_entry(Optional = {}) const override { return PerformanceTimeline::ShouldAddEntry::Yes; }
virtual FlyString const& entry_type() const override;
diff --git a/Userland/Libraries/LibWeb/idl_files.cmake b/Userland/Libraries/LibWeb/idl_files.cmake
index b8068a70246..839f9db8eb0 100644
--- a/Userland/Libraries/LibWeb/idl_files.cmake
+++ b/Userland/Libraries/LibWeb/idl_files.cmake
@@ -199,6 +199,8 @@ libweb_js_bindings(IntersectionObserver/IntersectionObserverEntry)
libweb_js_bindings(MathML/MathMLElement)
libweb_js_bindings(NavigationTiming/PerformanceTiming)
libweb_js_bindings(PerformanceTimeline/PerformanceEntry)
+libweb_js_bindings(PerformanceTimeline/PerformanceObserver)
+libweb_js_bindings(PerformanceTimeline/PerformanceObserverEntryList)
libweb_js_bindings(RequestIdleCallback/IdleDeadline)
libweb_js_bindings(ResizeObserver/ResizeObserver)
libweb_js_bindings(Streams/ByteLengthQueuingStrategy)