mirror of
https://github.com/RGBCube/serenity
synced 2025-07-25 17:57:35 +00:00
LibWeb: Implement PerformanceObserver
This commit is contained in:
parent
5055883b9f
commit
af2886449a
22 changed files with 793 additions and 57 deletions
|
@ -4,5 +4,7 @@ source_set("PerformanceTimeline") {
|
||||||
sources = [
|
sources = [
|
||||||
"EntryTypes.cpp",
|
"EntryTypes.cpp",
|
||||||
"PerformanceEntry.cpp",
|
"PerformanceEntry.cpp",
|
||||||
|
"PerformanceObserver.cpp",
|
||||||
|
"PerformanceObserverEntryList.cpp",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -212,6 +212,8 @@ standard_idl_files = [
|
||||||
"//Userland/Libraries/LibWeb/MathML/MathMLElement.idl",
|
"//Userland/Libraries/LibWeb/MathML/MathMLElement.idl",
|
||||||
"//Userland/Libraries/LibWeb/NavigationTiming/PerformanceTiming.idl",
|
"//Userland/Libraries/LibWeb/NavigationTiming/PerformanceTiming.idl",
|
||||||
"//Userland/Libraries/LibWeb/PerformanceTimeline/PerformanceEntry.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/RequestIdleCallback/IdleDeadline.idl",
|
||||||
"//Userland/Libraries/LibWeb/ResizeObserver/ResizeObserver.idl",
|
"//Userland/Libraries/LibWeb/ResizeObserver/ResizeObserver.idl",
|
||||||
"//Userland/Libraries/LibWeb/Streams/ByteLengthQueuingStrategy.idl",
|
"//Userland/Libraries/LibWeb/Streams/ByteLengthQueuingStrategy.idl",
|
||||||
|
|
|
@ -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
|
|
@ -0,0 +1,98 @@
|
||||||
|
<script src="../include.js"></script>
|
||||||
|
<script>
|
||||||
|
const bufferedMessages = [];
|
||||||
|
function printlnBuffered(message) {
|
||||||
|
bufferedMessages.push(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
const globalObserver = new PerformanceObserver((list, observer) => {
|
||||||
|
printlnBuffered(`observer === globalObserver: ${observer === globalObserver}`);
|
||||||
|
printlnBuffered(
|
||||||
|
`list instanceof PerformanceObserverEntryList: ${
|
||||||
|
list instanceof PerformanceObserverEntryList
|
||||||
|
}`
|
||||||
|
);
|
||||||
|
|
||||||
|
const allEntries = list.getEntries();
|
||||||
|
printlnBuffered(`allEntries instanceof Array: ${allEntries instanceof Array}`);
|
||||||
|
printlnBuffered(`allEntries.length === 3: ${allEntries.length === 3}`);
|
||||||
|
printlnBuffered(`allEntries[0] === startMark: ${allEntries[0] === startMark}`);
|
||||||
|
printlnBuffered(`allEntries[1] === endMark: ${allEntries[1] === endMark}`);
|
||||||
|
printlnBuffered(`allEntries[2] === measureMark: ${allEntries[2] === measureMark}`);
|
||||||
|
|
||||||
|
const markEntries = list.getEntriesByType("mark");
|
||||||
|
printlnBuffered(`markEntries instanceof Array: ${markEntries instanceof Array}`);
|
||||||
|
printlnBuffered(`markEntries.length === 2: ${markEntries.length === 2}`);
|
||||||
|
printlnBuffered(`markEntries[0] === startMark: ${markEntries[0] === startMark}`);
|
||||||
|
printlnBuffered(`markEntries[1] === endMark: ${markEntries[1] === endMark}`);
|
||||||
|
|
||||||
|
const measureEntries = list.getEntriesByType("measure");
|
||||||
|
printlnBuffered(`measureEntries instanceof Array: ${measureEntries instanceof Array}`);
|
||||||
|
printlnBuffered(`measureEntries.length === 1: ${measureEntries.length === 1}`);
|
||||||
|
printlnBuffered(`measureEntries[0] === measureMark: ${measureEntries[0] === measureMark}`);
|
||||||
|
|
||||||
|
const startEntries = list.getEntriesByName("start");
|
||||||
|
printlnBuffered(`startEntries instanceof Array: ${startEntries instanceof Array}`);
|
||||||
|
printlnBuffered(`startEntries.length === 1: ${startEntries.length === 1}`);
|
||||||
|
printlnBuffered(`startEntries[0] === startMark: ${startEntries[0] === startMark}`);
|
||||||
|
|
||||||
|
const endEntries = list.getEntriesByName("end");
|
||||||
|
printlnBuffered(`endEntries instanceof Array: ${endEntries instanceof Array}`);
|
||||||
|
printlnBuffered(`endEntries.length === 1: ${endEntries.length === 1}`);
|
||||||
|
printlnBuffered(`endEntries[0] === endMark: ${endEntries[0] === endMark}`);
|
||||||
|
|
||||||
|
const measureEntriesByName = list.getEntriesByName("measure");
|
||||||
|
printlnBuffered(
|
||||||
|
`measureEntriesByName instanceof Array: ${measureEntriesByName instanceof Array}`
|
||||||
|
);
|
||||||
|
printlnBuffered(`measureEntriesByName.length === 1: ${measureEntriesByName.length === 1}`);
|
||||||
|
printlnBuffered(
|
||||||
|
`measureEntriesByName[0] === measureMark: ${measureEntriesByName[0] === measureMark}`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
globalObserver.observe({ entryTypes: ["measure", "mark"] });
|
||||||
|
|
||||||
|
const startMark = performance.mark("start");
|
||||||
|
const endMark = performance.mark("end");
|
||||||
|
const measureMark = performance.measure("measure", "start", "end");
|
||||||
|
|
||||||
|
function printCatchedException(func) {
|
||||||
|
try {
|
||||||
|
func();
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof DOMException) {
|
||||||
|
printlnBuffered(`${e.name}: ${e.message}`);
|
||||||
|
} else {
|
||||||
|
printlnBuffered(e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
printCatchedException(() => {
|
||||||
|
globalObserver.observe();
|
||||||
|
});
|
||||||
|
|
||||||
|
printCatchedException(() => {
|
||||||
|
globalObserver.observe({ entryTypes: [], buffered: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
printCatchedException(() => {
|
||||||
|
globalObserver.observe({ entryTypes: [], type: "" });
|
||||||
|
});
|
||||||
|
|
||||||
|
printCatchedException(() => {
|
||||||
|
globalObserver.observe({ type: "" });
|
||||||
|
});
|
||||||
|
|
||||||
|
test(() => {
|
||||||
|
for (const message of bufferedMessages) {
|
||||||
|
println(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
globalObserver.disconnect();
|
||||||
|
performance.mark("bad");
|
||||||
|
performance.measure("badmeasure", "end", "bad");
|
||||||
|
const records = globalObserver.takeRecords();
|
||||||
|
println(`records.length === 0: ${records.length === 0}`);
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -509,6 +509,8 @@ set(SOURCES
|
||||||
Painting/ViewportPaintable.cpp
|
Painting/ViewportPaintable.cpp
|
||||||
PerformanceTimeline/EntryTypes.cpp
|
PerformanceTimeline/EntryTypes.cpp
|
||||||
PerformanceTimeline/PerformanceEntry.cpp
|
PerformanceTimeline/PerformanceEntry.cpp
|
||||||
|
PerformanceTimeline/PerformanceObserver.cpp
|
||||||
|
PerformanceTimeline/PerformanceObserverEntryList.cpp
|
||||||
PermissionsPolicy/AutoplayAllowlist.cpp
|
PermissionsPolicy/AutoplayAllowlist.cpp
|
||||||
PixelUnits.cpp
|
PixelUnits.cpp
|
||||||
Platform/AudioCodecPlugin.cpp
|
Platform/AudioCodecPlugin.cpp
|
||||||
|
|
|
@ -543,6 +543,9 @@ struct LinearGradientData;
|
||||||
|
|
||||||
namespace Web::PerformanceTimeline {
|
namespace Web::PerformanceTimeline {
|
||||||
class PerformanceEntry;
|
class PerformanceEntry;
|
||||||
|
class PerformanceObserver;
|
||||||
|
class PerformanceObserverEntryList;
|
||||||
|
struct PerformanceObserverInit;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace Web::PermissionsPolicy {
|
namespace Web::PermissionsPolicy {
|
||||||
|
|
|
@ -41,6 +41,9 @@ public:
|
||||||
// https://www.w3.org/TR/intersection-observer/#intersectionobserver-task-source
|
// https://www.w3.org/TR/intersection-observer/#intersectionobserver-task-source
|
||||||
IntersectionObserver,
|
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.
|
// 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.
|
// Keep this field last, to serve as the base value of all unique task sources.
|
||||||
UniqueTaskSourceStart,
|
UniqueTaskSourceStart,
|
||||||
|
|
|
@ -23,8 +23,11 @@
|
||||||
#include <LibWeb/HTML/Timer.h>
|
#include <LibWeb/HTML/Timer.h>
|
||||||
#include <LibWeb/HTML/Window.h>
|
#include <LibWeb/HTML/Window.h>
|
||||||
#include <LibWeb/HTML/WindowOrWorkerGlobalScope.h>
|
#include <LibWeb/HTML/WindowOrWorkerGlobalScope.h>
|
||||||
|
#include <LibWeb/HighResolutionTime/SupportedPerformanceTypes.h>
|
||||||
#include <LibWeb/Infra/Base64.h>
|
#include <LibWeb/Infra/Base64.h>
|
||||||
#include <LibWeb/PerformanceTimeline/EntryTypes.h>
|
#include <LibWeb/PerformanceTimeline/EntryTypes.h>
|
||||||
|
#include <LibWeb/PerformanceTimeline/PerformanceObserver.h>
|
||||||
|
#include <LibWeb/PerformanceTimeline/PerformanceObserverEntryList.h>
|
||||||
#include <LibWeb/UserTiming/PerformanceMark.h>
|
#include <LibWeb/UserTiming/PerformanceMark.h>
|
||||||
#include <LibWeb/UserTiming/PerformanceMeasure.h>
|
#include <LibWeb/UserTiming/PerformanceMeasure.h>
|
||||||
#include <LibWeb/WebIDL/AbstractOperations.h>
|
#include <LibWeb/WebIDL/AbstractOperations.h>
|
||||||
|
@ -35,11 +38,6 @@ namespace Web::HTML {
|
||||||
|
|
||||||
WindowOrWorkerGlobalScopeMixin::~WindowOrWorkerGlobalScopeMixin() = default;
|
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&)
|
void WindowOrWorkerGlobalScopeMixin::initialize(JS::Realm&)
|
||||||
{
|
{
|
||||||
#define __ENUMERATE_SUPPORTED_PERFORMANCE_ENTRY_TYPES(entry_type, cpp_class) \
|
#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)
|
for (auto& it : m_timers)
|
||||||
visitor.visit(it.value);
|
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
|
// 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
|
// 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();
|
// 1. Let interested observers be an initially empty set of PerformanceObserver objects.
|
||||||
|
Vector<JS::Handle<PerformanceTimeline::PerformanceObserver>> interested_observers;
|
||||||
// FIXME: 1. Let interested observers be an initially empty set of PerformanceObserver objects.
|
|
||||||
|
|
||||||
// 2. Let entryType be newEntry’s entryType value.
|
// 2. Let entryType be newEntry’s entryType value.
|
||||||
auto const& entry_type = new_entry->entry_type();
|
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.
|
// 3. Let relevantGlobal be newEntry's relevant global object.
|
||||||
// NOTE: Already is `this`.
|
// NOTE: Already is `this`.
|
||||||
|
|
||||||
// FIXME: 4. For each registered performance observer regObs in relevantGlobal's list of registered performance observer
|
// 4. For each registered performance observer regObs in relevantGlobal's list of registered performance observer
|
||||||
// objects:
|
// objects:
|
||||||
// 1. If regObs's options list contains a PerformanceObserverInit options whose entryTypes member includes entryType
|
for (auto const& registered_observer : m_registered_performance_observer_objects) {
|
||||||
// or whose type member equals to entryType:
|
// 1. If regObs's options list contains a PerformanceObserverInit options whose entryTypes member includes entryType
|
||||||
// 1. If should add entry with newEntry and options returns true, append regObs's observer to interested observers.
|
// 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:
|
VERIFY(entry.type.has_value());
|
||||||
// 1. Append newEntry to observer's observer buffer.
|
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.
|
// 6. Let tuple be the relevant performance entry tuple of entryType and relevantGlobal.
|
||||||
auto& tuple = relevant_performance_entry_tuple(entry_type);
|
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.
|
// 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)
|
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.
|
// 10. Queue the PerformanceObserver task with relevantGlobal as input.
|
||||||
return {};
|
queue_the_performance_observer_task();
|
||||||
}
|
}
|
||||||
|
|
||||||
void WindowOrWorkerGlobalScopeMixin::clear_performance_entry_buffer(Badge<HighResolutionTime::Performance>, FlyString const& entry_type)
|
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
|
// 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
|
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;
|
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 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<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 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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,12 +48,18 @@ public:
|
||||||
void clear_interval(i32);
|
void clear_interval(i32);
|
||||||
|
|
||||||
PerformanceTimeline::PerformanceEntryTuple& relevant_performance_entry_tuple(FlyString const& entry_type);
|
PerformanceTimeline::PerformanceEntryTuple& relevant_performance_entry_tuple(FlyString const& entry_type);
|
||||||
WebIDL::ExceptionOr<void> queue_performance_entry(JS::NonnullGCPtr<PerformanceTimeline::PerformanceEntry> new_entry);
|
void queue_performance_entry(JS::NonnullGCPtr<PerformanceTimeline::PerformanceEntry> new_entry);
|
||||||
void clear_performance_entry_buffer(Badge<HighResolutionTime::Performance>, FlyString const& entry_type);
|
void clear_performance_entry_buffer(Badge<HighResolutionTime::Performance>, FlyString const& entry_type);
|
||||||
void remove_entries_from_performance_entry_buffer(Badge<HighResolutionTime::Performance>, FlyString const& entry_type, String entry_name);
|
void remove_entries_from_performance_entry_buffer(Badge<HighResolutionTime::Performance>, FlyString const& entry_type, String entry_name);
|
||||||
|
|
||||||
ErrorOr<Vector<JS::Handle<PerformanceTimeline::PerformanceEntry>>> filter_buffer_map_by_name_and_type(Optional<String> name, Optional<String> type) const;
|
ErrorOr<Vector<JS::Handle<PerformanceTimeline::PerformanceEntry>>> filter_buffer_map_by_name_and_type(Optional<String> name, Optional<String> type) const;
|
||||||
|
|
||||||
|
void register_performance_observer(Badge<PerformanceTimeline::PerformanceObserver>, JS::NonnullGCPtr<PerformanceTimeline::PerformanceObserver>);
|
||||||
|
void unregister_performance_observer(Badge<PerformanceTimeline::PerformanceObserver>, JS::NonnullGCPtr<PerformanceTimeline::PerformanceObserver>);
|
||||||
|
bool has_registered_performance_observer(JS::NonnullGCPtr<PerformanceTimeline::PerformanceObserver>);
|
||||||
|
|
||||||
|
void queue_the_performance_observer_task();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void initialize(JS::Realm&);
|
void initialize(JS::Realm&);
|
||||||
void visit_edges(JS::Cell::Visitor&);
|
void visit_edges(JS::Cell::Visitor&);
|
||||||
|
@ -70,8 +76,11 @@ private:
|
||||||
|
|
||||||
// https://www.w3.org/TR/performance-timeline/#performance-timeline
|
// https://www.w3.org/TR/performance-timeline/#performance-timeline
|
||||||
// Each global object has:
|
// Each global object has:
|
||||||
// FIXME: - a performance observer task queued flag
|
// - a performance observer task queued flag
|
||||||
// FIXME: - a list of registered performance observer objects that is initially empty
|
bool m_performance_observer_task_queued { false };
|
||||||
|
|
||||||
|
// - a list of registered performance observer objects that is initially empty
|
||||||
|
OrderedHashTable<JS::NonnullGCPtr<PerformanceTimeline::PerformanceObserver>> m_registered_performance_observer_objects;
|
||||||
|
|
||||||
// https://www.w3.org/TR/performance-timeline/#dfn-performance-entry-buffer-map
|
// 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:
|
// 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:
|
||||||
|
|
|
@ -63,7 +63,7 @@ WebIDL::ExceptionOr<JS::NonnullGCPtr<UserTiming::PerformanceMark>> Performance::
|
||||||
// 2. Queue entry.
|
// 2. Queue entry.
|
||||||
auto* window_or_worker = dynamic_cast<HTML::WindowOrWorkerGlobalScopeMixin*>(&realm.global_object());
|
auto* window_or_worker = dynamic_cast<HTML::WindowOrWorkerGlobalScopeMixin*>(&realm.global_object());
|
||||||
VERIFY(window_or_worker);
|
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.
|
// 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.
|
// 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<JS::NonnullGCPtr<UserTiming::PerformanceMeasure>> Performanc
|
||||||
auto entry = realm.heap().allocate<UserTiming::PerformanceMeasure>(realm, realm, measure_name, start_time, duration, detail);
|
auto entry = realm.heap().allocate<UserTiming::PerformanceMeasure>(realm, realm, measure_name, start_time, duration, detail);
|
||||||
|
|
||||||
// 10. Queue entry.
|
// 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.
|
// 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.
|
// FIXME: This seems to be a holdover from moving to the `queue` structure for PerformanceObserver, as this would cause a double append.
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023, Luke Wilde <lukew@serenityos.org>
|
||||||
|
*
|
||||||
|
* 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)
|
||||||
|
|
||||||
|
}
|
|
@ -36,7 +36,7 @@ public:
|
||||||
HighResolutionTime::DOMHighResTimeStamp duration() const { return m_duration; }
|
HighResolutionTime::DOMHighResTimeStamp duration() const { return m_duration; }
|
||||||
|
|
||||||
// https://w3c.github.io/timing-entrytypes-registry/#dfn-should-add-entry
|
// 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<PerformanceObserverInit const&> = {}) const = 0;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
PerformanceEntry(JS::Realm&, String const& name, HighResolutionTime::DOMHighResTimeStamp start_time, HighResolutionTime::DOMHighResTimeStamp duration);
|
PerformanceEntry(JS::Realm&, String const& name, HighResolutionTime::DOMHighResTimeStamp start_time, HighResolutionTime::DOMHighResTimeStamp duration);
|
||||||
|
|
|
@ -14,7 +14,7 @@ namespace Web::PerformanceTimeline {
|
||||||
struct PerformanceEntryTuple {
|
struct PerformanceEntryTuple {
|
||||||
// https://www.w3.org/TR/performance-timeline/#dfn-performance-entry-buffer
|
// https://www.w3.org/TR/performance-timeline/#dfn-performance-entry-buffer
|
||||||
// A performance entry buffer to store PerformanceEntry objects, that is initially empty.
|
// A performance entry buffer to store PerformanceEntry objects, that is initially empty.
|
||||||
Vector<JS::Handle<PerformanceEntry>> performance_entry_buffer;
|
Vector<JS::NonnullGCPtr<PerformanceEntry>> performance_entry_buffer;
|
||||||
|
|
||||||
// https://www.w3.org/TR/performance-timeline/#dfn-maxbuffersize
|
// https://www.w3.org/TR/performance-timeline/#dfn-maxbuffersize
|
||||||
// An integer maxBufferSize, initialized to the registry value for this entry type.
|
// An integer maxBufferSize, initialized to the registry value for this entry type.
|
||||||
|
@ -45,6 +45,12 @@ struct PerformanceEntryTuple {
|
||||||
// 4. Return true.
|
// 4. Return true.
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void visit_edges(JS::Cell::Visitor& visitor)
|
||||||
|
{
|
||||||
|
for (auto& entry : performance_entry_buffer)
|
||||||
|
visitor.visit(entry);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,229 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023, Luke Wilde <lukew@serenityos.org>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <LibWeb/Bindings/Intrinsics.h>
|
||||||
|
#include <LibWeb/Bindings/PerformanceObserverPrototype.h>
|
||||||
|
#include <LibWeb/HTML/WindowOrWorkerGlobalScope.h>
|
||||||
|
#include <LibWeb/HighResolutionTime/SupportedPerformanceTypes.h>
|
||||||
|
#include <LibWeb/PerformanceTimeline/EntryTypes.h>
|
||||||
|
#include <LibWeb/PerformanceTimeline/PerformanceEntry.h>
|
||||||
|
#include <LibWeb/PerformanceTimeline/PerformanceObserver.h>
|
||||||
|
#include <LibWeb/WebIDL/CallbackType.h>
|
||||||
|
#include <LibWeb/WebIDL/ExceptionOr.h>
|
||||||
|
|
||||||
|
namespace Web::PerformanceTimeline {
|
||||||
|
|
||||||
|
WebIDL::ExceptionOr<JS::NonnullGCPtr<PerformanceObserver>> PerformanceObserver::construct_impl(JS::Realm& realm, JS::GCPtr<WebIDL::CallbackType> callback)
|
||||||
|
{
|
||||||
|
return realm.heap().allocate<PerformanceObserver>(realm, realm, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
PerformanceObserver::PerformanceObserver(JS::Realm& realm, JS::GCPtr<WebIDL::CallbackType> 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<Bindings::PerformanceObserverPrototype>(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<void> PerformanceObserver::observe(PerformanceObserverInit& options)
|
||||||
|
{
|
||||||
|
auto& realm = this->realm();
|
||||||
|
|
||||||
|
// 1. Let relevantGlobal be this's relevant global object.
|
||||||
|
auto* relevant_global = dynamic_cast<HTML::WindowOrWorkerGlobalScopeMixin*>(&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::WindowOrWorkerGlobalScopeMixin*>(&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<JS::Handle<PerformanceTimeline::PerformanceEntry>> PerformanceObserver::take_records()
|
||||||
|
{
|
||||||
|
// The takeRecords() method must return a copy of this's observer buffer, and also empty this's observer buffer.
|
||||||
|
Vector<JS::Handle<PerformanceTimeline::PerformanceEntry>> records;
|
||||||
|
for (auto& record : m_observer_buffer)
|
||||||
|
records.append(*record);
|
||||||
|
m_observer_buffer.clear();
|
||||||
|
return records;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PerformanceObserver::unset_requires_dropped_entries(Badge<HTML::WindowOrWorkerGlobalScopeMixin>)
|
||||||
|
{
|
||||||
|
m_requires_dropped_entries = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PerformanceObserver::append_to_observer_buffer(Badge<HTML::WindowOrWorkerGlobalScopeMixin>, JS::NonnullGCPtr<PerformanceTimeline::PerformanceEntry> entry)
|
||||||
|
{
|
||||||
|
m_observer_buffer.append(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023, Luke Wilde <lukew@serenityos.org>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <LibWeb/Bindings/PlatformObject.h>
|
||||||
|
#include <LibWeb/HTML/WindowOrWorkerGlobalScope.h>
|
||||||
|
|
||||||
|
namespace Web::PerformanceTimeline {
|
||||||
|
|
||||||
|
// https://w3c.github.io/performance-timeline/#dom-performanceobserverinit
|
||||||
|
struct PerformanceObserverInit {
|
||||||
|
Optional<Vector<String>> entry_types;
|
||||||
|
Optional<String> type;
|
||||||
|
Optional<bool> 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<JS::NonnullGCPtr<PerformanceObserver>> construct_impl(JS::Realm&, JS::GCPtr<WebIDL::CallbackType>);
|
||||||
|
virtual ~PerformanceObserver() override;
|
||||||
|
|
||||||
|
WebIDL::ExceptionOr<void> observe(PerformanceObserverInit& options);
|
||||||
|
void disconnect();
|
||||||
|
Vector<JS::Handle<PerformanceTimeline::PerformanceEntry>> take_records();
|
||||||
|
|
||||||
|
bool requires_dropped_entries() const { return m_requires_dropped_entries; }
|
||||||
|
void unset_requires_dropped_entries(Badge<HTML::WindowOrWorkerGlobalScopeMixin>);
|
||||||
|
|
||||||
|
Vector<PerformanceObserverInit> const& options_list() const { return m_options_list; }
|
||||||
|
|
||||||
|
WebIDL::CallbackType& callback() { return *m_callback; }
|
||||||
|
|
||||||
|
void append_to_observer_buffer(Badge<HTML::WindowOrWorkerGlobalScopeMixin>, JS::NonnullGCPtr<PerformanceTimeline::PerformanceEntry>);
|
||||||
|
|
||||||
|
private:
|
||||||
|
PerformanceObserver(JS::Realm&, JS::GCPtr<WebIDL::CallbackType>);
|
||||||
|
|
||||||
|
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<WebIDL::CallbackType> m_callback;
|
||||||
|
|
||||||
|
// https://w3c.github.io/performance-timeline/#dfn-observer-buffer
|
||||||
|
// A PerformanceEntryList object called the observer buffer that is initially empty.
|
||||||
|
Vector<JS::NonnullGCPtr<PerformanceTimeline::PerformanceEntry>> 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<PerformanceObserverInit> m_options_list;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
#import <PerformanceTimeline/PerformanceObserverEntryList.idl>
|
||||||
|
|
||||||
|
// 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<DOMString> 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<DOMString> supportedEntryTypes;
|
||||||
|
};
|
|
@ -0,0 +1,91 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023, Luke Wilde <lukew@serenityos.org>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <AK/QuickSort.h>
|
||||||
|
#include <LibWeb/Bindings/Intrinsics.h>
|
||||||
|
#include <LibWeb/Bindings/PerformanceObserverEntryListPrototype.h>
|
||||||
|
#include <LibWeb/PerformanceTimeline/PerformanceEntry.h>
|
||||||
|
#include <LibWeb/PerformanceTimeline/PerformanceObserverEntryList.h>
|
||||||
|
#include <LibWeb/WebIDL/ExceptionOr.h>
|
||||||
|
|
||||||
|
namespace Web::PerformanceTimeline {
|
||||||
|
|
||||||
|
PerformanceObserverEntryList::PerformanceObserverEntryList(JS::Realm& realm, Vector<JS::NonnullGCPtr<PerformanceTimeline::PerformanceEntry>>&& 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<Bindings::PerformanceObserverEntryListPrototype>(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<Vector<JS::Handle<PerformanceTimeline::PerformanceEntry>>> filter_buffer_by_name_and_type(Vector<JS::NonnullGCPtr<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://w3c.github.io/performance-timeline/#dom-performanceobserverentrylist-getentries
|
||||||
|
WebIDL::ExceptionOr<Vector<JS::Handle<PerformanceTimeline::PerformanceEntry>>> 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<String> {}, /* type= */ Optional<String> {}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://w3c.github.io/performance-timeline/#dom-performanceobserverentrylist-getentriesbytype
|
||||||
|
WebIDL::ExceptionOr<Vector<JS::Handle<PerformanceTimeline::PerformanceEntry>>> 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<String> {}, type));
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://w3c.github.io/performance-timeline/#dom-performanceobserverentrylist-getentriesbyname
|
||||||
|
WebIDL::ExceptionOr<Vector<JS::Handle<PerformanceTimeline::PerformanceEntry>>> PerformanceObserverEntryList::get_entries_by_name(String const& name, Optional<String> 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));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023, Luke Wilde <lukew@serenityos.org>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <LibWeb/Bindings/PlatformObject.h>
|
||||||
|
|
||||||
|
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<Vector<JS::Handle<PerformanceTimeline::PerformanceEntry>>> get_entries() const;
|
||||||
|
WebIDL::ExceptionOr<Vector<JS::Handle<PerformanceTimeline::PerformanceEntry>>> get_entries_by_type(String const& type) const;
|
||||||
|
WebIDL::ExceptionOr<Vector<JS::Handle<PerformanceTimeline::PerformanceEntry>>> get_entries_by_name(String const& name, Optional<String> type) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
PerformanceObserverEntryList(JS::Realm&, Vector<JS::NonnullGCPtr<PerformanceTimeline::PerformanceEntry>>&&);
|
||||||
|
|
||||||
|
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<JS::NonnullGCPtr<PerformanceTimeline::PerformanceEntry>> m_entry_list;
|
||||||
|
};
|
||||||
|
|
||||||
|
ErrorOr<Vector<JS::Handle<PerformanceTimeline::PerformanceEntry>>> filter_buffer_by_name_and_type(Vector<JS::NonnullGCPtr<PerformanceTimeline::PerformanceEntry>> const& buffer, Optional<String> name, Optional<String> type);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
#import <HighResolutionTime/Performance.idl>
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
};
|
|
@ -36,7 +36,7 @@ public:
|
||||||
static Optional<u64> max_buffer_size() { return OptionalNone {}; }
|
static Optional<u64> max_buffer_size() { return OptionalNone {}; }
|
||||||
|
|
||||||
// https://w3c.github.io/timing-entrytypes-registry/#dfn-should-add-entry
|
// 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<PerformanceTimeline::PerformanceObserverInit const&> = {}) const override { return PerformanceTimeline::ShouldAddEntry::Yes; }
|
||||||
|
|
||||||
virtual FlyString const& entry_type() const override;
|
virtual FlyString const& entry_type() const override;
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,7 @@ public:
|
||||||
static Optional<u64> max_buffer_size() { return OptionalNone {}; }
|
static Optional<u64> max_buffer_size() { return OptionalNone {}; }
|
||||||
|
|
||||||
// https://w3c.github.io/timing-entrytypes-registry/#dfn-should-add-entry
|
// 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<PerformanceTimeline::PerformanceObserverInit const&> = {}) const override { return PerformanceTimeline::ShouldAddEntry::Yes; }
|
||||||
|
|
||||||
virtual FlyString const& entry_type() const override;
|
virtual FlyString const& entry_type() const override;
|
||||||
|
|
||||||
|
|
|
@ -199,6 +199,8 @@ libweb_js_bindings(IntersectionObserver/IntersectionObserverEntry)
|
||||||
libweb_js_bindings(MathML/MathMLElement)
|
libweb_js_bindings(MathML/MathMLElement)
|
||||||
libweb_js_bindings(NavigationTiming/PerformanceTiming)
|
libweb_js_bindings(NavigationTiming/PerformanceTiming)
|
||||||
libweb_js_bindings(PerformanceTimeline/PerformanceEntry)
|
libweb_js_bindings(PerformanceTimeline/PerformanceEntry)
|
||||||
|
libweb_js_bindings(PerformanceTimeline/PerformanceObserver)
|
||||||
|
libweb_js_bindings(PerformanceTimeline/PerformanceObserverEntryList)
|
||||||
libweb_js_bindings(RequestIdleCallback/IdleDeadline)
|
libweb_js_bindings(RequestIdleCallback/IdleDeadline)
|
||||||
libweb_js_bindings(ResizeObserver/ResizeObserver)
|
libweb_js_bindings(ResizeObserver/ResizeObserver)
|
||||||
libweb_js_bindings(Streams/ByteLengthQueuingStrategy)
|
libweb_js_bindings(Streams/ByteLengthQueuingStrategy)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue