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