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::reflector::MutDomObject;
35use script_traits::NewPipelineInfo;
36use serde::{Deserialize, Serialize};
37use servo_base::generic_channel;
38use servo_base::generic_channel::GenericSend;
39use servo_base::id::{BrowsingContextId, PipelineId, WebViewId};
40use servo_constellation_traits::{
41 AuxiliaryWebViewCreationRequest, LoadData, LoadOrigin, NavigationHistoryBehavior,
42 ScriptToConstellationMessage, TargetSnapshotParams,
43};
44use servo_url::{ImmutableOrigin, ServoUrl};
45use storage_traits::webstorage_thread::WebStorageThreadMsg;
46use style::attr::parse_integer;
47
48use crate::dom::bindings::cell::DomRefCell;
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, DomObject, Reflector};
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 name: DOMString,
302 noopener: bool,
303 ) -> Option<DomRoot<WindowProxy>> {
304 let (response_sender, response_receiver) = generic_channel::channel().unwrap();
305 let window = self
306 .currently_active
307 .get()
308 .and_then(ScriptThread::find_document)
309 .map(|doc| DomRoot::from_ref(doc.window()))
310 .unwrap();
311
312 let document = self
313 .currently_active
314 .get()
315 .and_then(ScriptThread::find_document)
316 .expect("A WindowProxy creating an auxiliary to have an active document");
317
318 let sandboxing_flag_set = document.active_sandboxing_flag_set();
326 let propagate_sandbox = sandboxing_flag_set
327 .contains(SandboxingFlagSet::SANDBOX_PROPOGATES_TO_AUXILIARY_BROWSING_CONTEXTS_FLAG);
328 let sandboxing_flag_set = if propagate_sandbox {
329 sandboxing_flag_set
330 } else {
331 SandboxingFlagSet::empty()
332 };
333
334 let blank_url = ServoUrl::parse("about:blank").ok().unwrap();
335 let load_data = LoadData::new(
336 LoadOrigin::Script(document.origin().snapshot()),
337 blank_url,
338 Some(document.base_url()),
339 Some(window.pipeline_id()),
342 document.global().get_referrer(),
343 document.get_referrer_policy(),
344 None, None,
346 false,
347 sandboxing_flag_set,
348 );
349 let load_info = AuxiliaryWebViewCreationRequest {
350 load_data: load_data.clone(),
351 opener_webview_id: window.webview_id(),
352 opener_pipeline_id: self.currently_active.get().unwrap(),
353 response_sender,
354 };
355 let constellation_msg = ScriptToConstellationMessage::CreateAuxiliaryWebView(load_info);
356 window.send_to_constellation(constellation_msg);
357
358 let response = response_receiver.recv().unwrap()?;
359 let new_browsing_context_id = BrowsingContextId::from(response.new_webview_id);
360 let new_pipeline_info = NewPipelineInfo {
361 parent_info: None,
362 new_pipeline_id: response.new_pipeline_id,
363 browsing_context_id: new_browsing_context_id,
364 webview_id: response.new_webview_id,
365 opener: Some(self.browsing_context_id),
366 load_data,
367 viewport_details: window.viewport_details(),
368 user_content_manager_id: response.user_content_manager_id,
369 theme: window.theme(),
372 target_snapshot_params: TargetSnapshotParams {
373 sandboxing_flags: sandboxing_flag_set,
374 iframe_element_referrer_policy: ReferrerPolicy::EmptyString,
375 },
376 };
377
378 with_script_thread(|script_thread| {
379 script_thread.spawn_pipeline(new_pipeline_info);
380 });
381
382 let new_window_proxy = ScriptThread::find_document(response.new_pipeline_id)
383 .and_then(|doc| doc.browsing_context())?;
384 if name.to_lowercase() != "_blank" {
385 new_window_proxy.set_name(name);
386 }
387 if noopener {
388 new_window_proxy.disown();
389 } else {
390 let (sender, receiver) = generic_channel::channel().unwrap();
395
396 let msg = WebStorageThreadMsg::Clone {
397 sender,
398 src: window.window_proxy().webview_id(),
399 dest: response.new_webview_id,
400 };
401
402 GenericSend::send(document.global().storage_threads(), msg).unwrap();
403 receiver.recv().unwrap();
404 }
405 Some(new_window_proxy)
406 }
407
408 pub(crate) fn is_delaying_load_events_mode(&self) -> bool {
410 self.delaying_load_events_mode.get()
411 }
412
413 pub(crate) fn start_delaying_load_events_mode(&self) {
415 self.delaying_load_events_mode.set(true);
416 }
417
418 pub(crate) fn stop_delaying_load_events_mode(&self) {
420 self.delaying_load_events_mode.set(false);
421 if let Some(document) = self.document() {
422 if !document.loader().events_inhibited() {
423 ScriptThread::mark_document_with_no_blocked_loads(&document);
424 }
425 }
426 }
427
428 pub(crate) fn disown(&self) {
430 self.disowned.set(true);
431 }
432
433 pub(crate) fn close(&self) {
436 self.is_closing.set(true);
437 }
438
439 pub(crate) fn is_closing(&self) -> bool {
441 self.is_closing.get()
442 }
443
444 pub(crate) fn opener(&self, cx: &mut CurrentRealm, mut retval: MutableHandleValue) {
446 if self.disowned.get() {
447 return retval.set(NullValue());
448 }
449 let opener_id = match self.opener {
450 Some(opener_browsing_context_id) => opener_browsing_context_id,
451 None => return retval.set(NullValue()),
452 };
453 let parent_browsing_context = self.parent.as_deref();
454 let opener_proxy = match self.script_window_proxies.find_window_proxy(opener_id) {
455 Some(window_proxy) => window_proxy,
456 None => {
457 let sender_pipeline_id = self.currently_active().unwrap();
458 match ScriptThread::get_top_level_for_browsing_context(
459 self.webview_id(),
460 sender_pipeline_id,
461 opener_id,
462 ) {
463 Some(opener_top_id) => {
464 let global_to_clone_from = GlobalScope::from_current_realm(cx);
465 let creator =
466 CreatorBrowsingContextInfo::from(parent_browsing_context, None);
467 WindowProxy::new_dissimilar_origin(
468 cx,
469 &global_to_clone_from,
470 opener_id,
471 opener_top_id,
472 None,
473 None,
474 creator,
475 )
476 },
477 None => return retval.set(NullValue()),
478 }
479 },
480 };
481 if opener_proxy.is_browsing_context_discarded() {
482 return retval.set(NullValue());
483 }
484 opener_proxy.safe_to_jsval(cx, retval);
485 }
486
487 pub(crate) fn open(
489 &self,
490 cx: &mut js::context::JSContext,
491 url: USVString,
492 target: DOMString,
493 features: DOMString,
494 ) -> Fallible<Option<DomRoot<WindowProxy>>> {
495 if self.discarded.get() {
501 return Ok(None);
502 }
503 let non_empty_target = if target.is_empty() {
505 DOMString::from("_blank")
506 } else {
507 target
508 };
509 let tokenized_features = tokenize_open_features(features);
511 let noreferrer = parse_open_feature_boolean(&tokenized_features, "noreferrer");
515
516 let noopener = if noreferrer {
519 true
520 } else {
521 parse_open_feature_boolean(&tokenized_features, "noopener")
522 };
523 let (chosen, new) = match self.choose_browsing_context(non_empty_target, noopener) {
533 (Some(chosen), new) => (chosen, new),
534 (None, _) => return Ok(None),
535 };
536 let target_document = match chosen.document() {
539 Some(target_document) => target_document,
540 None => return Ok(None),
541 };
542 let has_trustworthy_ancestor_origin = if new {
543 target_document.has_trustworthy_ancestor_or_current_origin()
544 } else {
545 false
546 };
547 let target_window = target_document.window();
548 if !url.is_empty() {
551 let existing_document = self
552 .currently_active
553 .get()
554 .and_then(ScriptThread::find_document)
555 .unwrap();
556 let url = match existing_document.url().join(&url) {
557 Ok(url) => url,
558 Err(_) => return Err(Error::Syntax(None)),
559 };
560 let referrer = if noreferrer {
561 Referrer::NoReferrer
562 } else {
563 target_window.as_global_scope().get_referrer()
564 };
565 let csp_list = existing_document.get_csp_list().clone();
567 target_document.set_csp_list(csp_list);
568
569 let mut load_data = LoadData::new(
573 LoadOrigin::Script(existing_document.origin().snapshot()),
574 url,
575 target_document.about_base_url(),
576 Some(target_window.pipeline_id()),
577 referrer,
578 target_document.get_referrer_policy(),
579 Some(target_window.as_global_scope().is_secure_context()),
580 Some(target_document.insecure_requests_policy()),
581 has_trustworthy_ancestor_origin,
582 target_document.creation_sandboxing_flag_set_considering_parent_iframe(),
583 );
584
585 if load_data.url.scheme() == "javascript" {
588 let existing_global = existing_document.global();
589
590 if !ScriptThread::can_navigate_to_javascript_url(
592 cx,
593 &existing_global,
594 target_window.as_global_scope(),
595 &mut load_data,
596 None,
597 ) {
598 return Ok(target_document.browsing_context());
600 }
601 }
602
603 let history_handling = if new {
604 NavigationHistoryBehavior::Replace
605 } else {
606 NavigationHistoryBehavior::Push
607 };
608 navigate(cx, target_window, history_handling, false, load_data);
609 }
610 if noopener {
612 return Ok(None);
613 }
614 Ok(target_document.browsing_context())
616 }
617
618 pub(crate) fn choose_browsing_context(
620 &self,
621 name: DOMString,
622 noopener: bool,
623 ) -> (Option<DomRoot<WindowProxy>>, bool) {
624 match name.to_lowercase().as_ref() {
625 "" | "_self" => {
626 (Some(DomRoot::from_ref(self)), false)
628 },
629 "_parent" => {
630 if let Some(parent) = self.parent() {
632 return (Some(DomRoot::from_ref(parent)), false);
633 }
634 (None, false)
635 },
636 "_top" => {
637 (Some(DomRoot::from_ref(self.top())), false)
639 },
640 "_blank" => (self.create_auxiliary_browsing_context(name, noopener), true),
641 _ => {
642 match ScriptThread::find_window_proxy_by_name(&name) {
647 Some(proxy) => (Some(proxy), false),
648 None => (self.create_auxiliary_browsing_context(name, noopener), true),
649 }
650 },
651 }
652 }
653
654 pub(crate) fn is_auxiliary(&self) -> bool {
655 self.opener.is_some()
656 }
657
658 pub(crate) fn discard_browsing_context(&self) {
659 self.discarded.set(true);
660 }
661
662 pub(crate) fn is_browsing_context_discarded(&self) -> bool {
663 self.discarded.get()
664 }
665
666 pub(crate) fn browsing_context_id(&self) -> BrowsingContextId {
667 self.browsing_context_id
668 }
669
670 pub(crate) fn webview_id(&self) -> WebViewId {
671 self.webview_id
672 }
673
674 pub(crate) fn frame_element(&self) -> Option<&Element> {
678 self.frame_element.as_deref()
679 }
680
681 pub(crate) fn document(&self) -> Option<DomRoot<Document>> {
682 self.currently_active
683 .get()
684 .and_then(ScriptThread::find_document)
685 }
686
687 pub(crate) fn parent(&self) -> Option<&WindowProxy> {
688 self.parent.as_deref()
689 }
690
691 pub(crate) fn top(&self) -> &WindowProxy {
692 let mut result = self;
693 while let Some(parent) = result.parent() {
694 result = parent;
695 }
696 result
697 }
698
699 pub fn focus(&self) {
703 debug!(
704 "Requesting the constellation to initiate a focus operation for \
705 browsing context {}",
706 self.browsing_context_id()
707 );
708 self.global()
709 .script_to_constellation_chan()
710 .send(ScriptToConstellationMessage::FocusRemoteDocument(
711 self.browsing_context_id(),
712 ))
713 .unwrap();
714 }
715
716 pub fn document_origin(&self) -> Option<String> {
717 let pipeline_id = self.currently_active()?;
718 let (result_sender, result_receiver) = generic_channel::channel().unwrap();
719 self.global()
720 .script_to_constellation_chan()
721 .send(ScriptToConstellationMessage::GetDocumentOrigin(
722 pipeline_id,
723 result_sender,
724 ))
725 .ok()?;
726 result_receiver.recv().ok()?
727 }
728
729 #[expect(unsafe_code)]
730 fn set_window(&self, window: &GlobalScope, handler: &WindowProxyHandler, _can_gc: CanGc) {
734 unsafe {
735 debug!("Setting window of {:p}.", self);
736
737 let cx = GlobalScope::get_cx();
738 let window_jsobject = window.reflector().get_jsobject();
739 let old_js_proxy = self.reflector.get_jsobject();
740 assert!(!window_jsobject.get().is_null());
741 assert_ne!(
742 ((*get_object_class(window_jsobject.get())).flags & JSCLASS_IS_GLOBAL),
743 0
744 );
745 let _ac = enter_realm(window);
746
747 SetProxyReservedSlot(old_js_proxy.get(), 0, &PrivateValue(ptr::null_mut()));
749
750 rooted!(in(*cx) let new_js_proxy = handler.new_window_proxy(&cx, window_jsobject));
756 SetProxyReservedSlot(new_js_proxy.get(), 0, &PrivateValue(ptr::null_mut()));
759 debug!(
760 "Transplanting proxy from {:p} to {:p}.",
761 old_js_proxy.get(),
762 new_js_proxy.get()
763 );
764 rooted!(in(*cx) let new_js_proxy = JS_TransplantObject(*cx, old_js_proxy, new_js_proxy.handle()));
765 debug!("Transplanted proxy is {:p}.", new_js_proxy.get());
766
767 SetProxyReservedSlot(
769 new_js_proxy.get(),
770 0,
771 &PrivateValue(self as *const _ as *const libc::c_void),
772 );
773
774 SetWindowProxy(*cx, window_jsobject, new_js_proxy.handle());
776
777 debug!(
779 "Setting reflector of {:p} to {:p}.",
780 self,
781 new_js_proxy.get()
782 );
783 self.reflector.rootable().set(new_js_proxy.get());
784 }
785 }
786
787 pub(crate) fn set_currently_active(&self, window: &Window, can_gc: CanGc) {
788 if let Some(pipeline_id) = self.currently_active() {
789 if pipeline_id == window.pipeline_id() {
790 return debug!(
791 "Attempt to set the currently active window to the currently active window."
792 );
793 }
794 }
795
796 let global_scope = window.as_global_scope();
797 self.set_window(global_scope, WindowProxyHandler::proxy_handler(), can_gc);
798 self.currently_active.set(Some(global_scope.pipeline_id()));
799 }
800
801 pub(crate) fn unset_currently_active(&self, cx: &mut js::context::JSContext) {
802 if self.currently_active().is_none() {
803 return debug!(
804 "Attempt to unset the currently active window on a windowproxy that does not have one."
805 );
806 }
807 let globalscope = self.global();
808 let window = DissimilarOriginWindow::new(cx, &globalscope, self);
809 self.set_window(
810 window.upcast(),
811 WindowProxyHandler::x_origin_proxy_handler(),
812 CanGc::from_cx(cx),
813 );
814 self.currently_active.set(None);
815 }
816
817 pub(crate) fn currently_active(&self) -> Option<PipelineId> {
818 self.currently_active.get()
819 }
820
821 pub(crate) fn get_name(&self) -> DOMString {
822 self.name.borrow().clone()
823 }
824
825 pub(crate) fn set_name(&self, name: DOMString) {
826 *self.name.borrow_mut() = name;
827 }
828}
829
830#[derive(Debug, Deserialize, Serialize)]
842pub(crate) struct CreatorBrowsingContextInfo {
843 url: Option<ServoUrl>,
845
846 origin: Option<ImmutableOrigin>,
848}
849
850impl CreatorBrowsingContextInfo {
851 pub(crate) fn from(
852 parent: Option<&WindowProxy>,
853 opener: Option<&WindowProxy>,
854 ) -> CreatorBrowsingContextInfo {
855 let creator = match (parent, opener) {
856 (Some(parent), _) => parent.document(),
857 (None, Some(opener)) => opener.document(),
858 (None, None) => None,
859 };
860
861 let url = creator.as_deref().map(|document| document.url());
862 let origin = creator
863 .as_deref()
864 .map(|document| document.origin().immutable().clone());
865
866 CreatorBrowsingContextInfo { url, origin }
867 }
868}
869
870fn tokenize_open_features(features: DOMString) -> IndexMap<String, String> {
872 let is_feature_sep = |c: char| c.is_ascii_whitespace() || ['=', ','].contains(&c);
873 let mut tokenized_features = IndexMap::new();
875 let features = features.str();
877 let mut iter = features.chars();
878 let mut cur = iter.next();
879
880 while cur.is_some() {
882 let mut name = String::new();
884 let mut value = String::new();
885 while let Some(cur_char) = cur {
887 if !is_feature_sep(cur_char) {
888 break;
889 }
890 cur = iter.next();
891 }
892 while let Some(cur_char) = cur {
894 if is_feature_sep(cur_char) {
895 break;
896 }
897 name.push(cur_char.to_ascii_lowercase());
898 cur = iter.next();
899 }
900 let normalized_name = String::from(match name.as_ref() {
902 "screenx" => "left",
903 "screeny" => "top",
904 "innerwidth" => "width",
905 "innerheight" => "height",
906 _ => name.as_ref(),
907 });
908 while let Some(cur_char) = cur {
910 if cur_char == '=' || cur_char == ',' || !is_feature_sep(cur_char) {
911 break;
912 }
913 cur = iter.next();
914 }
915 if cur.is_some() && is_feature_sep(cur.unwrap()) {
917 while let Some(cur_char) = cur {
919 if !is_feature_sep(cur_char) || cur_char == ',' {
920 break;
921 }
922 cur = iter.next();
923 }
924 while let Some(cur_char) = cur {
926 if is_feature_sep(cur_char) {
927 break;
928 }
929 value.push(cur_char.to_ascii_lowercase());
930 cur = iter.next();
931 }
932 }
933 if !name.is_empty() {
935 tokenized_features.insert(normalized_name, value);
936 }
937 }
938 tokenized_features
940}
941
942fn parse_open_feature_boolean(tokenized_features: &IndexMap<String, String>, name: &str) -> bool {
944 if let Some(value) = tokenized_features.get(name) {
945 if value.is_empty() || value == "yes" {
947 return true;
948 }
949 if let Ok(int) = parse_integer(value.chars()) {
951 return int != 0;
952 }
953 }
954 false
956}
957
958#[expect(unsafe_code)]
962#[expect(non_snake_case)]
963unsafe fn GetSubframeWindowProxy(
964 cx: *mut JSContext,
965 proxy: RawHandleObject,
966 id: RawHandleId,
967) -> Option<(DomRoot<WindowProxy>, u32)> {
968 let index = get_array_index_from_id(unsafe { Handle::from_raw(id) });
969 if let Some(index) = index {
970 let mut slot = UndefinedValue();
971 unsafe { GetProxyPrivate(*proxy, &mut slot) };
972 rooted!(in(cx) let target = slot.to_object());
973 let script_window_proxies = ScriptThread::window_proxies();
974 if let Ok(win) = root_from_handleobject::<Window>(target.handle(), cx) {
975 let browsing_context_id = win.window_proxy().browsing_context_id();
976 let (result_sender, result_receiver) = generic_channel::channel().unwrap();
977
978 let _ = win.as_global_scope().script_to_constellation_chan().send(
979 ScriptToConstellationMessage::GetChildBrowsingContextId(
980 browsing_context_id,
981 index as usize,
982 result_sender,
983 ),
984 );
985 return result_receiver
986 .recv()
987 .ok()
988 .and_then(|maybe_bcid| maybe_bcid)
989 .and_then(|id| script_window_proxies.find_window_proxy(id))
990 .map(|proxy| (proxy, (JSPROP_ENUMERATE | JSPROP_READONLY) as u32));
991 } else if let Ok(win) =
992 root_from_handleobject::<DissimilarOriginWindow>(target.handle(), cx)
993 {
994 let browsing_context_id = win.window_proxy().browsing_context_id();
995 let (result_sender, result_receiver) = generic_channel::channel().unwrap();
996
997 let _ = win.global().script_to_constellation_chan().send(
998 ScriptToConstellationMessage::GetChildBrowsingContextId(
999 browsing_context_id,
1000 index as usize,
1001 result_sender,
1002 ),
1003 );
1004 return result_receiver
1005 .recv()
1006 .ok()
1007 .and_then(|maybe_bcid| maybe_bcid)
1008 .and_then(|id| script_window_proxies.find_window_proxy(id))
1009 .map(|proxy| (proxy, JSPROP_READONLY as u32));
1010 }
1011 }
1012
1013 None
1014}
1015
1016#[expect(unsafe_code)]
1017unsafe extern "C" fn get_own_property_descriptor(
1018 cx: *mut JSContext,
1019 proxy: RawHandleObject,
1020 id: RawHandleId,
1021 desc: RawMutableHandle<PropertyDescriptor>,
1022 is_none: *mut bool,
1023) -> bool {
1024 let window = unsafe { GetSubframeWindowProxy(cx, proxy, id) };
1025 if let Some((window, attrs)) = window {
1026 rooted!(in(cx) let mut val = UndefinedValue());
1027 unsafe { window.to_jsval(cx, val.handle_mut()) };
1028 set_property_descriptor(
1029 unsafe { MutableHandle::from_raw(desc) },
1030 val.handle(),
1031 attrs,
1032 unsafe { &mut *is_none },
1033 );
1034 return true;
1035 }
1036
1037 let mut slot = UndefinedValue();
1038 unsafe { GetProxyPrivate(proxy.get(), &mut slot) };
1039 rooted!(in(cx) let target = slot.to_object());
1040 unsafe { JS_GetOwnPropertyDescriptorById(cx, target.handle().into(), id, desc, is_none) }
1041}
1042
1043#[expect(unsafe_code)]
1044unsafe extern "C" fn define_property(
1045 cx: *mut JSContext,
1046 proxy: RawHandleObject,
1047 id: RawHandleId,
1048 desc: RawHandle<PropertyDescriptor>,
1049 res: *mut ObjectOpResult,
1050) -> bool {
1051 if get_array_index_from_id(unsafe { Handle::from_raw(id) }).is_some() {
1052 unsafe {
1057 (*res).code_ = JSErrNum::JSMSG_CANT_DEFINE_WINDOW_ELEMENT as ::libc::uintptr_t;
1058 }
1059 return true;
1060 }
1061
1062 let mut slot = UndefinedValue();
1063 unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1064 rooted!(in(cx) let target = slot.to_object());
1065 unsafe { JS_DefinePropertyById(cx, target.handle().into(), id, desc, res) }
1066}
1067
1068#[expect(unsafe_code)]
1069unsafe extern "C" fn has(
1070 cx: *mut JSContext,
1071 proxy: RawHandleObject,
1072 id: RawHandleId,
1073 bp: *mut bool,
1074) -> bool {
1075 let window = unsafe { GetSubframeWindowProxy(cx, proxy, id) };
1076 if window.is_some() {
1077 unsafe { *bp = true };
1078 return true;
1079 }
1080
1081 let mut slot = UndefinedValue();
1082 unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1083 rooted!(in(cx) let target = slot.to_object());
1084 let mut found = false;
1085 if !unsafe { JS_HasPropertyById(cx, target.handle().into(), id, &mut found) } {
1086 return false;
1087 }
1088
1089 unsafe { *bp = found };
1090 true
1091}
1092
1093#[expect(unsafe_code)]
1094unsafe extern "C" fn get(
1095 cx: *mut JSContext,
1096 proxy: RawHandleObject,
1097 receiver: RawHandleValue,
1098 id: RawHandleId,
1099 vp: RawMutableHandleValue,
1100) -> bool {
1101 let window = unsafe { GetSubframeWindowProxy(cx, proxy, id) };
1102 if let Some((window, _attrs)) = window {
1103 unsafe { window.to_jsval(cx, MutableHandle::from_raw(vp)) };
1104 return true;
1105 }
1106
1107 let mut slot = UndefinedValue();
1108 unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1109 rooted!(in(cx) let target = slot.to_object());
1110 unsafe { JS_ForwardGetPropertyTo(cx, target.handle().into(), id, receiver, vp) }
1111}
1112
1113#[expect(unsafe_code)]
1114unsafe extern "C" fn set(
1115 cx: *mut JSContext,
1116 proxy: RawHandleObject,
1117 id: RawHandleId,
1118 v: RawHandleValue,
1119 receiver: RawHandleValue,
1120 res: *mut ObjectOpResult,
1121) -> bool {
1122 if get_array_index_from_id(unsafe { Handle::from_raw(id) }).is_some() {
1123 unsafe { (*res).code_ = JSErrNum::JSMSG_READ_ONLY as ::libc::uintptr_t };
1125 return true;
1126 }
1127
1128 let mut slot = UndefinedValue();
1129 unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1130 rooted!(in(cx) let target = slot.to_object());
1131 unsafe { JS_ForwardSetPropertyTo(cx, target.handle().into(), id, v, receiver, res) }
1132}
1133
1134#[expect(unsafe_code)]
1135unsafe extern "C" fn get_prototype_if_ordinary(
1136 _: *mut JSContext,
1137 _: RawHandleObject,
1138 is_ordinary: *mut bool,
1139 _: RawMutableHandleObject,
1140) -> bool {
1141 unsafe { *is_ordinary = false };
1154 true
1155}
1156
1157static PROXY_TRAPS: ProxyTraps = ProxyTraps {
1158 enter: None,
1161 getOwnPropertyDescriptor: Some(get_own_property_descriptor),
1162 defineProperty: Some(define_property),
1163 ownPropertyKeys: None,
1164 delete_: None,
1165 enumerate: None,
1166 getPrototypeIfOrdinary: Some(get_prototype_if_ordinary),
1167 getPrototype: None, setPrototype: None,
1169 setImmutablePrototype: None,
1170 preventExtensions: None,
1171 isExtensible: None,
1172 has: Some(has),
1173 get: Some(get),
1174 set: Some(set),
1175 call: None,
1176 construct: None,
1177 hasOwn: None,
1178 getOwnEnumerablePropertyKeys: None,
1179 nativeCall: None,
1180 objectClassIs: None,
1181 className: None,
1182 fun_toString: None,
1183 boxedValue_unbox: None,
1184 defaultValue: None,
1185 trace: Some(trace),
1186 finalize: Some(finalize),
1187 objectMoved: None,
1188 isCallable: None,
1189 isConstructor: None,
1190};
1191
1192pub(crate) struct WindowProxyHandler(*const libc::c_void);
1195
1196impl MallocSizeOf for WindowProxyHandler {
1197 fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize {
1198 0
1200 }
1201}
1202
1203#[expect(unsafe_code)]
1205unsafe impl Send for WindowProxyHandler {}
1206#[expect(unsafe_code)]
1208unsafe impl Sync for WindowProxyHandler {}
1209
1210#[expect(unsafe_code)]
1211impl WindowProxyHandler {
1212 fn new(traps: &ProxyTraps) -> Self {
1213 let ptr = unsafe { CreateWrapperProxyHandler(traps) };
1215 assert!(!ptr.is_null());
1216 Self(ptr)
1217 }
1218
1219 pub(crate) fn x_origin_proxy_handler() -> &'static Self {
1221 use std::sync::OnceLock;
1222 static SINGLETON: OnceLock<WindowProxyHandler> = OnceLock::new();
1228 SINGLETON.get_or_init(|| Self::new(&XORIGIN_PROXY_TRAPS))
1229 }
1230
1231 pub(crate) fn proxy_handler() -> &'static Self {
1233 use std::sync::OnceLock;
1234 static SINGLETON: OnceLock<WindowProxyHandler> = OnceLock::new();
1240 SINGLETON.get_or_init(|| Self::new(&PROXY_TRAPS))
1241 }
1242
1243 pub(crate) fn new_window_proxy(
1246 &self,
1247 cx: &crate::script_runtime::JSContext,
1248 window_jsobject: js::gc::HandleObject,
1249 ) -> *mut JSObject {
1250 let obj = unsafe { NewWindowProxy(**cx, window_jsobject, self.0) };
1251 assert!(!obj.is_null());
1252 obj
1253 }
1254}
1255
1256#[expect(unsafe_code)]
1257impl Drop for WindowProxyHandler {
1258 fn drop(&mut self) {
1259 unsafe {
1262 DeleteWrapperProxyHandler(self.0);
1263 }
1264 }
1265}
1266
1267#[expect(unsafe_code)]
1275fn throw_security_error(cx: SafeJSContext, realm: InRealm) -> bool {
1276 if !unsafe { JS_IsExceptionPending(*cx) } {
1277 let global = unsafe { GlobalScope::from_context(*cx, realm) };
1278 throw_dom_exception(cx, &global, Error::Security(None), CanGc::note());
1279 }
1280 false
1281}
1282
1283#[expect(unsafe_code)]
1284unsafe extern "C" fn has_xorigin(
1285 cx: *mut JSContext,
1286 proxy: RawHandleObject,
1287 id: RawHandleId,
1288 bp: *mut bool,
1289) -> bool {
1290 let mut slot = UndefinedValue();
1291 unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1292 rooted!(in(cx) let target = slot.to_object());
1293 let mut found = false;
1294 unsafe { JS_HasOwnPropertyById(cx, target.handle().into(), id, &mut found) };
1295 if found {
1296 unsafe { *bp = true };
1297 true
1298 } else {
1299 let cx = unsafe { SafeJSContext::from_ptr(cx) };
1300 let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
1301 throw_security_error(cx, InRealm::Already(&in_realm_proof))
1302 }
1303}
1304
1305#[expect(unsafe_code)]
1306unsafe extern "C" fn get_xorigin(
1307 cx: *mut JSContext,
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 JSContext,
1321 _: RawHandleObject,
1322 _: RawHandleId,
1323 _: RawHandleValue,
1324 _: RawHandleValue,
1325 _: *mut ObjectOpResult,
1326) -> bool {
1327 let cx = unsafe { SafeJSContext::from_ptr(cx) };
1328 let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
1329 throw_security_error(cx, InRealm::Already(&in_realm_proof))
1330}
1331
1332#[expect(unsafe_code)]
1333unsafe extern "C" fn delete_xorigin(
1334 cx: *mut JSContext,
1335 _: RawHandleObject,
1336 _: RawHandleId,
1337 _: *mut ObjectOpResult,
1338) -> bool {
1339 let cx = unsafe { SafeJSContext::from_ptr(cx) };
1340 let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
1341 throw_security_error(cx, InRealm::Already(&in_realm_proof))
1342}
1343
1344#[expect(unsafe_code)]
1345#[expect(non_snake_case)]
1346unsafe extern "C" fn getOwnPropertyDescriptor_xorigin(
1347 cx: *mut JSContext,
1348 proxy: RawHandleObject,
1349 id: RawHandleId,
1350 desc: RawMutableHandle<PropertyDescriptor>,
1351 is_none: *mut bool,
1352) -> bool {
1353 let mut found = false;
1354 unsafe { has_xorigin(cx, proxy, id, &mut found) };
1355 found && unsafe { get_own_property_descriptor(cx, proxy, id, desc, is_none) }
1356}
1357
1358#[expect(unsafe_code)]
1359#[expect(non_snake_case)]
1360unsafe extern "C" fn defineProperty_xorigin(
1361 cx: *mut JSContext,
1362 _: RawHandleObject,
1363 _: RawHandleId,
1364 _: RawHandle<PropertyDescriptor>,
1365 _: *mut ObjectOpResult,
1366) -> bool {
1367 let cx = unsafe { SafeJSContext::from_ptr(cx) };
1368 let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
1369 throw_security_error(cx, InRealm::Already(&in_realm_proof))
1370}
1371
1372#[expect(unsafe_code)]
1373#[expect(non_snake_case)]
1374unsafe extern "C" fn preventExtensions_xorigin(
1375 cx: *mut JSContext,
1376 _: RawHandleObject,
1377 _: *mut ObjectOpResult,
1378) -> bool {
1379 let cx = unsafe { SafeJSContext::from_ptr(cx) };
1380 let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
1381 throw_security_error(cx, InRealm::Already(&in_realm_proof))
1382}
1383
1384static XORIGIN_PROXY_TRAPS: ProxyTraps = ProxyTraps {
1385 enter: None,
1386 getOwnPropertyDescriptor: Some(getOwnPropertyDescriptor_xorigin),
1387 defineProperty: Some(defineProperty_xorigin),
1388 ownPropertyKeys: None,
1389 delete_: Some(delete_xorigin),
1390 enumerate: None,
1391 getPrototypeIfOrdinary: None,
1392 getPrototype: None,
1393 setPrototype: None,
1394 setImmutablePrototype: None,
1395 preventExtensions: Some(preventExtensions_xorigin),
1396 isExtensible: None,
1397 has: Some(has_xorigin),
1398 get: Some(get_xorigin),
1399 set: Some(set_xorigin),
1400 call: None,
1401 construct: None,
1402 hasOwn: Some(has_xorigin),
1403 getOwnEnumerablePropertyKeys: None,
1404 nativeCall: None,
1405 objectClassIs: None,
1406 className: None,
1407 fun_toString: None,
1408 boxedValue_unbox: None,
1409 defaultValue: None,
1410 trace: Some(trace),
1411 finalize: Some(finalize),
1412 objectMoved: None,
1413 isCallable: None,
1414 isConstructor: None,
1415};
1416
1417#[expect(unsafe_code)]
1420unsafe extern "C" fn finalize(_fop: *mut GCContext, obj: *mut JSObject) {
1421 let mut slot = UndefinedValue();
1422 unsafe { GetProxyReservedSlot(obj, 0, &mut slot) };
1423 let this = slot.to_private() as *mut WindowProxy;
1424 if this.is_null() {
1425 return;
1427 }
1428 unsafe {
1429 (*this).reflector.drop_memory(&*this);
1430 let jsobject = (*this).reflector.get_jsobject().get();
1431 debug!(
1432 "WindowProxy finalize: {:p}, with reflector {:p} from {:p}.",
1433 this, jsobject, obj
1434 );
1435 let _ = Box::from_raw(this);
1436 }
1437}
1438
1439#[expect(unsafe_code)]
1440unsafe extern "C" fn trace(trc: *mut JSTracer, obj: *mut JSObject) {
1441 let mut slot = UndefinedValue();
1442 unsafe { GetProxyReservedSlot(obj, 0, &mut slot) };
1443 let this = slot.to_private() as *const WindowProxy;
1444 if this.is_null() {
1445 return;
1447 }
1448 unsafe { (*this).trace(trc) };
1449}