LibWeb: Introduce Performance Timeline and its Performance functions

This commit is contained in:
Luke Wilde 2023-03-22 19:12:57 +00:00 committed by Linus Groh
parent 4c3f1481ea
commit 31b507afbf
Notes: sideshowbarker 2024-07-17 21:16:31 +09:00
16 changed files with 381 additions and 1 deletions

View File

@ -44,6 +44,7 @@ static bool is_platform_object(Type const& type)
"NamedNodeMap"sv,
"Node"sv,
"Path2D"sv,
"PerformanceEntry"sv,
"Range"sv,
"ReadableStream"sv,
"Request"sv,
@ -3145,6 +3146,7 @@ using namespace Web::Geometry;
using namespace Web::HighResolutionTime;
using namespace Web::HTML;
using namespace Web::IntersectionObserver;
using namespace Web::PerformanceTimeline;
using namespace Web::RequestIdleCallback;
using namespace Web::ResizeObserver;
using namespace Web::Selection;
@ -3377,6 +3379,7 @@ using namespace Web::HighResolutionTime;
using namespace Web::HTML;
using namespace Web::IntersectionObserver;
using namespace Web::NavigationTiming;
using namespace Web::PerformanceTimeline;
using namespace Web::RequestIdleCallback;
using namespace Web::ResizeObserver;
using namespace Web::Selection;
@ -3506,6 +3509,7 @@ using namespace Web::HighResolutionTime;
using namespace Web::HTML;
using namespace Web::IntersectionObserver;
using namespace Web::NavigationTiming;
using namespace Web::PerformanceTimeline;
using namespace Web::RequestIdleCallback;
using namespace Web::ResizeObserver;
using namespace Web::Selection;
@ -3636,6 +3640,7 @@ using namespace Web::HighResolutionTime;
using namespace Web::HTML;
using namespace Web::IntersectionObserver;
using namespace Web::NavigationTiming;
using namespace Web::PerformanceTimeline;
using namespace Web::RequestIdleCallback;
using namespace Web::ResizeObserver;
using namespace Web::Selection;

View File

@ -32,6 +32,7 @@
#include <LibWeb/HTML/Window.h>
#include <LibWeb/HTML/WindowProxy.h>
#include <LibWeb/Namespace.h>
#include <LibWeb/PerformanceTimeline/EntryTypes.h>
#include <LibWeb/Platform/EventLoopPlugin.h>
#include <LibWeb/SVG/AttributeNames.h>
#include <LibWeb/SVG/TagNames.h>
@ -79,6 +80,7 @@ ErrorOr<void> initialize_main_thread_vm()
TRY(HTML::EventNames::initialize_strings());
TRY(HTML::TagNames::initialize_strings());
TRY(Namespace::initialize_strings());
TRY(PerformanceTimeline::EntryTypes::initialize_strings());
TRY(SVG::AttributeNames::initialize_strings());
TRY(SVG::TagNames::initialize_strings());
TRY(UIEvents::EventNames::initialize_strings());

View File

