Skip to main content

script/
window_named_properties.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5use std::ptr;
6use std::ptr::NonNull;
7use std::sync::LazyLock;
8
9use js::conversions::jsstr_to_string;
10use js::glue::{AppendToIdVector, CreateProxyHandler, ProxyTraps};
11use js::jsapi::{
12    Handle, HandleId, HandleObject, JSCLASS_DELAY_METADATA_BUILDER, JSCLASS_IS_PROXY,
13    JSCLASS_RESERVED_SLOTS_MASK, JSCLASS_RESERVED_SLOTS_SHIFT, JSClass, JSClass_NON_NATIVE,
14    JSContext, JSErrNum, JSPROP_READONLY, MutableHandle, MutableHandleIdVector,
15    MutableHandleObject, ObjectOpResult, PropertyDescriptor, ProxyClassExtension, ProxyClassOps,
16    ProxyObjectOps, SymbolCode, UndefinedHandleValue,
17};
18use js::jsid::SymbolId;
19use js::jsval::UndefinedValue;
20use js::rust::wrappers2::{GetWellKnownSymbol, JS_SetImmutablePrototype, NewProxyObject};
21use js::rust::{
22    Handle as RustHandle, HandleObject as RustHandleObject, MutableHandle as RustMutableHandle,
23    MutableHandleObject as RustMutableHandleObject,
24};
25use script_bindings::proxyhandler::set_property_descriptor;
26
27use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
28use crate::dom::bindings::root::Root;
29use crate::dom::bindings::utils::has_property_on_prototype;
30use crate::dom::globalscope::GlobalScope;
31use crate::dom::window::Window;
32use crate::js::conversions::ToJSValConvertible;
33
34struct SyncWrapper(*const libc::c_void);
35#[expect(unsafe_code)]
36unsafe impl Sync for SyncWrapper {}
37#[expect(unsafe_code)]
38unsafe impl Send for SyncWrapper {}
39
40static HANDLER: LazyLock<SyncWrapper> = LazyLock::new(|| {
41    let traps = ProxyTraps {
42        enter: None,
43        getOwnPropertyDescriptor: Some(get_own_property_descriptor),
44        defineProperty: Some(define_property),
45        ownPropertyKeys: Some(own_property_keys),
46        delete_: Some(delete),
47        enumerate: None,
48        getPrototypeIfOrdinary: Some(get_prototype_if_ordinary),
49        getPrototype: None,
50        setPrototype: None,
51        setImmutablePrototype: None,
52        preventExtensions: Some(prevent_extensions),
53        isExtensible: Some(is_extensible),
54        has: None,
55        get: None,
56        set: None,
57        call: None,
58        construct: None,
59        hasOwn: None,
60        getOwnEnumerablePropertyKeys: None,
61        nativeCall: None,
62        objectClassIs: None,
63        className: Some(class_name),
64        fun_toString: None,
65        boxedValue_unbox: None,
66        defaultValue: None,
67        trace: None,
68        finalize: None,
69        objectMoved: None,
70        isCallable: None,
71        isConstructor: None,
72    };
73
74    #[expect(unsafe_code)]
75    unsafe {
76        SyncWrapper(CreateProxyHandler(&traps, ptr::null()))
77    }
78});
79
80#[expect(unsafe_code)]
81unsafe extern "C" fn get_own_property_descriptor(
82    cx: *mut JSContext,
83    proxy: HandleObject,
84    id: HandleId,
85    desc: MutableHandle<PropertyDescriptor>,
86    is_none: *mut bool,
87) -> bool {
88    // SAFETY: it is safe to construct a JSContext from an engine callback.
89    let mut cx = unsafe { js::context::JSContext::from_ptr(ptr::NonNull::new(cx).unwrap()) };
90
91    if id.is_symbol() {
92        if id.get().asBits_ ==
93            SymbolId(unsafe { GetWellKnownSymbol(&cx, SymbolCode::toStringTag) }).asBits_
94        {
95            rooted!(&in(cx) let mut rval = UndefinedValue());
96            "WindowProperties".safe_to_jsval(&mut cx, rval.handle_mut());
97            set_property_descriptor(
98                unsafe { RustMutableHandle::from_raw(desc) },
99                rval.handle(),
100                JSPROP_READONLY.into(),
101                unsafe { &mut *is_none },
102            );
103        }
104        return true;
105    }
106
107    let mut found = false;
108    let lookup_succeeded = has_property_on_prototype(
109        &mut cx,
110        unsafe { RustHandle::from_raw(proxy) },
111        unsafe { RustHandle::from_raw(id) },
112        &mut found,
113    );
114    if !lookup_succeeded {
115        return false;
116    }
117    if found {
118        return true;
119    }
120
121    let s = if id.is_string() {
122        unsafe { jsstr_to_string(&cx, NonNull::new(id.to_string()).unwrap()) }
123    } else if id.is_int() {
124        // If the property key is an integer index, convert it to a String too.
125        // For indexed access on the window object, which may shadow this, see
126        // the getOwnPropertyDescriptor trap in dom/windowproxy.rs.
127        id.to_int().to_string()
128    } else if id.is_symbol() {
129        // Symbol properties were already handled above.
130        unreachable!()
131    } else {
132        unimplemented!()
133    };
134    if s.is_empty() {
135        return true;
136    }
137
138    let window = Root::downcast::<Window>(unsafe { GlobalScope::from_object(proxy.get()) })
139        .expect("global is not a window");
140    if let Some(obj) = window.NamedGetter(&mut cx, s.into()) {
141        rooted!(&in(cx) let mut rval = UndefinedValue());
142        obj.safe_to_jsval(&mut cx, rval.handle_mut());
143        set_property_descriptor(
144            unsafe { RustMutableHandle::from_raw(desc) },
145            rval.handle(),
146            0,
147            unsafe { &mut *is_none },
148        );
149    }
150    true
151}
152
153#[expect(unsafe_code)]
154unsafe extern "C" fn own_property_keys(
155    cx: *mut JSContext,
156    _proxy: HandleObject,
157    props: MutableHandleIdVector,
158) -> bool {
159    // TODO is this all we need to return? compare with gecko:
160    // https://searchfox.org/mozilla-central/rev/af78418c4b5f2c8721d1a06486cf4cf0b33e1e8d/dom/base/WindowNamedPropertiesHandler.cpp#175-232
161    // see also https://github.com/whatwg/html/issues/9068
162    unsafe {
163        rooted!(in(cx) let mut rooted = SymbolId(js::jsapi::GetWellKnownSymbol(cx, SymbolCode::toStringTag)));
164        AppendToIdVector(props, rooted.handle().into());
165    }
166    true
167}
168
169#[expect(unsafe_code)]
170unsafe extern "C" fn define_property(
171    _cx: *mut JSContext,
172    _proxy: HandleObject,
173    _id: HandleId,
174    _desc: Handle<PropertyDescriptor>,
175    result: *mut ObjectOpResult,
176) -> bool {
177    unsafe {
178        (*result).code_ = JSErrNum::JSMSG_CANT_DEFINE_WINDOW_NAMED_PROPERTY as usize;
179    }
180    true
181}
182
183#[expect(unsafe_code)]
184unsafe extern "C" fn delete(
185    _cx: *mut JSContext,
186    _proxy: HandleObject,
187    _id: HandleId,
188    result: *mut ObjectOpResult,
189) -> bool {
190    unsafe {
191        (*result).code_ = JSErrNum::JSMSG_CANT_DELETE_WINDOW_NAMED_PROPERTY as usize;
192    }
193    true
194}
195
196#[expect(unsafe_code)]
197unsafe extern "C" fn get_prototype_if_ordinary(
198    _cx: *mut JSContext,
199    proxy: HandleObject,
200    is_ordinary: *mut bool,
201    proto: MutableHandleObject,
202) -> bool {
203    unsafe {
204        *is_ordinary = true;
205        proto.set(js::jsapi::GetStaticPrototype(proxy.get()));
206    }
207    true
208}
209
210#[expect(unsafe_code)]
211unsafe extern "C" fn prevent_extensions(
212    _cx: *mut JSContext,
213    _proxy: HandleObject,
214    result: *mut ObjectOpResult,
215) -> bool {
216    unsafe {
217        (*result).code_ = JSErrNum::JSMSG_CANT_PREVENT_EXTENSIONS as usize;
218    }
219    true
220}
221
222#[expect(unsafe_code)]
223unsafe extern "C" fn is_extensible(
224    _cx: *mut JSContext,
225    _proxy: HandleObject,
226    extensible: *mut bool,
227) -> bool {
228    unsafe {
229        *extensible = true;
230    }
231    true
232}
233
234#[expect(unsafe_code)]
235unsafe extern "C" fn class_name(_cx: *mut JSContext, _proxy: HandleObject) -> *const libc::c_char {
236    c"WindowProperties".as_ptr()
237}
238
239// Maybe this should be a DOMJSClass. See https://bugzilla.mozilla.org/show_bug.cgi?id=787070
240#[expect(unsafe_code)]
241static CLASS: JSClass = JSClass {
242    name: c"WindowProperties".as_ptr(),
243    flags: JSClass_NON_NATIVE |
244        JSCLASS_IS_PROXY |
245        JSCLASS_DELAY_METADATA_BUILDER |
246        ((1 & JSCLASS_RESERVED_SLOTS_MASK) << JSCLASS_RESERVED_SLOTS_SHIFT), /* JSCLASS_HAS_RESERVED_SLOTS(1) */
247    cOps: unsafe { &ProxyClassOps },
248    spec: ptr::null(),
249    ext: unsafe { &ProxyClassExtension },
250    oOps: unsafe { &ProxyObjectOps },
251};
252
253#[expect(unsafe_code)]
254pub(crate) fn create(
255    cx: &mut js::context::JSContext,
256    proto: RustHandleObject,
257    mut properties_obj: RustMutableHandleObject,
258) {
259    unsafe {
260        properties_obj.set(NewProxyObject(
261            cx,
262            HANDLER.0,
263            RustHandle::from_raw(UndefinedHandleValue),
264            proto.get(),
265            &CLASS,
266            false,
267        ));
268    }
269    assert!(!properties_obj.get().is_null());
270    let mut succeeded = false;
271    unsafe {
272        assert!(JS_SetImmutablePrototype(
273            cx,
274            properties_obj.handle(),
275            &mut succeeded
276        ));
277    }
278    assert!(succeeded);
279}