1use 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 Handle, HandleId, HandleObject, JS_SetImmutablePrototype, JSCLASS_DELAY_METADATA_BUILDER,
13 JSCLASS_IS_PROXY, JSCLASS_RESERVED_SLOTS_MASK, JSCLASS_RESERVED_SLOTS_SHIFT, JSClass,
14 JSClass_NON_NATIVE, 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;
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#[expect(unsafe_code)]
37unsafe impl Sync for SyncWrapper {}
38#[expect(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 #[expect(unsafe_code)]
76 unsafe {
77 SyncWrapper(CreateProxyHandler(&traps, ptr::null()))
78 }
79});
80
81#[expect(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 mut cx = unsafe { js::context::JSContext::from_ptr(ptr::NonNull::new(cx).unwrap()) };
91
92 if id.is_symbol() {
93 if id.get().asBits_ ==
94 SymbolId(unsafe { GetWellKnownSymbol(&cx, SymbolCode::toStringTag) }).asBits_
95 {
96 rooted!(&in(cx) let mut rval = UndefinedValue());
97 "WindowProperties".safe_to_jsval(&mut cx, rval.handle_mut());
98 set_property_descriptor(
99 unsafe { RustMutableHandle::from_raw(desc) },
100 rval.handle(),
101 JSPROP_READONLY.into(),
102 unsafe { &mut *is_none },
103 );
104 }
105 return true;
106 }
107
108 let mut found = false;
109 let lookup_succeeded = unsafe {
110 has_property_on_prototype(
111 cx.raw_cx(),
112 RustHandle::from_raw(proxy),
113 RustHandle::from_raw(id),
114 &mut found,
115 )
116 };
117 if !lookup_succeeded {
118 return false;
119 }
120 if found {
121 return true;
122 }
123
124 let s = if id.is_string() {
125 unsafe { jsstr_to_string(cx.raw_cx_no_gc(), NonNull::new(id.to_string()).unwrap()) }
126 } else if id.is_int() {
127 id.to_int().to_string()
131 } else if id.is_symbol() {
132 unreachable!()
134 } else {
135 unimplemented!()
136 };
137 if s.is_empty() {
138 return true;
139 }
140
141 let window = Root::downcast::<Window>(unsafe { GlobalScope::from_object(proxy.get()) })
142 .expect("global is not a window");
143 if let Some(obj) = window.NamedGetter(&mut cx, s.into()) {
144 rooted!(&in(cx) let mut rval = UndefinedValue());
145 obj.safe_to_jsval(&mut cx, rval.handle_mut());
146 set_property_descriptor(
147 unsafe { RustMutableHandle::from_raw(desc) },
148 rval.handle(),
149 0,
150 unsafe { &mut *is_none },
151 );
152 }
153 true
154}
155
156#[expect(unsafe_code)]
157unsafe extern "C" fn own_property_keys(
158 cx: *mut JSContext,
159 _proxy: HandleObject,
160 props: MutableHandleIdVector,
161) -> bool {
162 unsafe {
166 rooted!(in(cx) let mut rooted = SymbolId(js::jsapi::GetWellKnownSymbol(cx, SymbolCode::toStringTag)));
167 AppendToIdVector(props, rooted.handle().into());
168 }
169 true
170}
171
172#[expect(unsafe_code)]
173unsafe extern "C" fn define_property(
174 _cx: *mut JSContext,
175 _proxy: HandleObject,
176 _id: HandleId,
177 _desc: Handle<PropertyDescriptor>,
178 result: *mut ObjectOpResult,
179) -> bool {
180 unsafe {
181 (*result).code_ = JSErrNum::JSMSG_CANT_DEFINE_WINDOW_NAMED_PROPERTY as usize;
182 }
183 true
184}
185
186#[expect(unsafe_code)]
187unsafe extern "C" fn delete(
188 _cx: *mut JSContext,
189 _proxy: HandleObject,
190 _id: HandleId,
191 result: *mut ObjectOpResult,
192) -> bool {
193 unsafe {
194 (*result).code_ = JSErrNum::JSMSG_CANT_DELETE_WINDOW_NAMED_PROPERTY as usize;
195 }
196 true
197}
198
199#[expect(unsafe_code)]
200unsafe extern "C" fn get_prototype_if_ordinary(
201 _cx: *mut JSContext,
202 proxy: HandleObject,
203 is_ordinary: *mut bool,
204 proto: MutableHandleObject,
205) -> bool {
206 unsafe {
207 *is_ordinary = true;
208 proto.set(js::jsapi::GetStaticPrototype(proxy.get()));
209 }
210 true
211}
212
213#[expect(unsafe_code)]
214unsafe extern "C" fn prevent_extensions(
215 _cx: *mut JSContext,
216 _proxy: HandleObject,
217 result: *mut ObjectOpResult,
218) -> bool {
219 unsafe {
220 (*result).code_ = JSErrNum::JSMSG_CANT_PREVENT_EXTENSIONS as usize;
221 }
222 true
223}
224
225#[expect(unsafe_code)]
226unsafe extern "C" fn is_extensible(
227 _cx: *mut JSContext,
228 _proxy: HandleObject,
229 extensible: *mut bool,
230) -> bool {
231 unsafe {
232 *extensible = true;
233 }
234 true
235}
236
237#[expect(unsafe_code)]
238unsafe extern "C" fn class_name(_cx: *mut JSContext, _proxy: HandleObject) -> *const libc::c_char {
239 c"WindowProperties".as_ptr()
240}
241
242#[expect(unsafe_code)]
244static CLASS: JSClass = JSClass {
245 name: c"WindowProperties".as_ptr(),
246 flags: JSClass_NON_NATIVE |
247 JSCLASS_IS_PROXY |
248 JSCLASS_DELAY_METADATA_BUILDER |
249 ((1 & JSCLASS_RESERVED_SLOTS_MASK) << JSCLASS_RESERVED_SLOTS_SHIFT), cOps: unsafe { &ProxyClassOps },
251 spec: ptr::null(),
252 ext: unsafe { &ProxyClassExtension },
253 oOps: unsafe { &ProxyObjectOps },
254};
255
256#[expect(unsafe_code)]
257pub(crate) fn create(
258 cx: SafeJSContext,
259 proto: RustHandleObject,
260 mut properties_obj: RustMutableHandleObject,
261) {
262 unsafe {
263 properties_obj.set(NewProxyObject(
264 *cx,
265 HANDLER.0,
266 UndefinedHandleValue,
267 proto.get(),
268 &CLASS,
269 false,
270 ));
271 assert!(!properties_obj.get().is_null());
272 let mut succeeded = false;
273 assert!(JS_SetImmutablePrototype(
274 *cx,
275 properties_obj.handle().into_handle(),
276 &mut succeeded
277 ));
278 assert!(succeeded);
279 }
280}