1use std::cell::Cell;
6use std::ptr;
7use std::rc::Rc;
8
9use base::generic_channel;
10use base::generic_channel::GenericSend;
11use base::id::{BrowsingContextId, PipelineId, WebViewId};
12use constellation_traits::{
13 AuxiliaryWebViewCreationRequest, LoadData, LoadOrigin, NavigationHistoryBehavior,
14 ScriptToConstellationMessage,
15};
16use content_security_policy::sandboxing_directive::SandboxingFlagSet;
17use dom_struct::dom_struct;
18use html5ever::local_name;
19use indexmap::map::IndexMap;
20use ipc_channel::ipc;
21use js::JSCLASS_IS_GLOBAL;
22use js::glue::{
23 CreateWrapperProxyHandler, DeleteWrapperProxyHandler, GetProxyPrivate, GetProxyReservedSlot,
24 ProxyTraps, SetProxyReservedSlot,
25};
26use js::jsapi::{
27 GCContext, Handle as RawHandle, HandleId as RawHandleId, HandleObject as RawHandleObject,
28 HandleValue as RawHandleValue, JS_DefinePropertyById, JS_ForwardGetPropertyTo,
29 JS_ForwardSetPropertyTo, JS_GetOwnPropertyDescriptorById, JS_HasOwnPropertyById,
30 JS_HasPropertyById, JS_IsExceptionPending, JSAutoRealm, JSContext, JSErrNum, JSObject,
31 JSPROP_ENUMERATE, JSPROP_READONLY, JSTracer, MutableHandle as RawMutableHandle,
32 MutableHandleObject as RawMutableHandleObject, MutableHandleValue as RawMutableHandleValue,
33 ObjectOpResult, PropertyDescriptor,
34};
35use js::jsval::{NullValue, PrivateValue, UndefinedValue};
36use js::rust::wrappers::{JS_TransplantObject, NewWindowProxy, SetWindowProxy};
37use js::rust::{Handle, MutableHandle, MutableHandleValue, get_object_class};
38use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
39use net_traits::request::Referrer;
40use script_bindings::reflector::MutDomObject;
41use script_traits::NewPipelineInfo;
42use serde::{Deserialize, Serialize};
43use servo_url::{ImmutableOrigin, ServoUrl};
44use storage_traits::webstorage_thread::WebStorageThreadMsg;
45use style::attr::parse_integer;
46
47use crate::dom::bindings::cell::DomRefCell;
48use crate::dom::bindings::conversions::{ToJSValConvertible, root_from_handleobject};
49use crate::dom::bindings::error::{Error, Fallible, throw_dom_exception};
50use crate::dom::bindings::inheritance::Castable;
51use crate::dom::bindings::proxyhandler::set_property_descriptor;
52use crate::dom::bindings::reflector::{DomGlobal, DomObject, Reflector};
53use crate::dom::bindings::root::{Dom, DomRoot};
54use crate::dom::bindings::str::{DOMString, USVString};
55use crate::dom::bindings::trace::JSTraceable;
56use crate::dom::bindings::utils::get_array_index_from_id;
57use crate::dom::dissimilaroriginwindow::DissimilarOriginWindow;
58use crate::dom::document::Document;
59use crate::dom::element::Element;
60use crate::dom::globalscope::GlobalScope;
61use crate::dom::window::Window;
62use crate::realms::{AlreadyInRealm, InRealm, enter_realm};
63use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
64use crate::script_thread::{ScriptThread, with_script_thread};
65use crate::script_window_proxies::ScriptWindowProxies;
66
67#[dom_struct]
68pub(crate) struct WindowProxy {
73 reflector: Reflector,
78
79 #[no_trace]
83 browsing_context_id: BrowsingContextId,
84
85 #[no_trace]
87 opener: Option<BrowsingContextId>,
88
89 #[no_trace]
92 webview_id: WebViewId,
93
94 name: DomRefCell<DOMString>,
97 #[no_trace]
103 currently_active: Cell<Option<PipelineId>>,
104
105 discarded: Cell<bool>,
107
108 disowned: Cell<bool>,
110
111 is_closing: Cell<bool>,
113
114 frame_element: Option<Dom<Element>>,
118
119 parent: Option<Dom<WindowProxy>>,
121
122 delaying_load_events_mode: Cell<bool>,
124
125 #[no_trace]
127 creator_url: Option<ServoUrl>,
128
129 #[no_trace]
131 creator_origin: Option<ImmutableOrigin>,
132
133 #[conditional_malloc_size_of]
135 script_window_proxies: Rc<ScriptWindowProxies>,
136}
137
138impl Drop for WindowProxy {
139 fn drop(&mut self) {
140 self.reflector.drop_memory(self);
141 }
142}
143
144impl WindowProxy {
145 fn new_inherited(
146 browsing_context_id: BrowsingContextId,
147 webview_id: WebViewId,
148 currently_active: Option<PipelineId>,
149 frame_element: Option<&Element>,
150 parent: Option<&WindowProxy>,
151 opener: Option<BrowsingContextId>,
152 creator: CreatorBrowsingContextInfo,
153 ) -> WindowProxy {
154 let name = frame_element.map_or(DOMString::new(), |e| {
155 e.get_string_attribute(&local_name!("name"))
156 });
157 WindowProxy {
158 reflector: Reflector::new(),
159 browsing_context_id,
160 webview_id,
161 name: DomRefCell::new(name),
162 currently_active: Cell::new(currently_active),
163 discarded: Cell::new(false),
164 disowned: Cell::new(false),
165 is_closing: Cell::new(false),
166 frame_element: frame_element.map(Dom::from_ref),
167 parent: parent.map(Dom::from_ref),
168 delaying_load_events_mode: Cell::new(false),
169 opener,
170 creator_url: creator.url,
171 creator_origin: creator.origin,
172 script_window_proxies: ScriptThread::window_proxies(),
173 }
174 }
175
176 #[expect(unsafe_code)]
177 pub(crate) fn new(
178 window: &Window,
179 browsing_context_id: BrowsingContextId,
180 webview_id: WebViewId,
181 frame_element: Option<&Element>,
182 parent: Option<&WindowProxy>,
183 opener: Option<BrowsingContextId>,
184 creator: CreatorBrowsingContextInfo,
185 ) -> DomRoot<WindowProxy> {
186 unsafe {
187 let handler = window.windowproxy_handler();
188
189 let cx = GlobalScope::get_cx();
190 let window_jsobject = window.reflector().get_jsobject();
191 assert!(!window_jsobject.get().is_null());
192 assert_ne!(
193 ((*get_object_class(window_jsobject.get())).flags & JSCLASS_IS_GLOBAL),
194 0
195 );
196 let _ac = JSAutoRealm::new(*cx, window_jsobject.get());
197
198 rooted!(in(*cx) let js_proxy = handler.new_window_proxy(&cx, window_jsobject));
200 assert!(!js_proxy.is_null());
201
202 let current = Some(window.upcast::<GlobalScope>().pipeline_id());
205 let window_proxy = Box::new(WindowProxy::new_inherited(
206 browsing_context_id,
207 webview_id,
208 current,
209 frame_element,
210 parent,
211 opener,
212 creator,
213 ));
214
215 SetProxyReservedSlot(
218 js_proxy.get(),
219 0,
220 &PrivateValue(&raw const (*window_proxy) as *const libc::c_void),
221 );
222
223 SetWindowProxy(*cx, window_jsobject, js_proxy.handle());
225
226 debug!(
228 "Initializing reflector of {:p} to {:p}.",
229 window_proxy,
230 js_proxy.get()
231 );
232 window_proxy
233 .reflector
234 .init_reflector::<WindowProxy>(js_proxy.get());
235 DomRoot::from_ref(&*Box::into_raw(window_proxy))
236 }
237 }
238
239 #[expect(unsafe_code)]
240 pub(crate) fn new_dissimilar_origin(
241 global_to_clone_from: &GlobalScope,
242 browsing_context_id: BrowsingContextId,
243 webview_id: WebViewId,
244 parent: Option<&WindowProxy>,
245 opener: Option<BrowsingContextId>,
246 creator: CreatorBrowsingContextInfo,
247 ) -> DomRoot<WindowProxy> {
248 unsafe {
249 let handler = WindowProxyHandler::x_origin_proxy_handler();
250
251 let cx = GlobalScope::get_cx();
252
253 let window_proxy = Box::new(WindowProxy::new_inherited(
255 browsing_context_id,
256 webview_id,
257 None,
258 None,
259 parent,
260 opener,
261 creator,
262 ));
263
264 let window = DissimilarOriginWindow::new(global_to_clone_from, &window_proxy);
266 let window_jsobject = window.reflector().get_jsobject();
267 assert!(!window_jsobject.get().is_null());
268 assert_ne!(
269 ((*get_object_class(window_jsobject.get())).flags & JSCLASS_IS_GLOBAL),
270 0
271 );
272 let _ac = JSAutoRealm::new(*cx, window_jsobject.get());
273
274 rooted!(in(*cx) let js_proxy = handler.new_window_proxy(&cx, window_jsobject));
276 assert!(!js_proxy.is_null());
277
278 SetProxyReservedSlot(
281 js_proxy.get(),
282 0,
283 &PrivateValue(&raw const (*window_proxy) as *const libc::c_void),
284 );
285
286 SetWindowProxy(*cx, window_jsobject, js_proxy.handle());
288
289 debug!(
291 "Initializing reflector of {:p} to {:p}.",
292 window_proxy,
293 js_proxy.get()
294 );
295 window_proxy
296 .reflector
297 .init_reflector::<WindowProxy>(js_proxy.get());
298 DomRoot::from_ref(&*Box::into_raw(window_proxy))
299 }
300 }
301
302 fn create_auxiliary_browsing_context(
304 &self,
305 name: DOMString,
306 noopener: bool,
307 ) -> Option<DomRoot<WindowProxy>> {
308 let (response_sender, response_receiver) = ipc::channel().unwrap();
309 let window = self
310 .currently_active
311 .get()
312 .and_then(ScriptThread::find_document)
313 .map(|doc| DomRoot::from_ref(doc.window()))
314 .unwrap();
315
316 let document = self
317 .currently_active
318 .get()
319 .and_then(ScriptThread::find_document)
320 .expect("A WindowProxy creating an auxiliary to have an active document");
321 let blank_url = ServoUrl::parse("about:blank").ok().unwrap();
322 let load_data = LoadData::new(
323 LoadOrigin::Script(document.origin().snapshot()),
324 blank_url,
325 Some(document.base_url()),
326 Some(window.pipeline_id()),
329 document.global().get_referrer(),
330 document.get_referrer_policy(),
331 None, None,
333 false,
334 SandboxingFlagSet::empty(),
336 );
337 let load_info = AuxiliaryWebViewCreationRequest {
338 load_data: load_data.clone(),
339 opener_webview_id: window.webview_id(),
340 opener_pipeline_id: self.currently_active.get().unwrap(),
341 response_sender,
342 };
343 let constellation_msg = ScriptToConstellationMessage::CreateAuxiliaryWebView(load_info);
344 window.send_to_constellation(constellation_msg);
345
346 let response = response_receiver.recv().unwrap()?;
347 let new_browsing_context_id = BrowsingContextId::from(response.new_webview_id);
348 let new_pipeline_info = NewPipelineInfo {
349 parent_info: None,
350 new_pipeline_id: response.new_pipeline_id,
351 browsing_context_id: new_browsing_context_id,
352 webview_id: response.new_webview_id,
353 opener: Some(self.browsing_context_id),
354 load_data,
355 viewport_details: window.viewport_details(),
356 user_content_manager_id: response.user_content_manager_id,
357 theme: window.theme(),
360 };
361
362 with_script_thread(|script_thread| {
363 script_thread.spawn_pipeline(new_pipeline_info);
364 });
365
366 let new_window_proxy = ScriptThread::find_document(response.new_pipeline_id)
367 .and_then(|doc| doc.browsing_context())?;
368 if name.to_lowercase() != "_blank" {
369 new_window_proxy.set_name(name);
370 }
371 if noopener {
372 new_window_proxy.disown();
373 } else {
374 let (sender, receiver) = generic_channel::channel().unwrap();
379
380 let msg = WebStorageThreadMsg::Clone {
381 sender,
382 src: window.window_proxy().webview_id(),
383 dest: response.new_webview_id,
384 };
385
386 GenericSend::send(document.global().storage_threads(), msg).unwrap();
387 receiver.recv().unwrap();
388 }
389 Some(new_window_proxy)
390 }
391
392 pub(crate) fn is_delaying_load_events_mode(&self) -> bool {
394 self.delaying_load_events_mode.get()
395 }
396
397 pub(crate) fn start_delaying_load_events_mode(&self) {
399 self.delaying_load_events_mode.set(true);
400 }
401
402 pub(crate) fn stop_delaying_load_events_mode(&self) {
404 self.delaying_load_events_mode.set(false);
405 if let Some(document) = self.document() {
406 if !document.loader().events_inhibited() {
407 ScriptThread::mark_document_with_no_blocked_loads(&document);
408 }
409 }
410 }
411
412 pub(crate) fn disown(&self) {
414 self.disowned.set(true);
415 }
416
417 pub(crate) fn close(&self) {
420 self.is_closing.set(true);
421 }
422
423 pub(crate) fn is_closing(&self) -> bool {
425 self.is_closing.get()
426 }
427
428 #[expect(unsafe_code)]
429 pub(crate) fn opener(
431 &self,
432 cx: *mut JSContext,
433 in_realm_proof: InRealm,
434 mut retval: MutableHandleValue,
435 ) {
436 if self.disowned.get() {
437 return retval.set(NullValue());
438 }
439 let opener_id = match self.opener {
440 Some(opener_browsing_context_id) => opener_browsing_context_id,
441 None => return retval.set(NullValue()),
442 };
443 let parent_browsing_context = self.parent.as_deref();
444 let opener_proxy = match self.script_window_proxies.find_window_proxy(opener_id) {
445 Some(window_proxy) => window_proxy,
446 None => {
447 let sender_pipeline_id = self.currently_active().unwrap();
448 match ScriptThread::get_top_level_for_browsing_context(
449 self.webview_id(),
450 sender_pipeline_id,
451 opener_id,
452 ) {
453 Some(opener_top_id) => {
454 let global_to_clone_from =
455 unsafe { GlobalScope::from_context(cx, in_realm_proof) };
456 let creator =
457 CreatorBrowsingContextInfo::from(parent_browsing_context, None);
458 WindowProxy::new_dissimilar_origin(
459 &global_to_clone_from,
460 opener_id,
461 opener_top_id,
462 None,
463 None,
464 creator,
465 )
466 },
467 None => return retval.set(NullValue()),
468 }
469 },
470 };
471 if opener_proxy.is_browsing_context_discarded() {
472 return retval.set(NullValue());
473 }
474 unsafe { opener_proxy.to_jsval(cx, retval) };
475 }
476
477 pub(crate) fn open(
479 &self,
480 url: USVString,
481 target: DOMString,
482 features: DOMString,
483 can_gc: CanGc,
484 ) -> Fallible<Option<DomRoot<WindowProxy>>> {
485 if self.discarded.get() {
491 return Ok(None);
492 }
493 let non_empty_target = if target.is_empty() {
495 DOMString::from("_blank")
496 } else {
497 target
498 };
499 let tokenized_features = tokenize_open_features(features);
501 let noreferrer = parse_open_feature_boolean(&tokenized_features, "noreferrer");
505
506 let noopener = if noreferrer {
509 true
510 } else {
511 parse_open_feature_boolean(&tokenized_features, "noopener")
512 };
513 let (chosen, new) = match self.choose_browsing_context(non_empty_target, noopener) {
523 (Some(chosen), new) => (chosen, new),
524 (None, _) => return Ok(None),
525 };
526 let target_document = match chosen.document() {
529 Some(target_document) => target_document,
530 None => return Ok(None),
531 };
532 let has_trustworthy_ancestor_origin = if new {
533 target_document.has_trustworthy_ancestor_or_current_origin()
534 } else {
535 false
536 };
537 let target_window = target_document.window();
538 if !url.is_empty() {
541 let existing_document = self
542 .currently_active
543 .get()
544 .and_then(ScriptThread::find_document)
545 .unwrap();
546 let url = match existing_document.url().join(&url) {
547 Ok(url) => url,
548 Err(_) => return Err(Error::Syntax(None)),
549 };
550 let referrer = if noreferrer {
551 Referrer::NoReferrer
552 } else {
553 target_window.as_global_scope().get_referrer()
554 };
555 let csp_list = existing_document.get_csp_list();
557 target_document.set_csp_list(csp_list);
558
559 let mut load_data = LoadData::new(
563 LoadOrigin::Script(existing_document.origin().snapshot()),
564 url,
565 target_document.about_base_url(),
566 Some(target_window.pipeline_id()),
567 referrer,
568 target_document.get_referrer_policy(),
569 Some(target_window.as_global_scope().is_secure_context()),
570 Some(target_document.insecure_requests_policy()),
571 has_trustworthy_ancestor_origin,
572 target_document.creation_sandboxing_flag_set_considering_parent_iframe(),
573 );
574
575 if load_data.url.scheme() == "javascript" {
578 let existing_global = existing_document.global();
579
580 if !ScriptThread::can_navigate_to_javascript_url(
582 &existing_global,
583 target_window.as_global_scope(),
584 &mut load_data,
585 None,
586 can_gc,
587 ) {
588 return Ok(target_document.browsing_context());
590 }
591 }
592
593 let history_handling = if new {
594 NavigationHistoryBehavior::Replace
595 } else {
596 NavigationHistoryBehavior::Push
597 };
598 target_window.load_url(history_handling, false, load_data, can_gc);
599 }
600 if noopener {
602 return Ok(None);
603 }
604 Ok(target_document.browsing_context())
606 }
607
608 pub(crate) fn choose_browsing_context(
610 &self,
611 name: DOMString,
612 noopener: bool,
613 ) -> (Option<DomRoot<WindowProxy>>, bool) {
614 match name.to_lowercase().as_ref() {
615 "" | "_self" => {
616 (Some(DomRoot::from_ref(self)), false)
618 },
619 "_parent" => {
620 if let Some(parent) = self.parent() {
622 return (Some(DomRoot::from_ref(parent)), false);
623 }
624 (None, false)
625 },
626 "_top" => {
627 (Some(DomRoot::from_ref(self.top())), false)
629 },
630 "_blank" => (self.create_auxiliary_browsing_context(name, noopener), true),
631 _ => {
632 match ScriptThread::find_window_proxy_by_name(&name) {
637 Some(proxy) => (Some(proxy), false),
638 None => (self.create_auxiliary_browsing_context(name, noopener), true),
639 }
640 },
641 }
642 }
643
644 pub(crate) fn is_auxiliary(&self) -> bool {
645 self.opener.is_some()
646 }
647
648 pub(crate) fn discard_browsing_context(&self) {
649 self.discarded.set(true);
650 }
651
652 pub(crate) fn is_browsing_context_discarded(&self) -> bool {
653 self.discarded.get()
654 }
655
656 pub(crate) fn browsing_context_id(&self) -> BrowsingContextId {
657 self.browsing_context_id
658 }
659
660 pub(crate) fn webview_id(&self) -> WebViewId {
661 self.webview_id
662 }
663
664 pub(crate) fn frame_element(&self) -> Option<&Element> {
668 self.frame_element.as_deref()
669 }
670
671 pub(crate) fn document(&self) -> Option<DomRoot<Document>> {
672 self.currently_active
673 .get()
674 .and_then(ScriptThread::find_document)
675 }
676
677 pub(crate) fn parent(&self) -> Option<&WindowProxy> {
678 self.parent.as_deref()
679 }
680
681 pub(crate) fn top(&self) -> &WindowProxy {
682 let mut result = self;
683 while let Some(parent) = result.parent() {
684 result = parent;
685 }
686 result
687 }
688
689 pub fn focus(&self) {
693 debug!(
694 "Requesting the constellation to initiate a focus operation for \
695 browsing context {}",
696 self.browsing_context_id()
697 );
698 self.global()
699 .script_to_constellation_chan()
700 .send(ScriptToConstellationMessage::FocusRemoteDocument(
701 self.browsing_context_id(),
702 ))
703 .unwrap();
704 }
705
706 #[expect(unsafe_code)]
707 fn set_window(&self, window: &GlobalScope, handler: &WindowProxyHandler, _can_gc: CanGc) {
711 unsafe {
712 debug!("Setting window of {:p}.", self);
713
714 let cx = GlobalScope::get_cx();
715 let window_jsobject = window.reflector().get_jsobject();
716 let old_js_proxy = self.reflector.get_jsobject();
717 assert!(!window_jsobject.get().is_null());
718 assert_ne!(
719 ((*get_object_class(window_jsobject.get())).flags & JSCLASS_IS_GLOBAL),
720 0
721 );
722 let _ac = enter_realm(window);
723
724 SetProxyReservedSlot(old_js_proxy.get(), 0, &PrivateValue(ptr::null_mut()));
726
727 rooted!(in(*cx) let new_js_proxy = handler.new_window_proxy(&cx, window_jsobject));
733 SetProxyReservedSlot(new_js_proxy.get(), 0, &PrivateValue(ptr::null_mut()));
736 debug!(
737 "Transplanting proxy from {:p} to {:p}.",
738 old_js_proxy.get(),
739 new_js_proxy.get()
740 );
741 rooted!(in(*cx) let new_js_proxy = JS_TransplantObject(*cx, old_js_proxy, new_js_proxy.handle()));
742 debug!("Transplanted proxy is {:p}.", new_js_proxy.get());
743
744 SetProxyReservedSlot(
746 new_js_proxy.get(),
747 0,
748 &PrivateValue(self as *const _ as *const libc::c_void),
749 );
750
751 SetWindowProxy(*cx, window_jsobject, new_js_proxy.handle());
753
754 debug!(
756 "Setting reflector of {:p} to {:p}.",
757 self,
758 new_js_proxy.get()
759 );
760 self.reflector.rootable().set(new_js_proxy.get());
761 }
762 }
763
764 pub(crate) fn set_currently_active(&self, window: &Window, can_gc: CanGc) {
765 if let Some(pipeline_id) = self.currently_active() {
766 if pipeline_id == window.pipeline_id() {
767 return debug!(
768 "Attempt to set the currently active window to the currently active window."
769 );
770 }
771 }
772
773 let global_scope = window.as_global_scope();
774 self.set_window(global_scope, WindowProxyHandler::proxy_handler(), can_gc);
775 self.currently_active.set(Some(global_scope.pipeline_id()));
776 }
777
778 pub(crate) fn unset_currently_active(&self, can_gc: CanGc) {
779 if self.currently_active().is_none() {
780 return debug!(
781 "Attempt to unset the currently active window on a windowproxy that does not have one."
782 );
783 }
784 let globalscope = self.global();
785 let window = DissimilarOriginWindow::new(&globalscope, self);
786 self.set_window(
787 window.upcast(),
788 WindowProxyHandler::x_origin_proxy_handler(),
789 can_gc,
790 );
791 self.currently_active.set(None);
792 }
793
794 pub(crate) fn currently_active(&self) -> Option<PipelineId> {
795 self.currently_active.get()
796 }
797
798 pub(crate) fn get_name(&self) -> DOMString {
799 self.name.borrow().clone()
800 }
801
802 pub(crate) fn set_name(&self, name: DOMString) {
803 *self.name.borrow_mut() = name;
804 }
805}
806
807#[derive(Debug, Deserialize, Serialize)]
819pub(crate) struct CreatorBrowsingContextInfo {
820 url: Option<ServoUrl>,
822
823 origin: Option<ImmutableOrigin>,
825}
826
827impl CreatorBrowsingContextInfo {
828 pub(crate) fn from(
829 parent: Option<&WindowProxy>,
830 opener: Option<&WindowProxy>,
831 ) -> CreatorBrowsingContextInfo {
832 let creator = match (parent, opener) {
833 (Some(parent), _) => parent.document(),
834 (None, Some(opener)) => opener.document(),
835 (None, None) => None,
836 };
837
838 let url = creator.as_deref().map(|document| document.url());
839 let origin = creator
840 .as_deref()
841 .map(|document| document.origin().immutable().clone());
842
843 CreatorBrowsingContextInfo { url, origin }
844 }
845}
846
847fn tokenize_open_features(features: DOMString) -> IndexMap<String, String> {
849 let is_feature_sep = |c: char| c.is_ascii_whitespace() || ['=', ','].contains(&c);
850 let mut tokenized_features = IndexMap::new();
852 let features = features.str();
854 let mut iter = features.chars();
855 let mut cur = iter.next();
856
857 while cur.is_some() {
859 let mut name = String::new();
861 let mut value = String::new();
862 while let Some(cur_char) = cur {
864 if !is_feature_sep(cur_char) {
865 break;
866 }
867 cur = iter.next();
868 }
869 while let Some(cur_char) = cur {
871 if is_feature_sep(cur_char) {
872 break;
873 }
874 name.push(cur_char.to_ascii_lowercase());
875 cur = iter.next();
876 }
877 let normalized_name = String::from(match name.as_ref() {
879 "screenx" => "left",
880 "screeny" => "top",
881 "innerwidth" => "width",
882 "innerheight" => "height",
883 _ => name.as_ref(),
884 });
885 while let Some(cur_char) = cur {
887 if cur_char == '=' || cur_char == ',' || !is_feature_sep(cur_char) {
888 break;
889 }
890 cur = iter.next();
891 }
892 if cur.is_some() && is_feature_sep(cur.unwrap()) {
894 while let Some(cur_char) = cur {
896 if !is_feature_sep(cur_char) || cur_char == ',' {
897 break;
898 }
899 cur = iter.next();
900 }
901 while let Some(cur_char) = cur {
903 if is_feature_sep(cur_char) {
904 break;
905 }
906 value.push(cur_char.to_ascii_lowercase());
907 cur = iter.next();
908 }
909 }
910 if !name.is_empty() {
912 tokenized_features.insert(normalized_name, value);
913 }
914 }
915 tokenized_features
917}
918
919fn parse_open_feature_boolean(tokenized_features: &IndexMap<String, String>, name: &str) -> bool {
921 if let Some(value) = tokenized_features.get(name) {
922 if value.is_empty() || value == "yes" {
924 return true;
925 }
926 if let Ok(int) = parse_integer(value.chars()) {
928 return int != 0;
929 }
930 }
931 false
933}
934
935#[expect(unsafe_code)]
939#[expect(non_snake_case)]
940unsafe fn GetSubframeWindowProxy(
941 cx: *mut JSContext,
942 proxy: RawHandleObject,
943 id: RawHandleId,
944) -> Option<(DomRoot<WindowProxy>, u32)> {
945 let index = get_array_index_from_id(unsafe { Handle::from_raw(id) });
946 if let Some(index) = index {
947 let mut slot = UndefinedValue();
948 unsafe { GetProxyPrivate(*proxy, &mut slot) };
949 rooted!(in(cx) let target = slot.to_object());
950 let script_window_proxies = ScriptThread::window_proxies();
951 if let Ok(win) = root_from_handleobject::<Window>(target.handle(), cx) {
952 let browsing_context_id = win.window_proxy().browsing_context_id();
953 let (result_sender, result_receiver) = ipc::channel().unwrap();
954
955 let _ = win.as_global_scope().script_to_constellation_chan().send(
956 ScriptToConstellationMessage::GetChildBrowsingContextId(
957 browsing_context_id,
958 index as usize,
959 result_sender,
960 ),
961 );
962 return result_receiver
963 .recv()
964 .ok()
965 .and_then(|maybe_bcid| maybe_bcid)
966 .and_then(|id| script_window_proxies.find_window_proxy(id))
967 .map(|proxy| (proxy, (JSPROP_ENUMERATE | JSPROP_READONLY) as u32));
968 } else if let Ok(win) =
969 root_from_handleobject::<DissimilarOriginWindow>(target.handle(), cx)
970 {
971 let browsing_context_id = win.window_proxy().browsing_context_id();
972 let (result_sender, result_receiver) = ipc::channel().unwrap();
973
974 let _ = win.global().script_to_constellation_chan().send(
975 ScriptToConstellationMessage::GetChildBrowsingContextId(
976 browsing_context_id,
977 index as usize,
978 result_sender,
979 ),
980 );
981 return result_receiver
982 .recv()
983 .ok()
984 .and_then(|maybe_bcid| maybe_bcid)
985 .and_then(|id| script_window_proxies.find_window_proxy(id))
986 .map(|proxy| (proxy, JSPROP_READONLY as u32));
987 }
988 }
989
990 None
991}
992
993#[expect(unsafe_code)]
994unsafe extern "C" fn get_own_property_descriptor(
995 cx: *mut JSContext,
996 proxy: RawHandleObject,
997 id: RawHandleId,
998 desc: RawMutableHandle<PropertyDescriptor>,
999 is_none: *mut bool,
1000) -> bool {
1001 let window = unsafe { GetSubframeWindowProxy(cx, proxy, id) };
1002 if let Some((window, attrs)) = window {
1003 rooted!(in(cx) let mut val = UndefinedValue());
1004 unsafe { window.to_jsval(cx, val.handle_mut()) };
1005 set_property_descriptor(
1006 unsafe { MutableHandle::from_raw(desc) },
1007 val.handle(),
1008 attrs,
1009 unsafe { &mut *is_none },
1010 );
1011 return true;
1012 }
1013
1014 let mut slot = UndefinedValue();
1015 unsafe { GetProxyPrivate(proxy.get(), &mut slot) };
1016 rooted!(in(cx) let target = slot.to_object());
1017 unsafe { JS_GetOwnPropertyDescriptorById(cx, target.handle().into(), id, desc, is_none) }
1018}
1019
1020#[expect(unsafe_code)]
1021unsafe extern "C" fn define_property(
1022 cx: *mut JSContext,
1023 proxy: RawHandleObject,
1024 id: RawHandleId,
1025 desc: RawHandle<PropertyDescriptor>,
1026 res: *mut ObjectOpResult,
1027) -> bool {
1028 if get_array_index_from_id(unsafe { Handle::from_raw(id) }).is_some() {
1029 unsafe {
1034 (*res).code_ = JSErrNum::JSMSG_CANT_DEFINE_WINDOW_ELEMENT as ::libc::uintptr_t;
1035 }
1036 return true;
1037 }
1038
1039 let mut slot = UndefinedValue();
1040 unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1041 rooted!(in(cx) let target = slot.to_object());
1042 unsafe { JS_DefinePropertyById(cx, target.handle().into(), id, desc, res) }
1043}
1044
1045#[expect(unsafe_code)]
1046unsafe extern "C" fn has(
1047 cx: *mut JSContext,
1048 proxy: RawHandleObject,
1049 id: RawHandleId,
1050 bp: *mut bool,
1051) -> bool {
1052 let window = unsafe { GetSubframeWindowProxy(cx, proxy, id) };
1053 if window.is_some() {
1054 unsafe { *bp = true };
1055 return true;
1056 }
1057
1058 let mut slot = UndefinedValue();
1059 unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1060 rooted!(in(cx) let target = slot.to_object());
1061 let mut found = false;
1062 if !unsafe { JS_HasPropertyById(cx, target.handle().into(), id, &mut found) } {
1063 return false;
1064 }
1065
1066 unsafe { *bp = found };
1067 true
1068}
1069
1070#[expect(unsafe_code)]
1071unsafe extern "C" fn get(
1072 cx: *mut JSContext,
1073 proxy: RawHandleObject,
1074 receiver: RawHandleValue,
1075 id: RawHandleId,
1076 vp: RawMutableHandleValue,
1077) -> bool {
1078 let window = unsafe { GetSubframeWindowProxy(cx, proxy, id) };
1079 if let Some((window, _attrs)) = window {
1080 unsafe { window.to_jsval(cx, MutableHandle::from_raw(vp)) };
1081 return true;
1082 }
1083
1084 let mut slot = UndefinedValue();
1085 unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1086 rooted!(in(cx) let target = slot.to_object());
1087 unsafe { JS_ForwardGetPropertyTo(cx, target.handle().into(), id, receiver, vp) }
1088}
1089
1090#[expect(unsafe_code)]
1091unsafe extern "C" fn set(
1092 cx: *mut JSContext,
1093 proxy: RawHandleObject,
1094 id: RawHandleId,
1095 v: RawHandleValue,
1096 receiver: RawHandleValue,
1097 res: *mut ObjectOpResult,
1098) -> bool {
1099 if get_array_index_from_id(unsafe { Handle::from_raw(id) }).is_some() {
1100 unsafe { (*res).code_ = JSErrNum::JSMSG_READ_ONLY as ::libc::uintptr_t };
1102 return true;
1103 }
1104
1105 let mut slot = UndefinedValue();
1106 unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1107 rooted!(in(cx) let target = slot.to_object());
1108 unsafe { JS_ForwardSetPropertyTo(cx, target.handle().into(), id, v, receiver, res) }
1109}
1110
1111#[expect(unsafe_code)]
1112unsafe extern "C" fn get_prototype_if_ordinary(
1113 _: *mut JSContext,
1114 _: RawHandleObject,
1115 is_ordinary: *mut bool,
1116 _: RawMutableHandleObject,
1117) -> bool {
1118 unsafe { *is_ordinary = false };
1131 true
1132}
1133
1134static PROXY_TRAPS: ProxyTraps = ProxyTraps {
1135 enter: None,
1138 getOwnPropertyDescriptor: Some(get_own_property_descriptor),
1139 defineProperty: Some(define_property),
1140 ownPropertyKeys: None,
1141 delete_: None,
1142 enumerate: None,
1143 getPrototypeIfOrdinary: Some(get_prototype_if_ordinary),
1144 getPrototype: None, setPrototype: None,
1146 setImmutablePrototype: None,
1147 preventExtensions: None,
1148 isExtensible: None,
1149 has: Some(has),
1150 get: Some(get),
1151 set: Some(set),
1152 call: None,
1153 construct: None,
1154 hasOwn: None,
1155 getOwnEnumerablePropertyKeys: None,
1156 nativeCall: None,
1157 objectClassIs: None,
1158 className: None,
1159 fun_toString: None,
1160 boxedValue_unbox: None,
1161 defaultValue: None,
1162 trace: Some(trace),
1163 finalize: Some(finalize),
1164 objectMoved: None,
1165 isCallable: None,
1166 isConstructor: None,
1167};
1168
1169pub(crate) struct WindowProxyHandler(*const libc::c_void);
1172
1173impl MallocSizeOf for WindowProxyHandler {
1174 fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize {
1175 0
1177 }
1178}
1179
1180#[expect(unsafe_code)]
1182unsafe impl Send for WindowProxyHandler {}
1183#[expect(unsafe_code)]
1185unsafe impl Sync for WindowProxyHandler {}
1186
1187#[expect(unsafe_code)]
1188impl WindowProxyHandler {
1189 fn new(traps: &ProxyTraps) -> Self {
1190 let ptr = unsafe { CreateWrapperProxyHandler(traps) };
1192 assert!(!ptr.is_null());
1193 Self(ptr)
1194 }
1195
1196 pub(crate) fn x_origin_proxy_handler() -> &'static Self {
1198 use std::sync::OnceLock;
1199 static SINGLETON: OnceLock<WindowProxyHandler> = OnceLock::new();
1205 SINGLETON.get_or_init(|| Self::new(&XORIGIN_PROXY_TRAPS))
1206 }
1207
1208 pub(crate) fn proxy_handler() -> &'static Self {
1210 use std::sync::OnceLock;
1211 static SINGLETON: OnceLock<WindowProxyHandler> = OnceLock::new();
1217 SINGLETON.get_or_init(|| Self::new(&PROXY_TRAPS))
1218 }
1219
1220 pub(crate) fn new_window_proxy(
1223 &self,
1224 cx: &crate::script_runtime::JSContext,
1225 window_jsobject: js::gc::HandleObject,
1226 ) -> *mut JSObject {
1227 let obj = unsafe { NewWindowProxy(**cx, window_jsobject, self.0) };
1228 assert!(!obj.is_null());
1229 obj
1230 }
1231}
1232
1233#[expect(unsafe_code)]
1234impl Drop for WindowProxyHandler {
1235 fn drop(&mut self) {
1236 unsafe {
1239 DeleteWrapperProxyHandler(self.0);
1240 }
1241 }
1242}
1243
1244#[expect(unsafe_code)]
1252fn throw_security_error(cx: SafeJSContext, realm: InRealm) -> bool {
1253 if !unsafe { JS_IsExceptionPending(*cx) } {
1254 let global = unsafe { GlobalScope::from_context(*cx, realm) };
1255 throw_dom_exception(cx, &global, Error::Security(None), CanGc::note());
1256 }
1257 false
1258}
1259
1260#[expect(unsafe_code)]
1261unsafe extern "C" fn has_xorigin(
1262 cx: *mut JSContext,
1263 proxy: RawHandleObject,
1264 id: RawHandleId,
1265 bp: *mut bool,
1266) -> bool {
1267 let mut slot = UndefinedValue();
1268 unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1269 rooted!(in(cx) let target = slot.to_object());
1270 let mut found = false;
1271 unsafe { JS_HasOwnPropertyById(cx, target.handle().into(), id, &mut found) };
1272 if found {
1273 unsafe { *bp = true };
1274 true
1275 } else {
1276 let cx = unsafe { SafeJSContext::from_ptr(cx) };
1277 let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
1278 throw_security_error(cx, InRealm::Already(&in_realm_proof))
1279 }
1280}
1281
1282#[expect(unsafe_code)]
1283unsafe extern "C" fn get_xorigin(
1284 cx: *mut JSContext,
1285 proxy: RawHandleObject,
1286 receiver: RawHandleValue,
1287 id: RawHandleId,
1288 vp: RawMutableHandleValue,
1289) -> bool {
1290 let mut found = false;
1291 unsafe { has_xorigin(cx, proxy, id, &mut found) };
1292 found && unsafe { get(cx, proxy, receiver, id, vp) }
1293}
1294
1295#[expect(unsafe_code)]
1296unsafe extern "C" fn set_xorigin(
1297 cx: *mut JSContext,
1298 _: RawHandleObject,
1299 _: RawHandleId,
1300 _: RawHandleValue,
1301 _: RawHandleValue,
1302 _: *mut ObjectOpResult,
1303) -> bool {
1304 let cx = unsafe { SafeJSContext::from_ptr(cx) };
1305 let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
1306 throw_security_error(cx, InRealm::Already(&in_realm_proof))
1307}
1308
1309#[expect(unsafe_code)]
1310unsafe extern "C" fn delete_xorigin(
1311 cx: *mut JSContext,
1312 _: RawHandleObject,
1313 _: RawHandleId,
1314 _: *mut ObjectOpResult,
1315) -> bool {
1316 let cx = unsafe { SafeJSContext::from_ptr(cx) };
1317 let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
1318 throw_security_error(cx, InRealm::Already(&in_realm_proof))
1319}
1320
1321#[expect(unsafe_code)]
1322#[expect(non_snake_case)]
1323unsafe extern "C" fn getOwnPropertyDescriptor_xorigin(
1324 cx: *mut JSContext,
1325 proxy: RawHandleObject,
1326 id: RawHandleId,
1327 desc: RawMutableHandle<PropertyDescriptor>,
1328 is_none: *mut bool,
1329) -> bool {
1330 let mut found = false;
1331 unsafe { has_xorigin(cx, proxy, id, &mut found) };
1332 found && unsafe { get_own_property_descriptor(cx, proxy, id, desc, is_none) }
1333}
1334
1335#[expect(unsafe_code)]
1336#[expect(non_snake_case)]
1337unsafe extern "C" fn defineProperty_xorigin(
1338 cx: *mut JSContext,
1339 _: RawHandleObject,
1340 _: RawHandleId,
1341 _: RawHandle<PropertyDescriptor>,
1342 _: *mut ObjectOpResult,
1343) -> bool {
1344 let cx = unsafe { SafeJSContext::from_ptr(cx) };
1345 let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
1346 throw_security_error(cx, InRealm::Already(&in_realm_proof))
1347}
1348
1349#[expect(unsafe_code)]
1350#[expect(non_snake_case)]
1351unsafe extern "C" fn preventExtensions_xorigin(
1352 cx: *mut JSContext,
1353 _: RawHandleObject,
1354 _: *mut ObjectOpResult,
1355) -> bool {
1356 let cx = unsafe { SafeJSContext::from_ptr(cx) };
1357 let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
1358 throw_security_error(cx, InRealm::Already(&in_realm_proof))
1359}
1360
1361static XORIGIN_PROXY_TRAPS: ProxyTraps = ProxyTraps {
1362 enter: None,
1363 getOwnPropertyDescriptor: Some(getOwnPropertyDescriptor_xorigin),
1364 defineProperty: Some(defineProperty_xorigin),
1365 ownPropertyKeys: None,
1366 delete_: Some(delete_xorigin),
1367 enumerate: None,
1368 getPrototypeIfOrdinary: None,
1369 getPrototype: None,
1370 setPrototype: None,
1371 setImmutablePrototype: None,
1372 preventExtensions: Some(preventExtensions_xorigin),
1373 isExtensible: None,
1374 has: Some(has_xorigin),
1375 get: Some(get_xorigin),
1376 set: Some(set_xorigin),
1377 call: None,
1378 construct: None,
1379 hasOwn: Some(has_xorigin),
1380 getOwnEnumerablePropertyKeys: None,
1381 nativeCall: None,
1382 objectClassIs: None,
1383 className: None,
1384 fun_toString: None,
1385 boxedValue_unbox: None,
1386 defaultValue: None,
1387 trace: Some(trace),
1388 finalize: Some(finalize),
1389 objectMoved: None,
1390 isCallable: None,
1391 isConstructor: None,
1392};
1393
1394#[expect(unsafe_code)]
1397unsafe extern "C" fn finalize(_fop: *mut GCContext, obj: *mut JSObject) {
1398 let mut slot = UndefinedValue();
1399 unsafe { GetProxyReservedSlot(obj, 0, &mut slot) };
1400 let this = slot.to_private() as *mut WindowProxy;
1401 if this.is_null() {
1402 return;
1404 }
1405 let jsobject = unsafe { (*this).reflector.get_jsobject().get() };
1406 debug!(
1407 "WindowProxy finalize: {:p}, with reflector {:p} from {:p}.",
1408 this, jsobject, obj
1409 );
1410 let _ = unsafe { Box::from_raw(this) };
1411}
1412
1413#[expect(unsafe_code)]
1414unsafe extern "C" fn trace(trc: *mut JSTracer, obj: *mut JSObject) {
1415 let mut slot = UndefinedValue();
1416 unsafe { GetProxyReservedSlot(obj, 0, &mut slot) };
1417 let this = slot.to_private() as *const WindowProxy;
1418 if this.is_null() {
1419 return;
1421 }
1422 unsafe { (*this).trace(trc) };
1423}