mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2026-04-19 02:10:26 +00:00
The timer nesting level throttling change (7577fd2a57) inadvertently reverted the repeating timer optimization from4d27e9aa5e, causing setInterval() to use single-shot timers that re-arm after the callback completes. This meant the next firing was scheduled relative to callback completion rather than the previous fire time, causing drift proportional to callback execution time. For example, DiabloWeb's 50ms game loop with ~20ms of WASM work per frame was only achieving 14 FPS (71ms intervals) instead of 20 FPS. Fix this by using repeating Core::Timer for setInterval() again. The repeating timer fires on schedule regardless of callback duration. On re-arm, we update the callback (for nesting level changes) and the interval (in case nesting level clamping kicks in), but don't restart the timer since it's already running on the correct schedule.
187 lines
8.4 KiB
C++
187 lines
8.4 KiB
C++
/*
|
|
* 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>
|
|
#include <AK/Variant.h>
|
|
#include <LibWeb/Bindings/PlatformObject.h>
|
|
#include <LibWeb/Export.h>
|
|
#include <LibWeb/Fetch/Request.h>
|
|
#include <LibWeb/Forward.h>
|
|
#include <LibWeb/HTML/ImageBitmap.h>
|
|
#include <LibWeb/PerformanceTimeline/PerformanceEntry.h>
|
|
#include <LibWeb/PerformanceTimeline/PerformanceEntryTuple.h>
|
|
#include <LibWeb/ServiceWorker/CacheStorage.h>
|
|
#include <LibWeb/WebSockets/WebSocket.h>
|
|
|
|
namespace Web::HTML {
|
|
|
|
// https://html.spec.whatwg.org/multipage/webappapis.html#timerhandler
|
|
using TimerHandler = Variant<GC::Ref<WebIDL::CallbackType>, String>;
|
|
|
|
// https://html.spec.whatwg.org/multipage/webappapis.html#windoworworkerglobalscope
|
|
class WEB_API WindowOrWorkerGlobalScopeMixin {
|
|
public:
|
|
virtual ~WindowOrWorkerGlobalScopeMixin();
|
|
|
|
virtual DOM::EventTarget& this_impl() = 0;
|
|
virtual DOM::EventTarget const& this_impl() const = 0;
|
|
|
|
// JS API functions
|
|
String origin() const;
|
|
bool is_secure_context() const;
|
|
bool cross_origin_isolated() const;
|
|
GC::Ref<WebIDL::Promise> create_image_bitmap(ImageBitmapSource image, Optional<ImageBitmapOptions> options = {}) const;
|
|
GC::Ref<WebIDL::Promise> create_image_bitmap(ImageBitmapSource image, WebIDL::Long sx, WebIDL::Long sy, WebIDL::Long sw, WebIDL::Long sh, Optional<ImageBitmapOptions> options = {}) const;
|
|
GC::Ref<WebIDL::Promise> fetch(Fetch::RequestInfo const&, Fetch::RequestInit const&) const;
|
|
|
|
i32 set_timeout(TimerHandler, i32 timeout, GC::RootVector<JS::Value> arguments);
|
|
i32 set_interval(TimerHandler, i32 timeout, GC::RootVector<JS::Value> arguments);
|
|
void clear_timeout(i32);
|
|
void clear_interval(i32);
|
|
void clear_map_of_active_timers();
|
|
|
|
enum class CheckIfPerformanceBufferIsFull {
|
|
No,
|
|
Yes,
|
|
};
|
|
|
|
PerformanceTimeline::PerformanceEntryTuple& relevant_performance_entry_tuple(FlyString const& entry_type);
|
|
void queue_performance_entry(GC::Ref<PerformanceTimeline::PerformanceEntry> new_entry);
|
|
void add_performance_entry(GC::Ref<PerformanceTimeline::PerformanceEntry> new_entry, CheckIfPerformanceBufferIsFull check_if_performance_buffer_is_full = CheckIfPerformanceBufferIsFull::No);
|
|
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);
|
|
|
|
ErrorOr<Vector<GC::Root<PerformanceTimeline::PerformanceEntry>>> filter_buffer_map_by_name_and_type(Optional<String> name, Optional<String> type) const;
|
|
|
|
void register_performance_observer(Badge<PerformanceTimeline::PerformanceObserver>, GC::Ref<PerformanceTimeline::PerformanceObserver>);
|
|
void unregister_performance_observer(Badge<PerformanceTimeline::PerformanceObserver>, GC::Ref<PerformanceTimeline::PerformanceObserver>);
|
|
bool has_registered_performance_observer(GC::Ref<PerformanceTimeline::PerformanceObserver>);
|
|
|
|
void queue_the_performance_observer_task();
|
|
|
|
void set_resource_timing_buffer_size_limit(Badge<HighResolutionTime::Performance>, u32 value) { m_resource_timing_buffer_size_limit = value; }
|
|
void add_resource_timing_entry(Badge<ResourceTiming::PerformanceResourceTiming>, GC::Ref<ResourceTiming::PerformanceResourceTiming> entry);
|
|
|
|
void register_event_source(Badge<EventSource>, GC::Ref<EventSource>);
|
|
void unregister_event_source(Badge<EventSource>, GC::Ref<EventSource>);
|
|
void forcibly_close_all_event_sources();
|
|
|
|
void register_web_socket(Badge<WebSockets::WebSocket>, GC::Ref<WebSockets::WebSocket>);
|
|
void unregister_web_socket(Badge<WebSockets::WebSocket>, GC::Ref<WebSockets::WebSocket>);
|
|
|
|
enum class AffectedAnyWebSockets {
|
|
No,
|
|
Yes,
|
|
};
|
|
AffectedAnyWebSockets make_disappear_all_web_sockets();
|
|
|
|
void run_steps_after_a_timeout(i32 timeout, Function<void()> completion_step);
|
|
|
|
[[nodiscard]] GC::Ref<HighResolutionTime::Performance> performance();
|
|
|
|
GC::Ref<JS::Object> supported_entry_types() const;
|
|
|
|
GC::Ref<IndexedDB::IDBFactory> indexed_db();
|
|
|
|
void report_error(JS::Value e);
|
|
|
|
enum class OmitError {
|
|
Yes,
|
|
No,
|
|
};
|
|
void report_an_exception(JS::Value exception, OmitError = OmitError::No);
|
|
|
|
[[nodiscard]] GC::Ref<Crypto::Crypto> crypto();
|
|
|
|
[[nodiscard]] GC::Ref<ServiceWorker::CacheStorage> caches();
|
|
|
|
[[nodiscard]] GC::Ref<TrustedTypes::TrustedTypePolicyFactory> trusted_types();
|
|
|
|
Optional<URL::Origin> window_or_worker_global_scope_extract_an_origin() const;
|
|
|
|
protected:
|
|
void initialize(JS::Realm&);
|
|
void visit_edges(JS::Cell::Visitor&);
|
|
void finalize();
|
|
|
|
private:
|
|
enum class Repeat {
|
|
Yes,
|
|
No,
|
|
};
|
|
i32 run_timer_initialization_steps(TimerHandler handler, i32 timeout, GC::RootVector<JS::Value> arguments, Repeat repeat, Optional<i32> previous_id = {});
|
|
void run_steps_after_a_timeout_impl(i32 timeout, Function<void()> completion_step, Optional<i32> timer_key, Repeat repeat = Repeat::No);
|
|
|
|
GC::Ref<WebIDL::Promise> create_image_bitmap_impl(ImageBitmapSource& image, Optional<WebIDL::Long> sx, Optional<WebIDL::Long> sy, Optional<WebIDL::Long> sw, Optional<WebIDL::Long> sh, Optional<ImageBitmapOptions>& options) const;
|
|
|
|
size_t resource_timing_buffer_current_size();
|
|
bool can_add_resource_timing_entry();
|
|
void fire_resource_timing_buffer_full_event();
|
|
void copy_resource_timing_secondary_buffer();
|
|
|
|
IDAllocator m_timer_id_allocator;
|
|
HashMap<int, GC::Ref<Timer>> m_timers;
|
|
|
|
// https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timer-nesting-level
|
|
HashMap<i32, u32> m_timer_nesting_levels;
|
|
|
|
// https://www.w3.org/TR/performance-timeline/#performance-timeline
|
|
// Each global object has:
|
|
// - 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<GC::Ref<PerformanceTimeline::PerformanceObserver>> 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:
|
|
// NOTE: See the PerformanceEntryTuple struct above for the map's value tuple.
|
|
OrderedHashMap<FlyString, PerformanceTimeline::PerformanceEntryTuple> m_performance_entry_buffer_map;
|
|
|
|
HashTable<GC::Ref<EventSource>> m_registered_event_sources;
|
|
|
|
GC::Ptr<HighResolutionTime::Performance> m_performance;
|
|
|
|
GC::Ptr<IndexedDB::IDBFactory> m_indexed_db;
|
|
|
|
mutable GC::Ptr<JS::Object> m_supported_entry_types_array;
|
|
|
|
GC::Ptr<Crypto::Crypto> m_crypto;
|
|
|
|
GC::Ptr<ServiceWorker::CacheStorage> m_cache_storage;
|
|
|
|
GC::Ptr<TrustedTypes::TrustedTypePolicyFactory> m_trusted_type_policy_factory;
|
|
|
|
bool m_error_reporting_mode { false };
|
|
|
|
WebSockets::WebSocket::List m_registered_web_sockets;
|
|
|
|
// https://w3c.github.io/resource-timing/#sec-extensions-performance-interface
|
|
// Each ECMAScript global environment has:
|
|
// https://w3c.github.io/resource-timing/#dfn-resource-timing-buffer-size-limit
|
|
// A resource timing buffer size limit which should initially be 250 or greater.
|
|
// The recommended minimum number of PerformanceResourceTiming objects is 250, though this may be changed by the
|
|
// user agent. setResourceTimingBufferSize can be called to request a change to this limit.
|
|
u32 m_resource_timing_buffer_size_limit { 250 };
|
|
|
|
// https://w3c.github.io/resource-timing/#dfn-resource-timing-buffer-full-event-pending-flag
|
|
// A resource timing buffer full event pending flag which is initially false.
|
|
bool m_resource_timing_buffer_full_event_pending { false };
|
|
|
|
// https://w3c.github.io/resource-timing/#dfn-resource-timing-secondary-buffer-current-size
|
|
// A resource timing secondary buffer current size which is initially 0.
|
|
// https://w3c.github.io/resource-timing/#dfn-resource-timing-secondary-buffer
|
|
// A resource timing secondary buffer to store PerformanceResourceTiming objects that is initially empty.
|
|
Vector<GC::Ref<ResourceTiming::PerformanceResourceTiming>> m_resource_timing_secondary_buffer;
|
|
};
|
|
|
|
}
|