mirror of
https://github.com/lexmount/moli.git
synced 2026-10-30 08:07:38 +00:00
feat(performance): share native User Timing and observers with workers
This commit is contained in:
@@ -347,13 +347,13 @@ pub(crate) use self::media_queries::{
|
||||
simple_object_event_target_add_listener, simple_object_event_target_remove_listener,
|
||||
simple_object_has_event_listeners,
|
||||
};
|
||||
use self::message_ports::schedule_host_callback;
|
||||
pub(crate) use self::message_ports::{
|
||||
MessagePortDeliveryRunResult, MessagePortRealmBinding, detach_message_port_owner_for_transfer,
|
||||
detach_transferred_message_port, dispatch_message_port_events_for_port_collecting_errors,
|
||||
dispatch_one_authorized_message_port_event, ensure_message_port_wrapper_for_id,
|
||||
ensure_message_port_wrapper_for_id_in_realm, message_port_id_from_object,
|
||||
};
|
||||
use self::message_ports::{schedule_host_callback, schedule_scope_callback};
|
||||
pub(crate) use self::microtask_checkpoint::{
|
||||
install_agent_microtask_checkpoint_tasks, run_end_of_microtask_checkpoint_tasks,
|
||||
};
|
||||
@@ -806,6 +806,17 @@ struct WorkerAbortControllerTemplateDeclaration {
|
||||
abort: (),
|
||||
}
|
||||
|
||||
#[derive(Default, WebApiObject)]
|
||||
#[webapi(fragment, prototype = "WorkerGlobalScope", enumerable)]
|
||||
struct WorkerGlobalScopePerformancePrototypeDeclaration {
|
||||
#[webapi(
|
||||
accessor_property,
|
||||
getter = worker_performance_getter_callback,
|
||||
setter = worker_performance_setter_callback
|
||||
)]
|
||||
performance: (),
|
||||
}
|
||||
|
||||
#[derive(Default, WebApiObject)]
|
||||
#[webapi(fragment, prototype = "WorkerGlobalScope", enumerable)]
|
||||
struct WorkerGlobalScopeCryptoPrototypeDeclaration {
|
||||
@@ -821,6 +832,75 @@ pub(crate) fn initialize_worker_fetch_realm_state<'s>(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn initialize_worker_performance_realm_state<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
global: v8::Local<'s, v8::Object>,
|
||||
) -> Result<()> {
|
||||
self::performance_runtime::install_worker_performance_runtime_state(scope, global)?;
|
||||
install_worker_performance_global_attribute(scope, global)
|
||||
}
|
||||
|
||||
fn install_worker_performance_global_attribute<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
global: v8::Local<'s, v8::Object>,
|
||||
) -> Result<()> {
|
||||
let performance = get_own_static_property(scope, global, WINDOW_PERFORMANCE_SLOT)
|
||||
.filter(|value| !value.is_undefined())
|
||||
.ok_or_else(|| anyhow!("worker performance runtime state did not install performance"))?;
|
||||
let Some(prototype) = global_constructor_prototype(scope, "WorkerGlobalScope") else {
|
||||
return define_global_value(scope, global, "performance", performance);
|
||||
};
|
||||
WorkerGlobalScopePerformancePrototypeDeclaration::default()
|
||||
.initialize(scope, prototype)
|
||||
.map_err(|error| anyhow!("failed to initialize WorkerGlobalScope performance: {error}"))
|
||||
}
|
||||
|
||||
fn worker_performance_getter_callback<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
if !require_current_worker_global_receiver(scope, args.this()) {
|
||||
return;
|
||||
}
|
||||
let global = scope.get_current_context().global(scope);
|
||||
match get_own_static_property(scope, global, WINDOW_PERFORMANCE_SLOT)
|
||||
.filter(|value| !value.is_undefined())
|
||||
{
|
||||
Some(value) => rv.set(value),
|
||||
None => rv.set_undefined(),
|
||||
}
|
||||
}
|
||||
|
||||
fn worker_performance_setter_callback<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
_rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
let receiver = args.this();
|
||||
if !require_current_worker_global_receiver(scope, receiver) {
|
||||
return;
|
||||
}
|
||||
let _ = receiver.define_own_property(
|
||||
scope,
|
||||
v8str(scope, "performance").into(),
|
||||
args.get(0),
|
||||
v8::PropertyAttribute::NONE,
|
||||
);
|
||||
}
|
||||
|
||||
fn require_current_worker_global_receiver(
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
receiver: v8::Local<'_, v8::Object>,
|
||||
) -> bool {
|
||||
let global = scope.get_current_context().global(scope);
|
||||
if receiver.strict_equals(global.into()) {
|
||||
return true;
|
||||
}
|
||||
throw_type_error(scope, "Illegal invocation");
|
||||
false
|
||||
}
|
||||
|
||||
pub(crate) fn initialize_worker_crypto_realm_state<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
global: v8::Local<'s, v8::Object>,
|
||||
@@ -860,11 +940,10 @@ fn worker_crypto_getter_callback<'s>(
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
let global = scope.get_current_context().global(scope);
|
||||
if !args.this().strict_equals(global.into()) {
|
||||
throw_type_error(scope, "Illegal invocation");
|
||||
if !require_current_worker_global_receiver(scope, args.this()) {
|
||||
return;
|
||||
}
|
||||
let global = scope.get_current_context().global(scope);
|
||||
match self::crypto::ensure_worker_crypto_for_global(scope, global) {
|
||||
Ok(crypto) => rv.set(crypto.into()),
|
||||
Err(error) => throw_error(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use anyhow::{Context, Result, anyhow};
|
||||
|
||||
use super::metadata::RealmKind;
|
||||
use crate::context_bootstrap::{
|
||||
crypto::finalize_crypto_realm_bindings, events::finalize_pointer_event_realm_bindings,
|
||||
navigator_runtime::finalize_navigator_credentials_realm_bindings,
|
||||
@@ -20,6 +21,7 @@ enum RealmDependentFinalizer {
|
||||
XmlHttpRequestEventTargetState,
|
||||
NotificationPermission,
|
||||
PointerEventSecureContextSurface,
|
||||
PerformanceWindowAccessors,
|
||||
PerformanceObserverSupportedEntryTypes,
|
||||
}
|
||||
|
||||
@@ -78,6 +80,10 @@ const REALM_DEPENDENT_FINALIZER_ALLOWLIST: &[(&str, RealmDependentFinalizer)] =
|
||||
"MathMLElement",
|
||||
RealmDependentFinalizer::GlobalEventHandlersSecureContextSurface,
|
||||
),
|
||||
(
|
||||
"Performance",
|
||||
RealmDependentFinalizer::PerformanceWindowAccessors,
|
||||
),
|
||||
(
|
||||
"PerformanceObserver",
|
||||
RealmDependentFinalizer::PerformanceObserverSupportedEntryTypes,
|
||||
@@ -102,6 +108,7 @@ pub(super) fn finalize_materialized_interface<'s>(
|
||||
interface_name: &str,
|
||||
constructor: v8::Local<'s, v8::Function>,
|
||||
prototype: v8::Local<'s, v8::Object>,
|
||||
realm_kind: RealmKind,
|
||||
) -> Result<()> {
|
||||
let Some(finalizer) = realm_dependent_finalizer(interface_name) else {
|
||||
return Ok(());
|
||||
@@ -139,8 +146,22 @@ pub(super) fn finalize_materialized_interface<'s>(
|
||||
RealmDependentFinalizer::PointerEventSecureContextSurface => {
|
||||
finalize_pointer_event_realm_bindings(scope, prototype)
|
||||
}
|
||||
RealmDependentFinalizer::PerformanceWindowAccessors => {
|
||||
if realm_kind == RealmKind::Window {
|
||||
crate::context_bootstrap::performance_runtime::finalize_window_performance_realm_bindings(
|
||||
scope, prototype,
|
||||
)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
RealmDependentFinalizer::PerformanceObserverSupportedEntryTypes => {
|
||||
finalize_performance_observer_realm_bindings(scope, constructor.into())
|
||||
let supported_entry_types = if realm_kind == RealmKind::Window {
|
||||
crate::context_bootstrap::performance_runtime::WINDOW_PERFORMANCE_OBSERVER_SUPPORTED_ENTRY_TYPES
|
||||
} else {
|
||||
crate::context_bootstrap::performance_runtime::WORKER_PERFORMANCE_OBSERVER_SUPPORTED_ENTRY_TYPES
|
||||
};
|
||||
finalize_performance_observer_realm_bindings(scope, constructor.into(), supported_entry_types)
|
||||
}
|
||||
}
|
||||
.with_context(|| format!("failed to finalize intrinsic interface `{interface_name}`"))
|
||||
@@ -189,6 +210,7 @@ mod tests {
|
||||
"HTMLElement",
|
||||
"SVGElement",
|
||||
"MathMLElement",
|
||||
"Performance",
|
||||
"PerformanceObserver",
|
||||
]
|
||||
);
|
||||
|
||||
@@ -292,7 +292,13 @@ fn finish_materialized_interface<'s>(
|
||||
};
|
||||
// Like Blink's ConstructorForTypeSlowCase, finish every fallible binding
|
||||
// installation before publishing the interface in the per-context cache.
|
||||
finalize_materialized_interface(scope, metadata.name, constructor, constructor_prototype)?;
|
||||
finalize_materialized_interface(
|
||||
scope,
|
||||
metadata.name,
|
||||
constructor,
|
||||
constructor_prototype,
|
||||
realm.realm_kind(),
|
||||
)?;
|
||||
realm.publish_ready(
|
||||
scope,
|
||||
id,
|
||||
|
||||
@@ -99,6 +99,12 @@ pub(in crate::context_bootstrap) const WORKER_SHARED_INTERFACE_NAMES: &[&str] =
|
||||
"FileReaderSync",
|
||||
"DOMException",
|
||||
"QuotaExceededError",
|
||||
"Performance",
|
||||
"PerformanceObserver",
|
||||
"PerformanceObserverEntryList",
|
||||
"PerformanceEntry",
|
||||
"PerformanceMark",
|
||||
"PerformanceMeasure",
|
||||
"URL",
|
||||
"URLSearchParams",
|
||||
"FormData",
|
||||
|
||||
@@ -23,8 +23,6 @@ struct FileApiRuntimeQueuesDeclaration {
|
||||
resize_observer_queue: (),
|
||||
#[webapi(slot = RESIZE_OBSERVER_REGISTRY_SLOT, init = "array")]
|
||||
resize_observer_registry: (),
|
||||
#[webapi(slot = PERFORMANCE_OBSERVER_QUEUE_SLOT, init = "array")]
|
||||
performance_observer_queue: (),
|
||||
}
|
||||
|
||||
pub(in crate::context_bootstrap) fn install_file_api_template_bindings<'s>(
|
||||
|
||||
@@ -18,7 +18,7 @@ pub(crate) use delivery::{
|
||||
pub(super) use methods::{
|
||||
message_port_close_callback, message_port_post_message_callback, message_port_start_callback,
|
||||
};
|
||||
pub(super) use scheduling::schedule_host_callback;
|
||||
pub(super) use scheduling::{schedule_host_callback, schedule_scope_callback};
|
||||
pub(in crate::context_bootstrap) use state::install_message_port_template_bindings;
|
||||
pub(crate) use state::{
|
||||
MessagePortRealmBinding, detach_message_port_owner_for_transfer,
|
||||
|
||||
@@ -2,8 +2,8 @@ use super::observer::{
|
||||
array_contains_string, performance_observer_active, performance_observer_callback_id,
|
||||
performance_observer_callback_residence, performance_observer_entry_types,
|
||||
performance_observer_observed_type, performance_observer_pending,
|
||||
performance_observer_scheduled, set_performance_observer_pending,
|
||||
set_performance_observer_scheduled,
|
||||
performance_observer_scheduled, prepare_worker_performance_observer_callback,
|
||||
set_performance_observer_pending, set_performance_observer_scheduled,
|
||||
};
|
||||
use super::*;
|
||||
use crate::host::report_event_callback_exception;
|
||||
@@ -37,19 +37,6 @@ fn performance_observer_flush_callback<'s>(
|
||||
if pending.length() == 0 {
|
||||
return;
|
||||
}
|
||||
let Some(callback_residence) = performance_observer_callback_residence(scope, observer) else {
|
||||
return;
|
||||
};
|
||||
let Some(host_ptr) = context_host_ptr_from_global_bridge(scope) else {
|
||||
return;
|
||||
};
|
||||
let Some(callback) =
|
||||
crate::observer_runtime::prepare_callback(scope, host_ptr, callback_residence)
|
||||
else {
|
||||
let pending = v8::Array::new(scope, 0);
|
||||
set_performance_observer_pending(scope, observer, pending);
|
||||
return;
|
||||
};
|
||||
let list = PerformanceObserverEntryListObjectDeclaration::new(pending)
|
||||
.bind(scope)
|
||||
.expect("PerformanceObserverEntryList declaration should bind");
|
||||
@@ -61,26 +48,61 @@ fn performance_observer_flush_callback<'s>(
|
||||
// droppedEntriesCount when a drop count exists.
|
||||
let options = v8::Object::new(scope);
|
||||
let observer_value: v8::Local<'_, v8::Value> = observer.into();
|
||||
match callback.invoke(
|
||||
scope,
|
||||
host_ptr,
|
||||
"PerformanceObserver callback",
|
||||
observer_value,
|
||||
&[list.into(), observer_value, options.into()],
|
||||
) {
|
||||
WindowWebIdlCallbackFunctionOutcome::Threw(report) => {
|
||||
report_event_callback_exception(
|
||||
scope,
|
||||
host_ptr,
|
||||
"performanceobserver",
|
||||
callback.relevant_identity(),
|
||||
None,
|
||||
&report,
|
||||
);
|
||||
let callback_arguments = [list.into(), observer_value, options.into()];
|
||||
if let Some(host_ptr) = context_host_ptr_from_global_bridge(scope) {
|
||||
let Some(callback_residence) = performance_observer_callback_residence(scope, observer)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let Some(callback) =
|
||||
crate::observer_runtime::prepare_callback(scope, host_ptr, callback_residence)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
match callback.invoke(
|
||||
scope,
|
||||
host_ptr,
|
||||
"PerformanceObserver callback",
|
||||
observer_value,
|
||||
&callback_arguments,
|
||||
) {
|
||||
WindowWebIdlCallbackFunctionOutcome::Threw(report) => {
|
||||
report_event_callback_exception(
|
||||
scope,
|
||||
host_ptr,
|
||||
"performanceobserver",
|
||||
callback.relevant_identity(),
|
||||
None,
|
||||
&report,
|
||||
);
|
||||
}
|
||||
WindowWebIdlCallbackFunctionOutcome::Returned
|
||||
| WindowWebIdlCallbackFunctionOutcome::Retired => {}
|
||||
}
|
||||
WindowWebIdlCallbackFunctionOutcome::Returned
|
||||
| WindowWebIdlCallbackFunctionOutcome::Retired => {}
|
||||
return;
|
||||
}
|
||||
|
||||
if crate::worker::get_worker_state(scope).is_none() {
|
||||
return;
|
||||
}
|
||||
let Some(callback) = prepare_worker_performance_observer_callback(scope, observer) else {
|
||||
return;
|
||||
};
|
||||
let _ = moli_webidl_callback::invoke_webidl_callback_function(
|
||||
scope,
|
||||
&callback,
|
||||
observer_value,
|
||||
&callback_arguments,
|
||||
|scope, callback, receiver, arguments| {
|
||||
crate::exception_reporting::invoke_callback(
|
||||
scope,
|
||||
"PerformanceObserver callback",
|
||||
callback,
|
||||
receiver,
|
||||
arguments,
|
||||
)
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn enqueue_buffered_performance_entries<'s>(
|
||||
@@ -117,18 +139,30 @@ pub(in crate::context_bootstrap) fn queue_matching_performance_observers<'s>(
|
||||
entry: v8::Local<'s, v8::Object>,
|
||||
entry_type: &str,
|
||||
) {
|
||||
let Some(host_ptr) = context_host_ptr_from_global_bridge(scope) else {
|
||||
let host_ptr = context_host_ptr_from_global_bridge(scope);
|
||||
let observers = if let Some(host_ptr) = host_ptr {
|
||||
crate::observer_runtime::active_performance_observer_callbacks(scope, host_ptr)
|
||||
} else if crate::worker::get_worker_state(scope).is_some() {
|
||||
let Some(registry) = global_queue_array(scope, PERFORMANCE_OBSERVER_REGISTRY_SLOT) else {
|
||||
return;
|
||||
};
|
||||
(0..registry.length())
|
||||
.filter_map(|index| registry.get_index(scope, index))
|
||||
.filter_map(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
||||
.collect()
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
let observers = crate::observer_runtime::active_performance_observer_callbacks(scope, host_ptr);
|
||||
for observer in observers {
|
||||
let Some(callback_id) = performance_observer_callback_id(scope, observer) else {
|
||||
continue;
|
||||
};
|
||||
if !crate::observer_runtime::callback_is_current(host_ptr, callback_id) {
|
||||
let pending = v8::Array::new(scope, 0);
|
||||
set_performance_observer_pending(scope, observer, pending);
|
||||
continue;
|
||||
if let Some(host_ptr) = host_ptr {
|
||||
let Some(callback_id) = performance_observer_callback_id(scope, observer) else {
|
||||
continue;
|
||||
};
|
||||
if !crate::observer_runtime::callback_is_current(host_ptr, callback_id) {
|
||||
let pending = v8::Array::new(scope, 0);
|
||||
set_performance_observer_pending(scope, observer, pending);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if !performance_observer_active(scope, observer) {
|
||||
continue;
|
||||
@@ -151,21 +185,21 @@ pub(super) fn queue_performance_observer_delivery<'s>(
|
||||
if performance_observer_scheduled(scope, observer) {
|
||||
return;
|
||||
}
|
||||
let Some(callback_id) = performance_observer_callback_id(scope, observer) else {
|
||||
return;
|
||||
};
|
||||
let Some(host_ptr) = context_host_ptr_from_global_bridge(scope) else {
|
||||
return;
|
||||
};
|
||||
if !crate::observer_runtime::callback_is_current(host_ptr, callback_id) {
|
||||
let pending = v8::Array::new(scope, 0);
|
||||
set_performance_observer_pending(scope, observer, pending);
|
||||
if let Some(host_ptr) = context_host_ptr_from_global_bridge(scope) {
|
||||
let Some(callback_id) = performance_observer_callback_id(scope, observer) else {
|
||||
return;
|
||||
};
|
||||
if !crate::observer_runtime::callback_is_current(host_ptr, callback_id) {
|
||||
let pending = v8::Array::new(scope, 0);
|
||||
set_performance_observer_pending(scope, observer, pending);
|
||||
return;
|
||||
}
|
||||
} else if crate::worker::get_worker_state(scope).is_none() {
|
||||
return;
|
||||
}
|
||||
set_performance_observer_scheduled(scope, observer, true);
|
||||
push_object_to_global_queue(scope, PERFORMANCE_OBSERVER_QUEUE_SLOT, observer);
|
||||
let host = unsafe { &mut *host_ptr };
|
||||
schedule_host_callback(scope, host, performance_observer_flush_callback);
|
||||
schedule_scope_callback(scope, performance_observer_flush_callback, None);
|
||||
}
|
||||
|
||||
fn performance_observer_matches_entry_type<'s>(
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use super::super::performance_runtime::PERFORMANCE_OBSERVER_SUPPORTED_ENTRY_TYPES;
|
||||
use super::delivery::{enqueue_buffered_performance_entries, queue_performance_observer_delivery};
|
||||
use super::*;
|
||||
use crate::observer_runtime::ObserverCallbackId;
|
||||
@@ -88,14 +87,40 @@ pub(in crate::context_bootstrap) fn performance_observer_constructor_callback<'s
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let host_ptr = context_host_ptr_from_global_bridge(scope)
|
||||
.expect("PerformanceObserver constructor must execute in a Window realm");
|
||||
let registered_callback =
|
||||
crate::observer_runtime::register_callback(scope, host_ptr, args.this(), parsed.callback);
|
||||
let (callback_id, callback, relevant_global, incumbent_global) =
|
||||
registered_callback.into_parts();
|
||||
if let Some(host_ptr) = context_host_ptr_from_global_bridge(scope) {
|
||||
let registered_callback = crate::observer_runtime::register_callback(
|
||||
scope,
|
||||
host_ptr,
|
||||
args.this(),
|
||||
parsed.callback,
|
||||
);
|
||||
let (callback_id, callback, relevant_global, incumbent_global) =
|
||||
registered_callback.into_parts();
|
||||
(
|
||||
callback_id.as_u32(),
|
||||
callback,
|
||||
relevant_global,
|
||||
incumbent_global,
|
||||
)
|
||||
} else {
|
||||
if crate::worker::get_worker_state(scope).is_none() {
|
||||
throw_type_error(
|
||||
scope,
|
||||
"PerformanceObserver is unavailable outside a Window or Worker realm.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
let callback = v8::Local::<v8::Object>::try_from(parsed.callback.value(scope))
|
||||
.expect("a Web IDL callback function must be an object");
|
||||
let relevant_global = parsed.callback.relevant_context(scope).global(scope);
|
||||
let incumbent_global = parsed.callback.incumbent_context(scope).global(scope);
|
||||
// Worker observers do not need a Window execution-context binding.
|
||||
// Zero is deliberately outside ObserverCallbackId's valid range.
|
||||
(0, callback, relevant_global, incumbent_global)
|
||||
};
|
||||
PerformanceObserverObjectDeclaration::new(
|
||||
callback_id.as_u32(),
|
||||
callback_id,
|
||||
callback,
|
||||
relevant_global,
|
||||
incumbent_global,
|
||||
@@ -167,7 +192,7 @@ pub(in crate::context_bootstrap) fn performance_observer_observe_callback<'s>(
|
||||
}
|
||||
|
||||
let observed_entry_types = if let Some(observed_type) = init.observed_type.as_deref() {
|
||||
if !performance_observer_entry_type_supported(observed_type) {
|
||||
if !performance_observer_entry_type_supported(scope, observed_type) {
|
||||
rv.set_undefined();
|
||||
return;
|
||||
}
|
||||
@@ -181,15 +206,15 @@ pub(in crate::context_bootstrap) fn performance_observer_observe_callback<'s>(
|
||||
}
|
||||
entry_types
|
||||
} else {
|
||||
performance_entry_types_array_from_strings(
|
||||
scope,
|
||||
init.entry_types
|
||||
.map(|entry_types| entry_types.0)
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.map(|entry_type| entry_type.0)
|
||||
.filter(|entry_type| performance_observer_entry_type_supported(entry_type)),
|
||||
)
|
||||
let supported_entry_types = init
|
||||
.entry_types
|
||||
.map(|entry_types| entry_types.0)
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.map(|entry_type| entry_type.0)
|
||||
.filter(|entry_type| performance_observer_entry_type_supported(scope, entry_type))
|
||||
.collect::<Vec<_>>();
|
||||
performance_entry_types_array_from_strings(scope, supported_entry_types)
|
||||
};
|
||||
if observed_entry_types.length() == 0 {
|
||||
rv.set_undefined();
|
||||
@@ -218,27 +243,33 @@ pub(in crate::context_bootstrap) fn performance_observer_observe_callback<'s>(
|
||||
.unwrap_or_else(|| v8::null(scope).into()),
|
||||
);
|
||||
set_performance_observer_entry_types(scope, args.this(), observed_entry_types);
|
||||
let Some(callback_id) = performance_observer_callback_id(scope, args.this()) else {
|
||||
rv.set_undefined();
|
||||
return;
|
||||
};
|
||||
let Some(host_ptr) = context_host_ptr_from_global_bridge(scope) else {
|
||||
rv.set_undefined();
|
||||
return;
|
||||
};
|
||||
// Blink keeps a PerformanceObserver alive only while it is registered
|
||||
// (`HasPendingActivity() == is_registered_`). The exact callback binding
|
||||
// owns the same active root so disconnect and Realm retirement can release
|
||||
// it without a parallel global JS registry.
|
||||
if !crate::observer_runtime::activate_performance_observer_callback(
|
||||
scope,
|
||||
host_ptr,
|
||||
callback_id,
|
||||
args.this(),
|
||||
) {
|
||||
set_performance_observer_active(scope, args.this(), false);
|
||||
let pending = v8::Array::new(scope, 0);
|
||||
set_performance_observer_pending(scope, args.this(), pending);
|
||||
if let Some(host_ptr) = context_host_ptr_from_global_bridge(scope) {
|
||||
let Some(callback_id) = performance_observer_callback_id(scope, args.this()) else {
|
||||
rv.set_undefined();
|
||||
return;
|
||||
};
|
||||
// Blink keeps a PerformanceObserver alive only while it is registered
|
||||
// (`HasPendingActivity() == is_registered_`). The exact callback binding
|
||||
// owns the same active root so disconnect and Realm retirement can release
|
||||
// it without a parallel global JS registry.
|
||||
if !crate::observer_runtime::activate_performance_observer_callback(
|
||||
scope,
|
||||
host_ptr,
|
||||
callback_id,
|
||||
args.this(),
|
||||
) {
|
||||
set_performance_observer_active(scope, args.this(), false);
|
||||
let pending = v8::Array::new(scope, 0);
|
||||
set_performance_observer_pending(scope, args.this(), pending);
|
||||
rv.set_undefined();
|
||||
return;
|
||||
}
|
||||
} else if crate::worker::get_worker_state(scope).is_some() {
|
||||
// A worker has one Realm for the lifetime of its isolate run, so its
|
||||
// active observer set can live on that Realm without Window generation
|
||||
// authorization.
|
||||
push_object_to_global_registry(scope, PERFORMANCE_OBSERVER_REGISTRY_SLOT, args.this());
|
||||
} else {
|
||||
rv.set_undefined();
|
||||
return;
|
||||
}
|
||||
@@ -260,6 +291,8 @@ pub(in crate::context_bootstrap) fn performance_observer_disconnect_callback<'s>
|
||||
&& let Some(host_ptr) = context_host_ptr_from_global_bridge(scope)
|
||||
{
|
||||
crate::observer_runtime::deactivate_performance_observer_callback(host_ptr, callback_id);
|
||||
} else if crate::worker::get_worker_state(scope).is_some() {
|
||||
remove_worker_observer_root(scope, args.this());
|
||||
}
|
||||
let pending = v8::Array::new(scope, 0);
|
||||
set_performance_observer_pending(scope, args.this(), pending);
|
||||
@@ -309,8 +342,18 @@ fn performance_observer_mode<'s>(
|
||||
}
|
||||
}
|
||||
|
||||
fn performance_observer_entry_type_supported(entry_type: &str) -> bool {
|
||||
PERFORMANCE_OBSERVER_SUPPORTED_ENTRY_TYPES.contains(&entry_type)
|
||||
fn performance_observer_entry_type_supported(
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
entry_type: &str,
|
||||
) -> bool {
|
||||
let global = scope.get_current_context().global(scope);
|
||||
get_private_value(
|
||||
scope,
|
||||
global,
|
||||
PERFORMANCE_OBSERVER_SUPPORTED_ENTRY_TYPES_SLOT,
|
||||
)
|
||||
.and_then(|value| v8::Local::<v8::Array>::try_from(value).ok())
|
||||
.is_some_and(|entry_types| array_contains_string(scope, entry_types, entry_type))
|
||||
}
|
||||
|
||||
pub(super) fn array_contains_string(
|
||||
@@ -365,6 +408,35 @@ pub(super) fn performance_observer_callback_residence<'s>(
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) fn prepare_worker_performance_observer_callback<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
observer: v8::Local<'s, v8::Object>,
|
||||
) -> Option<moli_webidl_callback::PreparedWebIdlCallbackFunction> {
|
||||
let callback =
|
||||
performance_observer_slot_value(scope, observer, PERFORMANCE_OBSERVER_CALLBACK_VALUE_SLOT)
|
||||
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())?;
|
||||
let relevant_global = performance_observer_slot_value(
|
||||
scope,
|
||||
observer,
|
||||
PERFORMANCE_OBSERVER_CALLBACK_RELEVANT_GLOBAL_SLOT,
|
||||
)
|
||||
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())?;
|
||||
let incumbent_global = performance_observer_slot_value(
|
||||
scope,
|
||||
observer,
|
||||
PERFORMANCE_OBSERVER_CALLBACK_INCUMBENT_GLOBAL_SLOT,
|
||||
)
|
||||
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())?;
|
||||
let relevant_context = relevant_global.get_creation_context(scope)?;
|
||||
let incumbent_context = incumbent_global.get_creation_context(scope)?;
|
||||
moli_webidl_callback::PreparedWebIdlCallbackFunction::try_new(
|
||||
scope,
|
||||
callback,
|
||||
relevant_context,
|
||||
incumbent_context,
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) fn performance_observer_pending<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
observer: v8::Local<'s, v8::Object>,
|
||||
@@ -500,3 +572,24 @@ fn set_performance_observer_bool_slot(
|
||||
let value = v8::Boolean::new(scope, value);
|
||||
set_performance_observer_slot_value(scope, observer, slot, value.into());
|
||||
}
|
||||
|
||||
fn remove_worker_observer_root<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
observer: v8::Local<'s, v8::Object>,
|
||||
) {
|
||||
let Some(registry) = global_queue_array(scope, PERFORMANCE_OBSERVER_REGISTRY_SLOT) else {
|
||||
return;
|
||||
};
|
||||
let retained: Vec<_> = (0..registry.length())
|
||||
.filter_map(|index| registry.get_index(scope, index))
|
||||
.filter(|value| !value.strict_equals(observer.into()))
|
||||
.collect();
|
||||
let registry = v8::Array::new_with_elements(scope, &retained);
|
||||
let global = scope.get_current_context().global(scope);
|
||||
set_private_value(
|
||||
scope,
|
||||
global,
|
||||
PERFORMANCE_OBSERVER_REGISTRY_SLOT,
|
||||
registry.into(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -86,8 +86,10 @@ pub(super) const PERFORMANCE_LIFECYCLE_TIMESTAMPS_SLOT: &str =
|
||||
"__moliPerformanceLifecycleTimestamps";
|
||||
pub(super) const PERFORMANCE_PENDING_EVENT_COUNTS_SLOT: &str =
|
||||
"__moliPerformancePendingEventCounts";
|
||||
pub(in crate::context_bootstrap) const PERFORMANCE_OBSERVER_SUPPORTED_ENTRY_TYPES: &[&str] =
|
||||
&["mark", "measure", "navigation", "resource"];
|
||||
pub(in crate::context_bootstrap) const WINDOW_PERFORMANCE_OBSERVER_SUPPORTED_ENTRY_TYPES:
|
||||
&[&str] = &["mark", "measure", "navigation", "resource"];
|
||||
pub(in crate::context_bootstrap) const WORKER_PERFORMANCE_OBSERVER_SUPPORTED_ENTRY_TYPES:
|
||||
&[&str] = &["mark", "measure", "resource"];
|
||||
|
||||
pub(super) use super::performance_observer_runtime::{
|
||||
performance_entry_list_get_entries_by_name_callback,
|
||||
@@ -108,9 +110,13 @@ pub(in crate::context_bootstrap) use entries::{
|
||||
performance_entry_slot_number, performance_entry_slot_string, performance_entry_slot_value,
|
||||
set_performance_entry_slot_number,
|
||||
};
|
||||
pub(crate) use install::finalize_performance_observer_realm_bindings;
|
||||
pub(crate) use install::increment_performance_event_count;
|
||||
pub(super) use install::install_performance_template_bindings;
|
||||
pub(crate) use install::{
|
||||
finalize_performance_observer_realm_bindings, finalize_window_performance_realm_bindings,
|
||||
};
|
||||
pub(super) use install::{
|
||||
install_performance_template_bindings, install_worker_performance_runtime_state,
|
||||
};
|
||||
pub(crate) use install::{
|
||||
record_performance_dom_content_loaded_event_end,
|
||||
record_performance_dom_content_loaded_event_start, record_performance_load_event_end,
|
||||
@@ -176,7 +182,7 @@ pub(in crate::context_bootstrap) fn current_window_navigation_timing_snapshot(
|
||||
pub(in crate::context_bootstrap) fn ensure_navigation_performance_entry_for_api<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
performance: v8::Local<'s, v8::Object>,
|
||||
) -> v8::Local<'s, v8::Object> {
|
||||
) {
|
||||
install::ensure_navigation_performance_entry(scope, performance)
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ use crate::webidl_iterator::{
|
||||
SnapshotWebIdlIteratorKind, invoke_webidl_collection_for_each_callback,
|
||||
new_snapshot_webidl_iterator, prepare_webidl_collection_for_each_callback,
|
||||
};
|
||||
use anyhow::{Result, anyhow};
|
||||
use moli_webapi_declare::{ObjectLiteralDeclaration, WebApiFunctionTemplate, WebApiObject};
|
||||
|
||||
pub(super) const MIN_LIFECYCLE_TIMING_DELTA_MILLIS: f64 = 0.001;
|
||||
@@ -77,6 +78,16 @@ struct PerformanceObjectDeclaration<'scope> {
|
||||
event_counts: v8::Local<'scope, v8::Value>,
|
||||
}
|
||||
|
||||
#[derive(WebApiObject)]
|
||||
#[webapi(interface = web_api_interfaces::Performance)]
|
||||
struct WorkerPerformanceObjectDeclaration {
|
||||
#[webapi(slot = PERFORMANCE_TIME_ORIGIN_SLOT)]
|
||||
time_origin: f64,
|
||||
|
||||
#[webapi(slot = PERFORMANCE_ENTRIES_SLOT, init = "array")]
|
||||
entries: (),
|
||||
}
|
||||
|
||||
#[derive(WebApiObject)]
|
||||
#[webapi(plain, data_properties, enumerable)]
|
||||
struct PerformanceJsonSnapshotDeclaration<'scope> {
|
||||
@@ -92,6 +103,16 @@ struct PerformanceObserverConstructorDeclaration {
|
||||
supported_entry_types: &'static [&'static str],
|
||||
}
|
||||
|
||||
#[derive(Default, WebApiObject)]
|
||||
#[webapi(fragment)]
|
||||
struct PerformanceObserverRuntimeStateDeclaration {
|
||||
#[webapi(slot = PERFORMANCE_OBSERVER_REGISTRY_SLOT, init = "array")]
|
||||
registry: (),
|
||||
|
||||
#[webapi(slot = PERFORMANCE_OBSERVER_QUEUE_SLOT, init = "array")]
|
||||
queue: (),
|
||||
}
|
||||
|
||||
#[derive(WebApiObject)]
|
||||
#[webapi(interface = web_api_interfaces::PerformanceNavigation)]
|
||||
struct PerformanceNavigationObjectDeclaration {
|
||||
@@ -161,16 +182,20 @@ struct PerformancePrototypeMethodsDeclaration {
|
||||
|
||||
#[derive(WebApiFunctionTemplate)]
|
||||
#[webapi(interface = web_api_interfaces::Performance, enumerable, receiver)]
|
||||
struct PerformancePrototypeAccessorsDeclaration {
|
||||
#[webapi(accessor_property, getter = super::memory::performance_memory_getter)]
|
||||
memory: (),
|
||||
|
||||
struct PerformancePrototypeCommonAccessorsDeclaration {
|
||||
#[webapi(
|
||||
accessor_property,
|
||||
getter = performance_attribute_getter_callback,
|
||||
data = callback_data_index_value(scope, 0)
|
||||
)]
|
||||
time_origin: (),
|
||||
}
|
||||
|
||||
#[derive(Default, WebApiObject)]
|
||||
#[webapi(fragment, prototype = "Performance", enumerable, receiver = web_api_interfaces::Performance::is_instance)]
|
||||
struct PerformancePrototypeWindowAccessorsDeclaration {
|
||||
#[webapi(accessor_property, getter = super::memory::performance_memory_getter)]
|
||||
memory: (),
|
||||
|
||||
#[webapi(
|
||||
accessor_property,
|
||||
@@ -299,7 +324,7 @@ pub(in crate::context_bootstrap) fn install_performance_template_bindings<'s>(
|
||||
match interface_name {
|
||||
"Performance" => {
|
||||
PerformancePrototypeMethodsDeclaration::initialize_prototype_template(scope, prototype);
|
||||
PerformancePrototypeAccessorsDeclaration::initialize_prototype_template(
|
||||
PerformancePrototypeCommonAccessorsDeclaration::initialize_prototype_template(
|
||||
scope, prototype,
|
||||
);
|
||||
super::resource_buffer::install_resource_timing_buffer_template_bindings(
|
||||
@@ -327,6 +352,46 @@ pub(in crate::context_bootstrap) fn install_performance_template_bindings<'s>(
|
||||
}
|
||||
}
|
||||
|
||||
pub(in crate::context_bootstrap) fn install_worker_performance_runtime_state<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
global: v8::Local<'s, v8::Object>,
|
||||
) -> Result<()> {
|
||||
install_performance_observer_runtime_state(
|
||||
scope,
|
||||
global,
|
||||
WORKER_PERFORMANCE_OBSERVER_SUPPORTED_ENTRY_TYPES,
|
||||
)?;
|
||||
super::super::exposed_interfaces::ensure_intrinsic_interface_constructor(scope, "Performance")?;
|
||||
|
||||
let performance = WorkerPerformanceObjectDeclaration::new(unix_epoch_millis())
|
||||
.bind(scope)
|
||||
.map_err(|error| anyhow!("failed to create worker performance: {error}"))?;
|
||||
install_simple_event_target_methods(
|
||||
scope,
|
||||
performance,
|
||||
PERFORMANCE_EVENT_LISTENERS_SLOT,
|
||||
false,
|
||||
);
|
||||
define_global_value(scope, global, WINDOW_PERFORMANCE_SLOT, performance.into())
|
||||
}
|
||||
|
||||
pub(super) fn install_performance_observer_runtime_state<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
global: v8::Local<'s, v8::Object>,
|
||||
supported_entry_types: &'static [&'static str],
|
||||
) -> Result<()> {
|
||||
PerformanceObserverRuntimeStateDeclaration::default().initialize(scope, global)?;
|
||||
let supported_entry_types_array = serialize_v8_array(scope, supported_entry_types)
|
||||
.unwrap_or_else(|| v8::Array::new(scope, 0));
|
||||
set_private_value(
|
||||
scope,
|
||||
global,
|
||||
PERFORMANCE_OBSERVER_SUPPORTED_ENTRY_TYPES_SLOT,
|
||||
supported_entry_types_array.into(),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn create_performance_object<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
window: Option<v8::Local<'s, v8::Object>>,
|
||||
@@ -439,12 +504,20 @@ pub(super) fn apply_pending_window_performance_state<'s>(
|
||||
pub(crate) fn finalize_performance_observer_realm_bindings<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
constructor: v8::Local<'s, v8::Object>,
|
||||
supported_entry_types: &'static [&'static str],
|
||||
) -> anyhow::Result<()> {
|
||||
PerformanceObserverConstructorDeclaration::new(PERFORMANCE_OBSERVER_SUPPORTED_ENTRY_TYPES)
|
||||
PerformanceObserverConstructorDeclaration::new(supported_entry_types)
|
||||
.initialize(scope, constructor)
|
||||
.map_err(|error| anyhow::anyhow!("failed to finalize PerformanceObserver: {error}"))
|
||||
}
|
||||
|
||||
pub(crate) fn finalize_window_performance_realm_bindings<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
prototype: v8::Local<'s, v8::Object>,
|
||||
) -> Result<()> {
|
||||
PerformancePrototypeWindowAccessorsDeclaration::default().initialize(scope, prototype)?;
|
||||
Ok(())
|
||||
}
|
||||
fn performance_navigation_timing_type(navigation_type: &str) -> &'static str {
|
||||
match navigation_type {
|
||||
"reload" => "reload",
|
||||
@@ -801,9 +874,11 @@ fn navigation_performance_entry<'s>(
|
||||
pub(super) fn ensure_navigation_performance_entry<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
performance: v8::Local<'s, v8::Object>,
|
||||
) -> v8::Local<'s, v8::Object> {
|
||||
if let Some(entry) = navigation_performance_entry(scope, performance) {
|
||||
return entry;
|
||||
) {
|
||||
if navigation_performance_entry(scope, performance).is_some()
|
||||
|| !is_window_performance(scope, performance)
|
||||
{
|
||||
return;
|
||||
}
|
||||
let navigation_type = performance_navigation_type_seed(scope, performance);
|
||||
let name = get_private_value(scope, performance, PERFORMANCE_NAVIGATION_NAME_SEED_SLOT)
|
||||
@@ -819,7 +894,6 @@ pub(super) fn ensure_navigation_performance_entry<'s>(
|
||||
);
|
||||
apply_lifecycle_to_navigation_entry(scope, performance, entry);
|
||||
append_performance_entry(scope, performance, entry);
|
||||
entry
|
||||
}
|
||||
|
||||
fn apply_lifecycle_to_navigation_entry<'s>(
|
||||
@@ -1041,6 +1115,10 @@ fn performance_attribute_getter_callback<'s>(
|
||||
rv.set_undefined();
|
||||
return;
|
||||
};
|
||||
if performance_slot_number(scope, args.this(), PERFORMANCE_TIME_ORIGIN_SLOT).is_none() {
|
||||
throw_type_error(scope, "Illegal invocation");
|
||||
return;
|
||||
}
|
||||
if let Some(subobject) = super::lazy_subobjects::PerformanceSubobject::from_slot(slot) {
|
||||
match super::lazy_subobjects::ensure_performance_subobject(scope, args.this(), subobject) {
|
||||
Ok(value) => rv.set(value),
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
use super::install::create_performance_object;
|
||||
use super::{
|
||||
WINDOW_PERFORMANCE_OBSERVER_SUPPORTED_ENTRY_TYPES,
|
||||
install::{create_performance_object, install_performance_observer_runtime_state},
|
||||
};
|
||||
use crate::{
|
||||
context_bootstrap::{
|
||||
WindowLazySurface, ensure_window_lazy_surface_object,
|
||||
@@ -85,6 +88,11 @@ pub(in crate::context_bootstrap) fn install_default_window_performance_seed<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
window: v8::Local<'s, v8::Object>,
|
||||
) -> Result<()> {
|
||||
install_performance_observer_runtime_state(
|
||||
scope,
|
||||
window,
|
||||
WINDOW_PERFORMANCE_OBSERVER_SUPPORTED_ENTRY_TYPES,
|
||||
)?;
|
||||
bind_window_performance_seed(scope, window, "navigate", unix_epoch_millis())
|
||||
}
|
||||
|
||||
|
||||
@@ -129,8 +129,12 @@ pub(in crate::context_bootstrap) const PERFORMANCE_OBSERVER_ACTIVE_SLOT: &str =
|
||||
"__moliPerformanceObserverActive";
|
||||
pub(in crate::context_bootstrap) const PERFORMANCE_OBSERVER_SCHEDULED_SLOT: &str =
|
||||
"__moliPerformanceObserverScheduled";
|
||||
pub(in crate::context_bootstrap) const PERFORMANCE_OBSERVER_REGISTRY_SLOT: &str =
|
||||
"__moliPerformanceObserverRegistry";
|
||||
pub(in crate::context_bootstrap) const PERFORMANCE_OBSERVER_QUEUE_SLOT: &str =
|
||||
"__moliPerformanceObserverQueue";
|
||||
pub(in crate::context_bootstrap) const PERFORMANCE_OBSERVER_SUPPORTED_ENTRY_TYPES_SLOT: &str =
|
||||
"__moliPerformanceObserverSupportedEntryTypes";
|
||||
pub(in crate::context_bootstrap) const PERFORMANCE_ENTRY_LIST_ENTRIES_SLOT: &str =
|
||||
"__moliPerformanceEntryListEntries";
|
||||
pub(in crate::context_bootstrap) const PERFORMANCE_ENTRIES_SLOT: &str = "__moliPerformanceEntries";
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::context_bootstrap::{
|
||||
PERFORMANCE_TIME_ORIGIN_SLOT, dom_time_since_origin_millis, performance_slot_number,
|
||||
};
|
||||
use crate::util::throw_type_error;
|
||||
use moli_webapi_declare::v8;
|
||||
|
||||
pub(in crate::context_bootstrap) fn performance_now_callback<'s>(
|
||||
@@ -8,8 +9,12 @@ pub(in crate::context_bootstrap) fn performance_now_callback<'s>(
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
let time_origin =
|
||||
performance_slot_number(scope, args.this(), PERFORMANCE_TIME_ORIGIN_SLOT).unwrap_or(0.0);
|
||||
let Some(time_origin) =
|
||||
performance_slot_number(scope, args.this(), PERFORMANCE_TIME_ORIGIN_SLOT)
|
||||
else {
|
||||
throw_type_error(scope, "Illegal invocation");
|
||||
return;
|
||||
};
|
||||
let now = dom_time_since_origin_millis(time_origin);
|
||||
rv.set(v8::Number::new(scope, now).into());
|
||||
}
|
||||
|
||||
@@ -142,7 +142,6 @@ pub(in crate::worker) fn install_worker_global_scope<'s>(
|
||||
)
|
||||
.initialize(scope, global)
|
||||
.map_err(|error| anyhow!("failed to initialize worker global bootstrap properties: {error}"))?;
|
||||
install_worker_performance(scope, global)?;
|
||||
install_worker_global_scope_constructors(scope, global, &global_kind, worker_templates)?;
|
||||
let (_, realm_kind) = worker_global_scope_interface(&global_kind);
|
||||
crate::context_bootstrap::install_worker_lazy_exposed_interfaces(
|
||||
@@ -178,6 +177,7 @@ pub(in crate::worker) fn install_worker_global_scope<'s>(
|
||||
install_service_worker_extendable_event_constructors(scope, global)?;
|
||||
}
|
||||
crate::context_bootstrap::initialize_worker_fetch_realm_state(scope, global)?;
|
||||
crate::context_bootstrap::initialize_worker_performance_realm_state(scope, global)?;
|
||||
let subtle_crypto_available = secure_context;
|
||||
crate::context_bootstrap::initialize_worker_crypto_realm_state(
|
||||
scope,
|
||||
|
||||
@@ -220,7 +220,7 @@ use network_state::{
|
||||
};
|
||||
pub(crate) use origin::worker_global_origin;
|
||||
use origin::{WorkerGlobalOriginDeclaration, WorkerGlobalOriginPrototypeDeclaration};
|
||||
use performance::{install_worker_performance, monotonic_unix_epoch_millis};
|
||||
use performance::monotonic_unix_epoch_millis;
|
||||
pub(super) use service_worker_results::{
|
||||
PendingServiceWorkerClientFocus, PendingServiceWorkerClientNavigate,
|
||||
PendingServiceWorkerClientQuery, PendingServiceWorkerClientQueryType,
|
||||
|
||||
@@ -1,53 +1,7 @@
|
||||
//! Worker performance clock and time origin.
|
||||
//! Worker monotonic clock. Performance API uses the shared realm implementation.
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(WebApiObject)]
|
||||
#[webapi(plain)]
|
||||
struct WorkerGlobalPerformanceDeclaration<'scope> {
|
||||
#[webapi(data_property)]
|
||||
performance: v8::Local<'scope, v8::Object>,
|
||||
}
|
||||
|
||||
#[derive(WebApiObject)]
|
||||
#[webapi(prototype = "Object", interface = web_api_interfaces::Performance)]
|
||||
struct WorkerPerformanceObjectDeclaration {
|
||||
#[webapi(data_property, readonly)]
|
||||
time_origin: f64,
|
||||
#[webapi(method, callback = worker_performance_now_callback, data = self.time_origin)]
|
||||
now: (),
|
||||
}
|
||||
|
||||
pub(super) fn install_worker_performance<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
global: v8::Local<'s, v8::Object>,
|
||||
) -> Result<()> {
|
||||
let time_origin = monotonic_unix_epoch_millis();
|
||||
let performance = WorkerPerformanceObjectDeclaration::new(time_origin)
|
||||
.bind(scope)
|
||||
.map_err(|error| anyhow!("failed to create worker performance: {error}"))?;
|
||||
WorkerGlobalPerformanceDeclaration::new(performance)
|
||||
.initialize(scope, global)
|
||||
.map_err(|error| anyhow!("failed to initialize worker performance: {error}"))
|
||||
}
|
||||
|
||||
fn worker_performance_now_callback<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
let time_origin = args.data().number_value(scope).unwrap_or(0.0);
|
||||
rv.set(
|
||||
v8::Number::new(
|
||||
scope,
|
||||
moli_time::coarsened_dom_time_millis(
|
||||
(monotonic_unix_epoch_millis() - time_origin).max(0.0),
|
||||
),
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
fn unix_epoch_millis() -> f64 {
|
||||
SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
|
||||
@@ -296,9 +296,10 @@ async fn worker_storage_surfaces_materialize_in_independent_stages() {
|
||||
);
|
||||
let diagnostics = lazy_diagnostics(&handle).await;
|
||||
assert_eq!(diagnostics.storage_constructor_materializations, 0);
|
||||
assert!(
|
||||
diagnostics.materialized_interfaces.is_empty(),
|
||||
"blank worker bootstrap must not materialize Navigator or Crypto constructors"
|
||||
assert_eq!(
|
||||
diagnostics.materialized_interfaces,
|
||||
vec![("Performance", 1), ("EventTarget", 1)],
|
||||
"only constructors required by the eagerly exposed performance object may materialize during worker bootstrap"
|
||||
);
|
||||
assert!(!diagnostics.storage_manager_materialized);
|
||||
assert!(!diagnostics.storage_bucket_manager_materialized);
|
||||
|
||||
@@ -2110,18 +2110,43 @@ async fn worker_performance_now_uses_readonly_monotonic_time_origin() {
|
||||
ensure_v8();
|
||||
let mut handle = spawn_worker(
|
||||
r#"
|
||||
const descriptor = Object.getOwnPropertyDescriptor(performance, "timeOrigin");
|
||||
const before = performance.timeOrigin;
|
||||
try { performance.timeOrigin = before + 1000000; } catch (_) {}
|
||||
const after = performance.timeOrigin;
|
||||
const first = performance.now();
|
||||
const second = performance.now();
|
||||
const original = performance;
|
||||
const globalDescriptor = Object.getOwnPropertyDescriptor(
|
||||
WorkerGlobalScope.prototype,
|
||||
"performance"
|
||||
);
|
||||
const timeOriginDescriptor = Object.getOwnPropertyDescriptor(
|
||||
Performance.prototype,
|
||||
"timeOrigin"
|
||||
);
|
||||
const before = original.timeOrigin;
|
||||
try { original.timeOrigin = before + 1000000; } catch (_) {}
|
||||
const after = original.timeOrigin;
|
||||
const first = original.now();
|
||||
const second = original.now();
|
||||
const replacement = {};
|
||||
self.performance = replacement;
|
||||
postMessage({
|
||||
readonly: descriptor && descriptor.writable === false,
|
||||
interfaceShape:
|
||||
original instanceof Performance
|
||||
&& original instanceof EventTarget
|
||||
&& Object.getPrototypeOf(original) === Performance.prototype,
|
||||
globalAccessor:
|
||||
typeof globalDescriptor?.get === "function"
|
||||
&& typeof globalDescriptor?.set === "function"
|
||||
&& globalDescriptor.enumerable === true,
|
||||
timeOriginAccessor:
|
||||
typeof timeOriginDescriptor?.get === "function"
|
||||
&& timeOriginDescriptor?.set === undefined
|
||||
&& timeOriginDescriptor.enumerable === true,
|
||||
unchanged: after === before,
|
||||
numeric: typeof first === "number" && typeof second === "number",
|
||||
monotonic: second >= first,
|
||||
noLegacyMemory: !("memory" in performance) && !("MemoryInfo" in self)
|
||||
replaceable:
|
||||
performance === replacement
|
||||
&& Object.prototype.hasOwnProperty.call(self, "performance"),
|
||||
originalStillWorks: typeof original.now() === "number",
|
||||
noLegacyMemory: !("memory" in original) && !("MemoryInfo" in self)
|
||||
});
|
||||
close();
|
||||
"#
|
||||
@@ -2135,7 +2160,180 @@ async fn worker_performance_now_uses_readonly_monotonic_time_origin() {
|
||||
.expect("channel closed");
|
||||
assert_eq!(
|
||||
expect_post_json(msg),
|
||||
r#"{"readonly":true,"unchanged":true,"numeric":true,"monotonic":true,"noLegacyMemory":true}"#
|
||||
r#"{"interfaceShape":true,"globalAccessor":true,"timeOriginAccessor":true,"unchanged":true,"numeric":true,"monotonic":true,"replaceable":true,"originalStillWorks":true,"noLegacyMemory":true}"#
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn worker_user_timing_uses_shared_mark_measure_runtime() {
|
||||
ensure_v8();
|
||||
let mut handle = spawn_worker(
|
||||
r#"
|
||||
const capture = callback => {
|
||||
try {
|
||||
callback();
|
||||
return "none";
|
||||
} catch (error) {
|
||||
return error.name;
|
||||
}
|
||||
};
|
||||
const timingNames = [
|
||||
"navigationStart",
|
||||
"unloadEventStart",
|
||||
"unloadEventEnd",
|
||||
"redirectStart",
|
||||
"redirectEnd",
|
||||
"fetchStart",
|
||||
"domainLookupStart",
|
||||
"domainLookupEnd",
|
||||
"connectStart",
|
||||
"connectEnd",
|
||||
"secureConnectionStart",
|
||||
"requestStart",
|
||||
"responseStart",
|
||||
"responseEnd",
|
||||
"domLoading",
|
||||
"domInteractive",
|
||||
"domContentLoadedEventStart",
|
||||
"domContentLoadedEventEnd",
|
||||
"domComplete",
|
||||
"loadEventStart",
|
||||
"loadEventEnd"
|
||||
];
|
||||
const timingNamesAccepted = timingNames.every(name => {
|
||||
const mark = performance.mark(name, { startTime: 1 });
|
||||
performance.clearMarks(name);
|
||||
const measure = performance.measure(name);
|
||||
performance.clearMeasures(name);
|
||||
return mark.name === name && measure.name === name;
|
||||
});
|
||||
|
||||
const sourceDetail = { state: "before" };
|
||||
const mark = performance.mark("worker-mark", {
|
||||
startTime: 2,
|
||||
detail: sourceDetail
|
||||
});
|
||||
sourceDetail.state = "after";
|
||||
const measure = performance.measure("worker-measure", {
|
||||
start: "worker-mark",
|
||||
end: 5,
|
||||
detail: { kind: "measure" }
|
||||
});
|
||||
const detached = new PerformanceMark("detached", {
|
||||
startTime: 3,
|
||||
detail: { kind: "detached" }
|
||||
});
|
||||
const reservedBoundary = capture(() => {
|
||||
performance.mark("navigationStart", { startTime: 4 });
|
||||
performance.measure("reserved", "navigationStart", "navigationStart");
|
||||
});
|
||||
|
||||
postMessage({
|
||||
timingNamesAccepted,
|
||||
interfaces:
|
||||
performance instanceof Performance
|
||||
&& mark instanceof PerformanceMark
|
||||
&& mark instanceof PerformanceEntry
|
||||
&& measure instanceof PerformanceMeasure
|
||||
&& measure instanceof PerformanceEntry
|
||||
&& detached instanceof PerformanceMark,
|
||||
inheritance:
|
||||
Object.getPrototypeOf(Performance) === EventTarget
|
||||
&& Object.getPrototypeOf(PerformanceMark) === PerformanceEntry
|
||||
&& Object.getPrototypeOf(PerformanceMeasure) === PerformanceEntry,
|
||||
mark: [
|
||||
mark.name,
|
||||
mark.entryType,
|
||||
mark.startTime,
|
||||
mark.duration,
|
||||
mark.detail.state,
|
||||
mark.detail === sourceDetail
|
||||
].join(":"),
|
||||
measure: [
|
||||
measure.name,
|
||||
measure.entryType,
|
||||
measure.startTime,
|
||||
measure.duration,
|
||||
measure.detail.kind
|
||||
].join(":"),
|
||||
detachedTimelineCount:
|
||||
performance.getEntriesByName("detached", "mark").length,
|
||||
bufferedTypes: performance.getEntries()
|
||||
.map(entry => entry.entryType)
|
||||
.join(","),
|
||||
reservedBoundary,
|
||||
missingBoundary: capture(() =>
|
||||
performance.measure("missing", "not-a-mark")),
|
||||
markBrand: capture(() => performance.mark.call({}, "bad")),
|
||||
nowBrand: capture(() => performance.now.call({}))
|
||||
});
|
||||
close();
|
||||
"#
|
||||
.into(),
|
||||
"test://worker_user_timing".into(),
|
||||
);
|
||||
|
||||
let msg = timeout(TIMEOUT, handle.recv())
|
||||
.await
|
||||
.expect("timed out")
|
||||
.expect("channel closed");
|
||||
assert_eq!(
|
||||
expect_post_json(msg),
|
||||
r#"{"timingNamesAccepted":true,"interfaces":true,"inheritance":true,"mark":"worker-mark:mark:2:0:before:false","measure":"worker-measure:measure:2:3:measure","detachedTimelineCount":0,"bufferedTypes":"mark,measure,mark","reservedBoundary":"TypeError","missingBoundary":"SyntaxError","markBrand":"TypeError","nowBrand":"TypeError"}"#
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn worker_performance_observer_delivers_live_and_buffered_user_timing_entries() {
|
||||
ensure_v8();
|
||||
let mut handle = spawn_worker(
|
||||
r#"
|
||||
const firstObserver = new PerformanceObserver((list, observer) => {
|
||||
const liveEntries = list.getEntries();
|
||||
observer.disconnect();
|
||||
const bufferedObserver = new PerformanceObserver(bufferedList => {
|
||||
const bufferedEntries = bufferedList.getEntries();
|
||||
postMessage({
|
||||
interfaces:
|
||||
firstObserver instanceof PerformanceObserver
|
||||
&& list instanceof PerformanceObserverEntryList,
|
||||
prototypeMethods:
|
||||
typeof list.getEntries === "function"
|
||||
&& typeof list.getEntriesByType === "function"
|
||||
&& typeof list.getEntriesByName === "function",
|
||||
supportedEntryTypes: PerformanceObserver.supportedEntryTypes.join(","),
|
||||
live: liveEntries
|
||||
.map(entry => `${entry.name}:${entry.entryType}`)
|
||||
.join(","),
|
||||
liveMarks: list.getEntriesByType("mark").length,
|
||||
namedMeasure: list.getEntriesByName("worker-observed-measure", "measure").length,
|
||||
buffered: bufferedEntries
|
||||
.map(entry => `${entry.name}:${entry.entryType}`)
|
||||
.join(",")
|
||||
});
|
||||
bufferedObserver.disconnect();
|
||||
close();
|
||||
});
|
||||
bufferedObserver.observe({ type: "mark", buffered: true });
|
||||
});
|
||||
firstObserver.observe({ entryTypes: ["mark", "measure"] });
|
||||
performance.mark("worker-observed-mark", { startTime: 1 });
|
||||
performance.measure("worker-observed-measure", {
|
||||
start: 1,
|
||||
end: 3
|
||||
});
|
||||
"#
|
||||
.into(),
|
||||
"test://worker_performance_observer".into(),
|
||||
);
|
||||
|
||||
let msg = timeout(TIMEOUT, handle.recv())
|
||||
.await
|
||||
.expect("timed out")
|
||||
.expect("channel closed");
|
||||
assert_eq!(
|
||||
expect_post_json(msg),
|
||||
r#"{"interfaces":true,"prototypeMethods":true,"supportedEntryTypes":"mark,measure,resource","live":"worker-observed-mark:mark,worker-observed-measure:measure","liveMarks":1,"namedMeasure":1,"buffered":"worker-observed-mark:mark"}"#
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user