@ -371,6 +371,7 @@ set(SOURCES
Loader/ResourceLoader.cpp
MimeSniff/MimeType.cpp
Namespace.cpp
NavigationTiming/EntryNames.cpp
NavigationTiming/PerformanceTiming.cpp
Page/EditEventHandler.cpp
Page/EventHandler.cpp
@ -400,6 +401,8 @@ set(SOURCES
Painting/ShadowPainting.cpp
Painting/StackingContext.cpp
Painting/TextPaintable.cpp
PerformanceTimeline/EntryTypes.cpp
PerformanceTimeline/PerformanceEntry.cpp
Platform/EventLoopPlugin.cpp
Platform/EventLoopPluginSerenity.cpp
Platform/FontPlugin.cpp

View File

@ -387,6 +387,10 @@ struct BorderRadiiData;
struct LinearGradientData;
}
namespace Web::PerformanceTimeline {
class PerformanceEntry;
}
namespace Web::Platform {
class Timer;
}

View File

@ -1,11 +1,13 @@
/*
* Copyright (c) 2022, Andrew Kaster <akaster@serenityos.org>
* Copyright (c) 2023, Linus Groh <linusg@serenityos.org>
* Copyright (c) 2023, Luke Wilde <lukew@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Base64.h>
#include <AK/QuickSort.h>
#include <AK/String.h>
#include <AK/Utf8View.h>
#include <AK/Vector.h>
@ -264,4 +266,81 @@ i32 WindowOrWorkerGlobalScopeMixin::run_timer_initialization_steps(TimerHandler
return id;
}
// https://www.w3.org/TR/performance-timeline/#dfn-filter-buffer-by-name-and-type
static ErrorOr<Vector<JS::Handle<PerformanceTimeline::PerformanceEntry>>> filter_buffer_by_name_and_type(Vector<JS::Handle<PerformanceTimeline::PerformanceEntry>> const& buffer, Optional<String> name, Optional<String> type)
{
// 1. Let result be an initially empty list.
Vector<JS::Handle<PerformanceTimeline::PerformanceEntry>> result;
// 2. For each PerformanceEntry entry in buffer, run the following steps:
for (auto const& entry : buffer) {
// 1. If type is not null and if type is not identical to entry's entryType attribute, continue to next entry.
if (type.has_value() && type.value() != entry->entry_type())
continue;
// 2. If name is not null and if name is not identical to entry's name attribute, continue to next entry.
if (name.has_value() && name.value() != entry->name())
continue;
// 3. append entry to result.
TRY(result.try_append(entry));
}
// 3. Sort results's entries in chronological order with respect to startTime
quick_sort(result, [](auto const& left_entry, auto const& right_entry) {
return left_entry->start_time() < right_entry->start_time();
});
// 4. Return result.
return result;
}
// https://www.w3.org/TR/performance-timeline/#dfn-filter-buffer-map-by-name-and-type
ErrorOr<Vector<JS::Handle<PerformanceTimeline::PerformanceEntry>>> WindowOrWorkerGlobalScopeMixin::filter_buffer_map_by_name_and_type(Optional<String> name, Optional<String> type) const
{
// 1. Let result be an initially empty list.
Vector<JS::Handle<PerformanceTimeline::PerformanceEntry>> result;
// 2. Let map be the performance entry buffer map associated with the relevant global object of this.
auto const& map = m_performance_entry_buffer_map;
// 3. Let tuple list be an empty list.
Vector<PerformanceTimeline::PerformanceEntryTuple const&> tuple_list;
// 4. If type is not null, append the result of getting the value of entry on map given type as key to tuple list.
// Otherwise, assign the result of get the values on map to tuple list.
if (type.has_value()) {
auto maybe_tuple = map.get(type.value());
if (maybe_tuple.has_value())
TRY(tuple_list.try_append(maybe_tuple.release_value()));
} else {
for (auto const& it : map)
TRY(tuple_list.try_append(it.value));
}
// 5. For each tuple in tuple list, run the following steps:
for (auto const& tuple : tuple_list) {
// 1. Let buffer be tuple's performance entry buffer.
auto const& buffer = tuple.performance_entry_buffer;
// 2. If tuple's availableFromTimeline is false, continue to the next tuple.
if (tuple.available_from_timeline == PerformanceTimeline::AvailableFromTimeline::No)
continue;
// 3. Let entries be the result of running filter buffer by name and type with buffer, name and type as inputs.
auto entries = TRY(filter_buffer_by_name_and_type(buffer, name, type));
// 4. For each entry in entries, append entry to result.
TRY(result.try_extend(entries));
}
// 6. 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();
});
// 7. Return result.
return result;
}
}

View File

@ -1,11 +1,13 @@
/*
* Copyright (c) 2023, Linus Groh <linusg@serenityos.org>
* Copyright (c) 2023, Luke Wilde <lukew@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/FlyString.h>
#include <AK/Forward.h>
#include <AK/HashMap.h>
#include <AK/IDAllocator.h>
@ -14,6 +16,8 @@
#include <LibWeb/Fetch/Request.h>
#include <LibWeb/Forward.h>
#include <LibWeb/HTML/MessagePort.h>
#include <LibWeb/PerformanceTimeline/PerformanceEntry.h>
#include <LibWeb/PerformanceTimeline/PerformanceEntryTuple.h>
namespace Web::HTML {
@ -43,6 +47,8 @@ public:
void clear_timeout(i32);
void clear_interval(i32);
ErrorOr<Vector<JS::Handle<PerformanceTimeline::PerformanceEntry>>> filter_buffer_map_by_name_and_type(Optional<String> name, Optional<String> type) const;
protected:
void visit_edges(JS::Cell::Visitor&);
@ -55,6 +61,16 @@ private:
IDAllocator m_timer_id_allocator;
HashMap<int, JS::NonnullGCPtr<Timer>> m_timers;
// 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
// 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:
// NOTE: See the PerformanceEntryTuple struct above for the map's value tuple.
OrderedHashMap<FlyString, PerformanceTimeline::PerformanceEntryTuple> m_performance_entry_buffer_map;
};
}

View File

@ -1,5 +1,6 @@
/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2023, Luke Wilde <lukew@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -49,4 +50,44 @@ double Performance::time_origin() const
return static_cast<double>(m_timer.origin_time().to_milliseconds());
}
// https://www.w3.org/TR/performance-timeline/#getentries-method
WebIDL::ExceptionOr<Vector<JS::Handle<PerformanceTimeline::PerformanceEntry>>> Performance::get_entries() const
{
auto& realm = this->realm();
auto& vm = this->vm();
auto* window_or_worker = dynamic_cast<HTML::WindowOrWorkerGlobalScopeMixin*>(&realm.global_object());
VERIFY(window_or_worker);
// Returns a PerformanceEntryList object returned by the filter buffer map by name and type algorithm with name and
// type set to null.
return TRY_OR_THROW_OOM(vm, window_or_worker->filter_buffer_map_by_name_and_type(/* name= */ Optional<String> {}, /* type= */ Optional<String> {}));
}
// https://www.w3.org/TR/performance-timeline/#dom-performance-getentriesbytype
WebIDL::ExceptionOr<Vector<JS::Handle<PerformanceTimeline::PerformanceEntry>>> Performance::get_entries_by_type(String const& type) const
{
auto& realm = this->realm();
auto& vm = this->vm();
auto* window_or_worker = dynamic_cast<HTML::WindowOrWorkerGlobalScopeMixin*>(&realm.global_object());
VERIFY(window_or_worker);
// Returns a PerformanceEntryList object returned by filter buffer map by name and type algorithm with name set to null,
// and type set to the method's input type parameter.
return TRY_OR_THROW_OOM(vm, window_or_worker->filter_buffer_map_by_name_and_type(/* name= */ Optional<String> {}, type));
}
// https://www.w3.org/TR/performance-timeline/#dom-performance-getentriesbyname
WebIDL::ExceptionOr<Vector<JS::Handle<PerformanceTimeline::PerformanceEntry>>> Performance::get_entries_by_name(String const& name, Optional<String> type) const
{
auto& realm = this->realm();
auto& vm = this->vm();
auto* window_or_worker = dynamic_cast<HTML::WindowOrWorkerGlobalScopeMixin*>(&realm.global_object());
VERIFY(window_or_worker);
// Returns a PerformanceEntryList object returned by filter buffer map by name and type algorithm with 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, window_or_worker->filter_buffer_map_by_name_and_type(name, type));
}
}

