1use std::ptr;
6use std::sync::LazyLock;
7
8use js::glue::{AppendToIdVector, CreateProxyHandler, ProxyTraps};
9use js::jsapi::{
10 Handle, HandleId, HandleObject, JSCLASS_DELAY_METADATA_BUILDER, JSCLASS_IS_PROXY,
11 JSCLASS_RESERVED_SLOTS_MASK, JSCLASS_RESERVED_SLOTS_SHIFT, JSClass, JSClass_NON_NATIVE,
12 JSContext, JSErrNum, JSPROP_READONLY, MutableHandle, MutableHandleIdVector,
13 MutableHandleObject, ObjectOpResult, PropertyDescriptor, ProxyClassExtension, ProxyClassOps,
14 ProxyObjectOps, SymbolCode, UndefinedHandleValue,
15};
16use js::jsid::SymbolId;
17use js::jsval::UndefinedValue;
18use js::rust::wrappers2::{GetWellKnownSymbol, JS_SetImmutablePrototype, NewProxyObject};
19use js::rust::{
20 Handle as RustHandle, HandleObject as RustHandleObject, MutableHandle as RustMutableHandle,
21 MutableHandleObject as RustMutableHandleObject,
22};
23use script_bindings::proxyhandler::set_property_descriptor;
24use script_bindings::str::DOMString;
25
26use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
27use crate::dom::bindings::root::Root;
28use crate::dom::bindings::utils::has_property_on_prototype;
29use crate::dom::globalscope::GlobalScope;
30use crate::dom::window::Window;
31use crate::js::conversions::ToJSValConvertible;
32
33struct SyncWrapper(*const libc::c_void);
34#[expect(unsafe_code)]
35unsafe impl Sync for SyncWrapper {}
36#[expect(unsafe_code)]
37unsafe impl Send for SyncWrapper {}
38
39static HANDLER: LazyLock<SyncWrapper> = LazyLock::new(|| {
40 let traps = ProxyTraps {
41 enter: None,
42 getOwnPropertyDescriptor: Some(get_own_property_descriptor),
43 defineProperty: Some(define_property),
44 ownPropertyKeys: Some(own_property_keys),
45 delete_: Some(delete),
46 enumerate: None,
47 getPrototypeIfOrdinary: Some(get_prototype_if_ordinary),
48 getPrototype: None,
49 setPrototype: None,
50 setImmutablePrototype: None,
51 preventExtensions: Some(prevent_extensions),
52 isExtensible: Some(is_extensible),
53 has: None,
54 get: None,
55 set: None,
56 call: None,
57 construct: None,
58 hasOwn: None,
59 getOwnEnumerablePropertyKeys: None,
60 nativeCall: None,
61 objectClassIs: None,
62 className: Some(class_name),
63 fun_toString: None,
64 boxedValue_unbox: None,
65 defaultValue: None,
66 trace: None,
67 finalize: None,
68 objectMoved: None,
69 isCallable: None,
70 isConstructor: None,
71 };
72
73 #[expect(unsafe_code)]
74 unsafe {
75 SyncWrapper(CreateProxyHandler(&traps, ptr::null()))
76 }
77});
78
79#[expect(unsafe_code)]
80unsafe extern "C" fn get_own_property_descriptor(
81 cx: *mut JSContext,
82 proxy: HandleObject,
83 id: HandleId,
84 desc: MutableHandle<PropertyDescriptor>,
85 is_none: *mut bool,
86) -> bool {
87 let mut cx = unsafe { js::context::JSContext::from_ptr(ptr::NonNull::new(cx).unwrap()) };
89
90 if id.is_symbol() {
91 if id.get().asBits_ ==
92 SymbolId(unsafe { GetWellKnownSymbol(&cx, SymbolCode::toStringTag) }).asBits_
93 {
94 rooted!(&in(cx) let mut rval = UndefinedValue());
95 "WindowProperties".to_jsval(&mut cx, rval.handle_mut());
96 set_property_descriptor(
97 unsafe { RustMutableHandle::from_raw(desc) },
98 rval.handle(),
99 JSPROP_READONLY.into(),
100 unsafe { &mut *is_none },
101 );
102 }
103 return true;
104 }
105
106 let mut found = false;
107 let lookup_succeeded = has_property_on_prototype(
108 &mut cx,
109 unsafe { RustHandle::from_raw(proxy) },
110 unsafe { RustHandle::from_raw(id) },
111 &mut found,
112 );
113 if !lookup_succeeded {
114 return false;
115 }
116 if found {
117 return true;
118 }
119
120 let s = if id.is_string() {
121 unsafe {
122 DOMString::from_raw_js_string(&cx, id.to_string())
123 .expect("Should always be able to convert because id is string")
124 }
125 } else if id.is_int() {
126 DOMString::from(id.to_int().to_string())
130 } else if id.is_symbol() {
131 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(&mut cx, s) {
143 rooted!(&in(cx) let mut rval = UndefinedValue());
144 obj.to_jsval(&mut cx, rval.handle_mut());
145 set_property_descriptor(
146 unsafe { RustMutableHandle::from_raw(desc) },
147 rval.handle(),
148 0,
149 unsafe { &mut *is_none },
150 );
151 }
152 true
153}
154
155#[expect(unsafe_code)]
156unsafe extern "C" fn own_property_keys(
157 cx: *mut JSContext,
158 _proxy: HandleObject,
159 props: MutableHandleIdVector,
160) -> bool {
161 unsafe {
165 rooted!(in(cx) let mut rooted = SymbolId(js::jsapi::GetWellKnownSymbol(cx, SymbolCode::toStringTag)));
166 AppendToIdVector(props, rooted.handle().into());
167 }
168 true
169}
170
171#[expect(unsafe_code)]
172unsafe extern "C" fn define_property(
173 _cx: *mut JSContext,
174 _proxy: HandleObject,
175 _id: HandleId,
176 _desc: Handle<PropertyDescriptor>,
177 result: *mut ObjectOpResult,
178) -> bool {
179 unsafe {
180 (*result).code_ = JSErrNum::JSMSG_CANT_DEFINE_WINDOW_NAMED_PROPERTY as usize;
181 }
182 true
183}
184
185#[expect(unsafe_code)]
186unsafe extern "C" fn delete(
187 _cx: *mut JSContext,
188 _proxy: HandleObject,
189 _id: HandleId,
190 result: *mut ObjectOpResult,
191) -> bool {
192 unsafe {
193 (*result).code_ = JSErrNum::JSMSG_CANT_DELETE_WINDOW_NAMED_PROPERTY as usize;
194 }
195 true
196}
197
198#[expect(unsafe_code)]
199unsafe extern "C" fn get_prototype_if_ordinary(
200 _cx: *mut JSContext,
201 proxy: HandleObject,
202 is_ordinary: *mut bool,
203 proto: MutableHandleObject,
204) -> bool {
205 unsafe {
206 *is_ordinary = true;
207 proto.set(js::jsapi::GetStaticPrototype(proxy.get()));
208 }
209 true
210}
211
212#[expect(unsafe_code)]
213unsafe extern "C" fn prevent_extensions(
214 _cx: *mut JSContext,
215 _proxy: HandleObject,
216 result: *mut ObjectOpResult,
217) -> bool {
218 unsafe {
219 (*result).code_ = JSErrNum::JSMSG_CANT_PREVENT_EXTENSIONS as usize;
220 }
221 true
222}
223
224#[expect(unsafe_code)]
225unsafe extern "C" fn is_extensible(
226 _cx: *mut JSContext,
227 _proxy: HandleObject,
228 extensible: *mut bool,
229) -> bool {
230 unsafe {
231 *extensible = true;
232 }
233 true
234}
235
236#[expect(unsafe_code)]
237unsafe extern "C" fn class_name(_cx: *mut JSContext, _proxy: HandleObject) -> *const libc::c_char {
238 c"WindowProperties".as_ptr()
239}
240
241#[expect(unsafe_code)]
243static CLASS: JSClass = JSClass {
244 name: c"WindowProperties".as_ptr(),
245 flags: JSClass_NON_NATIVE |
246 JSCLASS_IS_PROXY |
247 JSCLASS_DELAY_METADATA_BUILDER |
248 ((1 & JSCLASS_RESERVED_SLOTS_MASK) << JSCLASS_RESERVED_SLOTS_SHIFT), cOps: unsafe { &ProxyClassOps },
250 spec: ptr::null(),
251 ext: unsafe { &ProxyClassExtension },
252 oOps: unsafe { &ProxyObjectOps },
253};
254
255#[expect(unsafe_code)]
256pub(crate) fn create(
257 cx: &mut js::context::JSContext,
258 proto: RustHandleObject,
259 mut properties_obj: RustMutableHandleObject,
260) {
261 unsafe {
262 properties_obj.set(NewProxyObject(
263 cx,
264 HANDLER.0,
265 RustHandle::from_raw(UndefinedHandleValue),
266 proto.get(),
267 &CLASS,
268 false,
269 ));
270 }
271 assert!(!properties_obj.get().is_null());
272 let mut succeeded = false;
273 unsafe {
274 assert!(JS_SetImmutablePrototype(
275 cx,
276 properties_obj.handle(),
277 &mut succeeded
278 ));
279 }
280 assert!(succeeded);
281}