mirror of
https://github.com/lexmount/moli.git
synced 2026-10-31 00:06:19 +00:00
refactor(performance): use generated native receiver checks
This commit is contained in:
@@ -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");
|
||||
}
|
||||
Reference in New Issue
Block a user