View File

@ -1,5 +1,6 @@
/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2023, Luke Wilde <lukew@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -22,6 +23,10 @@ public:
JS::GCPtr<NavigationTiming::PerformanceTiming> timing();
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:
explicit Performance(HTML::Window&);

View File

@ -1,12 +1,21 @@
#import <DOM/EventTarget.idl>
#import <HighResolutionTime/DOMHighResTimeStamp.idl>
#import <NavigationTiming/PerformanceTiming.idl>
#import <PerformanceTimeline/PerformanceEntry.idl>
// https://www.w3.org/TR/performance-timeline/#dom-performanceentrylist
typedef sequence<PerformanceEntry> PerformanceEntryList;
// https://w3c.github.io/hr-time/#sec-performance
[Exposed=(Window, Worker)]
[Exposed=(Window, Worker), UseNewAKString]
interface Performance : EventTarget {
DOMHighResTimeStamp now();
readonly attribute DOMHighResTimeStamp timeOrigin;
readonly attribute PerformanceTiming timing;
// https://www.w3.org/TR/performance-timeline/#extensions-to-the-performance-interface
// "Performance Timeline" extensions to the Performance interface
PerformanceEntryList getEntries();
PerformanceEntryList getEntriesByType(DOMString type);
PerformanceEntryList getEntriesByName(DOMString name, optional DOMString type);
};

