From f7a49daf6a87338564446b5966ad573974a93443 Mon Sep 17 00:00:00 2001 From: ldm0 Date: Wed, 7 Oct 2026 05:49:30 +0800 Subject: [PATCH] refactor(performance): use generated native receiver checks --- .../performance_runtime/install.rs | 8 ++--- .../performance_runtime/marks_measures.rs | 27 +++------------ .../performance_runtime/memory.rs | 5 --- .../performance_runtime/resource_buffer.rs | 34 ++++--------------- .../browser_api/navigation/performance.rs | 12 +++++-- moli-renderer-v8/src/script_vm/tests/mod.rs | 1 + .../performance_receiver_consolidation.rs | 34 +++++++++++++++++++ 7 files changed, 56 insertions(+), 65 deletions(-) create mode 100644 moli-renderer-v8/src/script_vm/tests/performance_receiver_consolidation.rs diff --git a/moli-renderer-v8/src/context_bootstrap/performance_runtime/install.rs b/moli-renderer-v8/src/context_bootstrap/performance_runtime/install.rs index 1feaa3522f..6f4c843197 100644 --- a/moli-renderer-v8/src/context_bootstrap/performance_runtime/install.rs +++ b/moli-renderer-v8/src/context_bootstrap/performance_runtime/install.rs @@ -129,7 +129,7 @@ struct EventCountsObjectDeclaration { } #[derive(WebApiFunctionTemplate)] -#[webapi(interface = web_api_interfaces::Performance, enumerable)] +#[webapi(interface = web_api_interfaces::Performance, enumerable, receiver)] struct PerformancePrototypeMethodsDeclaration { #[webapi(method, length = 0, callback = performance_now_callback)] now: (), @@ -160,7 +160,7 @@ struct PerformancePrototypeMethodsDeclaration { } #[derive(WebApiFunctionTemplate)] -#[webapi(interface = web_api_interfaces::Performance, enumerable)] +#[webapi(interface = web_api_interfaces::Performance, enumerable, receiver)] struct PerformancePrototypeAccessorsDeclaration { #[webapi(accessor_property, getter = super::memory::performance_memory_getter)] memory: (), @@ -1042,10 +1042,6 @@ fn performance_attribute_getter_callback<'s>( return; }; if let Some(subobject) = super::lazy_subobjects::PerformanceSubobject::from_slot(slot) { - if performance_slot_value(scope, args.this(), PERFORMANCE_TIME_ORIGIN_SLOT).is_none() { - rv.set_undefined(); - return; - } match super::lazy_subobjects::ensure_performance_subobject(scope, args.this(), subobject) { Ok(value) => rv.set(value), Err(error) => throw_type_error(scope, &error.to_string()), diff --git a/moli-renderer-v8/src/context_bootstrap/performance_runtime/marks_measures.rs b/moli-renderer-v8/src/context_bootstrap/performance_runtime/marks_measures.rs index 99bb561246..1a2b690b59 100644 --- a/moli-renderer-v8/src/context_bootstrap/performance_runtime/marks_measures.rs +++ b/moli-renderer-v8/src/context_bootstrap/performance_runtime/marks_measures.rs @@ -79,9 +79,7 @@ pub(in crate::context_bootstrap) fn performance_mark_callback<'s>( args: v8::FunctionCallbackArguments<'s>, mut rv: v8::ReturnValue<'_, v8::Value>, ) { - let Some(performance) = require_performance_receiver(scope, args.this()) else { - return; - }; + let performance = args.this(); let Some(parsed) = webidl::parse_args::(scope, &args) else { return; }; @@ -201,25 +199,12 @@ fn prepare_performance_mark<'s>( clone_user_timing_detail(scope, options.detail).map(|detail| (start_time, detail)) } -fn require_performance_receiver<'s>( - scope: &mut v8::PinScope<'s, '_>, - receiver: v8::Local<'s, v8::Object>, -) -> Option> { - if performance_slot_array(scope, receiver, PERFORMANCE_ENTRIES_SLOT).is_none() { - webidl::throw_type_error(scope, "Illegal invocation"); - return None; - } - Some(receiver) -} - pub(in crate::context_bootstrap) fn performance_clear_marks_callback<'s>( scope: &mut v8::PinScope<'s, '_>, args: v8::FunctionCallbackArguments<'s>, mut rv: v8::ReturnValue<'_, v8::Value>, ) { - let Some(performance) = require_performance_receiver(scope, args.this()) else { - return; - }; + let performance = args.this(); let Some(parsed) = webidl::parse_args::(scope, &args) else { return; }; @@ -262,9 +247,7 @@ pub(in crate::context_bootstrap) fn performance_measure_callback<'s>( args: v8::FunctionCallbackArguments<'s>, mut rv: v8::ReturnValue<'_, v8::Value>, ) { - let Some(performance) = require_performance_receiver(scope, args.this()) else { - return; - }; + let performance = args.this(); let Some(parsed) = webidl::parse_args::(scope, &args) else { return; }; @@ -313,9 +296,7 @@ pub(in crate::context_bootstrap) fn performance_clear_measures_callback<'s>( args: v8::FunctionCallbackArguments<'s>, mut rv: v8::ReturnValue<'_, v8::Value>, ) { - let Some(performance) = require_performance_receiver(scope, args.this()) else { - return; - }; + let performance = args.this(); let Some(parsed) = webidl::parse_args::(scope, &args) else { return; }; diff --git a/moli-renderer-v8/src/context_bootstrap/performance_runtime/memory.rs b/moli-renderer-v8/src/context_bootstrap/performance_runtime/memory.rs index 4419420e31..74d9535688 100644 --- a/moli-renderer-v8/src/context_bootstrap/performance_runtime/memory.rs +++ b/moli-renderer-v8/src/context_bootstrap/performance_runtime/memory.rs @@ -1,4 +1,3 @@ -use super::PERFORMANCE_TIME_ORIGIN_SLOT; use crate::util::{ callback_data_index_value, callback_data_item, get_private_value, set_private_value, throw_type_error, @@ -55,10 +54,6 @@ pub(super) fn performance_memory_getter<'s>( args: v8::FunctionCallbackArguments<'s>, mut rv: v8::ReturnValue<'s, v8::Value>, ) { - if get_private_value(scope, args.this(), PERFORMANCE_TIME_ORIGIN_SLOT).is_none() { - throw_type_error(scope, "Illegal invocation"); - return; - } let sizes = heap_sample(scope); let Some(context) = args.this().get_creation_context(scope) else { return; diff --git a/moli-renderer-v8/src/context_bootstrap/performance_runtime/resource_buffer.rs b/moli-renderer-v8/src/context_bootstrap/performance_runtime/resource_buffer.rs index 5b7c968d2e..b4e8bace17 100644 --- a/moli-renderer-v8/src/context_bootstrap/performance_runtime/resource_buffer.rs +++ b/moli-renderer-v8/src/context_bootstrap/performance_runtime/resource_buffer.rs @@ -2,9 +2,7 @@ use super::*; use crate::web_api_interfaces; use crate::{ native_bridge::{JsContextHost, ResourceTimingBufferId}, - util::{ - context_host_ptr_from_global_bridge, get_private_value, set_private_value, throw_type_error, - }, + util::{context_host_ptr_from_global_bridge, get_private_value, set_private_value}, webidl, }; use moli_webapi_declare::WebApiFunctionTemplate; @@ -19,7 +17,7 @@ struct SetResourceTimingBufferSizeArgs { } #[derive(WebApiFunctionTemplate)] -#[webapi(interface = web_api_interfaces::Performance, enumerable)] +#[webapi(interface = web_api_interfaces::Performance, enumerable, receiver)] struct PerformanceResourceTimingBufferMembersDeclaration { #[webapi(method, length = 0, callback = clear_resource_timings_callback)] clear_resource_timings: (), @@ -95,9 +93,7 @@ fn clear_resource_timings_callback<'s>( args: v8::FunctionCallbackArguments<'s>, mut rv: v8::ReturnValue<'_, v8::Value>, ) { - let Some(performance) = performance_receiver(scope, &args) else { - return; - }; + let performance = args.this(); // Resource Timing ยง3.4 intentionally clears only the primary buffer and // its current size here. The pending flag and secondary buffer survive so // the already-queued buffer-full task can process overflow entries; see @@ -111,9 +107,7 @@ fn set_resource_timing_buffer_size_callback<'s>( args: v8::FunctionCallbackArguments<'s>, mut rv: v8::ReturnValue<'_, v8::Value>, ) { - let Some(performance) = performance_receiver(scope, &args) else { - return; - }; + let performance = args.this(); let Some(parsed) = webidl::parse_args::(scope, &args) else { return; }; @@ -129,9 +123,7 @@ fn on_resource_timing_buffer_full_getter<'s>( args: v8::FunctionCallbackArguments<'s>, mut rv: v8::ReturnValue<'_, v8::Value>, ) { - let Some(performance) = performance_receiver(scope, &args) else { - return; - }; + let performance = args.this(); let handler = get_private_value( scope, performance, @@ -147,9 +139,7 @@ fn on_resource_timing_buffer_full_setter<'s>( args: v8::FunctionCallbackArguments<'s>, _rv: v8::ReturnValue<'_, v8::Value>, ) { - let Some(performance) = performance_receiver(scope, &args) else { - return; - }; + let performance = args.this(); let value = args.get(0); let handler = if value.is_object() { value @@ -172,18 +162,6 @@ fn on_resource_timing_buffer_full_setter<'s>( ); } -fn performance_receiver<'s>( - scope: &mut v8::PinScope<'s, '_>, - args: &v8::FunctionCallbackArguments<'s>, -) -> Option> { - let receiver = args.this(); - if performance_slot_value(scope, receiver, PERFORMANCE_TIME_ORIGIN_SLOT).is_none() { - throw_type_error(scope, "Illegal invocation"); - return None; - } - Some(receiver) -} - fn resource_timing_buffer_context<'s>( scope: &mut v8::PinScope<'s, '_>, performance: v8::Local<'s, v8::Object>, diff --git a/moli-renderer-v8/src/script_vm/tests/browser_api/navigation/performance.rs b/moli-renderer-v8/src/script_vm/tests/browser_api/navigation/performance.rs index 09f5380b50..3d02a27c1c 100644 --- a/moli-renderer-v8/src/script_vm/tests/browser_api/navigation/performance.rs +++ b/moli-renderer-v8/src/script_vm/tests/browser_api/navigation/performance.rs @@ -667,6 +667,12 @@ fn performance_root_slots_ignore_reflection_and_spoofing() { }; const firstEntry = eventCounts.entries().next().value; const json = performance.toJSON(); + const receiverError = invoke => { + let caught; + try { invoke(); } catch (error) { caught = error; } + if (!(caught instanceof TypeError)) throw new Error('spoofed Performance receiver was accepted'); + return caught.name; + }; return JSON.stringify({ initialPerformanceNames, initialNavigationNames, @@ -687,8 +693,8 @@ fn performance_root_slots_ignore_reflection_and_spoofing() { eventCountsFirstEntry: `${firstEntry[0]}:${firstEntry[1]}`, jsonTimeOriginStable: json.timeOrigin === timeOrigin, jsonNavigationType: json.navigation.type, - fakeTimeOrigin: String(timeOriginGetter.call(fakePerformance)), - fakeTiming: String(timingGetter.call(fakePerformance)), + fakeTimeOrigin: receiverError(() => timeOriginGetter.call(fakePerformance)), + fakeTiming: receiverError(() => timingGetter.call(fakePerformance)), fakeNavigationType: String(navigationTypeGetter.call(fakeNavigation)), fakeEventCountsGet: String(eventCountsPrototype.get.call(fakeEventCounts, "click")), fakeEventCountsValue: String(eventCountsPrototype.values.call(fakeEventCounts).next().value) @@ -700,7 +706,7 @@ fn performance_root_slots_ignore_reflection_and_spoofing() { assert_eq!( result, - r#"{"initialPerformanceNames":[],"initialNavigationNames":[],"initialEventCountsNames":[],"timeOriginSpoofIgnored":true,"timingStable":true,"navigationStable":true,"eventCountsStable":true,"performanceDescriptorsStable":true,"entriesSpoofIgnored":1,"navigationType":0,"navigationRedirectCount":0,"navigationDescriptorsStable":true,"eventCountsClick":0,"eventCountsFirstValue":0,"eventCountsFirstEntry":"auxclick:0","jsonTimeOriginStable":true,"jsonNavigationType":0,"fakeTimeOrigin":"undefined","fakeTiming":"undefined","fakeNavigationType":"undefined","fakeEventCountsGet":"0","fakeEventCountsValue":"0"}"# + r#"{"initialPerformanceNames":[],"initialNavigationNames":[],"initialEventCountsNames":[],"timeOriginSpoofIgnored":true,"timingStable":true,"navigationStable":true,"eventCountsStable":true,"performanceDescriptorsStable":true,"entriesSpoofIgnored":1,"navigationType":0,"navigationRedirectCount":0,"navigationDescriptorsStable":true,"eventCountsClick":0,"eventCountsFirstValue":0,"eventCountsFirstEntry":"auxclick:0","jsonTimeOriginStable":true,"jsonNavigationType":0,"fakeTimeOrigin":"TypeError","fakeTiming":"TypeError","fakeNavigationType":"undefined","fakeEventCountsGet":"0","fakeEventCountsValue":"0"}"# ); } diff --git a/moli-renderer-v8/src/script_vm/tests/mod.rs b/moli-renderer-v8/src/script_vm/tests/mod.rs index 162c5fcb79..96fdaed66d 100644 --- a/moli-renderer-v8/src/script_vm/tests/mod.rs +++ b/moli-renderer-v8/src/script_vm/tests/mod.rs @@ -2158,6 +2158,7 @@ mod http_fixture; mod media_owner_playback_interfaces; mod media_recorder_shell; mod performance_observer_contract; +mod performance_receiver_consolidation; mod svg_animation_interfaces; mod svg_computed_path_precision; mod svg_transform_sync_consolidation; diff --git a/moli-renderer-v8/src/script_vm/tests/performance_receiver_consolidation.rs b/moli-renderer-v8/src/script_vm/tests/performance_receiver_consolidation.rs new file mode 100644 index 0000000000..24dbc7a039 --- /dev/null +++ b/moli-renderer-v8/src/script_vm/tests/performance_receiver_consolidation.rs @@ -0,0 +1,34 @@ +use super::*; + +#[test] +fn performance_bindings_validate_native_receivers_before_conversion() { + let mut vm = new_parsed_test_vm( + "https://performance_receiver_consolidation.test/", + "", + ); + assert_eq!(vm.eval(r#"(() => { + const child=document.body.appendChild(document.createElement('iframe')).contentWindow; + let conversions=0,traps=0; + const argument={toString(){conversions++;return 'entry';},valueOf(){conversions++;return 1;}}; + for(const realm of [window,child]) { + const real=realm.performance,proto=realm.Performance.prototype; + const revoked=Proxy.revocable(real,{});revoked.revoke(); + const invalid=[{},Object.create(real),Object.create(proto),new Proxy(real,{get(){traps++;throw Error('trap');}}),revoked.proxy]; + const calls=['now','mark','measure','clearMarks','clearMeasures','getEntries','getEntriesByName','getEntriesByType','toJSON','clearResourceTimings','setResourceTimingBufferSize'].map(name=>[name,(target)=>proto[name].call(target,argument)]); + for(const name of ['timeOrigin','memory','navigation','timing','eventCounts','onresourcetimingbufferfull']) { + const descriptor=Object.getOwnPropertyDescriptor(proto,name); + if(!descriptor)throw Error('missing '+name); + calls.push([name,(target)=>descriptor.get.call(target)]); + if(descriptor.set)calls.push([name+' setter',(target)=>descriptor.set.call(target,argument)]); + } + for(const [name,invoke] of calls)for(const target of invalid){ + let caught;try{invoke(target);}catch(error){caught=error;} + if(!(caught instanceof realm.TypeError))throw Error(name+' did not reject invalid receiver in callee realm'); + } + const getter=Object.getOwnPropertyDescriptor(proto,'timeOrigin').get; + for(const target of [performance,child.performance])if(getter.call(target)!==target.timeOrigin)throw Error('genuine cross-realm receiver'); + } + if(conversions||traps)throw Error('receiver validation executed author code'); + return 'ok'; +})()"#).expect("native binding regression"), "ok"); +}