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 referrer_policy = target_document.get_referrer_policy();
578 let pipeline_id = target_window.pipeline_id();
579 let secure = target_window.as_global_scope().is_secure_context();
580 let load_data = LoadData::new(
581 LoadOrigin::Script(existing_document.origin().immutable().clone()),
582 url,
583 Some(pipeline_id),
584 referrer,
585 referrer_policy,
586 Some(secure),
587 Some(target_document.insecure_requests_policy()),
588 has_trustworthy_ancestor_origin,
589 target_document.creation_sandboxing_flag_set_considering_parent_iframe(),
590 );
591 let history_handling = if new {
592 NavigationHistoryBehavior::Replace
593 } else {
594 NavigationHistoryBehavior::Push
595 };
596 target_window.load_url(history_handling, false, load_data, can_gc);
597 }
598 if noopener {
600 return Ok(None);
601 }
602 Ok(target_document.browsing_context())
604 }
605
606 pub(crate) fn choose_browsing_context(
608 &self,
609 name: DOMString,
610 noopener: bool,
611 ) -> (Option<DomRoot<WindowProxy>>, bool) {
612 match name.to_lowercase().as_ref() {
613 "" | "_self" => {
614 (Some(DomRoot::from_ref(self)), false)
616 },
617 "_parent" => {
618 if let Some(parent) = self.parent() {
620 return (Some(DomRoot::from_ref(parent)), false);
621 }
622 (None, false)
623 },
624 "_top" => {
625 (Some(DomRoot::from_ref(self.top())), false)
627 },
628 "_blank" => (self.create_auxiliary_browsing_context(name, noopener), true),
629 _ => {
630 match ScriptThread::find_window_proxy_by_name(&name) {
635 Some(proxy) => (Some(proxy), false),
636 None => (self.create_auxiliary_browsing_context(name, noopener), true),
637 }
638 },
639 }
640 }
641
642 pub(crate) fn is_auxiliary(&self) -> bool {
643 self.opener.is_some()
644 }
645
646 pub(crate) fn discard_browsing_context(&self) {
647 self.discarded.set(true);
648 }
649
650 pub(crate) fn is_browsing_context_discarded(&self) -> bool {
651 self.discarded.get()
652 }
653
654 pub(crate) fn browsing_context_id(&self) -> BrowsingContextId {
655 self.browsing_context_id
656 }
657
658 pub(crate) fn webview_id(&self) -> WebViewId {
659 self.webview_id
660 }
661
662 pub(crate) fn frame_element(&self) -> Option<&Element> {
666 self.frame_element.as_deref()
667 }
668
669 pub(crate) fn document(&self) -> Option<DomRoot<Document>> {
670 self.currently_active
671 .get()
672 .and_then(ScriptThread::find_document)
673 }
674
675 pub(crate) fn parent(&self) -> Option<&WindowProxy> {
676 self.parent.as_deref()
677 }
678
679 pub(crate) fn top(&self) -> &WindowProxy {
680 let mut result = self;
681 while let Some(parent) = result.parent() {
682 result = parent;
683 }
684 result
685 }
686
687 pub fn focus(&self) {
691 debug!(
692 "Requesting the constellation to initiate a focus operation for \
693 browsing context {}",
694 self.browsing_context_id()
695 );
696 self.global()
697 .script_to_constellation_chan()
698 .send(ScriptToConstellationMessage::FocusRemoteDocument(
699 self.browsing_context_id(),
700 ))
701 .unwrap();
702 }
703
704 #[expect(unsafe_code)]
705 fn set_window(&self, window: &GlobalScope, handler: &WindowProxyHandler, _can_gc: CanGc) {
709 unsafe {
710 debug!("Setting window of {:p}.", self);
711
712 let cx = GlobalScope::get_cx();
713 let window_jsobject = window.reflector().get_jsobject();
714 let old_js_proxy = self.reflector.get_jsobject();
715 assert!(!window_jsobject.get().is_null());
716 assert_ne!(
717 ((*get_object_class(window_jsobject.get())).flags & JSCLASS_IS_GLOBAL),
718 0
719 );
720 let _ac = enter_realm(window);
721
722 SetProxyReservedSlot(old_js_proxy.get(), 0, &PrivateValue(ptr::null_mut()));
724
725 rooted!(in(*cx) let new_js_proxy = handler.new_window_proxy(&cx, window_jsobject));
731 SetProxyReservedSlot(new_js_proxy.get(), 0, &PrivateValue(ptr::null_mut()));
734 debug!(
735 "Transplanting proxy from {:p} to {:p}.",
736 old_js_proxy.get(),
737 new_js_proxy.get()
738 );
739 rooted!(in(*cx) let new_js_proxy = JS_TransplantObject(*cx, old_js_proxy, new_js_proxy.handle()));
740 debug!("Transplanted proxy is {:p}.", new_js_proxy.get());
741
742 SetProxyReservedSlot(new_js_proxy.get(), 0, &PrivateValue(self.as_void_ptr()));
744
745 SetWindowProxy(*cx, window_jsobject, new_js_proxy.handle());
747
748 debug!(
750 "Setting reflector of {:p} to {:p}.",
751 self,
752 new_js_proxy.get()
753 );
754 self.reflector.rootable().set(new_js_proxy.get());
755 }
756 }
757
758 pub(crate) fn set_currently_active(&self, window: &Window, can_gc: CanGc) {
759 if let Some(pipeline_id) = self.currently_active() {
760 if pipeline_id == window.pipeline_id() {
761 return debug!(
762 "Attempt to set the currently active window to the currently active window."
763 );
764 }
765 }
766
767 let global_scope = window.as_global_scope();
768 self.set_window(global_scope, WindowProxyHandler::proxy_handler(), can_gc);
769 self.currently_active.set(Some(global_scope.pipeline_id()));
770 }
771
772 pub(crate) fn unset_currently_active(&self, can_gc: CanGc) {
773 if self.currently_active().is_none() {
774 return debug!(
775 "Attempt to unset the currently active window on a windowproxy that does not have one."
776 );
777 }
778 let globalscope = self.global();
779 let window = DissimilarOriginWindow::new(&globalscope, self);
780 self.set_window(
781 window.upcast(),
782 WindowProxyHandler::x_origin_proxy_handler(),
783 can_gc,
784 );
785 self.currently_active.set(None);
786 }
787
788 pub(crate) fn currently_active(&self) -> Option<PipelineId> {
789 self.currently_active.get()
790 }
791
792 pub(crate) fn get_name(&self) -> DOMString {
793 self.name.borrow().clone()
794 }
795
796 pub(crate) fn set_name(&self, name: DOMString) {
797 *self.name.borrow_mut() = name;
798 }
799}
800
801#[derive(Debug, Deserialize, Serialize)]
813pub(crate) struct CreatorBrowsingContextInfo {
814 url: Option<ServoUrl>,
816
817 base_url: Option<ServoUrl>,
819
820 origin: Option<ImmutableOrigin>,
822}
823
824impl CreatorBrowsingContextInfo {
825 pub(crate) fn from(
826 parent: Option<&WindowProxy>,
827 opener: Option<&WindowProxy>,
828 ) -> CreatorBrowsingContextInfo {
829 let creator = match (parent, opener) {
830 (Some(parent), _) => parent.document(),
831 (None, Some(opener)) => opener.document(),
832 (None, None) => None,
833 };
834
835 let base_url = creator.as_deref().map(|document| document.base_url());
836 let url = creator.as_deref().map(|document| document.url());
837 let origin = creator
838 .as_deref()
839 .map(|document| document.origin().immutable().clone());
840
841 CreatorBrowsingContextInfo {
842 base_url,
843 url,
844 origin,
845 }
846 }
847}
848
849fn tokenize_open_features(features: DOMString) -> IndexMap<String, String> {
851 let is_feature_sep = |c: char| c.is_ascii_whitespace() || ['=', ','].contains(&c);
852 let mut tokenized_features = IndexMap::new();
854 let features = features.str();
856 let mut iter = features.chars();
857 let mut cur = iter.next();
858
859 while cur.is_some() {
861 let mut name = String::new();
863 let mut value = String::new();
864 while let Some(cur_char) = cur {
866 if !is_feature_sep(cur_char) {
867 break;
868 }
869 cur = iter.next();
870 }
871 while let Some(cur_char) = cur {
873 if is_feature_sep(cur_char) {
874 break;
875 }
876 name.push(cur_char.to_ascii_lowercase());
877 cur = iter.next();
878 }
879 let normalized_name = String::from(match name.as_ref() {
881 "screenx" => "left",
882 "screeny" => "top",
883 "innerwidth" => "width",
884 "innerheight" => "height",
885 _ => name.as_ref(),
886 });
887 while let Some(cur_char) = cur {
889 if cur_char == '=' || cur_char == ',' || !is_feature_sep(cur_char) {
890 break;
891 }
892 cur = iter.next();
893 }
894 if cur.is_some() && is_feature_sep(cur.unwrap()) {
896 while let Some(cur_char) = cur {
898 if !is_feature_sep(cur_char) || cur_char == ',' {
899 break;
900 }
901 cur = iter.next();
902 }
903 while let Some(cur_char) = cur {
905 if is_feature_sep(cur_char) {
906 break;
907 }
908 value.push(cur_char.to_ascii_lowercase());
909 cur = iter.next();
910 }
911 }
912 if !name.is_empty() {
914 tokenized_features.insert(normalized_name, value);
915 }
916 }
917 tokenized_features
919}
920
921fn parse_open_feature_boolean(tokenized_features: &IndexMap<String, String>, name: &str) -> bool {
923 if let Some(value) = tokenized_features.get(name) {
924 if value.is_empty() || value == "yes" {
926 return true;
927 }
928 if let Ok(int) = parse_integer(value.chars()) {
930 return int != 0;
931 }
932 }
933 false
935}
936
937#[allow(unsafe_code, non_snake_case)]
941unsafe fn GetSubframeWindowProxy(
942 cx: *mut JSContext,
943 proxy: RawHandleObject,
944 id: RawHandleId,
945) -> Option<(DomRoot<WindowProxy>, u32)> {
946 let index = get_array_index_from_id(unsafe { Handle::from_raw(id) });
947 if let Some(index) = index {
948 let mut slot = UndefinedValue();
949 unsafe { GetProxyPrivate(*proxy, &mut slot) };
950 rooted!(in(cx) let target = slot.to_object());
951 let script_window_proxies = ScriptThread::window_proxies();
952 if let Ok(win) = root_from_handleobject::<Window>(target.handle(), cx) {
953 let browsing_context_id = win.window_proxy().browsing_context_id();
954 let (result_sender, result_receiver) = ipc::channel().unwrap();
955
956 let _ = win.as_global_scope().script_to_constellation_chan().send(
957 ScriptToConstellationMessage::GetChildBrowsingContextId(
958 browsing_context_id,
959 index as usize,
960 result_sender,
961 ),
962 );
963 return result_receiver
964 .recv()
965 .ok()
966 .and_then(|maybe_bcid| maybe_bcid)
967 .and_then(|id| script_window_proxies.find_window_proxy(id))
968 .map(|proxy| (proxy, (JSPROP_ENUMERATE | JSPROP_READONLY) as u32));
969 } else if let Ok(win) =
970 root_from_handleobject::<DissimilarOriginWindow>(target.handle(), cx)
971 {
972 let browsing_context_id = win.window_proxy().browsing_context_id();
973 let (result_sender, result_receiver) = ipc::channel().unwrap();
974
975 let _ = win.global().script_to_constellation_chan().send(
976 ScriptToConstellationMessage::GetChildBrowsingContextId(
977 browsing_context_id,
978 index as usize,
979 result_sender,
980 ),
981 );
982 return result_receiver
983 .recv()
984 .ok()
985 .and_then(|maybe_bcid| maybe_bcid)
986 .and_then(|id| script_window_proxies.find_window_proxy(id))
987 .map(|proxy| (proxy, JSPROP_READONLY as u32));
988 }
989 }
990
991 None
992}
993
994#[allow(unsafe_code, non_snake_case)]
995unsafe extern "C" fn get_own_property_descriptor(
996 cx: *mut JSContext,
997 proxy: RawHandleObject,
998 id: RawHandleId,
999 desc: RawMutableHandle<PropertyDescriptor>,
1000 is_none: *mut bool,
1001) -> bool {
1002 let window = unsafe { GetSubframeWindowProxy(cx, proxy, id) };
1003 if let Some((window, attrs)) = window {
1004 rooted!(in(cx) let mut val = UndefinedValue());
1005 unsafe { window.to_jsval(cx, val.handle_mut()) };
1006 set_property_descriptor(
1007 unsafe { MutableHandle::from_raw(desc) },
1008 val.handle(),
1009 attrs,
1010 unsafe { &mut *is_none },
1011 );
1012 return true;
1013 }
1014
1015 let mut slot = UndefinedValue();
1016 unsafe { GetProxyPrivate(proxy.get(), &mut slot) };
1017 rooted!(in(cx) let target = slot.to_object());
1018 unsafe { JS_GetOwnPropertyDescriptorById(cx, target.handle().into(), id, desc, is_none) }
1019}
1020
1021#[allow(unsafe_code, non_snake_case)]
1022unsafe extern "C" fn define_property(
1023 cx: *mut JSContext,
1024 proxy: RawHandleObject,
1025 id: RawHandleId,
1026 desc: RawHandle<PropertyDescriptor>,
1027 res: *mut ObjectOpResult,
1028) -> bool {
1029 if get_array_index_from_id(unsafe { Handle::from_raw(id) }).is_some() {
1030 unsafe {
1035 (*res).code_ = JSErrNum::JSMSG_CANT_DEFINE_WINDOW_ELEMENT as ::libc::uintptr_t;
1036 }
1037 return true;
1038 }
1039
1040 let mut slot = UndefinedValue();
1041 unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1042 rooted!(in(cx) let target = slot.to_object());
1043 unsafe { JS_DefinePropertyById(cx, target.handle().into(), id, desc, res) }
1044}
1045
1046#[expect(unsafe_code)]
1047unsafe extern "C" fn has(
1048 cx: *mut JSContext,
1049 proxy: RawHandleObject,
1050 id: RawHandleId,
1051 bp: *mut bool,
1052) -> bool {
1053 let window = unsafe { GetSubframeWindowProxy(cx, proxy, id) };
1054 if window.is_some() {
1055 unsafe { *bp = true };
1056 return true;
1057 }
1058
1059 let mut slot = UndefinedValue();
1060 unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1061 rooted!(in(cx) let target = slot.to_object());
1062 let mut found = false;
1063 if !unsafe { JS_HasPropertyById(cx, target.handle().into(), id, &mut found) } {
1064 return false;
1065 }
1066
1067 unsafe { *bp = found };
1068 true
1069}
1070
1071#[expect(unsafe_code)]
1072unsafe extern "C" fn get(
1073 cx: *mut JSContext,
1074 proxy: RawHandleObject,
1075 receiver: RawHandleValue,
1076 id: RawHandleId,
1077 vp: RawMutableHandleValue,
1078) -> bool {
1079 let window = unsafe { GetSubframeWindowProxy(cx, proxy, id) };
1080 if let Some((window, _attrs)) = window {
1081 unsafe { window.to_jsval(cx, MutableHandle::from_raw(vp)) };
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 unsafe { JS_ForwardGetPropertyTo(cx, target.handle().into(), id, receiver, vp) }
1089}
1090
1091#[expect(unsafe_code)]
1092unsafe extern "C" fn set(
1093 cx: *mut JSContext,
1094 proxy: RawHandleObject,
1095 id: RawHandleId,
1096 v: RawHandleValue,
1097 receiver: RawHandleValue,
1098 res: *mut ObjectOpResult,
1099) -> bool {
1100 if get_array_index_from_id(unsafe { Handle::from_raw(id) }).is_some() {
1101 unsafe { (*res).code_ = JSErrNum::JSMSG_READ_ONLY as ::libc::uintptr_t };
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 unsafe { JS_ForwardSetPropertyTo(cx, target.handle().into(), id, v, receiver, res) }
1110}
1111
1112#[expect(unsafe_code)]
1113unsafe extern "C" fn get_prototype_if_ordinary(
1114 _: *mut JSContext,
1115 _: RawHandleObject,
1116 is_ordinary: *mut bool,
1117 _: RawMutableHandleObject,
1118) -> bool {
1119 unsafe { *is_ordinary = false };
1132 true
1133}
1134
1135static PROXY_TRAPS: ProxyTraps = ProxyTraps {
1136 enter: None,
1139 getOwnPropertyDescriptor: Some(get_own_property_descriptor),
1140 defineProperty: Some(define_property),
1141 ownPropertyKeys: None,
1142 delete_: None,
1143 enumerate: None,
1144 getPrototypeIfOrdinary: Some(get_prototype_if_ordinary),
1145 getPrototype: None, setPrototype: None,
1147 setImmutablePrototype: None,
1148 preventExtensions: None,
1149 isExtensible: None,
1150 has: Some(has),
1151 get: Some(get),
1152 set: Some(set),
1153 call: None,
1154 construct: None,
1155 hasOwn: None,
1156 getOwnEnumerablePropertyKeys: None,
1157 nativeCall: None,
1158 objectClassIs: None,
1159 className: None,
1160 fun_toString: None,
1161 boxedValue_unbox: None,
1162 defaultValue: None,
1163 trace: Some(trace),
1164 finalize: Some(finalize),
1165 objectMoved: None,
1166 isCallable: None,
1167 isConstructor: None,
1168};
1169
1170pub(crate) struct WindowProxyHandler(*const libc::c_void);
1173
1174impl MallocSizeOf for WindowProxyHandler {
1175 fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize {
1176 0
1178 }
1179}
1180
1181#[expect(unsafe_code)]
1183unsafe impl Send for WindowProxyHandler {}
1184#[expect(unsafe_code)]
1186unsafe impl Sync for WindowProxyHandler {}
1187
1188#[expect(unsafe_code)]
1189impl WindowProxyHandler {
1190 fn new(traps: &ProxyTraps) -> Self {
1191 let ptr = unsafe { CreateWrapperProxyHandler(traps) };
1193 assert!(!ptr.is_null());
1194 Self(ptr)
1195 }
1196
1197 pub(crate) fn x_origin_proxy_handler() -> &'static Self {
1199 use std::sync::OnceLock;
1200 static SINGLETON: OnceLock<WindowProxyHandler> = OnceLock::new();
1206 SINGLETON.get_or_init(|| Self::new(&XORIGIN_PROXY_TRAPS))
1207 }
1208
1209 pub(crate) fn proxy_handler() -> &'static Self {
1211 use std::sync::OnceLock;
1212 static SINGLETON: OnceLock<WindowProxyHandler> = OnceLock::new();
1218 SINGLETON.get_or_init(|| Self::new(&PROXY_TRAPS))
1219 }
1220
1221 pub(crate) fn new_window_proxy(
1224 &self,
1225 cx: &crate::script_runtime::JSContext,
1226 window_jsobject: js::gc::HandleObject,
1227 ) -> *mut JSObject {
1228 let obj = unsafe { NewWindowProxy(**cx, window_jsobject, self.0) };
1229 assert!(!obj.is_null());
1230 obj
1231 }
1232}
1233
1234#[expect(unsafe_code)]
1235impl Drop for WindowProxyHandler {
1236 fn drop(&mut self) {
1237 unsafe {
1240 DeleteWrapperProxyHandler(self.0);
1241 }
1242 }
1243}
1244
1245#[expect(unsafe_code)]
1253fn throw_security_error(cx: SafeJSContext, realm: InRealm) -> bool {
1254 if !unsafe { JS_IsExceptionPending(*cx) } {
1255 let global = unsafe { GlobalScope::from_context(*cx, realm) };
1256 throw_dom_exception(cx, &global, Error::Security, CanGc::note());
1257 }
1258 false
1259}
1260
1261#[expect(unsafe_code)]
1262unsafe extern "C" fn has_xorigin(
1263 cx: *mut JSContext,
1264 proxy: RawHandleObject,
1265 id: RawHandleId,
1266 bp: *mut bool,
1267) -> bool {
1268 let mut slot = UndefinedValue();
1269 unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1270 rooted!(in(cx) let target = slot.to_object());
1271 let mut found = false;
1272 unsafe { JS_HasOwnPropertyById(cx, target.handle().into(), id, &mut found) };
1273 if found {
1274 unsafe { *bp = true };
1275 true
1276 } else {
1277 let cx = unsafe { SafeJSContext::from_ptr(cx) };
1278 let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
1279 throw_security_error(cx, InRealm::Already(&in_realm_proof))
1280 }
1281}
1282
1283#[expect(unsafe_code)]
1284unsafe extern "C" fn get_xorigin(
1285 cx: *mut JSContext,
1286 proxy: RawHandleObject,
1287 receiver: RawHandleValue,
1288 id: RawHandleId,
1289 vp: RawMutableHandleValue,
1290) -> bool {
1291 let mut found = false;
1292 unsafe { has_xorigin(cx, proxy, id, &mut found) };
1293 found && unsafe { get(cx, proxy, receiver, id, vp) }
1294}
1295
1296#[expect(unsafe_code)]
1297unsafe extern "C" fn set_xorigin(
1298 cx: *mut JSContext,
1299 _: RawHandleObject,
1300 _: RawHandleId,
1301 _: RawHandleValue,
1302 _: RawHandleValue,
1303 _: *mut ObjectOpResult,
1304) -> bool {
1305 let cx = unsafe { SafeJSContext::from_ptr(cx) };
1306 let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
1307 throw_security_error(cx, InRealm::Already(&in_realm_proof))
1308}
1309
1310#[expect(unsafe_code)]
1311unsafe extern "C" fn delete_xorigin(
1312 cx: *mut JSContext,
1313 _: RawHandleObject,
1314 _: RawHandleId,
1315 _: *mut ObjectOpResult,
1316) -> bool {
1317 let cx = unsafe { SafeJSContext::from_ptr(cx) };
1318 let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
1319 throw_security_error(cx, InRealm::Already(&in_realm_proof))
1320}
1321
1322#[allow(unsafe_code, non_snake_case)]
1323unsafe extern "C" fn getOwnPropertyDescriptor_xorigin(
1324 cx: *mut JSContext,
1325 proxy: RawHandleObject,
1326 id: RawHandleId,
1327 desc: RawMutableHandle<PropertyDescriptor>,
1328 is_none: *mut bool,
1329) -> bool {
1330 let mut found = false;
1331 unsafe { has_xorigin(cx, proxy, id, &mut found) };
1332 found && unsafe { get_own_property_descriptor(cx, proxy, id, desc, is_none) }
1333}
1334
1335#[allow(unsafe_code, non_snake_case)]
1336unsafe extern "C" fn defineProperty_xorigin(
1337 cx: *mut JSContext,
1338 _: RawHandleObject,
1339 _: RawHandleId,
1340 _: RawHandle<PropertyDescriptor>,
1341 _: *mut ObjectOpResult,
1342) -> bool {
1343 let cx = unsafe { SafeJSContext::from_ptr(cx) };
1344 let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
1345 throw_security_error(cx, InRealm::Already(&in_realm_proof))
1346}
1347
1348#[allow(unsafe_code, non_snake_case)]
1349unsafe extern "C" fn preventExtensions_xorigin(
1350 cx: *mut JSContext,
1351 _: RawHandleObject,
1352 _: *mut ObjectOpResult,
1353) -> bool {
1354 let cx = unsafe { SafeJSContext::from_ptr(cx) };
1355 let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
1356 throw_security_error(cx, InRealm::Already(&in_realm_proof))
1357}
1358
1359static XORIGIN_PROXY_TRAPS: ProxyTraps = ProxyTraps {
1360 enter: None,
1361 getOwnPropertyDescriptor: Some(getOwnPropertyDescriptor_xorigin),
1362 defineProperty: Some(defineProperty_xorigin),
1363 ownPropertyKeys: None,
1364 delete_: Some(delete_xorigin),
1365 enumerate: None,
1366 getPrototypeIfOrdinary: None,
1367 getPrototype: None,
1368 setPrototype: None,
1369 setImmutablePrototype: None,
1370 preventExtensions: Some(preventExtensions_xorigin),
1371 isExtensible: None,
1372 has: Some(has_xorigin),
1373 get: Some(get_xorigin),
1374 set: Some(set_xorigin),
1375 call: None,
1376 construct: None,
1377 hasOwn: Some(has_xorigin),
1378 getOwnEnumerablePropertyKeys: None,
1379 nativeCall: None,
1380 objectClassIs: None,
1381 className: None,
1382 fun_toString: None,
1383 boxedValue_unbox: None,
1384 defaultValue: None,
1385 trace: Some(trace),
1386 finalize: Some(finalize),
1387 objectMoved: None,
1388 isCallable: None,
1389 isConstructor: None,
1390};
1391
1392#[expect(unsafe_code)]
1395unsafe extern "C" fn finalize(_fop: *mut GCContext, obj: *mut JSObject) {
1396 let mut slot = UndefinedValue();
1397 unsafe { GetProxyReservedSlot(obj, 0, &mut slot) };
1398 let this = slot.to_private() as *mut WindowProxy;
1399 if this.is_null() {
1400 return;
1402 }
1403 let jsobject = unsafe { (*this).reflector.get_jsobject().get() };
1404 debug!(
1405 "WindowProxy finalize: {:p}, with reflector {:p} from {:p}.",
1406 this, jsobject, obj
1407 );
1408 let _ = unsafe { Box::from_raw(this) };
1409}
1410
1411#[expect(unsafe_code)]
1412unsafe extern "C" fn trace(trc: *mut JSTracer, obj: *mut JSObject) {
1413 let mut slot = UndefinedValue();
1414 unsafe { GetProxyReservedSlot(obj, 0, &mut slot) };
1415 let this = slot.to_private() as *const WindowProxy;
1416 if this.is_null() {
1417 return;
1419 }
1420 unsafe { (*this).trace(trc) };
1421}