View File

@ -0,0 +1,34 @@
/*
* Copyright (c) 2023, Luke Wilde <lukew@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibWeb/PerformanceTimeline/EntryTypes.h>
namespace Web::PerformanceTimeline::EntryTypes {
#define __ENUMERATE_PERFORMANCE_TIMELINE_ENTRY_TYPE(name) FlyString name;
ENUMERATE_PERFORMANCE_TIMELINE_ENTRY_TYPES
#undef __ENUMERATE_PERFORMANCE_TIMELINE_ENTRY_TYPE
ErrorOr<void> initialize_strings()
{
static bool s_initialized = false;
VERIFY(!s_initialized);
#define __ENUMERATE_PERFORMANCE_TIMELINE_ENTRY_TYPE(name) \
name = TRY(#name##_fly_string);
ENUMERATE_PERFORMANCE_TIMELINE_ENTRY_TYPES
#undef __ENUMERATE_PERFORMANCE_TIMELINE_ENTRY_TYPE
// NOTE: Special cases for attributes with dashes in them.
first_input = TRY("first-input"_fly_string);
largest_contentful_paint = TRY("largest-contentful-paint"_fly_string);
layout_shift = TRY("layout-shift"_fly_string);
s_initialized = true;
return {};
}
}

View File

@ -0,0 +1,33 @@
/*
* Copyright (c) 2023, Luke Wilde <lukew@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/FlyString.h>
namespace Web::PerformanceTimeline::EntryTypes {
// https://w3c.github.io/timing-entrytypes-registry/#registry
#define ENUMERATE_PERFORMANCE_TIMELINE_ENTRY_TYPES \
__ENUMERATE_PERFORMANCE_TIMELINE_ENTRY_TYPE(element) \
__ENUMERATE_PERFORMANCE_TIMELINE_ENTRY_TYPE(event) \
__ENUMERATE_PERFORMANCE_TIMELINE_ENTRY_TYPE(first_input) \
__ENUMERATE_PERFORMANCE_TIMELINE_ENTRY_TYPE(largest_contentful_paint) \
__ENUMERATE_PERFORMANCE_TIMELINE_ENTRY_TYPE(layout_shift) \
__ENUMERATE_PERFORMANCE_TIMELINE_ENTRY_TYPE(longtask) \
__ENUMERATE_PERFORMANCE_TIMELINE_ENTRY_TYPE(mark) \
__ENUMERATE_PERFORMANCE_TIMELINE_ENTRY_TYPE(measure) \
__ENUMERATE_PERFORMANCE_TIMELINE_ENTRY_TYPE(navigation) \
__ENUMERATE_PERFORMANCE_TIMELINE_ENTRY_TYPE(resource) \
__ENUMERATE_PERFORMANCE_TIMELINE_ENTRY_TYPE(paint)
#define __ENUMERATE_PERFORMANCE_TIMELINE_ENTRY_TYPE(name) extern FlyString name;
ENUMERATE_PERFORMANCE_TIMELINE_ENTRY_TYPES
#undef __ENUMERATE_PERFORMANCE_TIMELINE_ENTRY_TYPE
ErrorOr<void> initialize_strings();
}

View File

@ -0,0 +1,31 @@
/*
* Copyright (c) 2023, Luke Wilde <lukew@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/Bindings/PerformanceEntryPrototype.h>
#include <LibWeb/PerformanceTimeline/PerformanceEntry.h>
namespace Web::PerformanceTimeline {
PerformanceEntry::PerformanceEntry(JS::Realm& realm, String const& name, HighResolutionTime::DOMHighResTimeStamp start_time, HighResolutionTime::DOMHighResTimeStamp duration)
: Bindings::PlatformObject(realm)
, m_name(name)
, m_start_time(start_time)
, m_duration(duration)
{
}
PerformanceEntry::~PerformanceEntry() = default;
JS::ThrowCompletionOr<void> PerformanceEntry::initialize(JS::Realm& realm)
{
MUST_OR_THROW_OOM(Base::initialize(realm));
set_prototype(&Bindings::ensure_web_prototype<Bindings::PerformanceEntryPrototype>(realm, "PerformanceEntry"));
return {};
}
}

View File

@ -0,0 +1,56 @@
/*
* Copyright (c) 2023, Luke Wilde <lukew@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibWeb/Bindings/PlatformObject.h>
#include <LibWeb/HighResolutionTime/DOMHighResTimeStamp.h>
namespace Web::PerformanceTimeline {
enum class AvailableFromTimeline {
No,
Yes,
};
enum class ShouldAddEntry {
No,
Yes,
};
// https://www.w3.org/TR/performance-timeline/#dom-performanceentry
class PerformanceEntry : public Bindings::PlatformObject {
WEB_PLATFORM_OBJECT(PerformanceEntry, Bindings::PlatformObject);
public:
virtual ~PerformanceEntry();
// https://www.w3.org/TR/performance-timeline/#dom-performanceentry-entrytype
virtual FlyString const& entry_type() const = 0;
String const& name() const { return m_name; }
HighResolutionTime::DOMHighResTimeStamp start_time() const { return m_start_time; }
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;
protected:
PerformanceEntry(JS::Realm&, String const& name, HighResolutionTime::DOMHighResTimeStamp start_time, HighResolutionTime::DOMHighResTimeStamp duration);
virtual JS::ThrowCompletionOr<void> initialize(JS::Realm&) override;
private:
// https://www.w3.org/TR/performance-timeline/#dom-performanceentry-name
String m_name;
// https://www.w3.org/TR/performance-timeline/#dom-performanceentry-starttime
HighResolutionTime::DOMHighResTimeStamp m_start_time { 0.0 };
// https://www.w3.org/TR/performance-timeline/#dom-performanceentry-duration
HighResolutionTime::DOMHighResTimeStamp m_duration { 0.0 };
};
}

View File

@ -0,0 +1,11 @@
#import <HighResolutionTime/DOMHighResTimeStamp.idl>
// https://www.w3.org/TR/performance-timeline/#dom-performanceentry
[Exposed=(Window,Worker), UseNewAKString]
interface PerformanceEntry {
readonly attribute DOMString name;
readonly attribute DOMString entryType;
readonly attribute DOMHighResTimeStamp startTime;
readonly attribute DOMHighResTimeStamp duration;
[Default] object toJSON();
};

View File

@ -0,0 +1,50 @@
/*
* Copyright (c) 2023, Luke Wilde <lukew@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibWeb/PerformanceTimeline/PerformanceEntry.h>
namespace Web::PerformanceTimeline {
// https://www.w3.org/TR/performance-timeline/#dfn-performance-entry-buffer-map
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<JS::Handle<PerformanceEntry>> performance_entry_buffer;
// https://www.w3.org/TR/performance-timeline/#dfn-maxbuffersize
// An integer maxBufferSize, initialized to the registry value for this entry type.
// NOTE: The empty state represents Infinite size.
Optional<u64> max_buffer_size;
// https://www.w3.org/TR/performance-timeline/#dfn-availablefromtimeline
// A boolean availableFromTimeline, initialized to the registry value for this entry type.
AvailableFromTimeline available_from_timeline { AvailableFromTimeline::No };
// https://www.w3.org/TR/performance-timeline/#dfn-dropped-entries-count
// An integer dropped entries count that is initially 0.
u64 dropped_entries_count { 0 };
// https://www.w3.org/TR/performance-timeline/#dfn-determine-if-a-performance-entry-buffer-is-full
bool is_full()
{
// 1. Let num current entries be the size of tuple's performance entry buffer.
auto num_current_entries = performance_entry_buffer.size();
// 2. If num current entries is less than tuples's maxBufferSize, return false.
if (!max_buffer_size.has_value() || num_current_entries < max_buffer_size.value())
return false;
// 3. Increase tuple's dropped entries count by 1.
++dropped_entries_count;
// 4. Return true.
return true;
}
};
}

View File

@ -172,6 +172,7 @@ libweb_js_bindings(HTML/WorkerNavigator)
libweb_js_bindings(HighResolutionTime/Performance)
libweb_js_bindings(IntersectionObserver/IntersectionObserver)
libweb_js_bindings(NavigationTiming/PerformanceTiming)
libweb_js_bindings(PerformanceTimeline/PerformanceEntry)
libweb_js_bindings(RequestIdleCallback/IdleDeadline)
libweb_js_bindings(ResizeObserver/ResizeObserver)
libweb_js_bindings(Streams/ReadableStream)