diff options
28 files changed, 836 insertions, 691 deletions
diff --git a/components/script/dom/bindings/callback.rs b/components/script/dom/bindings/callback.rs index b5e32dc97a5..7a28451763f 100644 --- a/components/script/dom/bindings/callback.rs +++ b/components/script/dom/bindings/callback.rs @@ -15,6 +15,7 @@ use js::jsapi::{ use js::jsval::{JSVal, ObjectValue, UndefinedValue}; use js::rust::wrappers::{JS_GetProperty, JS_WrapObject}; use js::rust::{MutableHandleValue, Runtime}; +use script_bindings::interfaces::DocumentHelpers; use crate::DomTypes; use crate::dom::bindings::codegen::Bindings::WindowBinding::Window_Binding::WindowMethods; @@ -23,7 +24,6 @@ use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::root::{Dom, DomRoot}; use crate::dom::bindings::settings_stack::{GenericAutoEntryScript, GenericAutoIncumbentScript}; use crate::dom::bindings::utils::AsCCharPtrPtr; -use crate::dom::document::DocumentHelpers; use crate::dom::globalscope::GlobalScopeHelpers; use crate::realms::{InRealm, enter_realm}; use crate::script_runtime::{CanGc, JSContext}; diff --git a/components/script/dom/bindings/conversions.rs b/components/script/dom/bindings/conversions.rs index 0df5a82c3b0..0ea59e6ee83 100644 --- a/components/script/dom/bindings/conversions.rs +++ b/components/script/dom/bindings/conversions.rs @@ -37,60 +37,17 @@ use std::ffi; pub(crate) use js::conversions::{ ConversionBehavior, ConversionResult, FromJSValConvertible, ToJSValConvertible, }; -use js::error::throw_type_error; use js::glue::GetProxyReservedSlot; use js::jsapi::{Heap, IsWindowProxy, JS_IsExceptionPending, JSContext, JSObject}; use js::jsval::UndefinedValue; use js::rust::wrappers::{IsArrayObject, JS_GetProperty, JS_HasProperty}; use js::rust::{HandleObject, HandleValue, MutableHandleValue}; -use num_traits::Float; pub(crate) use script_bindings::conversions::*; use crate::dom::bindings::error::{Error, Fallible}; -use crate::dom::bindings::num::Finite; use crate::dom::bindings::reflector::DomObject; use crate::dom::bindings::root::DomRoot; use crate::dom::bindings::trace::{JSTraceable, RootedTraceableBox}; -use crate::dom::bindings::utils::DOMClass; -use crate::dom::filelist::FileList; -use crate::dom::htmlcollection::HTMLCollection; -use crate::dom::htmlformcontrolscollection::HTMLFormControlsCollection; -use crate::dom::htmloptionscollection::HTMLOptionsCollection; -use crate::dom::nodelist::NodeList; - -impl<T: Float + ToJSValConvertible> ToJSValConvertible for Finite<T> { - #[inline] - unsafe fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) { - let value = **self; - value.to_jsval(cx, rval); - } -} - -impl<T: Float + FromJSValConvertible<Config = ()>> FromJSValConvertible for Finite<T> { - type Config = (); - - unsafe fn from_jsval( - cx: *mut JSContext, - value: HandleValue, - option: (), - ) -> Result<ConversionResult<Finite<T>>, ()> { - let result = match FromJSValConvertible::from_jsval(cx, value, option)? { - ConversionResult::Success(v) => v, - ConversionResult::Failure(error) => { - // FIXME(emilio): Why throwing instead of propagating the error? - throw_type_error(cx, &error); - return Err(()); - }, - }; - match Finite::new(result) { - Some(v) => Ok(ConversionResult::Success(v)), - None => { - throw_type_error(cx, "this argument is not a finite floating-point value"); - Err(()) - }, - } - } -} impl<T: ToJSValConvertible + JSTraceable> ToJSValConvertible for RootedTraceableBox<T> { #[inline] @@ -123,35 +80,6 @@ where pub(crate) use script_bindings::conversions::is_dom_proxy; -/// Get a `*const libc::c_void` for the given DOM object, unless it is a DOM -/// wrapper, and checking if the object is of the correct type. -/// -/// Returns Err(()) if `obj` is a wrapper or if the object is not an object -/// for a DOM object of the given type (as defined by the proto_id and proto_depth). -#[inline] -unsafe fn private_from_proto_check_static( - obj: *mut JSObject, - proto_check: fn(&'static DOMClass) -> bool, -) -> Result<*const libc::c_void, ()> { - let dom_class = get_dom_class(obj).map_err(|_| ())?; - if proto_check(dom_class) { - trace!("good prototype"); - Ok(private_from_object(obj)) - } else { - trace!("bad prototype"); - Err(()) - } -} - -/// Get a `*const T` for a DOM object accessible from a `JSObject`, where the DOM object -/// is guaranteed not to be a wrapper. -pub(crate) fn native_from_object_static<T>(obj: *mut JSObject) -> Result<*const T, ()> -where - T: DomObject + IDLInterface, -{ - unsafe { private_from_proto_check_static(obj, T::derives).map(|ptr| ptr as *const T) } -} - /// Get a `DomRoot<T>` for the given DOM object, unwrapping any wrapper /// around it first, and checking if the object is of the correct type. /// @@ -162,19 +90,7 @@ pub(crate) fn root_from_object_static<T>(obj: *mut JSObject) -> Result<DomRoot<T where T: DomObject + IDLInterface, { - native_from_object_static(obj).map(|ptr| unsafe { DomRoot::from_ref(&*ptr) }) -} - -/// Get a `*const T` for a DOM object accessible from a `HandleValue`. -/// Caller is responsible for throwing a JS exception if needed in case of error. -pub(crate) fn native_from_handlevalue<T>(v: HandleValue, cx: *mut JSContext) -> Result<*const T, ()> -where - T: DomObject + IDLInterface, -{ - if !v.get().is_object() { - return Err(()); - } - unsafe { native_from_object(v.get().to_object(), cx) } + unsafe { native_from_object_static(obj).map(|ptr| DomRoot::from_ref(&*ptr)) } } /// Get a `DomRoot<T>` for a DOM object accessible from a `HandleObject`. @@ -191,7 +107,10 @@ where /// Returns whether `value` is an array-like object (Array, FileList, /// HTMLCollection, HTMLFormControlsCollection, HTMLOptionsCollection, /// NodeList). -pub(crate) unsafe fn is_array_like(cx: *mut JSContext, value: HandleValue) -> bool { +pub(crate) unsafe fn is_array_like<D: crate::DomTypes>( + cx: *mut JSContext, + value: HandleValue, +) -> bool { let mut is_array = false; assert!(IsArrayObject(cx, value, &mut is_array)); if is_array { @@ -203,19 +122,19 @@ pub(crate) unsafe fn is_array_like(cx: *mut JSContext, value: HandleValue) -> bo _ => return false, }; - if root_from_object::<FileList>(object, cx).is_ok() { + if root_from_object::<D::FileList>(object, cx).is_ok() { return true; } - if root_from_object::<HTMLCollection>(object, cx).is_ok() { + if root_from_object::<D::HTMLCollection>(object, cx).is_ok() { return true; } - if root_from_object::<HTMLFormControlsCollection>(object, cx).is_ok() { + if root_from_object::<D::HTMLFormControlsCollection>(object, cx).is_ok() { return true; } - if root_from_object::<HTMLOptionsCollection>(object, cx).is_ok() { + if root_from_object::<D::HTMLOptionsCollection>(object, cx).is_ok() { return true; } - if root_from_object::<NodeList>(object, cx).is_ok() { + if root_from_object::<D::NodeList>(object, cx).is_ok() { return true; } diff --git a/components/script/dom/bindings/error.rs b/components/script/dom/bindings/error.rs index 13bdd97cad1..b9d51e77fcf 100644 --- a/components/script/dom/bindings/error.rs +++ b/components/script/dom/bindings/error.rs @@ -20,7 +20,6 @@ pub(crate) use script_bindings::error::*; #[cfg(feature = "js_backtrace")] use crate::dom::bindings::cell::DomRefCell; -use crate::dom::bindings::codegen::PrototypeList::proto_id_to_name; use crate::dom::bindings::conversions::{ ConversionResult, FromJSValConvertible, ToJSValConvertible, root_from_object, }; @@ -257,23 +256,6 @@ pub(crate) fn report_pending_exception( } } -/// Throw an exception to signal that a `JSObject` can not be converted to a -/// given DOM type. -pub(crate) fn throw_invalid_this(cx: SafeJSContext, proto_id: u16) { - debug_assert!(unsafe { !JS_IsExceptionPending(*cx) }); - let error = format!( - "\"this\" object does not implement interface {}.", - proto_id_to_name(proto_id) - ); - unsafe { throw_type_error(*cx, &error) }; -} - -pub(crate) fn throw_constructor_without_new(cx: SafeJSContext, name: &str) { - debug_assert!(unsafe { !JS_IsExceptionPending(*cx) }); - let error = format!("{} constructor: 'new' is required", name); - unsafe { throw_type_error(*cx, &error) }; -} - pub(crate) trait ErrorToJsval { fn to_jsval( self, diff --git a/components/script/dom/bindings/guard.rs b/components/script/dom/bindings/guard.rs index fb2b2ebc807..16b5187abef 100644 --- a/components/script/dom/bindings/guard.rs +++ b/components/script/dom/bindings/guard.rs @@ -5,11 +5,12 @@ //! Machinery to conditionally expose things. use js::rust::HandleObject; +use script_bindings::codegen::Globals::Globals; use servo_config::prefs::get; -use crate::dom::bindings::codegen::InterfaceObjectMap; +use crate::DomTypes; use crate::dom::bindings::interface::is_exposed_in; -use crate::dom::globalscope::GlobalScope; +use crate::dom::globalscope::GlobalScopeHelpers; use crate::realms::{AlreadyInRealm, InRealm}; use crate::script_runtime::JSContext; @@ -28,7 +29,7 @@ impl<T: Clone + Copy> Guard<T> { /// Expose the value if the conditions are satisfied. /// /// The passed handle is the object on which the value may be exposed. - pub(crate) fn expose( + pub(crate) fn expose<D: DomTypes>( &self, cx: JSContext, obj: HandleObject, @@ -45,7 +46,7 @@ impl<T: Clone + Copy> Guard<T> { exposed_on_global |= is_exposed_in(global, *globals); true }, - _ => c.is_satisfied(cx, obj, global), + _ => c.is_satisfied::<D>(cx, obj, global), }); if conditions_satisfied && exposed_on_global { @@ -64,21 +65,21 @@ pub(crate) enum Condition { /// The condition is satisfied if the preference is set. Pref(&'static str), // The condition is satisfied if the interface is exposed in the global. - Exposed(InterfaceObjectMap::Globals), + Exposed(Globals), SecureContext(), /// The condition is always satisfied. Satisfied, } -fn is_secure_context(cx: JSContext) -> bool { +fn is_secure_context<D: DomTypes>(cx: JSContext) -> bool { unsafe { let in_realm_proof = AlreadyInRealm::assert_for_cx(JSContext::from_ptr(*cx)); - GlobalScope::from_context(*cx, InRealm::Already(&in_realm_proof)).is_secure_context() + D::GlobalScope::from_context(*cx, InRealm::Already(&in_realm_proof)).is_secure_context() } } impl Condition { - pub(crate) fn is_satisfied( + pub(crate) fn is_satisfied<D: DomTypes>( &self, cx: JSContext, obj: HandleObject, @@ -88,7 +89,7 @@ impl Condition { Condition::Pref(name) => get().get_value(name).try_into().unwrap_or(false), Condition::Func(f) => f(cx, obj), Condition::Exposed(globals) => is_exposed_in(global, globals), - Condition::SecureContext() => is_secure_context(cx), + Condition::SecureContext() => is_secure_context::<D>(cx), Condition::Satisfied => true, } } diff --git a/components/script/dom/bindings/import.rs b/components/script/dom/bindings/import.rs index 33511666d17..fb893af33f4 100644 --- a/components/script/dom/bindings/import.rs +++ b/components/script/dom/bindings/import.rs @@ -16,6 +16,7 @@ pub(crate) mod base { pub(crate) use js::panic::maybe_resume_unwind; pub(crate) use js::rust::wrappers::{Call, JS_WrapValue}; pub(crate) use js::rust::{HandleObject, HandleValue, MutableHandleObject, MutableHandleValue}; + pub(crate) use script_bindings::lock::ThreadUnsafeOnceLock; pub(crate) use crate::dom::bindings::callback::{ CallSetup, CallbackContainer, CallbackFunction, CallbackInterface, CallbackObject, @@ -40,7 +41,7 @@ pub(crate) mod base { pub(crate) use crate::dom::bindings::str::{ByteString, DOMString, USVString}; pub(crate) use crate::dom::bindings::trace::RootedTraceableBox; pub(crate) use crate::dom::bindings::utils::{ - DomHelpers, ThreadUnsafeOnceLock, get_dictionary_property, set_dictionary_property, + DomHelpers, get_dictionary_property, set_dictionary_property, }; pub(crate) use crate::dom::globalscope::{GlobalScope, GlobalScopeHelpers}; pub(crate) use crate::dom::promise::PromiseHelpers; @@ -99,7 +100,12 @@ pub(crate) mod module { JS_CALLEE, JSCLASS_GLOBAL_SLOT_COUNT, JSCLASS_IS_DOMJSCLASS, JSCLASS_IS_GLOBAL, JSCLASS_RESERVED_SLOTS_MASK, jsapi, typedarray, }; + pub(crate) use script_bindings::codegen::Globals::Globals; pub(crate) use script_bindings::constant::{ConstantSpec, ConstantVal}; + pub(crate) use script_bindings::finalize::{ + finalize_common, finalize_global, finalize_weak_referenceable, + }; + pub(crate) use script_bindings::interfaces::*; pub(crate) use script_bindings::record::Record; pub(crate) use servo_config::pref; @@ -124,9 +130,6 @@ pub(crate) mod module { pub(crate) use crate::dom::bindings::error::{ Error, ErrorResult, throw_constructor_without_new, }; - pub(crate) use crate::dom::bindings::finalize::{ - finalize_common, finalize_global, finalize_weak_referenceable, - }; pub(crate) use crate::dom::bindings::guard::{Condition, Guard}; pub(crate) use crate::dom::bindings::inheritance::Castable; pub(crate) use crate::dom::bindings::interface::{ diff --git a/components/script/dom/bindings/interface.rs b/components/script/dom/bindings/interface.rs index 56c50ef2b72..835d2f7f0be 100644 --- a/components/script/dom/bindings/interface.rs +++ b/components/script/dom/bindings/interface.rs @@ -35,6 +35,7 @@ use js::rust::{ use script_bindings::constant::{ConstantSpec, define_constants}; use servo_url::MutableOrigin; +use crate::DomTypes; use crate::dom::bindings::codegen::InterfaceObjectMap::Globals; use crate::dom::bindings::codegen::PrototypeList; use crate::dom::bindings::conversions::{DOM_OBJECT_SLOT, get_dom_class}; @@ -43,8 +44,6 @@ use crate::dom::bindings::principals::ServoJSPrincipals; use crate::dom::bindings::utils::{ DOM_PROTOTYPE_SLOT, DOMJSClass, JSCLASS_DOM_GLOBAL, ProtoOrIfaceArray, get_proto_or_iface_array, }; -use crate::dom::globalscope::GlobalScope; -use crate::realms::{AlreadyInRealm, InRealm}; use crate::script_runtime::JSContext as SafeJSContext; /// The class of a non-callback interface object. @@ -213,7 +212,7 @@ fn select_compartment(cx: SafeJSContext, options: &mut RealmOptions) { } /// Create and define the interface object of a callback interface. -pub(crate) fn create_callback_interface_object( +pub(crate) fn create_callback_interface_object<D: DomTypes>( cx: SafeJSContext, global: HandleObject, constants: &[Guard<&[ConstantSpec]>], @@ -225,14 +224,14 @@ pub(crate) fn create_callback_interface_object( rval.set(JS_NewObject(*cx, ptr::null())); } assert!(!rval.is_null()); - define_guarded_constants(cx, rval.handle(), constants, global); + define_guarded_constants::<D>(cx, rval.handle(), constants, global); define_name(cx, rval.handle(), name); define_on_global_object(cx, global, name, rval.handle()); } /// Create the interface prototype object of a non-callback interface. #[allow(clippy::too_many_arguments)] -pub(crate) fn create_interface_prototype_object( +pub(crate) fn create_interface_prototype_object<D: DomTypes>( cx: SafeJSContext, global: HandleObject, proto: HandleObject, @@ -243,7 +242,7 @@ pub(crate) fn create_interface_prototype_object( unscopable_names: &[&CStr], mut rval: MutableHandleObject, ) { - create_object( + create_object::<D>( cx, global, proto, @@ -277,7 +276,7 @@ pub(crate) fn create_interface_prototype_object( /// Create and define the interface object of a non-callback interface. #[allow(clippy::too_many_arguments)] -pub(crate) fn create_noncallback_interface_object( +pub(crate) fn create_noncallback_interface_object<D: DomTypes>( cx: SafeJSContext, global: HandleObject, proto: HandleObject, @@ -291,7 +290,7 @@ pub(crate) fn create_noncallback_interface_object( legacy_window_alias_names: &[&CStr], mut rval: MutableHandleObject, ) { - create_object( + create_object::<D>( cx, global, proto, @@ -350,7 +349,7 @@ pub(crate) fn create_named_constructors( /// Create a new object with a unique type. #[allow(clippy::too_many_arguments)] -pub(crate) fn create_object( +pub(crate) fn create_object<D: DomTypes>( cx: SafeJSContext, global: HandleObject, proto: HandleObject, @@ -364,34 +363,34 @@ pub(crate) fn create_object( rval.set(JS_NewObjectWithGivenProto(*cx, class, proto)); } assert!(!rval.is_null()); - define_guarded_methods(cx, rval.handle(), methods, global); - define_guarded_properties(cx, rval.handle(), properties, global); - define_guarded_constants(cx, rval.handle(), constants, global); + define_guarded_methods::<D>(cx, rval.handle(), methods, global); + define_guarded_properties::<D>(cx, rval.handle(), properties, global); + define_guarded_constants::<D>(cx, rval.handle(), constants, global); } /// Conditionally define constants on an object. -pub(crate) fn define_guarded_constants( +pub(crate) fn define_guarded_constants<D: DomTypes>( cx: SafeJSContext, obj: HandleObject, constants: &[Guard<&[ConstantSpec]>], global: HandleObject, ) { for guard in constants { - if let Some(specs) = guard.expose(cx, obj, global) { + if let Some(specs) = guard.expose::<D>(cx, obj, global) { define_constants(cx, obj, specs); } } } /// Conditionally define methods on an object. -pub(crate) fn define_guarded_methods( +pub(crate) fn define_guarded_methods<D: DomTypes>( cx: SafeJSContext, obj: HandleObject, methods: &[Guard<&'static [JSFunctionSpec]>], global: HandleObject, ) { for guard in methods { - if let Some(specs) = guard.expose(cx, obj, global) { + if let Some(specs) = guard.expose::<D>(cx, obj, global) { unsafe { define_methods(*cx, obj, specs).unwrap(); } @@ -400,14 +399,14 @@ pub(crate) fn define_guarded_methods( } /// Conditionally define properties on an object. -pub(crate) fn define_guarded_properties( +pub(crate) fn define_guarded_properties<D: DomTypes>( cx: SafeJSContext, obj: HandleObject, properties: &[Guard<&'static [JSPropertySpec]>], global: HandleObject, ) { for guard in properties { - if let Some(specs) = guard.expose(cx, obj, global) { + if let Some(specs) = guard.expose::<D>(cx, obj, global) { unsafe { define_properties(*cx, obj, specs).unwrap(); } @@ -425,16 +424,6 @@ pub(crate) fn is_exposed_in(object: HandleObject, globals: Globals) -> bool { } } -/// The navigator.servo api is only exposed to about: pages except about:blank -pub(crate) fn is_servo_internal(cx: SafeJSContext, _object: HandleObject) -> bool { - unsafe { - let in_realm_proof = AlreadyInRealm::assert_for_cx(cx); - let global_scope = GlobalScope::from_context(*cx, InRealm::Already(&in_realm_proof)); - let url = global_scope.get_url(); - url.scheme() == "about" && url.as_str() != "about:blank" - } -} - /// Define a property with a given name on the global object. Should be called /// through the resolve hook. pub(crate) fn define_on_global_object( diff --git a/components/script/dom/bindings/mod.rs b/components/script/dom/bindings/mod.rs index 9385df640c8..833915c0b98 100644 --- a/components/script/dom/bindings/mod.rs +++ b/components/script/dom/bindings/mod.rs @@ -141,7 +141,6 @@ pub(crate) mod cell; pub(crate) mod constructor; pub(crate) mod conversions; pub(crate) mod error; -pub(crate) mod finalize; pub(crate) mod frozenarray; pub(crate) mod function; pub(crate) mod guard; @@ -151,7 +150,6 @@ pub(crate) mod interface; pub(crate) mod iterable; pub(crate) mod like; pub(crate) mod namespace; -pub(crate) mod num; pub(crate) mod principals; pub(crate) mod proxyhandler; pub(crate) mod refcounted; @@ -167,6 +165,8 @@ pub(crate) mod utils; pub(crate) mod weakref; pub(crate) mod xmlname; +pub(crate) use script_bindings::num; + /// Generated JS-Rust bindings. #[allow(missing_docs, non_snake_case)] pub(crate) mod codegen { diff --git a/components/script/dom/bindings/namespace.rs b/components/script/dom/bindings/namespace.rs index 46079a787c9..ad0a5801519 100644 --- a/components/script/dom/bindings/namespace.rs +++ b/components/script/dom/bindings/namespace.rs @@ -11,6 +11,7 @@ use js::jsapi::{JSClass, JSFunctionSpec}; use js::rust::{HandleObject, MutableHandleObject}; use script_bindings::constant::ConstantSpec; +use crate::DomTypes; use crate::dom::bindings::guard::Guard; use crate::dom::bindings::interface::{create_object, define_on_global_object}; use crate::script_runtime::JSContext; @@ -37,7 +38,7 @@ impl NamespaceObjectClass { /// Create a new namespace object. #[allow(clippy::too_many_arguments)] -pub(crate) fn create_namespace_object( +pub(crate) fn create_namespace_object<D: DomTypes>( cx: JSContext, global: HandleObject, proto: HandleObject, @@ -47,7 +48,7 @@ pub(crate) fn create_namespace_object( name: &CStr, mut rval: MutableHandleObject, ) { - create_object( + create_object::<D>( cx, global, proto, diff --git a/components/script/dom/bindings/utils.rs b/components/script/dom/bindings/utils.rs index cf89f8379cd..f97c7188f32 100644 --- a/components/script/dom/bindings/utils.rs +++ b/components/script/dom/bindings/utils.rs @@ -5,86 +5,31 @@ //! Various utilities to glue JavaScript and the DOM implementation together. use std::cell::RefCell; -use std::ffi::CString; use std::os::raw::c_char; -use std::ptr::NonNull; -use std::sync::OnceLock; use std::thread::LocalKey; -use std::{ptr, slice, str}; +use std::{ptr, slice}; -use js::JS_CALLEE; use js::conversions::ToJSValConvertible; -use js::glue::{ - CallJitGetterOp, CallJitMethodOp, CallJitSetterOp, IsWrapper, JS_GetReservedSlot, - RUST_FUNCTION_VALUE_TO_JITINFO, UnwrapObjectDynamic, UnwrapObjectStatic, -}; +use js::glue::{IsWrapper, UnwrapObjectDynamic, UnwrapObjectStatic}; use js::jsapi::{ - AtomToLinearString, CallArgs, DOMCallbacks, ExceptionStackBehavior, GetLinearStringCharAt, - GetLinearStringLength, GetNonCCWObjectGlobal, HandleId as RawHandleId, - HandleObject as RawHandleObject, Heap, JS_ClearPendingException, + CallArgs, DOMCallbacks, HandleId as RawHandleId, HandleObject as RawHandleObject, Heap, JS_DeprecatedStringHasLatin1Chars, JS_EnumerateStandardClasses, JS_FreezeObject, - JS_GetLatin1StringCharsAndLength, JS_IsExceptionPending, JS_IsGlobalObject, - JS_ResolveStandardClass, JSAtom, JSContext, JSJitInfo, JSObject, JSTracer, - MutableHandleIdVector as RawMutableHandleIdVector, MutableHandleValue as RawMutableHandleValue, - ObjectOpResult, StringIsArrayIndex, -}; -use js::jsval::{JSVal, UndefinedValue}; -use js::rust::wrappers::{ - CallOriginalPromiseReject, JS_DeletePropertyById, JS_ForwardGetPropertyTo, - JS_GetPendingException, JS_GetProperty, JS_GetPrototype, JS_HasProperty, JS_HasPropertyById, - JS_SetPendingException, JS_SetProperty, -}; -use js::rust::{ - GCMethods, Handle, HandleId, HandleObject, HandleValue, MutableHandleValue, ToString, - get_object_class, is_dom_class, + JS_GetLatin1StringCharsAndLength, JS_IsGlobalObject, JS_ResolveStandardClass, JSContext, + JSObject, JSTracer, MutableHandleIdVector as RawMutableHandleIdVector, }; +use js::rust::{Handle, HandleObject, MutableHandleValue, get_object_class, is_dom_class}; use crate::DomTypes; -use crate::dom::bindings::codegen::InterfaceObjectMap; -use crate::dom::bindings::codegen::PrototypeList::{self, PROTO_OR_IFACE_LENGTH}; +use crate::dom::bindings::codegen::{InterfaceObjectMap, PrototypeList}; use crate::dom::bindings::constructor::call_html_constructor; -use crate::dom::bindings::conversions::{ - DerivedFrom, PrototypeCheck, jsstring_to_str, private_from_proto_check, -}; -use crate::dom::bindings::error::{Error, throw_dom_exception, throw_invalid_this}; +use crate::dom::bindings::conversions::DerivedFrom; +use crate::dom::bindings::error::{Error, throw_dom_exception}; use crate::dom::bindings::reflector::DomObject; use crate::dom::bindings::settings_stack::{self, StackEntry}; -use crate::dom::bindings::str::DOMString; use crate::dom::bindings::trace::trace_object; use crate::dom::windowproxy::WindowProxyHandler; use crate::script_runtime::{CanGc, JSContext as SafeJSContext}; -/// A OnceLock wrapping a type that is not considered threadsafe by the Rust compiler, but -/// will be used in a threadsafe manner (it will not be mutated, after being initialized). -/// -/// This is needed to allow using JS API types (which usually involve raw pointers) in static initializers, -/// when Servo guarantees through the use of OnceLock that only one thread will ever initialize -/// the value. -pub(crate) struct ThreadUnsafeOnceLock<T>(OnceLock<T>); - -impl<T> ThreadUnsafeOnceLock<T> { - pub(crate) const fn new() -> Self { - Self(OnceLock::new()) - } - - /// Initialize the value inside this lock. Panics if the lock has been previously initialized. - pub(crate) fn set(&self, val: T) { - assert!(self.0.set(val).is_ok()); - } - - /// Get a reference to the value inside this lock. Panics if the lock has not been initialized. - /// - /// SAFETY: - /// The caller must ensure that it does not mutate value contained inside this lock - /// (using interior mutability). - pub(crate) unsafe fn get(&self) -> &T { - self.0.get().unwrap() - } -} - -unsafe impl<T> Sync for ThreadUnsafeOnceLock<T> {} -unsafe impl<T> Send for ThreadUnsafeOnceLock<T> {} - #[derive(JSTraceable, MallocSizeOf)] /// Static data associated with a global object. pub(crate) struct GlobalStaticData { @@ -102,21 +47,7 @@ impl GlobalStaticData { } } -/// The index of the slot where the object holder of that interface's -/// unforgeable members are defined. -pub(crate) const DOM_PROTO_UNFORGEABLE_HOLDER_SLOT: u32 = 0; - -/// The index of the slot that contains a reference to the ProtoOrIfaceArray. -// All DOM globals must have a slot at DOM_PROTOTYPE_SLOT. -pub(crate) const DOM_PROTOTYPE_SLOT: u32 = js::JSCLASS_GLOBAL_SLOT_COUNT; - -/// The flag set on the `JSClass`es for DOM global objects. -// NOTE: This is baked into the Ion JIT as 0 in codegen for LGetDOMProperty and -// LSetDOMProperty. Those constants need to be changed accordingly if this value -// changes. -pub(crate) const JSCLASS_DOM_GLOBAL: u32 = js::JSCLASS_USERBIT1; - -pub(crate) use script_bindings::utils::{DOMClass, DOMJSClass}; +pub(crate) use script_bindings::utils::*; /// Returns a JSVal representing the frozen JavaScript array pub(crate) fn to_frozen_array<T: ToJSValConvertible>( @@ -131,132 +62,6 @@ pub(crate) fn to_frozen_array<T: ToJSValConvertible>( unsafe { JS_FreezeObject(*cx, RawHandleObject::from(obj.handle())) }; } -/// Returns the ProtoOrIfaceArray for the given global object. -/// Fails if `global` is not a DOM global object. -pub(crate) fn get_proto_or_iface_array(global: *mut JSObject) -> *mut ProtoOrIfaceArray { - unsafe { - assert_ne!(((*get_object_class(global)).flags & JSCLASS_DOM_GLOBAL), 0); - let mut slot = UndefinedValue(); - JS_GetReservedSlot(global, DOM_PROTOTYPE_SLOT, &mut slot); - slot.to_private() as *mut ProtoOrIfaceArray - } -} - -/// An array of *mut JSObject of size PROTO_OR_IFACE_LENGTH. -pub(crate) type ProtoOrIfaceArray = [*mut JSObject; PROTO_OR_IFACE_LENGTH]; - -/// Gets the property `id` on `proxy`'s prototype. If it exists, `*found` is -/// set to true and `*vp` to the value, otherwise `*found` is set to false. -/// -/// Returns false on JSAPI failure. -pub(crate) unsafe fn get_property_on_prototype( - cx: *mut JSContext, - proxy: HandleObject, - receiver: HandleValue, - id: HandleId, - found: *mut bool, - vp: MutableHandleValue, -) -> bool { - rooted!(in(cx) let mut proto = ptr::null_mut::<JSObject>()); - if !JS_GetPrototype(cx, proxy, proto.handle_mut()) || proto.is_null() { - *found = false; - return true; - } - let mut has_property = false; - if !JS_HasPropertyById(cx, proto.handle(), id, &mut has_property) { - return false; - } - *found = has_property; - if !has_property { - return true; - } - - JS_ForwardGetPropertyTo(cx, proto.handle(), id, receiver, vp) -} - -/// Get an array index from the given `jsid`. Returns `None` if the given -/// `jsid` is not an integer. -pub(crate) unsafe fn get_array_index_from_id(_cx: *mut JSContext, id: HandleId) -> Option<u32> { - let raw_id = *id; - if raw_id.is_int() { - return Some(raw_id.to_int() as u32); - } - - if raw_id.is_void() || !raw_id.is_string() { - return None; - } - - let atom = raw_id.to_string() as *mut JSAtom; - let s = AtomToLinearString(atom); - if GetLinearStringLength(s) == 0 { - return None; - } - - let chars = [GetLinearStringCharAt(s, 0)]; - let first_char = char::decode_utf16(chars.iter().cloned()) - .next() - .map_or('\0', |r| r.unwrap_or('\0')); - if first_char.is_ascii_lowercase() { - return None; - } - - let mut i = 0; - if StringIsArrayIndex(s, &mut i) { - Some(i) - } else { - None - } - - /*let s = jsstr_to_string(cx, RUST_JSID_TO_STRING(raw_id)); - if s.len() == 0 { - return None; - } - - let first = s.chars().next().unwrap(); - if first.is_ascii_lowercase() { - return None; - } - - let mut i: u32 = 0; - let is_array = if s.is_ascii() { - let chars = s.as_bytes(); - StringIsArrayIndex1(chars.as_ptr() as *const _, chars.len() as u32, &mut i) - } else { - let chars = s.encode_utf16().collect::<Vec<u16>>(); - let slice = chars.as_slice(); - StringIsArrayIndex2(slice.as_ptr(), chars.len() as u32, &mut i) - }; - - if is_array { - Some(i) - } else { - None - }*/ -} - -/// Find the enum equivelent of a string given by `v` in `pairs`. -/// Returns `Err(())` on JSAPI failure (there is a pending exception), and -/// `Ok((None, value))` if there was no matching string. -pub(crate) unsafe fn find_enum_value<'a, T>( - cx: *mut JSContext, - v: HandleValue, - pairs: &'a [(&'static str, T)], -) -> Result<(Option<&'a T>, DOMString), ()> { - match ptr::NonNull::new(ToString(cx, v)) { - Some(jsstr) => { - let search = jsstring_to_str(cx, jsstr); - Ok(( - pairs - .iter() - .find(|&&(key, _)| search == *key) - .map(|(_, ev)| ev), - search, - )) - }, - None => Err(()), - } -} - /// Returns wether `obj` is a platform object using dynamic unwrap /// <https://heycam.github.io/webidl/#dfn-platform-object> #[allow(dead_code)] @@ -295,104 +100,6 @@ fn is_platform_object( } } -/// Get the property with name `property` from `object`. -/// Returns `Err(())` on JSAPI failure (there is a pending exception), and -/// `Ok(false)` if there was no property with the given name. -pub(crate) fn get_dictionary_property( - cx: *mut JSContext, - object: HandleObject, - property: &str, - rval: MutableHandleValue, - _can_gc: CanGc, -) -> Result<bool, ()> { - fn has_property( - cx: *mut JSContext, - object: HandleObject, - property: &CString, - found: &mut bool, - ) -> bool { - unsafe { JS_HasProperty(cx, object, property.as_ptr(), found) } - } - fn get_property( - cx: *mut JSContext, - object: HandleObject, - property: &CString, - value: MutableHandleValue, - ) -> bool { - unsafe { JS_GetProperty(cx, object, property.as_ptr(), value) } - } - - let property = CString::new(property).unwrap(); - if object.get().is_null() { - return Ok(false); - } - - let mut found = false; - if !has_property(cx, object, &property, &mut found) { - return Err(()); - } - - if !found { - return Ok(false); - } - - if !get_property(cx, object, &property, rval) { - return Err(()); - } - - Ok(true) -} - -/// Set the property with name `property` from `object`. -/// Returns `Err(())` on JSAPI failure, or null object, -/// and Ok(()) otherwise -pub(crate) fn set_dictionary_property( - cx: *mut JSContext, - object: HandleObject, - property: &str, - value: HandleValue, -) -> Result<(), ()> { - if object.get().is_null() { - return Err(()); - } - - let property = CString::new(property).unwrap(); - unsafe { - if !JS_SetProperty(cx, object, property.as_ptr(), value) { - return Err(()); - } - } - - Ok(()) -} - -/// Returns whether `proxy` has a property `id` on its prototype. -pub(crate) unsafe fn has_property_on_prototype( - cx: *mut JSContext, - proxy: HandleObject, - id: HandleId, - found: &mut bool, -) -> bool { - rooted!(in(cx) let mut proto = ptr::null_mut::<JSObject>()); - if !JS_GetPrototype(cx, proxy, proto.handle_mut()) { - return false; - } - assert!(!proto.is_null()); - JS_HasPropertyById(cx, proto.handle(), id, found) -} - -/// Drop the resources held by reserved slots of a global object -pub(crate) unsafe fn finalize_global(obj: *mut JSObject) { - let protolist = get_proto_or_iface_array(obj); - let list = (*protolist).as_mut_ptr(); - for idx in 0..PROTO_OR_IFACE_LENGTH as isize { - let entry = list.offset(idx); - let value = *entry; - <*mut JSObject>::post_barrier(entry, value, ptr::null_mut()); - } - let _: Box<ProtoOrIfaceArray> = Box::from_raw(protolist); -} - /// Trace the resources held by reserved slots of a global object pub(crate) unsafe fn trace_global(tracer: *mut JSTracer, obj: *mut JSObject) { let array = get_proto_or_iface_array(obj); @@ -462,141 +169,6 @@ pub(crate) unsafe extern "C" fn resolve_global( true } -/// Deletes the property `id` from `object`. -pub(crate) unsafe fn delete_property_by_id( - cx: *mut JSContext, - object: HandleObject, - id: HandleId, - bp: *mut ObjectOpResult, -) -> bool { - JS_DeletePropertyById(cx, object, id, bp) -} - -unsafe fn generic_call<const EXCEPTION_TO_REJECTION: bool>( - cx: *mut JSContext, - argc: libc::c_uint, - vp: *mut JSVal, - is_lenient: bool, - call: unsafe extern "C" fn( - *const JSJitInfo, - *mut JSContext, - RawHandleObject, - *mut libc::c_void, - u32, - *mut JSVal, - ) -> bool, - can_gc: CanGc, -) -> bool { - let args = CallArgs::from_vp(vp, argc); - - let info = RUST_FUNCTION_VALUE_TO_JITINFO(JS_CALLEE(cx, vp)); - let proto_id = (*info).__bindgen_anon_2.protoID; - let cx = SafeJSContext::from_ptr(cx); - - let thisobj = args.thisv(); - if !thisobj.get().is_null_or_undefined() && !thisobj.get().is_object() { - throw_invalid_this(cx, proto_id); - return if EXCEPTION_TO_REJECTION { - exception_to_promise(*cx, args.rval(), can_gc) - } else { - false - }; - } - - rooted!(in(*cx) let obj = if thisobj.get().is_object() { - thisobj.get().to_object() - } else { - GetNonCCWObjectGlobal(JS_CALLEE(*cx, vp).to_object_or_null()) - }); - let depth = (*info).__bindgen_anon_3.depth as usize; - let proto_check = PrototypeCheck::Depth { depth, proto_id }; - let this = match private_from_proto_check(obj.get(), *cx, proto_check) { - Ok(val) => val, - Err(()) => { - if is_lenient { - debug_assert!(!JS_IsExceptionPending(*cx)); - *vp = UndefinedValue(); - return true; - } else { - throw_invalid_this(cx, proto_id); - return if EXCEPTION_TO_REJECTION { - exception_to_promise(*cx, args.rval(), can_gc) - } else { - false - }; - } - }, - }; - call( - info, - *cx, - obj.handle().into(), - this as *mut libc::c_void, - argc, - vp, - ) -} - -/// Generic method of IDL interface. -pub(crate) unsafe extern "C" fn generic_method<const EXCEPTION_TO_REJECTION: bool>( - cx: *mut JSContext, - argc: libc::c_uint, - vp: *mut JSVal, -) -> bool { - generic_call::<EXCEPTION_TO_REJECTION>(cx, argc, vp, false, CallJitMethodOp, CanGc::note()) -} - -/// Generic getter of IDL interface. -pub(crate) unsafe extern "C" fn generic_getter<const EXCEPTION_TO_REJECTION: bool>( - cx: *mut JSContext, - argc: libc::c_uint, - vp: *mut JSVal, -) -> bool { - generic_call::<EXCEPTION_TO_REJECTION>(cx, argc, vp, false, CallJitGetterOp, CanGc::note()) -} - -/// Generic lenient getter of IDL interface. -pub(crate) unsafe extern "C" fn generic_lenient_getter<const EXCEPTION_TO_REJECTION: bool>( - cx: *mut JSContext, - argc: libc::c_uint, - vp: *mut JSVal, -) -> bool { - generic_call::<EXCEPTION_TO_REJECTION>(cx, argc, vp, true, CallJitGetterOp, CanGc::note()) -} - -unsafe extern "C" fn call_setter( - info: *const JSJitInfo, - cx: *mut JSContext, - handle: RawHandleObject, - this: *mut libc::c_void, - argc: u32, - vp: *mut JSVal, -) -> bool { - if !CallJitSetterOp(info, cx, handle, this, argc, vp) { - return false; - } - *vp = UndefinedValue(); - true -} - -/// Generic setter of IDL interface. -pub(crate) unsafe extern "C" fn generic_setter( - cx: *mut JSContext, - argc: libc::c_uint, - vp: *mut JSVal, -) -> bool { - generic_call::<false>(cx, argc, vp, false, call_setter, CanGc::note()) -} - -/// Generic lenient setter of IDL interface. -pub(crate) unsafe extern "C" fn generic_lenient_setter( - cx: *mut JSContext, - argc: libc::c_uint, - vp: *mut JSVal, -) -> bool { - generic_call::<false>(cx, argc, vp, true, call_setter, CanGc::note()) -} - unsafe extern "C" fn instance_class_has_proto_at_depth( clasp: *const js::jsapi::JSClass, proto_id: u32, @@ -633,48 +205,6 @@ impl AsCCharPtrPtr for [u8] { } } -/// <https://searchfox.org/mozilla-central/rev/7279a1df13a819be254fd4649e07c4ff93e4bd45/dom/bindings/BindingUtils.cpp#3300> -pub(crate) unsafe extern "C" fn generic_static_promise_method( - cx: *mut JSContext, - argc: libc::c_uint, - vp: *mut JSVal, -) -> bool { - let args = CallArgs::from_vp(vp, argc); - - let info = RUST_FUNCTION_VALUE_TO_JITINFO(JS_CALLEE(cx, vp)); - assert!(!info.is_null()); - // TODO: we need safe wrappers for this in mozjs! - //assert_eq!((*info)._bitfield_1, JSJitInfo_OpType::StaticMethod as u8) - let static_fn = (*info).__bindgen_anon_1.staticMethod.unwrap(); - if static_fn(cx, argc, vp) { - return true; - } - exception_to_promise(cx, args.rval(), CanGc::note()) -} - -/// Coverts exception to promise rejection -/// -/// <https://searchfox.org/mozilla-central/rev/b220e40ff2ee3d10ce68e07d8a8a577d5558e2a2/dom/bindings/BindingUtils.cpp#3315> -pub(crate) unsafe fn exception_to_promise( - cx: *mut JSContext, - rval: RawMutableHandleValue, - _can_gc: CanGc, -) -> bool { - rooted!(in(cx) let mut exception = UndefinedValue()); - if !JS_GetPendingException(cx, exception.handle_mut()) { - return false; - } - JS_ClearPendingException(cx); - if let Some(promise) = NonNull::new(CallOriginalPromiseReject(cx, exception.handle())) { - promise.to_jsval(cx, MutableHandleValue::from_raw(rval)); - true - } else { - // We just give up. Put the exception back. - JS_SetPendingException(cx, exception.handle(), ExceptionStackBehavior::Capture); - false - } -} - /// Operations that must be invoked from the generated bindings. pub(crate) trait DomHelpers<D: DomTypes> { fn throw_dom_exception(cx: SafeJSContext, global: &D::GlobalScope, result: Error); diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index f27bd303648..4945892ef60 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -51,6 +51,7 @@ use num_traits::ToPrimitive; use percent_encoding::percent_decode; use profile_traits::ipc as profile_ipc; use profile_traits::time::TimerMetadataFrameType; +use script_bindings::interfaces::DocumentHelpers; use script_layout_interface::{PendingRestyle, TrustedNodeAddress}; use script_traits::{ AnimationState, ConstellationInputEvent, DocumentActivity, ProgressiveWebMetricType, ScriptMsg, @@ -78,7 +79,6 @@ use super::bindings::codegen::Bindings::XPathEvaluatorBinding::XPathEvaluatorMet use super::canvasrenderingcontext2d::CanvasRenderingContext2D; use super::clipboardevent::ClipboardEventType; use super::performancepainttiming::PerformancePaintTiming; -use crate::DomTypes; use crate::animation_timeline::AnimationTimeline; use crate::animations::Animations; use crate::canvas_context::CanvasContext as _; @@ -6398,11 +6398,7 @@ fn is_named_element_with_id_attribute(elem: &Element) -> bool { elem.is::<HTMLImageElement>() && elem.get_name().is_some_and(|name| !name.is_empty()) } -pub(crate) trait DocumentHelpers<D: DomTypes> { - fn ensure_safe_to_run_script_or_layout(&self); -} - -impl DocumentHelpers<crate::DomTypeHolder> for Document { +impl DocumentHelpers for Document { fn ensure_safe_to_run_script_or_layout(&self) { Document::ensure_safe_to_run_script_or_layout(self) } diff --git a/components/script/dom/globalscope.rs b/components/script/dom/globalscope.rs index c4625895f3a..cb700d7d4c2 100644 --- a/components/script/dom/globalscope.rs +++ b/components/script/dom/globalscope.rs @@ -3348,6 +3348,8 @@ pub(crate) trait GlobalScopeHelpers<D: crate::DomTypes> { fn perform_a_microtask_checkpoint(&self, can_gc: CanGc); fn get_url(&self) -> ServoUrl; + + fn is_secure_context(&self) -> bool; } #[allow(unsafe_code)] @@ -3387,4 +3389,8 @@ impl GlobalScopeHelpers<crate::DomTypeHolder> for GlobalScope { fn get_url(&self) -> ServoUrl { self.get_url() } + + fn is_secure_context(&self) -> bool { + self.is_secure_context() + } } diff --git a/components/script/dom/readablestream.rs b/components/script/dom/readablestream.rs index f8b23072e66..b5048a4644d 100644 --- a/components/script/dom/readablestream.rs +++ b/components/script/dom/readablestream.rs @@ -155,6 +155,7 @@ impl Callback for PipeTo { /// - the current state. /// - the type of `result`. /// - the state of a stored promise(in some cases). + #[allow(unsafe_code)] fn callback(&self, cx: SafeJSContext, result: SafeHandleValue, realm: InRealm, can_gc: CanGc) { let global = self.reader.global(); @@ -197,14 +198,16 @@ impl Callback for PipeTo { } else { rooted!(in(*cx) let object = result.to_object()); rooted!(in(*cx) let mut done = UndefinedValue()); - get_dictionary_property( - *cx, - object.handle(), - "done", - done.handle_mut(), - can_gc, - ) - .unwrap() + unsafe { + get_dictionary_property( + *cx, + object.handle(), + "done", + done.handle_mut(), + can_gc, + ) + .unwrap() + } } }; // If any chunks have been read but not yet written, write them to dest. @@ -342,6 +345,7 @@ impl PipeTo { /// Try to write a chunk using the jsval, and returns wether it succeeded // It will fail if it is the last `done` chunk, or if it is not a chunk at all. + #[allow(unsafe_code)] fn write_chunk( &self, cx: SafeJSContext, @@ -352,9 +356,10 @@ impl PipeTo { if chunk.is_object() { rooted!(in(*cx) let object = chunk.to_object()); rooted!(in(*cx) let mut bytes = UndefinedValue()); - let has_value = + let has_value = unsafe { get_dictionary_property(*cx, object.handle(), "value", bytes.handle_mut(), can_gc) - .expect("Chunk should have a value."); + .expect("Chunk should have a value.") + }; if !bytes.is_undefined() && has_value { // Write the chunk. let write_promise = self.writer.write(cx, global, bytes.handle(), can_gc); diff --git a/components/script/dom/servointernals.rs b/components/script/dom/servointernals.rs index 295bd838443..58d749d8227 100644 --- a/components/script/dom/servointernals.rs +++ b/components/script/dom/servointernals.rs @@ -5,7 +5,10 @@ use std::rc::Rc; use dom_struct::dom_struct; +use js::rust::HandleObject; use profile_traits::mem::MemoryReportResult; +use script_bindings::interfaces::ServoInternalsHelpers; +use script_bindings::script_runtime::JSContext; use script_traits::ScriptMsg; use crate::dom::bindings::codegen::Bindings::ServoInternalsBinding::ServoInternalsMethods; @@ -14,7 +17,7 @@ use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object}; use crate::dom::bindings::root::DomRoot; use crate::dom::globalscope::GlobalScope; use crate::dom::promise::Promise; -use crate::realms::InRealm; +use crate::realms::{AlreadyInRealm, InRealm}; use crate::routed_promise::{RoutedPromiseListener, route_promise}; use crate::script_runtime::CanGc; @@ -57,3 +60,16 @@ impl RoutedPromiseListener<MemoryReportResult> for ServoInternals { promise.resolve_native(&response.content, can_gc); } } + +impl ServoInternalsHelpers for ServoInternals { + /// The navigator.servo api is only exposed to about: pages except about:blank + #[allow(unsafe_code)] + fn is_servo_internal(cx: JSContext, _global: HandleObject) -> bool { + unsafe { + let in_realm_proof = AlreadyInRealm::assert_for_cx(cx); + let global_scope = GlobalScope::from_context(*cx, InRealm::Already(&in_realm_proof)); + let url = global_scope.get_url(); + url.scheme() == "about" && url.as_str() != "about:blank" + } + } +} diff --git a/components/script/dom/testbinding.rs b/components/script/dom/testbinding.rs index 4095ba1319a..e54fea515a6 100644 --- a/components/script/dom/testbinding.rs +++ b/components/script/dom/testbinding.rs @@ -14,6 +14,7 @@ use js::jsapi::{Heap, JS_NewPlainObject, JSObject}; use js::jsval::JSVal; use js::rust::{CustomAutoRooterGuard, HandleObject, HandleValue, MutableHandleValue}; use js::typedarray::{self, Uint8ClampedArray}; +use script_bindings::interfaces::TestBindingHelpers; use script_bindings::record::Record; use script_traits::serializable::BlobImpl; use servo_config::prefs; @@ -1171,3 +1172,12 @@ impl TestBindingCallback { .resolve_native(&self.value, CanGc::note()); } } + +impl TestBindingHelpers for TestBinding { + fn condition_satisfied(cx: SafeJSContext, global: HandleObject) -> bool { + Self::condition_satisfied(cx, global) + } + fn condition_unsatisfied(cx: SafeJSContext, global: HandleObject) -> bool { + Self::condition_unsatisfied(cx, global) + } +} diff --git a/components/script/dom/webgl2renderingcontext.rs b/components/script/dom/webgl2renderingcontext.rs index d64a7f70376..bb6ffa11849 100644 --- a/components/script/dom/webgl2renderingcontext.rs +++ b/components/script/dom/webgl2renderingcontext.rs @@ -21,6 +21,7 @@ use js::jsapi::{JSObject, Type}; use js::jsval::{BooleanValue, DoubleValue, Int32Value, NullValue, ObjectValue, UInt32Value}; use js::rust::{CustomAutoRooterGuard, HandleObject, MutableHandleValue}; use js::typedarray::{ArrayBufferView, CreateWith, Float32, Int32Array, Uint32, Uint32Array}; +use script_bindings::interfaces::WebGL2RenderingContextHelpers; use script_layout_interface::HTMLCanvasDataSource; use servo_config::pref; use url::Host; @@ -4709,3 +4710,9 @@ impl LayoutCanvasRenderingContextHelpers for LayoutDom<'_, WebGL2RenderingContex unsafe { (*this.base.to_layout().unsafe_get()).layout_handle() } } } + +impl WebGL2RenderingContextHelpers for WebGL2RenderingContext { + fn is_webgl2_enabled(cx: JSContext, global: HandleObject) -> bool { + Self::is_webgl2_enabled(cx, global) + } +} diff --git a/components/script/dom/windowproxy.rs b/components/script/dom/windowproxy.rs index d008c76f905..812499b3785 100644 --- a/components/script/dom/windowproxy.rs +++ b/components/script/dom/windowproxy.rs @@ -852,7 +852,7 @@ unsafe fn GetSubframeWindowProxy( proxy: RawHandleObject, id: RawHandleId, ) -> Option<(DomRoot<WindowProxy>, u32)> { - let index = get_array_index_from_id(cx, Handle::from_raw(id)); + let index = get_array_index_from_id(Handle::from_raw(id)); if let Some(index) = index { let mut slot = UndefinedValue(); GetProxyPrivate(*proxy, &mut slot); @@ -934,7 +934,7 @@ unsafe extern "C" fn defineProperty( desc: RawHandle<PropertyDescriptor>, res: *mut ObjectOpResult, ) -> bool { - if get_array_index_from_id(cx, Handle::from_raw(id)).is_some() { + if get_array_index_from_id(Handle::from_raw(id)).is_some() { // Spec says to Reject whether this is a supported index or not, // since we have no indexed setter or indexed creator. That means // throwing in strict mode (FIXME: Bug 828137), doing nothing in @@ -1003,7 +1003,7 @@ unsafe extern "C" fn set( receiver: RawHandleValue, res: *mut ObjectOpResult, ) -> bool { - if get_array_index_from_id(cx, Handle::from_raw(id)).is_some() { + if get_array_index_from_id(Handle::from_raw(id)).is_some() { // Reject (which means throw if and only if strict) the set. (*res).code_ = JSErrNum::JSMSG_READ_ONLY as ::libc::uintptr_t; return true; diff --git a/components/script/webdriver_handlers.rs b/components/script/webdriver_handlers.rs index a0ce6f1ca4a..9427784342e 100644 --- a/components/script/webdriver_handlers.rs +++ b/components/script/webdriver_handlers.rs @@ -224,7 +224,7 @@ pub(crate) unsafe fn jsval_to_webdriver( }); let _ac = JSAutoRealm::new(cx, *object); - if is_array_like(cx, val) || is_arguments_object(cx, val) { + if is_array_like::<crate::DomTypeHolder>(cx, val) || is_arguments_object(cx, val) { let mut result: Vec<WebDriverJSValue> = Vec::new(); let length = match get_property::<u32>( diff --git a/components/script_bindings/codegen/Bindings.conf b/components/script_bindings/codegen/Bindings.conf index 949b6360537..3f356d5dff0 100644 --- a/components/script_bindings/codegen/Bindings.conf +++ b/components/script_bindings/codegen/Bindings.conf @@ -137,7 +137,7 @@ DOMInterfaces = { }, 'Document': { - 'additionalTraits': ["crate::dom::document::DocumentHelpers<Self>"], + 'additionalTraits': ["script_bindings::interfaces::DocumentHelpers"], 'canGc': ['Close', 'CreateElement', 'CreateElementNS', 'ImportNode', 'SetTitle', 'Write', 'Writeln', 'CreateEvent', 'CreateRange', 'Open', 'Open_', 'CreateComment', 'CreateAttribute', 'CreateAttributeNS', 'CreateDocumentFragment', 'CreateTextNode', 'CreateCDATASection', 'CreateProcessingInstruction', 'Prepend', 'Append', 'ReplaceChildren', 'SetBgColor', 'SetFgColor', 'Fonts', 'ElementFromPoint', 'ElementsFromPoint', 'ExitFullscreen', 'CreateExpression', 'CreateNSResolver', 'Evaluate'], }, @@ -527,6 +527,7 @@ DOMInterfaces = { 'ServoInternals': { 'inRealms': ['ReportMemory'], 'canGc': ['ReportMemory'], + 'additionalTraits': ['script_bindings::interfaces::ServoInternalsHelpers'], }, 'ShadowRoot': { @@ -550,6 +551,7 @@ DOMInterfaces = { 'TestBinding': { 'inRealms': ['PromiseAttribute', 'PromiseNativeHandler'], 'canGc': ['InterfaceAttribute', 'GetInterfaceAttributeNullable', 'ReceiveInterface', 'ReceiveInterfaceSequence', 'ReceiveNullableInterface', 'PromiseAttribute', 'PromiseNativeHandler', 'PromiseResolveNative', 'PromiseRejectNative', 'PromiseRejectWithTypeError'], + 'additionalTraits': ['script_bindings::interfaces::TestBindingHelpers'], }, 'TestWorklet': { @@ -580,6 +582,7 @@ DOMInterfaces = { 'WebGL2RenderingContext': { 'canGc': ['MakeXRCompatible'], + 'additionalTraits': ['script_bindings::interfaces::WebGL2RenderingContextHelpers'], }, 'Window': { diff --git a/components/script_bindings/codegen/CodegenRust.py b/components/script_bindings/codegen/CodegenRust.py index 1f18ab2c894..783590aad70 100644 --- a/components/script_bindings/codegen/CodegenRust.py +++ b/components/script_bindings/codegen/CodegenRust.py @@ -498,7 +498,7 @@ class CGMethodCall(CGThing): # XXXbz Now we're supposed to check for distinguishingArg being # an array or a platform object that supports indexed # properties... skip that last for now. It's a bit of a pain. - pickFirstSignature(f"{distinguishingArg}.get().is_object() && is_array_like(*cx, {distinguishingArg})", + pickFirstSignature(f"{distinguishingArg}.get().is_object() && is_array_like::<D>(*cx, {distinguishingArg})", lambda s: (s[1][distinguishingIndex].type.isSequence() or s[1][distinguishingIndex].type.isObject())) @@ -1565,10 +1565,10 @@ def MemberCondition(pref, func, exposed, secure): if pref: conditions.append(f'Condition::Pref("{pref}")') if func: - conditions.append(f'Condition::Func({func})') + conditions.append(f'Condition::Func(D::{func})') if exposed: conditions.extend([ - f"Condition::Exposed(InterfaceObjectMap::Globals::{camel_to_upper_snake(i)})" for i in exposed + f"Condition::Exposed(Globals::{camel_to_upper_snake(i)})" for i in exposed ]) if len(conditions) == 0: conditions.append("Condition::Satisfied") @@ -2369,7 +2369,7 @@ DOMClass {{ depth: {descriptor.prototypeDepth}, type_id: {DOMClassTypeId(descriptor)}, malloc_size_of: {mallocSizeOf} as unsafe fn(&mut _, _) -> _, - global: InterfaceObjectMap::Globals::{globals_}, + global: Globals::{globals_}, }}""" @@ -2962,14 +2962,15 @@ class CGConstructorEnabled(CGAbstractMethod): CGAbstractMethod.__init__(self, descriptor, 'ConstructorEnabled', 'bool', [Argument("SafeJSContext", "aCx"), - Argument("HandleObject", "aObj")]) + Argument("HandleObject", "aObj")], + templateArgs=['D: DomTypes']) def definition_body(self): conditions = [] iface = self.descriptor.interface bits = " | ".join(sorted( - f"InterfaceObjectMap::Globals::{camel_to_upper_snake(i)}" for i in iface.exposureSet + f"Globals::{camel_to_upper_snake(i)}" for i in iface.exposureSet )) conditions.append(f"is_exposed_in(aObj, {bits})") @@ -2981,14 +2982,14 @@ class CGConstructorEnabled(CGAbstractMethod): func = iface.getExtendedAttribute("Func") if func: assert isinstance(func, list) and len(func) == 1 - conditions.append(f"{func[0]}(aCx, aObj)") + conditions.append(f"D::{func[0]}(aCx, aObj)") secure = iface.getExtendedAttribute("SecureContext") if secure: conditions.append(""" unsafe { let in_realm_proof = AlreadyInRealm::assert_for_cx(aCx); - GlobalScope::from_context(*aCx, InRealm::Already(&in_realm_proof)).is_secure_context() + D::GlobalScope::from_context(*aCx, InRealm::Already(&in_realm_proof)).is_secure_context() } """) @@ -3004,8 +3005,8 @@ def InitLegacyUnforgeablePropertiesOnHolder(descriptor, properties): """ unforgeables = [] - defineLegacyUnforgeableAttrs = "define_guarded_properties(cx, unforgeable_holder.handle(), %s, global);" - defineLegacyUnforgeableMethods = "define_guarded_methods(cx, unforgeable_holder.handle(), %s, global);" + defineLegacyUnforgeableAttrs = "define_guarded_properties::<D>(cx, unforgeable_holder.handle(), %s, global);" + defineLegacyUnforgeableMethods = "define_guarded_methods::<D>(cx, unforgeable_holder.handle(), %s, global);" unforgeableMembers = [ (defineLegacyUnforgeableAttrs, properties.unforgeable_attrs), @@ -3175,7 +3176,7 @@ class CGWrapGlobalMethod(CGAbstractMethod): ("define_guarded_methods", self.properties.methods), ("define_guarded_constants", self.properties.consts) ] - members = [f"{function}(cx, obj.handle(), {array.variableName()}.get(), obj.handle());" + members = [f"{function}::<D>(cx, obj.handle(), {array.variableName()}.get(), obj.handle());" for (function, array) in pairs if array.length() > 0] membersStr = "\n".join(members) @@ -3413,7 +3414,7 @@ let global = incumbent_global.reflector().get_jsobject();\n""" """ let conditions = ${conditions}; let is_satisfied = conditions.iter().any(|c| - c.is_satisfied( + c.is_satisfied::<D>( SafeJSContext::from_ptr(cx), HandleObject::from_raw(obj), global)); @@ -3469,7 +3470,7 @@ class CGCreateInterfaceObjectsMethod(CGAbstractMethod): rooted!(in(*cx) let proto = {proto}); assert!(!proto.is_null()); rooted!(in(*cx) let mut namespace = ptr::null_mut::<JSObject>()); -create_namespace_object(cx, global, proto.handle(), &NAMESPACE_OBJECT_CLASS, +create_namespace_object::<D>(cx, global, proto.handle(), &NAMESPACE_OBJECT_CLASS, {methods}, {constants}, {str_to_cstr(name)}, namespace.handle_mut()); assert!(!namespace.is_null()); assert!((*cache)[PrototypeList::Constructor::{id} as usize].is_null()); @@ -3483,7 +3484,7 @@ assert!((*cache)[PrototypeList::Constructor::{id} as usize].is_null()); cName = str_to_cstr(name) return CGGeneric(f""" rooted!(in(*cx) let mut interface = ptr::null_mut::<JSObject>()); -create_callback_interface_object(cx, global, sConstants.get(), {cName}, interface.handle_mut()); +create_callback_interface_object::<D>(cx, global, sConstants.get(), {cName}, interface.handle_mut()); assert!(!interface.is_null()); assert!((*cache)[PrototypeList::Constructor::{name} as usize].is_null()); (*cache)[PrototypeList::Constructor::{name} as usize] = interface.get(); @@ -3544,7 +3545,7 @@ assert!(!prototype_proto.is_null());""")) code.append(CGGeneric(f""" rooted!(in(*cx) let mut prototype = ptr::null_mut::<JSObject>()); -create_interface_prototype_object(cx, +create_interface_prototype_object::<D>(cx, global, prototype_proto.handle(), &PrototypeClass, @@ -3579,7 +3580,7 @@ assert!((*cache)[PrototypeList::ID::{proto_properties['id']} as usize].is_null() assert!(!interface_proto.is_null()); rooted!(in(*cx) let mut interface = ptr::null_mut::<JSObject>()); -create_noncallback_interface_object(cx, +create_noncallback_interface_object::<D>(cx, global, interface_proto.handle(), INTERFACE_OBJECT_CLASS.get(), @@ -3870,7 +3871,7 @@ class CGDefineDOMInterfaceMethod(CGAbstractMethod): return CGGeneric( "define_dom_interface" f"(cx, global, ProtoOrIfaceIndex::{self.variant}({self.id})," - "CreateInterfaceObjects::<D>, ConstructorEnabled)" + "CreateInterfaceObjects::<D>, ConstructorEnabled::<D>)" ) @@ -5927,7 +5928,7 @@ class CGDOMJSProxyHandler_getOwnPropertyDescriptor(CGAbstractExternMethod): """) if indexedGetter: - get += "let index = get_array_index_from_id(*cx, Handle::from_raw(id));\n" + get += "let index = get_array_index_from_id(Handle::from_raw(id));\n" attrs = "JSPROP_ENUMERATE" if self.descriptor.operations['IndexedSetter'] is None: @@ -6042,7 +6043,7 @@ class CGDOMJSProxyHandler_defineProperty(CGAbstractExternMethod): indexedSetter = self.descriptor.operations['IndexedSetter'] if indexedSetter: - set += ("let index = get_array_index_from_id(*cx, Handle::from_raw(id));\n" + set += ("let index = get_array_index_from_id(Handle::from_raw(id));\n" "if let Some(index) = index {\n" " let this = UnwrapProxy::<D>(proxy);\n" " let this = &*this;\n" @@ -6050,7 +6051,7 @@ class CGDOMJSProxyHandler_defineProperty(CGAbstractExternMethod): " return (*opresult).succeed();\n" "}\n") elif self.descriptor.operations['IndexedGetter']: - set += ("if get_array_index_from_id(*cx, Handle::from_raw(id)).is_some() {\n" + set += ("if get_array_index_from_id(Handle::from_raw(id)).is_some() {\n" " return (*opresult).failNoIndexedSetter();\n" "}\n") @@ -6265,7 +6266,7 @@ class CGDOMJSProxyHandler_hasOwn(CGAbstractExternMethod): """) if indexedGetter: - indexed += ("let index = get_array_index_from_id(*cx, Handle::from_raw(id));\n" + indexed += ("let index = get_array_index_from_id(Handle::from_raw(id));\n" "if let Some(index) = index {\n" " let this = UnwrapProxy::<D>(proxy);\n" " let this = &*this;\n" @@ -6357,7 +6358,7 @@ if !expando.is_null() { indexedGetter = self.descriptor.operations['IndexedGetter'] if indexedGetter: - getIndexedOrExpando = ("let index = get_array_index_from_id(*cx, id_lt);\n" + getIndexedOrExpando = ("let index = get_array_index_from_id(id_lt);\n" "if let Some(index) = index {\n" " let this = UnwrapProxy::<D>(proxy);\n" " let this = &*this;\n" diff --git a/components/script_bindings/conversions.rs b/components/script_bindings/conversions.rs index 6e0ce7adee0..847f83fc606 100644 --- a/components/script_bindings/conversions.rs +++ b/components/script_bindings/conversions.rs @@ -21,8 +21,10 @@ use js::rust::{ HandleId, HandleValue, MutableHandleValue, ToString, get_object_class, is_dom_class, is_dom_object, maybe_wrap_value, }; +use num_traits::Float; use crate::inheritance::Castable; +use crate::num::Finite; use crate::reflector::{DomObject, Reflector}; use crate::root::DomRoot; use crate::str::{ByteString, DOMString, USVString}; @@ -408,3 +410,87 @@ pub unsafe fn jsid_to_string(cx: *mut JSContext, id: HandleId) -> Option<DOMStri None } + +impl<T: Float + ToJSValConvertible> ToJSValConvertible for Finite<T> { + #[inline] + unsafe fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) { + let value = **self; + value.to_jsval(cx, rval); + } +} + +impl<T: Float + FromJSValConvertible<Config = ()>> FromJSValConvertible for Finite<T> { + type Config = (); + + unsafe fn from_jsval( + cx: *mut JSContext, + value: HandleValue, + option: (), + ) -> Result<ConversionResult<Finite<T>>, ()> { + let result = match FromJSValConvertible::from_jsval(cx, value, option)? { + ConversionResult::Success(v) => v, + ConversionResult::Failure(error) => { + // FIXME(emilio): Why throwing instead of propagating the error? + throw_type_error(cx, &error); + return Err(()); + }, + }; + match Finite::new(result) { + Some(v) => Ok(ConversionResult::Success(v)), + None => { + throw_type_error(cx, "this argument is not a finite floating-point value"); + Err(()) + }, + } + } +} + +/// Get a `*const libc::c_void` for the given DOM object, unless it is a DOM +/// wrapper, and checking if the object is of the correct type. +/// +/// Returns Err(()) if `obj` is a wrapper or if the object is not an object +/// for a DOM object of the given type (as defined by the proto_id and proto_depth). +#[inline] +#[allow(clippy::result_unit_err)] +unsafe fn private_from_proto_check_static( + obj: *mut JSObject, + proto_check: fn(&'static DOMClass) -> bool, +) -> Result<*const libc::c_void, ()> { + let dom_class = get_dom_class(obj).map_err(|_| ())?; + if proto_check(dom_class) { + trace!("good prototype"); + Ok(private_from_object(obj)) + } else { + trace!("bad prototype"); + Err(()) + } +} + +/// Get a `*const T` for a DOM object accessible from a `JSObject`, where the DOM object +/// is guaranteed not to be a wrapper. +/// +/// # Safety +/// `obj` must point to a valid, non-null JSObject. +#[allow(clippy::result_unit_err)] +pub unsafe fn native_from_object_static<T>(obj: *mut JSObject) -> Result<*const T, ()> +where + T: DomObject + IDLInterface, +{ + private_from_proto_check_static(obj, T::derives).map(|ptr| ptr as *const T) +} + +/// Get a `*const T` for a DOM object accessible from a `HandleValue`. +/// Caller is responsible for throwing a JS exception if needed in case of error. +/// +/// # Safety +/// `cx` must point to a valid, non-null JSContext. +#[allow(clippy::result_unit_err)] +pub unsafe fn native_from_handlevalue<T>(v: HandleValue, cx: *mut JSContext) -> Result<*const T, ()> +where + T: DomObject + IDLInterface, +{ + if !v.get().is_object() { + return Err(()); + } + native_from_object(v.get().to_object(), cx) +} diff --git a/components/script_bindings/error.rs b/components/script_bindings/error.rs index bd8a5b0cb95..031eb41ee22 100644 --- a/components/script_bindings/error.rs +++ b/components/script_bindings/error.rs @@ -2,6 +2,12 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +use js::error::throw_type_error; +use js::jsapi::JS_IsExceptionPending; + +use crate::codegen::PrototypeList::proto_id_to_name; +use crate::script_runtime::JSContext as SafeJSContext; + /// DOM exceptions that can be thrown by a native DOM method. #[derive(Clone, Debug, MallocSizeOf)] pub enum Error { @@ -69,3 +75,20 @@ pub type Fallible<T> = Result<T, Error>; /// The return type for IDL operations that can throw DOM exceptions and /// return `()`. pub type ErrorResult = Fallible<()>; + +/// Throw an exception to signal that a `JSObject` can not be converted to a +/// given DOM type. +pub fn throw_invalid_this(cx: SafeJSContext, proto_id: u16) { + debug_assert!(unsafe { !JS_IsExceptionPending(*cx) }); + let error = format!( + "\"this\" object does not implement interface {}.", + proto_id_to_name(proto_id) + ); + unsafe { throw_type_error(*cx, &error) }; +} + +pub fn throw_constructor_without_new(cx: SafeJSContext, name: &str) { + debug_assert!(unsafe { !JS_IsExceptionPending(*cx) }); + let error = format!("{} constructor: 'new' is required", name); + unsafe { throw_type_error(*cx, &error) }; +} diff --git a/components/script/dom/bindings/finalize.rs b/components/script_bindings/finalize.rs index 03d4950e1c7..fa1079b5624 100644 --- a/components/script/dom/bindings/finalize.rs +++ b/components/script_bindings/finalize.rs @@ -5,16 +5,32 @@ //! Generic finalizer implementations for DOM binding implementations. use std::any::type_name; -use std::mem; +use std::{mem, ptr}; use js::glue::JS_GetReservedSlot; use js::jsapi::JSObject; use js::jsval::UndefinedValue; +use js::rust::GCMethods; -use crate::dom::bindings::utils::finalize_global as do_finalize_global; -use crate::dom::bindings::weakref::{DOM_WEAK_SLOT, WeakBox, WeakReferenceable}; +use crate::codegen::PrototypeList::PROTO_OR_IFACE_LENGTH; +use crate::utils::{ProtoOrIfaceArray, get_proto_or_iface_array}; +use crate::weakref::{DOM_WEAK_SLOT, WeakBox, WeakReferenceable}; -pub(crate) unsafe fn finalize_common<T>(this: *const T) { +/// Drop the resources held by reserved slots of a global object +unsafe fn do_finalize_global(obj: *mut JSObject) { + let protolist = get_proto_or_iface_array(obj); + let list = (*protolist).as_mut_ptr(); + for idx in 0..PROTO_OR_IFACE_LENGTH as isize { + let entry = list.offset(idx); + let value = *entry; + <*mut JSObject>::post_barrier(entry, value, ptr::null_mut()); + } + let _: Box<ProtoOrIfaceArray> = Box::from_raw(protolist); +} + +/// # Safety +/// `this` must point to a valid, non-null instance of T. +pub unsafe fn finalize_common<T>(this: *const T) { if !this.is_null() { // The pointer can be null if the object is the unforgeable holder of that interface. let _ = Box::from_raw(this as *mut T); @@ -22,12 +38,18 @@ pub(crate) unsafe fn finalize_common<T>(this: *const T) { debug!("{} finalize: {:p}", type_name::<T>(), this); } -pub(crate) unsafe fn finalize_global<T>(obj: *mut JSObject, this: *const T) { +/// # Safety +/// `obj` must point to a valid, non-null JS object. +/// `this` must point to a valid, non-null instance of T. +pub unsafe fn finalize_global<T>(obj: *mut JSObject, this: *const T) { do_finalize_global(obj); finalize_common::<T>(this); } -pub(crate) unsafe fn finalize_weak_referenceable<T: WeakReferenceable>( +/// # Safety +/// `obj` must point to a valid, non-null JS object. +/// `this` must point to a valid, non-null instance of T. +pub unsafe fn finalize_weak_referenceable<T: WeakReferenceable>( obj: *mut JSObject, this: *const T, ) { diff --git a/components/script_bindings/interfaces.rs b/components/script_bindings/interfaces.rs new file mode 100644 index 00000000000..2e7f6017e75 --- /dev/null +++ b/components/script_bindings/interfaces.rs @@ -0,0 +1,24 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use js::rust::HandleObject; + +use crate::script_runtime::JSContext; + +pub trait DocumentHelpers { + fn ensure_safe_to_run_script_or_layout(&self); +} + +pub trait ServoInternalsHelpers { + fn is_servo_internal(cx: JSContext, global: HandleObject) -> bool; +} + +pub trait TestBindingHelpers { + fn condition_satisfied(cx: JSContext, global: HandleObject) -> bool; + fn condition_unsatisfied(cx: JSContext, global: HandleObject) -> bool; +} + +pub trait WebGL2RenderingContextHelpers { + fn is_webgl2_enabled(cx: JSContext, global: HandleObject) -> bool; +} diff --git a/components/script_bindings/lib.rs b/components/script_bindings/lib.rs index e857a2289f8..17dc827b872 100644 --- a/components/script_bindings/lib.rs +++ b/components/script_bindings/lib.rs @@ -21,9 +21,13 @@ pub mod callback; pub mod constant; pub mod conversions; pub mod error; +pub mod finalize; pub mod inheritance; +pub mod interfaces; pub mod iterable; pub mod like; +pub mod lock; +pub mod num; pub mod record; pub mod reflector; pub mod root; diff --git a/components/script_bindings/lock.rs b/components/script_bindings/lock.rs new file mode 100644 index 00000000000..e8cde2e4087 --- /dev/null +++ b/components/script_bindings/lock.rs @@ -0,0 +1,37 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use std::sync::OnceLock; + +/// A OnceLock wrapping a type that is not considered threadsafe by the Rust compiler, but +/// will be used in a threadsafe manner (it will not be mutated, after being initialized). +/// +/// This is needed to allow using JS API types (which usually involve raw pointers) in static initializers, +/// when Servo guarantees through the use of OnceLock that only one thread will ever initialize +/// the value. +pub struct ThreadUnsafeOnceLock<T>(OnceLock<T>); + +impl<T> ThreadUnsafeOnceLock<T> { + #[allow(clippy::new_without_default)] + pub const fn new() -> Self { + Self(OnceLock::new()) + } + + /// Initialize the value inside this lock. Panics if the lock has been previously initialized. + pub fn set(&self, val: T) { + assert!(self.0.set(val).is_ok()); + } + + /// Get a reference to the value inside this lock. Panics if the lock has not been initialized. + /// + /// # Safety + /// The caller must ensure that it does not mutate value contained inside this lock + /// (using interior mutability). + pub unsafe fn get(&self) -> &T { + self.0.get().unwrap() + } +} + +unsafe impl<T> Sync for ThreadUnsafeOnceLock<T> {} +unsafe impl<T> Send for ThreadUnsafeOnceLock<T> {} diff --git a/components/script/dom/bindings/num.rs b/components/script_bindings/num.rs index 59b89a29e3e..58b12f6cb7f 100644 --- a/components/script/dom/bindings/num.rs +++ b/components/script_bindings/num.rs @@ -12,11 +12,11 @@ use num_traits::Float; /// Encapsulates the IDL restricted float type. #[derive(Clone, Copy, Eq, JSTraceable, PartialEq)] -pub(crate) struct Finite<T: Float>(T); +pub struct Finite<T: Float>(T); impl<T: Float> Finite<T> { /// Create a new `Finite<T: Float>` safely. - pub(crate) fn new(value: T) -> Option<Finite<T>> { + pub fn new(value: T) -> Option<Finite<T>> { if value.is_finite() { Some(Finite(value)) } else { @@ -26,7 +26,7 @@ impl<T: Float> Finite<T> { /// Create a new `Finite<T: Float>`. #[inline] - pub(crate) fn wrap(value: T) -> Finite<T> { + pub fn wrap(value: T) -> Finite<T> { assert!( value.is_finite(), "Finite<T> doesn't encapsulate unrestricted value." diff --git a/components/script_bindings/utils.rs b/components/script_bindings/utils.rs index fd307fd5ab3..2317f80dbfa 100644 --- a/components/script_bindings/utils.rs +++ b/components/script_bindings/utils.rs @@ -2,13 +2,40 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +use std::ffi::CString; use std::os::raw::c_void; +use std::ptr::{self, NonNull}; +use js::conversions::ToJSValConvertible; +use js::glue::{ + CallJitGetterOp, CallJitMethodOp, CallJitSetterOp, JS_GetReservedSlot, + RUST_FUNCTION_VALUE_TO_JITINFO, +}; +use js::jsapi::{ + AtomToLinearString, CallArgs, ExceptionStackBehavior, GetLinearStringCharAt, + GetLinearStringLength, GetNonCCWObjectGlobal, HandleObject as RawHandleObject, + JS_ClearPendingException, JS_IsExceptionPending, JSAtom, JSContext, JSJitInfo, JSObject, + MutableHandleValue as RawMutableHandleValue, ObjectOpResult, StringIsArrayIndex, +}; +use js::jsval::{JSVal, UndefinedValue}; +use js::rust::wrappers::{ + CallOriginalPromiseReject, JS_DeletePropertyById, JS_ForwardGetPropertyTo, + JS_GetPendingException, JS_GetProperty, JS_GetPrototype, JS_HasProperty, JS_HasPropertyById, + JS_SetPendingException, JS_SetProperty, +}; +use js::rust::{ + HandleId, HandleObject, HandleValue, MutableHandleValue, ToString, get_object_class, +}; +use js::{JS_CALLEE, rooted}; use malloc_size_of::MallocSizeOfOps; use crate::codegen::Globals::Globals; use crate::codegen::InheritTypes::TopTypeId; -use crate::codegen::PrototypeList::{self, MAX_PROTO_CHAIN_LENGTH}; +use crate::codegen::PrototypeList::{self, MAX_PROTO_CHAIN_LENGTH, PROTO_OR_IFACE_LENGTH}; +use crate::conversions::{PrototypeCheck, jsstring_to_str, private_from_proto_check}; +use crate::error::throw_invalid_this; +use crate::script_runtime::{CanGc, JSContext as SafeJSContext}; +use crate::str::DOMString; /// The struct that holds inheritance information for DOM object reflectors. #[derive(Clone, Copy)] @@ -46,3 +73,456 @@ impl Clone for DOMJSClass { } } unsafe impl Sync for DOMJSClass {} + +/// The index of the slot where the object holder of that interface's +/// unforgeable members are defined. +pub const DOM_PROTO_UNFORGEABLE_HOLDER_SLOT: u32 = 0; + +/// The index of the slot that contains a reference to the ProtoOrIfaceArray. +// All DOM globals must have a slot at DOM_PROTOTYPE_SLOT. +pub const DOM_PROTOTYPE_SLOT: u32 = js::JSCLASS_GLOBAL_SLOT_COUNT; + +/// The flag set on the `JSClass`es for DOM global objects. +// NOTE: This is baked into the Ion JIT as 0 in codegen for LGetDOMProperty and +// LSetDOMProperty. Those constants need to be changed accordingly if this value +// changes. +pub const JSCLASS_DOM_GLOBAL: u32 = js::JSCLASS_USERBIT1; + +/// Returns the ProtoOrIfaceArray for the given global object. +/// Fails if `global` is not a DOM global object. +/// +/// # Safety +/// `global` must point to a valid, non-null JS object. +pub unsafe fn get_proto_or_iface_array(global: *mut JSObject) -> *mut ProtoOrIfaceArray { + assert_ne!(((*get_object_class(global)).flags & JSCLASS_DOM_GLOBAL), 0); + let mut slot = UndefinedValue(); + JS_GetReservedSlot(global, DOM_PROTOTYPE_SLOT, &mut slot); + slot.to_private() as *mut ProtoOrIfaceArray +} + +/// An array of *mut JSObject of size PROTO_OR_IFACE_LENGTH. +pub type ProtoOrIfaceArray = [*mut JSObject; PROTO_OR_IFACE_LENGTH]; + +/// Gets the property `id` on `proxy`'s prototype. If it exists, `*found` is +/// set to true and `*vp` to the value, otherwise `*found` is set to false. +/// +/// Returns false on JSAPI failure. +/// +/// # Safety +/// `cx` must point to a valid, non-null JSContext. +/// `found` must point to a valid, non-null bool. +pub unsafe fn get_property_on_prototype( + cx: *mut JSContext, + proxy: HandleObject, + receiver: HandleValue, + id: HandleId, + found: *mut bool, + vp: MutableHandleValue, +) -> bool { + rooted!(in(cx) let mut proto = ptr::null_mut::<JSObject>()); + if !JS_GetPrototype(cx, proxy, proto.handle_mut()) || proto.is_null() { + *found = false; + return true; + } + let mut has_property = false; + if !JS_HasPropertyById(cx, proto.handle(), id, &mut has_property) { + return false; + } + *found = has_property; + if !has_property { + return true; + } + + JS_ForwardGetPropertyTo(cx, proto.handle(), id, receiver, vp) +} + +/// Get an array index from the given `jsid`. Returns `None` if the given +/// `jsid` is not an integer. +pub fn get_array_index_from_id(id: HandleId) -> Option<u32> { + let raw_id = *id; + if raw_id.is_int() { + return Some(raw_id.to_int() as u32); + } + + if raw_id.is_void() || !raw_id.is_string() { + return None; + } + + unsafe { + let atom = raw_id.to_string() as *mut JSAtom; + let s = AtomToLinearString(atom); + if GetLinearStringLength(s) == 0 { + return None; + } + + let chars = [GetLinearStringCharAt(s, 0)]; + let first_char = char::decode_utf16(chars.iter().cloned()) + .next() + .map_or('\0', |r| r.unwrap_or('\0')); + if first_char.is_ascii_lowercase() { + return None; + } + + let mut i = 0; + if StringIsArrayIndex(s, &mut i) { + Some(i) + } else { + None + } + } + + /*let s = jsstr_to_string(cx, RUST_JSID_TO_STRING(raw_id)); + if s.len() == 0 { + return None; + } + + let first = s.chars().next().unwrap(); + if first.is_ascii_lowercase() { + return None; + } + + let mut i: u32 = 0; + let is_array = if s.is_ascii() { + let chars = s.as_bytes(); + StringIsArrayIndex1(chars.as_ptr() as *const _, chars.len() as u32, &mut i) + } else { + let chars = s.encode_utf16().collect::<Vec<u16>>(); + let slice = chars.as_slice(); + StringIsArrayIndex2(slice.as_ptr(), chars.len() as u32, &mut i) + }; + + if is_array { + Some(i) + } else { + None + }*/ +} + +/// Find the enum equivelent of a string given by `v` in `pairs`. +/// Returns `Err(())` on JSAPI failure (there is a pending exception), and +/// `Ok((None, value))` if there was no matching string. +/// +/// # Safety +/// `cx` must point to a valid, non-null JSContext. +#[allow(clippy::result_unit_err)] +pub unsafe fn find_enum_value<'a, T>( + cx: *mut JSContext, + v: HandleValue, + pairs: &'a [(&'static str, T)], +) -> Result<(Option<&'a T>, DOMString), ()> { + match ptr::NonNull::new(ToString(cx, v)) { + Some(jsstr) => { + let search = jsstring_to_str(cx, jsstr); + Ok(( + pairs + .iter() + .find(|&&(key, _)| search == *key) + .map(|(_, ev)| ev), + search, + )) + }, + None => Err(()), + } +} + +/// Get the property with name `property` from `object`. +/// Returns `Err(())` on JSAPI failure (there is a pending exception), and +/// `Ok(false)` if there was no property with the given name. +/// +/// # Safety +/// `cx` must point to a valid, non-null JSContext. +#[allow(clippy::result_unit_err)] +pub unsafe fn get_dictionary_property( + cx: *mut JSContext, + object: HandleObject, + property: &str, + rval: MutableHandleValue, + _can_gc: CanGc, +) -> Result<bool, ()> { + unsafe fn has_property( + cx: *mut JSContext, + object: HandleObject, + property: &CString, + found: &mut bool, + ) -> bool { + JS_HasProperty(cx, object, property.as_ptr(), found) + } + unsafe fn get_property( + cx: *mut JSContext, + object: HandleObject, + property: &CString, + value: MutableHandleValue, + ) -> bool { + JS_GetProperty(cx, object, property.as_ptr(), value) + } + + let property = CString::new(property).unwrap(); + if object.get().is_null() { + return Ok(false); + } + + let mut found = false; + if !has_property(cx, object, &property, &mut found) { + return Err(()); + } + + if !found { + return Ok(false); + } + + if !get_property(cx, object, &property, rval) { + return Err(()); + } + + Ok(true) +} + +/// Set the property with name `property` from `object`. +/// Returns `Err(())` on JSAPI failure, or null object, +/// and Ok(()) otherwise +/// +/// # Safety +/// `cx` must point to a valid, non-null JSContext. +#[allow(clippy::result_unit_err)] +pub unsafe fn set_dictionary_property( + cx: *mut JSContext, + object: HandleObject, + property: &str, + value: HandleValue, +) -> Result<(), ()> { + if object.get().is_null() { + return Err(()); + } + + let property = CString::new(property).unwrap(); + if !JS_SetProperty(cx, object, property.as_ptr(), value) { + return Err(()); + } + + Ok(()) +} + +/// Returns whether `proxy` has a property `id` on its prototype. +/// +/// # Safety +/// `cx` must point to a valid, non-null JSContext. +pub unsafe fn has_property_on_prototype( + cx: *mut JSContext, + proxy: HandleObject, + id: HandleId, + found: &mut bool, +) -> bool { + rooted!(in(cx) let mut proto = ptr::null_mut::<JSObject>()); + if !JS_GetPrototype(cx, proxy, proto.handle_mut()) { + return false; + } + assert!(!proto.is_null()); + JS_HasPropertyById(cx, proto.handle(), id, found) +} + +/// Deletes the property `id` from `object`. +/// +/// # Safety +/// `cx` must point to a valid, non-null JSContext. +pub unsafe fn delete_property_by_id( + cx: *mut JSContext, + object: HandleObject, + id: HandleId, + bp: *mut ObjectOpResult, +) -> bool { + JS_DeletePropertyById(cx, object, id, bp) +} + +unsafe fn generic_call<const EXCEPTION_TO_REJECTION: bool>( + cx: *mut JSContext, + argc: libc::c_uint, + vp: *mut JSVal, + is_lenient: bool, + call: unsafe extern "C" fn( + *const JSJitInfo, + *mut JSContext, + RawHandleObject, + *mut libc::c_void, + u32, + *mut JSVal, + ) -> bool, + can_gc: CanGc, +) -> bool { + let args = CallArgs::from_vp(vp, argc); + + let info = RUST_FUNCTION_VALUE_TO_JITINFO(JS_CALLEE(cx, vp)); + let proto_id = (*info).__bindgen_anon_2.protoID; + let cx = SafeJSContext::from_ptr(cx); + + let thisobj = args.thisv(); + if !thisobj.get().is_null_or_undefined() && !thisobj.get().is_object() { + throw_invalid_this(cx, proto_id); + return if EXCEPTION_TO_REJECTION { + exception_to_promise(*cx, args.rval(), can_gc) + } else { + false + }; + } + + rooted!(in(*cx) let obj = if thisobj.get().is_object() { + thisobj.get().to_object() + } else { + GetNonCCWObjectGlobal(JS_CALLEE(*cx, vp).to_object_or_null()) + }); + let depth = (*info).__bindgen_anon_3.depth as usize; + let proto_check = PrototypeCheck::Depth { depth, proto_id }; + let this = match private_from_proto_check(obj.get(), *cx, proto_check) { + Ok(val) => val, + Err(()) => { + if is_lenient { + debug_assert!(!JS_IsExceptionPending(*cx)); + *vp = UndefinedValue(); + return true; + } else { + throw_invalid_this(cx, proto_id); + return if EXCEPTION_TO_REJECTION { + exception_to_promise(*cx, args.rval(), can_gc) + } else { + false + }; + } + }, + }; + call( + info, + *cx, + obj.handle().into(), + this as *mut libc::c_void, + argc, + vp, + ) +} + +/// Generic method of IDL interface. +/// +/// # Safety +/// `cx` must point to a valid, non-null JSContext. +/// `vp` must point to a VALID, non-null JSVal. +pub unsafe extern "C" fn generic_method<const EXCEPTION_TO_REJECTION: bool>( + cx: *mut JSContext, + argc: libc::c_uint, + vp: *mut JSVal, +) -> bool { + generic_call::<EXCEPTION_TO_REJECTION>(cx, argc, vp, false, CallJitMethodOp, CanGc::note()) +} + +/// Generic getter of IDL interface. +/// +/// # Safety +/// `cx` must point to a valid, non-null JSContext. +/// `vp` must point to a VALID, non-null JSVal. +pub unsafe extern "C" fn generic_getter<const EXCEPTION_TO_REJECTION: bool>( + cx: *mut JSContext, + argc: libc::c_uint, + vp: *mut JSVal, +) -> bool { + generic_call::<EXCEPTION_TO_REJECTION>(cx, argc, vp, false, CallJitGetterOp, CanGc::note()) +} + +/// Generic lenient getter of IDL interface. +/// +/// # Safety +/// `cx` must point to a valid, non-null JSContext. +/// `vp` must point to a VALID, non-null JSVal. +pub unsafe extern "C" fn generic_lenient_getter<const EXCEPTION_TO_REJECTION: bool>( + cx: *mut JSContext, + argc: libc::c_uint, + vp: *mut JSVal, +) -> bool { + generic_call::<EXCEPTION_TO_REJECTION>(cx, argc, vp, true, CallJitGetterOp, CanGc::note()) +} + +unsafe extern "C" fn call_setter( + info: *const JSJitInfo, + cx: *mut JSContext, + handle: RawHandleObject, + this: *mut libc::c_void, + argc: u32, + vp: *mut JSVal, +) -> bool { + if !CallJitSetterOp(info, cx, handle, this, argc, vp) { + return false; + } + *vp = UndefinedValue(); + true +} + +/// Generic setter of IDL interface. +/// +/// # Safety +/// `cx` must point to a valid, non-null JSContext. +/// `vp` must point to a VALID, non-null JSVal. +pub unsafe extern "C" fn generic_setter( + cx: *mut JSContext, + argc: libc::c_uint, + vp: *mut JSVal, +) -> bool { + generic_call::<false>(cx, argc, vp, false, call_setter, CanGc::note()) +} + +/// Generic lenient setter of IDL interface. +/// +/// # Safety +/// `cx` must point to a valid, non-null JSContext. +/// `vp` must point to a VALID, non-null JSVal. +pub unsafe extern "C" fn generic_lenient_setter( + cx: *mut JSContext, + argc: libc::c_uint, + vp: *mut JSVal, +) -> bool { + generic_call::<false>(cx, argc, vp, true, call_setter, CanGc::note()) +} + +/// <https://searchfox.org/mozilla-central/rev/7279a1df13a819be254fd4649e07c4ff93e4bd45/dom/bindings/BindingUtils.cpp#3300> +/// # Safety +/// +/// `cx` must point to a valid, non-null JSContext. +/// `vp` must point to a VALID, non-null JSVal. +pub unsafe extern "C" fn generic_static_promise_method( + cx: *mut JSContext, + argc: libc::c_uint, + vp: *mut JSVal, +) -> bool { + let args = CallArgs::from_vp(vp, argc); + + let info = RUST_FUNCTION_VALUE_TO_JITINFO(JS_CALLEE(cx, vp)); + assert!(!info.is_null()); + // TODO: we need safe wrappers for this in mozjs! + //assert_eq!((*info)._bitfield_1, JSJitInfo_OpType::StaticMethod as u8) + let static_fn = (*info).__bindgen_anon_1.staticMethod.unwrap(); + if static_fn(cx, argc, vp) { + return true; + } + exception_to_promise(cx, args.rval(), CanGc::note()) +} + +/// Coverts exception to promise rejection +/// +/// <https://searchfox.org/mozilla-central/rev/b220e40ff2ee3d10ce68e07d8a8a577d5558e2a2/dom/bindings/BindingUtils.cpp#3315> +/// +/// # Safety +/// `cx` must point to a valid, non-null JSContext. +pub unsafe fn exception_to_promise( + cx: *mut JSContext, + rval: RawMutableHandleValue, + _can_gc: CanGc, +) -> bool { + rooted!(in(cx) let mut exception = UndefinedValue()); + if !JS_GetPendingException(cx, exception.handle_mut()) { + return false; + } + JS_ClearPendingException(cx); + if let Some(promise) = NonNull::new(CallOriginalPromiseReject(cx, exception.handle())) { + promise.to_jsval(cx, MutableHandleValue::from_raw(rval)); + true + } else { + // We just give up. Put the exception back. + JS_SetPendingException(cx, exception.handle(), ExceptionStackBehavior::Capture); + false + } +} diff --git a/components/script_bindings/webidls/ServoInternals.webidl b/components/script_bindings/webidls/ServoInternals.webidl index af3dc7b35e6..609d49180e4 100644 --- a/components/script_bindings/webidls/ServoInternals.webidl +++ b/components/script_bindings/webidls/ServoInternals.webidl @@ -8,12 +8,12 @@ // This interface is entirely internal to Servo, and should not be accessible to // web pages. [Exposed=Window, -Func="dom::bindings::interface::is_servo_internal"] +Func="ServoInternals::is_servo_internal"] interface ServoInternals { Promise<object> reportMemory(); }; partial interface Navigator { - [Func="dom::bindings::interface::is_servo_internal"] + [Func="ServoInternals::is_servo_internal"] readonly attribute ServoInternals servo; }; |