1use std::cell::Cell;
6use std::ptr::{self, NonNull};
7use std::rc::Rc;
8
9use content_security_policy::sandboxing_directive::SandboxingFlagSet;
10use dom_struct::dom_struct;
11use html5ever::local_name;
12use indexmap::map::IndexMap;
13use js::JSCLASS_IS_GLOBAL;
14use js::context::JSContext;
15use js::glue::{
16 CreateWrapperProxyHandler, DeleteWrapperProxyHandler, GetProxyPrivate, GetProxyReservedSlot,
17 ProxyTraps, SetProxyReservedSlot,
18};
19use js::jsapi::{
20 GCContext, Handle as RawHandle, HandleId as RawHandleId, HandleObject as RawHandleObject,
21 HandleValue as RawHandleValue, JS_DefinePropertyById, JS_ForwardGetPropertyTo,
22 JS_ForwardSetPropertyTo, JS_GetOwnPropertyDescriptorById, JS_HasPropertyById,
23 JSContext as RawJSContext, JSErrNum, JSObject, JSPROP_ENUMERATE, JSPROP_READONLY, JSTracer,
24 MutableHandle as RawMutableHandle, MutableHandleObject as RawMutableHandleObject,
25 MutableHandleValue as RawMutableHandleValue, ObjectOpResult, PropertyDescriptor,
26};
27use js::jsval::{NullValue, PrivateValue, UndefinedValue};
28use js::realm::{AutoRealm, CurrentRealm};
29use js::rust::wrappers2::{
30 JS_HasOwnPropertyById, JS_IsExceptionPending, JS_TransplantObject, NewWindowProxy,
31 SetWindowProxy,
32};
33use js::rust::{Handle, MutableHandle, MutableHandleValue, get_object_class};
34use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
35use net_traits::ReferrerPolicy;
36use net_traits::request::Referrer;
37use script_bindings::cell::DomRefCell;
38use script_bindings::reflector::{DomObject, MutDomObject, Reflector};
39use script_traits::NewPipelineInfo;
40use serde::{Deserialize, Serialize};
41use servo_base::generic_channel;
42use servo_base::generic_channel::GenericSend;
43use servo_base::id::{BrowsingContextId, PipelineId, WebViewId};
44use servo_constellation_traits::{
45 AuxiliaryWebViewCreationRequest, LoadData, LoadOrigin, NavigationHistoryBehavior,
46 ScriptToConstellationMessage, TargetSnapshotParams,
47};
48use servo_url::{ImmutableOrigin, ServoUrl};
49use storage_traits::webstorage_thread::WebStorageThreadMsg;
50use style::attr::parse_integer;
51
52use crate::dom::bindings::conversions::{ToJSValConvertible, root_from_handleobject};
53use crate::dom::bindings::error::{Error, Fallible, throw_dom_exception};
54use crate::dom::bindings::inheritance::Castable;
55use crate::dom::bindings::proxyhandler::set_property_descriptor;
56use crate::dom::bindings::reflector::DomGlobal;
57use crate::dom::bindings::root::{Dom, DomRoot};
58use crate::dom::bindings::str::{DOMString, USVString};
59use crate::dom::bindings::trace::JSTraceable;
60use crate::dom::bindings::utils::get_array_index_from_id;
61use crate::dom::dissimilaroriginwindow::DissimilarOriginWindow;
62use crate::dom::document::Document;
63use crate::dom::element::Element;
64use crate::dom::globalscope::GlobalScope;
65use crate::dom::window::Window;
66use crate::navigation::navigate;
67use crate::script_thread::{ScriptThread, with_script_thread};
68use crate::script_window_proxies::ScriptWindowProxies;
69
70#[dom_struct]
71pub(crate) struct WindowProxy {
76 reflector: Reflector,
81
82 #[no_trace]
86 browsing_context_id: BrowsingContextId,
87
88 #[no_trace]
90 opener: Option<BrowsingContextId>,
91
92 #[no_trace]
95 webview_id: WebViewId,
96
97 name: DomRefCell<DOMString>,
100 #[no_trace]
106 currently_active: Cell<Option<PipelineId>>,
107
108 discarded: Cell<bool>,
110
111 disowned: Cell<bool>,
113
114 is_closing: Cell<bool>,
116
117 frame_element: Option<Dom<Element>>,
121
122 parent: Option<Dom<WindowProxy>>,
124
125 delaying_load_events_mode: Cell<bool>,
127
128 #[no_trace]
130 creator_url: Option<ServoUrl>,
131
132 #[no_trace]
134 creator_origin: Option<ImmutableOrigin>,
135
136 #[conditional_malloc_size_of]
138 script_window_proxies: Rc<ScriptWindowProxies>,
139}
140
141impl WindowProxy {
142 fn new_inherited(
143 browsing_context_id: BrowsingContextId,
144 webview_id: WebViewId,
145 currently_active: Option<PipelineId>,
146 frame_element: Option<&Element>,
147 parent: Option<&WindowProxy>,
148 opener: Option<BrowsingContextId>,
149 creator: CreatorBrowsingContextInfo,
150 ) -> WindowProxy {
151 let name = frame_element.map_or(DOMString::new(), |e| {
152 e.get_string_attribute(&local_name!("name"))
153 });
154 WindowProxy {
155 reflector: Reflector::new(),
156 browsing_context_id,
157 webview_id,
158 name: DomRefCell::new(name),
159 currently_active: Cell::new(currently_active),
160 discarded: Cell::new(false),
161 disowned: Cell::new(false),
162 is_closing: Cell::new(false),
163 frame_element: frame_element.map(Dom::from_ref),
164 parent: parent.map(Dom::from_ref),
165 delaying_load_events_mode: Cell::new(false),
166 opener,
167 creator_url: creator.url,
168 creator_origin: creator.origin,
169 script_window_proxies: ScriptThread::window_proxies(),
170 }
171 }
172
173 #[expect(unsafe_code)]
174 #[expect(clippy::too_many_arguments)]
175 pub(crate) fn new(
176 cx: &mut JSContext,
177 window: &Window,
178 browsing_context_id: BrowsingContextId,
179 webview_id: WebViewId,
180 frame_element: Option<&Element>,
181 parent: Option<&WindowProxy>,
182 opener: Option<BrowsingContextId>,
183 creator: CreatorBrowsingContextInfo,
184 ) -> DomRoot<WindowProxy> {
185 unsafe {
186 let handler = window.windowproxy_handler();
187
188 let window_jsobject = window.reflector().get_jsobject();
189 assert!(!window_jsobject.get().is_null());
190 assert_ne!(
191 ((*get_object_class(window_jsobject.get())).flags & JSCLASS_IS_GLOBAL),
192 0
193 );
194
195 let mut realm = AutoRealm::new_from_handle(cx, window_jsobject);
196 let cx = &mut realm;
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 cx: &mut JSContext,
242 global_to_clone_from: &GlobalScope,
243 browsing_context_id: BrowsingContextId,
244 webview_id: WebViewId,
245 parent: Option<&WindowProxy>,
246 opener: Option<BrowsingContextId>,
247 creator: CreatorBrowsingContextInfo,
248 ) -> DomRoot<WindowProxy> {
249 unsafe {
250 let handler = WindowProxyHandler::x_origin_proxy_handler();
251
252 let window_proxy = Box::new(WindowProxy::new_inherited(
254 browsing_context_id,
255 webview_id,
256 None,
257 None,
258 parent,
259 opener,
260 creator,
261 ));
262
263 let window = DissimilarOriginWindow::new(cx, global_to_clone_from, &window_proxy);
265 let window_jsobject = window.reflector().get_jsobject();
266 assert!(!window_jsobject.get().is_null());
267 assert_ne!(
268 ((*get_object_class(window_jsobject.get())).flags & JSCLASS_IS_GLOBAL),
269 0
270 );
271
272 let mut realm = AutoRealm::new_from_handle(cx, window_jsobject);
273 let cx = &mut realm;
274
275 rooted!(&in(cx) let js_proxy = handler.new_window_proxy(cx, window_jsobject));
277 assert!(!js_proxy.is_null());
278
279 SetProxyReservedSlot(
282 js_proxy.get(),
283 0,
284 &PrivateValue(&raw const (*window_proxy) as *const libc::c_void),
285 );
286
287 SetWindowProxy(cx, window_jsobject, js_proxy.handle());
289
290 debug!(
292 "Initializing reflector of {:p} to {:p}.",
293 window_proxy,
294 js_proxy.get()
295 );
296 window_proxy
297 .reflector
298 .init_reflector::<WindowProxy>(js_proxy.get());
299 DomRoot::from_ref(&*Box::into_raw(window_proxy))
300 }
301 }
302
303 fn create_auxiliary_browsing_context(
305 &self,
306 cx: &mut JSContext,
307 name: DOMString,
308 noopener: bool,
309 ) -> Option<DomRoot<WindowProxy>> {
310 let (response_sender, response_receiver) = generic_channel::channel().unwrap();
311 let window = self
312 .currently_active
313 .get()
314 .and_then(ScriptThread::find_document)
315 .map(|doc| DomRoot::from_ref(doc.window()))
316 .unwrap();
317
318 let document = self
319 .currently_active
320 .get()
321 .and_then(ScriptThread::find_document)
322 .expect("A WindowProxy creating an auxiliary to have an active document");
323
324 let sandboxing_flag_set = document.active_sandboxing_flag_set();
332 let propagate_sandbox = sandboxing_flag_set
333 .contains(SandboxingFlagSet::SANDBOX_PROPOGATES_TO_AUXILIARY_BROWSING_CONTEXTS_FLAG);
334 let sandboxing_flag_set = if propagate_sandbox {
335 sandboxing_flag_set
336 } else {
337 SandboxingFlagSet::empty()
338 };
339
340 let blank_url = ServoUrl::parse("about:blank").ok().unwrap();
341 let load_data = LoadData::new(
342 LoadOrigin::Script(document.origin().snapshot()),
343 blank_url,
344 Some(document.base_url()),
345 Some(window.pipeline_id()),
348 document.global().get_referrer(),
349 document.get_referrer_policy(),
350 None, None,
352 false,
353 sandboxing_flag_set,
354 );
355 let load_info = AuxiliaryWebViewCreationRequest {
356 load_data: load_data.clone(),
357 opener_webview_id: window.webview_id(),
358 opener_pipeline_id: self.currently_active.get().unwrap(),
359 response_sender,
360 };
361 let constellation_msg = ScriptToConstellationMessage::CreateAuxiliaryWebView(load_info);
362 window.send_to_constellation(constellation_msg);
363
364 let response = response_receiver.recv().unwrap()?;
365 let new_browsing_context_id = BrowsingContextId::from(response.new_webview_id);
366 let new_pipeline_info = NewPipelineInfo {
367 parent_info: None,
368 new_pipeline_id: response.new_pipeline_id,
369 browsing_context_id: new_browsing_context_id,
370 webview_id: response.new_webview_id,
371 opener: Some(self.browsing_context_id),
372 load_data,
373 viewport_details: window.viewport_details(),
374 user_content_manager_id: response.user_content_manager_id,
375 theme: window.theme(),
378 target_snapshot_params: TargetSnapshotParams {
379 sandboxing_flags: sandboxing_flag_set,
380 iframe_element_referrer_policy: ReferrerPolicy::EmptyString,
381 },
382 };
383
384 with_script_thread(|script_thread| {
385 script_thread.spawn_pipeline(cx, new_pipeline_info);
386 });
387
388 let new_window_proxy = ScriptThread::find_document(response.new_pipeline_id)
389 .and_then(|doc| doc.browsing_context())?;
390 if name.to_lowercase() != "_blank" {
391 new_window_proxy.set_name(name);
392 }
393 if noopener {
394 new_window_proxy.disown();
395 } else {
396 let (sender, receiver) = generic_channel::channel().unwrap();
401
402 let msg = WebStorageThreadMsg::Clone {
403 sender,
404 src: window.window_proxy().webview_id(),
405 dest: response.new_webview_id,
406 };
407
408 GenericSend::send(document.global().storage_threads(), msg).unwrap();
409 receiver.recv().unwrap();
410 }
411 Some(new_window_proxy)
412 }
413
414 pub(crate) fn is_delaying_load_events_mode(&self) -> bool {
416 self.delaying_load_events_mode.get()
417 }
418
419 pub(crate) fn start_delaying_load_events_mode(&self) {
421 self.delaying_load_events_mode.set(true);
422 }
423
424 pub(crate) fn stop_delaying_load_events_mode(&self) {
426 self.delaying_load_events_mode.set(false);
427 if let Some(document) = self.document() &&
428 !document.loader().events_inhibited()
429 {
430 ScriptThread::mark_document_with_no_blocked_loads(&document);
431 }
432 }
433
434 pub(crate) fn disown(&self) {
436 self.disowned.set(true);
437 }
438
439 pub(crate) fn close(&self) {
442 self.is_closing.set(true);
443 }
444
445 pub(crate) fn is_closing(&self) -> bool {
447 self.is_closing.get()
448 }
449
450 pub(crate) fn opener(&self, cx: &mut CurrentRealm, mut retval: MutableHandleValue) {
452 if self.disowned.get() {
453 return retval.set(NullValue());
454 }
455 let opener_id = match self.opener {
456 Some(opener_browsing_context_id) => opener_browsing_context_id,
457 None => return retval.set(NullValue()),
458 };
459 let parent_browsing_context = self.parent.as_deref();
460 let opener_proxy = match self.script_window_proxies.find_window_proxy(opener_id) {
461 Some(window_proxy) => window_proxy,
462 None => {
463 let sender_pipeline_id = self.currently_active().unwrap();
464 match ScriptThread::get_top_level_for_browsing_context(
465 self.webview_id(),
466 sender_pipeline_id,
467 opener_id,
468 ) {
469 Some(opener_top_id) => {
470 let global_to_clone_from = GlobalScope::from_current_realm(cx);
471 let creator =
472 CreatorBrowsingContextInfo::from(parent_browsing_context, None);
473 WindowProxy::new_dissimilar_origin(
474 cx,
475 &global_to_clone_from,
476 opener_id,
477 opener_top_id,
478 None,
479 None,
480 creator,
481 )
482 },
483 None => return retval.set(NullValue()),
484 }
485 },
486 };
487 if opener_proxy.is_browsing_context_discarded() {
488 return retval.set(NullValue());
489 }
490 opener_proxy.safe_to_jsval(cx, retval);
491 }
492
493 pub(crate) fn open(
495 &self,
496 cx: &mut JSContext,
497 url: USVString,
498 target: DOMString,
499 features: DOMString,
500 ) -> Fallible<Option<DomRoot<WindowProxy>>> {
501 if self.discarded.get() {
507 return Ok(None);
508 }
509 let non_empty_target = if target.is_empty() {
511 DOMString::from("_blank")
512 } else {
513 target
514 };
515 let tokenized_features = tokenize_open_features(features);
517 let noreferrer = parse_open_feature_boolean(&tokenized_features, "noreferrer");
521
522 let noopener = if noreferrer {
525 true
526 } else {
527 parse_open_feature_boolean(&tokenized_features, "noopener")
528 };
529 let (chosen, new) = match self.choose_browsing_context(cx, non_empty_target, noopener) {
539 (Some(chosen), new) => (chosen, new),
540 (None, _) => return Ok(None),
541 };
542 let target_document = match chosen.document() {
545 Some(target_document) => target_document,
546 None => return Ok(None),
547 };
548 let has_trustworthy_ancestor_origin = if new {
549 target_document.has_trustworthy_ancestor_or_current_origin()
550 } else {
551 false
552 };
553 let target_window = target_document.window();
554 if !url.is_empty() {
557 let existing_document = self
558 .currently_active
559 .get()
560 .and_then(ScriptThread::find_document)
561 .unwrap();
562 let url = match existing_document.url().join(&url) {
563 Ok(url) => url,
564 Err(_) => return Err(Error::Syntax(None)),
565 };
566 let referrer = if noreferrer {
567 Referrer::NoReferrer
568 } else {
569 target_window.as_global_scope().get_referrer()
570 };
571 let csp_list = existing_document.get_csp_list().clone();
573 target_document.set_csp_list(csp_list);
574
575 let mut load_data = LoadData::new(
579 LoadOrigin::Script(existing_document.origin().snapshot()),
580 url,
581 target_document.about_base_url(),
582 Some(target_window.pipeline_id()),
583 referrer,
584 target_document.get_referrer_policy(),
585 Some(target_window.as_global_scope().is_secure_context()),
586 Some(target_document.insecure_requests_policy()),
587 has_trustworthy_ancestor_origin,
588 target_document.creation_sandboxing_flag_set_considering_parent_iframe(),
589 );
590
591 if load_data.url.scheme() == "javascript" {
594 let existing_global = existing_document.global();
595
596 if !ScriptThread::can_navigate_to_javascript_url(
598 cx,
599 &existing_global,
600 target_window.as_global_scope(),
601 &mut load_data,
602 None,
603 ) {
604 return Ok(target_document.browsing_context());
606 }
607 }
608
609 let history_handling = if new {
610 NavigationHistoryBehavior::Replace
611 } else {
612 NavigationHistoryBehavior::Push
613 };
614 navigate(cx, target_window, history_handling, false, load_data);
615 }
616 if noopener {
618 return Ok(None);
619 }
620 Ok(target_document.browsing_context())
622 }
623
624 pub(crate) fn choose_browsing_context(
626 &self,
627 cx: &mut JSContext,
628 name: DOMString,
629 noopener: bool,
630 ) -> (Option<DomRoot<WindowProxy>>, bool) {
631 match name.to_lowercase().as_ref() {
632 "" | "_self" => {
633 (Some(DomRoot::from_ref(self)), false)
635 },
636 "_parent" => {
637 if let Some(parent) = self.parent() {
639 return (Some(DomRoot::from_ref(parent)), false);
640 }
641 (None, false)
642 },
643 "_top" => {
644 (Some(DomRoot::from_ref(self.top())), false)
646 },
647 "_blank" => (
648 self.create_auxiliary_browsing_context(cx, name, noopener),
649 true,
650 ),
651 _ => {
652 match ScriptThread::find_window_proxy_by_name(&name) {
657 Some(proxy) => (Some(proxy), false),
658 None => (
659 self.create_auxiliary_browsing_context(cx, name, noopener),
660 true,
661 ),
662 }
663 },
664 }
665 }
666
667 pub(crate) fn is_auxiliary(&self) -> bool {
668 self.opener.is_some()
669 }
670
671 pub(crate) fn discard_browsing_context(&self) {
672 self.discarded.set(true);
673 }
674
675 pub(crate) fn is_browsing_context_discarded(&self) -> bool {
676 self.discarded.get()
677 }
678
679 pub(crate) fn browsing_context_id(&self) -> BrowsingContextId {
680 self.browsing_context_id
681 }
682
683 pub(crate) fn webview_id(&self) -> WebViewId {
684 self.webview_id
685 }
686
687 pub(crate) fn frame_element(&self) -> Option<&Element> {
691 self.frame_element.as_deref()
692 }
693
694 pub(crate) fn document(&self) -> Option<DomRoot<Document>> {
695 self.currently_active
696 .get()
697 .and_then(ScriptThread::find_document)
698 }
699
700 pub(crate) fn parent(&self) -> Option<&WindowProxy> {
701 self.parent.as_deref()
702 }
703
704 pub(crate) fn top(&self) -> &WindowProxy {
705 let mut result = self;
706 while let Some(parent) = result.parent() {
707 result = parent;
708 }
709 result
710 }
711
712 pub fn document_origin(&self) -> Option<String> {
713 let pipeline_id = self.currently_active()?;
714 let (result_sender, result_receiver) = generic_channel::channel().unwrap();
715 self.global()
716 .script_to_constellation_chan()
717 .send(ScriptToConstellationMessage::GetDocumentOrigin(
718 pipeline_id,
719 result_sender,
720 ))
721 .ok()?;
722 result_receiver.recv().ok()?
723 }
724
725 #[expect(unsafe_code)]
726 fn set_window(&self, cx: &mut JSContext, window: &GlobalScope, handler: &WindowProxyHandler) {
730 unsafe {
731 debug!("Setting window of {:p}.", self);
732
733 let window_jsobject = window.reflector().get_jsobject();
734 let old_js_proxy = self.reflector.get_jsobject();
735 assert!(!window_jsobject.get().is_null());
736 assert_ne!(
737 ((*get_object_class(window_jsobject.get())).flags & JSCLASS_IS_GLOBAL),
738 0
739 );
740
741 let mut realm = AutoRealm::new_from_handle(cx, window_jsobject);
742 let cx = &mut realm;
743
744 SetProxyReservedSlot(old_js_proxy.get(), 0, &PrivateValue(ptr::null_mut()));
746
747 rooted!(&in(cx) let new_js_proxy = handler.new_window_proxy(cx, window_jsobject));
753 SetProxyReservedSlot(new_js_proxy.get(), 0, &PrivateValue(ptr::null_mut()));
756 debug!(
757 "Transplanting proxy from {:p} to {:p}.",
758 old_js_proxy.get(),
759 new_js_proxy.get()
760 );
761 rooted!(&in(cx) let new_js_proxy = JS_TransplantObject(cx, old_js_proxy, new_js_proxy.handle()));
762 debug!("Transplanted proxy is {:p}.", new_js_proxy.get());
763
764 SetProxyReservedSlot(
766 new_js_proxy.get(),
767 0,
768 &PrivateValue(self as *const _ as *const libc::c_void),
769 );
770
771 SetWindowProxy(cx, window_jsobject, new_js_proxy.handle());
773
774 debug!(
776 "Setting reflector of {:p} to {:p}.",
777 self,
778 new_js_proxy.get()
779 );
780 self.reflector.rootable().set(new_js_proxy.get());
781 }
782 }
783
784 pub(crate) fn set_currently_active(&self, cx: &mut JSContext, window: &Window) {
785 if let Some(pipeline_id) = self.currently_active() &&
786 pipeline_id == window.pipeline_id()
787 {
788 return debug!(
789 "Attempt to set the currently active window to the currently active window."
790 );
791 }
792
793 let global_scope = window.as_global_scope();
794 self.set_window(cx, global_scope, WindowProxyHandler::proxy_handler());
795 self.currently_active.set(Some(global_scope.pipeline_id()));
796 }
797
798 pub(crate) fn unset_currently_active(&self, cx: &mut JSContext) {
799 if self.currently_active().is_none() {
800 return debug!(
801 "Attempt to unset the currently active window on a windowproxy that does not have one."
802 );
803 }
804 let globalscope = self.global();
805 let window = DissimilarOriginWindow::new(cx, &globalscope, self);
806 self.set_window(
807 cx,
808 window.upcast(),
809 WindowProxyHandler::x_origin_proxy_handler(),
810 );
811 self.currently_active.set(None);
812 }
813
814 pub(crate) fn currently_active(&self) -> Option<PipelineId> {
815 self.currently_active.get()
816 }
817
818 pub(crate) fn get_name(&self) -> DOMString {
819 self.name.borrow().clone()
820 }
821
822 pub(crate) fn set_name(&self, name: DOMString) {
823 *self.name.borrow_mut() = name;
824 }
825}
826
827#[derive(Debug, Deserialize, Serialize)]
839pub(crate) struct CreatorBrowsingContextInfo {
840 url: Option<ServoUrl>,
842
843 origin: Option<ImmutableOrigin>,
845}
846
847impl CreatorBrowsingContextInfo {
848 pub(crate) fn from(
849 parent: Option<&WindowProxy>,
850 opener: Option<&WindowProxy>,
851 ) -> CreatorBrowsingContextInfo {
852 let creator = match (parent, opener) {
853 (Some(parent), _) => parent.document(),
854 (None, Some(opener)) => opener.document(),
855 (None, None) => None,
856 };
857
858 let url = creator.as_deref().map(|document| document.url());
859 let origin = creator
860 .as_deref()
861 .map(|document| document.origin().immutable().clone());
862
863 CreatorBrowsingContextInfo { url, origin }
864 }
865}
866
867fn tokenize_open_features(features: DOMString) -> IndexMap<String, String> {
869 let is_feature_sep = |c: char| c.is_ascii_whitespace() || ['=', ','].contains(&c);
870 let mut tokenized_features = IndexMap::new();
872 let features = features.str();
874 let mut iter = features.chars();
875 let mut cur = iter.next();
876
877 while cur.is_some() {
879 let mut name = String::new();
881 let mut value = String::new();
882 while let Some(cur_char) = cur {
884 if !is_feature_sep(cur_char) {
885 break;
886 }
887 cur = iter.next();
888 }
889 while let Some(cur_char) = cur {
891 if is_feature_sep(cur_char) {
892 break;
893 }
894 name.push(cur_char.to_ascii_lowercase());
895 cur = iter.next();
896 }
897 let normalized_name = String::from(match name.as_ref() {
899 "screenx" => "left",
900 "screeny" => "top",
901 "innerwidth" => "width",
902 "innerheight" => "height",
903 _ => name.as_ref(),
904 });
905 while let Some(cur_char) = cur {
907 if cur_char == '=' || cur_char == ',' || !is_feature_sep(cur_char) {
908 break;
909 }
910 cur = iter.next();
911 }
912 if cur.is_some() && is_feature_sep(cur.unwrap()) {
914 while let Some(cur_char) = cur {
916 if !is_feature_sep(cur_char) || cur_char == ',' {
917 break;
918 }
919 cur = iter.next();
920 }
921 while let Some(cur_char) = cur {
923 if is_feature_sep(cur_char) {
924 break;
925 }
926 value.push(cur_char.to_ascii_lowercase());
927 cur = iter.next();
928 }
929 }
930 if !name.is_empty() {
932 tokenized_features.insert(normalized_name, value);
933 }
934 }
935 tokenized_features
937}
938
939fn parse_open_feature_boolean(tokenized_features: &IndexMap<String, String>, name: &str) -> bool {
941 if let Some(value) = tokenized_features.get(name) {
942 if value.is_empty() || value == "yes" {
944 return true;
945 }
946 if let Ok(int) = parse_integer(value.chars()) {
948 return int != 0;
949 }
950 }
951 false
953}
954
955#[expect(unsafe_code)]
959#[expect(non_snake_case)]
960unsafe fn GetSubframeWindowProxy(
961 cx: *mut RawJSContext,
962 proxy: RawHandleObject,
963 id: RawHandleId,
964) -> Option<(DomRoot<WindowProxy>, u32)> {
965 let index = get_array_index_from_id(unsafe { Handle::from_raw(id) });
966 if let Some(index) = index {
967 let mut slot = UndefinedValue();
968 unsafe { GetProxyPrivate(*proxy, &mut slot) };
969 rooted!(in(cx) let target = slot.to_object());
970 let script_window_proxies = ScriptThread::window_proxies();
971 if let Ok(win) = root_from_handleobject::<Window>(target.handle(), cx) {
972 let browsing_context_id = win.window_proxy().browsing_context_id();
973 let (result_sender, result_receiver) = generic_channel::channel().unwrap();
974
975 let _ = win.as_global_scope().script_to_constellation_chan().send(
976 ScriptToConstellationMessage::GetChildBrowsingContextId(
977 browsing_context_id,
978 index as usize,
979 result_sender,
980 ),
981 );
982 return result_receiver
983 .recv()
984 .ok()
985 .and_then(|maybe_bcid| maybe_bcid)
986 .and_then(|id| script_window_proxies.find_window_proxy(id))
987 .map(|proxy| (proxy, (JSPROP_ENUMERATE | JSPROP_READONLY) as u32));
988 } else if let Ok(win) =
989 root_from_handleobject::<DissimilarOriginWindow>(target.handle(), cx)
990 {
991 let browsing_context_id = win.window_proxy().browsing_context_id();
992 let (result_sender, result_receiver) = generic_channel::channel().unwrap();
993
994 let _ = win.global().script_to_constellation_chan().send(
995 ScriptToConstellationMessage::GetChildBrowsingContextId(
996 browsing_context_id,
997 index as usize,
998 result_sender,
999 ),
1000 );
1001 return result_receiver
1002 .recv()
1003 .ok()
1004 .and_then(|maybe_bcid| maybe_bcid)
1005 .and_then(|id| script_window_proxies.find_window_proxy(id))
1006 .map(|proxy| (proxy, JSPROP_READONLY as u32));
1007 }
1008 }
1009
1010 None
1011}
1012
1013#[expect(unsafe_code)]
1014unsafe extern "C" fn get_own_property_descriptor(
1015 cx: *mut RawJSContext,
1016 proxy: RawHandleObject,
1017 id: RawHandleId,
1018 desc: RawMutableHandle<PropertyDescriptor>,
1019 is_none: *mut bool,
1020) -> bool {
1021 let window = unsafe { GetSubframeWindowProxy(cx, proxy, id) };
1022 if let Some((window, attrs)) = window {
1023 rooted!(in(cx) let mut val = UndefinedValue());
1024 unsafe { window.to_jsval(cx, val.handle_mut()) };
1025 set_property_descriptor(
1026 unsafe { MutableHandle::from_raw(desc) },
1027 val.handle(),
1028 attrs,
1029 unsafe { &mut *is_none },
1030 );
1031 return true;
1032 }
1033
1034 let mut slot = UndefinedValue();
1035 unsafe { GetProxyPrivate(proxy.get(), &mut slot) };
1036 rooted!(in(cx) let target = slot.to_object());
1037 unsafe { JS_GetOwnPropertyDescriptorById(cx, target.handle().into(), id, desc, is_none) }
1038}
1039
1040#[expect(unsafe_code)]
1041unsafe extern "C" fn define_property(
1042 cx: *mut RawJSContext,
1043 proxy: RawHandleObject,
1044 id: RawHandleId,
1045 desc: RawHandle<PropertyDescriptor>,
1046 res: *mut ObjectOpResult,
1047) -> bool {
1048 if get_array_index_from_id(unsafe { Handle::from_raw(id) }).is_some() {
1049 unsafe {
1054 (*res).code_ = JSErrNum::JSMSG_CANT_DEFINE_WINDOW_ELEMENT as ::libc::uintptr_t;
1055 }
1056 return true;
1057 }
1058
1059 let mut slot = UndefinedValue();
1060 unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1061 rooted!(in(cx) let target = slot.to_object());
1062 unsafe { JS_DefinePropertyById(cx, target.handle().into(), id, desc, res) }
1063}
1064
1065#[expect(unsafe_code)]
1066unsafe extern "C" fn has(
1067 cx: *mut RawJSContext,
1068 proxy: RawHandleObject,
1069 id: RawHandleId,
1070 bp: *mut bool,
1071) -> bool {
1072 let window = unsafe { GetSubframeWindowProxy(cx, proxy, id) };
1073 if window.is_some() {
1074 unsafe { *bp = true };
1075 return true;
1076 }
1077
1078 let mut slot = UndefinedValue();
1079 unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1080 rooted!(in(cx) let target = slot.to_object());
1081 let mut found = false;
1082 if !unsafe { JS_HasPropertyById(cx, target.handle().into(), id, &mut found) } {
1083 return false;
1084 }
1085
1086 unsafe { *bp = found };
1087 true
1088}
1089
1090#[expect(unsafe_code)]
1091unsafe extern "C" fn get(
1092 cx: *mut RawJSContext,
1093 proxy: RawHandleObject,
1094 receiver: RawHandleValue,
1095 id: RawHandleId,
1096 vp: RawMutableHandleValue,
1097) -> bool {
1098 let window = unsafe { GetSubframeWindowProxy(cx, proxy, id) };
1099 if let Some((window, _attrs)) = window {
1100 unsafe { window.to_jsval(cx, MutableHandle::from_raw(vp)) };
1101 return true;
1102 }
1103
1104 let mut slot = UndefinedValue();
1105 unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1106 rooted!(in(cx) let target = slot.to_object());
1107 unsafe { JS_ForwardGetPropertyTo(cx, target.handle().into(), id, receiver, vp) }
1108}
1109
1110#[expect(unsafe_code)]
1111unsafe extern "C" fn set(
1112 cx: *mut RawJSContext,
1113 proxy: RawHandleObject,
1114 id: RawHandleId,
1115 v: RawHandleValue,
1116 receiver: RawHandleValue,
1117 res: *mut ObjectOpResult,
1118) -> bool {
1119 if get_array_index_from_id(unsafe { Handle::from_raw(id) }).is_some() {
1120 unsafe { (*res).code_ = JSErrNum::JSMSG_READ_ONLY as ::libc::uintptr_t };
1122 return true;
1123 }
1124
1125 let mut slot = UndefinedValue();
1126 unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1127 rooted!(in(cx) let target = slot.to_object());
1128 unsafe { JS_ForwardSetPropertyTo(cx, target.handle().into(), id, v, receiver, res) }
1129}
1130
1131#[expect(unsafe_code)]
1132unsafe extern "C" fn get_prototype_if_ordinary(
1133 _: *mut RawJSContext,
1134 _: RawHandleObject,
1135 is_ordinary: *mut bool,
1136 _: RawMutableHandleObject,
1137) -> bool {
1138 unsafe { *is_ordinary = false };
1151 true
1152}
1153
1154static PROXY_TRAPS: ProxyTraps = ProxyTraps {
1155 enter: None,
1158 getOwnPropertyDescriptor: Some(get_own_property_descriptor),
1159 defineProperty: Some(define_property),
1160 ownPropertyKeys: None,
1161 delete_: None,
1162 enumerate: None,
1163 getPrototypeIfOrdinary: Some(get_prototype_if_ordinary),
1164 getPrototype: None, setPrototype: None,
1166 setImmutablePrototype: None,
1167 preventExtensions: None,
1168 isExtensible: None,
1169 has: Some(has),
1170 get: Some(get),
1171 set: Some(set),
1172 call: None,
1173 construct: None,
1174 hasOwn: None,
1175 getOwnEnumerablePropertyKeys: None,
1176 nativeCall: None,
1177 objectClassIs: None,
1178 className: None,
1179 fun_toString: None,
1180 boxedValue_unbox: None,
1181 defaultValue: None,
1182 trace: Some(trace),
1183 finalize: Some(finalize),
1184 objectMoved: None,
1185 isCallable: None,
1186 isConstructor: None,
1187};
1188
1189pub(crate) struct WindowProxyHandler(*const libc::c_void);
1192
1193impl MallocSizeOf for WindowProxyHandler {
1194 fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize {
1195 0
1197 }
1198}
1199
1200#[expect(unsafe_code)]
1202unsafe impl Send for WindowProxyHandler {}
1203#[expect(unsafe_code)]
1205unsafe impl Sync for WindowProxyHandler {}
1206
1207#[expect(unsafe_code)]
1208impl WindowProxyHandler {
1209 fn new(traps: &ProxyTraps) -> Self {
1210 let ptr = unsafe { CreateWrapperProxyHandler(traps) };
1212 assert!(!ptr.is_null());
1213 Self(ptr)
1214 }
1215
1216 pub(crate) fn x_origin_proxy_handler() -> &'static Self {
1218 use std::sync::OnceLock;
1219 static SINGLETON: OnceLock<WindowProxyHandler> = OnceLock::new();
1225 SINGLETON.get_or_init(|| Self::new(&XORIGIN_PROXY_TRAPS))
1226 }
1227
1228 pub(crate) fn proxy_handler() -> &'static Self {
1230 use std::sync::OnceLock;
1231 static SINGLETON: OnceLock<WindowProxyHandler> = OnceLock::new();
1237 SINGLETON.get_or_init(|| Self::new(&PROXY_TRAPS))
1238 }
1239
1240 fn new_window_proxy(
1243 &self,
1244 cx: &mut JSContext,
1245 window_jsobject: js::gc::HandleObject,
1246 ) -> *mut JSObject {
1247 let obj = unsafe { NewWindowProxy(cx, window_jsobject, self.0) };
1248 assert!(!obj.is_null());
1249 obj
1250 }
1251}
1252
1253#[expect(unsafe_code)]
1254impl Drop for WindowProxyHandler {
1255 fn drop(&mut self) {
1256 unsafe {
1259 DeleteWrapperProxyHandler(self.0);
1260 }
1261 }
1262}
1263
1264#[expect(unsafe_code)]
1272fn throw_security_error(realm: &mut CurrentRealm) -> bool {
1273 if !unsafe { JS_IsExceptionPending(realm) } {
1274 let global = GlobalScope::from_current_realm(realm);
1275 throw_dom_exception(realm, &global, Error::Security(None));
1276 }
1277 false
1278}
1279
1280#[expect(unsafe_code)]
1281unsafe extern "C" fn has_xorigin(
1282 cx: *mut RawJSContext,
1283 proxy: RawHandleObject,
1284 id: RawHandleId,
1285 bp: *mut bool,
1286) -> bool {
1287 let mut cx = unsafe {
1288 JSContext::from_ptr(NonNull::new(cx).expect("JSContext should not be null in SM hook"))
1290 };
1291 let mut slot = UndefinedValue();
1292 unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1293 rooted!(&in(cx) let target = slot.to_object());
1294 let mut found = false;
1295 unsafe { JS_HasOwnPropertyById(&mut cx, target.handle(), Handle::from_raw(id), &mut found) };
1296 if found {
1297 unsafe { *bp = true };
1298 true
1299 } else {
1300 let mut realm = CurrentRealm::assert(&mut cx);
1301 throw_security_error(&mut realm)
1302 }
1303}
1304
1305#[expect(unsafe_code)]
1306unsafe extern "C" fn get_xorigin(
1307 cx: *mut RawJSContext,
1308 proxy: RawHandleObject,
1309 receiver: RawHandleValue,
1310 id: RawHandleId,
1311 vp: RawMutableHandleValue,
1312) -> bool {
1313 let mut found = false;
1314 unsafe { has_xorigin(cx, proxy, id, &mut found) };
1315 found && unsafe { get(cx, proxy, receiver, id, vp) }
1316}
1317
1318#[expect(unsafe_code)]
1319unsafe extern "C" fn set_xorigin(
1320 cx: *mut RawJSContext,
1321 _: RawHandleObject,
1322 _: RawHandleId,
1323 _: RawHandleValue,
1324 _: RawHandleValue,
1325 _: *mut ObjectOpResult,
1326) -> bool {
1327 let mut cx = unsafe {
1328 JSContext::from_ptr(NonNull::new(cx).expect("JSContext should not be null in SM hook"))
1330 };
1331 let mut realm = CurrentRealm::assert(&mut cx);
1332 throw_security_error(&mut realm)
1333}
1334
1335#[expect(unsafe_code)]
1336unsafe extern "C" fn delete_xorigin(
1337 cx: *mut RawJSContext,
1338 _: RawHandleObject,
1339 _: RawHandleId,
1340 _: *mut ObjectOpResult,
1341) -> bool {
1342 let mut cx = unsafe {
1343 JSContext::from_ptr(NonNull::new(cx).expect("JSContext should not be null in SM hook"))
1345 };
1346 let mut realm = CurrentRealm::assert(&mut cx);
1347 throw_security_error(&mut realm)
1348}
1349
1350#[expect(unsafe_code)]
1351#[expect(non_snake_case)]
1352unsafe extern "C" fn getOwnPropertyDescriptor_xorigin(
1353 cx: *mut RawJSContext,
1354 proxy: RawHandleObject,
1355 id: RawHandleId,
1356 desc: RawMutableHandle<PropertyDescriptor>,
1357 is_none: *mut bool,
1358) -> bool {
1359 let mut found = false;
1360 unsafe { has_xorigin(cx, proxy, id, &mut found) };
1361 found && unsafe { get_own_property_descriptor(cx, proxy, id, desc, is_none) }
1362}
1363
1364#[expect(unsafe_code)]
1365#[expect(non_snake_case)]
1366unsafe extern "C" fn defineProperty_xorigin(
1367 cx: *mut RawJSContext,
1368 _: RawHandleObject,
1369 _: RawHandleId,
1370 _: RawHandle<PropertyDescriptor>,
1371 _: *mut ObjectOpResult,
1372) -> bool {
1373 let mut cx = unsafe {
1374 JSContext::from_ptr(NonNull::new(cx).expect("JSContext should not be null in SM hook"))
1376 };
1377 let mut realm = CurrentRealm::assert(&mut cx);
1378 throw_security_error(&mut realm)
1379}
1380
1381#[expect(unsafe_code)]
1382#[expect(non_snake_case)]
1383unsafe extern "C" fn preventExtensions_xorigin(
1384 cx: *mut RawJSContext,
1385 _: RawHandleObject,
1386 _: *mut ObjectOpResult,
1387) -> bool {
1388 let mut cx = unsafe {
1389 JSContext::from_ptr(NonNull::new(cx).expect("JSContext should not be null in SM hook"))
1391 };
1392 let mut realm = CurrentRealm::assert(&mut cx);
1393 throw_security_error(&mut realm)
1394}
1395
1396static XORIGIN_PROXY_TRAPS: ProxyTraps = ProxyTraps {
1397 enter: None,
1398 getOwnPropertyDescriptor: Some(getOwnPropertyDescriptor_xorigin),
1399 defineProperty: Some(defineProperty_xorigin),
1400 ownPropertyKeys: None,
1401 delete_: Some(delete_xorigin),
1402 enumerate: None,
1403 getPrototypeIfOrdinary: None,
1404 getPrototype: None,
1405 setPrototype: None,
1406 setImmutablePrototype: None,
1407 preventExtensions: Some(preventExtensions_xorigin),
1408 isExtensible: None,
1409 has: Some(has_xorigin),
1410 get: Some(get_xorigin),
1411 set: Some(set_xorigin),
1412 call: None,
1413 construct: None,
1414 hasOwn: Some(has_xorigin),
1415 getOwnEnumerablePropertyKeys: None,
1416 nativeCall: None,
1417 objectClassIs: None,
1418 className: None,
1419 fun_toString: None,
1420 boxedValue_unbox: None,
1421 defaultValue: None,
1422 trace: Some(trace),
1423 finalize: Some(finalize),
1424 objectMoved: None,
1425 isCallable: None,
1426 isConstructor: None,
1427};
1428
1429#[expect(unsafe_code)]
1432unsafe extern "C" fn finalize(_fop: *mut GCContext, obj: *mut JSObject) {
1433 let mut slot = UndefinedValue();
1434 unsafe { GetProxyReservedSlot(obj, 0, &mut slot) };
1435 let this = slot.to_private() as *mut WindowProxy;
1436 if this.is_null() {
1437 return;
1439 }
1440 unsafe {
1441 (*this).reflector.drop_memory(&*this);
1442 let jsobject = (*this).reflector.get_jsobject().get();
1443 debug!(
1444 "WindowProxy finalize: {:p}, with reflector {:p} from {:p}.",
1445 this, jsobject, obj
1446 );
1447 let _ = Box::from_raw(this);
1448 }
1449}
1450
1451#[expect(unsafe_code)]
1452unsafe extern "C" fn trace(trc: *mut JSTracer, obj: *mut JSObject) {
1453 let mut slot = UndefinedValue();
1454 unsafe { GetProxyReservedSlot(obj, 0, &mut slot) };
1455 let this = slot.to_private() as *const WindowProxy;
1456 if this.is_null() {
1457 return;
1459 }
1460 unsafe { (*this).trace(trc) };
1461}