refactor(performance): use generated native receiver checks

This commit is contained in:
ldm0
2026-10-11 01:39:25 +08:00
committed by Donough Liu
parent 9b726d5821
commit f7a49daf6a
7 changed files with 56 additions and 65 deletions
@@ -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()),
@@ -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::<PerformanceMarkArgs>(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<v8::Local<'s, v8::Object>> {
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::<PerformanceClearMarksArgs>(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::<PerformanceMeasureArgs>(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::<PerformanceClearMeasuresArgs>(scope, &args) else {
return;
};
@@ -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;
@@ -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::<SetResourceTimingBufferSizeArgs>(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<v8::Local<'s, v8::Object>> {
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>,
@@ -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"}"#
);
}
@@ -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;
@@ -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/",
"<!doctype html><body></body>",
);
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");
}