1use std::cell::Cell;
6use std::ptr;
7use std::rc::Rc;
8
9use base::generic_channel;
10use base::generic_channel::GenericSend;
11use base::id::{BrowsingContextId, PipelineId, WebViewId};
12use constellation_traits::{
13 AuxiliaryWebViewCreationRequest, LoadData, LoadOrigin, NavigationHistoryBehavior,
14 ScriptToConstellationMessage,
15};
16use content_security_policy::sandboxing_directive::SandboxingFlagSet;
17use dom_struct::dom_struct;
18use html5ever::local_name;
19use indexmap::map::IndexMap;
20use ipc_channel::ipc;
21use js::JSCLASS_IS_GLOBAL;
22use js::glue::{
23 CreateWrapperProxyHandler, DeleteWrapperProxyHandler, GetProxyPrivate, GetProxyReservedSlot,
24 ProxyTraps, SetProxyReservedSlot,
25};
26use js::jsapi::{
27 GCContext, Handle as RawHandle, HandleId as RawHandleId, HandleObject as RawHandleObject,
28 HandleValue as RawHandleValue, JS_DefinePropertyById, JS_ForwardGetPropertyTo,
29 JS_ForwardSetPropertyTo, JS_GetOwnPropertyDescriptorById, JS_HasOwnPropertyById,
30 JS_HasPropertyById, JS_IsExceptionPending, JSAutoRealm, JSContext, JSErrNum, JSObject,
31 JSPROP_ENUMERATE, JSPROP_READONLY, JSTracer, MutableHandle as RawMutableHandle,
32 MutableHandleObject as RawMutableHandleObject, MutableHandleValue as RawMutableHandleValue,
33 ObjectOpResult, PropertyDescriptor,
34};
35use js::jsval::{NullValue, PrivateValue, UndefinedValue};
36use js::rust::wrappers::{JS_TransplantObject, NewWindowProxy, SetWindowProxy};
37use js::rust::{Handle, MutableHandle, MutableHandleValue, get_object_class};
38use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
39use net_traits::request::Referrer;
40use script_traits::NewPipelineInfo;
41use serde::{Deserialize, Serialize};
42use servo_url::{ImmutableOrigin, ServoUrl};
43use storage_traits::webstorage_thread::WebStorageThreadMsg;
44use style::attr::parse_integer;
45
46use crate::dom::bindings::cell::DomRefCell;
47use crate::dom::bindings::conversions::{ToJSValConvertible, root_from_handleobject};
48use crate::dom::bindings::error::{Error, Fallible, throw_dom_exception};
49use crate::dom::bindings::inheritance::Castable;
50use crate::dom::bindings::proxyhandler::set_property_descriptor;
51use crate::dom::bindings::reflector::{DomGlobal, DomObject, Reflector};
52use crate::dom::bindings::root::{Dom, DomRoot};
53use crate::dom::bindings::str::{DOMString, USVString};
54use crate::dom::bindings::trace::JSTraceable;
55use crate::dom::bindings::utils::{AsVoidPtr, get_array_index_from_id};
56use crate::dom::dissimilaroriginwindow::DissimilarOriginWindow;
57use crate::dom::document::Document;
58use crate::dom::element::Element;
59use crate::dom::globalscope::GlobalScope;
60use crate::dom::window::Window;
61use crate::realms::{AlreadyInRealm, InRealm, enter_realm};
62use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
63use crate::script_thread::{ScriptThread, with_script_thread};
64use crate::script_window_proxies::ScriptWindowProxies;
65
66#[dom_struct]
67pub(crate) struct WindowProxy {
72 reflector: Reflector,
77
78 #[no_trace]
82 browsing_context_id: BrowsingContextId,
83
84 #[no_trace]
86 opener: Option<BrowsingContextId>,
87
88 #[no_trace]
91 webview_id: WebViewId,
92
93 name: DomRefCell<DOMString>,
96 #[no_trace]
102 currently_active: Cell<Option<PipelineId>>,
103
104 discarded: Cell<bool>,
106
107 disowned: Cell<bool>,
109
110 is_closing: Cell<bool>,
112
113 frame_element: Option<Dom<Element>>,
117
118 parent: Option<Dom<WindowProxy>>,
120
121 delaying_load_events_mode: Cell<bool>,
123
124 #[no_trace]
126 creator_base_url: Option<ServoUrl>,
127
128 #[no_trace]
130 creator_url: Option<ServoUrl>,
131
132 #[no_trace]
134 creator_origin: Option<ImmutableOrigin>,
135
136 #[conditional_malloc_size_of]
138 script_window_proxies: Rc<ScriptWindowProxies>,
139}
140
141impl WindowProxy {
142 fn new_inherited(
143 browsing_context_id: BrowsingContextId,
144 webview_id: WebViewId,
145 currently_active: Option<PipelineId>,
146 frame_element: Option<&Element>,
147 parent: Option<&WindowProxy>,
148 opener: Option<BrowsingContextId>,
149 creator: CreatorBrowsingContextInfo,
150 ) -> WindowProxy {
151 let name = frame_element.map_or(DOMString::new(), |e| {
152 e.get_string_attribute(&local_name!("name"))
153 });
154 WindowProxy {
155 reflector: Reflector::new(),
156 browsing_context_id,
157 webview_id,
158 name: DomRefCell::new(name),
159 currently_active: Cell::new(currently_active),
160 discarded: Cell::new(false),
161 disowned: Cell::new(false),
162 is_closing: Cell::new(false),
163 frame_element: frame_element.map(Dom::from_ref),
164 parent: parent.map(Dom::from_ref),
165 delaying_load_events_mode: Cell::new(false),
166 opener,
167 creator_base_url: creator.base_url,
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 pub(crate) fn new(
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 cx = GlobalScope::get_cx();
188 let window_jsobject = window.reflector().get_jsobject();
189 assert!(!window_jsobject.get().is_null());
190 assert_ne!(
191 ((*get_object_class(window_jsobject.get())).flags & JSCLASS_IS_GLOBAL),
192 0
193 );
194 let _ac = JSAutoRealm::new(*cx, window_jsobject.get());
195
196 rooted!(in(*cx) let js_proxy = handler.new_window_proxy(&cx, window_jsobject));
198 assert!(!js_proxy.is_null());
199
200 let current = Some(window.upcast::<GlobalScope>().pipeline_id());
203 let window_proxy = Box::new(WindowProxy::new_inherited(
204 browsing_context_id,
205 webview_id,
206 current,
207 frame_element,
208 parent,
209 opener,
210 creator,
211 ));
212
213 SetProxyReservedSlot(
216 js_proxy.get(),
217 0,
218 &PrivateValue((*window_proxy).as_void_ptr()),
219 );
220
221 SetWindowProxy(*cx, window_jsobject, js_proxy.handle());
223
224 debug!(
226 "Initializing reflector of {:p} to {:p}.",
227 window_proxy,
228 js_proxy.get()
229 );
230 window_proxy.reflector.set_jsobject(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 global_to_clone_from: &GlobalScope,
238 browsing_context_id: BrowsingContextId,
239 webview_id: WebViewId,
240 parent: Option<&WindowProxy>,
241 opener: Option<BrowsingContextId>,
242 creator: CreatorBrowsingContextInfo,
243 ) -> DomRoot<WindowProxy> {
244 unsafe {
245 let handler = WindowProxyHandler::x_origin_proxy_handler();
246
247 let cx = GlobalScope::get_cx();
248
249 let window_proxy = Box::new(WindowProxy::new_inherited(
251 browsing_context_id,
252 webview_id,
253 None,
254 None,
255 parent,
256 opener,
257 creator,
258 ));
259
260 let window = DissimilarOriginWindow::new(global_to_clone_from, &window_proxy);
262 let window_jsobject = window.reflector().get_jsobject();
263 assert!(!window_jsobject.get().is_null());
264 assert_ne!(
265 ((*get_object_class(window_jsobject.get())).flags & JSCLASS_IS_GLOBAL),
266 0
267 );
268 let _ac = JSAutoRealm::new(*cx, window_jsobject.get());
269
270 rooted!(in(*cx) let js_proxy = handler.new_window_proxy(&cx, window_jsobject));
272 assert!(!js_proxy.is_null());
273
274 SetProxyReservedSlot(
277 js_proxy.get(),
278 0,
279 &PrivateValue((*window_proxy).as_void_ptr()),
280 );
281
282 SetWindowProxy(*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.reflector.set_jsobject(js_proxy.get());
292 DomRoot::from_ref(&*Box::into_raw(window_proxy))
293 }
294 }
295
296 fn create_auxiliary_browsing_context(
298 &self,
299 name: DOMString,
300 noopener: bool,
301 ) -> Option<DomRoot<WindowProxy>> {
302 let (response_sender, response_receiver) = ipc::channel().unwrap();
303 let window = self
304 .currently_active
305 .get()
306 .and_then(ScriptThread::find_document)
307 .map(|doc| DomRoot::from_ref(doc.window()))
308 .unwrap();
309
310 let document = self
311 .currently_active
312 .get()
313 .and_then(ScriptThread::find_document)
314 .expect("A WindowProxy creating an auxiliary to have an active document");
315 let blank_url = ServoUrl::parse("about:blank").ok().unwrap();
316 let load_data = LoadData::new(
317 LoadOrigin::Script(document.origin().immutable().clone()),
318 blank_url,
319 Some(window.pipeline_id()),
322 document.global().get_referrer(),
323 document.get_referrer_policy(),
324 None, None,
326 false,
327 SandboxingFlagSet::empty(),
329 );
330 let load_info = AuxiliaryWebViewCreationRequest {
331 load_data: load_data.clone(),
332 opener_webview_id: window.webview_id(),
333 opener_pipeline_id: self.currently_active.get().unwrap(),
334 response_sender,
335 };
336 let constellation_msg = ScriptToConstellationMessage::CreateAuxiliaryWebView(load_info);
337 window.send_to_constellation(constellation_msg);
338
339 let response = response_receiver.recv().unwrap()?;
340 let new_browsing_context_id = BrowsingContextId::from(response.new_webview_id);
341 let new_pipeline_info = NewPipelineInfo {
342 parent_info: None,
343 new_pipeline_id: response.new_pipeline_id,
344 browsing_context_id: new_browsing_context_id,
345 webview_id: response.new_webview_id,
346 opener: Some(self.browsing_context_id),
347 load_data,
348 viewport_details: window.viewport_details(),
349 theme: window.theme(),
352 };
353
354 with_script_thread(|script_thread| {
355 script_thread.spawn_pipeline(new_pipeline_info);
356 });
357
358 let new_window_proxy = ScriptThread::find_document(response.new_pipeline_id)
359 .and_then(|doc| doc.browsing_context())?;
360 if name.to_lowercase() != "_blank" {
361 new_window_proxy.set_name(name);
362 }
363 if noopener {
364 new_window_proxy.disown();
365 } else {
366 let (sender, receiver) = generic_channel::channel().unwrap();
371
372 let msg = WebStorageThreadMsg::Clone {
373 sender,
374 src: window.window_proxy().webview_id(),
375 dest: response.new_webview_id,
376 };
377
378 GenericSend::send(document.global().storage_threads(), msg).unwrap();
379 receiver.recv().unwrap();
380 }
381 Some(new_window_proxy)
382 }
383
384 pub(crate) fn is_delaying_load_events_mode(&self) -> bool {
386 self.delaying_load_events_mode.get()
387 }
388
389 pub(crate) fn start_delaying_load_events_mode(&self) {
391 self.delaying_load_events_mode.set(true);
392 }
393
394 pub(crate) fn stop_delaying_load_events_mode(&self) {
396 self.delaying_load_events_mode.set(false);
397 if let Some(document) = self.document() {
398 if !document.loader().events_inhibited() {
399 ScriptThread::mark_document_with_no_blocked_loads(&document);
400 }
401 }
402 }
403
404 pub(crate) fn disown(&self) {
406 self.disowned.set(true);
407 }
408
409 pub(crate) fn close(&self) {
412 self.is_closing.set(true);
413 }
414
415 pub(crate) fn is_closing(&self) -> bool {
417 self.is_closing.get()
418 }
419
420 pub(crate) fn creator_base_url(&self) -> Option<ServoUrl> {
422 self.creator_base_url.clone()
423 }
424
425 pub(crate) fn has_creator_base_url(&self) -> bool {
426 self.creator_base_url.is_some()
427 }
428
429 pub(crate) fn creator_url(&self) -> Option<ServoUrl> {
431 self.creator_url.clone()
432 }
433
434 pub(crate) fn has_creator_url(&self) -> bool {
435 self.creator_base_url.is_some()
436 }
437
438 pub(crate) fn creator_origin(&self) -> Option<ImmutableOrigin> {
440 self.creator_origin.clone()
441 }
442
443 pub(crate) fn has_creator_origin(&self) -> bool {
444 self.creator_origin.is_some()
445 }
446
447 #[expect(unsafe_code)]
448 pub(crate) fn opener(
450 &self,
451 cx: *mut JSContext,
452 in_realm_proof: InRealm,
453 mut retval: MutableHandleValue,
454 ) {
455 if self.disowned.get() {
456 return retval.set(NullValue());
457 }
458 let opener_id = match self.opener {
459 Some(opener_browsing_context_id) => opener_browsing_context_id,
460 None => return retval.set(NullValue()),
461 };
462 let parent_browsing_context = self.parent.as_deref();
463 let opener_proxy = match self.script_window_proxies.find_window_proxy(opener_id) {
464 Some(window_proxy) => window_proxy,
465 None => {
466 let sender_pipeline_id = self.currently_active().unwrap();
467 match ScriptThread::get_top_level_for_browsing_context(
468 self.webview_id(),
469 sender_pipeline_id,
470 opener_id,
471 ) {
472 Some(opener_top_id) => {
473 let global_to_clone_from =
474 unsafe { GlobalScope::from_context(cx, in_realm_proof) };
475 let creator =
476 CreatorBrowsingContextInfo::from(parent_browsing_context, None);
477 WindowProxy::new_dissimilar_origin(
478 &global_to_clone_from,
479 opener_id,
480 opener_top_id,
481 None,
482 None,
483 creator,
484 )
485 },
486 None => return retval.set(NullValue()),
487 }
488 },
489 };
490 if opener_proxy.is_browsing_context_discarded() {
491 return retval.set(NullValue());
492 }
493 unsafe { opener_proxy.to_jsval(cx, retval) };
494 }
495
496 pub(crate) fn open(
498 &self,
499 url: USVString,
500 target: DOMString,
501 features: DOMString,
502 can_gc: CanGc,
503 ) -> Fallible<Option<DomRoot<WindowProxy>>> {
504 if self.discarded.get() {
510 return Ok(None);
511 }
512 let non_empty_target = if target.is_empty() {
514 DOMString::from("_blank")
515 } else {
516 target
517 };
518 let tokenized_features = tokenize_open_features(features);
520 let noreferrer = parse_open_feature_boolean(&tokenized_features, "noreferrer");
524
525 let noopener = if noreferrer {
528 true
529 } else {
530 parse_open_feature_boolean(&tokenized_features, "noopener")
531 };
532 let (chosen, new) = match self.choose_browsing_context(non_empty_target, noopener) {
542 (Some(chosen), new) => (chosen, new),
543 (None, _) => return Ok(None),
544 };
545 let target_document = match chosen.document() {
548 Some(target_document) => target_document,
549 None => return Ok(None),
550 };
551 let has_trustworthy_ancestor_origin = if new {
552 target_document.has_trustworthy_ancestor_or_current_origin()
553 } else {
554 false
555 };
556 let target_window = target_document.window();
557 if !url.is_empty() {
560 let existing_document = self
561 .currently_active
562 .get()
563 .and_then(ScriptThread::find_document)
564 .unwrap();
565 let url = match existing_document.url().join(&url) {
566 Ok(url) => url,
567 Err(_) => return Err(Error::Syntax(None)),
568 };
569 let referrer = if noreferrer {
570 Referrer::NoReferrer
571 } else {
572 target_window.as_global_scope().get_referrer()
573 };
574 let csp_list = existing_document.get_csp_list();
576 target_document.set_csp_list(csp_list);
577
578 let mut load_data = LoadData::new(
582 LoadOrigin::Script(existing_document.origin().immutable().clone()),
583 url,
584 Some(target_window.pipeline_id()),
585 referrer,
586 target_document.get_referrer_policy(),
587 Some(target_window.as_global_scope().is_secure_context()),
588 Some(target_document.insecure_requests_policy()),
589 has_trustworthy_ancestor_origin,
590 target_document.creation_sandboxing_flag_set_considering_parent_iframe(),
591 );
592
593 if load_data.url.scheme() == "javascript" {
596 let existing_global = existing_document.global();
597
598 if !ScriptThread::can_navigate_to_javascript_url(
600 &existing_global,
601 &mut load_data,
602 None,
603 can_gc,
604 ) {
605 return Ok(target_document.browsing_context());
607 }
608 }
609
610 let history_handling = if new {
611 NavigationHistoryBehavior::Replace
612 } else {
613 NavigationHistoryBehavior::Push
614 };
615 target_window.load_url(history_handling, false, load_data, can_gc);
616 }
617 if noopener {
619 return Ok(None);
620 }
621 Ok(target_document.browsing_context())
623 }
624
625 pub(crate) fn choose_browsing_context(
627 &self,
628 name: DOMString,
629 noopener: bool,
630 ) -> (Option<DomRoot<WindowProxy>>, bool) {
631 match name.to_lowercase().as_ref() {
632 "" | "_self" => {
633 (Some(DomRoot::from_ref(self)), false)
635 },
636 "_parent" => {
637 if let Some(parent) = self.parent() {
639 return (Some(DomRoot::from_ref(parent)), false);
640 }
641 (None, false)
642 },
643 "_top" => {
644 (Some(DomRoot::from_ref(self.top())), false)
646 },
647 "_blank" => (self.create_auxiliary_browsing_context(name, noopener), true),
648 _ => {
649 match ScriptThread::find_window_proxy_by_name(&name) {
654 Some(proxy) => (Some(proxy), false),
655 None => (self.create_auxiliary_browsing_context(name, noopener), true),
656 }
657 },
658 }
659 }
660
661 pub(crate) fn is_auxiliary(&self) -> bool {
662 self.opener.is_some()
663 }
664
665 pub(crate) fn discard_browsing_context(&self) {
666 self.discarded.set(true);
667 }
668
669 pub(crate) fn is_browsing_context_discarded(&self) -> bool {
670 self.discarded.get()
671 }
672
673 pub(crate) fn browsing_context_id(&self) -> BrowsingContextId {
674 self.browsing_context_id
675 }
676
677 pub(crate) fn webview_id(&self) -> WebViewId {
678 self.webview_id
679 }
680
681 pub(crate) fn frame_element(&self) -> Option<&Element> {
685 self.frame_element.as_deref()
686 }
687
688 pub(crate) fn document(&self) -> Option<DomRoot<Document>> {
689 self.currently_active
690 .get()
691 .and_then(ScriptThread::find_document)
692 }
693
694 pub(crate) fn parent(&self) -> Option<&WindowProxy> {
695 self.parent.as_deref()
696 }
697
698 pub(crate) fn top(&self) -> &WindowProxy {
699 let mut result = self;
700 while let Some(parent) = result.parent() {
701 result = parent;
702 }
703 result
704 }
705
706 pub fn focus(&self) {
710 debug!(
711 "Requesting the constellation to initiate a focus operation for \
712 browsing context {}",
713 self.browsing_context_id()
714 );
715 self.global()
716 .script_to_constellation_chan()
717 .send(ScriptToConstellationMessage::FocusRemoteDocument(
718 self.browsing_context_id(),
719 ))
720 .unwrap();
721 }
722
723 #[expect(unsafe_code)]
724 fn set_window(&self, window: &GlobalScope, handler: &WindowProxyHandler, _can_gc: CanGc) {
728 unsafe {
729 debug!("Setting window of {:p}.", self);
730
731 let cx = GlobalScope::get_cx();
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 let _ac = enter_realm(window);
740
741 SetProxyReservedSlot(old_js_proxy.get(), 0, &PrivateValue(ptr::null_mut()));
743
744 rooted!(in(*cx) let new_js_proxy = handler.new_window_proxy(&cx, window_jsobject));
750 SetProxyReservedSlot(new_js_proxy.get(), 0, &PrivateValue(ptr::null_mut()));
753 debug!(
754 "Transplanting proxy from {:p} to {:p}.",
755 old_js_proxy.get(),
756 new_js_proxy.get()
757 );
758 rooted!(in(*cx) let new_js_proxy = JS_TransplantObject(*cx, old_js_proxy, new_js_proxy.handle()));
759 debug!("Transplanted proxy is {:p}.", new_js_proxy.get());
760
761 SetProxyReservedSlot(new_js_proxy.get(), 0, &PrivateValue(self.as_void_ptr()));
763
764 SetWindowProxy(*cx, window_jsobject, new_js_proxy.handle());
766
767 debug!(
769 "Setting reflector of {:p} to {:p}.",
770 self,
771 new_js_proxy.get()
772 );
773 self.reflector.rootable().set(new_js_proxy.get());
774 }
775 }
776
777 pub(crate) fn set_currently_active(&self, window: &Window, can_gc: CanGc) {
778 if let Some(pipeline_id) = self.currently_active() {
779 if pipeline_id == window.pipeline_id() {
780 return debug!(
781 "Attempt to set the currently active window to the currently active window."
782 );
783 }
784 }
785
786 let global_scope = window.as_global_scope();
787 self.set_window(global_scope, WindowProxyHandler::proxy_handler(), can_gc);
788 self.currently_active.set(Some(global_scope.pipeline_id()));
789 }
790
791 pub(crate) fn unset_currently_active(&self, can_gc: CanGc) {
792 if self.currently_active().is_none() {
793 return debug!(
794 "Attempt to unset the currently active window on a windowproxy that does not have one."
795 );
796 }
797 let globalscope = self.global();
798 let window = DissimilarOriginWindow::new(&globalscope, self);
799 self.set_window(
800 window.upcast(),
801 WindowProxyHandler::x_origin_proxy_handler(),
802 can_gc,
803 );
804 self.currently_active.set(None);
805 }
806
807 pub(crate) fn currently_active(&self) -> Option<PipelineId> {
808 self.currently_active.get()
809 }
810
811 pub(crate) fn get_name(&self) -> DOMString {
812 self.name.borrow().clone()
813 }
814
815 pub(crate) fn set_name(&self, name: DOMString) {
816 *self.name.borrow_mut() = name;
817 }
818}
819
820#[derive(Debug, Deserialize, Serialize)]
832pub(crate) struct CreatorBrowsingContextInfo {
833 url: Option<ServoUrl>,
835
836 base_url: Option<ServoUrl>,
838
839 origin: Option<ImmutableOrigin>,
841}
842
843impl CreatorBrowsingContextInfo {
844 pub(crate) fn from(
845 parent: Option<&WindowProxy>,
846 opener: Option<&WindowProxy>,
847 ) -> CreatorBrowsingContextInfo {
848 let creator = match (parent, opener) {
849 (Some(parent), _) => parent.document(),
850 (None, Some(opener)) => opener.document(),
851 (None, None) => None,
852 };
853
854 let base_url = creator.as_deref().map(|document| document.base_url());
855 let url = creator.as_deref().map(|document| document.url());
856 let origin = creator
857 .as_deref()
858 .map(|document| document.origin().immutable().clone());
859
860 CreatorBrowsingContextInfo {
861 base_url,
862 url,
863 origin,
864 }
865 }
866}
867
868fn tokenize_open_features(features: DOMString) -> IndexMap<String, String> {
870 let is_feature_sep = |c: char| c.is_ascii_whitespace() || ['=', ','].contains(&c);
871 let mut tokenized_features = IndexMap::new();
873 let features = features.str();
875 let mut iter = features.chars();
876 let mut cur = iter.next();
877
878 while cur.is_some() {
880 let mut name = String::new();
882 let mut value = String::new();
883 while let Some(cur_char) = cur {
885 if !is_feature_sep(cur_char) {
886 break;
887 }
888 cur = iter.next();
889 }
890 while let Some(cur_char) = cur {
892 if is_feature_sep(cur_char) {
893 break;
894 }
895 name.push(cur_char.to_ascii_lowercase());
896 cur = iter.next();
897 }
898 let normalized_name = String::from(match name.as_ref() {
900 "screenx" => "left",
901 "screeny" => "top",
902 "innerwidth" => "width",
903 "innerheight" => "height",
904 _ => name.as_ref(),
905 });
906 while let Some(cur_char) = cur {
908 if cur_char == '=' || cur_char == ',' || !is_feature_sep(cur_char) {
909 break;
910 }
911 cur = iter.next();
912 }
913 if cur.is_some() && is_feature_sep(cur.unwrap()) {
915 while let Some(cur_char) = cur {
917 if !is_feature_sep(cur_char) || cur_char == ',' {
918 break;
919 }
920 cur = iter.next();
921 }
922 while let Some(cur_char) = cur {
924 if is_feature_sep(cur_char) {
925 break;
926 }
927 value.push(cur_char.to_ascii_lowercase());
928 cur = iter.next();
929 }
930 }
931 if !name.is_empty() {
933 tokenized_features.insert(normalized_name, value);
934 }
935 }
936 tokenized_features
938}
939
940fn parse_open_feature_boolean(tokenized_features: &IndexMap<String, String>, name: &str) -> bool {
942 if let Some(value) = tokenized_features.get(name) {
943 if value.is_empty() || value == "yes" {
945 return true;
946 }
947 if let Ok(int) = parse_integer(value.chars()) {
949 return int != 0;
950 }
951 }
952 false
954}
955
956#[expect(unsafe_code)]
960#[allow(non_snake_case)]
961unsafe fn GetSubframeWindowProxy(
962 cx: *mut JSContext,
963 proxy: RawHandleObject,
964 id: RawHandleId,
965) -> Option<(DomRoot<WindowProxy>, u32)> {
966 let index = get_array_index_from_id(unsafe { Handle::from_raw(id) });
967 if let Some(index) = index {
968 let mut slot = UndefinedValue();
969 unsafe { GetProxyPrivate(*proxy, &mut slot) };
970 rooted!(in(cx) let target = slot.to_object());
971 let script_window_proxies = ScriptThread::window_proxies();
972 if let Ok(win) = root_from_handleobject::<Window>(target.handle(), cx) {
973 let browsing_context_id = win.window_proxy().browsing_context_id();
974 let (result_sender, result_receiver) = ipc::channel().unwrap();
975
976 let _ = win.as_global_scope().script_to_constellation_chan().send(
977 ScriptToConstellationMessage::GetChildBrowsingContextId(
978 browsing_context_id,
979 index as usize,
980 result_sender,
981 ),
982 );
983 return result_receiver
984 .recv()
985 .ok()
986 .and_then(|maybe_bcid| maybe_bcid)
987 .and_then(|id| script_window_proxies.find_window_proxy(id))
988 .map(|proxy| (proxy, (JSPROP_ENUMERATE | JSPROP_READONLY) as u32));
989 } else if let Ok(win) =
990 root_from_handleobject::<DissimilarOriginWindow>(target.handle(), cx)
991 {
992 let browsing_context_id = win.window_proxy().browsing_context_id();
993 let (result_sender, result_receiver) = ipc::channel().unwrap();
994
995 let _ = win.global().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_READONLY as u32));
1008 }
1009 }
1010
1011 None
1012}
1013
1014#[expect(unsafe_code)]
1015#[allow(non_snake_case)]
1016unsafe extern "C" fn get_own_property_descriptor(
1017 cx: *mut JSContext,
1018 proxy: RawHandleObject,
1019 id: RawHandleId,
1020 desc: RawMutableHandle<PropertyDescriptor>,
1021 is_none: *mut bool,
1022) -> bool {
1023 let window = unsafe { GetSubframeWindowProxy(cx, proxy, id) };
1024 if let Some((window, attrs)) = window {
1025 rooted!(in(cx) let mut val = UndefinedValue());
1026 unsafe { window.to_jsval(cx, val.handle_mut()) };
1027 set_property_descriptor(
1028 unsafe { MutableHandle::from_raw(desc) },
1029 val.handle(),
1030 attrs,
1031 unsafe { &mut *is_none },
1032 );
1033 return true;
1034 }
1035
1036 let mut slot = UndefinedValue();
1037 unsafe { GetProxyPrivate(proxy.get(), &mut slot) };
1038 rooted!(in(cx) let target = slot.to_object());
1039 unsafe { JS_GetOwnPropertyDescriptorById(cx, target.handle().into(), id, desc, is_none) }
1040}
1041
1042#[expect(unsafe_code)]
1043#[allow(non_snake_case)]
1044unsafe extern "C" fn define_property(
1045 cx: *mut JSContext,
1046 proxy: RawHandleObject,
1047 id: RawHandleId,
1048 desc: RawHandle<PropertyDescriptor>,
1049 res: *mut ObjectOpResult,
1050) -> bool {
1051 if get_array_index_from_id(unsafe { Handle::from_raw(id) }).is_some() {
1052 unsafe {
1057 (*res).code_ = JSErrNum::JSMSG_CANT_DEFINE_WINDOW_ELEMENT as ::libc::uintptr_t;
1058 }
1059 return true;
1060 }
1061
1062 let mut slot = UndefinedValue();
1063 unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1064 rooted!(in(cx) let target = slot.to_object());
1065 unsafe { JS_DefinePropertyById(cx, target.handle().into(), id, desc, res) }
1066}
1067
1068#[expect(unsafe_code)]
1069unsafe extern "C" fn has(
1070 cx: *mut JSContext,
1071 proxy: RawHandleObject,
1072 id: RawHandleId,
1073 bp: *mut bool,
1074) -> bool {
1075 let window = unsafe { GetSubframeWindowProxy(cx, proxy, id) };
1076 if window.is_some() {
1077 unsafe { *bp = true };
1078 return true;
1079 }
1080
1081 let mut slot = UndefinedValue();
1082 unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1083 rooted!(in(cx) let target = slot.to_object());
1084 let mut found = false;
1085 if !unsafe { JS_HasPropertyById(cx, target.handle().into(), id, &mut found) } {
1086 return false;
1087 }
1088
1089 unsafe { *bp = found };
1090 true
1091}
1092
1093#[expect(unsafe_code)]
1094unsafe extern "C" fn get(
1095 cx: *mut JSContext,
1096 proxy: RawHandleObject,
1097 receiver: RawHandleValue,
1098 id: RawHandleId,
1099 vp: RawMutableHandleValue,
1100) -> bool {
1101 let window = unsafe { GetSubframeWindowProxy(cx, proxy, id) };
1102 if let Some((window, _attrs)) = window {
1103 unsafe { window.to_jsval(cx, MutableHandle::from_raw(vp)) };
1104 return true;
1105 }
1106
1107 let mut slot = UndefinedValue();
1108 unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1109 rooted!(in(cx) let target = slot.to_object());
1110 unsafe { JS_ForwardGetPropertyTo(cx, target.handle().into(), id, receiver, vp) }
1111}
1112
1113#[expect(unsafe_code)]
1114unsafe extern "C" fn set(
1115 cx: *mut JSContext,
1116 proxy: RawHandleObject,
1117 id: RawHandleId,
1118 v: RawHandleValue,
1119 receiver: RawHandleValue,
1120 res: *mut ObjectOpResult,
1121) -> bool {
1122 if get_array_index_from_id(unsafe { Handle::from_raw(id) }).is_some() {
1123 unsafe { (*res).code_ = JSErrNum::JSMSG_READ_ONLY as ::libc::uintptr_t };
1125 return true;
1126 }
1127
1128 let mut slot = UndefinedValue();
1129 unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1130 rooted!(in(cx) let target = slot.to_object());
1131 unsafe { JS_ForwardSetPropertyTo(cx, target.handle().into(), id, v, receiver, res) }
1132}
1133
1134#[expect(unsafe_code)]
1135unsafe extern "C" fn get_prototype_if_ordinary(
1136 _: *mut JSContext,
1137 _: RawHandleObject,
1138 is_ordinary: *mut bool,
1139 _: RawMutableHandleObject,
1140) -> bool {
1141 unsafe { *is_ordinary = false };
1154 true
1155}
1156
1157static PROXY_TRAPS: ProxyTraps = ProxyTraps {
1158 enter: None,
1161 getOwnPropertyDescriptor: Some(get_own_property_descriptor),
1162 defineProperty: Some(define_property),
1163 ownPropertyKeys: None,
1164 delete_: None,
1165 enumerate: None,
1166 getPrototypeIfOrdinary: Some(get_prototype_if_ordinary),
1167 getPrototype: None, setPrototype: None,
1169 setImmutablePrototype: None,
1170 preventExtensions: None,
1171 isExtensible: None,
1172 has: Some(has),
1173 get: Some(get),
1174 set: Some(set),
1175 call: None,
1176 construct: None,
1177 hasOwn: None,
1178 getOwnEnumerablePropertyKeys: None,
1179 nativeCall: None,
1180 objectClassIs: None,
1181 className: None,
1182 fun_toString: None,
1183 boxedValue_unbox: None,
1184 defaultValue: None,
1185 trace: Some(trace),
1186 finalize: Some(finalize),
1187 objectMoved: None,
1188 isCallable: None,
1189 isConstructor: None,
1190};
1191
1192pub(crate) struct WindowProxyHandler(*const libc::c_void);
1195
1196impl MallocSizeOf for WindowProxyHandler {
1197 fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize {
1198 0
1200 }
1201}
1202
1203#[expect(unsafe_code)]
1205unsafe impl Send for WindowProxyHandler {}
1206#[expect(unsafe_code)]
1208unsafe impl Sync for WindowProxyHandler {}
1209
1210#[expect(unsafe_code)]
1211impl WindowProxyHandler {
1212 fn new(traps: &ProxyTraps) -> Self {
1213 let ptr = unsafe { CreateWrapperProxyHandler(traps) };
1215 assert!(!ptr.is_null());
1216 Self(ptr)
1217 }
1218
1219 pub(crate) fn x_origin_proxy_handler() -> &'static Self {
1221 use std::sync::OnceLock;
1222 static SINGLETON: OnceLock<WindowProxyHandler> = OnceLock::new();
1228 SINGLETON.get_or_init(|| Self::new(&XORIGIN_PROXY_TRAPS))
1229 }
1230
1231 pub(crate) fn proxy_handler() -> &'static Self {
1233 use std::sync::OnceLock;
1234 static SINGLETON: OnceLock<WindowProxyHandler> = OnceLock::new();
1240 SINGLETON.get_or_init(|| Self::new(&PROXY_TRAPS))
1241 }
1242
1243 pub(crate) fn new_window_proxy(
1246 &self,
1247 cx: &crate::script_runtime::JSContext,
1248 window_jsobject: js::gc::HandleObject,
1249 ) -> *mut JSObject {
1250 let obj = unsafe { NewWindowProxy(**cx, window_jsobject, self.0) };
1251 assert!(!obj.is_null());
1252 obj
1253 }
1254}
1255
1256#[expect(unsafe_code)]
1257impl Drop for WindowProxyHandler {
1258 fn drop(&mut self) {
1259 unsafe {
1262 DeleteWrapperProxyHandler(self.0);
1263 }
1264 }
1265}
1266
1267#[expect(unsafe_code)]
1275fn throw_security_error(cx: SafeJSContext, realm: InRealm) -> bool {
1276 if !unsafe { JS_IsExceptionPending(*cx) } {
1277 let global = unsafe { GlobalScope::from_context(*cx, realm) };
1278 throw_dom_exception(cx, &global, Error::Security(None), CanGc::note());
1279 }
1280 false
1281}
1282
1283#[expect(unsafe_code)]
1284unsafe extern "C" fn has_xorigin(
1285 cx: *mut JSContext,
1286 proxy: RawHandleObject,
1287 id: RawHandleId,
1288 bp: *mut bool,
1289) -> bool {
1290 let mut slot = UndefinedValue();
1291 unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1292 rooted!(in(cx) let target = slot.to_object());
1293 let mut found = false;
1294 unsafe { JS_HasOwnPropertyById(cx, target.handle().into(), id, &mut found) };
1295 if found {
1296 unsafe { *bp = true };
1297 true
1298 } else {
1299 let cx = unsafe { SafeJSContext::from_ptr(cx) };
1300 let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
1301 throw_security_error(cx, InRealm::Already(&in_realm_proof))
1302 }
1303}
1304
1305#[expect(unsafe_code)]
1306unsafe extern "C" fn get_xorigin(
1307 cx: *mut JSContext,
1308 proxy: RawHandleObject,
1309 receiver: RawHandleValue,
1310 id: RawHandleId,
1311 vp: RawMutableHandleValue,
1312) -> bool {
1313 let mut found = false;
1314 unsafe { has_xorigin(cx, proxy, id, &mut found) };
1315 found && unsafe { get(cx, proxy, receiver, id, vp) }
1316}
1317
1318#[expect(unsafe_code)]
1319unsafe extern "C" fn set_xorigin(
1320 cx: *mut JSContext,
1321 _: RawHandleObject,
1322 _: RawHandleId,
1323 _: RawHandleValue,
1324 _: RawHandleValue,
1325 _: *mut ObjectOpResult,
1326) -> bool {
1327 let cx = unsafe { SafeJSContext::from_ptr(cx) };
1328 let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
1329 throw_security_error(cx, InRealm::Already(&in_realm_proof))
1330}
1331
1332#[expect(unsafe_code)]
1333unsafe extern "C" fn delete_xorigin(
1334 cx: *mut JSContext,
1335 _: RawHandleObject,
1336 _: RawHandleId,
1337 _: *mut ObjectOpResult,
1338) -> bool {
1339 let cx = unsafe { SafeJSContext::from_ptr(cx) };
1340 let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
1341 throw_security_error(cx, InRealm::Already(&in_realm_proof))
1342}
1343
1344#[expect(unsafe_code)]
1345#[allow(non_snake_case)]
1346unsafe extern "C" fn getOwnPropertyDescriptor_xorigin(
1347 cx: *mut JSContext,
1348 proxy: RawHandleObject,
1349 id: RawHandleId,
1350 desc: RawMutableHandle<PropertyDescriptor>,
1351 is_none: *mut bool,
1352) -> bool {
1353 let mut found = false;
1354 unsafe { has_xorigin(cx, proxy, id, &mut found) };
1355 found && unsafe { get_own_property_descriptor(cx, proxy, id, desc, is_none) }
1356}
1357
1358#[expect(unsafe_code)]
1359#[allow(non_snake_case)]
1360unsafe extern "C" fn defineProperty_xorigin(
1361 cx: *mut JSContext,
1362 _: RawHandleObject,
1363 _: RawHandleId,
1364 _: RawHandle<PropertyDescriptor>,
1365 _: *mut ObjectOpResult,
1366) -> bool {
1367 let cx = unsafe { SafeJSContext::from_ptr(cx) };
1368 let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
1369 throw_security_error(cx, InRealm::Already(&in_realm_proof))
1370}
1371
1372#[expect(unsafe_code)]
1373#[allow(non_snake_case)]
1374unsafe extern "C" fn preventExtensions_xorigin(
1375 cx: *mut JSContext,
1376 _: RawHandleObject,
1377 _: *mut ObjectOpResult,
1378) -> bool {
1379 let cx = unsafe { SafeJSContext::from_ptr(cx) };
1380 let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
1381 throw_security_error(cx, InRealm::Already(&in_realm_proof))
1382}
1383
1384static XORIGIN_PROXY_TRAPS: ProxyTraps = ProxyTraps {
1385 enter: None,
1386 getOwnPropertyDescriptor: Some(getOwnPropertyDescriptor_xorigin),
1387 defineProperty: Some(defineProperty_xorigin),
1388 ownPropertyKeys: None,
1389 delete_: Some(delete_xorigin),
1390 enumerate: None,
1391 getPrototypeIfOrdinary: None,
1392 getPrototype: None,
1393 setPrototype: None,
1394 setImmutablePrototype: None,
1395 preventExtensions: Some(preventExtensions_xorigin),
1396 isExtensible: None,
1397 has: Some(has_xorigin),
1398 get: Some(get_xorigin),
1399 set: Some(set_xorigin),
1400 call: None,
1401 construct: None,
1402 hasOwn: Some(has_xorigin),
1403 getOwnEnumerablePropertyKeys: None,
1404 nativeCall: None,
1405 objectClassIs: None,
1406 className: None,
1407 fun_toString: None,
1408 boxedValue_unbox: None,
1409 defaultValue: None,
1410 trace: Some(trace),
1411 finalize: Some(finalize),
1412 objectMoved: None,
1413 isCallable: None,
1414 isConstructor: None,
1415};
1416
1417#[expect(unsafe_code)]
1420unsafe extern "C" fn finalize(_fop: *mut GCContext, obj: *mut JSObject) {
1421 let mut slot = UndefinedValue();
1422 unsafe { GetProxyReservedSlot(obj, 0, &mut slot) };
1423 let this = slot.to_private() as *mut WindowProxy;
1424 if this.is_null() {
1425 return;
1427 }
1428 let jsobject = unsafe { (*this).reflector.get_jsobject().get() };
1429 debug!(
1430 "WindowProxy finalize: {:p}, with reflector {:p} from {:p}.",
1431 this, jsobject, obj
1432 );
1433 let _ = unsafe { Box::from_raw(this) };
1434}
1435
1436#[expect(unsafe_code)]
1437unsafe extern "C" fn trace(trc: *mut JSTracer, obj: *mut JSObject) {
1438 let mut slot = UndefinedValue();
1439 unsafe { GetProxyReservedSlot(obj, 0, &mut slot) };
1440 let this = slot.to_private() as *const WindowProxy;
1441 if this.is_null() {
1442 return;
1444 }
1445 unsafe { (*this).trace(trc) };
1446}