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::codegen::GenericBindings::WindowBinding::WindowMethods;
38use script_bindings::proxyhandler::set_property_descriptor;
39use script_bindings::reflector::{DomObject, MutDomObject, Reflector};
40use script_traits::NewPipelineInfo;
41use serde::{Deserialize, Serialize};
42use servo_base::generic_channel;
43use servo_base::generic_channel::GenericSend;
44use servo_base::id::{BrowsingContextId, PipelineId, WebViewId};
45use servo_constellation_traits::{
46 AuxiliaryWebViewCreationRequest, LoadData, LoadOrigin, NavigationHistoryBehavior,
47 ScriptToConstellationMessage, TargetSnapshotParams,
48};
49use servo_url::{ImmutableOrigin, ServoUrl};
50use storage_traits::webstorage_thread::WebStorageThreadMsg;
51use style::attr::parse_integer;
52
53use crate::dom::bindings::conversions::{ToJSValConvertible, root_from_handleobject};
54use crate::dom::bindings::error::{Error, Fallible, throw_dom_exception};
55use crate::dom::bindings::inheritance::Castable;
56use crate::dom::bindings::reflector::DomGlobal;
57use crate::dom::bindings::root::{Dom, DomRoot};
58use crate::dom::bindings::settings_stack::maybe_entry_global;
59use crate::dom::bindings::str::{DOMString, USVString};
60use crate::dom::bindings::trace::JSTraceable;
61use crate::dom::bindings::utils::get_array_index_from_id;
62use crate::dom::dissimilaroriginwindow::DissimilarOriginWindow;
63use crate::dom::document::Document;
64use crate::dom::element::Element;
65use crate::dom::globalscope::GlobalScope;
66use crate::dom::window::Window;
67use crate::navigation::navigate;
68use crate::script_thread::{ScriptThread, with_script_thread};
69use crate::script_window_proxies::ScriptWindowProxies;
70
71#[dom_struct]
72pub(crate) struct WindowProxy {
77 reflector: Reflector,
82
83 #[no_trace]
87 browsing_context_id: BrowsingContextId,
88
89 #[no_trace]
91 opener: Option<BrowsingContextId>,
92
93 #[no_trace]
96 webview_id: WebViewId,
97
98 name: DomRefCell<DOMString>,
101 #[no_trace]
107 currently_active: Cell<Option<PipelineId>>,
108
109 discarded: Cell<bool>,
111
112 disowned: Cell<bool>,
114
115 is_closing: Cell<bool>,
117
118 frame_element: Option<Dom<Element>>,
122
123 parent: Option<Dom<WindowProxy>>,
125
126 delaying_load_events_mode: Cell<bool>,
128
129 #[no_trace]
131 creator_url: Option<ServoUrl>,
132
133 #[no_trace]
135 creator_origin: Option<ImmutableOrigin>,
136
137 #[conditional_malloc_size_of]
139 script_window_proxies: Rc<ScriptWindowProxies>,
140}
141
142impl WindowProxy {
143 fn new_inherited(
144 browsing_context_id: BrowsingContextId,
145 webview_id: WebViewId,
146 currently_active: Option<PipelineId>,
147 frame_element: Option<&Element>,
148 parent: Option<&WindowProxy>,
149 opener: Option<BrowsingContextId>,
150 creator: CreatorBrowsingContextInfo,
151 ) -> WindowProxy {
152 let name = frame_element.map_or(DOMString::new(), |e| {
153 e.get_string_attribute(&local_name!("name"))
154 });
155 WindowProxy {
156 reflector: Reflector::new(),
157 browsing_context_id,
158 webview_id,
159 name: DomRefCell::new(name),
160 currently_active: Cell::new(currently_active),
161 discarded: Cell::new(false),
162 disowned: Cell::new(false),
163 is_closing: Cell::new(false),
164 frame_element: frame_element.map(Dom::from_ref),
165 parent: parent.map(Dom::from_ref),
166 delaying_load_events_mode: Cell::new(false),
167 opener,
168 creator_url: creator.url,
169 creator_origin: creator.origin,
170 script_window_proxies: ScriptThread::window_proxies(),
171 }
172 }
173
174 #[expect(unsafe_code)]
175 #[expect(clippy::too_many_arguments)]
176 pub(crate) fn new(
177 cx: &mut JSContext,
178 window: &Window,
179 browsing_context_id: BrowsingContextId,
180 webview_id: WebViewId,
181 frame_element: Option<&Element>,
182 parent: Option<&WindowProxy>,
183 opener: Option<BrowsingContextId>,
184 creator: CreatorBrowsingContextInfo,
185 ) -> DomRoot<WindowProxy> {
186 unsafe {
187 let handler = window.windowproxy_handler();
188
189 let window_jsobject = window.reflector().get_jsobject();
190 assert!(!window_jsobject.get().is_null());
191 assert_ne!(
192 ((*get_object_class(window_jsobject.get())).flags & JSCLASS_IS_GLOBAL),
193 0
194 );
195
196 let mut realm = AutoRealm::new_from_handle(cx, window_jsobject);
197 let cx = &mut realm;
198
199 rooted!(&in(cx) let js_proxy = handler.new_window_proxy(cx, window_jsobject));
201 assert!(!js_proxy.is_null());
202
203 let current = Some(window.upcast::<GlobalScope>().pipeline_id());
206 let window_proxy = Box::new(WindowProxy::new_inherited(
207 browsing_context_id,
208 webview_id,
209 current,
210 frame_element,
211 parent,
212 opener,
213 creator,
214 ));
215
216 SetProxyReservedSlot(
219 js_proxy.get(),
220 0,
221 &PrivateValue(&raw const (*window_proxy) as *const libc::c_void),
222 );
223
224 SetWindowProxy(cx, window_jsobject, js_proxy.handle());
226
227 debug!(
229 "Initializing reflector of {:p} to {:p}.",
230 window_proxy,
231 js_proxy.get()
232 );
233 window_proxy
234 .reflector
235 .init_reflector::<WindowProxy>(js_proxy.get());
236 DomRoot::from_ref(&*Box::into_raw(window_proxy))
237 }
238 }
239
240 #[expect(unsafe_code)]
241 pub(crate) fn new_dissimilar_origin(
242 cx: &mut JSContext,
243 global_to_clone_from: &GlobalScope,
244 browsing_context_id: BrowsingContextId,
245 webview_id: WebViewId,
246 parent: Option<&WindowProxy>,
247 opener: Option<BrowsingContextId>,
248 creator: CreatorBrowsingContextInfo,
249 ) -> DomRoot<WindowProxy> {
250 unsafe {
251 let handler = WindowProxyHandler::x_origin_proxy_handler();
252
253 let window_proxy = Box::new(WindowProxy::new_inherited(
255 browsing_context_id,
256 webview_id,
257 None,
258 None,
259 parent,
260 opener,
261 creator,
262 ));
263
264 let window = DissimilarOriginWindow::new(cx, global_to_clone_from, &window_proxy);
266 let window_jsobject = window.reflector().get_jsobject();
267 assert!(!window_jsobject.get().is_null());
268 assert_ne!(
269 ((*get_object_class(window_jsobject.get())).flags & JSCLASS_IS_GLOBAL),
270 0
271 );
272
273 let mut realm = AutoRealm::new_from_handle(cx, window_jsobject);
274 let cx = &mut realm;
275
276 rooted!(&in(cx) let js_proxy = handler.new_window_proxy(cx, window_jsobject));
278 assert!(!js_proxy.is_null());
279
280 SetProxyReservedSlot(
283 js_proxy.get(),
284 0,
285 &PrivateValue(&raw const (*window_proxy) as *const libc::c_void),
286 );
287
288 SetWindowProxy(cx, window_jsobject, js_proxy.handle());
290
291 debug!(
293 "Initializing reflector of {:p} to {:p}.",
294 window_proxy,
295 js_proxy.get()
296 );
297 window_proxy
298 .reflector
299 .init_reflector::<WindowProxy>(js_proxy.get());
300 DomRoot::from_ref(&*Box::into_raw(window_proxy))
301 }
302 }
303
304 fn create_auxiliary_browsing_context(
306 &self,
307 cx: &mut JSContext,
308 name: DOMString,
309 noopener: bool,
310 ) -> Option<DomRoot<WindowProxy>> {
311 let (response_sender, response_receiver) = generic_channel::channel().unwrap();
312 let window = self
313 .currently_active
314 .get()
315 .and_then(ScriptThread::find_document)
316 .map(|doc| DomRoot::from_ref(doc.window()))
317 .unwrap();
318
319 let document = self
320 .currently_active
321 .get()
322 .and_then(ScriptThread::find_document)
323 .expect("A WindowProxy creating an auxiliary to have an active document");
324
325 let sandboxing_flag_set = document.active_sandboxing_flag_set();
333 let propagate_sandbox = sandboxing_flag_set
334 .contains(SandboxingFlagSet::SANDBOX_PROPOGATES_TO_AUXILIARY_BROWSING_CONTEXTS_FLAG);
335 let sandboxing_flag_set = if propagate_sandbox {
336 sandboxing_flag_set
337 } else {
338 SandboxingFlagSet::empty()
339 };
340
341 let blank_url = ServoUrl::parse("about:blank").ok().unwrap();
342 let mut load_data = LoadData::new(
343 LoadOrigin::Script(document.origin().snapshot()),
344 blank_url,
345 Some(document.base_url()),
346 Some(window.pipeline_id()),
349 document.global().get_referrer(),
350 document.get_referrer_policy(),
351 None, None,
353 false,
354 sandboxing_flag_set,
355 );
356 load_data.is_initial_about_blank = true;
357 let load_info = AuxiliaryWebViewCreationRequest {
358 load_data: load_data.clone(),
359 opener_webview_id: window.webview_id(),
360 opener_pipeline_id: self.currently_active.get().unwrap(),
361 response_sender,
362 };
363 let constellation_msg = ScriptToConstellationMessage::CreateAuxiliaryWebView(load_info);
364 window.send_to_constellation(constellation_msg);
365
366 let response = response_receiver.recv().unwrap()?;
367 let new_browsing_context_id = BrowsingContextId::from(response.new_webview_id);
368 let new_pipeline_info = NewPipelineInfo {
369 parent_info: None,
370 new_pipeline_id: response.new_pipeline_id,
371 browsing_context_id: new_browsing_context_id,
372 webview_id: response.new_webview_id,
373 opener: Some(self.browsing_context_id),
374 load_data,
375 viewport_details: window.viewport_details(),
376 user_content_manager_id: response.user_content_manager_id,
377 theme: window.theme(),
380 target_snapshot_params: TargetSnapshotParams {
381 sandboxing_flags: sandboxing_flag_set,
382 iframe_element_referrer_policy: ReferrerPolicy::EmptyString,
383 },
384 };
385
386 with_script_thread(|script_thread| {
387 script_thread.spawn_pipeline(cx, new_pipeline_info);
388 });
389
390 let new_window_proxy = ScriptThread::find_document(response.new_pipeline_id)
391 .and_then(|doc| doc.browsing_context())?;
392 if name.to_lowercase() != "_blank" {
393 new_window_proxy.set_name(name);
394 }
395 if noopener {
396 new_window_proxy.disown();
397 } else {
398 let (sender, receiver) = generic_channel::channel().unwrap();
403
404 let msg = WebStorageThreadMsg::Clone {
405 sender,
406 src: window.window_proxy().webview_id(),
407 dest: response.new_webview_id,
408 };
409
410 GenericSend::send(document.global().storage_threads(), msg).unwrap();
411 receiver.recv().unwrap();
412 }
413 Some(new_window_proxy)
414 }
415
416 pub(crate) fn is_delaying_load_events_mode(&self) -> bool {
418 self.delaying_load_events_mode.get()
419 }
420
421 pub(crate) fn start_delaying_load_events_mode(&self) {
423 self.delaying_load_events_mode.set(true);
424 }
425
426 pub(crate) fn stop_delaying_load_events_mode(&self) {
428 self.delaying_load_events_mode.set(false);
429 if let Some(document) = self.document() &&
430 !document.loader().events_inhibited()
431 {
432 ScriptThread::mark_document_with_no_blocked_loads(&document);
433 }
434 }
435
436 pub(crate) fn disown(&self) {
438 self.disowned.set(true);
439 }
440
441 pub(crate) fn close(&self) {
444 self.is_closing.set(true);
445 }
446
447 pub(crate) fn is_closing(&self) -> bool {
449 self.is_closing.get()
450 }
451
452 pub(crate) fn opener(&self, cx: &mut CurrentRealm, mut retval: MutableHandleValue) {
454 if self.disowned.get() {
455 return retval.set(NullValue());
456 }
457 let opener_id = match self.opener {
458 Some(opener_browsing_context_id) => opener_browsing_context_id,
459 None => return retval.set(NullValue()),
460 };
461 let parent_browsing_context = self.parent.as_deref();
462 let opener_proxy = match self.script_window_proxies.find_window_proxy(opener_id) {
463 Some(window_proxy) => window_proxy,
464 None => {
465 let sender_pipeline_id = self.currently_active().unwrap();
466 match ScriptThread::get_top_level_for_browsing_context(
467 self.webview_id(),
468 sender_pipeline_id,
469 opener_id,
470 ) {
471 Some(opener_top_id) => {
472 let global_to_clone_from = GlobalScope::from_current_realm(cx);
473 let creator =
474 CreatorBrowsingContextInfo::from(parent_browsing_context, None);
475 WindowProxy::new_dissimilar_origin(
476 cx,
477 &global_to_clone_from,
478 opener_id,
479 opener_top_id,
480 None,
481 None,
482 creator,
483 )
484 },
485 None => return retval.set(NullValue()),
486 }
487 },
488 };
489 if opener_proxy.is_browsing_context_discarded() {
490 return retval.set(NullValue());
491 }
492 opener_proxy.safe_to_jsval(cx, retval);
493 }
494
495 pub(crate) fn open(
497 &self,
498 cx: &mut JSContext,
499 url: USVString,
500 target: DOMString,
501 features: DOMString,
502 ) -> Fallible<Option<DomRoot<WindowProxy>>> {
503 if self.discarded.get() {
509 return Ok(None);
510 }
511 let source_document = maybe_entry_global()
517 .map(|global| global.as_window().Document())
518 .or_else(|| self.document())
519 .expect("Must have an entry global or active document");
520 let url_record = if !url.is_empty() {
522 let Ok(url) = source_document.encoding_parse_a_url(&url) else {
524 return Err(Error::Syntax(Some(format!(
526 "Error parsing URL '{url}' relative to '{}'",
527 source_document.url()
528 ))));
529 };
530 Some(url)
531 } else {
532 None
534 };
535 let non_empty_target = if target.is_empty() {
537 DOMString::from("_blank")
538 } else {
539 target
540 };
541 let tokenized_features = tokenize_open_features(features);
543 let noreferrer = parse_open_feature_boolean(&tokenized_features, "noreferrer");
547
548 let noopener = if noreferrer {
551 true
552 } else {
553 parse_open_feature_boolean(&tokenized_features, "noopener")
554 };
555 let (chosen, new) = match self.choose_browsing_context(cx, non_empty_target, noopener) {
565 (Some(chosen), new) => (chosen, new),
566 (None, _) => return Ok(None),
567 };
568 let target_document = match chosen.document() {
571 Some(target_document) => target_document,
572 None => return Ok(None),
573 };
574 let has_trustworthy_ancestor_origin = if new {
575 target_document.has_trustworthy_ancestor_or_current_origin()
576 } else {
577 false
578 };
579 let target_window = target_document.window();
580 let url_record = url_record.unwrap_or(ServoUrl::parse("about:blank").unwrap());
582 if !url_record.matches_about_blank() {
586 let referrer = if noreferrer {
587 Referrer::NoReferrer
588 } else {
589 target_window.as_global_scope().get_referrer()
590 };
591 let csp_list = source_document.get_csp_list().clone();
593 target_document.set_csp_list(csp_list);
594
595 let mut load_data = LoadData::new(
599 LoadOrigin::Script(source_document.origin().snapshot()),
600 url_record,
601 target_document.about_base_url(),
602 Some(target_window.pipeline_id()),
603 referrer,
604 target_document.get_referrer_policy(),
605 Some(target_window.as_global_scope().is_secure_context()),
606 Some(target_document.insecure_requests_policy()),
607 has_trustworthy_ancestor_origin,
608 target_document.creation_sandboxing_flag_set_considering_parent_iframe(),
609 );
610
611 if load_data.url.scheme() == "javascript" {
614 let existing_global = source_document.global();
615
616 if !ScriptThread::can_navigate_to_javascript_url(
618 cx,
619 &existing_global,
620 target_window.as_global_scope(),
621 &mut load_data,
622 None,
623 ) {
624 return Ok(target_document.browsing_context());
626 }
627 }
628
629 let history_handling = if new {
630 NavigationHistoryBehavior::Replace
631 } else {
632 NavigationHistoryBehavior::Push
633 };
634 navigate(cx, target_window, history_handling, false, load_data);
635 }
636 if noopener {
638 return Ok(None);
639 }
640 Ok(target_document.browsing_context())
642 }
643
644 pub(crate) fn choose_browsing_context(
646 &self,
647 cx: &mut JSContext,
648 name: DOMString,
649 noopener: bool,
650 ) -> (Option<DomRoot<WindowProxy>>, bool) {
651 match name.to_lowercase().as_ref() {
652 "" | "_self" => {
653 (Some(DomRoot::from_ref(self)), false)
655 },
656 "_parent" => {
657 if let Some(parent) = self.parent() {
659 return (Some(DomRoot::from_ref(parent)), false);
660 }
661 (None, false)
662 },
663 "_top" => {
664 (Some(DomRoot::from_ref(self.top())), false)
666 },
667 "_blank" => (
668 self.create_auxiliary_browsing_context(cx, name, noopener),
669 true,
670 ),
671 _ => {
672 match ScriptThread::find_window_proxy_by_name(&name) {
677 Some(proxy) => (Some(proxy), false),
678 None => (
679 self.create_auxiliary_browsing_context(cx, name, noopener),
680 true,
681 ),
682 }
683 },
684 }
685 }
686
687 pub(crate) fn is_auxiliary(&self) -> bool {
688 self.opener.is_some()
689 }
690
691 pub(crate) fn discard_browsing_context(&self) {
692 self.discarded.set(true);
693 }
694
695 pub(crate) fn is_browsing_context_discarded(&self) -> bool {
696 self.discarded.get()
697 }
698
699 pub(crate) fn browsing_context_id(&self) -> BrowsingContextId {
700 self.browsing_context_id
701 }
702
703 pub(crate) fn webview_id(&self) -> WebViewId {
704 self.webview_id
705 }
706
707 pub(crate) fn frame_element(&self) -> Option<&Element> {
711 self.frame_element.as_deref()
712 }
713
714 pub(crate) fn document(&self) -> Option<DomRoot<Document>> {
715 self.currently_active
716 .get()
717 .and_then(ScriptThread::find_document)
718 }
719
720 pub(crate) fn parent(&self) -> Option<&WindowProxy> {
721 self.parent.as_deref()
722 }
723
724 pub(crate) fn top(&self) -> &WindowProxy {
725 let mut result = self;
726 while let Some(parent) = result.parent() {
727 result = parent;
728 }
729 result
730 }
731
732 pub fn document_origin(&self) -> Option<String> {
733 let pipeline_id = self.currently_active()?;
734 let (result_sender, result_receiver) = generic_channel::channel().unwrap();
735 self.global()
736 .script_to_constellation_chan()
737 .send(ScriptToConstellationMessage::GetDocumentOrigin(
738 pipeline_id,
739 result_sender,
740 ))
741 .ok()?;
742 result_receiver.recv().ok()?
743 }
744
745 #[expect(unsafe_code)]
746 fn set_window(&self, cx: &mut JSContext, window: &GlobalScope, handler: &WindowProxyHandler) {
750 unsafe {
751 debug!("Setting window of {:p}.", self);
752
753 let window_jsobject = window.reflector().get_jsobject();
754 let old_js_proxy = self.reflector.get_jsobject();
755 assert!(!window_jsobject.get().is_null());
756 assert_ne!(
757 ((*get_object_class(window_jsobject.get())).flags & JSCLASS_IS_GLOBAL),
758 0
759 );
760
761 let mut realm = AutoRealm::new_from_handle(cx, window_jsobject);
762 let cx = &mut realm;
763
764 SetProxyReservedSlot(old_js_proxy.get(), 0, &PrivateValue(ptr::null_mut()));
766
767 rooted!(&in(cx) let new_js_proxy = handler.new_window_proxy(cx, window_jsobject));
773 SetProxyReservedSlot(new_js_proxy.get(), 0, &PrivateValue(ptr::null_mut()));
776 debug!(
777 "Transplanting proxy from {:p} to {:p}.",
778 old_js_proxy.get(),
779 new_js_proxy.get()
780 );
781 rooted!(&in(cx) let new_js_proxy = JS_TransplantObject(cx, old_js_proxy, new_js_proxy.handle()));
782 debug!("Transplanted proxy is {:p}.", new_js_proxy.get());
783
784 SetProxyReservedSlot(
786 new_js_proxy.get(),
787 0,
788 &PrivateValue(self as *const _ as *const libc::c_void),
789 );
790
791 SetWindowProxy(cx, window_jsobject, new_js_proxy.handle());
793
794 debug!(
796 "Setting reflector of {:p} to {:p}.",
797 self,
798 new_js_proxy.get()
799 );
800 self.reflector.rootable().set(new_js_proxy.get());
801 }
802 }
803
804 pub(crate) fn set_currently_active(&self, cx: &mut JSContext, window: &Window) {
805 if let Some(pipeline_id) = self.currently_active() &&
806 pipeline_id == window.pipeline_id()
807 {
808 return debug!(
809 "Attempt to set the currently active window to the currently active window."
810 );
811 }
812
813 let global_scope = window.as_global_scope();
814 self.set_window(cx, global_scope, WindowProxyHandler::proxy_handler());
815 self.currently_active.set(Some(global_scope.pipeline_id()));
816 }
817
818 pub(crate) fn unset_currently_active(&self, cx: &mut JSContext) {
819 if self.currently_active().is_none() {
820 return debug!(
821 "Attempt to unset the currently active window on a windowproxy that does not have one."
822 );
823 }
824 let globalscope = self.global();
825 let window = DissimilarOriginWindow::new(cx, &globalscope, self);
826 self.set_window(
827 cx,
828 window.upcast(),
829 WindowProxyHandler::x_origin_proxy_handler(),
830 );
831 self.currently_active.set(None);
832 }
833
834 pub(crate) fn currently_active(&self) -> Option<PipelineId> {
835 self.currently_active.get()
836 }
837
838 pub(crate) fn get_name(&self) -> DOMString {
839 self.name.borrow().clone()
840 }
841
842 pub(crate) fn set_name(&self, name: DOMString) {
843 *self.name.borrow_mut() = name;
844 }
845}
846
847#[derive(Debug, Deserialize, Serialize)]
859pub(crate) struct CreatorBrowsingContextInfo {
860 url: Option<ServoUrl>,
862
863 origin: Option<ImmutableOrigin>,
865}
866
867impl CreatorBrowsingContextInfo {
868 pub(crate) fn from(
869 parent: Option<&WindowProxy>,
870 opener: Option<&WindowProxy>,
871 ) -> CreatorBrowsingContextInfo {
872 let creator = match (parent, opener) {
873 (Some(parent), _) => parent.document(),
874 (None, Some(opener)) => opener.document(),
875 (None, None) => None,
876 };
877
878 let url = creator.as_deref().map(|document| document.url());
879 let origin = creator
880 .as_deref()
881 .map(|document| document.origin().immutable().clone());
882
883 CreatorBrowsingContextInfo { url, origin }
884 }
885}
886
887fn tokenize_open_features(features: DOMString) -> IndexMap<String, String> {
889 let is_feature_sep = |c: char| c.is_ascii_whitespace() || ['=', ','].contains(&c);
890 let mut tokenized_features = IndexMap::new();
892 let features = features.str();
894 let mut iter = features.chars();
895 let mut cur = iter.next();
896
897 while cur.is_some() {
899 let mut name = String::new();
901 let mut value = String::new();
902 while let Some(cur_char) = cur {
904 if !is_feature_sep(cur_char) {
905 break;
906 }
907 cur = iter.next();
908 }
909 while let Some(cur_char) = cur {
911 if is_feature_sep(cur_char) {
912 break;
913 }
914 name.push(cur_char.to_ascii_lowercase());
915 cur = iter.next();
916 }
917 let normalized_name = String::from(match name.as_ref() {
919 "screenx" => "left",
920 "screeny" => "top",
921 "innerwidth" => "width",
922 "innerheight" => "height",
923 _ => name.as_ref(),
924 });
925 while let Some(cur_char) = cur {
927 if cur_char == '=' || cur_char == ',' || !is_feature_sep(cur_char) {
928 break;
929 }
930 cur = iter.next();
931 }
932 if cur.is_some() && is_feature_sep(cur.unwrap()) {
934 while let Some(cur_char) = cur {
936 if !is_feature_sep(cur_char) || cur_char == ',' {
937 break;
938 }
939 cur = iter.next();
940 }
941 while let Some(cur_char) = cur {
943 if is_feature_sep(cur_char) {
944 break;
945 }
946 value.push(cur_char.to_ascii_lowercase());
947 cur = iter.next();
948 }
949 }
950 if !name.is_empty() {
952 tokenized_features.insert(normalized_name, value);
953 }
954 }
955 tokenized_features
957}
958
959fn parse_open_feature_boolean(tokenized_features: &IndexMap<String, String>, name: &str) -> bool {
961 if let Some(value) = tokenized_features.get(name) {
962 if value.is_empty() || value == "yes" {
964 return true;
965 }
966 if let Ok(int) = parse_integer(value.chars()) {
968 return int != 0;
969 }
970 }
971 false
973}
974
975#[expect(unsafe_code)]
979#[expect(non_snake_case)]
980unsafe fn GetSubframeWindowProxy(
981 cx: &mut JSContext,
982 proxy: RawHandleObject,
983 id: RawHandleId,
984) -> Option<(DomRoot<WindowProxy>, u32)> {
985 let index = get_array_index_from_id(unsafe { Handle::from_raw(id) });
986 if let Some(index) = index {
987 let mut slot = UndefinedValue();
988 unsafe { GetProxyPrivate(*proxy, &mut slot) };
989 rooted!(&in(cx) let target = slot.to_object());
990 let script_window_proxies = ScriptThread::window_proxies();
991 if let Ok(win) = root_from_handleobject::<Window>(cx, target.handle()) {
992 let browsing_context_id = win.window_proxy().browsing_context_id();
993 let (result_sender, result_receiver) = generic_channel::channel().unwrap();
994
995 let _ = win.as_global_scope().script_to_constellation_chan().send(
996 ScriptToConstellationMessage::GetChildBrowsingContextId(
997 browsing_context_id,
998 index as usize,
999 result_sender,
1000 ),
1001 );
1002 return result_receiver
1003 .recv()
1004 .ok()
1005 .and_then(|maybe_bcid| maybe_bcid)
1006 .and_then(|id| script_window_proxies.find_window_proxy(id))
1007 .map(|proxy| (proxy, (JSPROP_ENUMERATE | JSPROP_READONLY) as u32));
1008 } else if let Ok(win) =
1009 root_from_handleobject::<DissimilarOriginWindow>(cx, target.handle())
1010 {
1011 let browsing_context_id = win.window_proxy().browsing_context_id();
1012 let (result_sender, result_receiver) = generic_channel::channel().unwrap();
1013
1014 let _ = win.global().script_to_constellation_chan().send(
1015 ScriptToConstellationMessage::GetChildBrowsingContextId(
1016 browsing_context_id,
1017 index as usize,
1018 result_sender,
1019 ),
1020 );
1021 return result_receiver
1022 .recv()
1023 .ok()
1024 .and_then(|maybe_bcid| maybe_bcid)
1025 .and_then(|id| script_window_proxies.find_window_proxy(id))
1026 .map(|proxy| (proxy, JSPROP_READONLY as u32));
1027 }
1028 }
1029
1030 None
1031}
1032
1033#[expect(unsafe_code)]
1034unsafe extern "C" fn get_own_property_descriptor(
1035 cx: *mut RawJSContext,
1036 proxy: RawHandleObject,
1037 id: RawHandleId,
1038 desc: RawMutableHandle<PropertyDescriptor>,
1039 is_none: *mut bool,
1040) -> bool {
1041 let mut cx = unsafe {
1042 JSContext::from_ptr(NonNull::new(cx).expect("JSContext should not be null in SM hook"))
1044 };
1045 let cx = &mut cx;
1046 let window = unsafe { GetSubframeWindowProxy(cx, proxy, id) };
1047 let desc = unsafe { MutableHandle::from_raw(desc) };
1048 if let Some((window, attrs)) = window {
1049 rooted!(&in(cx) let mut val = UndefinedValue());
1050 window.safe_to_jsval(cx, val.handle_mut());
1051 set_property_descriptor(desc, val.handle(), attrs, unsafe { &mut *is_none });
1052 return true;
1053 }
1054
1055 let mut slot = UndefinedValue();
1056 unsafe { GetProxyPrivate(proxy.get(), &mut slot) };
1057 rooted!(&in(cx) let target = slot.to_object());
1058 unsafe {
1059 JS_GetOwnPropertyDescriptorById(cx, target.handle(), Handle::from_raw(id), desc, is_none)
1060 }
1061}
1062
1063#[expect(unsafe_code)]
1064unsafe extern "C" fn define_property(
1065 cx: *mut RawJSContext,
1066 proxy: RawHandleObject,
1067 id: RawHandleId,
1068 desc: RawHandle<PropertyDescriptor>,
1069 res: *mut ObjectOpResult,
1070) -> bool {
1071 if get_array_index_from_id(unsafe { Handle::from_raw(id) }).is_some() {
1072 unsafe {
1077 (*res).code_ = JSErrNum::JSMSG_CANT_DEFINE_WINDOW_ELEMENT as ::libc::uintptr_t;
1078 }
1079 return true;
1080 }
1081
1082 let mut slot = UndefinedValue();
1083 unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1084 rooted!(in(cx) let target = slot.to_object());
1085 unsafe { JS_DefinePropertyById(cx, target.handle().into(), id, desc, res) }
1086}
1087
1088#[expect(unsafe_code)]
1089unsafe extern "C" fn has(
1090 cx: *mut RawJSContext,
1091 proxy: RawHandleObject,
1092 id: RawHandleId,
1093 bp: *mut bool,
1094) -> bool {
1095 let mut cx = unsafe {
1096 JSContext::from_ptr(NonNull::new(cx).expect("JSContext should not be null in SM hook"))
1098 };
1099 let cx = &mut cx;
1100 let window = unsafe { GetSubframeWindowProxy(cx, proxy, id) };
1101 if window.is_some() {
1102 unsafe { *bp = true };
1103 return true;
1104 }
1105
1106 let mut slot = UndefinedValue();
1107 unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1108 rooted!(&in(cx) let target = slot.to_object());
1109 let mut found = false;
1110 if !unsafe { JS_HasPropertyById(cx, target.handle(), Handle::from_raw(id), &mut found) } {
1111 return false;
1112 }
1113
1114 unsafe { *bp = found };
1115 true
1116}
1117
1118#[expect(unsafe_code)]
1119unsafe extern "C" fn get(
1120 cx: *mut RawJSContext,
1121 proxy: RawHandleObject,
1122 receiver: RawHandleValue,
1123 id: RawHandleId,
1124 vp: RawMutableHandleValue,
1125) -> bool {
1126 let mut cx = unsafe {
1127 JSContext::from_ptr(NonNull::new(cx).expect("JSContext should not be null in SM hook"))
1129 };
1130 let cx = &mut cx;
1131 let window = unsafe { GetSubframeWindowProxy(cx, proxy, id) };
1132 let vp = unsafe { MutableHandle::from_raw(vp) };
1133 if let Some((window, _attrs)) = window {
1134 window.safe_to_jsval(cx, vp);
1135 return true;
1136 }
1137
1138 let mut slot = UndefinedValue();
1139 unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1140 rooted!(&in(cx) let target = slot.to_object());
1141 unsafe {
1142 JS_ForwardGetPropertyTo(
1143 cx,
1144 target.handle(),
1145 Handle::from_raw(id),
1146 Handle::from_raw(receiver),
1147 vp,
1148 )
1149 }
1150}
1151
1152#[expect(unsafe_code)]
1153unsafe extern "C" fn set(
1154 cx: *mut RawJSContext,
1155 proxy: RawHandleObject,
1156 id: RawHandleId,
1157 v: RawHandleValue,
1158 receiver: RawHandleValue,
1159 res: *mut ObjectOpResult,
1160) -> bool {
1161 if get_array_index_from_id(unsafe { Handle::from_raw(id) }).is_some() {
1162 unsafe { (*res).code_ = JSErrNum::JSMSG_READ_ONLY as ::libc::uintptr_t };
1164 return true;
1165 }
1166
1167 let mut slot = UndefinedValue();
1168 unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1169 rooted!(in(cx) let target = slot.to_object());
1170 unsafe { JS_ForwardSetPropertyTo(cx, target.handle().into(), id, v, receiver, res) }
1171}
1172
1173#[expect(unsafe_code)]
1174unsafe extern "C" fn get_prototype_if_ordinary(
1175 _: *mut RawJSContext,
1176 _: RawHandleObject,
1177 is_ordinary: *mut bool,
1178 _: RawMutableHandleObject,
1179) -> bool {
1180 unsafe { *is_ordinary = false };
1193 true
1194}
1195
1196static PROXY_TRAPS: ProxyTraps = ProxyTraps {
1197 enter: None,
1200 getOwnPropertyDescriptor: Some(get_own_property_descriptor),
1201 defineProperty: Some(define_property),
1202 ownPropertyKeys: None,
1203 delete_: None,
1204 enumerate: None,
1205 getPrototypeIfOrdinary: Some(get_prototype_if_ordinary),
1206 getPrototype: None, setPrototype: None,
1208 setImmutablePrototype: None,
1209 preventExtensions: None,
1210 isExtensible: None,
1211 has: Some(has),
1212 get: Some(get),
1213 set: Some(set),
1214 call: None,
1215 construct: None,
1216 hasOwn: None,
1217 getOwnEnumerablePropertyKeys: None,
1218 nativeCall: None,
1219 objectClassIs: None,
1220 className: None,
1221 fun_toString: None,
1222 boxedValue_unbox: None,
1223 defaultValue: None,
1224 trace: Some(trace),
1225 finalize: Some(finalize),
1226 objectMoved: None,
1227 isCallable: None,
1228 isConstructor: None,
1229};
1230
1231pub(crate) struct WindowProxyHandler(*const libc::c_void);
1234
1235impl MallocSizeOf for WindowProxyHandler {
1236 fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize {
1237 0
1239 }
1240}
1241
1242#[expect(unsafe_code)]
1244unsafe impl Send for WindowProxyHandler {}
1245#[expect(unsafe_code)]
1247unsafe impl Sync for WindowProxyHandler {}
1248
1249#[expect(unsafe_code)]
1250impl WindowProxyHandler {
1251 fn new(traps: &ProxyTraps) -> Self {
1252 let ptr = unsafe { CreateWrapperProxyHandler(traps) };
1254 assert!(!ptr.is_null());
1255 Self(ptr)
1256 }
1257
1258 pub(crate) fn x_origin_proxy_handler() -> &'static Self {
1260 use std::sync::OnceLock;
1261 static SINGLETON: OnceLock<WindowProxyHandler> = OnceLock::new();
1267 SINGLETON.get_or_init(|| Self::new(&XORIGIN_PROXY_TRAPS))
1268 }
1269
1270 pub(crate) fn proxy_handler() -> &'static Self {
1272 use std::sync::OnceLock;
1273 static SINGLETON: OnceLock<WindowProxyHandler> = OnceLock::new();
1279 SINGLETON.get_or_init(|| Self::new(&PROXY_TRAPS))
1280 }
1281
1282 fn new_window_proxy(
1285 &self,
1286 cx: &mut JSContext,
1287 window_jsobject: js::gc::HandleObject,
1288 ) -> *mut JSObject {
1289 let obj = unsafe { NewWindowProxy(cx, window_jsobject, self.0) };
1290 assert!(!obj.is_null());
1291 obj
1292 }
1293}
1294
1295#[expect(unsafe_code)]
1296impl Drop for WindowProxyHandler {
1297 fn drop(&mut self) {
1298 unsafe {
1301 DeleteWrapperProxyHandler(self.0);
1302 }
1303 }
1304}
1305
1306#[expect(unsafe_code)]
1314fn throw_security_error(realm: &mut CurrentRealm) -> bool {
1315 if !unsafe { JS_IsExceptionPending(realm) } {
1316 let global = GlobalScope::from_current_realm(realm);
1317 throw_dom_exception(realm, &global, Error::Security(None));
1318 }
1319 false
1320}
1321
1322#[expect(unsafe_code)]
1323unsafe extern "C" fn has_xorigin(
1324 cx: *mut RawJSContext,
1325 proxy: RawHandleObject,
1326 id: RawHandleId,
1327 bp: *mut bool,
1328) -> bool {
1329 let mut cx = unsafe {
1330 JSContext::from_ptr(NonNull::new(cx).expect("JSContext should not be null in SM hook"))
1332 };
1333 let mut slot = UndefinedValue();
1334 unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1335 rooted!(&in(cx) let target = slot.to_object());
1336 let mut found = false;
1337 unsafe { JS_HasOwnPropertyById(&mut cx, target.handle(), Handle::from_raw(id), &mut found) };
1338 if found {
1339 unsafe { *bp = true };
1340 true
1341 } else {
1342 let mut realm = CurrentRealm::assert(&mut cx);
1343 throw_security_error(&mut realm)
1344 }
1345}
1346
1347#[expect(unsafe_code)]
1348unsafe extern "C" fn get_xorigin(
1349 cx: *mut RawJSContext,
1350 proxy: RawHandleObject,
1351 receiver: RawHandleValue,
1352 id: RawHandleId,
1353 vp: RawMutableHandleValue,
1354) -> bool {
1355 let mut found = false;
1356 unsafe { has_xorigin(cx, proxy, id, &mut found) };
1357 found && unsafe { get(cx, proxy, receiver, id, vp) }
1358}
1359
1360#[expect(unsafe_code)]
1361unsafe extern "C" fn set_xorigin(
1362 cx: *mut RawJSContext,
1363 _: RawHandleObject,
1364 _: RawHandleId,
1365 _: RawHandleValue,
1366 _: RawHandleValue,
1367 _: *mut ObjectOpResult,
1368) -> bool {
1369 let mut cx = unsafe {
1370 JSContext::from_ptr(NonNull::new(cx).expect("JSContext should not be null in SM hook"))
1372 };
1373 let mut realm = CurrentRealm::assert(&mut cx);
1374 throw_security_error(&mut realm)
1375}
1376
1377#[expect(unsafe_code)]
1378unsafe extern "C" fn delete_xorigin(
1379 cx: *mut RawJSContext,
1380 _: RawHandleObject,
1381 _: RawHandleId,
1382 _: *mut ObjectOpResult,
1383) -> bool {
1384 let mut cx = unsafe {
1385 JSContext::from_ptr(NonNull::new(cx).expect("JSContext should not be null in SM hook"))
1387 };
1388 let mut realm = CurrentRealm::assert(&mut cx);
1389 throw_security_error(&mut realm)
1390}
1391
1392#[expect(unsafe_code)]
1393#[expect(non_snake_case)]
1394unsafe extern "C" fn getOwnPropertyDescriptor_xorigin(
1395 cx: *mut RawJSContext,
1396 proxy: RawHandleObject,
1397 id: RawHandleId,
1398 desc: RawMutableHandle<PropertyDescriptor>,
1399 is_none: *mut bool,
1400) -> bool {
1401 let mut found = false;
1402 unsafe { has_xorigin(cx, proxy, id, &mut found) };
1403 found && unsafe { get_own_property_descriptor(cx, proxy, id, desc, is_none) }
1404}
1405
1406#[expect(unsafe_code)]
1407#[expect(non_snake_case)]
1408unsafe extern "C" fn defineProperty_xorigin(
1409 cx: *mut RawJSContext,
1410 _: RawHandleObject,
1411 _: RawHandleId,
1412 _: RawHandle<PropertyDescriptor>,
1413 _: *mut ObjectOpResult,
1414) -> bool {
1415 let mut cx = unsafe {
1416 JSContext::from_ptr(NonNull::new(cx).expect("JSContext should not be null in SM hook"))
1418 };
1419 let mut realm = CurrentRealm::assert(&mut cx);
1420 throw_security_error(&mut realm)
1421}
1422
1423#[expect(unsafe_code)]
1424#[expect(non_snake_case)]
1425unsafe extern "C" fn preventExtensions_xorigin(
1426 cx: *mut RawJSContext,
1427 _: RawHandleObject,
1428 _: *mut ObjectOpResult,
1429) -> bool {
1430 let mut cx = unsafe {
1431 JSContext::from_ptr(NonNull::new(cx).expect("JSContext should not be null in SM hook"))
1433 };
1434 let mut realm = CurrentRealm::assert(&mut cx);
1435 throw_security_error(&mut realm)
1436}
1437
1438static XORIGIN_PROXY_TRAPS: ProxyTraps = ProxyTraps {
1439 enter: None,
1440 getOwnPropertyDescriptor: Some(getOwnPropertyDescriptor_xorigin),
1441 defineProperty: Some(defineProperty_xorigin),
1442 ownPropertyKeys: None,
1443 delete_: Some(delete_xorigin),
1444 enumerate: None,
1445 getPrototypeIfOrdinary: None,
1446 getPrototype: None,
1447 setPrototype: None,
1448 setImmutablePrototype: None,
1449 preventExtensions: Some(preventExtensions_xorigin),
1450 isExtensible: None,
1451 has: Some(has_xorigin),
1452 get: Some(get_xorigin),
1453 set: Some(set_xorigin),
1454 call: None,
1455 construct: None,
1456 hasOwn: Some(has_xorigin),
1457 getOwnEnumerablePropertyKeys: None,
1458 nativeCall: None,
1459 objectClassIs: None,
1460 className: None,
1461 fun_toString: None,
1462 boxedValue_unbox: None,
1463 defaultValue: None,
1464 trace: Some(trace),
1465 finalize: Some(finalize),
1466 objectMoved: None,
1467 isCallable: None,
1468 isConstructor: None,
1469};
1470
1471#[expect(unsafe_code)]
1474unsafe extern "C" fn finalize(_fop: *mut GCContext, obj: *mut JSObject) {
1475 let mut slot = UndefinedValue();
1476 unsafe { GetProxyReservedSlot(obj, 0, &mut slot) };
1477 let this = slot.to_private() as *mut WindowProxy;
1478 if this.is_null() {
1479 return;
1481 }
1482 unsafe {
1483 (*this).reflector.drop_memory(&*this);
1484 let jsobject = (*this).reflector.get_jsobject().get();
1485 debug!(
1486 "WindowProxy finalize: {:p}, with reflector {:p} from {:p}.",
1487 this, jsobject, obj
1488 );
1489 let _ = Box::from_raw(this);
1490 }
1491}
1492
1493#[expect(unsafe_code)]
1494unsafe extern "C" fn trace(trc: *mut JSTracer, obj: *mut JSObject) {
1495 let mut slot = UndefinedValue();
1496 unsafe { GetProxyReservedSlot(obj, 0, &mut slot) };
1497 let this = slot.to_private() as *const WindowProxy;
1498 if this.is_null() {
1499 return;
1501 }
1502 unsafe { (*this).trace(trc) };
1503}