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 document_origin(&self) -> Option<String> {
700 let pipeline_id = self.currently_active()?;
701 let (result_sender, result_receiver) = generic_channel::channel().unwrap();
702 self.global()
703 .script_to_constellation_chan()
704 .send(ScriptToConstellationMessage::GetDocumentOrigin(
705 pipeline_id,
706 result_sender,
707 ))
708 .ok()?;
709 result_receiver.recv().ok()?
710 }
711
712 #[expect(unsafe_code)]
713 fn set_window(&self, window: &GlobalScope, handler: &WindowProxyHandler, _can_gc: CanGc) {
717 unsafe {
718 debug!("Setting window of {:p}.", self);
719
720 let cx = GlobalScope::get_cx();
721 let window_jsobject = window.reflector().get_jsobject();
722 let old_js_proxy = self.reflector.get_jsobject();
723 assert!(!window_jsobject.get().is_null());
724 assert_ne!(
725 ((*get_object_class(window_jsobject.get())).flags & JSCLASS_IS_GLOBAL),
726 0
727 );
728 let _ac = enter_realm(window);
729
730 SetProxyReservedSlot(old_js_proxy.get(), 0, &PrivateValue(ptr::null_mut()));
732
733 rooted!(in(*cx) let new_js_proxy = handler.new_window_proxy(&cx, window_jsobject));
739 SetProxyReservedSlot(new_js_proxy.get(), 0, &PrivateValue(ptr::null_mut()));
742 debug!(
743 "Transplanting proxy from {:p} to {:p}.",
744 old_js_proxy.get(),
745 new_js_proxy.get()
746 );
747 rooted!(in(*cx) let new_js_proxy = JS_TransplantObject(*cx, old_js_proxy, new_js_proxy.handle()));
748 debug!("Transplanted proxy is {:p}.", new_js_proxy.get());
749
750 SetProxyReservedSlot(
752 new_js_proxy.get(),
753 0,
754 &PrivateValue(self as *const _ as *const libc::c_void),
755 );
756
757 SetWindowProxy(*cx, window_jsobject, new_js_proxy.handle());
759
760 debug!(
762 "Setting reflector of {:p} to {:p}.",
763 self,
764 new_js_proxy.get()
765 );
766 self.reflector.rootable().set(new_js_proxy.get());
767 }
768 }
769
770 pub(crate) fn set_currently_active(&self, window: &Window, can_gc: CanGc) {
771 if let Some(pipeline_id) = self.currently_active() {
772 if pipeline_id == window.pipeline_id() {
773 return debug!(
774 "Attempt to set the currently active window to the currently active window."
775 );
776 }
777 }
778
779 let global_scope = window.as_global_scope();
780 self.set_window(global_scope, WindowProxyHandler::proxy_handler(), can_gc);
781 self.currently_active.set(Some(global_scope.pipeline_id()));
782 }
783
784 pub(crate) fn unset_currently_active(&self, cx: &mut js::context::JSContext) {
785 if self.currently_active().is_none() {
786 return debug!(
787 "Attempt to unset the currently active window on a windowproxy that does not have one."
788 );
789 }
790 let globalscope = self.global();
791 let window = DissimilarOriginWindow::new(cx, &globalscope, self);
792 self.set_window(
793 window.upcast(),
794 WindowProxyHandler::x_origin_proxy_handler(),
795 CanGc::from_cx(cx),
796 );
797 self.currently_active.set(None);
798 }
799
800 pub(crate) fn currently_active(&self) -> Option<PipelineId> {
801 self.currently_active.get()
802 }
803
804 pub(crate) fn get_name(&self) -> DOMString {
805 self.name.borrow().clone()
806 }
807
808 pub(crate) fn set_name(&self, name: DOMString) {
809 *self.name.borrow_mut() = name;
810 }
811}
812
813#[derive(Debug, Deserialize, Serialize)]
825pub(crate) struct CreatorBrowsingContextInfo {
826 url: Option<ServoUrl>,
828
829 origin: Option<ImmutableOrigin>,
831}
832
833impl CreatorBrowsingContextInfo {
834 pub(crate) fn from(
835 parent: Option<&WindowProxy>,
836 opener: Option<&WindowProxy>,
837 ) -> CreatorBrowsingContextInfo {
838 let creator = match (parent, opener) {
839 (Some(parent), _) => parent.document(),
840 (None, Some(opener)) => opener.document(),
841 (None, None) => None,
842 };
843
844 let url = creator.as_deref().map(|document| document.url());
845 let origin = creator
846 .as_deref()
847 .map(|document| document.origin().immutable().clone());
848
849 CreatorBrowsingContextInfo { url, origin }
850 }
851}
852
853fn tokenize_open_features(features: DOMString) -> IndexMap<String, String> {
855 let is_feature_sep = |c: char| c.is_ascii_whitespace() || ['=', ','].contains(&c);
856 let mut tokenized_features = IndexMap::new();
858 let features = features.str();
860 let mut iter = features.chars();
861 let mut cur = iter.next();
862
863 while cur.is_some() {
865 let mut name = String::new();
867 let mut value = String::new();
868 while let Some(cur_char) = cur {
870 if !is_feature_sep(cur_char) {
871 break;
872 }
873 cur = iter.next();
874 }
875 while let Some(cur_char) = cur {
877 if is_feature_sep(cur_char) {
878 break;
879 }
880 name.push(cur_char.to_ascii_lowercase());
881 cur = iter.next();
882 }
883 let normalized_name = String::from(match name.as_ref() {
885 "screenx" => "left",
886 "screeny" => "top",
887 "innerwidth" => "width",
888 "innerheight" => "height",
889 _ => name.as_ref(),
890 });
891 while let Some(cur_char) = cur {
893 if cur_char == '=' || cur_char == ',' || !is_feature_sep(cur_char) {
894 break;
895 }
896 cur = iter.next();
897 }
898 if cur.is_some() && is_feature_sep(cur.unwrap()) {
900 while let Some(cur_char) = cur {
902 if !is_feature_sep(cur_char) || cur_char == ',' {
903 break;
904 }
905 cur = iter.next();
906 }
907 while let Some(cur_char) = cur {
909 if is_feature_sep(cur_char) {
910 break;
911 }
912 value.push(cur_char.to_ascii_lowercase());
913 cur = iter.next();
914 }
915 }
916 if !name.is_empty() {
918 tokenized_features.insert(normalized_name, value);
919 }
920 }
921 tokenized_features
923}
924
925fn parse_open_feature_boolean(tokenized_features: &IndexMap<String, String>, name: &str) -> bool {
927 if let Some(value) = tokenized_features.get(name) {
928 if value.is_empty() || value == "yes" {
930 return true;
931 }
932 if let Ok(int) = parse_integer(value.chars()) {
934 return int != 0;
935 }
936 }
937 false
939}
940
941#[expect(unsafe_code)]
945#[expect(non_snake_case)]
946unsafe fn GetSubframeWindowProxy(
947 cx: *mut JSContext,
948 proxy: RawHandleObject,
949 id: RawHandleId,
950) -> Option<(DomRoot<WindowProxy>, u32)> {
951 let index = get_array_index_from_id(unsafe { Handle::from_raw(id) });
952 if let Some(index) = index {
953 let mut slot = UndefinedValue();
954 unsafe { GetProxyPrivate(*proxy, &mut slot) };
955 rooted!(in(cx) let target = slot.to_object());
956 let script_window_proxies = ScriptThread::window_proxies();
957 if let Ok(win) = root_from_handleobject::<Window>(target.handle(), cx) {
958 let browsing_context_id = win.window_proxy().browsing_context_id();
959 let (result_sender, result_receiver) = generic_channel::channel().unwrap();
960
961 let _ = win.as_global_scope().script_to_constellation_chan().send(
962 ScriptToConstellationMessage::GetChildBrowsingContextId(
963 browsing_context_id,
964 index as usize,
965 result_sender,
966 ),
967 );
968 return result_receiver
969 .recv()
970 .ok()
971 .and_then(|maybe_bcid| maybe_bcid)
972 .and_then(|id| script_window_proxies.find_window_proxy(id))
973 .map(|proxy| (proxy, (JSPROP_ENUMERATE | JSPROP_READONLY) as u32));
974 } else if let Ok(win) =
975 root_from_handleobject::<DissimilarOriginWindow>(target.handle(), cx)
976 {
977 let browsing_context_id = win.window_proxy().browsing_context_id();
978 let (result_sender, result_receiver) = generic_channel::channel().unwrap();
979
980 let _ = win.global().script_to_constellation_chan().send(
981 ScriptToConstellationMessage::GetChildBrowsingContextId(
982 browsing_context_id,
983 index as usize,
984 result_sender,
985 ),
986 );
987 return result_receiver
988 .recv()
989 .ok()
990 .and_then(|maybe_bcid| maybe_bcid)
991 .and_then(|id| script_window_proxies.find_window_proxy(id))
992 .map(|proxy| (proxy, JSPROP_READONLY as u32));
993 }
994 }
995
996 None
997}
998
999#[expect(unsafe_code)]
1000unsafe extern "C" fn get_own_property_descriptor(
1001 cx: *mut JSContext,
1002 proxy: RawHandleObject,
1003 id: RawHandleId,
1004 desc: RawMutableHandle<PropertyDescriptor>,
1005 is_none: *mut bool,
1006) -> bool {
1007 let window = unsafe { GetSubframeWindowProxy(cx, proxy, id) };
1008 if let Some((window, attrs)) = window {
1009 rooted!(in(cx) let mut val = UndefinedValue());
1010 unsafe { window.to_jsval(cx, val.handle_mut()) };
1011 set_property_descriptor(
1012 unsafe { MutableHandle::from_raw(desc) },
1013 val.handle(),
1014 attrs,
1015 unsafe { &mut *is_none },
1016 );
1017 return true;
1018 }
1019
1020 let mut slot = UndefinedValue();
1021 unsafe { GetProxyPrivate(proxy.get(), &mut slot) };
1022 rooted!(in(cx) let target = slot.to_object());
1023 unsafe { JS_GetOwnPropertyDescriptorById(cx, target.handle().into(), id, desc, is_none) }
1024}
1025
1026#[expect(unsafe_code)]
1027unsafe extern "C" fn define_property(
1028 cx: *mut JSContext,
1029 proxy: RawHandleObject,
1030 id: RawHandleId,
1031 desc: RawHandle<PropertyDescriptor>,
1032 res: *mut ObjectOpResult,
1033) -> bool {
1034 if get_array_index_from_id(unsafe { Handle::from_raw(id) }).is_some() {
1035 unsafe {
1040 (*res).code_ = JSErrNum::JSMSG_CANT_DEFINE_WINDOW_ELEMENT as ::libc::uintptr_t;
1041 }
1042 return true;
1043 }
1044
1045 let mut slot = UndefinedValue();
1046 unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1047 rooted!(in(cx) let target = slot.to_object());
1048 unsafe { JS_DefinePropertyById(cx, target.handle().into(), id, desc, res) }
1049}
1050
1051#[expect(unsafe_code)]
1052unsafe extern "C" fn has(
1053 cx: *mut JSContext,
1054 proxy: RawHandleObject,
1055 id: RawHandleId,
1056 bp: *mut bool,
1057) -> bool {
1058 let window = unsafe { GetSubframeWindowProxy(cx, proxy, id) };
1059 if window.is_some() {
1060 unsafe { *bp = true };
1061 return true;
1062 }
1063
1064 let mut slot = UndefinedValue();
1065 unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1066 rooted!(in(cx) let target = slot.to_object());
1067 let mut found = false;
1068 if !unsafe { JS_HasPropertyById(cx, target.handle().into(), id, &mut found) } {
1069 return false;
1070 }
1071
1072 unsafe { *bp = found };
1073 true
1074}
1075
1076#[expect(unsafe_code)]
1077unsafe extern "C" fn get(
1078 cx: *mut JSContext,
1079 proxy: RawHandleObject,
1080 receiver: RawHandleValue,
1081 id: RawHandleId,
1082 vp: RawMutableHandleValue,
1083) -> bool {
1084 let window = unsafe { GetSubframeWindowProxy(cx, proxy, id) };
1085 if let Some((window, _attrs)) = window {
1086 unsafe { window.to_jsval(cx, MutableHandle::from_raw(vp)) };
1087 return true;
1088 }
1089
1090 let mut slot = UndefinedValue();
1091 unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1092 rooted!(in(cx) let target = slot.to_object());
1093 unsafe { JS_ForwardGetPropertyTo(cx, target.handle().into(), id, receiver, vp) }
1094}
1095
1096#[expect(unsafe_code)]
1097unsafe extern "C" fn set(
1098 cx: *mut JSContext,
1099 proxy: RawHandleObject,
1100 id: RawHandleId,
1101 v: RawHandleValue,
1102 receiver: RawHandleValue,
1103 res: *mut ObjectOpResult,
1104) -> bool {
1105 if get_array_index_from_id(unsafe { Handle::from_raw(id) }).is_some() {
1106 unsafe { (*res).code_ = JSErrNum::JSMSG_READ_ONLY as ::libc::uintptr_t };
1108 return true;
1109 }
1110
1111 let mut slot = UndefinedValue();
1112 unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1113 rooted!(in(cx) let target = slot.to_object());
1114 unsafe { JS_ForwardSetPropertyTo(cx, target.handle().into(), id, v, receiver, res) }
1115}
1116
1117#[expect(unsafe_code)]
1118unsafe extern "C" fn get_prototype_if_ordinary(
1119 _: *mut JSContext,
1120 _: RawHandleObject,
1121 is_ordinary: *mut bool,
1122 _: RawMutableHandleObject,
1123) -> bool {
1124 unsafe { *is_ordinary = false };
1137 true
1138}
1139
1140static PROXY_TRAPS: ProxyTraps = ProxyTraps {
1141 enter: None,
1144 getOwnPropertyDescriptor: Some(get_own_property_descriptor),
1145 defineProperty: Some(define_property),
1146 ownPropertyKeys: None,
1147 delete_: None,
1148 enumerate: None,
1149 getPrototypeIfOrdinary: Some(get_prototype_if_ordinary),
1150 getPrototype: None, setPrototype: None,
1152 setImmutablePrototype: None,
1153 preventExtensions: None,
1154 isExtensible: None,
1155 has: Some(has),
1156 get: Some(get),
1157 set: Some(set),
1158 call: None,
1159 construct: None,
1160 hasOwn: None,
1161 getOwnEnumerablePropertyKeys: None,
1162 nativeCall: None,
1163 objectClassIs: None,
1164 className: None,
1165 fun_toString: None,
1166 boxedValue_unbox: None,
1167 defaultValue: None,
1168 trace: Some(trace),
1169 finalize: Some(finalize),
1170 objectMoved: None,
1171 isCallable: None,
1172 isConstructor: None,
1173};
1174
1175pub(crate) struct WindowProxyHandler(*const libc::c_void);
1178
1179impl MallocSizeOf for WindowProxyHandler {
1180 fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize {
1181 0
1183 }
1184}
1185
1186#[expect(unsafe_code)]
1188unsafe impl Send for WindowProxyHandler {}
1189#[expect(unsafe_code)]
1191unsafe impl Sync for WindowProxyHandler {}
1192
1193#[expect(unsafe_code)]
1194impl WindowProxyHandler {
1195 fn new(traps: &ProxyTraps) -> Self {
1196 let ptr = unsafe { CreateWrapperProxyHandler(traps) };
1198 assert!(!ptr.is_null());
1199 Self(ptr)
1200 }
1201
1202 pub(crate) fn x_origin_proxy_handler() -> &'static Self {
1204 use std::sync::OnceLock;
1205 static SINGLETON: OnceLock<WindowProxyHandler> = OnceLock::new();
1211 SINGLETON.get_or_init(|| Self::new(&XORIGIN_PROXY_TRAPS))
1212 }
1213
1214 pub(crate) fn proxy_handler() -> &'static Self {
1216 use std::sync::OnceLock;
1217 static SINGLETON: OnceLock<WindowProxyHandler> = OnceLock::new();
1223 SINGLETON.get_or_init(|| Self::new(&PROXY_TRAPS))
1224 }
1225
1226 pub(crate) fn new_window_proxy(
1229 &self,
1230 cx: &crate::script_runtime::JSContext,
1231 window_jsobject: js::gc::HandleObject,
1232 ) -> *mut JSObject {
1233 let obj = unsafe { NewWindowProxy(**cx, window_jsobject, self.0) };
1234 assert!(!obj.is_null());
1235 obj
1236 }
1237}
1238
1239#[expect(unsafe_code)]
1240impl Drop for WindowProxyHandler {
1241 fn drop(&mut self) {
1242 unsafe {
1245 DeleteWrapperProxyHandler(self.0);
1246 }
1247 }
1248}
1249
1250#[expect(unsafe_code)]
1258fn throw_security_error(cx: SafeJSContext, realm: InRealm) -> bool {
1259 if !unsafe { JS_IsExceptionPending(*cx) } {
1260 let global = unsafe { GlobalScope::from_context(*cx, realm) };
1261 throw_dom_exception(cx, &global, Error::Security(None), CanGc::deprecated_note());
1262 }
1263 false
1264}
1265
1266#[expect(unsafe_code)]
1267unsafe extern "C" fn has_xorigin(
1268 cx: *mut JSContext,
1269 proxy: RawHandleObject,
1270 id: RawHandleId,
1271 bp: *mut bool,
1272) -> bool {
1273 let mut slot = UndefinedValue();
1274 unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1275 rooted!(in(cx) let target = slot.to_object());
1276 let mut found = false;
1277 unsafe { JS_HasOwnPropertyById(cx, target.handle().into(), id, &mut found) };
1278 if found {
1279 unsafe { *bp = true };
1280 true
1281 } else {
1282 let cx = unsafe { SafeJSContext::from_ptr(cx) };
1283 let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
1284 throw_security_error(cx, InRealm::Already(&in_realm_proof))
1285 }
1286}
1287
1288#[expect(unsafe_code)]
1289unsafe extern "C" fn get_xorigin(
1290 cx: *mut JSContext,
1291 proxy: RawHandleObject,
1292 receiver: RawHandleValue,
1293 id: RawHandleId,
1294 vp: RawMutableHandleValue,
1295) -> bool {
1296 let mut found = false;
1297 unsafe { has_xorigin(cx, proxy, id, &mut found) };
1298 found && unsafe { get(cx, proxy, receiver, id, vp) }
1299}
1300
1301#[expect(unsafe_code)]
1302unsafe extern "C" fn set_xorigin(
1303 cx: *mut JSContext,
1304 _: RawHandleObject,
1305 _: RawHandleId,
1306 _: RawHandleValue,
1307 _: RawHandleValue,
1308 _: *mut ObjectOpResult,
1309) -> bool {
1310 let cx = unsafe { SafeJSContext::from_ptr(cx) };
1311 let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
1312 throw_security_error(cx, InRealm::Already(&in_realm_proof))
1313}
1314
1315#[expect(unsafe_code)]
1316unsafe extern "C" fn delete_xorigin(
1317 cx: *mut JSContext,
1318 _: RawHandleObject,
1319 _: RawHandleId,
1320 _: *mut ObjectOpResult,
1321) -> bool {
1322 let cx = unsafe { SafeJSContext::from_ptr(cx) };
1323 let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
1324 throw_security_error(cx, InRealm::Already(&in_realm_proof))
1325}
1326
1327#[expect(unsafe_code)]
1328#[expect(non_snake_case)]
1329unsafe extern "C" fn getOwnPropertyDescriptor_xorigin(
1330 cx: *mut JSContext,
1331 proxy: RawHandleObject,
1332 id: RawHandleId,
1333 desc: RawMutableHandle<PropertyDescriptor>,
1334 is_none: *mut bool,
1335) -> bool {
1336 let mut found = false;
1337 unsafe { has_xorigin(cx, proxy, id, &mut found) };
1338 found && unsafe { get_own_property_descriptor(cx, proxy, id, desc, is_none) }
1339}
1340
1341#[expect(unsafe_code)]
1342#[expect(non_snake_case)]
1343unsafe extern "C" fn defineProperty_xorigin(
1344 cx: *mut JSContext,
1345 _: RawHandleObject,
1346 _: RawHandleId,
1347 _: RawHandle<PropertyDescriptor>,
1348 _: *mut ObjectOpResult,
1349) -> bool {
1350 let cx = unsafe { SafeJSContext::from_ptr(cx) };
1351 let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
1352 throw_security_error(cx, InRealm::Already(&in_realm_proof))
1353}
1354
1355#[expect(unsafe_code)]
1356#[expect(non_snake_case)]
1357unsafe extern "C" fn preventExtensions_xorigin(
1358 cx: *mut JSContext,
1359 _: RawHandleObject,
1360 _: *mut ObjectOpResult,
1361) -> bool {
1362 let cx = unsafe { SafeJSContext::from_ptr(cx) };
1363 let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
1364 throw_security_error(cx, InRealm::Already(&in_realm_proof))
1365}
1366
1367static XORIGIN_PROXY_TRAPS: ProxyTraps = ProxyTraps {
1368 enter: None,
1369 getOwnPropertyDescriptor: Some(getOwnPropertyDescriptor_xorigin),
1370 defineProperty: Some(defineProperty_xorigin),
1371 ownPropertyKeys: None,
1372 delete_: Some(delete_xorigin),
1373 enumerate: None,
1374 getPrototypeIfOrdinary: None,
1375 getPrototype: None,
1376 setPrototype: None,
1377 setImmutablePrototype: None,
1378 preventExtensions: Some(preventExtensions_xorigin),
1379 isExtensible: None,
1380 has: Some(has_xorigin),
1381 get: Some(get_xorigin),
1382 set: Some(set_xorigin),
1383 call: None,
1384 construct: None,
1385 hasOwn: Some(has_xorigin),
1386 getOwnEnumerablePropertyKeys: None,
1387 nativeCall: None,
1388 objectClassIs: None,
1389 className: None,
1390 fun_toString: None,
1391 boxedValue_unbox: None,
1392 defaultValue: None,
1393 trace: Some(trace),
1394 finalize: Some(finalize),
1395 objectMoved: None,
1396 isCallable: None,
1397 isConstructor: None,
1398};
1399
1400#[expect(unsafe_code)]
1403unsafe extern "C" fn finalize(_fop: *mut GCContext, obj: *mut JSObject) {
1404 let mut slot = UndefinedValue();
1405 unsafe { GetProxyReservedSlot(obj, 0, &mut slot) };
1406 let this = slot.to_private() as *mut WindowProxy;
1407 if this.is_null() {
1408 return;
1410 }
1411 unsafe {
1412 (*this).reflector.drop_memory(&*this);
1413 let jsobject = (*this).reflector.get_jsobject().get();
1414 debug!(
1415 "WindowProxy finalize: {:p}, with reflector {:p} from {:p}.",
1416 this, jsobject, obj
1417 );
1418 let _ = Box::from_raw(this);
1419 }
1420}
1421
1422#[expect(unsafe_code)]
1423unsafe extern "C" fn trace(trc: *mut JSTracer, obj: *mut JSObject) {
1424 let mut slot = UndefinedValue();
1425 unsafe { GetProxyReservedSlot(obj, 0, &mut slot) };
1426 let this = slot.to_private() as *const WindowProxy;
1427 if this.is_null() {
1428 return;
1430 }
1431 unsafe { (*this).trace(trc) };
1432}