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