diff --git a/moli-renderer-v8/src/context_bootstrap/crypto/keys.rs b/moli-renderer-v8/src/context_bootstrap/crypto/keys.rs index 81f4164ba1..9acf5db7ff 100644 --- a/moli-renderer-v8/src/context_bootstrap/crypto/keys.rs +++ b/moli-renderer-v8/src/context_bootstrap/crypto/keys.rs @@ -1,7 +1,6 @@ use super::*; use crate::util::{ - callback_data_index_value, get_private_value, global_constructor_object, - serialize_v8_iter_array, set_private_value, + callback_data_index_value, get_private_value, serialize_v8_iter_array, set_private_value, }; use crate::web_api_interfaces; use moli_webapi_declare::{WebApiFunctionTemplate, WebApiObject}; @@ -295,7 +294,13 @@ pub(crate) fn crypto_key_object_from_clone_payload<'s>( scope: &mut v8::PinScope<'s, '_>, payload: CryptoKeyClonePayload, ) -> Option> { - global_constructor_object(scope, "CryptoKey")?; + // Like Blink's ExecutionContextExposesInterface(kCryptoKeyTag), check + // native exposure before resolving the intrinsic identity. Public globals + // can be deleted or replaced independently of secure-context policy. + if !super::super::exposed_interfaces::is_realm_interface_exposed(scope, "CryptoKey") { + return None; + } + super::super::ensure_intrinsic_interface_constructor(scope, "CryptoKey").ok()?; if !clone_payload_algorithm_is_supported(&payload.key_type, &payload.algorithm) { return None; } diff --git a/moli-renderer-v8/src/context_bootstrap/event_document.rs b/moli-renderer-v8/src/context_bootstrap/event_document.rs index 8dd88031c2..6ef0299e89 100644 --- a/moli-renderer-v8/src/context_bootstrap/event_document.rs +++ b/moli-renderer-v8/src/context_bootstrap/event_document.rs @@ -100,10 +100,7 @@ pub(super) fn document_create_event_callback<'s>( // belongs to the callee realm even when its receiver is from another one. let exposed = { let target_scope = &mut v8::ContextScope::new(scope, relevant_context); - super::exposed_interfaces::is_window_interface_exposed( - target_scope, - kind.constructor_name(), - ) + super::exposed_interfaces::is_realm_interface_exposed(target_scope, kind.constructor_name()) }; if !exposed { throw_not_supported_dom_exception( diff --git a/moli-renderer-v8/src/context_bootstrap/exposed_interfaces/finalize.rs b/moli-renderer-v8/src/context_bootstrap/exposed_interfaces/finalize.rs index 45e1de1e7d..c9e7ef02e2 100644 --- a/moli-renderer-v8/src/context_bootstrap/exposed_interfaces/finalize.rs +++ b/moli-renderer-v8/src/context_bootstrap/exposed_interfaces/finalize.rs @@ -1,8 +1,5 @@ use anyhow::{Context, Result, anyhow}; -use super::materialize::{ - ensure_intrinsic_interface_constructor, ensure_intrinsic_interface_prototype, -}; use crate::context_bootstrap::{ crypto::finalize_crypto_realm_bindings, events::finalize_pointer_event_realm_bindings, notification_runtime::finalize_notification_realm_bindings, @@ -73,20 +70,22 @@ fn realm_dependent_finalizer(interface_name: &str) -> Option, +pub(super) fn finalize_materialized_interface<'s>( + scope: &mut v8::PinScope<'s, '_>, interface_name: &str, + constructor: v8::Local<'s, v8::Function>, + prototype: v8::Local<'s, v8::Object>, ) -> Result<()> { let Some(finalizer) = realm_dependent_finalizer(interface_name) else { return Ok(()); }; - let prototype = ensure_intrinsic_interface_prototype(scope, interface_name)?; match finalizer { RealmDependentFinalizer::NodeMixinUnscopables => { finalize_node_mixin_unscopables(scope, prototype) @@ -101,14 +100,12 @@ pub(super) fn finalize_materialized_interface( finalize_xml_http_request_event_target_realm_bindings(scope, prototype) } RealmDependentFinalizer::NotificationPermission => { - let constructor = ensure_intrinsic_interface_constructor(scope, "Notification")?; finalize_notification_realm_bindings(scope, constructor.into()) } RealmDependentFinalizer::PointerEventSecureContextSurface => { finalize_pointer_event_realm_bindings(scope, prototype) } RealmDependentFinalizer::PerformanceObserverSupportedEntryTypes => { - let constructor = ensure_intrinsic_interface_constructor(scope, "PerformanceObserver")?; finalize_performance_observer_realm_bindings(scope, constructor.into()) } } diff --git a/moli-renderer-v8/src/context_bootstrap/exposed_interfaces/install.rs b/moli-renderer-v8/src/context_bootstrap/exposed_interfaces/install.rs index c4a7292468..eb7032c719 100644 --- a/moli-renderer-v8/src/context_bootstrap/exposed_interfaces/install.rs +++ b/moli-renderer-v8/src/context_bootstrap/exposed_interfaces/install.rs @@ -1,18 +1,19 @@ use std::rc::Rc; use anyhow::{Result, anyhow}; +use moli_v8_util::install_web_api_intrinsic_resolver; -use super::materialize::exposed_interface_lazy_getter; +use super::materialize::{exposed_interface_lazy_getter, resolve_web_api_intrinsic}; #[cfg(test)] use super::metadata::STORAGE_INTERFACE_NAMES; use super::metadata::{GlobalInstallation, RealmKind, TemplateBuildProfile}; -use super::realm_registry::{IntrinsicInterfaceRegistry, RealmInterfaceState}; +use super::realm_registry::{IntrinsicInterfaceRegistry, RealmInterfaceEntry}; use super::template_registry::ExposedInterfaceTemplateRegistry; use crate::context_bootstrap::specs::ConstructorSpec; use crate::util::{ - constructor_object, constructor_prototype_object, get_private_value, - initialize_intrinsic_interface_registry, register_intrinsic_interface, - registered_intrinsic_constructor, registered_intrinsic_prototype, v8str, + constructor_object, constructor_prototype_object, initialize_ecmascript_intrinsic_registry, + register_ecmascript_intrinsic, registered_ecmascript_constructor, + registered_ecmascript_prototype, v8str, }; const LEGACY_WINDOW_INTERFACE_ALIASES: &[(&str, &str)] = &[ @@ -155,24 +156,32 @@ pub(crate) fn filter_window_exposed_interfaces( /// Exposure is determined by the realm's native metadata, independently of /// author changes to the corresponding global property. -pub(in crate::context_bootstrap) fn is_window_interface_exposed( +pub(in crate::context_bootstrap) fn is_realm_interface_exposed( scope: &mut v8::PinScope<'_, '_>, name: &str, ) -> bool { - let global = scope.get_current_context().global(scope); - let secure = get_private_value( - scope, - global, - crate::context_bootstrap::runtime_state::WINDOW_SECURE_CONTEXT_AVAILABLE_SLOT, - ) - .is_some_and(|value| value.boolean_value(scope)); let Some(registry) = ExposedInterfaceTemplateRegistry::current(scope) else { return false; }; + let Ok(realm) = IntrinsicInterfaceRegistry::for_current_context(scope, registry.len()) else { + return false; + }; + let realm_kind = realm.realm_kind(); + let secure = match realm_kind { + RealmKind::Window => { + let global = scope.get_current_context().global(scope); + crate::context_bootstrap::runtime_state::window_realm_secure_context_available( + scope, global, + ) + } + RealmKind::DedicatedWorker | RealmKind::SharedWorker | RealmKind::ServiceWorker => { + crate::worker::worker_realm_secure_context_available(scope) + } + }; registry .id_by_name(name) .and_then(|id| registry.metadata(id)) - .is_some_and(|metadata| metadata.is_exposed(RealmKind::Window, secure)) + .is_some_and(|metadata| metadata.is_exposed(realm_kind, secure)) } pub(crate) fn initialize_realm_interface_registry( @@ -194,7 +203,7 @@ pub(crate) fn capture_eager_intrinsic_interfaces<'s>( ) -> Result<()> { let registry = ExposedInterfaceTemplateRegistry::current(scope) .ok_or_else(|| anyhow!("exposed interface template registry is unavailable"))?; - initialize_intrinsic_interface_registry(scope, global); + initialize_ecmascript_intrinsic_registry(scope, global); capture_ecmascript_intrinsic(scope, global, "Error")?; let realm = IntrinsicInterfaceRegistry::initialize_for_current_context( scope, @@ -203,34 +212,21 @@ pub(crate) fn capture_eager_intrinsic_interfaces<'s>( )?; for metadata in registry.metadata_entries() { - let captured_constructor = registered_intrinsic_constructor(scope, global, metadata.name); - let captured_prototype = registered_intrinsic_prototype(scope, global, metadata.name); - if captured_constructor.is_some() != captured_prototype.is_some() { - realm.set_state(metadata.id, RealmInterfaceState::Failed)?; - return Err(anyhow!( - "eager intrinsic `{}` has partial constructor/prototype state", - metadata.name - )); - } - if let Some(constructor) = captured_constructor { - let prototype = captured_prototype.expect("captured intrinsic pair was validated"); - let public_interface = realm - .public_interface(scope, metadata.id) - .unwrap_or(constructor); - realm.register_intrinsic_objects( - scope, - global, - metadata.id, - metadata.name, - constructor, - prototype, - public_interface, - )?; - realm.set_state(metadata.id, RealmInterfaceState::Ready)?; - continue; + match &*realm + .entry(metadata.id) + .ok_or_else(|| anyhow!("interface id is out of range"))? + { + RealmInterfaceEntry::Ready(_) => continue, + RealmInterfaceEntry::Failed | RealmInterfaceEntry::Materializing => { + return Err(anyhow!( + "eager intrinsic `{}` is not ready for capture", + metadata.name + )); + } + RealmInterfaceEntry::Uninitialized => {} } // Reading a public lazy property here would defeat lazy - // materialization. Lazy callbacks register their own intrinsic pair. + // materialization. Lazy callbacks publish their completed realm entry. // // Worker bootstrap still constructs interfaces outside the shared // registry in a few cohorts. Those entries have metadata, but no @@ -248,17 +244,9 @@ pub(crate) fn capture_eager_intrinsic_interfaces<'s>( let Some(prototype) = constructor_prototype_object(scope, constructor) else { continue; }; - realm.register_intrinsic_objects( - scope, - global, - metadata.id, - metadata.name, - constructor, - prototype, - constructor, - )?; - realm.set_state(metadata.id, RealmInterfaceState::Ready)?; + realm.publish_ready(scope, metadata.id, constructor, prototype, constructor)?; } + install_web_api_intrinsic_resolver(scope, resolve_web_api_intrinsic); Ok(()) } @@ -268,8 +256,8 @@ pub(super) fn capture_ecmascript_intrinsic<'s>( name: &str, ) -> Result<()> { match ( - registered_intrinsic_constructor(scope, global, name), - registered_intrinsic_prototype(scope, global, name), + registered_ecmascript_constructor(scope, global, name), + registered_ecmascript_prototype(scope, global, name), ) { (Some(_), Some(_)) => return Ok(()), (None, None) => {} @@ -283,7 +271,7 @@ pub(super) fn capture_ecmascript_intrinsic<'s>( .ok_or_else(|| anyhow!("missing ECMAScript intrinsic constructor `{name}`"))?; let prototype = constructor_prototype_object(scope, constructor) .ok_or_else(|| anyhow!("ECMAScript intrinsic `{name}` has no object prototype"))?; - if !register_intrinsic_interface(scope, global, name, constructor, prototype) { + if !register_ecmascript_intrinsic(scope, global, name, constructor, prototype) { return Err(anyhow!("failed to capture ECMAScript intrinsic `{name}`")); } Ok(()) diff --git a/moli-renderer-v8/src/context_bootstrap/exposed_interfaces/materialize.rs b/moli-renderer-v8/src/context_bootstrap/exposed_interfaces/materialize.rs index 9555168507..13a2daa39b 100644 --- a/moli-renderer-v8/src/context_bootstrap/exposed_interfaces/materialize.rs +++ b/moli-renderer-v8/src/context_bootstrap/exposed_interfaces/materialize.rs @@ -1,19 +1,38 @@ use std::rc::Rc; use anyhow::{Result, anyhow}; +use moli_v8_util::{WebApiIntrinsicKind, WebApiIntrinsicLookup}; use super::finalize::finalize_materialized_interface; use super::metadata::{InterfaceId, ResolvedPrototypeProperty}; -use super::realm_registry::{IntrinsicInterfaceRegistry, RealmInterfaceState}; +use super::realm_registry::{IntrinsicInterfaceRegistry, RealmInterfaceEntry}; use super::template_registry::ExposedInterfaceTemplateRegistry; use crate::context_bootstrap::constructors::html_element_constructor_with_early_sanity_trap; use crate::context_bootstrap::runtime_state::set_interface_prototype_constructor; use crate::context_bootstrap::shared::throw_error; use crate::context_bootstrap::specs::ConstructorKind; -use crate::util::{ - constructor_prototype_object, registered_intrinsic_constructor, registered_intrinsic_prototype, - v8str, -}; +use crate::util::{constructor_prototype_object, v8str}; + +/// Adapter for shared declaration helpers. Recognized Web APIs always resolve +/// through the realm entry, even when a public binding was deleted or replaced. +pub(super) fn resolve_web_api_intrinsic<'s>( + scope: &mut v8::PinScope<'s, '_>, + name: &str, + kind: WebApiIntrinsicKind, +) -> WebApiIntrinsicLookup<'s> { + let Some(registry) = ExposedInterfaceTemplateRegistry::current(scope) else { + return WebApiIntrinsicLookup::Unmanaged; + }; + if registry.id_by_name(name).is_none() { + return WebApiIntrinsicLookup::Unmanaged; + } + WebApiIntrinsicLookup::Managed(match kind { + WebApiIntrinsicKind::Constructor => ensure_intrinsic_interface_constructor(scope, name) + .ok() + .map(Into::into), + WebApiIntrinsicKind::Prototype => ensure_intrinsic_interface_prototype(scope, name).ok(), + }) +} pub(super) fn exposed_interface_lazy_getter<'s>( scope: &mut v8::PinScope<'s, '_>, @@ -56,11 +75,11 @@ pub(super) fn materialize_interface<'s>( .ok_or_else(|| anyhow!("unknown exposed interface id {}", id.index()))?; let realm = IntrinsicInterfaceRegistry::for_current_context(scope, registry.len())?; - match realm - .state(id) + match &*realm + .entry(id) .ok_or_else(|| anyhow!("interface state is out of range"))? { - RealmInterfaceState::Ready => { + RealmInterfaceEntry::Ready(_) => { return realm .public_interface(scope, id) .map(Into::into) @@ -71,34 +90,23 @@ pub(super) fn materialize_interface<'s>( ) }); } - RealmInterfaceState::Materializing => { + RealmInterfaceEntry::Materializing => { return Err(anyhow!("materialization cycle reached `{}`", metadata.name)); } - RealmInterfaceState::Finalizing => { - return realm - .public_interface(scope, id) - .map(Into::into) - .ok_or_else(|| { - anyhow!( - "finalizing realm-owned public interface object `{}` is missing", - metadata.name - ) - }); - } - RealmInterfaceState::Failed => { + RealmInterfaceEntry::Failed => { return Err(anyhow!( "a previous materialization of `{}` failed", metadata.name )); } - RealmInterfaceState::Uninitialized => {} + RealmInterfaceEntry::Uninitialized => {} } - realm.set_state(id, RealmInterfaceState::Materializing)?; + realm.begin_materialization(id)?; match materialize_uninitialized_interface(scope, ®istry, &realm, id) { Ok(interface) => Ok(interface), Err(error) => { - realm.set_state(id, RealmInterfaceState::Failed)?; + realm.fail(id)?; Err(error) } } @@ -186,46 +194,6 @@ fn materialize_uninitialized_interface<'s>( let metadata = registry .metadata(id) .ok_or_else(|| anyhow!("unknown exposed interface id {}", id.index()))?; - let context = scope.get_current_context(); - let global = context.global(scope); - match ( - registered_intrinsic_constructor(scope, global, metadata.name), - registered_intrinsic_prototype(scope, global, metadata.name), - ) { - (Some(constructor), Some(prototype)) => { - // A complete native-only pair can outlive an interrupted eager - // capture. Reuse its identities without consulting author bindings. - if constructor.get_creation_context(scope) != Some(context) - || prototype.get_creation_context(scope) != Some(context) - { - return Err(anyhow!( - "registered intrinsic interface `{}` belongs to another realm", - metadata.name - )); - } - let constructor = v8::Local::::try_from(constructor).map_err(|_| { - anyhow!( - "intrinsic constructor `{}` is not a Function", - metadata.name - ) - })?; - return finish_materialized_interface( - scope, - registry, - realm, - id, - constructor, - prototype, - ); - } - (None, None) => {} - _ => { - return Err(anyhow!( - "uninitialized interface `{}` has partial registry state", - metadata.name - )); - } - } let parent = metadata .parent .map(|parent_id| intrinsic_parent(scope, registry, parent_id)) @@ -322,19 +290,16 @@ fn finish_materialized_interface<'s>( } _ => constructor.into(), }; - let global = scope.get_current_context().global(scope); - realm.register_intrinsic_objects( + // 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)?; + realm.publish_ready( scope, - global, id, - metadata.name, constructor.into(), constructor_prototype, public_interface, )?; - realm.set_state(id, RealmInterfaceState::Finalizing)?; - finalize_materialized_interface(scope, metadata.name)?; - realm.set_state(id, RealmInterfaceState::Ready)?; registry.record_materialization(id); Ok(public_interface.into()) } diff --git a/moli-renderer-v8/src/context_bootstrap/exposed_interfaces/mod.rs b/moli-renderer-v8/src/context_bootstrap/exposed_interfaces/mod.rs index bbacebf965..888e685109 100644 --- a/moli-renderer-v8/src/context_bootstrap/exposed_interfaces/mod.rs +++ b/moli-renderer-v8/src/context_bootstrap/exposed_interfaces/mod.rs @@ -11,7 +11,7 @@ pub(crate) use install::capture_eager_intrinsic_interfaces; pub(super) use install::{ filter_window_exposed_interfaces, initialize_realm_interface_registry, install_interface_template_metadata, install_window_exposed_interfaces, - install_worker_exposed_interfaces, is_lazy_exposed_interface, is_window_interface_exposed, + install_worker_exposed_interfaces, is_lazy_exposed_interface, is_realm_interface_exposed, prepare_worker_event_target_template, }; #[cfg(test)] diff --git a/moli-renderer-v8/src/context_bootstrap/exposed_interfaces/realm_registry.rs b/moli-renderer-v8/src/context_bootstrap/exposed_interfaces/realm_registry.rs index 8267716715..cdcba7a632 100644 --- a/moli-renderer-v8/src/context_bootstrap/exposed_interfaces/realm_registry.rs +++ b/moli-renderer-v8/src/context_bootstrap/exposed_interfaces/realm_registry.rs @@ -1,29 +1,25 @@ -use std::cell::RefCell; +use std::cell::{Ref, RefCell}; use std::rc::Rc; use anyhow::{Result, anyhow}; use super::metadata::{InterfaceId, RealmKind}; -use crate::util::{ - register_intrinsic_interface, registered_intrinsic_constructor, registered_intrinsic_prototype, -}; -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub(super) enum RealmInterfaceState { +/// Only a completely initialized interface retains intrinsic handles and is +/// visible to callers. State and identities share one realm-owned entry. +pub(super) enum RealmInterfaceEntry { Uninitialized, Materializing, - Finalizing, - Ready, + Ready(RealmInterfaceObjects), Failed, } pub(super) struct IntrinsicInterfaceRegistry { realm_kind: RealmKind, - states: RefCell>, - objects: RefCell>>, + entries: RefCell>, } -struct RealmInterfaceObjects { +pub(super) struct RealmInterfaceObjects { constructor: v8::Global, prototype: v8::Global, public_interface: v8::Global, @@ -33,8 +29,11 @@ impl IntrinsicInterfaceRegistry { fn new(interface_count: usize, realm_kind: RealmKind) -> Self { Self { realm_kind, - states: RefCell::new(vec![RealmInterfaceState::Uninitialized; interface_count]), - objects: RefCell::new((0..interface_count).map(|_| None).collect()), + entries: RefCell::new( + (0..interface_count) + .map(|_| RealmInterfaceEntry::Uninitialized) + .collect(), + ), } } @@ -45,12 +44,7 @@ impl IntrinsicInterfaceRegistry { ) -> Result> { let context = scope.get_current_context(); if let Some(registry) = context.get_slot::() { - if registry.states.borrow().len() != interface_count { - return Err(anyhow!( - "realm interface registry size changed from {} to {interface_count}", - registry.states.borrow().len() - )); - } + registry.validate_size(interface_count)?; if registry.realm_kind != realm_kind { return Err(anyhow!( "realm interface registry kind changed from {:?} to {realm_kind:?}", @@ -60,18 +54,8 @@ impl IntrinsicInterfaceRegistry { return Ok(registry); } let registry = Rc::new(Self::new(interface_count, realm_kind)); - if let Some(previous) = context.set_slot(registry.clone()) { - if previous.states.borrow().len() != interface_count - || previous.realm_kind != realm_kind - { - return Err(anyhow!( - "realm interface registry was concurrently initialized with incompatible metadata" - )); - } - Ok(previous) - } else { - Ok(registry) - } + context.set_slot(registry.clone()); + Ok(registry) } pub(super) fn for_current_context( @@ -82,127 +66,96 @@ impl IntrinsicInterfaceRegistry { .get_current_context() .get_slot::() .ok_or_else(|| anyhow!("realm interface registry is not initialized"))?; - if registry.states.borrow().len() != interface_count { - return Err(anyhow!( - "realm interface registry size changed from {} to {interface_count}", - registry.states.borrow().len() - )); - } + registry.validate_size(interface_count)?; Ok(registry) } - pub(super) fn state(&self, id: InterfaceId) -> Option { - self.states.borrow().get(id.index()).copied() - } - - pub(super) fn set_state(&self, id: InterfaceId, state: RealmInterfaceState) -> Result<()> { - let interface_count = self.states.borrow().len(); - let mut states = self.states.borrow_mut(); - let slot = states.get_mut(id.index()).ok_or_else(|| { - anyhow!( - "interface state id {} is out of range for {interface_count} entries", - id.index() - ) - })?; - *slot = state; - if state == RealmInterfaceState::Failed { - self.objects.borrow_mut()[id.index()] = None; + fn validate_size(&self, interface_count: usize) -> Result<()> { + let current_count = self.entries.borrow().len(); + if current_count != interface_count { + return Err(anyhow!( + "realm interface registry size changed from {current_count} to {interface_count}" + )); } Ok(()) } - /// Publishes the same intrinsic identities to both registries. Mark the - /// interface in progress before either store changes, and make any failed - /// publication terminal even if V8 has retained an immutable partial pair. - /// Successful publication stays Materializing: eager capture then marks it - /// Ready, while lazy materialization exposes it only when Finalizing starts. - pub(super) fn register_intrinsic_objects<'s>( - &self, - scope: &mut v8::PinScope<'s, '_>, - global: v8::Local<'s, v8::Object>, - id: InterfaceId, - name: &str, - constructor: v8::Local<'s, v8::Object>, - prototype: v8::Local<'s, v8::Object>, - public_interface: v8::Local<'s, v8::Object>, - ) -> Result<()> { - if self.state(id) == Some(RealmInterfaceState::Failed) { - return Err(anyhow!("a previous materialization of `{name}` failed")); - } - self.set_state(id, RealmInterfaceState::Materializing)?; - let result = (|| { - // Retain the Rust objects before writing the immutable JS entries. - // Keep them hidden until the caller starts finalization or - // completes eager capture. - self.register_objects(scope, id, constructor, prototype, public_interface)?; - match ( - registered_intrinsic_constructor(scope, global, name), - registered_intrinsic_prototype(scope, global, name), - ) { - (Some(existing_constructor), Some(existing_prototype)) => { - if !existing_constructor.strict_equals(constructor.into()) - || !existing_prototype.strict_equals(prototype.into()) - { - return Err(anyhow!( - "intrinsic interface `{name}` was replaced after registration" - )); - } - } - (None, None) => { - if !register_intrinsic_interface(scope, global, name, constructor, prototype) { - return Err(anyhow!("failed to register intrinsic interface `{name}`")); - } - } - _ => { - return Err(anyhow!( - "intrinsic interface `{name}` has partial registry state" - )); - } - } - Ok(()) - })(); - if result.is_err() { - self.set_state(id, RealmInterfaceState::Failed)?; - } - result + pub(super) const fn realm_kind(&self) -> RealmKind { + self.realm_kind } - fn register_objects<'s>( - &self, - scope: &mut v8::PinScope<'s, '_>, - id: InterfaceId, - constructor: v8::Local<'s, v8::Object>, - prototype: v8::Local<'s, v8::Object>, - public_interface: v8::Local<'s, v8::Object>, - ) -> Result<()> { - let interface_count = self.objects.borrow().len(); - { - let objects = self.objects.borrow(); - if let Some(existing) = objects.get(id.index()).and_then(Option::as_ref) { - let same_constructor = - v8::Local::new(scope, &existing.constructor).strict_equals(constructor.into()); - let same_prototype = - v8::Local::new(scope, &existing.prototype).strict_equals(prototype.into()); - let same_public_interface = v8::Local::new(scope, &existing.public_interface) - .strict_equals(public_interface.into()); - if same_constructor && same_prototype && same_public_interface { - return Ok(()); - } - return Err(anyhow!( - "interface objects for id {} were replaced after registration", - id.index() - )); - } - } - let mut objects = self.objects.borrow_mut(); - let slot = objects.get_mut(id.index()).ok_or_else(|| { + pub(super) fn entry(&self, id: InterfaceId) -> Option> { + Ref::filter_map(self.entries.borrow(), |entries| entries.get(id.index())).ok() + } + + pub(super) fn begin_materialization(&self, id: InterfaceId) -> Result<()> { + self.set_pending_entry(id, RealmInterfaceEntry::Materializing) + } + + pub(super) fn fail(&self, id: InterfaceId) -> Result<()> { + self.set_pending_entry(id, RealmInterfaceEntry::Failed) + } + + fn set_pending_entry(&self, id: InterfaceId, entry: RealmInterfaceEntry) -> Result<()> { + let mut entries = self.entries.borrow_mut(); + let interface_count = entries.len(); + let slot = entries.get_mut(id.index()).ok_or_else(|| { anyhow!( - "interface object id {} is out of range for {interface_count} entries", + "interface id {} is out of range for {interface_count} entries", id.index() ) })?; - debug_assert!(slot.is_none()); - *slot = Some(RealmInterfaceObjects { + if !matches!( + (&*slot, &entry), + ( + RealmInterfaceEntry::Uninitialized, + RealmInterfaceEntry::Materializing + ) | ( + RealmInterfaceEntry::Materializing, + RealmInterfaceEntry::Failed + ) + ) { + return Err(anyhow!( + "invalid materialization transition for interface id {}", + id.index() + )); + } + *slot = entry; + Ok(()) + } + + /// The only publication point, shared by completed lazy materialization + /// and eager capture after bootstrap. Keep the prototype handle because + /// legacy factories share prototypes and author code can mutate properties. + pub(super) fn publish_ready<'s>( + &self, + scope: &mut v8::PinScope<'s, '_>, + id: InterfaceId, + constructor: v8::Local<'s, v8::Object>, + prototype: v8::Local<'s, v8::Object>, + public_interface: v8::Local<'s, v8::Object>, + ) -> Result<()> { + let mut entries = self.entries.borrow_mut(); + let interface_count = entries.len(); + let slot = entries.get_mut(id.index()).ok_or_else(|| { + anyhow!( + "interface id {} is out of range for {interface_count} entries", + id.index() + ) + })?; + match slot { + RealmInterfaceEntry::Ready(_) => { + return Err(anyhow!("interface id {} is already published", id.index())); + } + RealmInterfaceEntry::Failed => { + return Err(anyhow!( + "a previous materialization of interface id {} failed", + id.index() + )); + } + RealmInterfaceEntry::Uninitialized | RealmInterfaceEntry::Materializing => {} + } + *slot = RealmInterfaceEntry::Ready(RealmInterfaceObjects { constructor: v8::Global::new(scope, constructor), prototype: v8::Global::new(scope, prototype), public_interface: v8::Global::new(scope, public_interface), @@ -215,15 +168,11 @@ impl IntrinsicInterfaceRegistry { scope: &mut v8::PinScope<'s, '_>, id: InterfaceId, ) -> Option> { - if !matches!( - self.state(id)?, - RealmInterfaceState::Ready | RealmInterfaceState::Finalizing - ) { + let entries = self.entries.borrow(); + let RealmInterfaceEntry::Ready(objects) = entries.get(id.index())? else { return None; - } - let objects = self.objects.borrow(); - let object = objects.get(id.index())?.as_ref()?; - Some(v8::Local::new(scope, &object.constructor)) + }; + Some(v8::Local::new(scope, &objects.constructor)) } pub(super) fn prototype<'s>( @@ -231,15 +180,11 @@ impl IntrinsicInterfaceRegistry { scope: &mut v8::PinScope<'s, '_>, id: InterfaceId, ) -> Option> { - if !matches!( - self.state(id)?, - RealmInterfaceState::Ready | RealmInterfaceState::Finalizing - ) { + let entries = self.entries.borrow(); + let RealmInterfaceEntry::Ready(objects) = entries.get(id.index())? else { return None; - } - let objects = self.objects.borrow(); - let object = objects.get(id.index())?.as_ref()?; - Some(v8::Local::new(scope, &object.prototype)) + }; + Some(v8::Local::new(scope, &objects.prototype)) } pub(super) fn public_interface<'s>( @@ -247,15 +192,11 @@ impl IntrinsicInterfaceRegistry { scope: &mut v8::PinScope<'s, '_>, id: InterfaceId, ) -> Option> { - if !matches!( - self.state(id)?, - RealmInterfaceState::Ready | RealmInterfaceState::Finalizing - ) { + let entries = self.entries.borrow(); + let RealmInterfaceEntry::Ready(objects) = entries.get(id.index())? else { return None; - } - let objects = self.objects.borrow(); - let object = objects.get(id.index())?.as_ref()?; - Some(v8::Local::new(scope, &object.public_interface)) + }; + Some(v8::Local::new(scope, &objects.public_interface)) } } @@ -266,30 +207,15 @@ mod tests { use super::*; #[test] - fn set_state_updates_a_registered_interface() { - let registry = IntrinsicInterfaceRegistry::new(1, RealmKind::Window); - let id = InterfaceId::from_callback_data(0); - - registry - .set_state(id, RealmInterfaceState::Ready) - .expect("registered interface state should update"); - - assert_eq!(registry.state(id), Some(RealmInterfaceState::Ready)); - } - - #[test] - fn set_state_rejects_an_out_of_range_interface() { + fn materialization_rejects_an_out_of_range_interface() { let registry = IntrinsicInterfaceRegistry::new(1, RealmKind::Window); let error = registry - .set_state( - InterfaceId::from_callback_data(1), - RealmInterfaceState::Ready, - ) - .expect_err("out-of-range interface state must fail"); + .begin_materialization(InterfaceId::from_callback_data(1)) + .expect_err("out-of-range interface must fail"); assert_eq!( error.to_string(), - "interface state id 1 is out of range for 1 entries" + "interface id 1 is out of range for 1 entries" ); } @@ -308,11 +234,8 @@ mod tests { let public_interface = v8::Object::new(scope); registry - .register_objects(scope, id, constructor, prototype, public_interface) - .expect("realm interface objects should register"); - registry - .set_state(id, RealmInterfaceState::Ready) - .expect("realm interface objects should become ready"); + .publish_ready(scope, id, constructor, prototype, public_interface) + .expect("completed realm interface should publish"); context.detach_global(); assert!( diff --git a/moli-renderer-v8/src/context_bootstrap/exposed_interfaces/tests.rs b/moli-renderer-v8/src/context_bootstrap/exposed_interfaces/tests.rs index 8496bce14e..4a8a09fbfe 100644 --- a/moli-renderer-v8/src/context_bootstrap/exposed_interfaces/tests.rs +++ b/moli-renderer-v8/src/context_bootstrap/exposed_interfaces/tests.rs @@ -300,7 +300,7 @@ fn entered_context_template_build_is_isolate_cached_and_realm_neutral() { } #[test] -fn uninitialized_dom_string_map_adopts_its_registered_intrinsics() { +fn intrinsic_dom_string_map_materializes_without_reading_the_public_binding() { crate::ensure_v8_for_test(); let mut isolate = v8::Isolate::new(Default::default()); let scope = pin!(v8::HandleScope::new(&mut isolate)); @@ -330,13 +330,10 @@ fn uninitialized_dom_string_map_adopts_its_registered_intrinsics() { let prototype = crate::util::constructor_prototype_object(scope, constructor.into()) .expect("dataset prototype"); let global = context.global(scope); - assert!(crate::util::register_intrinsic_interface( + moli_v8_util::install_web_api_intrinsic_resolver( scope, - global, - "DOMStringMap", - constructor.into(), - prototype, - )); + super::materialize::resolve_web_api_intrinsic, + ); assert_eq!( global.set_lazy_data_property( scope, @@ -347,17 +344,17 @@ fn uninitialized_dom_string_map_adopts_its_registered_intrinsics() { ); reset_lazy_getter_calls(); - let recovered = super::ensure_intrinsic_interface_prototype(scope, "DOMStringMap") - .expect("a complete trusted registration must recover the dataset prototype"); + let intrinsic = crate::util::global_constructor_prototype(scope, "DOMStringMap") + .expect("the shared helper must materialize the trusted dataset prototype"); - assert!(recovered.strict_equals(prototype.into())); - assert_eq!( - realm.state(id), - Some(super::realm_registry::RealmInterfaceState::Ready) - ); + assert!(intrinsic.strict_equals(prototype.into())); + assert!(matches!( + &*realm.entry(id).unwrap(), + super::realm_registry::RealmInterfaceEntry::Ready(_) + )); assert!( super::ensure_intrinsic_interface_constructor(scope, "DOMStringMap") - .expect("recovered dataset constructor") + .expect("trusted dataset constructor") .strict_equals(constructor.into()) ); assert!( @@ -369,10 +366,45 @@ fn uninitialized_dom_string_map_adopts_its_registered_intrinsics() { assert_eq!( lazy_getter_calls(), 0, - "recovery must not read the public binding" + "materialization must not read the public binding" ); } +#[test] +fn materialization_cycle_keeps_intrinsic_objects_unpublished() { + crate::ensure_v8_for_test(); + let mut isolate = v8::Isolate::new(Default::default()); + let scope = pin!(v8::HandleScope::new(&mut isolate)); + let scope = &mut scope.init(); + let context = v8::Context::new(scope, Default::default()); + let scope = &mut v8::ContextScope::new(scope, context); + let registry = super::template_registry::ExposedInterfaceTemplateRegistry::install( + scope, + crate::context_bootstrap::specs::constructor_specs(), + super::metadata::TemplateBuildProfile::Window, + ) + .expect("template registry"); + let realm = super::realm_registry::IntrinsicInterfaceRegistry::initialize_for_current_context( + scope, + registry.len(), + super::RealmKind::Window, + ) + .expect("realm registry"); + let id = registry.id_by_name("Crypto").expect("Crypto interface"); + realm + .begin_materialization(id) + .expect("start materialization"); + + let error = super::materialize::materialize_interface(scope, id) + .expect_err("a reentrant lookup must report a cycle"); + + assert!(error.to_string().contains("materialization cycle")); + assert!(realm.constructor(scope, id).is_none()); + assert!(realm.prototype(scope, id).is_none()); + assert!(realm.public_interface(scope, id).is_none()); + assert_eq!(registry.materialization_count(id), 0); +} + #[test] fn ecmascript_intrinsic_capture_rejects_partial_registration_on_retry() { crate::ensure_v8_for_test(); @@ -382,8 +414,8 @@ fn ecmascript_intrinsic_capture_rejects_partial_registration_on_retry() { let context = v8::Context::new(scope, Default::default()); let scope = &mut v8::ContextScope::new(scope, context); let global = context.global(scope); - crate::util::initialize_intrinsic_interface_registry(scope, global); - let prototypes = crate::util::get_private_object(scope, global, "__moliIntrinsicPrototypes") + crate::util::initialize_ecmascript_intrinsic_registry(scope, global); + let prototypes = crate::util::get_private_object(scope, global, "__moliEcmascriptPrototypes") .expect("private prototype registry"); assert_eq!( prototypes.set_integrity_level(scope, v8::IntegrityLevel::Frozen), @@ -392,8 +424,8 @@ fn ecmascript_intrinsic_capture_rejects_partial_registration_on_retry() { super::install::capture_ecmascript_intrinsic(scope, global, "Error") .expect_err("the first capture must report its failed prototype publication"); - assert!(crate::util::registered_intrinsic_constructor(scope, global, "Error").is_some()); - assert!(crate::util::registered_intrinsic_prototype(scope, global, "Error").is_none()); + assert!(crate::util::registered_ecmascript_constructor(scope, global, "Error").is_some()); + assert!(crate::util::registered_ecmascript_prototype(scope, global, "Error").is_none()); let error = super::install::capture_ecmascript_intrinsic(scope, global, "Error") .expect_err("retrying a partial Error pair must not report success"); assert!(error.to_string().contains("partial registry state")); @@ -408,8 +440,8 @@ fn ecmascript_intrinsic_capture_rejects_prototype_without_constructor() { let context = v8::Context::new(scope, Default::default()); let scope = &mut v8::ContextScope::new(scope, context); let global = context.global(scope); - crate::util::initialize_intrinsic_interface_registry(scope, global); - let prototypes = crate::util::get_private_object(scope, global, "__moliIntrinsicPrototypes") + crate::util::initialize_ecmascript_intrinsic_registry(scope, global); + let prototypes = crate::util::get_private_object(scope, global, "__moliEcmascriptPrototypes") .expect("private prototype registry"); let constructor = crate::util::constructor_object(scope, global, "Error") .expect("ECMAScript Error constructor"); @@ -424,11 +456,18 @@ fn ecmascript_intrinsic_capture_rejects_prototype_without_constructor() { .expect_err("a prototype-only Error registration must not be completed from the global"); assert!(error.to_string().contains("partial registry state")); - assert!(crate::util::registered_intrinsic_constructor(scope, global, "Error").is_none()); + assert!(crate::util::registered_ecmascript_constructor(scope, global, "Error").is_none()); } #[test] -fn incomplete_intrinsic_registration_marks_materialization_failed() { +fn repeated_eager_capture_preserves_intrinsics_and_lazy_public_proxies() { + fn eager_constructor<'s>( + _scope: &mut v8::PinScope<'s, '_>, + _args: v8::FunctionCallbackArguments<'s>, + _rv: v8::ReturnValue<'s>, + ) { + } + crate::ensure_v8_for_test(); let mut isolate = v8::Isolate::new(Default::default()); let scope = pin!(v8::HandleScope::new(&mut isolate)); @@ -441,116 +480,46 @@ fn incomplete_intrinsic_registration_marks_materialization_failed() { super::metadata::TemplateBuildProfile::Window, ) .expect("template registry"); - let realm = super::realm_registry::IntrinsicInterfaceRegistry::initialize_for_current_context( - scope, - registry.len(), - super::RealmKind::Window, - ) - .expect("realm registry"); - let id = registry - .id_by_name("DOMStringMap") - .expect("dataset interface"); let global = context.global(scope); - crate::util::initialize_intrinsic_interface_registry(scope, global); - let prototypes = crate::util::get_private_object(scope, global, "__moliIntrinsicPrototypes") - .expect("private prototype registry"); - assert_eq!( - prototypes.set_integrity_level(scope, v8::IntegrityLevel::Frozen), - Some(true) - ); - let constructor = v8::Object::new(scope); - let prototype = v8::Object::new(scope); - assert!(!crate::util::register_intrinsic_interface( - scope, - global, - "DOMStringMap", - constructor, - prototype, - )); - assert!(crate::util::registered_intrinsic_constructor(scope, global, "DOMStringMap").is_some()); - assert!(crate::util::registered_intrinsic_prototype(scope, global, "DOMStringMap").is_none()); - - let error = super::ensure_intrinsic_interface_prototype(scope, "DOMStringMap") - .expect_err("an incomplete pair must not supply a dataset prototype"); - - assert!(error.to_string().contains("partial registry state")); - assert_eq!( - realm.state(id), - Some(super::realm_registry::RealmInterfaceState::Failed) - ); - let repeated_error = super::ensure_intrinsic_interface_prototype(scope, "DOMStringMap") - .expect_err("a failed materialization must not retry the partial registration"); - assert!( - repeated_error - .to_string() - .contains("previous materialization") - ); - assert_eq!(registry.build_count(id), 0); -} - -#[test] -fn failed_eager_intrinsic_capture_does_not_leave_an_uninitialized_interface() { - crate::ensure_v8_for_test(); - let mut isolate = v8::Isolate::new(Default::default()); - let scope = pin!(v8::HandleScope::new(&mut isolate)); - let scope = &mut scope.init(); - let context = v8::Context::new(scope, Default::default()); - let scope = &mut v8::ContextScope::new(scope, context); - let registry = super::template_registry::ExposedInterfaceTemplateRegistry::install( - scope, - crate::context_bootstrap::specs::constructor_specs(), - super::metadata::TemplateBuildProfile::Window, - ) - .expect("template registry"); - let realm = super::realm_registry::IntrinsicInterfaceRegistry::initialize_for_current_context( - scope, - registry.len(), - super::RealmKind::Window, - ) - .expect("realm registry"); - let global = context.global(scope); - let error_constructor = crate::util::constructor_object(scope, global, "Error") - .expect("ECMAScript Error constructor"); - let error_prototype = crate::util::constructor_prototype_object(scope, error_constructor) - .expect("ECMAScript Error prototype"); - assert!(crate::util::register_intrinsic_interface( - scope, - global, - "Error", - error_constructor, - error_prototype, - )); - let id = registry - .id_by_name("Window") - .expect("eager Window interface"); - let constructor = v8::Object::new(scope); - let prototype = v8::Object::new(scope); - assert_eq!( - constructor.set(scope, v8str(scope, "prototype").into(), prototype.into()), - Some(true) - ); + let constructor = v8::Function::new(scope, eager_constructor).expect("eager constructor"); + let prototype = crate::util::constructor_prototype_object(scope, constructor.into()) + .expect("eager prototype"); assert_eq!( global.set(scope, v8str(scope, "Window").into(), constructor.into()), Some(true) ); - let prototypes = crate::util::get_private_object(scope, global, "__moliIntrinsicPrototypes") - .expect("private prototype registry"); + super::capture_eager_intrinsic_interfaces(scope, global, super::RealmKind::Window) + .expect("first eager capture"); + let id = registry + .id_by_name("HTMLDivElement") + .expect("HTML interface"); + let public = super::materialize::materialize_interface(scope, id) + .expect("HTML interface materialization"); + assert!(public.is_proxy()); assert_eq!( - prototypes.set_integrity_level(scope, v8::IntegrityLevel::Frozen), + global.set(scope, v8str(scope, "Window").into(), v8::null(scope).into()), Some(true) ); super::capture_eager_intrinsic_interfaces(scope, global, super::RealmKind::Window) - .expect_err("a failed private registration must abort eager capture"); + .expect("repeat eager capture must preserve existing entries"); - assert_eq!( - realm.state(id), - Some(super::realm_registry::RealmInterfaceState::Failed) + assert!( + crate::util::global_constructor_object(scope, "Window") + .unwrap() + .strict_equals(constructor.into()) ); - assert!(realm.constructor(scope, id).is_none()); - assert!(realm.prototype(scope, id).is_none()); - assert!(realm.public_interface(scope, id).is_none()); - assert!(super::ensure_intrinsic_interface_prototype(scope, "Window").is_err()); + assert!( + crate::util::global_constructor_prototype(scope, "Window") + .unwrap() + .strict_equals(prototype.into()) + ); + assert!( + super::materialize::materialize_interface(scope, id) + .unwrap() + .strict_equals(public) + ); + assert_eq!(registry.materialization_count(id), 1); } #[test] @@ -582,13 +551,10 @@ fn failed_intrinsic_finalization_does_not_expose_registered_objects() { let prototype = crate::util::constructor_prototype_object(scope, constructor.into()) .expect("Crypto prototype"); let global = context.global(scope); - assert!(crate::util::register_intrinsic_interface( + moli_v8_util::install_web_api_intrinsic_resolver( scope, - global, - "Crypto", - constructor.into(), - prototype, - )); + super::materialize::resolve_web_api_intrinsic, + ); let subtle_crypto_available = v8::Boolean::new(scope, true); crate::util::set_private_value( scope, @@ -604,10 +570,10 @@ fn failed_intrinsic_finalization_does_not_expose_registered_objects() { super::ensure_intrinsic_interface_prototype(scope, "Crypto") .expect_err("a frozen prototype must reject secure-context finalization"); - assert_eq!( - realm.state(id), - Some(super::realm_registry::RealmInterfaceState::Failed) - ); + assert!(matches!( + &*realm.entry(id).unwrap(), + super::realm_registry::RealmInterfaceEntry::Failed + )); assert!(realm.constructor(scope, id).is_none()); assert!(realm.prototype(scope, id).is_none()); assert!(realm.public_interface(scope, id).is_none()); @@ -615,14 +581,10 @@ fn failed_intrinsic_finalization_does_not_expose_registered_objects() { assert!(super::ensure_intrinsic_interface_prototype(scope, "Crypto").is_err()); assert!(super::materialized_intrinsic_interface_prototype(scope, "Crypto").is_none()); assert_eq!(registry.materialization_count(id), 0); - assert!( - crate::util::registered_intrinsic_constructor(scope, global, "Crypto") - .is_some_and(|value| value.strict_equals(constructor.into())) - ); - assert!( - crate::util::registered_intrinsic_prototype(scope, global, "Crypto") - .is_some_and(|value| value.strict_equals(prototype.into())) - ); + assert!(crate::util::global_constructor_object(scope, "Crypto").is_none()); + assert!(crate::util::global_constructor_prototype(scope, "Crypto").is_none()); + assert!(crate::util::registered_ecmascript_constructor(scope, global, "Crypto").is_none()); + assert!(crate::util::registered_ecmascript_prototype(scope, global, "Crypto").is_none()); } #[test] diff --git a/moli-renderer-v8/src/context_bootstrap/exposed_interfaces/tests/finalization.rs b/moli-renderer-v8/src/context_bootstrap/exposed_interfaces/tests/finalization.rs index 72c1ee56af..3478ac7c73 100644 --- a/moli-renderer-v8/src/context_bootstrap/exposed_interfaces/tests/finalization.rs +++ b/moli-renderer-v8/src/context_bootstrap/exposed_interfaces/tests/finalization.rs @@ -6,15 +6,15 @@ use super::super::materialize::{ materialize_interface, }; use super::super::metadata::{InterfaceId, RealmKind, TemplateBuildProfile}; -use super::super::realm_registry::{IntrinsicInterfaceRegistry, RealmInterfaceState}; +use super::super::realm_registry::{IntrinsicInterfaceRegistry, RealmInterfaceEntry}; use super::super::template_registry::ExposedInterfaceTemplateRegistry; use super::{counting_lazy_getter, lazy_getter_calls, reset_lazy_getter_calls}; use crate::util::{ - constructor_prototype_object, get_private_value, register_intrinsic_interface, - registered_intrinsic_constructor, registered_intrinsic_prototype, v8str, + constructor_prototype_object, get_private_value, registered_ecmascript_constructor, + registered_ecmascript_prototype, v8str, }; -struct RegisteredInterface<'s> { +struct UnpublishedInterface<'s> { registry: Rc, realm: Rc, id: InterfaceId, @@ -23,12 +23,15 @@ struct RegisteredInterface<'s> { prototype: v8::Local<'s, v8::Object>, } -impl RegisteredInterface<'_> { +impl UnpublishedInterface<'_> { fn assert_failed(&self, scope: &mut v8::PinScope<'_, '_>, cause: &str) { let error = materialize_interface(scope, self.id) - .expect_err("failed recovery must return an error without panicking"); + .expect_err("failed materialization must return an error without panicking"); assert!(format!("{error:#}").contains(cause), "{error:#}"); - assert_eq!(self.realm.state(self.id), Some(RealmInterfaceState::Failed)); + assert!(matches!( + &*self.realm.entry(self.id).unwrap(), + RealmInterfaceEntry::Failed + )); assert!(self.realm.constructor(scope, self.id).is_none()); assert!(self.realm.prototype(scope, self.id).is_none()); assert!(self.realm.public_interface(scope, self.id).is_none()); @@ -37,22 +40,29 @@ impl RegisteredInterface<'_> { assert_eq!(self.registry.materialization_count(self.id), 0); assert_eq!(lazy_getter_calls(), 0); - // Immutable private entries survive, but Failed prevents their reuse. + assert!(crate::util::global_constructor_object(scope, self.name).is_none()); + assert!(crate::util::global_constructor_prototype(scope, self.name).is_none()); + // No intrinsic identity survives in a second registry or public lookup. let global = scope.get_current_context().global(scope); - assert!( - registered_intrinsic_constructor(scope, global, self.name) - .is_some_and(|value| value.strict_equals(self.constructor.into())) - ); - assert!( - registered_intrinsic_prototype(scope, global, self.name) - .is_some_and(|value| value.strict_equals(self.prototype.into())) + assert!(registered_ecmascript_constructor(scope, global, self.name).is_none()); + assert!(registered_ecmascript_prototype(scope, global, self.name).is_none()); + assert_eq!( + lazy_getter_calls(), + 0, + "failed lookups must not read public bindings" ); + super::super::install::capture_eager_intrinsic_interfaces(scope, global, RealmKind::Window) + .expect_err("eager capture cannot revive a failed interface"); + assert!(matches!( + &*self.realm.entry(self.id).unwrap(), + RealmInterfaceEntry::Failed + )); } } -fn with_registered_interface( +fn with_unpublished_interface( name: &'static str, - test: impl for<'s, 'i> FnOnce(&mut v8::PinScope<'s, 'i>, RegisteredInterface<'s>), + test: impl for<'s, 'i> FnOnce(&mut v8::PinScope<'s, 'i>, UnpublishedInterface<'s>), ) { crate::ensure_v8_for_test(); let mut isolate = v8::Isolate::new(Default::default()); @@ -81,13 +91,10 @@ fn with_registered_interface( let prototype = constructor_prototype_object(scope, constructor.into()).expect("interface prototype"); let global = context.global(scope); - assert!(register_intrinsic_interface( + moli_v8_util::install_web_api_intrinsic_resolver( scope, - global, - name, - constructor.into(), - prototype, - )); + super::super::materialize::resolve_web_api_intrinsic, + ); assert_eq!( global.set_lazy_data_property(scope, v8str(scope, name).into(), counting_lazy_getter), Some(true) @@ -95,7 +102,7 @@ fn with_registered_interface( reset_lazy_getter_calls(); test( scope, - RegisteredInterface { + UnpublishedInterface { registry, realm, id, @@ -108,7 +115,7 @@ fn with_registered_interface( #[test] fn failed_unscopables_finalization_is_terminal() { - with_registered_interface("Document", |scope, interface| { + with_unpublished_interface("Document", |scope, interface| { let unscopables = interface .prototype .get(scope, v8::Symbol::get_unscopables(scope).into()) @@ -125,7 +132,7 @@ fn failed_unscopables_finalization_is_terminal() { #[test] fn missing_unscopables_finalization_is_terminal() { - with_registered_interface("DocumentFragment", |scope, interface| { + with_unpublished_interface("DocumentFragment", |scope, interface| { assert_eq!( interface .prototype @@ -138,7 +145,15 @@ fn missing_unscopables_finalization_is_terminal() { #[test] fn failed_notification_finalization_is_terminal() { - with_registered_interface("Notification", |scope, interface| { + with_unpublished_interface("Notification", |scope, interface| { + // Freeze after constructor inheritance is linked so the failure still + // exercises the permission finalizer, rather than an earlier step. + let parent = ensure_intrinsic_interface_constructor(scope, "EventTarget") + .expect("Notification parent constructor"); + assert_eq!( + interface.constructor.set_prototype(scope, parent.into()), + Some(true) + ); assert_eq!( interface .constructor @@ -151,7 +166,7 @@ fn failed_notification_finalization_is_terminal() { #[test] fn failed_performance_observer_finalization_is_terminal() { - with_registered_interface("PerformanceObserver", |scope, interface| { + with_unpublished_interface("PerformanceObserver", |scope, interface| { assert_eq!( interface .constructor @@ -164,7 +179,7 @@ fn failed_performance_observer_finalization_is_terminal() { #[test] fn failed_html_constructor_link_is_terminal() { - with_registered_interface("HTMLDivElement", |scope, interface| { + with_unpublished_interface("HTMLDivElement", |scope, interface| { assert_eq!( interface .prototype @@ -176,54 +191,78 @@ fn failed_html_constructor_link_is_terminal() { } #[test] -fn published_intrinsics_stay_hidden_until_the_caller_exposes_them() { - with_registered_interface("DOMStringMap", |scope, interface| { +fn shared_binding_helpers_preserve_intrinsics_after_author_overrides() { + with_unpublished_interface("DOMStringMap", |scope, interface| { + let public = materialize_interface(scope, interface.id).expect("completed materialization"); let global = scope.get_current_context().global(scope); - interface - .realm - .register_intrinsic_objects( - scope, - global, - interface.id, - interface.name, - interface.constructor.into(), - interface.prototype, - interface.constructor.into(), - ) - .expect("intrinsic publication"); + let replacement = v8::Object::new(scope); + let replacement_prototype = v8::Object::new(scope); assert_eq!( - interface.realm.state(interface.id), - Some(RealmInterfaceState::Materializing) + replacement.set( + scope, + v8str(scope, "prototype").into(), + replacement_prototype.into(), + ), + Some(true) ); - assert!(interface.realm.constructor(scope, interface.id).is_none()); - assert!(interface.realm.prototype(scope, interface.id).is_none()); - assert!( - interface - .realm - .public_interface(scope, interface.id) - .is_none() + assert_eq!( + global.set( + scope, + v8str(scope, interface.name).into(), + replacement.into() + ), + Some(true) ); - interface - .realm - .set_state(interface.id, RealmInterfaceState::Finalizing) - .expect("caller starts finalization"); - assert!(interface.realm.constructor(scope, interface.id).is_some()); - assert!(interface.realm.prototype(scope, interface.id).is_some()); + assert!( - interface - .realm - .public_interface(scope, interface.id) - .is_some() + crate::util::global_constructor_object(scope, interface.name) + .unwrap() + .strict_equals(interface.constructor.into()) + ); + assert!( + crate::util::global_constructor_prototype(scope, interface.name) + .unwrap() + .strict_equals(interface.prototype.into()) + ); + let instance = v8::Object::new(scope); + moli_webapi_declare::set_required_interface_prototype(scope, instance, interface.name) + .expect("shared declarations must use the realm intrinsic"); + assert!( + instance + .get_prototype(scope) + .unwrap() + .strict_equals(interface.prototype.into()) + ); + assert!( + materialize_interface(scope, interface.id) + .unwrap() + .strict_equals(public) + ); + assert_eq!(interface.registry.materialization_count(interface.id), 1); + assert_eq!( + global.delete(scope, v8str(scope, interface.name).into()), + Some(true) + ); + scope.get_current_context().detach_global(); + assert!( + crate::util::global_constructor_object(scope, interface.name) + .unwrap() + .strict_equals(interface.constructor.into()) + ); + assert!( + crate::util::global_constructor_prototype(scope, interface.name) + .unwrap() + .strict_equals(interface.prototype.into()) ); }); } #[test] -fn recovered_xhr_event_target_initializes_its_private_state() { - with_registered_interface("XMLHttpRequestEventTarget", |scope, interface| { +fn materialized_xhr_event_target_initializes_its_private_state() { + with_unpublished_interface("XMLHttpRequestEventTarget", |scope, interface| { assert!( ensure_intrinsic_interface_prototype(scope, interface.name) - .expect("event target recovery") + .expect("event target materialization") .strict_equals(interface.prototype.into()) ); let listener_slot = get_private_value( @@ -246,27 +285,27 @@ fn recovered_xhr_event_target_initializes_its_private_state() { .is_true() ); } - assert_eq!( - interface.realm.state(interface.id), - Some(RealmInterfaceState::Ready) - ); + assert!(matches!( + &*interface.realm.entry(interface.id).unwrap(), + RealmInterfaceEntry::Ready(_) + )); assert_eq!(lazy_getter_calls(), 0); }); } #[test] -fn uninitialized_crypto_adopts_registered_intrinsics_and_finalizes_secure_surface() { - assert_registered_crypto_recovery(false); +fn crypto_finalizes_its_secure_surface_before_publication() { + assert_crypto_materialization(false); } #[test] -fn uninitialized_crypto_adopts_registered_intrinsics_and_refinalizes_secure_surface() { - assert_registered_crypto_recovery(true); +fn crypto_finalization_preserves_existing_secure_surface() { + assert_crypto_materialization(true); } -fn assert_registered_crypto_recovery(already_finalized: bool) { - with_registered_interface("Crypto", |scope, interface| { - let RegisteredInterface { +fn assert_crypto_materialization(already_finalized: bool) { + with_unpublished_interface("Crypto", |scope, interface| { + let UnpublishedInterface { registry, realm, id, @@ -292,15 +331,21 @@ fn assert_registered_crypto_recovery(already_finalized: bool) { let get_random_values = prototype .get(scope, v8str(scope, "getRandomValues").into()) .expect("template-defined getRandomValues method"); - assert_eq!(realm.state(id), Some(RealmInterfaceState::Uninitialized)); + assert!(matches!( + &*realm.entry(id).unwrap(), + RealmInterfaceEntry::Uninitialized + )); - let recovered = ensure_intrinsic_interface_prototype(scope, "Crypto") - .expect("adoption must allow secure-context finalization to run again"); - assert!(recovered.strict_equals(prototype.into())); - assert_eq!(realm.state(id), Some(RealmInterfaceState::Ready)); + let intrinsic = ensure_intrinsic_interface_prototype(scope, "Crypto") + .expect("materialization must finalize the secure surface"); + assert!(intrinsic.strict_equals(prototype.into())); + assert!(matches!( + &*realm.entry(id).unwrap(), + RealmInterfaceEntry::Ready(_) + )); assert!( ensure_intrinsic_interface_constructor(scope, "Crypto") - .expect("adopted Crypto constructor") + .expect("trusted Crypto constructor") .strict_equals(constructor.into()) ); assert!( diff --git a/moli-renderer-v8/src/network_host/fetch/bindings.rs b/moli-renderer-v8/src/network_host/fetch/bindings.rs index 09ef518492..1a4de609f7 100644 --- a/moli-renderer-v8/src/network_host/fetch/bindings.rs +++ b/moli-renderer-v8/src/network_host/fetch/bindings.rs @@ -77,7 +77,7 @@ fn error_name_message_snapshot<'s>( value: v8::Local<'s, v8::Value>, ) -> Option> { let global = scope.get_current_context().global(scope); - let error_constructor = crate::util::registered_intrinsic_constructor(scope, global, "Error")?; + let error_constructor = crate::util::registered_ecmascript_constructor(scope, global, "Error")?; if !value.instance_of(scope, error_constructor).unwrap_or(false) { return None; } diff --git a/moli-renderer-v8/src/util.rs b/moli-renderer-v8/src/util.rs index 3b9cdb8b3a..8dd09648a6 100644 --- a/moli-renderer-v8/src/util.rs +++ b/moli-renderer-v8/src/util.rs @@ -7,12 +7,12 @@ pub use moli_v8_util::{ define_non_enumerable_static_property, define_non_enumerable_static_string_property, get_own_static_property, get_private_object, get_private_value, get_property, global_constructor_object, global_constructor_prototype, - initialize_intrinsic_interface_registry, new_null_prototype_object, object_bool_property, + initialize_ecmascript_intrinsic_registry, new_null_prototype_object, object_bool_property, object_chain_contains, object_defined_string_property, object_non_empty_string_property, object_number_property, object_own_static_bool_property, object_own_static_property_as_array, object_own_static_string_property, object_property_as_array, object_property_as_object, - object_string_property, private_key, register_intrinsic_interface, - registered_intrinsic_constructor, registered_intrinsic_prototype, set_null_prototype, + object_string_property, private_key, register_ecmascript_intrinsic, + registered_ecmascript_constructor, registered_ecmascript_prototype, set_null_prototype, set_private_value, throw_range_error, throw_type_error, v8_json_parse, v8_string, v8str, walk_object_chain, }; diff --git a/moli-renderer-v8/src/worker/global_scope/mod.rs b/moli-renderer-v8/src/worker/global_scope/mod.rs index 2050ece39a..ae1ee0abc6 100644 --- a/moli-renderer-v8/src/worker/global_scope/mod.rs +++ b/moli-renderer-v8/src/worker/global_scope/mod.rs @@ -242,8 +242,8 @@ pub(super) use service_worker_results::{ use state::worker_close_callback; pub(crate) use state::{ WORKER_STATE_SLOT, WorkerGlobalState, get_worker_state, worker_current_script_url, - worker_exception_report_target, worker_global_is_closed, worker_service_worker_control_state, - worker_storage_key, worker_storage_partition_identity, worker_termination_requested, - worker_uses_shared_worker_agent_cluster, + worker_exception_report_target, worker_global_is_closed, worker_realm_secure_context_available, + worker_service_worker_control_state, worker_storage_key, worker_storage_partition_identity, + worker_termination_requested, worker_uses_shared_worker_agent_cluster, }; pub(super) use timers::{TimerInfo, WorkerIsolateTimerQueues, worker_isolate_timer_queues}; diff --git a/moli-renderer-v8/src/worker/global_scope/state.rs b/moli-renderer-v8/src/worker/global_scope/state.rs index ff8ed68817..4959854000 100644 --- a/moli-renderer-v8/src/worker/global_scope/state.rs +++ b/moli-renderer-v8/src/worker/global_scope/state.rs @@ -245,6 +245,11 @@ pub(crate) fn worker_service_worker_control_state( runtime.matching_controller_for_client(client_id) } +/// Returns the native secure-context policy, independently of public bindings. +pub(crate) fn worker_realm_secure_context_available(scope: &mut v8::PinScope<'_, '_>) -> bool { + get_worker_state(scope).is_some_and(|state| state.borrow().secure_context) +} + /// Retrieve the `WorkerGlobalState` from a callback scope. pub(crate) fn get_worker_state<'s>( scope: &mut v8::PinScope<'s, '_>, diff --git a/moli-renderer-v8/src/worker/mod.rs b/moli-renderer-v8/src/worker/mod.rs index c25a5d4385..45986d4ac8 100644 --- a/moli-renderer-v8/src/worker/mod.rs +++ b/moli-renderer-v8/src/worker/mod.rs @@ -42,9 +42,9 @@ pub(crate) use global_scope::{ worker_global_origin, worker_message_port_registry, worker_message_port_wake_sender, worker_message_port_wrapper, worker_notification_permission_state, worker_opfs_directory_iterator_registry, worker_opfs_handle_registry, - worker_requires_trusted_types_for_script, worker_service_worker_control_state, - worker_storage_key, worker_storage_partition_identity, worker_termination_requested, - worker_uses_shared_worker_agent_cluster, + worker_realm_secure_context_available, worker_requires_trusted_types_for_script, + worker_service_worker_control_state, worker_storage_key, worker_storage_partition_identity, + worker_termination_requested, worker_uses_shared_worker_agent_cluster, }; pub(crate) use handle::WorkerMessage; pub(crate) use handle::{ diff --git a/moli-renderer-v8/src/worker/thread/tests/postmessage.rs b/moli-renderer-v8/src/worker/thread/tests/postmessage.rs index 8730853878..7e24a45a58 100644 --- a/moli-renderer-v8/src/worker/thread/tests/postmessage.rs +++ b/moli-renderer-v8/src/worker/thread/tests/postmessage.rs @@ -785,6 +785,48 @@ async fn worker_postmessage_cryptokey_to_nonsecure_worker_fires_messageerror() { ); } +#[tokio::test] +async fn worker_postmessage_cryptokey_exposure_ignores_public_constructor_overrides() { + ensure_v8(); + for (url, setup, expected) in [ + ( + "https://worker-crypto.test/receive-cryptokey-override.js", + "Object.defineProperty(globalThis, 'CryptoKey', {get() {throw new Error('public constructor read');}, configurable: true});", + r#"["message","[object CryptoKey]"]"#, + ), + ( + "http://worker-crypto.test/receive-cryptokey-override.js", + "globalThis.CryptoKey = function FakeCryptoKey() {};", + r#"["messageerror",true]"#, + ), + ] { + let callbacks = r#" + onmessage = event => { + postMessage([event.type, Object.prototype.toString.call(event.data.key)]); + }; + onmessageerror = event => { + postMessage([event.type, event.data === null]); + }; + "#; + let mut handle = spawn_worker(format!("{setup}\n{callbacks}"), url.into()); + let payload = serialize_test_crypto_value( + r#" + (async () => ({key: await crypto.subtle.importKey( + "raw", new Uint8Array([1, 2, 3, 4]), + {name: "HMAC", hash: "SHA-256"}, true, ["sign"] + )}))() + "#, + ); + handle.post_message(payload); + + let msg = timeout(TIMEOUT, handle.recv()) + .await + .expect("timed out") + .expect("channel closed"); + assert_eq!(expect_post_json(msg), expected, "receiver {url}"); + } +} + #[tokio::test] async fn worker_crypto_subtle_kdf_and_x25519_derivation_match_wpt_any_tests() { ensure_v8(); diff --git a/moli-v8-util/src/constructors.rs b/moli-v8-util/src/constructors.rs index 7184d86f29..28cd24e074 100644 --- a/moli-v8-util/src/constructors.rs +++ b/moli-v8-util/src/constructors.rs @@ -1,11 +1,43 @@ +use std::rc::Rc; + use v8::{Local, Object, PinScope, PropertyAttribute}; use crate::properties::new_null_prototype_object; use crate::strings::{v8_string, v8str}; use crate::symbols::{get_private_object, set_private_value}; -const INTRINSIC_CONSTRUCTORS_SLOT: &str = "__moliIntrinsicConstructors"; -const INTRINSIC_PROTOTYPES_SLOT: &str = "__moliIntrinsicPrototypes"; +const ECMASCRIPT_CONSTRUCTORS_SLOT: &str = "__moliEcmascriptConstructors"; +const ECMASCRIPT_PROTOTYPES_SLOT: &str = "__moliEcmascriptPrototypes"; + +#[derive(Clone, Copy)] +pub enum WebApiIntrinsicKind { + Constructor, + Prototype, +} + +pub enum WebApiIntrinsicLookup<'s> { + /// This name is outside the embedder's Web API registry. + Unmanaged, + /// A managed name must never fall back to an author-controlled global, + /// including when materialization failed or the interface is unavailable. + Managed(Option>), +} + +pub type WebApiIntrinsicResolver = + for<'s, 'i> fn(&mut PinScope<'s, 'i>, &str, WebApiIntrinsicKind) -> WebApiIntrinsicLookup<'s>; + +struct RealmWebApiIntrinsicResolver(WebApiIntrinsicResolver); + +/// Connects shared binding helpers to the embedder's canonical realm cache. +/// Install after eager bootstrap; the resolver stores no intrinsic identities. +pub fn install_web_api_intrinsic_resolver( + scope: &mut PinScope<'_, '_>, + resolver: WebApiIntrinsicResolver, +) { + scope + .get_current_context() + .set_slot(Rc::new(RealmWebApiIntrinsicResolver(resolver))); +} fn intrinsic_registry_object<'s>( scope: &mut PinScope<'s, '_>, @@ -21,18 +53,18 @@ fn intrinsic_registry_object<'s>( registry } -/// Ensures that a realm global owns its native-only intrinsic interface maps. +/// Ensures that a realm global owns its native-only ECMAScript intrinsic maps. /// /// The maps are stored under V8 private symbols, so author code cannot observe /// or replace them through JavaScript reflection. They intentionally contain /// realm-local V8 objects rather than Rust `Global` handles, allowing V8 to /// reclaim the whole realm graph when its context becomes unreachable. -pub fn initialize_intrinsic_interface_registry<'s>( +pub fn initialize_ecmascript_intrinsic_registry<'s>( scope: &mut PinScope<'s, '_>, global: Local<'s, Object>, ) { - let _ = intrinsic_registry_object(scope, global, INTRINSIC_CONSTRUCTORS_SLOT); - let _ = intrinsic_registry_object(scope, global, INTRINSIC_PROTOTYPES_SLOT); + let _ = intrinsic_registry_object(scope, global, ECMASCRIPT_CONSTRUCTORS_SLOT); + let _ = intrinsic_registry_object(scope, global, ECMASCRIPT_PROTOTYPES_SLOT); } fn define_intrinsic<'s>( @@ -56,21 +88,22 @@ fn define_intrinsic<'s>( .unwrap_or(false) } -/// Records the trusted constructor and prototype for one realm-local Web API. +/// Records a trusted ECMAScript constructor and prototype. Web API identities +/// belong to the embedder's realm cache, accessed through its resolver. /// /// Registrations are immutable. Returning `false` means allocation failed or /// an entry with the same name was already finalized; callers should treat /// that as a bootstrap/materialization error rather than silently replacing an /// intrinsic identity. -pub fn register_intrinsic_interface<'s>( +pub fn register_ecmascript_intrinsic<'s>( scope: &mut PinScope<'s, '_>, global: Local<'s, Object>, name: &str, constructor: Local<'s, Object>, prototype: Local<'s, Object>, ) -> bool { - let constructors = intrinsic_registry_object(scope, global, INTRINSIC_CONSTRUCTORS_SLOT); - let prototypes = intrinsic_registry_object(scope, global, INTRINSIC_PROTOTYPES_SLOT); + let constructors = intrinsic_registry_object(scope, global, ECMASCRIPT_CONSTRUCTORS_SLOT); + let prototypes = intrinsic_registry_object(scope, global, ECMASCRIPT_PROTOTYPES_SLOT); let Some(key) = v8_string(scope, name) else { return false; @@ -107,20 +140,20 @@ fn registered_intrinsic<'s>( .and_then(|value| Local::::try_from(value).ok()) } -pub fn registered_intrinsic_constructor<'s>( +pub fn registered_ecmascript_constructor<'s>( scope: &mut PinScope<'s, '_>, global: Local<'s, Object>, name: &str, ) -> Option> { - registered_intrinsic(scope, global, INTRINSIC_CONSTRUCTORS_SLOT, name) + registered_intrinsic(scope, global, ECMASCRIPT_CONSTRUCTORS_SLOT, name) } -pub fn registered_intrinsic_prototype<'s>( +pub fn registered_ecmascript_prototype<'s>( scope: &mut PinScope<'s, '_>, global: Local<'s, Object>, name: &str, ) -> Option> { - registered_intrinsic(scope, global, INTRINSIC_PROTOTYPES_SLOT, name) + registered_intrinsic(scope, global, ECMASCRIPT_PROTOTYPES_SLOT, name) } pub fn constructor_object<'s>( @@ -157,8 +190,16 @@ pub fn global_constructor_object<'s>( scope: &mut PinScope<'s, '_>, name: &str, ) -> Option> { + if let Some(resolver) = scope + .get_current_context() + .get_slot::() + && let WebApiIntrinsicLookup::Managed(constructor) = + (resolver.0)(scope, name, WebApiIntrinsicKind::Constructor) + { + return constructor; + } let global = scope.get_current_context().global(scope); - registered_intrinsic_constructor(scope, global, name) + registered_ecmascript_constructor(scope, global, name) .or_else(|| constructor_object(scope, global, name)) } @@ -166,8 +207,16 @@ pub fn global_constructor_prototype<'s>( scope: &mut PinScope<'s, '_>, name: &str, ) -> Option> { + if let Some(resolver) = scope + .get_current_context() + .get_slot::() + && let WebApiIntrinsicLookup::Managed(prototype) = + (resolver.0)(scope, name, WebApiIntrinsicKind::Prototype) + { + return prototype; + } let global = scope.get_current_context().global(scope); - registered_intrinsic_prototype(scope, global, name) + registered_ecmascript_prototype(scope, global, name) .or_else(|| constructor_prototype(scope, global, name)) } @@ -178,10 +227,10 @@ mod tests { use moli_v8_test_util::ensure_v8; use super::{ - INTRINSIC_CONSTRUCTORS_SLOT, INTRINSIC_PROTOTYPES_SLOT, global_constructor_object, - global_constructor_prototype, initialize_intrinsic_interface_registry, - register_intrinsic_interface, registered_intrinsic_constructor, - registered_intrinsic_prototype, + ECMASCRIPT_CONSTRUCTORS_SLOT, ECMASCRIPT_PROTOTYPES_SLOT, global_constructor_object, + global_constructor_prototype, initialize_ecmascript_intrinsic_registry, + register_ecmascript_intrinsic, registered_ecmascript_constructor, + registered_ecmascript_prototype, }; use crate::strings::v8str; @@ -195,10 +244,10 @@ mod tests { let scope = &mut v8::ContextScope::new(scope, context); let global = context.global(scope); - initialize_intrinsic_interface_registry(scope, global); + initialize_ecmascript_intrinsic_registry(scope, global); let intrinsic_constructor = v8::Object::new(scope); let intrinsic_prototype = v8::Object::new(scope); - assert!(register_intrinsic_interface( + assert!(register_ecmascript_intrinsic( scope, global, "Sample", @@ -226,11 +275,11 @@ mod tests { ); assert!( - registered_intrinsic_constructor(scope, global, "Sample") + registered_ecmascript_constructor(scope, global, "Sample") .is_some_and(|value| value.strict_equals(intrinsic_constructor.into())) ); assert!( - registered_intrinsic_prototype(scope, global, "Sample") + registered_ecmascript_prototype(scope, global, "Sample") .is_some_and(|value| value.strict_equals(intrinsic_prototype.into())) ); assert!( @@ -253,7 +302,7 @@ mod tests { let scope = &mut v8::ContextScope::new(scope, context); let global = context.global(scope); - initialize_intrinsic_interface_registry(scope, global); + initialize_ecmascript_intrinsic_registry(scope, global); let constructor = v8::Object::new(scope); let prototype = v8::Object::new(scope); assert_eq!( @@ -287,7 +336,7 @@ mod tests { let constructor = v8::Object::new(scope); let prototype = v8::Object::new(scope); - assert!(register_intrinsic_interface( + assert!(register_ecmascript_intrinsic( scope, global, "Sample", @@ -296,7 +345,7 @@ mod tests { )); let replacement_constructor = v8::Object::new(scope); let replacement_prototype = v8::Object::new(scope); - assert!(!register_intrinsic_interface( + assert!(!register_ecmascript_intrinsic( scope, global, "Sample", @@ -312,8 +361,8 @@ mod tests { .get_index(scope, index) .and_then(|value| value.to_string(scope)) .map(|value| value.to_rust_string_lossy(scope)); - assert_ne!(name.as_deref(), Some(INTRINSIC_CONSTRUCTORS_SLOT)); - assert_ne!(name.as_deref(), Some(INTRINSIC_PROTOTYPES_SLOT)); + assert_ne!(name.as_deref(), Some(ECMASCRIPT_CONSTRUCTORS_SLOT)); + assert_ne!(name.as_deref(), Some(ECMASCRIPT_PROTOTYPES_SLOT)); } } }