1use 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 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 id.to_int().to_string()
128 } else if id.is_symbol() {
129 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 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#[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), 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}