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, JSAutoRealm,
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::wrappers::{JS_TransplantObject, NewWindowProxy, SetWindowProxy};
30use js::rust::wrappers2::{JS_HasOwnPropertyById, JS_IsExceptionPending};
31use js::rust::{Handle, MutableHandle, MutableHandleValue, get_object_class};
32use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
33use net_traits::ReferrerPolicy;
34use net_traits::request::Referrer;
35use script_bindings::cell::DomRefCell;
36use script_bindings::reflector::{DomObject, MutDomObject, Reflector};
37use script_traits::NewPipelineInfo;
38use serde::{Deserialize, Serialize};
39use servo_base::generic_channel;
40use servo_base::generic_channel::GenericSend;
41use servo_base::id::{BrowsingContextId, PipelineId, WebViewId};
42use servo_constellation_traits::{
43 AuxiliaryWebViewCreationRequest, LoadData, LoadOrigin, NavigationHistoryBehavior,
44 ScriptToConstellationMessage, TargetSnapshotParams,
45};
46use servo_url::{ImmutableOrigin, ServoUrl};
47use storage_traits::webstorage_thread::WebStorageThreadMsg;
48use style::attr::parse_integer;
49
50use crate::dom::bindings::conversions::{ToJSValConvertible, root_from_handleobject};
51use crate::dom::bindings::error::{Error, Fallible, throw_dom_exception};
52use crate::dom::bindings::inheritance::Castable;
53use crate::dom::bindings::proxyhandler::set_property_descriptor;
54use crate::dom::bindings::reflector::DomGlobal;
55use crate::dom::bindings::root::{Dom, DomRoot};
56use crate::dom::bindings::str::{DOMString, USVString};
57use crate::dom::bindings::trace::JSTraceable;
58use crate::dom::bindings::utils::get_array_index_from_id;
59use crate::dom::dissimilaroriginwindow::DissimilarOriginWindow;
60use crate::dom::document::Document;
61use crate::dom::element::Element;
62use crate::dom::globalscope::GlobalScope;
63use crate::dom::window::Window;
64use crate::navigation::navigate;
65use crate::realms::enter_realm;
66use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
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 pub(crate) fn new(
175 window: &Window,
176 browsing_context_id: BrowsingContextId,
177 webview_id: WebViewId,
178 frame_element: Option<&Element>,
179 parent: Option<&WindowProxy>,
180 opener: Option<BrowsingContextId>,
181 creator: CreatorBrowsingContextInfo,
182 ) -> DomRoot<WindowProxy> {
183 unsafe {
184 let handler = window.windowproxy_handler();
185
186 let cx = GlobalScope::get_cx();
187 let window_jsobject = window.reflector().get_jsobject();
188 assert!(!window_jsobject.get().is_null());
189 assert_ne!(
190 ((*get_object_class(window_jsobject.get())).flags & JSCLASS_IS_GLOBAL),
191 0
192 );
193 let _ac = JSAutoRealm::new(*cx, window_jsobject.get());
194
195 rooted!(in(*cx) let js_proxy = handler.new_window_proxy(&cx, window_jsobject));
197 assert!(!js_proxy.is_null());
198
199 let current = Some(window.upcast::<GlobalScope>().pipeline_id());
202 let window_proxy = Box::new(WindowProxy::new_inherited(
203 browsing_context_id,
204 webview_id,
205 current,
206 frame_element,
207 parent,
208 opener,
209 creator,
210 ));
211
212 SetProxyReservedSlot(
215 js_proxy.get(),
216 0,
217 &PrivateValue(&raw const (*window_proxy) as *const libc::c_void),
218 );
219
220 SetWindowProxy(*cx, window_jsobject, js_proxy.handle());
222
223 debug!(
225 "Initializing reflector of {:p} to {:p}.",
226 window_proxy,
227 js_proxy.get()
228 );
229 window_proxy
230 .reflector
231 .init_reflector::<WindowProxy>(js_proxy.get());
232 DomRoot::from_ref(&*Box::into_raw(window_proxy))
233 }
234 }
235
236 #[expect(unsafe_code)]
237 pub(crate) fn new_dissimilar_origin(
238 cx: &mut JSContext,
239 global_to_clone_from: &GlobalScope,
240 browsing_context_id: BrowsingContextId,
241 webview_id: WebViewId,
242 parent: Option<&WindowProxy>,
243 opener: Option<BrowsingContextId>,
244 creator: CreatorBrowsingContextInfo,
245 ) -> DomRoot<WindowProxy> {
246 unsafe {
247 let handler = WindowProxyHandler::x_origin_proxy_handler();
248
249 let window_proxy = Box::new(WindowProxy::new_inherited(
251 browsing_context_id,
252 webview_id,
253 None,
254 None,
255 parent,
256 opener,
257 creator,
258 ));
259
260 let window = DissimilarOriginWindow::new(cx, global_to_clone_from, &window_proxy);
262 let window_jsobject = window.reflector().get_jsobject();
263 assert!(!window_jsobject.get().is_null());
264 assert_ne!(
265 ((*get_object_class(window_jsobject.get())).flags & JSCLASS_IS_GLOBAL),
266 0
267 );
268 let mut realm = AutoRealm::new(cx, NonNull::new(window_jsobject.get()).unwrap());
269 let cx = &mut realm;
270
271 rooted!(&in(cx) let js_proxy = handler.new_window_proxy(&cx.into(), window_jsobject));
273 assert!(!js_proxy.is_null());
274
275 SetProxyReservedSlot(
278 js_proxy.get(),
279 0,
280 &PrivateValue(&raw const (*window_proxy) as *const libc::c_void),
281 );
282
283 SetWindowProxy(cx.raw_cx(), window_jsobject, js_proxy.handle());
285
286 debug!(
288 "Initializing reflector of {:p} to {:p}.",
289 window_proxy,
290 js_proxy.get()
291 );
292 window_proxy
293 .reflector
294 .init_reflector::<WindowProxy>(js_proxy.get());
295 DomRoot::from_ref(&*Box::into_raw(window_proxy))
296 }
297 }
298
299 fn create_auxiliary_browsing_context(
301 &self,
302 cx: &mut JSContext,
303 name: DOMString,
304 noopener: bool,
305 ) -> Option<DomRoot<WindowProxy>> {
306 let (response_sender, response_receiver) = generic_channel::channel().unwrap();
307 let window = self
308 .currently_active
309 .get()
310 .and_then(ScriptThread::find_document)
311 .map(|doc| DomRoot::from_ref(doc.window()))
312 .unwrap();
313
314 let document = self
315 .currently_active
316 .get()
317 .and_then(ScriptThread::find_document)
318 .expect("A WindowProxy creating an auxiliary to have an active document");
319
320 let sandboxing_flag_set = document.active_sandboxing_flag_set();
328 let propagate_sandbox = sandboxing_flag_set
329 .contains(SandboxingFlagSet::SANDBOX_PROPOGATES_TO_AUXILIARY_BROWSING_CONTEXTS_FLAG);
330 let sandboxing_flag_set = if propagate_sandbox {
331 sandboxing_flag_set
332 } else {
333 SandboxingFlagSet::empty()
334 };
335
336 let blank_url = ServoUrl::parse("about:blank").ok().unwrap();
337 let load_data = LoadData::new(
338 LoadOrigin::Script(document.origin().snapshot()),
339 blank_url,
340 Some(document.base_url()),
341 Some(window.pipeline_id()),
344 document.global().get_referrer(),
345 document.get_referrer_policy(),
346 None, None,
348 false,
349 sandboxing_flag_set,
350 );
351 let load_info = AuxiliaryWebViewCreationRequest {
352 load_data: load_data.clone(),
353 opener_webview_id: window.webview_id(),
354 opener_pipeline_id: self.currently_active.get().unwrap(),
355 response_sender,
356 };
357 let constellation_msg = ScriptToConstellationMessage::CreateAuxiliaryWebView(load_info);
358 window.send_to_constellation(constellation_msg);
359
360 let response = response_receiver.recv().unwrap()?;
361 let new_browsing_context_id = BrowsingContextId::from(response.new_webview_id);
362 let new_pipeline_info = NewPipelineInfo {
363 parent_info: None,
364 new_pipeline_id: response.new_pipeline_id,
365 browsing_context_id: new_browsing_context_id,
366 webview_id: response.new_webview_id,
367 opener: Some(self.browsing_context_id),
368 load_data,
369 viewport_details: window.viewport_details(),
370 user_content_manager_id: response.user_content_manager_id,
371 theme: window.theme(),
374 target_snapshot_params: TargetSnapshotParams {
375 sandboxing_flags: sandboxing_flag_set,
376 iframe_element_referrer_policy: ReferrerPolicy::EmptyString,
377 },
378 };
379
380 with_script_thread(|script_thread| {
381 script_thread.spawn_pipeline(cx, new_pipeline_info);
382 });
383
384 let new_window_proxy = ScriptThread::find_document(response.new_pipeline_id)
385 .and_then(|doc| doc.browsing_context())?;
386 if name.to_lowercase() != "_blank" {
387 new_window_proxy.set_name(name);
388 }
389 if noopener {
390 new_window_proxy.disown();
391 } else {
392 let (sender, receiver) = generic_channel::channel().unwrap();
397
398 let msg = WebStorageThreadMsg::Clone {
399 sender,
400 src: window.window_proxy().webview_id(),
401 dest: response.new_webview_id,
402 };
403
404 GenericSend::send(document.global().storage_threads(), msg).unwrap();
405 receiver.recv().unwrap();
406 }
407 Some(new_window_proxy)
408 }
409
410 pub(crate) fn is_delaying_load_events_mode(&self) -> bool {
412 self.delaying_load_events_mode.get()
413 }
414
415 pub(crate) fn start_delaying_load_events_mode(&self) {
417 self.delaying_load_events_mode.set(true);
418 }
419
420 pub(crate) fn stop_delaying_load_events_mode(&self) {
422 self.delaying_load_events_mode.set(false);
423 if let Some(document) = self.document() &&
424 !document.loader().events_inhibited()
425 {
426 ScriptThread::mark_document_with_no_blocked_loads(&document);
427 }
428 }
429
430 pub(crate) fn disown(&self) {
432 self.disowned.set(true);
433 }
434
435 pub(crate) fn close(&self) {
438 self.is_closing.set(true);
439 }
440
441 pub(crate) fn is_closing(&self) -> bool {
443 self.is_closing.get()
444 }
445
446 pub(crate) fn opener(&self, cx: &mut CurrentRealm, mut retval: MutableHandleValue) {
448 if self.disowned.get() {
449 return retval.set(NullValue());
450 }
451 let opener_id = match self.opener {
452 Some(opener_browsing_context_id) => opener_browsing_context_id,
453 None => return retval.set(NullValue()),
454 };
455 let parent_browsing_context = self.parent.as_deref();
456 let opener_proxy = match self.script_window_proxies.find_window_proxy(opener_id) {
457 Some(window_proxy) => window_proxy,
458 None => {
459 let sender_pipeline_id = self.currently_active().unwrap();
460 match ScriptThread::get_top_level_for_browsing_context(
461 self.webview_id(),
462 sender_pipeline_id,
463 opener_id,
464 ) {
465 Some(opener_top_id) => {
466 let global_to_clone_from = GlobalScope::from_current_realm(cx);
467 let creator =
468 CreatorBrowsingContextInfo::from(parent_browsing_context, None);
469 WindowProxy::new_dissimilar_origin(
470 cx,
471 &global_to_clone_from,
472 opener_id,
473 opener_top_id,
474 None,
475 None,
476 creator,
477 )
478 },
479 None => return retval.set(NullValue()),
480 }
481 },
482 };
483 if opener_proxy.is_browsing_context_discarded() {
484 return retval.set(NullValue());
485 }
486 opener_proxy.safe_to_jsval(cx, retval);
487 }
488
489 pub(crate) fn open(
491 &self,
492 cx: &mut JSContext,
493 url: USVString,
494 target: DOMString,
495 features: DOMString,
496 ) -> Fallible<Option<DomRoot<WindowProxy>>> {
497 if self.discarded.get() {
503 return Ok(None);
504 }
505 let non_empty_target = if target.is_empty() {
507 DOMString::from("_blank")
508 } else {
509 target
510 };
511 let tokenized_features = tokenize_open_features(features);
513 let noreferrer = parse_open_feature_boolean(&tokenized_features, "noreferrer");
517
518 let noopener = if noreferrer {
521 true
522 } else {
523 parse_open_feature_boolean(&tokenized_features, "noopener")
524 };
525 let (chosen, new) = match self.choose_browsing_context(cx, non_empty_target, noopener) {
535 (Some(chosen), new) => (chosen, new),
536 (None, _) => return Ok(None),
537 };
538 let target_document = match chosen.document() {
541 Some(target_document) => target_document,
542 None => return Ok(None),
543 };
544 let has_trustworthy_ancestor_origin = if new {
545 target_document.has_trustworthy_ancestor_or_current_origin()
546 } else {
547 false
548 };
549 let target_window = target_document.window();
550 if !url.is_empty() {
553 let existing_document = self
554 .currently_active
555 .get()
556 .and_then(ScriptThread::find_document)
557 .unwrap();
558 let url = match existing_document.url().join(&url) {
559 Ok(url) => url,
560 Err(_) => return Err(Error::Syntax(None)),
561 };
562 let referrer = if noreferrer {
563 Referrer::NoReferrer
564 } else {
565 target_window.as_global_scope().get_referrer()
566 };
567 let csp_list = existing_document.get_csp_list().clone();
569 target_document.set_csp_list(csp_list);
570
571 let mut load_data = LoadData::new(
575 LoadOrigin::Script(existing_document.origin().snapshot()),
576 url,
577 target_document.about_base_url(),
578 Some(target_window.pipeline_id()),
579 referrer,
580 target_document.get_referrer_policy(),
581 Some(target_window.as_global_scope().is_secure_context()),
582 Some(target_document.insecure_requests_policy()),
583 has_trustworthy_ancestor_origin,
584 target_document.creation_sandboxing_flag_set_considering_parent_iframe(),
585 );
586
587 if load_data.url.scheme() == "javascript" {
590 let existing_global = existing_document.global();
591
592 if !ScriptThread::can_navigate_to_javascript_url(
594 cx,
595 &existing_global,
596 target_window.as_global_scope(),
597 &mut load_data,
598 None,
599 ) {
600 return Ok(target_document.browsing_context());
602 }
603 }
604
605 let history_handling = if new {
606 NavigationHistoryBehavior::Replace
607 } else {
608 NavigationHistoryBehavior::Push
609 };
610 navigate(cx, target_window, history_handling, false, load_data);
611 }
612 if noopener {
614 return Ok(None);
615 }
616 Ok(target_document.browsing_context())
618 }
619
620 pub(crate) fn choose_browsing_context(
622 &self,
623 cx: &mut JSContext,
624 name: DOMString,
625 noopener: bool,
626 ) -> (Option<DomRoot<WindowProxy>>, bool) {
627 match name.to_lowercase().as_ref() {
628 "" | "_self" => {
629 (Some(DomRoot::from_ref(self)), false)
631 },
632 "_parent" => {
633 if let Some(parent) = self.parent() {
635 return (Some(DomRoot::from_ref(parent)), false);
636 }
637 (None, false)
638 },
639 "_top" => {
640 (Some(DomRoot::from_ref(self.top())), false)
642 },
643 "_blank" => (
644 self.create_auxiliary_browsing_context(cx, name, noopener),
645 true,
646 ),
647 _ => {
648 match ScriptThread::find_window_proxy_by_name(&name) {
653 Some(proxy) => (Some(proxy), false),
654 None => (
655 self.create_auxiliary_browsing_context(cx, name, noopener),
656 true,
657 ),
658 }
659 },
660 }
661 }
662
663 pub(crate) fn is_auxiliary(&self) -> bool {
664 self.opener.is_some()
665 }
666
667 pub(crate) fn discard_browsing_context(&self) {
668 self.discarded.set(true);
669 }
670
671 pub(crate) fn is_browsing_context_discarded(&self) -> bool {
672 self.discarded.get()
673 }
674
675 pub(crate) fn browsing_context_id(&self) -> BrowsingContextId {
676 self.browsing_context_id
677 }
678
679 pub(crate) fn webview_id(&self) -> WebViewId {
680 self.webview_id
681 }
682
683 pub(crate) fn frame_element(&self) -> Option<&Element> {
687 self.frame_element.as_deref()
688 }
689
690 pub(crate) fn document(&self) -> Option<DomRoot<Document>> {
691 self.currently_active
692 .get()
693 .and_then(ScriptThread::find_document)
694 }
695
696 pub(crate) fn parent(&self) -> Option<&WindowProxy> {
697 self.parent.as_deref()
698 }
699
700 pub(crate) fn top(&self) -> &WindowProxy {
701 let mut result = self;
702 while let Some(parent) = result.parent() {
703 result = parent;
704 }
705 result
706 }
707
708 pub fn document_origin(&self) -> Option<String> {
709 let pipeline_id = self.currently_active()?;
710 let (result_sender, result_receiver) = generic_channel::channel().unwrap();
711 self.global()
712 .script_to_constellation_chan()
713 .send(ScriptToConstellationMessage::GetDocumentOrigin(
714 pipeline_id,
715 result_sender,
716 ))
717 .ok()?;
718 result_receiver.recv().ok()?
719 }
720
721 #[expect(unsafe_code)]
722 fn set_window(&self, window: &GlobalScope, handler: &WindowProxyHandler, _can_gc: CanGc) {
726 unsafe {
727 debug!("Setting window of {:p}.", self);
728
729 let cx = GlobalScope::get_cx();
730 let window_jsobject = window.reflector().get_jsobject();
731 let old_js_proxy = self.reflector.get_jsobject();
732 assert!(!window_jsobject.get().is_null());
733 assert_ne!(
734 ((*get_object_class(window_jsobject.get())).flags & JSCLASS_IS_GLOBAL),
735 0
736 );
737 let _ac = enter_realm(window);
738
739 SetProxyReservedSlot(old_js_proxy.get(), 0, &PrivateValue(ptr::null_mut()));
741
742 rooted!(in(*cx) let new_js_proxy = handler.new_window_proxy(&cx, window_jsobject));
748 SetProxyReservedSlot(new_js_proxy.get(), 0, &PrivateValue(ptr::null_mut()));
751 debug!(
752 "Transplanting proxy from {:p} to {:p}.",
753 old_js_proxy.get(),
754 new_js_proxy.get()
755 );
756 rooted!(in(*cx) let new_js_proxy = JS_TransplantObject(*cx, old_js_proxy, new_js_proxy.handle()));
757 debug!("Transplanted proxy is {:p}.", new_js_proxy.get());
758
759 SetProxyReservedSlot(
761 new_js_proxy.get(),
762 0,
763 &PrivateValue(self as *const _ as *const libc::c_void),
764 );
765
766 SetWindowProxy(*cx, window_jsobject, new_js_proxy.handle());
768
769 debug!(
771 "Setting reflector of {:p} to {:p}.",
772 self,
773 new_js_proxy.get()
774 );
775 self.reflector.rootable().set(new_js_proxy.get());
776 }
777 }
778
779 pub(crate) fn set_currently_active(&self, window: &Window, can_gc: CanGc) {
780 if let Some(pipeline_id) = self.currently_active() &&
781 pipeline_id == window.pipeline_id()
782 {
783 return debug!(
784 "Attempt to set the currently active window to the currently active window."
785 );
786 }
787
788 let global_scope = window.as_global_scope();
789 self.set_window(global_scope, WindowProxyHandler::proxy_handler(), can_gc);
790 self.currently_active.set(Some(global_scope.pipeline_id()));
791 }
792
793 pub(crate) fn unset_currently_active(&self, cx: &mut JSContext) {
794 if self.currently_active().is_none() {
795 return debug!(
796 "Attempt to unset the currently active window on a windowproxy that does not have one."
797 );
798 }
799 let globalscope = self.global();
800 let window = DissimilarOriginWindow::new(cx, &globalscope, self);
801 self.set_window(
802 window.upcast(),
803 WindowProxyHandler::x_origin_proxy_handler(),
804 CanGc::from_cx(cx),
805 );
806 self.currently_active.set(None);
807 }
808
809 pub(crate) fn currently_active(&self) -> Option<PipelineId> {
810 self.currently_active.get()
811 }
812
813 pub(crate) fn get_name(&self) -> DOMString {
814 self.name.borrow().clone()
815 }
816
817 pub(crate) fn set_name(&self, name: DOMString) {
818 *self.name.borrow_mut() = name;
819 }
820}
821
822#[derive(Debug, Deserialize, Serialize)]
834pub(crate) struct CreatorBrowsingContextInfo {
835 url: Option<ServoUrl>,
837
838 origin: Option<ImmutableOrigin>,
840}
841
842impl CreatorBrowsingContextInfo {
843 pub(crate) fn from(
844 parent: Option<&WindowProxy>,
845 opener: Option<&WindowProxy>,
846 ) -> CreatorBrowsingContextInfo {
847 let creator = match (parent, opener) {
848 (Some(parent), _) => parent.document(),
849 (None, Some(opener)) => opener.document(),
850 (None, None) => None,
851 };
852
853 let url = creator.as_deref().map(|document| document.url());
854 let origin = creator
855 .as_deref()
856 .map(|document| document.origin().immutable().clone());
857
858 CreatorBrowsingContextInfo { url, origin }
859 }
860}
861
862fn tokenize_open_features(features: DOMString) -> IndexMap<String, String> {
864 let is_feature_sep = |c: char| c.is_ascii_whitespace() || ['=', ','].contains(&c);
865 let mut tokenized_features = IndexMap::new();
867 let features = features.str();
869 let mut iter = features.chars();
870 let mut cur = iter.next();
871
872 while cur.is_some() {
874 let mut name = String::new();
876 let mut value = String::new();
877 while let Some(cur_char) = cur {
879 if !is_feature_sep(cur_char) {
880 break;
881 }
882 cur = iter.next();
883 }
884 while let Some(cur_char) = cur {
886 if is_feature_sep(cur_char) {
887 break;
888 }
889 name.push(cur_char.to_ascii_lowercase());
890 cur = iter.next();
891 }
892 let normalized_name = String::from(match name.as_ref() {
894 "screenx" => "left",
895 "screeny" => "top",
896 "innerwidth" => "width",
897 "innerheight" => "height",
898 _ => name.as_ref(),
899 });
900 while let Some(cur_char) = cur {
902 if cur_char == '=' || cur_char == ',' || !is_feature_sep(cur_char) {
903 break;
904 }
905 cur = iter.next();
906 }
907 if cur.is_some() && is_feature_sep(cur.unwrap()) {
909 while let Some(cur_char) = cur {
911 if !is_feature_sep(cur_char) || cur_char == ',' {
912 break;
913 }
914 cur = iter.next();
915 }
916 while let Some(cur_char) = cur {
918 if is_feature_sep(cur_char) {
919 break;
920 }
921 value.push(cur_char.to_ascii_lowercase());
922 cur = iter.next();
923 }
924 }
925 if !name.is_empty() {
927 tokenized_features.insert(normalized_name, value);
928 }
929 }
930 tokenized_features
932}
933
934fn parse_open_feature_boolean(tokenized_features: &IndexMap<String, String>, name: &str) -> bool {
936 if let Some(value) = tokenized_features.get(name) {
937 if value.is_empty() || value == "yes" {
939 return true;
940 }
941 if let Ok(int) = parse_integer(value.chars()) {
943 return int != 0;
944 }
945 }
946 false
948}
949
950#[expect(unsafe_code)]
954#[expect(non_snake_case)]
955unsafe fn GetSubframeWindowProxy(
956 cx: *mut RawJSContext,
957 proxy: RawHandleObject,
958 id: RawHandleId,
959) -> Option<(DomRoot<WindowProxy>, u32)> {
960 let index = get_array_index_from_id(unsafe { Handle::from_raw(id) });
961 if let Some(index) = index {
962 let mut slot = UndefinedValue();
963 unsafe { GetProxyPrivate(*proxy, &mut slot) };
964 rooted!(in(cx) let target = slot.to_object());
965 let script_window_proxies = ScriptThread::window_proxies();
966 if let Ok(win) = root_from_handleobject::<Window>(target.handle(), cx) {
967 let browsing_context_id = win.window_proxy().browsing_context_id();
968 let (result_sender, result_receiver) = generic_channel::channel().unwrap();
969
970 let _ = win.as_global_scope().script_to_constellation_chan().send(
971 ScriptToConstellationMessage::GetChildBrowsingContextId(
972 browsing_context_id,
973 index as usize,
974 result_sender,
975 ),
976 );
977 return result_receiver
978 .recv()
979 .ok()
980 .and_then(|maybe_bcid| maybe_bcid)
981 .and_then(|id| script_window_proxies.find_window_proxy(id))
982 .map(|proxy| (proxy, (JSPROP_ENUMERATE | JSPROP_READONLY) as u32));
983 } else if let Ok(win) =
984 root_from_handleobject::<DissimilarOriginWindow>(target.handle(), cx)
985 {
986 let browsing_context_id = win.window_proxy().browsing_context_id();
987 let (result_sender, result_receiver) = generic_channel::channel().unwrap();
988
989 let _ = win.global().script_to_constellation_chan().send(
990 ScriptToConstellationMessage::GetChildBrowsingContextId(
991 browsing_context_id,
992 index as usize,
993 result_sender,
994 ),
995 );
996 return result_receiver
997 .recv()
998 .ok()
999 .and_then(|maybe_bcid| maybe_bcid)
1000 .and_then(|id| script_window_proxies.find_window_proxy(id))
1001 .map(|proxy| (proxy, JSPROP_READONLY as u32));
1002 }
1003 }
1004
1005 None
1006}
1007
1008#[expect(unsafe_code)]
1009unsafe extern "C" fn get_own_property_descriptor(
1010 cx: *mut RawJSContext,
1011 proxy: RawHandleObject,
1012 id: RawHandleId,
1013 desc: RawMutableHandle<PropertyDescriptor>,
1014 is_none: *mut bool,
1015) -> bool {
1016 let window = unsafe { GetSubframeWindowProxy(cx, proxy, id) };
1017 if let Some((window, attrs)) = window {
1018 rooted!(in(cx) let mut val = UndefinedValue());
1019 unsafe { window.to_jsval(cx, val.handle_mut()) };
1020 set_property_descriptor(
1021 unsafe { MutableHandle::from_raw(desc) },
1022 val.handle(),
1023 attrs,
1024 unsafe { &mut *is_none },
1025 );
1026 return true;
1027 }
1028
1029 let mut slot = UndefinedValue();
1030 unsafe { GetProxyPrivate(proxy.get(), &mut slot) };
1031 rooted!(in(cx) let target = slot.to_object());
1032 unsafe { JS_GetOwnPropertyDescriptorById(cx, target.handle().into(), id, desc, is_none) }
1033}
1034
1035#[expect(unsafe_code)]
1036unsafe extern "C" fn define_property(
1037 cx: *mut RawJSContext,
1038 proxy: RawHandleObject,
1039 id: RawHandleId,
1040 desc: RawHandle<PropertyDescriptor>,
1041 res: *mut ObjectOpResult,
1042) -> bool {
1043 if get_array_index_from_id(unsafe { Handle::from_raw(id) }).is_some() {
1044 unsafe {
1049 (*res).code_ = JSErrNum::JSMSG_CANT_DEFINE_WINDOW_ELEMENT as ::libc::uintptr_t;
1050 }
1051 return true;
1052 }
1053
1054 let mut slot = UndefinedValue();
1055 unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1056 rooted!(in(cx) let target = slot.to_object());
1057 unsafe { JS_DefinePropertyById(cx, target.handle().into(), id, desc, res) }
1058}
1059
1060#[expect(unsafe_code)]
1061unsafe extern "C" fn has(
1062 cx: *mut RawJSContext,
1063 proxy: RawHandleObject,
1064 id: RawHandleId,
1065 bp: *mut bool,
1066) -> bool {
1067 let window = unsafe { GetSubframeWindowProxy(cx, proxy, id) };
1068 if window.is_some() {
1069 unsafe { *bp = true };
1070 return true;
1071 }
1072
1073 let mut slot = UndefinedValue();
1074 unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1075 rooted!(in(cx) let target = slot.to_object());
1076 let mut found = false;
1077 if !unsafe { JS_HasPropertyById(cx, target.handle().into(), id, &mut found) } {
1078 return false;
1079 }
1080
1081 unsafe { *bp = found };
1082 true
1083}
1084
1085#[expect(unsafe_code)]
1086unsafe extern "C" fn get(
1087 cx: *mut RawJSContext,
1088 proxy: RawHandleObject,
1089 receiver: RawHandleValue,
1090 id: RawHandleId,
1091 vp: RawMutableHandleValue,
1092) -> bool {
1093 let window = unsafe { GetSubframeWindowProxy(cx, proxy, id) };
1094 if let Some((window, _attrs)) = window {
1095 unsafe { window.to_jsval(cx, MutableHandle::from_raw(vp)) };
1096 return true;
1097 }
1098
1099 let mut slot = UndefinedValue();
1100 unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1101 rooted!(in(cx) let target = slot.to_object());
1102 unsafe { JS_ForwardGetPropertyTo(cx, target.handle().into(), id, receiver, vp) }
1103}
1104
1105#[expect(unsafe_code)]
1106unsafe extern "C" fn set(
1107 cx: *mut RawJSContext,
1108 proxy: RawHandleObject,
1109 id: RawHandleId,
1110 v: RawHandleValue,
1111 receiver: RawHandleValue,
1112 res: *mut ObjectOpResult,
1113) -> bool {
1114 if get_array_index_from_id(unsafe { Handle::from_raw(id) }).is_some() {
1115 unsafe { (*res).code_ = JSErrNum::JSMSG_READ_ONLY as ::libc::uintptr_t };
1117 return true;
1118 }
1119
1120 let mut slot = UndefinedValue();
1121 unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1122 rooted!(in(cx) let target = slot.to_object());
1123 unsafe { JS_ForwardSetPropertyTo(cx, target.handle().into(), id, v, receiver, res) }
1124}
1125
1126#[expect(unsafe_code)]
1127unsafe extern "C" fn get_prototype_if_ordinary(
1128 _: *mut RawJSContext,
1129 _: RawHandleObject,
1130 is_ordinary: *mut bool,
1131 _: RawMutableHandleObject,
1132) -> bool {
1133 unsafe { *is_ordinary = false };
1146 true
1147}
1148
1149static PROXY_TRAPS: ProxyTraps = ProxyTraps {
1150 enter: None,
1153 getOwnPropertyDescriptor: Some(get_own_property_descriptor),
1154 defineProperty: Some(define_property),
1155 ownPropertyKeys: None,
1156 delete_: None,
1157 enumerate: None,
1158 getPrototypeIfOrdinary: Some(get_prototype_if_ordinary),
1159 getPrototype: None, setPrototype: None,
1161 setImmutablePrototype: None,
1162 preventExtensions: None,
1163 isExtensible: None,
1164 has: Some(has),
1165 get: Some(get),
1166 set: Some(set),
1167 call: None,
1168 construct: None,
1169 hasOwn: None,
1170 getOwnEnumerablePropertyKeys: None,
1171 nativeCall: None,
1172 objectClassIs: None,
1173 className: None,
1174 fun_toString: None,
1175 boxedValue_unbox: None,
1176 defaultValue: None,
1177 trace: Some(trace),
1178 finalize: Some(finalize),
1179 objectMoved: None,
1180 isCallable: None,
1181 isConstructor: None,
1182};
1183
1184pub(crate) struct WindowProxyHandler(*const libc::c_void);
1187
1188impl MallocSizeOf for WindowProxyHandler {
1189 fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize {
1190 0
1192 }
1193}
1194
1195#[expect(unsafe_code)]
1197unsafe impl Send for WindowProxyHandler {}
1198#[expect(unsafe_code)]
1200unsafe impl Sync for WindowProxyHandler {}
1201
1202#[expect(unsafe_code)]
1203impl WindowProxyHandler {
1204 fn new(traps: &ProxyTraps) -> Self {
1205 let ptr = unsafe { CreateWrapperProxyHandler(traps) };
1207 assert!(!ptr.is_null());
1208 Self(ptr)
1209 }
1210
1211 pub(crate) fn x_origin_proxy_handler() -> &'static Self {
1213 use std::sync::OnceLock;
1214 static SINGLETON: OnceLock<WindowProxyHandler> = OnceLock::new();
1220 SINGLETON.get_or_init(|| Self::new(&XORIGIN_PROXY_TRAPS))
1221 }
1222
1223 pub(crate) fn proxy_handler() -> &'static Self {
1225 use std::sync::OnceLock;
1226 static SINGLETON: OnceLock<WindowProxyHandler> = OnceLock::new();
1232 SINGLETON.get_or_init(|| Self::new(&PROXY_TRAPS))
1233 }
1234
1235 pub(crate) fn new_window_proxy(
1238 &self,
1239 cx: &SafeJSContext,
1240 window_jsobject: js::gc::HandleObject,
1241 ) -> *mut JSObject {
1242 let obj = unsafe { NewWindowProxy(**cx, window_jsobject, self.0) };
1243 assert!(!obj.is_null());
1244 obj
1245 }
1246}
1247
1248#[expect(unsafe_code)]
1249impl Drop for WindowProxyHandler {
1250 fn drop(&mut self) {
1251 unsafe {
1254 DeleteWrapperProxyHandler(self.0);
1255 }
1256 }
1257}
1258
1259#[expect(unsafe_code)]
1267fn throw_security_error(realm: &mut CurrentRealm) -> bool {
1268 if !unsafe { JS_IsExceptionPending(realm) } {
1269 let global = GlobalScope::from_current_realm(realm);
1270 throw_dom_exception(
1271 realm.into(),
1272 &global,
1273 Error::Security(None),
1274 CanGc::from_cx(realm),
1275 );
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}