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::glue::{
15 CreateWrapperProxyHandler, DeleteWrapperProxyHandler, GetProxyPrivate, GetProxyReservedSlot,
16 ProxyTraps, SetProxyReservedSlot,
17};
18use js::jsapi::{
19 GCContext, Handle as RawHandle, HandleId as RawHandleId, HandleObject as RawHandleObject,
20 HandleValue as RawHandleValue, JS_DefinePropertyById, JS_ForwardGetPropertyTo,
21 JS_ForwardSetPropertyTo, JS_GetOwnPropertyDescriptorById, JS_HasOwnPropertyById,
22 JS_HasPropertyById, JS_IsExceptionPending, JSAutoRealm, JSContext, JSErrNum, JSObject,
23 JSPROP_ENUMERATE, JSPROP_READONLY, JSTracer, MutableHandle as RawMutableHandle,
24 MutableHandleObject as RawMutableHandleObject, MutableHandleValue as RawMutableHandleValue,
25 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::{Handle, MutableHandle, MutableHandleValue, get_object_class};
31use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
32use net_traits::ReferrerPolicy;
33use net_traits::request::Referrer;
34use script_bindings::cell::DomRefCell;
35use script_bindings::reflector::{DomObject, MutDomObject, Reflector};
36use script_traits::NewPipelineInfo;
37use serde::{Deserialize, Serialize};
38use servo_base::generic_channel;
39use servo_base::generic_channel::GenericSend;
40use servo_base::id::{BrowsingContextId, PipelineId, WebViewId};
41use servo_constellation_traits::{
42 AuxiliaryWebViewCreationRequest, LoadData, LoadOrigin, NavigationHistoryBehavior,
43 ScriptToConstellationMessage, TargetSnapshotParams,
44};
45use servo_url::{ImmutableOrigin, ServoUrl};
46use storage_traits::webstorage_thread::WebStorageThreadMsg;
47use style::attr::parse_integer;
48
49use crate::dom::bindings::conversions::{ToJSValConvertible, root_from_handleobject};
50use crate::dom::bindings::error::{Error, Fallible, throw_dom_exception};
51use crate::dom::bindings::inheritance::Castable;
52use crate::dom::bindings::proxyhandler::set_property_descriptor;
53use crate::dom::bindings::reflector::DomGlobal;
54use crate::dom::bindings::root::{Dom, DomRoot};
55use crate::dom::bindings::str::{DOMString, USVString};
56use crate::dom::bindings::trace::JSTraceable;
57use crate::dom::bindings::utils::get_array_index_from_id;
58use crate::dom::dissimilaroriginwindow::DissimilarOriginWindow;
59use crate::dom::document::Document;
60use crate::dom::element::Element;
61use crate::dom::globalscope::GlobalScope;
62use crate::dom::window::Window;
63use crate::navigation::navigate;
64use crate::realms::{AlreadyInRealm, InRealm, enter_realm};
65use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
66use crate::script_thread::{ScriptThread, with_script_thread};
67use crate::script_window_proxies::ScriptWindowProxies;
68
69#[dom_struct]
70pub(crate) struct WindowProxy {
75 reflector: Reflector,
80
81 #[no_trace]
85 browsing_context_id: BrowsingContextId,
86
87 #[no_trace]
89 opener: Option<BrowsingContextId>,
90
91 #[no_trace]
94 webview_id: WebViewId,
95
96 name: DomRefCell<DOMString>,
99 #[no_trace]
105 currently_active: Cell<Option<PipelineId>>,
106
107 discarded: Cell<bool>,
109
110 disowned: Cell<bool>,
112
113 is_closing: Cell<bool>,
115
116 frame_element: Option<Dom<Element>>,
120
121 parent: Option<Dom<WindowProxy>>,
123
124 delaying_load_events_mode: Cell<bool>,
126
127 #[no_trace]
129 creator_url: Option<ServoUrl>,
130
131 #[no_trace]
133 creator_origin: Option<ImmutableOrigin>,
134
135 #[conditional_malloc_size_of]
137 script_window_proxies: Rc<ScriptWindowProxies>,
138}
139
140impl WindowProxy {
141 fn new_inherited(
142 browsing_context_id: BrowsingContextId,
143 webview_id: WebViewId,
144 currently_active: Option<PipelineId>,
145 frame_element: Option<&Element>,
146 parent: Option<&WindowProxy>,
147 opener: Option<BrowsingContextId>,
148 creator: CreatorBrowsingContextInfo,
149 ) -> WindowProxy {
150 let name = frame_element.map_or(DOMString::new(), |e| {
151 e.get_string_attribute(&local_name!("name"))
152 });
153 WindowProxy {
154 reflector: Reflector::new(),
155 browsing_context_id,
156 webview_id,
157 name: DomRefCell::new(name),
158 currently_active: Cell::new(currently_active),
159 discarded: Cell::new(false),
160 disowned: Cell::new(false),
161 is_closing: Cell::new(false),
162 frame_element: frame_element.map(Dom::from_ref),
163 parent: parent.map(Dom::from_ref),
164 delaying_load_events_mode: Cell::new(false),
165 opener,
166 creator_url: creator.url,
167 creator_origin: creator.origin,
168 script_window_proxies: ScriptThread::window_proxies(),
169 }
170 }
171
172 #[expect(unsafe_code)]
173 pub(crate) fn new(
174 window: &Window,
175 browsing_context_id: BrowsingContextId,
176 webview_id: WebViewId,
177 frame_element: Option<&Element>,
178 parent: Option<&WindowProxy>,
179 opener: Option<BrowsingContextId>,
180 creator: CreatorBrowsingContextInfo,
181 ) -> DomRoot<WindowProxy> {
182 unsafe {
183 let handler = window.windowproxy_handler();
184
185 let cx = GlobalScope::get_cx();
186 let window_jsobject = window.reflector().get_jsobject();
187 assert!(!window_jsobject.get().is_null());
188 assert_ne!(
189 ((*get_object_class(window_jsobject.get())).flags & JSCLASS_IS_GLOBAL),
190 0
191 );
192 let _ac = JSAutoRealm::new(*cx, window_jsobject.get());
193
194 rooted!(in(*cx) let js_proxy = handler.new_window_proxy(&cx, window_jsobject));
196 assert!(!js_proxy.is_null());
197
198 let current = Some(window.upcast::<GlobalScope>().pipeline_id());
201 let window_proxy = Box::new(WindowProxy::new_inherited(
202 browsing_context_id,
203 webview_id,
204 current,
205 frame_element,
206 parent,
207 opener,
208 creator,
209 ));
210
211 SetProxyReservedSlot(
214 js_proxy.get(),
215 0,
216 &PrivateValue(&raw const (*window_proxy) as *const libc::c_void),
217 );
218
219 SetWindowProxy(*cx, window_jsobject, js_proxy.handle());
221
222 debug!(
224 "Initializing reflector of {:p} to {:p}.",
225 window_proxy,
226 js_proxy.get()
227 );
228 window_proxy
229 .reflector
230 .init_reflector::<WindowProxy>(js_proxy.get());
231 DomRoot::from_ref(&*Box::into_raw(window_proxy))
232 }
233 }
234
235 #[expect(unsafe_code)]
236 pub(crate) fn new_dissimilar_origin(
237 cx: &mut js::context::JSContext,
238 global_to_clone_from: &GlobalScope,
239 browsing_context_id: BrowsingContextId,
240 webview_id: WebViewId,
241 parent: Option<&WindowProxy>,
242 opener: Option<BrowsingContextId>,
243 creator: CreatorBrowsingContextInfo,
244 ) -> DomRoot<WindowProxy> {
245 unsafe {
246 let handler = WindowProxyHandler::x_origin_proxy_handler();
247
248 let window_proxy = Box::new(WindowProxy::new_inherited(
250 browsing_context_id,
251 webview_id,
252 None,
253 None,
254 parent,
255 opener,
256 creator,
257 ));
258
259 let window = DissimilarOriginWindow::new(cx, global_to_clone_from, &window_proxy);
261 let window_jsobject = window.reflector().get_jsobject();
262 assert!(!window_jsobject.get().is_null());
263 assert_ne!(
264 ((*get_object_class(window_jsobject.get())).flags & JSCLASS_IS_GLOBAL),
265 0
266 );
267 let mut realm = AutoRealm::new(cx, NonNull::new(window_jsobject.get()).unwrap());
268 let cx = &mut realm;
269
270 rooted!(&in(cx) let js_proxy = handler.new_window_proxy(&cx.into(), window_jsobject));
272 assert!(!js_proxy.is_null());
273
274 SetProxyReservedSlot(
277 js_proxy.get(),
278 0,
279 &PrivateValue(&raw const (*window_proxy) as *const libc::c_void),
280 );
281
282 SetWindowProxy(cx.raw_cx(), window_jsobject, js_proxy.handle());
284
285 debug!(
287 "Initializing reflector of {:p} to {:p}.",
288 window_proxy,
289 js_proxy.get()
290 );
291 window_proxy
292 .reflector
293 .init_reflector::<WindowProxy>(js_proxy.get());
294 DomRoot::from_ref(&*Box::into_raw(window_proxy))
295 }
296 }
297
298 fn create_auxiliary_browsing_context(
300 &self,
301 cx: &mut js::context::JSContext,
302 name: DOMString,
303 noopener: bool,
304 ) -> Option<DomRoot<WindowProxy>> {
305 let (response_sender, response_receiver) = generic_channel::channel().unwrap();
306 let window = self
307 .currently_active
308 .get()
309 .and_then(ScriptThread::find_document)
310 .map(|doc| DomRoot::from_ref(doc.window()))
311 .unwrap();
312
313 let document = self
314 .currently_active
315 .get()
316 .and_then(ScriptThread::find_document)
317 .expect("A WindowProxy creating an auxiliary to have an active document");
318
319 let sandboxing_flag_set = document.active_sandboxing_flag_set();
327 let propagate_sandbox = sandboxing_flag_set
328 .contains(SandboxingFlagSet::SANDBOX_PROPOGATES_TO_AUXILIARY_BROWSING_CONTEXTS_FLAG);
329 let sandboxing_flag_set = if propagate_sandbox {
330 sandboxing_flag_set
331 } else {
332 SandboxingFlagSet::empty()
333 };
334
335 let blank_url = ServoUrl::parse("about:blank").ok().unwrap();
336 let load_data = LoadData::new(
337 LoadOrigin::Script(document.origin().snapshot()),
338 blank_url,
339 Some(document.base_url()),
340 Some(window.pipeline_id()),
343 document.global().get_referrer(),
344 document.get_referrer_policy(),
345 None, None,
347 false,
348 sandboxing_flag_set,
349 );
350 let load_info = AuxiliaryWebViewCreationRequest {
351 load_data: load_data.clone(),
352 opener_webview_id: window.webview_id(),
353 opener_pipeline_id: self.currently_active.get().unwrap(),
354 response_sender,
355 };
356 let constellation_msg = ScriptToConstellationMessage::CreateAuxiliaryWebView(load_info);
357 window.send_to_constellation(constellation_msg);
358
359 let response = response_receiver.recv().unwrap()?;
360 let new_browsing_context_id = BrowsingContextId::from(response.new_webview_id);
361 let new_pipeline_info = NewPipelineInfo {
362 parent_info: None,
363 new_pipeline_id: response.new_pipeline_id,
364 browsing_context_id: new_browsing_context_id,
365 webview_id: response.new_webview_id,
366 opener: Some(self.browsing_context_id),
367 load_data,
368 viewport_details: window.viewport_details(),
369 user_content_manager_id: response.user_content_manager_id,
370 theme: window.theme(),
373 target_snapshot_params: TargetSnapshotParams {
374 sandboxing_flags: sandboxing_flag_set,
375 iframe_element_referrer_policy: ReferrerPolicy::EmptyString,
376 },
377 };
378
379 with_script_thread(|script_thread| {
380 script_thread.spawn_pipeline(cx, new_pipeline_info);
381 });
382
383 let new_window_proxy = ScriptThread::find_document(response.new_pipeline_id)
384 .and_then(|doc| doc.browsing_context())?;
385 if name.to_lowercase() != "_blank" {
386 new_window_proxy.set_name(name);
387 }
388 if noopener {
389 new_window_proxy.disown();
390 } else {
391 let (sender, receiver) = generic_channel::channel().unwrap();
396
397 let msg = WebStorageThreadMsg::Clone {
398 sender,
399 src: window.window_proxy().webview_id(),
400 dest: response.new_webview_id,
401 };
402
403 GenericSend::send(document.global().storage_threads(), msg).unwrap();
404 receiver.recv().unwrap();
405 }
406 Some(new_window_proxy)
407 }
408
409 pub(crate) fn is_delaying_load_events_mode(&self) -> bool {
411 self.delaying_load_events_mode.get()
412 }
413
414 pub(crate) fn start_delaying_load_events_mode(&self) {
416 self.delaying_load_events_mode.set(true);
417 }
418
419 pub(crate) fn stop_delaying_load_events_mode(&self) {
421 self.delaying_load_events_mode.set(false);
422 if let Some(document) = self.document() &&
423 !document.loader().events_inhibited()
424 {
425 ScriptThread::mark_document_with_no_blocked_loads(&document);
426 }
427 }
428
429 pub(crate) fn disown(&self) {
431 self.disowned.set(true);
432 }
433
434 pub(crate) fn close(&self) {
437 self.is_closing.set(true);
438 }
439
440 pub(crate) fn is_closing(&self) -> bool {
442 self.is_closing.get()
443 }
444
445 pub(crate) fn opener(&self, cx: &mut CurrentRealm, mut retval: MutableHandleValue) {
447 if self.disowned.get() {
448 return retval.set(NullValue());
449 }
450 let opener_id = match self.opener {
451 Some(opener_browsing_context_id) => opener_browsing_context_id,
452 None => return retval.set(NullValue()),
453 };
454 let parent_browsing_context = self.parent.as_deref();
455 let opener_proxy = match self.script_window_proxies.find_window_proxy(opener_id) {
456 Some(window_proxy) => window_proxy,
457 None => {
458 let sender_pipeline_id = self.currently_active().unwrap();
459 match ScriptThread::get_top_level_for_browsing_context(
460 self.webview_id(),
461 sender_pipeline_id,
462 opener_id,
463 ) {
464 Some(opener_top_id) => {
465 let global_to_clone_from = GlobalScope::from_current_realm(cx);
466 let creator =
467 CreatorBrowsingContextInfo::from(parent_browsing_context, None);
468 WindowProxy::new_dissimilar_origin(
469 cx,
470 &global_to_clone_from,
471 opener_id,
472 opener_top_id,
473 None,
474 None,
475 creator,
476 )
477 },
478 None => return retval.set(NullValue()),
479 }
480 },
481 };
482 if opener_proxy.is_browsing_context_discarded() {
483 return retval.set(NullValue());
484 }
485 opener_proxy.safe_to_jsval(cx, retval);
486 }
487
488 pub(crate) fn open(
490 &self,
491 cx: &mut js::context::JSContext,
492 url: USVString,
493 target: DOMString,
494 features: DOMString,
495 ) -> Fallible<Option<DomRoot<WindowProxy>>> {
496 if self.discarded.get() {
502 return Ok(None);
503 }
504 let non_empty_target = if target.is_empty() {
506 DOMString::from("_blank")
507 } else {
508 target
509 };
510 let tokenized_features = tokenize_open_features(features);
512 let noreferrer = parse_open_feature_boolean(&tokenized_features, "noreferrer");
516
517 let noopener = if noreferrer {
520 true
521 } else {
522 parse_open_feature_boolean(&tokenized_features, "noopener")
523 };
524 let (chosen, new) = match self.choose_browsing_context(cx, non_empty_target, noopener) {
534 (Some(chosen), new) => (chosen, new),
535 (None, _) => return Ok(None),
536 };
537 let target_document = match chosen.document() {
540 Some(target_document) => target_document,
541 None => return Ok(None),
542 };
543 let has_trustworthy_ancestor_origin = if new {
544 target_document.has_trustworthy_ancestor_or_current_origin()
545 } else {
546 false
547 };
548 let target_window = target_document.window();
549 if !url.is_empty() {
552 let existing_document = self
553 .currently_active
554 .get()
555 .and_then(ScriptThread::find_document)
556 .unwrap();
557 let url = match existing_document.url().join(&url) {
558 Ok(url) => url,
559 Err(_) => return Err(Error::Syntax(None)),
560 };
561 let referrer = if noreferrer {
562 Referrer::NoReferrer
563 } else {
564 target_window.as_global_scope().get_referrer()
565 };
566 let csp_list = existing_document.get_csp_list().clone();
568 target_document.set_csp_list(csp_list);
569
570 let mut load_data = LoadData::new(
574 LoadOrigin::Script(existing_document.origin().snapshot()),
575 url,
576 target_document.about_base_url(),
577 Some(target_window.pipeline_id()),
578 referrer,
579 target_document.get_referrer_policy(),
580 Some(target_window.as_global_scope().is_secure_context()),
581 Some(target_document.insecure_requests_policy()),
582 has_trustworthy_ancestor_origin,
583 target_document.creation_sandboxing_flag_set_considering_parent_iframe(),
584 );
585
586 if load_data.url.scheme() == "javascript" {
589 let existing_global = existing_document.global();
590
591 if !ScriptThread::can_navigate_to_javascript_url(
593 cx,
594 &existing_global,
595 target_window.as_global_scope(),
596 &mut load_data,
597 None,
598 ) {
599 return Ok(target_document.browsing_context());
601 }
602 }
603
604 let history_handling = if new {
605 NavigationHistoryBehavior::Replace
606 } else {
607 NavigationHistoryBehavior::Push
608 };
609 navigate(cx, target_window, history_handling, false, load_data);
610 }
611 if noopener {
613 return Ok(None);
614 }
615 Ok(target_document.browsing_context())
617 }
618
619 pub(crate) fn choose_browsing_context(
621 &self,
622 cx: &mut js::context::JSContext,
623 name: DOMString,
624 noopener: bool,
625 ) -> (Option<DomRoot<WindowProxy>>, bool) {
626 match name.to_lowercase().as_ref() {
627 "" | "_self" => {
628 (Some(DomRoot::from_ref(self)), false)
630 },
631 "_parent" => {
632 if let Some(parent) = self.parent() {
634 return (Some(DomRoot::from_ref(parent)), false);
635 }
636 (None, false)
637 },
638 "_top" => {
639 (Some(DomRoot::from_ref(self.top())), false)
641 },
642 "_blank" => (
643 self.create_auxiliary_browsing_context(cx, name, noopener),
644 true,
645 ),
646 _ => {
647 match ScriptThread::find_window_proxy_by_name(&name) {
652 Some(proxy) => (Some(proxy), false),
653 None => (
654 self.create_auxiliary_browsing_context(cx, name, noopener),
655 true,
656 ),
657 }
658 },
659 }
660 }
661
662 pub(crate) fn is_auxiliary(&self) -> bool {
663 self.opener.is_some()
664 }
665
666 pub(crate) fn discard_browsing_context(&self) {
667 self.discarded.set(true);
668 }
669
670 pub(crate) fn is_browsing_context_discarded(&self) -> bool {
671 self.discarded.get()
672 }
673
674 pub(crate) fn browsing_context_id(&self) -> BrowsingContextId {
675 self.browsing_context_id
676 }
677
678 pub(crate) fn webview_id(&self) -> WebViewId {
679 self.webview_id
680 }
681
682 pub(crate) fn frame_element(&self) -> Option<&Element> {
686 self.frame_element.as_deref()
687 }
688
689 pub(crate) fn document(&self) -> Option<DomRoot<Document>> {
690 self.currently_active
691 .get()
692 .and_then(ScriptThread::find_document)
693 }
694
695 pub(crate) fn parent(&self) -> Option<&WindowProxy> {
696 self.parent.as_deref()
697 }
698
699 pub(crate) fn top(&self) -> &WindowProxy {
700 let mut result = self;
701 while let Some(parent) = result.parent() {
702 result = parent;
703 }
704 result
705 }
706
707 pub fn document_origin(&self) -> Option<String> {
708 let pipeline_id = self.currently_active()?;
709 let (result_sender, result_receiver) = generic_channel::channel().unwrap();
710 self.global()
711 .script_to_constellation_chan()
712 .send(ScriptToConstellationMessage::GetDocumentOrigin(
713 pipeline_id,
714 result_sender,
715 ))
716 .ok()?;
717 result_receiver.recv().ok()?
718 }
719
720 #[expect(unsafe_code)]
721 fn set_window(&self, window: &GlobalScope, handler: &WindowProxyHandler, _can_gc: CanGc) {
725 unsafe {
726 debug!("Setting window of {:p}.", self);
727
728 let cx = GlobalScope::get_cx();
729 let window_jsobject = window.reflector().get_jsobject();
730 let old_js_proxy = self.reflector.get_jsobject();
731 assert!(!window_jsobject.get().is_null());
732 assert_ne!(
733 ((*get_object_class(window_jsobject.get())).flags & JSCLASS_IS_GLOBAL),
734 0
735 );
736 let _ac = enter_realm(window);
737
738 SetProxyReservedSlot(old_js_proxy.get(), 0, &PrivateValue(ptr::null_mut()));
740
741 rooted!(in(*cx) let new_js_proxy = handler.new_window_proxy(&cx, window_jsobject));
747 SetProxyReservedSlot(new_js_proxy.get(), 0, &PrivateValue(ptr::null_mut()));
750 debug!(
751 "Transplanting proxy from {:p} to {:p}.",
752 old_js_proxy.get(),
753 new_js_proxy.get()
754 );
755 rooted!(in(*cx) let new_js_proxy = JS_TransplantObject(*cx, old_js_proxy, new_js_proxy.handle()));
756 debug!("Transplanted proxy is {:p}.", new_js_proxy.get());
757
758 SetProxyReservedSlot(
760 new_js_proxy.get(),
761 0,
762 &PrivateValue(self as *const _ as *const libc::c_void),
763 );
764
765 SetWindowProxy(*cx, window_jsobject, new_js_proxy.handle());
767
768 debug!(
770 "Setting reflector of {:p} to {:p}.",
771 self,
772 new_js_proxy.get()
773 );
774 self.reflector.rootable().set(new_js_proxy.get());
775 }
776 }
777
778 pub(crate) fn set_currently_active(&self, window: &Window, can_gc: CanGc) {
779 if let Some(pipeline_id) = self.currently_active() &&
780 pipeline_id == window.pipeline_id()
781 {
782 return debug!(
783 "Attempt to set the currently active window to the currently active window."
784 );
785 }
786
787 let global_scope = window.as_global_scope();
788 self.set_window(global_scope, WindowProxyHandler::proxy_handler(), can_gc);
789 self.currently_active.set(Some(global_scope.pipeline_id()));
790 }
791
792 pub(crate) fn unset_currently_active(&self, cx: &mut js::context::JSContext) {
793 if self.currently_active().is_none() {
794 return debug!(
795 "Attempt to unset the currently active window on a windowproxy that does not have one."
796 );
797 }
798 let globalscope = self.global();
799 let window = DissimilarOriginWindow::new(cx, &globalscope, self);
800 self.set_window(
801 window.upcast(),
802 WindowProxyHandler::x_origin_proxy_handler(),
803 CanGc::from_cx(cx),
804 );
805 self.currently_active.set(None);
806 }
807
808 pub(crate) fn currently_active(&self) -> Option<PipelineId> {
809 self.currently_active.get()
810 }
811
812 pub(crate) fn get_name(&self) -> DOMString {
813 self.name.borrow().clone()
814 }
815
816 pub(crate) fn set_name(&self, name: DOMString) {
817 *self.name.borrow_mut() = name;
818 }
819}
820
821#[derive(Debug, Deserialize, Serialize)]
833pub(crate) struct CreatorBrowsingContextInfo {
834 url: Option<ServoUrl>,
836
837 origin: Option<ImmutableOrigin>,
839}
840
841impl CreatorBrowsingContextInfo {
842 pub(crate) fn from(
843 parent: Option<&WindowProxy>,
844 opener: Option<&WindowProxy>,
845 ) -> CreatorBrowsingContextInfo {
846 let creator = match (parent, opener) {
847 (Some(parent), _) => parent.document(),
848 (None, Some(opener)) => opener.document(),
849 (None, None) => None,
850 };
851
852 let url = creator.as_deref().map(|document| document.url());
853 let origin = creator
854 .as_deref()
855 .map(|document| document.origin().immutable().clone());
856
857 CreatorBrowsingContextInfo { url, origin }
858 }
859}
860
861fn tokenize_open_features(features: DOMString) -> IndexMap<String, String> {
863 let is_feature_sep = |c: char| c.is_ascii_whitespace() || ['=', ','].contains(&c);
864 let mut tokenized_features = IndexMap::new();
866 let features = features.str();
868 let mut iter = features.chars();
869 let mut cur = iter.next();
870
871 while cur.is_some() {
873 let mut name = String::new();
875 let mut value = String::new();
876 while let Some(cur_char) = cur {
878 if !is_feature_sep(cur_char) {
879 break;
880 }
881 cur = iter.next();
882 }
883 while let Some(cur_char) = cur {
885 if is_feature_sep(cur_char) {
886 break;
887 }
888 name.push(cur_char.to_ascii_lowercase());
889 cur = iter.next();
890 }
891 let normalized_name = String::from(match name.as_ref() {
893 "screenx" => "left",
894 "screeny" => "top",
895 "innerwidth" => "width",
896 "innerheight" => "height",
897 _ => name.as_ref(),
898 });
899 while let Some(cur_char) = cur {
901 if cur_char == '=' || cur_char == ',' || !is_feature_sep(cur_char) {
902 break;
903 }
904 cur = iter.next();
905 }
906 if cur.is_some() && is_feature_sep(cur.unwrap()) {
908 while let Some(cur_char) = cur {
910 if !is_feature_sep(cur_char) || cur_char == ',' {
911 break;
912 }
913 cur = iter.next();
914 }
915 while let Some(cur_char) = cur {
917 if is_feature_sep(cur_char) {
918 break;
919 }
920 value.push(cur_char.to_ascii_lowercase());
921 cur = iter.next();
922 }
923 }
924 if !name.is_empty() {
926 tokenized_features.insert(normalized_name, value);
927 }
928 }
929 tokenized_features
931}
932
933fn parse_open_feature_boolean(tokenized_features: &IndexMap<String, String>, name: &str) -> bool {
935 if let Some(value) = tokenized_features.get(name) {
936 if value.is_empty() || value == "yes" {
938 return true;
939 }
940 if let Ok(int) = parse_integer(value.chars()) {
942 return int != 0;
943 }
944 }
945 false
947}
948
949#[expect(unsafe_code)]
953#[expect(non_snake_case)]
954unsafe fn GetSubframeWindowProxy(
955 cx: *mut JSContext,
956 proxy: RawHandleObject,
957 id: RawHandleId,
958) -> Option<(DomRoot<WindowProxy>, u32)> {
959 let index = get_array_index_from_id(unsafe { Handle::from_raw(id) });
960 if let Some(index) = index {
961 let mut slot = UndefinedValue();
962 unsafe { GetProxyPrivate(*proxy, &mut slot) };
963 rooted!(in(cx) let target = slot.to_object());
964 let script_window_proxies = ScriptThread::window_proxies();
965 if let Ok(win) = root_from_handleobject::<Window>(target.handle(), cx) {
966 let browsing_context_id = win.window_proxy().browsing_context_id();
967 let (result_sender, result_receiver) = generic_channel::channel().unwrap();
968
969 let _ = win.as_global_scope().script_to_constellation_chan().send(
970 ScriptToConstellationMessage::GetChildBrowsingContextId(
971 browsing_context_id,
972 index as usize,
973 result_sender,
974 ),
975 );
976 return result_receiver
977 .recv()
978 .ok()
979 .and_then(|maybe_bcid| maybe_bcid)
980 .and_then(|id| script_window_proxies.find_window_proxy(id))
981 .map(|proxy| (proxy, (JSPROP_ENUMERATE | JSPROP_READONLY) as u32));
982 } else if let Ok(win) =
983 root_from_handleobject::<DissimilarOriginWindow>(target.handle(), cx)
984 {
985 let browsing_context_id = win.window_proxy().browsing_context_id();
986 let (result_sender, result_receiver) = generic_channel::channel().unwrap();
987
988 let _ = win.global().script_to_constellation_chan().send(
989 ScriptToConstellationMessage::GetChildBrowsingContextId(
990 browsing_context_id,
991 index as usize,
992 result_sender,
993 ),
994 );
995 return result_receiver
996 .recv()
997 .ok()
998 .and_then(|maybe_bcid| maybe_bcid)
999 .and_then(|id| script_window_proxies.find_window_proxy(id))
1000 .map(|proxy| (proxy, JSPROP_READONLY as u32));
1001 }
1002 }
1003
1004 None
1005}
1006
1007#[expect(unsafe_code)]
1008unsafe extern "C" fn get_own_property_descriptor(
1009 cx: *mut JSContext,
1010 proxy: RawHandleObject,
1011 id: RawHandleId,
1012 desc: RawMutableHandle<PropertyDescriptor>,
1013 is_none: *mut bool,
1014) -> bool {
1015 let window = unsafe { GetSubframeWindowProxy(cx, proxy, id) };
1016 if let Some((window, attrs)) = window {
1017 rooted!(in(cx) let mut val = UndefinedValue());
1018 unsafe { window.to_jsval(cx, val.handle_mut()) };
1019 set_property_descriptor(
1020 unsafe { MutableHandle::from_raw(desc) },
1021 val.handle(),
1022 attrs,
1023 unsafe { &mut *is_none },
1024 );
1025 return true;
1026 }
1027
1028 let mut slot = UndefinedValue();
1029 unsafe { GetProxyPrivate(proxy.get(), &mut slot) };
1030 rooted!(in(cx) let target = slot.to_object());
1031 unsafe { JS_GetOwnPropertyDescriptorById(cx, target.handle().into(), id, desc, is_none) }
1032}
1033
1034#[expect(unsafe_code)]
1035unsafe extern "C" fn define_property(
1036 cx: *mut JSContext,
1037 proxy: RawHandleObject,
1038 id: RawHandleId,
1039 desc: RawHandle<PropertyDescriptor>,
1040 res: *mut ObjectOpResult,
1041) -> bool {
1042 if get_array_index_from_id(unsafe { Handle::from_raw(id) }).is_some() {
1043 unsafe {
1048 (*res).code_ = JSErrNum::JSMSG_CANT_DEFINE_WINDOW_ELEMENT as ::libc::uintptr_t;
1049 }
1050 return true;
1051 }
1052
1053 let mut slot = UndefinedValue();
1054 unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1055 rooted!(in(cx) let target = slot.to_object());
1056 unsafe { JS_DefinePropertyById(cx, target.handle().into(), id, desc, res) }
1057}
1058
1059#[expect(unsafe_code)]
1060unsafe extern "C" fn has(
1061 cx: *mut JSContext,
1062 proxy: RawHandleObject,
1063 id: RawHandleId,
1064 bp: *mut bool,
1065) -> bool {
1066 let window = unsafe { GetSubframeWindowProxy(cx, proxy, id) };
1067 if window.is_some() {
1068 unsafe { *bp = true };
1069 return true;
1070 }
1071
1072 let mut slot = UndefinedValue();
1073 unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1074 rooted!(in(cx) let target = slot.to_object());
1075 let mut found = false;
1076 if !unsafe { JS_HasPropertyById(cx, target.handle().into(), id, &mut found) } {
1077 return false;
1078 }
1079
1080 unsafe { *bp = found };
1081 true
1082}
1083
1084#[expect(unsafe_code)]
1085unsafe extern "C" fn get(
1086 cx: *mut JSContext,
1087 proxy: RawHandleObject,
1088 receiver: RawHandleValue,
1089 id: RawHandleId,
1090 vp: RawMutableHandleValue,
1091) -> bool {
1092 let window = unsafe { GetSubframeWindowProxy(cx, proxy, id) };
1093 if let Some((window, _attrs)) = window {
1094 unsafe { window.to_jsval(cx, MutableHandle::from_raw(vp)) };
1095 return true;
1096 }
1097
1098 let mut slot = UndefinedValue();
1099 unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1100 rooted!(in(cx) let target = slot.to_object());
1101 unsafe { JS_ForwardGetPropertyTo(cx, target.handle().into(), id, receiver, vp) }
1102}
1103
1104#[expect(unsafe_code)]
1105unsafe extern "C" fn set(
1106 cx: *mut JSContext,
1107 proxy: RawHandleObject,
1108 id: RawHandleId,
1109 v: RawHandleValue,
1110 receiver: RawHandleValue,
1111 res: *mut ObjectOpResult,
1112) -> bool {
1113 if get_array_index_from_id(unsafe { Handle::from_raw(id) }).is_some() {
1114 unsafe { (*res).code_ = JSErrNum::JSMSG_READ_ONLY as ::libc::uintptr_t };
1116 return true;
1117 }
1118
1119 let mut slot = UndefinedValue();
1120 unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1121 rooted!(in(cx) let target = slot.to_object());
1122 unsafe { JS_ForwardSetPropertyTo(cx, target.handle().into(), id, v, receiver, res) }
1123}
1124
1125#[expect(unsafe_code)]
1126unsafe extern "C" fn get_prototype_if_ordinary(
1127 _: *mut JSContext,
1128 _: RawHandleObject,
1129 is_ordinary: *mut bool,
1130 _: RawMutableHandleObject,
1131) -> bool {
1132 unsafe { *is_ordinary = false };
1145 true
1146}
1147
1148static PROXY_TRAPS: ProxyTraps = ProxyTraps {
1149 enter: None,
1152 getOwnPropertyDescriptor: Some(get_own_property_descriptor),
1153 defineProperty: Some(define_property),
1154 ownPropertyKeys: None,
1155 delete_: None,
1156 enumerate: None,
1157 getPrototypeIfOrdinary: Some(get_prototype_if_ordinary),
1158 getPrototype: None, setPrototype: None,
1160 setImmutablePrototype: None,
1161 preventExtensions: None,
1162 isExtensible: None,
1163 has: Some(has),
1164 get: Some(get),
1165 set: Some(set),
1166 call: None,
1167 construct: None,
1168 hasOwn: None,
1169 getOwnEnumerablePropertyKeys: None,
1170 nativeCall: None,
1171 objectClassIs: None,
1172 className: None,
1173 fun_toString: None,
1174 boxedValue_unbox: None,
1175 defaultValue: None,
1176 trace: Some(trace),
1177 finalize: Some(finalize),
1178 objectMoved: None,
1179 isCallable: None,
1180 isConstructor: None,
1181};
1182
1183pub(crate) struct WindowProxyHandler(*const libc::c_void);
1186
1187impl MallocSizeOf for WindowProxyHandler {
1188 fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize {
1189 0
1191 }
1192}
1193
1194#[expect(unsafe_code)]
1196unsafe impl Send for WindowProxyHandler {}
1197#[expect(unsafe_code)]
1199unsafe impl Sync for WindowProxyHandler {}
1200
1201#[expect(unsafe_code)]
1202impl WindowProxyHandler {
1203 fn new(traps: &ProxyTraps) -> Self {
1204 let ptr = unsafe { CreateWrapperProxyHandler(traps) };
1206 assert!(!ptr.is_null());
1207 Self(ptr)
1208 }
1209
1210 pub(crate) fn x_origin_proxy_handler() -> &'static Self {
1212 use std::sync::OnceLock;
1213 static SINGLETON: OnceLock<WindowProxyHandler> = OnceLock::new();
1219 SINGLETON.get_or_init(|| Self::new(&XORIGIN_PROXY_TRAPS))
1220 }
1221
1222 pub(crate) fn proxy_handler() -> &'static Self {
1224 use std::sync::OnceLock;
1225 static SINGLETON: OnceLock<WindowProxyHandler> = OnceLock::new();
1231 SINGLETON.get_or_init(|| Self::new(&PROXY_TRAPS))
1232 }
1233
1234 pub(crate) fn new_window_proxy(
1237 &self,
1238 cx: &crate::script_runtime::JSContext,
1239 window_jsobject: js::gc::HandleObject,
1240 ) -> *mut JSObject {
1241 let obj = unsafe { NewWindowProxy(**cx, window_jsobject, self.0) };
1242 assert!(!obj.is_null());
1243 obj
1244 }
1245}
1246
1247#[expect(unsafe_code)]
1248impl Drop for WindowProxyHandler {
1249 fn drop(&mut self) {
1250 unsafe {
1253 DeleteWrapperProxyHandler(self.0);
1254 }
1255 }
1256}
1257
1258#[expect(unsafe_code)]
1266fn throw_security_error(cx: SafeJSContext, realm: InRealm) -> bool {
1267 if !unsafe { JS_IsExceptionPending(*cx) } {
1268 let global = unsafe { GlobalScope::from_context(*cx, realm) };
1269 throw_dom_exception(cx, &global, Error::Security(None), CanGc::deprecated_note());
1270 }
1271 false
1272}
1273
1274#[expect(unsafe_code)]
1275unsafe extern "C" fn has_xorigin(
1276 cx: *mut JSContext,
1277 proxy: RawHandleObject,
1278 id: RawHandleId,
1279 bp: *mut bool,
1280) -> bool {
1281 let mut slot = UndefinedValue();
1282 unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1283 rooted!(in(cx) let target = slot.to_object());
1284 let mut found = false;
1285 unsafe { JS_HasOwnPropertyById(cx, target.handle().into(), id, &mut found) };
1286 if found {
1287 unsafe { *bp = true };
1288 true
1289 } else {
1290 let cx = unsafe { SafeJSContext::from_ptr(cx) };
1291 let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
1292 throw_security_error(cx, InRealm::Already(&in_realm_proof))
1293 }
1294}
1295
1296#[expect(unsafe_code)]
1297unsafe extern "C" fn get_xorigin(
1298 cx: *mut JSContext,
1299 proxy: RawHandleObject,
1300 receiver: RawHandleValue,
1301 id: RawHandleId,
1302 vp: RawMutableHandleValue,
1303) -> bool {
1304 let mut found = false;
1305 unsafe { has_xorigin(cx, proxy, id, &mut found) };
1306 found && unsafe { get(cx, proxy, receiver, id, vp) }
1307}
1308
1309#[expect(unsafe_code)]
1310unsafe extern "C" fn set_xorigin(
1311 cx: *mut JSContext,
1312 _: RawHandleObject,
1313 _: RawHandleId,
1314 _: RawHandleValue,
1315 _: RawHandleValue,
1316 _: *mut ObjectOpResult,
1317) -> bool {
1318 let cx = unsafe { SafeJSContext::from_ptr(cx) };
1319 let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
1320 throw_security_error(cx, InRealm::Already(&in_realm_proof))
1321}
1322
1323#[expect(unsafe_code)]
1324unsafe extern "C" fn delete_xorigin(
1325 cx: *mut JSContext,
1326 _: RawHandleObject,
1327 _: RawHandleId,
1328 _: *mut ObjectOpResult,
1329) -> bool {
1330 let cx = unsafe { SafeJSContext::from_ptr(cx) };
1331 let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
1332 throw_security_error(cx, InRealm::Already(&in_realm_proof))
1333}
1334
1335#[expect(unsafe_code)]
1336#[expect(non_snake_case)]
1337unsafe extern "C" fn getOwnPropertyDescriptor_xorigin(
1338 cx: *mut JSContext,
1339 proxy: RawHandleObject,
1340 id: RawHandleId,
1341 desc: RawMutableHandle<PropertyDescriptor>,
1342 is_none: *mut bool,
1343) -> bool {
1344 let mut found = false;
1345 unsafe { has_xorigin(cx, proxy, id, &mut found) };
1346 found && unsafe { get_own_property_descriptor(cx, proxy, id, desc, is_none) }
1347}
1348
1349#[expect(unsafe_code)]
1350#[expect(non_snake_case)]
1351unsafe extern "C" fn defineProperty_xorigin(
1352 cx: *mut JSContext,
1353 _: RawHandleObject,
1354 _: RawHandleId,
1355 _: RawHandle<PropertyDescriptor>,
1356 _: *mut ObjectOpResult,
1357) -> bool {
1358 let cx = unsafe { SafeJSContext::from_ptr(cx) };
1359 let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
1360 throw_security_error(cx, InRealm::Already(&in_realm_proof))
1361}
1362
1363#[expect(unsafe_code)]
1364#[expect(non_snake_case)]
1365unsafe extern "C" fn preventExtensions_xorigin(
1366 cx: *mut JSContext,
1367 _: RawHandleObject,
1368 _: *mut ObjectOpResult,
1369) -> bool {
1370 let cx = unsafe { SafeJSContext::from_ptr(cx) };
1371 let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
1372 throw_security_error(cx, InRealm::Already(&in_realm_proof))
1373}
1374
1375static XORIGIN_PROXY_TRAPS: ProxyTraps = ProxyTraps {
1376 enter: None,
1377 getOwnPropertyDescriptor: Some(getOwnPropertyDescriptor_xorigin),
1378 defineProperty: Some(defineProperty_xorigin),
1379 ownPropertyKeys: None,
1380 delete_: Some(delete_xorigin),
1381 enumerate: None,
1382 getPrototypeIfOrdinary: None,
1383 getPrototype: None,
1384 setPrototype: None,
1385 setImmutablePrototype: None,
1386 preventExtensions: Some(preventExtensions_xorigin),
1387 isExtensible: None,
1388 has: Some(has_xorigin),
1389 get: Some(get_xorigin),
1390 set: Some(set_xorigin),
1391 call: None,
1392 construct: None,
1393 hasOwn: Some(has_xorigin),
1394 getOwnEnumerablePropertyKeys: None,
1395 nativeCall: None,
1396 objectClassIs: None,
1397 className: None,
1398 fun_toString: None,
1399 boxedValue_unbox: None,
1400 defaultValue: None,
1401 trace: Some(trace),
1402 finalize: Some(finalize),
1403 objectMoved: None,
1404 isCallable: None,
1405 isConstructor: None,
1406};
1407
1408#[expect(unsafe_code)]
1411unsafe extern "C" fn finalize(_fop: *mut GCContext, obj: *mut JSObject) {
1412 let mut slot = UndefinedValue();
1413 unsafe { GetProxyReservedSlot(obj, 0, &mut slot) };
1414 let this = slot.to_private() as *mut WindowProxy;
1415 if this.is_null() {
1416 return;
1418 }
1419 unsafe {
1420 (*this).reflector.drop_memory(&*this);
1421 let jsobject = (*this).reflector.get_jsobject().get();
1422 debug!(
1423 "WindowProxy finalize: {:p}, with reflector {:p} from {:p}.",
1424 this, jsobject, obj
1425 );
1426 let _ = Box::from_raw(this);
1427 }
1428}
1429
1430#[expect(unsafe_code)]
1431unsafe extern "C" fn trace(trc: *mut JSTracer, obj: *mut JSObject) {
1432 let mut slot = UndefinedValue();
1433 unsafe { GetProxyReservedSlot(obj, 0, &mut slot) };
1434 let this = slot.to_private() as *const WindowProxy;
1435 if this.is_null() {
1436 return;
1438 }
1439 unsafe { (*this).trace(trc) };
1440}