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_bindings::reflector::MutDomObject;
41use script_traits::NewPipelineInfo;
42use serde::{Deserialize, Serialize};
43use servo_url::{ImmutableOrigin, ServoUrl};
44use storage_traits::webstorage_thread::WebStorageThreadMsg;
45use style::attr::parse_integer;
46
47use crate::dom::bindings::cell::DomRefCell;
48use crate::dom::bindings::conversions::{ToJSValConvertible, root_from_handleobject};
49use crate::dom::bindings::error::{Error, Fallible, throw_dom_exception};
50use crate::dom::bindings::inheritance::Castable;
51use crate::dom::bindings::proxyhandler::set_property_descriptor;
52use crate::dom::bindings::reflector::{DomGlobal, DomObject, Reflector};
53use crate::dom::bindings::root::{Dom, DomRoot};
54use crate::dom::bindings::str::{DOMString, USVString};
55use crate::dom::bindings::trace::JSTraceable;
56use crate::dom::bindings::utils::get_array_index_from_id;
57use crate::dom::dissimilaroriginwindow::DissimilarOriginWindow;
58use crate::dom::document::Document;
59use crate::dom::element::Element;
60use crate::dom::globalscope::GlobalScope;
61use crate::dom::window::Window;
62use crate::realms::{AlreadyInRealm, InRealm, enter_realm};
63use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
64use crate::script_thread::{ScriptThread, with_script_thread};
65use crate::script_window_proxies::ScriptWindowProxies;
66
67#[dom_struct]
68pub(crate) struct WindowProxy {
73 reflector: Reflector,
78
79 #[no_trace]
83 browsing_context_id: BrowsingContextId,
84
85 #[no_trace]
87 opener: Option<BrowsingContextId>,
88
89 #[no_trace]
92 webview_id: WebViewId,
93
94 name: DomRefCell<DOMString>,
97 #[no_trace]
103 currently_active: Cell<Option<PipelineId>>,
104
105 discarded: Cell<bool>,
107
108 disowned: Cell<bool>,
110
111 is_closing: Cell<bool>,
113
114 frame_element: Option<Dom<Element>>,
118
119 parent: Option<Dom<WindowProxy>>,
121
122 delaying_load_events_mode: Cell<bool>,
124
125 #[no_trace]
127 creator_url: Option<ServoUrl>,
128
129 #[no_trace]
131 creator_origin: Option<ImmutableOrigin>,
132
133 #[conditional_malloc_size_of]
135 script_window_proxies: Rc<ScriptWindowProxies>,
136}
137
138impl WindowProxy {
139 fn new_inherited(
140 browsing_context_id: BrowsingContextId,
141 webview_id: WebViewId,
142 currently_active: Option<PipelineId>,
143 frame_element: Option<&Element>,
144 parent: Option<&WindowProxy>,
145 opener: Option<BrowsingContextId>,
146 creator: CreatorBrowsingContextInfo,
147 ) -> WindowProxy {
148 let name = frame_element.map_or(DOMString::new(), |e| {
149 e.get_string_attribute(&local_name!("name"))
150 });
151 WindowProxy {
152 reflector: Reflector::new(),
153 browsing_context_id,
154 webview_id,
155 name: DomRefCell::new(name),
156 currently_active: Cell::new(currently_active),
157 discarded: Cell::new(false),
158 disowned: Cell::new(false),
159 is_closing: Cell::new(false),
160 frame_element: frame_element.map(Dom::from_ref),
161 parent: parent.map(Dom::from_ref),
162 delaying_load_events_mode: Cell::new(false),
163 opener,
164 creator_url: creator.url,
165 creator_origin: creator.origin,
166 script_window_proxies: ScriptThread::window_proxies(),
167 }
168 }
169
170 #[expect(unsafe_code)]
171 pub(crate) fn new(
172 window: &Window,
173 browsing_context_id: BrowsingContextId,
174 webview_id: WebViewId,
175 frame_element: Option<&Element>,
176 parent: Option<&WindowProxy>,
177 opener: Option<BrowsingContextId>,
178 creator: CreatorBrowsingContextInfo,
179 ) -> DomRoot<WindowProxy> {
180 unsafe {
181 let handler = window.windowproxy_handler();
182
183 let cx = GlobalScope::get_cx();
184 let window_jsobject = window.reflector().get_jsobject();
185 assert!(!window_jsobject.get().is_null());
186 assert_ne!(
187 ((*get_object_class(window_jsobject.get())).flags & JSCLASS_IS_GLOBAL),
188 0
189 );
190 let _ac = JSAutoRealm::new(*cx, window_jsobject.get());
191
192 rooted!(in(*cx) let js_proxy = handler.new_window_proxy(&cx, window_jsobject));
194 assert!(!js_proxy.is_null());
195
196 let current = Some(window.upcast::<GlobalScope>().pipeline_id());
199 let window_proxy = Box::new(WindowProxy::new_inherited(
200 browsing_context_id,
201 webview_id,
202 current,
203 frame_element,
204 parent,
205 opener,
206 creator,
207 ));
208
209 SetProxyReservedSlot(
212 js_proxy.get(),
213 0,
214 &PrivateValue(&raw const (*window_proxy) as *const libc::c_void),
215 );
216
217 SetWindowProxy(*cx, window_jsobject, js_proxy.handle());
219
220 debug!(
222 "Initializing reflector of {:p} to {:p}.",
223 window_proxy,
224 js_proxy.get()
225 );
226 window_proxy
227 .reflector
228 .init_reflector::<WindowProxy>(js_proxy.get());
229 DomRoot::from_ref(&*Box::into_raw(window_proxy))
230 }
231 }
232
233 #[expect(unsafe_code)]
234 pub(crate) fn new_dissimilar_origin(
235 global_to_clone_from: &GlobalScope,
236 browsing_context_id: BrowsingContextId,
237 webview_id: WebViewId,
238 parent: Option<&WindowProxy>,
239 opener: Option<BrowsingContextId>,
240 creator: CreatorBrowsingContextInfo,
241 ) -> DomRoot<WindowProxy> {
242 unsafe {
243 let handler = WindowProxyHandler::x_origin_proxy_handler();
244
245 let cx = GlobalScope::get_cx();
246
247 let window_proxy = Box::new(WindowProxy::new_inherited(
249 browsing_context_id,
250 webview_id,
251 None,
252 None,
253 parent,
254 opener,
255 creator,
256 ));
257
258 let window = DissimilarOriginWindow::new(global_to_clone_from, &window_proxy);
260 let window_jsobject = window.reflector().get_jsobject();
261 assert!(!window_jsobject.get().is_null());
262 assert_ne!(
263 ((*get_object_class(window_jsobject.get())).flags & JSCLASS_IS_GLOBAL),
264 0
265 );
266 let _ac = JSAutoRealm::new(*cx, window_jsobject.get());
267
268 rooted!(in(*cx) let js_proxy = handler.new_window_proxy(&cx, window_jsobject));
270 assert!(!js_proxy.is_null());
271
272 SetProxyReservedSlot(
275 js_proxy.get(),
276 0,
277 &PrivateValue(&raw const (*window_proxy) as *const libc::c_void),
278 );
279
280 SetWindowProxy(*cx, window_jsobject, js_proxy.handle());
282
283 debug!(
285 "Initializing reflector of {:p} to {:p}.",
286 window_proxy,
287 js_proxy.get()
288 );
289 window_proxy
290 .reflector
291 .init_reflector::<WindowProxy>(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().snapshot()),
318 blank_url,
319 Some(document.base_url()),
320 Some(window.pipeline_id()),
323 document.global().get_referrer(),
324 document.get_referrer_policy(),
325 None, None,
327 false,
328 SandboxingFlagSet::empty(),
330 );
331 let load_info = AuxiliaryWebViewCreationRequest {
332 load_data: load_data.clone(),
333 opener_webview_id: window.webview_id(),
334 opener_pipeline_id: self.currently_active.get().unwrap(),
335 response_sender,
336 };
337 let constellation_msg = ScriptToConstellationMessage::CreateAuxiliaryWebView(load_info);
338 window.send_to_constellation(constellation_msg);
339
340 let response = response_receiver.recv().unwrap()?;
341 let new_browsing_context_id = BrowsingContextId::from(response.new_webview_id);
342 let new_pipeline_info = NewPipelineInfo {
343 parent_info: None,
344 new_pipeline_id: response.new_pipeline_id,
345 browsing_context_id: new_browsing_context_id,
346 webview_id: response.new_webview_id,
347 opener: Some(self.browsing_context_id),
348 load_data,
349 viewport_details: window.viewport_details(),
350 user_content_manager_id: response.user_content_manager_id,
351 theme: window.theme(),
354 };
355
356 with_script_thread(|script_thread| {
357 script_thread.spawn_pipeline(new_pipeline_info);
358 });
359
360 let new_window_proxy = ScriptThread::find_document(response.new_pipeline_id)
361 .and_then(|doc| doc.browsing_context())?;
362 if name.to_lowercase() != "_blank" {
363 new_window_proxy.set_name(name);
364 }
365 if noopener {
366 new_window_proxy.disown();
367 } else {
368 let (sender, receiver) = generic_channel::channel().unwrap();
373
374 let msg = WebStorageThreadMsg::Clone {
375 sender,
376 src: window.window_proxy().webview_id(),
377 dest: response.new_webview_id,
378 };
379
380 GenericSend::send(document.global().storage_threads(), msg).unwrap();
381 receiver.recv().unwrap();
382 }
383 Some(new_window_proxy)
384 }
385
386 pub(crate) fn is_delaying_load_events_mode(&self) -> bool {
388 self.delaying_load_events_mode.get()
389 }
390
391 pub(crate) fn start_delaying_load_events_mode(&self) {
393 self.delaying_load_events_mode.set(true);
394 }
395
396 pub(crate) fn stop_delaying_load_events_mode(&self) {
398 self.delaying_load_events_mode.set(false);
399 if let Some(document) = self.document() {
400 if !document.loader().events_inhibited() {
401 ScriptThread::mark_document_with_no_blocked_loads(&document);
402 }
403 }
404 }
405
406 pub(crate) fn disown(&self) {
408 self.disowned.set(true);
409 }
410
411 pub(crate) fn close(&self) {
414 self.is_closing.set(true);
415 }
416
417 pub(crate) fn is_closing(&self) -> bool {
419 self.is_closing.get()
420 }
421
422 #[expect(unsafe_code)]
423 pub(crate) fn opener(
425 &self,
426 cx: *mut JSContext,
427 in_realm_proof: InRealm,
428 mut retval: MutableHandleValue,
429 ) {
430 if self.disowned.get() {
431 return retval.set(NullValue());
432 }
433 let opener_id = match self.opener {
434 Some(opener_browsing_context_id) => opener_browsing_context_id,
435 None => return retval.set(NullValue()),
436 };
437 let parent_browsing_context = self.parent.as_deref();
438 let opener_proxy = match self.script_window_proxies.find_window_proxy(opener_id) {
439 Some(window_proxy) => window_proxy,
440 None => {
441 let sender_pipeline_id = self.currently_active().unwrap();
442 match ScriptThread::get_top_level_for_browsing_context(
443 self.webview_id(),
444 sender_pipeline_id,
445 opener_id,
446 ) {
447 Some(opener_top_id) => {
448 let global_to_clone_from =
449 unsafe { GlobalScope::from_context(cx, in_realm_proof) };
450 let creator =
451 CreatorBrowsingContextInfo::from(parent_browsing_context, None);
452 WindowProxy::new_dissimilar_origin(
453 &global_to_clone_from,
454 opener_id,
455 opener_top_id,
456 None,
457 None,
458 creator,
459 )
460 },
461 None => return retval.set(NullValue()),
462 }
463 },
464 };
465 if opener_proxy.is_browsing_context_discarded() {
466 return retval.set(NullValue());
467 }
468 unsafe { opener_proxy.to_jsval(cx, retval) };
469 }
470
471 pub(crate) fn open(
473 &self,
474 url: USVString,
475 target: DOMString,
476 features: DOMString,
477 can_gc: CanGc,
478 ) -> Fallible<Option<DomRoot<WindowProxy>>> {
479 if self.discarded.get() {
485 return Ok(None);
486 }
487 let non_empty_target = if target.is_empty() {
489 DOMString::from("_blank")
490 } else {
491 target
492 };
493 let tokenized_features = tokenize_open_features(features);
495 let noreferrer = parse_open_feature_boolean(&tokenized_features, "noreferrer");
499
500 let noopener = if noreferrer {
503 true
504 } else {
505 parse_open_feature_boolean(&tokenized_features, "noopener")
506 };
507 let (chosen, new) = match self.choose_browsing_context(non_empty_target, noopener) {
517 (Some(chosen), new) => (chosen, new),
518 (None, _) => return Ok(None),
519 };
520 let target_document = match chosen.document() {
523 Some(target_document) => target_document,
524 None => return Ok(None),
525 };
526 let has_trustworthy_ancestor_origin = if new {
527 target_document.has_trustworthy_ancestor_or_current_origin()
528 } else {
529 false
530 };
531 let target_window = target_document.window();
532 if !url.is_empty() {
535 let existing_document = self
536 .currently_active
537 .get()
538 .and_then(ScriptThread::find_document)
539 .unwrap();
540 let url = match existing_document.url().join(&url) {
541 Ok(url) => url,
542 Err(_) => return Err(Error::Syntax(None)),
543 };
544 let referrer = if noreferrer {
545 Referrer::NoReferrer
546 } else {
547 target_window.as_global_scope().get_referrer()
548 };
549 let csp_list = existing_document.get_csp_list();
551 target_document.set_csp_list(csp_list);
552
553 let mut load_data = LoadData::new(
557 LoadOrigin::Script(existing_document.origin().snapshot()),
558 url,
559 target_document.about_base_url(),
560 Some(target_window.pipeline_id()),
561 referrer,
562 target_document.get_referrer_policy(),
563 Some(target_window.as_global_scope().is_secure_context()),
564 Some(target_document.insecure_requests_policy()),
565 has_trustworthy_ancestor_origin,
566 target_document.creation_sandboxing_flag_set_considering_parent_iframe(),
567 );
568
569 if load_data.url.scheme() == "javascript" {
572 let existing_global = existing_document.global();
573
574 if !ScriptThread::can_navigate_to_javascript_url(
576 &existing_global,
577 target_window.as_global_scope(),
578 &mut load_data,
579 None,
580 can_gc,
581 ) {
582 return Ok(target_document.browsing_context());
584 }
585 }
586
587 let history_handling = if new {
588 NavigationHistoryBehavior::Replace
589 } else {
590 NavigationHistoryBehavior::Push
591 };
592 target_window.load_url(history_handling, false, load_data, can_gc);
593 }
594 if noopener {
596 return Ok(None);
597 }
598 Ok(target_document.browsing_context())
600 }
601
602 pub(crate) fn choose_browsing_context(
604 &self,
605 name: DOMString,
606 noopener: bool,
607 ) -> (Option<DomRoot<WindowProxy>>, bool) {
608 match name.to_lowercase().as_ref() {
609 "" | "_self" => {
610 (Some(DomRoot::from_ref(self)), false)
612 },
613 "_parent" => {
614 if let Some(parent) = self.parent() {
616 return (Some(DomRoot::from_ref(parent)), false);
617 }
618 (None, false)
619 },
620 "_top" => {
621 (Some(DomRoot::from_ref(self.top())), false)
623 },
624 "_blank" => (self.create_auxiliary_browsing_context(name, noopener), true),
625 _ => {
626 match ScriptThread::find_window_proxy_by_name(&name) {
631 Some(proxy) => (Some(proxy), false),
632 None => (self.create_auxiliary_browsing_context(name, noopener), true),
633 }
634 },
635 }
636 }
637
638 pub(crate) fn is_auxiliary(&self) -> bool {
639 self.opener.is_some()
640 }
641
642 pub(crate) fn discard_browsing_context(&self) {
643 self.discarded.set(true);
644 }
645
646 pub(crate) fn is_browsing_context_discarded(&self) -> bool {
647 self.discarded.get()
648 }
649
650 pub(crate) fn browsing_context_id(&self) -> BrowsingContextId {
651 self.browsing_context_id
652 }
653
654 pub(crate) fn webview_id(&self) -> WebViewId {
655 self.webview_id
656 }
657
658 pub(crate) fn frame_element(&self) -> Option<&Element> {
662 self.frame_element.as_deref()
663 }
664
665 pub(crate) fn document(&self) -> Option<DomRoot<Document>> {
666 self.currently_active
667 .get()
668 .and_then(ScriptThread::find_document)
669 }
670
671 pub(crate) fn parent(&self) -> Option<&WindowProxy> {
672 self.parent.as_deref()
673 }
674
675 pub(crate) fn top(&self) -> &WindowProxy {
676 let mut result = self;
677 while let Some(parent) = result.parent() {
678 result = parent;
679 }
680 result
681 }
682
683 pub fn focus(&self) {
687 debug!(
688 "Requesting the constellation to initiate a focus operation for \
689 browsing context {}",
690 self.browsing_context_id()
691 );
692 self.global()
693 .script_to_constellation_chan()
694 .send(ScriptToConstellationMessage::FocusRemoteDocument(
695 self.browsing_context_id(),
696 ))
697 .unwrap();
698 }
699
700 #[expect(unsafe_code)]
701 fn set_window(&self, window: &GlobalScope, handler: &WindowProxyHandler, _can_gc: CanGc) {
705 unsafe {
706 debug!("Setting window of {:p}.", self);
707
708 let cx = GlobalScope::get_cx();
709 let window_jsobject = window.reflector().get_jsobject();
710 let old_js_proxy = self.reflector.get_jsobject();
711 assert!(!window_jsobject.get().is_null());
712 assert_ne!(
713 ((*get_object_class(window_jsobject.get())).flags & JSCLASS_IS_GLOBAL),
714 0
715 );
716 let _ac = enter_realm(window);
717
718 SetProxyReservedSlot(old_js_proxy.get(), 0, &PrivateValue(ptr::null_mut()));
720
721 rooted!(in(*cx) let new_js_proxy = handler.new_window_proxy(&cx, window_jsobject));
727 SetProxyReservedSlot(new_js_proxy.get(), 0, &PrivateValue(ptr::null_mut()));
730 debug!(
731 "Transplanting proxy from {:p} to {:p}.",
732 old_js_proxy.get(),
733 new_js_proxy.get()
734 );
735 rooted!(in(*cx) let new_js_proxy = JS_TransplantObject(*cx, old_js_proxy, new_js_proxy.handle()));
736 debug!("Transplanted proxy is {:p}.", new_js_proxy.get());
737
738 SetProxyReservedSlot(
740 new_js_proxy.get(),
741 0,
742 &PrivateValue(self as *const _ as *const libc::c_void),
743 );
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 origin: Option<ImmutableOrigin>,
819}
820
821impl CreatorBrowsingContextInfo {
822 pub(crate) fn from(
823 parent: Option<&WindowProxy>,
824 opener: Option<&WindowProxy>,
825 ) -> CreatorBrowsingContextInfo {
826 let creator = match (parent, opener) {
827 (Some(parent), _) => parent.document(),
828 (None, Some(opener)) => opener.document(),
829 (None, None) => None,
830 };
831
832 let url = creator.as_deref().map(|document| document.url());
833 let origin = creator
834 .as_deref()
835 .map(|document| document.origin().immutable().clone());
836
837 CreatorBrowsingContextInfo { url, origin }
838 }
839}
840
841fn tokenize_open_features(features: DOMString) -> IndexMap<String, String> {
843 let is_feature_sep = |c: char| c.is_ascii_whitespace() || ['=', ','].contains(&c);
844 let mut tokenized_features = IndexMap::new();
846 let features = features.str();
848 let mut iter = features.chars();
849 let mut cur = iter.next();
850
851 while cur.is_some() {
853 let mut name = String::new();
855 let mut value = String::new();
856 while let Some(cur_char) = cur {
858 if !is_feature_sep(cur_char) {
859 break;
860 }
861 cur = iter.next();
862 }
863 while let Some(cur_char) = cur {
865 if is_feature_sep(cur_char) {
866 break;
867 }
868 name.push(cur_char.to_ascii_lowercase());
869 cur = iter.next();
870 }
871 let normalized_name = String::from(match name.as_ref() {
873 "screenx" => "left",
874 "screeny" => "top",
875 "innerwidth" => "width",
876 "innerheight" => "height",
877 _ => name.as_ref(),
878 });
879 while let Some(cur_char) = cur {
881 if cur_char == '=' || cur_char == ',' || !is_feature_sep(cur_char) {
882 break;
883 }
884 cur = iter.next();
885 }
886 if cur.is_some() && is_feature_sep(cur.unwrap()) {
888 while let Some(cur_char) = cur {
890 if !is_feature_sep(cur_char) || cur_char == ',' {
891 break;
892 }
893 cur = iter.next();
894 }
895 while let Some(cur_char) = cur {
897 if is_feature_sep(cur_char) {
898 break;
899 }
900 value.push(cur_char.to_ascii_lowercase());
901 cur = iter.next();
902 }
903 }
904 if !name.is_empty() {
906 tokenized_features.insert(normalized_name, value);
907 }
908 }
909 tokenized_features
911}
912
913fn parse_open_feature_boolean(tokenized_features: &IndexMap<String, String>, name: &str) -> bool {
915 if let Some(value) = tokenized_features.get(name) {
916 if value.is_empty() || value == "yes" {
918 return true;
919 }
920 if let Ok(int) = parse_integer(value.chars()) {
922 return int != 0;
923 }
924 }
925 false
927}
928
929#[expect(unsafe_code)]
933#[expect(non_snake_case)]
934unsafe fn GetSubframeWindowProxy(
935 cx: *mut JSContext,
936 proxy: RawHandleObject,
937 id: RawHandleId,
938) -> Option<(DomRoot<WindowProxy>, u32)> {
939 let index = get_array_index_from_id(unsafe { Handle::from_raw(id) });
940 if let Some(index) = index {
941 let mut slot = UndefinedValue();
942 unsafe { GetProxyPrivate(*proxy, &mut slot) };
943 rooted!(in(cx) let target = slot.to_object());
944 let script_window_proxies = ScriptThread::window_proxies();
945 if let Ok(win) = root_from_handleobject::<Window>(target.handle(), cx) {
946 let browsing_context_id = win.window_proxy().browsing_context_id();
947 let (result_sender, result_receiver) = ipc::channel().unwrap();
948
949 let _ = win.as_global_scope().script_to_constellation_chan().send(
950 ScriptToConstellationMessage::GetChildBrowsingContextId(
951 browsing_context_id,
952 index as usize,
953 result_sender,
954 ),
955 );
956 return result_receiver
957 .recv()
958 .ok()
959 .and_then(|maybe_bcid| maybe_bcid)
960 .and_then(|id| script_window_proxies.find_window_proxy(id))
961 .map(|proxy| (proxy, (JSPROP_ENUMERATE | JSPROP_READONLY) as u32));
962 } else if let Ok(win) =
963 root_from_handleobject::<DissimilarOriginWindow>(target.handle(), cx)
964 {
965 let browsing_context_id = win.window_proxy().browsing_context_id();
966 let (result_sender, result_receiver) = ipc::channel().unwrap();
967
968 let _ = win.global().script_to_constellation_chan().send(
969 ScriptToConstellationMessage::GetChildBrowsingContextId(
970 browsing_context_id,
971 index as usize,
972 result_sender,
973 ),
974 );
975 return result_receiver
976 .recv()
977 .ok()
978 .and_then(|maybe_bcid| maybe_bcid)
979 .and_then(|id| script_window_proxies.find_window_proxy(id))
980 .map(|proxy| (proxy, JSPROP_READONLY as u32));
981 }
982 }
983
984 None
985}
986
987#[expect(unsafe_code)]
988unsafe extern "C" fn get_own_property_descriptor(
989 cx: *mut JSContext,
990 proxy: RawHandleObject,
991 id: RawHandleId,
992 desc: RawMutableHandle<PropertyDescriptor>,
993 is_none: *mut bool,
994) -> bool {
995 let window = unsafe { GetSubframeWindowProxy(cx, proxy, id) };
996 if let Some((window, attrs)) = window {
997 rooted!(in(cx) let mut val = UndefinedValue());
998 unsafe { window.to_jsval(cx, val.handle_mut()) };
999 set_property_descriptor(
1000 unsafe { MutableHandle::from_raw(desc) },
1001 val.handle(),
1002 attrs,
1003 unsafe { &mut *is_none },
1004 );
1005 return true;
1006 }
1007
1008 let mut slot = UndefinedValue();
1009 unsafe { GetProxyPrivate(proxy.get(), &mut slot) };
1010 rooted!(in(cx) let target = slot.to_object());
1011 unsafe { JS_GetOwnPropertyDescriptorById(cx, target.handle().into(), id, desc, is_none) }
1012}
1013
1014#[expect(unsafe_code)]
1015unsafe extern "C" fn define_property(
1016 cx: *mut JSContext,
1017 proxy: RawHandleObject,
1018 id: RawHandleId,
1019 desc: RawHandle<PropertyDescriptor>,
1020 res: *mut ObjectOpResult,
1021) -> bool {
1022 if get_array_index_from_id(unsafe { Handle::from_raw(id) }).is_some() {
1023 unsafe {
1028 (*res).code_ = JSErrNum::JSMSG_CANT_DEFINE_WINDOW_ELEMENT as ::libc::uintptr_t;
1029 }
1030 return true;
1031 }
1032
1033 let mut slot = UndefinedValue();
1034 unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1035 rooted!(in(cx) let target = slot.to_object());
1036 unsafe { JS_DefinePropertyById(cx, target.handle().into(), id, desc, res) }
1037}
1038
1039#[expect(unsafe_code)]
1040unsafe extern "C" fn has(
1041 cx: *mut JSContext,
1042 proxy: RawHandleObject,
1043 id: RawHandleId,
1044 bp: *mut bool,
1045) -> bool {
1046 let window = unsafe { GetSubframeWindowProxy(cx, proxy, id) };
1047 if window.is_some() {
1048 unsafe { *bp = true };
1049 return true;
1050 }
1051
1052 let mut slot = UndefinedValue();
1053 unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1054 rooted!(in(cx) let target = slot.to_object());
1055 let mut found = false;
1056 if !unsafe { JS_HasPropertyById(cx, target.handle().into(), id, &mut found) } {
1057 return false;
1058 }
1059
1060 unsafe { *bp = found };
1061 true
1062}
1063
1064#[expect(unsafe_code)]
1065unsafe extern "C" fn get(
1066 cx: *mut JSContext,
1067 proxy: RawHandleObject,
1068 receiver: RawHandleValue,
1069 id: RawHandleId,
1070 vp: RawMutableHandleValue,
1071) -> bool {
1072 let window = unsafe { GetSubframeWindowProxy(cx, proxy, id) };
1073 if let Some((window, _attrs)) = window {
1074 unsafe { window.to_jsval(cx, MutableHandle::from_raw(vp)) };
1075 return true;
1076 }
1077
1078 let mut slot = UndefinedValue();
1079 unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1080 rooted!(in(cx) let target = slot.to_object());
1081 unsafe { JS_ForwardGetPropertyTo(cx, target.handle().into(), id, receiver, vp) }
1082}
1083
1084#[expect(unsafe_code)]
1085unsafe extern "C" fn set(
1086 cx: *mut JSContext,
1087 proxy: RawHandleObject,
1088 id: RawHandleId,
1089 v: RawHandleValue,
1090 receiver: RawHandleValue,
1091 res: *mut ObjectOpResult,
1092) -> bool {
1093 if get_array_index_from_id(unsafe { Handle::from_raw(id) }).is_some() {
1094 unsafe { (*res).code_ = JSErrNum::JSMSG_READ_ONLY as ::libc::uintptr_t };
1096 return true;
1097 }
1098
1099 let mut slot = UndefinedValue();
1100 unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1101 rooted!(in(cx) let target = slot.to_object());
1102 unsafe { JS_ForwardSetPropertyTo(cx, target.handle().into(), id, v, receiver, res) }
1103}
1104
1105#[expect(unsafe_code)]
1106unsafe extern "C" fn get_prototype_if_ordinary(
1107 _: *mut JSContext,
1108 _: RawHandleObject,
1109 is_ordinary: *mut bool,
1110 _: RawMutableHandleObject,
1111) -> bool {
1112 unsafe { *is_ordinary = false };
1125 true
1126}
1127
1128static PROXY_TRAPS: ProxyTraps = ProxyTraps {
1129 enter: None,
1132 getOwnPropertyDescriptor: Some(get_own_property_descriptor),
1133 defineProperty: Some(define_property),
1134 ownPropertyKeys: None,
1135 delete_: None,
1136 enumerate: None,
1137 getPrototypeIfOrdinary: Some(get_prototype_if_ordinary),
1138 getPrototype: None, setPrototype: None,
1140 setImmutablePrototype: None,
1141 preventExtensions: None,
1142 isExtensible: None,
1143 has: Some(has),
1144 get: Some(get),
1145 set: Some(set),
1146 call: None,
1147 construct: None,
1148 hasOwn: None,
1149 getOwnEnumerablePropertyKeys: None,
1150 nativeCall: None,
1151 objectClassIs: None,
1152 className: None,
1153 fun_toString: None,
1154 boxedValue_unbox: None,
1155 defaultValue: None,
1156 trace: Some(trace),
1157 finalize: Some(finalize),
1158 objectMoved: None,
1159 isCallable: None,
1160 isConstructor: None,
1161};
1162
1163pub(crate) struct WindowProxyHandler(*const libc::c_void);
1166
1167impl MallocSizeOf for WindowProxyHandler {
1168 fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize {
1169 0
1171 }
1172}
1173
1174#[expect(unsafe_code)]
1176unsafe impl Send for WindowProxyHandler {}
1177#[expect(unsafe_code)]
1179unsafe impl Sync for WindowProxyHandler {}
1180
1181#[expect(unsafe_code)]
1182impl WindowProxyHandler {
1183 fn new(traps: &ProxyTraps) -> Self {
1184 let ptr = unsafe { CreateWrapperProxyHandler(traps) };
1186 assert!(!ptr.is_null());
1187 Self(ptr)
1188 }
1189
1190 pub(crate) fn x_origin_proxy_handler() -> &'static Self {
1192 use std::sync::OnceLock;
1193 static SINGLETON: OnceLock<WindowProxyHandler> = OnceLock::new();
1199 SINGLETON.get_or_init(|| Self::new(&XORIGIN_PROXY_TRAPS))
1200 }
1201
1202 pub(crate) fn proxy_handler() -> &'static Self {
1204 use std::sync::OnceLock;
1205 static SINGLETON: OnceLock<WindowProxyHandler> = OnceLock::new();
1211 SINGLETON.get_or_init(|| Self::new(&PROXY_TRAPS))
1212 }
1213
1214 pub(crate) fn new_window_proxy(
1217 &self,
1218 cx: &crate::script_runtime::JSContext,
1219 window_jsobject: js::gc::HandleObject,
1220 ) -> *mut JSObject {
1221 let obj = unsafe { NewWindowProxy(**cx, window_jsobject, self.0) };
1222 assert!(!obj.is_null());
1223 obj
1224 }
1225}
1226
1227#[expect(unsafe_code)]
1228impl Drop for WindowProxyHandler {
1229 fn drop(&mut self) {
1230 unsafe {
1233 DeleteWrapperProxyHandler(self.0);
1234 }
1235 }
1236}
1237
1238#[expect(unsafe_code)]
1246fn throw_security_error(cx: SafeJSContext, realm: InRealm) -> bool {
1247 if !unsafe { JS_IsExceptionPending(*cx) } {
1248 let global = unsafe { GlobalScope::from_context(*cx, realm) };
1249 throw_dom_exception(cx, &global, Error::Security(None), CanGc::note());
1250 }
1251 false
1252}
1253
1254#[expect(unsafe_code)]
1255unsafe extern "C" fn has_xorigin(
1256 cx: *mut JSContext,
1257 proxy: RawHandleObject,
1258 id: RawHandleId,
1259 bp: *mut bool,
1260) -> bool {
1261 let mut slot = UndefinedValue();
1262 unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1263 rooted!(in(cx) let target = slot.to_object());
1264 let mut found = false;
1265 unsafe { JS_HasOwnPropertyById(cx, target.handle().into(), id, &mut found) };
1266 if found {
1267 unsafe { *bp = true };
1268 true
1269 } else {
1270 let cx = unsafe { SafeJSContext::from_ptr(cx) };
1271 let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
1272 throw_security_error(cx, InRealm::Already(&in_realm_proof))
1273 }
1274}
1275
1276#[expect(unsafe_code)]
1277unsafe extern "C" fn get_xorigin(
1278 cx: *mut JSContext,
1279 proxy: RawHandleObject,
1280 receiver: RawHandleValue,
1281 id: RawHandleId,
1282 vp: RawMutableHandleValue,
1283) -> bool {
1284 let mut found = false;
1285 unsafe { has_xorigin(cx, proxy, id, &mut found) };
1286 found && unsafe { get(cx, proxy, receiver, id, vp) }
1287}
1288
1289#[expect(unsafe_code)]
1290unsafe extern "C" fn set_xorigin(
1291 cx: *mut JSContext,
1292 _: RawHandleObject,
1293 _: RawHandleId,
1294 _: RawHandleValue,
1295 _: RawHandleValue,
1296 _: *mut ObjectOpResult,
1297) -> bool {
1298 let cx = unsafe { SafeJSContext::from_ptr(cx) };
1299 let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
1300 throw_security_error(cx, InRealm::Already(&in_realm_proof))
1301}
1302
1303#[expect(unsafe_code)]
1304unsafe extern "C" fn delete_xorigin(
1305 cx: *mut JSContext,
1306 _: RawHandleObject,
1307 _: RawHandleId,
1308 _: *mut ObjectOpResult,
1309) -> bool {
1310 let cx = unsafe { SafeJSContext::from_ptr(cx) };
1311 let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
1312 throw_security_error(cx, InRealm::Already(&in_realm_proof))
1313}
1314
1315#[expect(unsafe_code)]
1316#[expect(non_snake_case)]
1317unsafe extern "C" fn getOwnPropertyDescriptor_xorigin(
1318 cx: *mut JSContext,
1319 proxy: RawHandleObject,
1320 id: RawHandleId,
1321 desc: RawMutableHandle<PropertyDescriptor>,
1322 is_none: *mut bool,
1323) -> bool {
1324 let mut found = false;
1325 unsafe { has_xorigin(cx, proxy, id, &mut found) };
1326 found && unsafe { get_own_property_descriptor(cx, proxy, id, desc, is_none) }
1327}
1328
1329#[expect(unsafe_code)]
1330#[expect(non_snake_case)]
1331unsafe extern "C" fn defineProperty_xorigin(
1332 cx: *mut JSContext,
1333 _: RawHandleObject,
1334 _: RawHandleId,
1335 _: RawHandle<PropertyDescriptor>,
1336 _: *mut ObjectOpResult,
1337) -> bool {
1338 let cx = unsafe { SafeJSContext::from_ptr(cx) };
1339 let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
1340 throw_security_error(cx, InRealm::Already(&in_realm_proof))
1341}
1342
1343#[expect(unsafe_code)]
1344#[expect(non_snake_case)]
1345unsafe extern "C" fn preventExtensions_xorigin(
1346 cx: *mut JSContext,
1347 _: RawHandleObject,
1348 _: *mut ObjectOpResult,
1349) -> bool {
1350 let cx = unsafe { SafeJSContext::from_ptr(cx) };
1351 let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
1352 throw_security_error(cx, InRealm::Already(&in_realm_proof))
1353}
1354
1355static XORIGIN_PROXY_TRAPS: ProxyTraps = ProxyTraps {
1356 enter: None,
1357 getOwnPropertyDescriptor: Some(getOwnPropertyDescriptor_xorigin),
1358 defineProperty: Some(defineProperty_xorigin),
1359 ownPropertyKeys: None,
1360 delete_: Some(delete_xorigin),
1361 enumerate: None,
1362 getPrototypeIfOrdinary: None,
1363 getPrototype: None,
1364 setPrototype: None,
1365 setImmutablePrototype: None,
1366 preventExtensions: Some(preventExtensions_xorigin),
1367 isExtensible: None,
1368 has: Some(has_xorigin),
1369 get: Some(get_xorigin),
1370 set: Some(set_xorigin),
1371 call: None,
1372 construct: None,
1373 hasOwn: Some(has_xorigin),
1374 getOwnEnumerablePropertyKeys: None,
1375 nativeCall: None,
1376 objectClassIs: None,
1377 className: None,
1378 fun_toString: None,
1379 boxedValue_unbox: None,
1380 defaultValue: None,
1381 trace: Some(trace),
1382 finalize: Some(finalize),
1383 objectMoved: None,
1384 isCallable: None,
1385 isConstructor: None,
1386};
1387
1388#[expect(unsafe_code)]
1391unsafe extern "C" fn finalize(_fop: *mut GCContext, obj: *mut JSObject) {
1392 let mut slot = UndefinedValue();
1393 unsafe { GetProxyReservedSlot(obj, 0, &mut slot) };
1394 let this = slot.to_private() as *mut WindowProxy;
1395 if this.is_null() {
1396 return;
1398 }
1399 let jsobject = unsafe { (*this).reflector.get_jsobject().get() };
1400 debug!(
1401 "WindowProxy finalize: {:p}, with reflector {:p} from {:p}.",
1402 this, jsobject, obj
1403 );
1404 let _ = unsafe { Box::from_raw(this) };
1405}
1406
1407#[expect(unsafe_code)]
1408unsafe extern "C" fn trace(trc: *mut JSTracer, obj: *mut JSObject) {
1409 let mut slot = UndefinedValue();
1410 unsafe { GetProxyReservedSlot(obj, 0, &mut slot) };
1411 let this = slot.to_private() as *const WindowProxy;
1412 if this.is_null() {
1413 return;
1415 }
1416 unsafe { (*this).trace(trc) };
1417}