1use std::cell::Cell;
6use std::ptr::{self, NonNull};
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::realm::{AutoRealm, CurrentRealm};
37use js::rust::wrappers::{JS_TransplantObject, NewWindowProxy, SetWindowProxy};
38use js::rust::{Handle, MutableHandle, MutableHandleValue, get_object_class};
39use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
40use net_traits::request::Referrer;
41use script_bindings::reflector::MutDomObject;
42use script_traits::NewPipelineInfo;
43use serde::{Deserialize, Serialize};
44use servo_url::{ImmutableOrigin, ServoUrl};
45use storage_traits::webstorage_thread::WebStorageThreadMsg;
46use style::attr::parse_integer;
47
48use crate::dom::bindings::cell::DomRefCell;
49use crate::dom::bindings::conversions::{ToJSValConvertible, root_from_handleobject};
50use crate::dom::bindings::error::{Error, Fallible, throw_dom_exception};
51use crate::dom::bindings::inheritance::Castable;
52use crate::dom::bindings::proxyhandler::set_property_descriptor;
53use crate::dom::bindings::reflector::{DomGlobal, DomObject, Reflector};
54use crate::dom::bindings::root::{Dom, DomRoot};
55use crate::dom::bindings::str::{DOMString, USVString};
56use crate::dom::bindings::trace::JSTraceable;
57use crate::dom::bindings::utils::get_array_index_from_id;
58use crate::dom::dissimilaroriginwindow::DissimilarOriginWindow;
59use crate::dom::document::Document;
60use crate::dom::element::Element;
61use crate::dom::globalscope::GlobalScope;
62use crate::dom::window::Window;
63use crate::realms::{AlreadyInRealm, InRealm, enter_realm};
64use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
65use crate::script_thread::{ScriptThread, with_script_thread};
66use crate::script_window_proxies::ScriptWindowProxies;
67
68#[dom_struct]
69pub(crate) struct WindowProxy {
74 reflector: Reflector,
79
80 #[no_trace]
84 browsing_context_id: BrowsingContextId,
85
86 #[no_trace]
88 opener: Option<BrowsingContextId>,
89
90 #[no_trace]
93 webview_id: WebViewId,
94
95 name: DomRefCell<DOMString>,
98 #[no_trace]
104 currently_active: Cell<Option<PipelineId>>,
105
106 discarded: Cell<bool>,
108
109 disowned: Cell<bool>,
111
112 is_closing: Cell<bool>,
114
115 frame_element: Option<Dom<Element>>,
119
120 parent: Option<Dom<WindowProxy>>,
122
123 delaying_load_events_mode: Cell<bool>,
125
126 #[no_trace]
128 creator_url: Option<ServoUrl>,
129
130 #[no_trace]
132 creator_origin: Option<ImmutableOrigin>,
133
134 #[conditional_malloc_size_of]
136 script_window_proxies: Rc<ScriptWindowProxies>,
137}
138
139impl WindowProxy {
140 fn new_inherited(
141 browsing_context_id: BrowsingContextId,
142 webview_id: WebViewId,
143 currently_active: Option<PipelineId>,
144 frame_element: Option<&Element>,
145 parent: Option<&WindowProxy>,
146 opener: Option<BrowsingContextId>,
147 creator: CreatorBrowsingContextInfo,
148 ) -> WindowProxy {
149 let name = frame_element.map_or(DOMString::new(), |e| {
150 e.get_string_attribute(&local_name!("name"))
151 });
152 WindowProxy {
153 reflector: Reflector::new(),
154 browsing_context_id,
155 webview_id,
156 name: DomRefCell::new(name),
157 currently_active: Cell::new(currently_active),
158 discarded: Cell::new(false),
159 disowned: Cell::new(false),
160 is_closing: Cell::new(false),
161 frame_element: frame_element.map(Dom::from_ref),
162 parent: parent.map(Dom::from_ref),
163 delaying_load_events_mode: Cell::new(false),
164 opener,
165 creator_url: creator.url,
166 creator_origin: creator.origin,
167 script_window_proxies: ScriptThread::window_proxies(),
168 }
169 }
170
171 #[expect(unsafe_code)]
172 pub(crate) fn new(
173 window: &Window,
174 browsing_context_id: BrowsingContextId,
175 webview_id: WebViewId,
176 frame_element: Option<&Element>,
177 parent: Option<&WindowProxy>,
178 opener: Option<BrowsingContextId>,
179 creator: CreatorBrowsingContextInfo,
180 ) -> DomRoot<WindowProxy> {
181 unsafe {
182 let handler = window.windowproxy_handler();
183
184 let cx = GlobalScope::get_cx();
185 let window_jsobject = window.reflector().get_jsobject();
186 assert!(!window_jsobject.get().is_null());
187 assert_ne!(
188 ((*get_object_class(window_jsobject.get())).flags & JSCLASS_IS_GLOBAL),
189 0
190 );
191 let _ac = JSAutoRealm::new(*cx, window_jsobject.get());
192
193 rooted!(in(*cx) let js_proxy = handler.new_window_proxy(&cx, window_jsobject));
195 assert!(!js_proxy.is_null());
196
197 let current = Some(window.upcast::<GlobalScope>().pipeline_id());
200 let window_proxy = Box::new(WindowProxy::new_inherited(
201 browsing_context_id,
202 webview_id,
203 current,
204 frame_element,
205 parent,
206 opener,
207 creator,
208 ));
209
210 SetProxyReservedSlot(
213 js_proxy.get(),
214 0,
215 &PrivateValue(&raw const (*window_proxy) as *const libc::c_void),
216 );
217
218 SetWindowProxy(*cx, window_jsobject, js_proxy.handle());
220
221 debug!(
223 "Initializing reflector of {:p} to {:p}.",
224 window_proxy,
225 js_proxy.get()
226 );
227 window_proxy
228 .reflector
229 .init_reflector::<WindowProxy>(js_proxy.get());
230 DomRoot::from_ref(&*Box::into_raw(window_proxy))
231 }
232 }
233
234 #[expect(unsafe_code)]
235 pub(crate) fn new_dissimilar_origin(
236 cx: &mut js::context::JSContext,
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 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(cx, 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 mut realm = AutoRealm::new(cx, NonNull::new(window_jsobject.get()).unwrap());
267 let cx = &mut realm;
268
269 rooted!(&in(cx) let js_proxy = handler.new_window_proxy(&cx.into(), window_jsobject));
271 assert!(!js_proxy.is_null());
272
273 SetProxyReservedSlot(
276 js_proxy.get(),
277 0,
278 &PrivateValue(&raw const (*window_proxy) as *const libc::c_void),
279 );
280
281 SetWindowProxy(cx.raw_cx(), window_jsobject, js_proxy.handle());
283
284 debug!(
286 "Initializing reflector of {:p} to {:p}.",
287 window_proxy,
288 js_proxy.get()
289 );
290 window_proxy
291 .reflector
292 .init_reflector::<WindowProxy>(js_proxy.get());
293 DomRoot::from_ref(&*Box::into_raw(window_proxy))
294 }
295 }
296
297 fn create_auxiliary_browsing_context(
299 &self,
300 name: DOMString,
301 noopener: bool,
302 ) -> Option<DomRoot<WindowProxy>> {
303 let (response_sender, response_receiver) = ipc::channel().unwrap();
304 let window = self
305 .currently_active
306 .get()
307 .and_then(ScriptThread::find_document)
308 .map(|doc| DomRoot::from_ref(doc.window()))
309 .unwrap();
310
311 let document = self
312 .currently_active
313 .get()
314 .and_then(ScriptThread::find_document)
315 .expect("A WindowProxy creating an auxiliary to have an active document");
316 let blank_url = ServoUrl::parse("about:blank").ok().unwrap();
317 let load_data = LoadData::new(
318 LoadOrigin::Script(document.origin().snapshot()),
319 blank_url,
320 Some(document.base_url()),
321 Some(window.pipeline_id()),
324 document.global().get_referrer(),
325 document.get_referrer_policy(),
326 None, None,
328 false,
329 SandboxingFlagSet::empty(),
331 );
332 let load_info = AuxiliaryWebViewCreationRequest {
333 load_data: load_data.clone(),
334 opener_webview_id: window.webview_id(),
335 opener_pipeline_id: self.currently_active.get().unwrap(),
336 response_sender,
337 };
338 let constellation_msg = ScriptToConstellationMessage::CreateAuxiliaryWebView(load_info);
339 window.send_to_constellation(constellation_msg);
340
341 let response = response_receiver.recv().unwrap()?;
342 let new_browsing_context_id = BrowsingContextId::from(response.new_webview_id);
343 let new_pipeline_info = NewPipelineInfo {
344 parent_info: None,
345 new_pipeline_id: response.new_pipeline_id,
346 browsing_context_id: new_browsing_context_id,
347 webview_id: response.new_webview_id,
348 opener: Some(self.browsing_context_id),
349 load_data,
350 viewport_details: window.viewport_details(),
351 user_content_manager_id: response.user_content_manager_id,
352 theme: window.theme(),
355 };
356
357 with_script_thread(|script_thread| {
358 script_thread.spawn_pipeline(new_pipeline_info);
359 });
360
361 let new_window_proxy = ScriptThread::find_document(response.new_pipeline_id)
362 .and_then(|doc| doc.browsing_context())?;
363 if name.to_lowercase() != "_blank" {
364 new_window_proxy.set_name(name);
365 }
366 if noopener {
367 new_window_proxy.disown();
368 } else {
369 let (sender, receiver) = generic_channel::channel().unwrap();
374
375 let msg = WebStorageThreadMsg::Clone {
376 sender,
377 src: window.window_proxy().webview_id(),
378 dest: response.new_webview_id,
379 };
380
381 GenericSend::send(document.global().storage_threads(), msg).unwrap();
382 receiver.recv().unwrap();
383 }
384 Some(new_window_proxy)
385 }
386
387 pub(crate) fn is_delaying_load_events_mode(&self) -> bool {
389 self.delaying_load_events_mode.get()
390 }
391
392 pub(crate) fn start_delaying_load_events_mode(&self) {
394 self.delaying_load_events_mode.set(true);
395 }
396
397 pub(crate) fn stop_delaying_load_events_mode(&self) {
399 self.delaying_load_events_mode.set(false);
400 if let Some(document) = self.document() {
401 if !document.loader().events_inhibited() {
402 ScriptThread::mark_document_with_no_blocked_loads(&document);
403 }
404 }
405 }
406
407 pub(crate) fn disown(&self) {
409 self.disowned.set(true);
410 }
411
412 pub(crate) fn close(&self) {
415 self.is_closing.set(true);
416 }
417
418 pub(crate) fn is_closing(&self) -> bool {
420 self.is_closing.get()
421 }
422
423 pub(crate) fn opener(&self, cx: &mut CurrentRealm, mut retval: MutableHandleValue) {
425 if self.disowned.get() {
426 return retval.set(NullValue());
427 }
428 let opener_id = match self.opener {
429 Some(opener_browsing_context_id) => opener_browsing_context_id,
430 None => return retval.set(NullValue()),
431 };
432 let parent_browsing_context = self.parent.as_deref();
433 let opener_proxy = match self.script_window_proxies.find_window_proxy(opener_id) {
434 Some(window_proxy) => window_proxy,
435 None => {
436 let sender_pipeline_id = self.currently_active().unwrap();
437 match ScriptThread::get_top_level_for_browsing_context(
438 self.webview_id(),
439 sender_pipeline_id,
440 opener_id,
441 ) {
442 Some(opener_top_id) => {
443 let global_to_clone_from = GlobalScope::from_current_realm(cx);
444 let creator =
445 CreatorBrowsingContextInfo::from(parent_browsing_context, None);
446 WindowProxy::new_dissimilar_origin(
447 cx,
448 &global_to_clone_from,
449 opener_id,
450 opener_top_id,
451 None,
452 None,
453 creator,
454 )
455 },
456 None => return retval.set(NullValue()),
457 }
458 },
459 };
460 if opener_proxy.is_browsing_context_discarded() {
461 return retval.set(NullValue());
462 }
463 opener_proxy.safe_to_jsval(cx, retval);
464 }
465
466 pub(crate) fn open(
468 &self,
469 url: USVString,
470 target: DOMString,
471 features: DOMString,
472 can_gc: CanGc,
473 ) -> Fallible<Option<DomRoot<WindowProxy>>> {
474 if self.discarded.get() {
480 return Ok(None);
481 }
482 let non_empty_target = if target.is_empty() {
484 DOMString::from("_blank")
485 } else {
486 target
487 };
488 let tokenized_features = tokenize_open_features(features);
490 let noreferrer = parse_open_feature_boolean(&tokenized_features, "noreferrer");
494
495 let noopener = if noreferrer {
498 true
499 } else {
500 parse_open_feature_boolean(&tokenized_features, "noopener")
501 };
502 let (chosen, new) = match self.choose_browsing_context(non_empty_target, noopener) {
512 (Some(chosen), new) => (chosen, new),
513 (None, _) => return Ok(None),
514 };
515 let target_document = match chosen.document() {
518 Some(target_document) => target_document,
519 None => return Ok(None),
520 };
521 let has_trustworthy_ancestor_origin = if new {
522 target_document.has_trustworthy_ancestor_or_current_origin()
523 } else {
524 false
525 };
526 let target_window = target_document.window();
527 if !url.is_empty() {
530 let existing_document = self
531 .currently_active
532 .get()
533 .and_then(ScriptThread::find_document)
534 .unwrap();
535 let url = match existing_document.url().join(&url) {
536 Ok(url) => url,
537 Err(_) => return Err(Error::Syntax(None)),
538 };
539 let referrer = if noreferrer {
540 Referrer::NoReferrer
541 } else {
542 target_window.as_global_scope().get_referrer()
543 };
544 let csp_list = existing_document.get_csp_list().clone();
546 target_document.set_csp_list(csp_list);
547
548 let mut load_data = LoadData::new(
552 LoadOrigin::Script(existing_document.origin().snapshot()),
553 url,
554 target_document.about_base_url(),
555 Some(target_window.pipeline_id()),
556 referrer,
557 target_document.get_referrer_policy(),
558 Some(target_window.as_global_scope().is_secure_context()),
559 Some(target_document.insecure_requests_policy()),
560 has_trustworthy_ancestor_origin,
561 target_document.creation_sandboxing_flag_set_considering_parent_iframe(),
562 );
563
564 if load_data.url.scheme() == "javascript" {
567 let existing_global = existing_document.global();
568
569 if !ScriptThread::can_navigate_to_javascript_url(
571 &existing_global,
572 target_window.as_global_scope(),
573 &mut load_data,
574 None,
575 can_gc,
576 ) {
577 return Ok(target_document.browsing_context());
579 }
580 }
581
582 let history_handling = if new {
583 NavigationHistoryBehavior::Replace
584 } else {
585 NavigationHistoryBehavior::Push
586 };
587 target_window.load_url(history_handling, false, load_data, can_gc);
588 }
589 if noopener {
591 return Ok(None);
592 }
593 Ok(target_document.browsing_context())
595 }
596
597 pub(crate) fn choose_browsing_context(
599 &self,
600 name: DOMString,
601 noopener: bool,
602 ) -> (Option<DomRoot<WindowProxy>>, bool) {
603 match name.to_lowercase().as_ref() {
604 "" | "_self" => {
605 (Some(DomRoot::from_ref(self)), false)
607 },
608 "_parent" => {
609 if let Some(parent) = self.parent() {
611 return (Some(DomRoot::from_ref(parent)), false);
612 }
613 (None, false)
614 },
615 "_top" => {
616 (Some(DomRoot::from_ref(self.top())), false)
618 },
619 "_blank" => (self.create_auxiliary_browsing_context(name, noopener), true),
620 _ => {
621 match ScriptThread::find_window_proxy_by_name(&name) {
626 Some(proxy) => (Some(proxy), false),
627 None => (self.create_auxiliary_browsing_context(name, noopener), true),
628 }
629 },
630 }
631 }
632
633 pub(crate) fn is_auxiliary(&self) -> bool {
634 self.opener.is_some()
635 }
636
637 pub(crate) fn discard_browsing_context(&self) {
638 self.discarded.set(true);
639 }
640
641 pub(crate) fn is_browsing_context_discarded(&self) -> bool {
642 self.discarded.get()
643 }
644
645 pub(crate) fn browsing_context_id(&self) -> BrowsingContextId {
646 self.browsing_context_id
647 }
648
649 pub(crate) fn webview_id(&self) -> WebViewId {
650 self.webview_id
651 }
652
653 pub(crate) fn frame_element(&self) -> Option<&Element> {
657 self.frame_element.as_deref()
658 }
659
660 pub(crate) fn document(&self) -> Option<DomRoot<Document>> {
661 self.currently_active
662 .get()
663 .and_then(ScriptThread::find_document)
664 }
665
666 pub(crate) fn parent(&self) -> Option<&WindowProxy> {
667 self.parent.as_deref()
668 }
669
670 pub(crate) fn top(&self) -> &WindowProxy {
671 let mut result = self;
672 while let Some(parent) = result.parent() {
673 result = parent;
674 }
675 result
676 }
677
678 pub fn focus(&self) {
682 debug!(
683 "Requesting the constellation to initiate a focus operation for \
684 browsing context {}",
685 self.browsing_context_id()
686 );
687 self.global()
688 .script_to_constellation_chan()
689 .send(ScriptToConstellationMessage::FocusRemoteDocument(
690 self.browsing_context_id(),
691 ))
692 .unwrap();
693 }
694
695 #[expect(unsafe_code)]
696 fn set_window(&self, window: &GlobalScope, handler: &WindowProxyHandler, _can_gc: CanGc) {
700 unsafe {
701 debug!("Setting window of {:p}.", self);
702
703 let cx = GlobalScope::get_cx();
704 let window_jsobject = window.reflector().get_jsobject();
705 let old_js_proxy = self.reflector.get_jsobject();
706 assert!(!window_jsobject.get().is_null());
707 assert_ne!(
708 ((*get_object_class(window_jsobject.get())).flags & JSCLASS_IS_GLOBAL),
709 0
710 );
711 let _ac = enter_realm(window);
712
713 SetProxyReservedSlot(old_js_proxy.get(), 0, &PrivateValue(ptr::null_mut()));
715
716 rooted!(in(*cx) let new_js_proxy = handler.new_window_proxy(&cx, window_jsobject));
722 SetProxyReservedSlot(new_js_proxy.get(), 0, &PrivateValue(ptr::null_mut()));
725 debug!(
726 "Transplanting proxy from {:p} to {:p}.",
727 old_js_proxy.get(),
728 new_js_proxy.get()
729 );
730 rooted!(in(*cx) let new_js_proxy = JS_TransplantObject(*cx, old_js_proxy, new_js_proxy.handle()));
731 debug!("Transplanted proxy is {:p}.", new_js_proxy.get());
732
733 SetProxyReservedSlot(
735 new_js_proxy.get(),
736 0,
737 &PrivateValue(self as *const _ as *const libc::c_void),
738 );
739
740 SetWindowProxy(*cx, window_jsobject, new_js_proxy.handle());
742
743 debug!(
745 "Setting reflector of {:p} to {:p}.",
746 self,
747 new_js_proxy.get()
748 );
749 self.reflector.rootable().set(new_js_proxy.get());
750 }
751 }
752
753 pub(crate) fn set_currently_active(&self, window: &Window, can_gc: CanGc) {
754 if let Some(pipeline_id) = self.currently_active() {
755 if pipeline_id == window.pipeline_id() {
756 return debug!(
757 "Attempt to set the currently active window to the currently active window."
758 );
759 }
760 }
761
762 let global_scope = window.as_global_scope();
763 self.set_window(global_scope, WindowProxyHandler::proxy_handler(), can_gc);
764 self.currently_active.set(Some(global_scope.pipeline_id()));
765 }
766
767 pub(crate) fn unset_currently_active(&self, cx: &mut js::context::JSContext) {
768 if self.currently_active().is_none() {
769 return debug!(
770 "Attempt to unset the currently active window on a windowproxy that does not have one."
771 );
772 }
773 let globalscope = self.global();
774 let window = DissimilarOriginWindow::new(cx, &globalscope, self);
775 self.set_window(
776 window.upcast(),
777 WindowProxyHandler::x_origin_proxy_handler(),
778 CanGc::from_cx(cx),
779 );
780 self.currently_active.set(None);
781 }
782
783 pub(crate) fn currently_active(&self) -> Option<PipelineId> {
784 self.currently_active.get()
785 }
786
787 pub(crate) fn get_name(&self) -> DOMString {
788 self.name.borrow().clone()
789 }
790
791 pub(crate) fn set_name(&self, name: DOMString) {
792 *self.name.borrow_mut() = name;
793 }
794}
795
796#[derive(Debug, Deserialize, Serialize)]
808pub(crate) struct CreatorBrowsingContextInfo {
809 url: Option<ServoUrl>,
811
812 origin: Option<ImmutableOrigin>,
814}
815
816impl CreatorBrowsingContextInfo {
817 pub(crate) fn from(
818 parent: Option<&WindowProxy>,
819 opener: Option<&WindowProxy>,
820 ) -> CreatorBrowsingContextInfo {
821 let creator = match (parent, opener) {
822 (Some(parent), _) => parent.document(),
823 (None, Some(opener)) => opener.document(),
824 (None, None) => None,
825 };
826
827 let url = creator.as_deref().map(|document| document.url());
828 let origin = creator
829 .as_deref()
830 .map(|document| document.origin().immutable().clone());
831
832 CreatorBrowsingContextInfo { url, origin }
833 }
834}
835
836fn tokenize_open_features(features: DOMString) -> IndexMap<String, String> {
838 let is_feature_sep = |c: char| c.is_ascii_whitespace() || ['=', ','].contains(&c);
839 let mut tokenized_features = IndexMap::new();
841 let features = features.str();
843 let mut iter = features.chars();
844 let mut cur = iter.next();
845
846 while cur.is_some() {
848 let mut name = String::new();
850 let mut value = String::new();
851 while let Some(cur_char) = cur {
853 if !is_feature_sep(cur_char) {
854 break;
855 }
856 cur = iter.next();
857 }
858 while let Some(cur_char) = cur {
860 if is_feature_sep(cur_char) {
861 break;
862 }
863 name.push(cur_char.to_ascii_lowercase());
864 cur = iter.next();
865 }
866 let normalized_name = String::from(match name.as_ref() {
868 "screenx" => "left",
869 "screeny" => "top",
870 "innerwidth" => "width",
871 "innerheight" => "height",
872 _ => name.as_ref(),
873 });
874 while let Some(cur_char) = cur {
876 if cur_char == '=' || cur_char == ',' || !is_feature_sep(cur_char) {
877 break;
878 }
879 cur = iter.next();
880 }
881 if cur.is_some() && is_feature_sep(cur.unwrap()) {
883 while let Some(cur_char) = cur {
885 if !is_feature_sep(cur_char) || 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 value.push(cur_char.to_ascii_lowercase());
896 cur = iter.next();
897 }
898 }
899 if !name.is_empty() {
901 tokenized_features.insert(normalized_name, value);
902 }
903 }
904 tokenized_features
906}
907
908fn parse_open_feature_boolean(tokenized_features: &IndexMap<String, String>, name: &str) -> bool {
910 if let Some(value) = tokenized_features.get(name) {
911 if value.is_empty() || value == "yes" {
913 return true;
914 }
915 if let Ok(int) = parse_integer(value.chars()) {
917 return int != 0;
918 }
919 }
920 false
922}
923
924#[expect(unsafe_code)]
928#[expect(non_snake_case)]
929unsafe fn GetSubframeWindowProxy(
930 cx: *mut JSContext,
931 proxy: RawHandleObject,
932 id: RawHandleId,
933) -> Option<(DomRoot<WindowProxy>, u32)> {
934 let index = get_array_index_from_id(unsafe { Handle::from_raw(id) });
935 if let Some(index) = index {
936 let mut slot = UndefinedValue();
937 unsafe { GetProxyPrivate(*proxy, &mut slot) };
938 rooted!(in(cx) let target = slot.to_object());
939 let script_window_proxies = ScriptThread::window_proxies();
940 if let Ok(win) = root_from_handleobject::<Window>(target.handle(), cx) {
941 let browsing_context_id = win.window_proxy().browsing_context_id();
942 let (result_sender, result_receiver) = ipc::channel().unwrap();
943
944 let _ = win.as_global_scope().script_to_constellation_chan().send(
945 ScriptToConstellationMessage::GetChildBrowsingContextId(
946 browsing_context_id,
947 index as usize,
948 result_sender,
949 ),
950 );
951 return result_receiver
952 .recv()
953 .ok()
954 .and_then(|maybe_bcid| maybe_bcid)
955 .and_then(|id| script_window_proxies.find_window_proxy(id))
956 .map(|proxy| (proxy, (JSPROP_ENUMERATE | JSPROP_READONLY) as u32));
957 } else if let Ok(win) =
958 root_from_handleobject::<DissimilarOriginWindow>(target.handle(), cx)
959 {
960 let browsing_context_id = win.window_proxy().browsing_context_id();
961 let (result_sender, result_receiver) = ipc::channel().unwrap();
962
963 let _ = win.global().script_to_constellation_chan().send(
964 ScriptToConstellationMessage::GetChildBrowsingContextId(
965 browsing_context_id,
966 index as usize,
967 result_sender,
968 ),
969 );
970 return result_receiver
971 .recv()
972 .ok()
973 .and_then(|maybe_bcid| maybe_bcid)
974 .and_then(|id| script_window_proxies.find_window_proxy(id))
975 .map(|proxy| (proxy, JSPROP_READONLY as u32));
976 }
977 }
978
979 None
980}
981
982#[expect(unsafe_code)]
983unsafe extern "C" fn get_own_property_descriptor(
984 cx: *mut JSContext,
985 proxy: RawHandleObject,
986 id: RawHandleId,
987 desc: RawMutableHandle<PropertyDescriptor>,
988 is_none: *mut bool,
989) -> bool {
990 let window = unsafe { GetSubframeWindowProxy(cx, proxy, id) };
991 if let Some((window, attrs)) = window {
992 rooted!(in(cx) let mut val = UndefinedValue());
993 unsafe { window.to_jsval(cx, val.handle_mut()) };
994 set_property_descriptor(
995 unsafe { MutableHandle::from_raw(desc) },
996 val.handle(),
997 attrs,
998 unsafe { &mut *is_none },
999 );
1000 return true;
1001 }
1002
1003 let mut slot = UndefinedValue();
1004 unsafe { GetProxyPrivate(proxy.get(), &mut slot) };
1005 rooted!(in(cx) let target = slot.to_object());
1006 unsafe { JS_GetOwnPropertyDescriptorById(cx, target.handle().into(), id, desc, is_none) }
1007}
1008
1009#[expect(unsafe_code)]
1010unsafe extern "C" fn define_property(
1011 cx: *mut JSContext,
1012 proxy: RawHandleObject,
1013 id: RawHandleId,
1014 desc: RawHandle<PropertyDescriptor>,
1015 res: *mut ObjectOpResult,
1016) -> bool {
1017 if get_array_index_from_id(unsafe { Handle::from_raw(id) }).is_some() {
1018 unsafe {
1023 (*res).code_ = JSErrNum::JSMSG_CANT_DEFINE_WINDOW_ELEMENT as ::libc::uintptr_t;
1024 }
1025 return true;
1026 }
1027
1028 let mut slot = UndefinedValue();
1029 unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1030 rooted!(in(cx) let target = slot.to_object());
1031 unsafe { JS_DefinePropertyById(cx, target.handle().into(), id, desc, res) }
1032}
1033
1034#[expect(unsafe_code)]
1035unsafe extern "C" fn has(
1036 cx: *mut JSContext,
1037 proxy: RawHandleObject,
1038 id: RawHandleId,
1039 bp: *mut bool,
1040) -> bool {
1041 let window = unsafe { GetSubframeWindowProxy(cx, proxy, id) };
1042 if window.is_some() {
1043 unsafe { *bp = true };
1044 return true;
1045 }
1046
1047 let mut slot = UndefinedValue();
1048 unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1049 rooted!(in(cx) let target = slot.to_object());
1050 let mut found = false;
1051 if !unsafe { JS_HasPropertyById(cx, target.handle().into(), id, &mut found) } {
1052 return false;
1053 }
1054
1055 unsafe { *bp = found };
1056 true
1057}
1058
1059#[expect(unsafe_code)]
1060unsafe extern "C" fn get(
1061 cx: *mut JSContext,
1062 proxy: RawHandleObject,
1063 receiver: RawHandleValue,
1064 id: RawHandleId,
1065 vp: RawMutableHandleValue,
1066) -> bool {
1067 let window = unsafe { GetSubframeWindowProxy(cx, proxy, id) };
1068 if let Some((window, _attrs)) = window {
1069 unsafe { window.to_jsval(cx, MutableHandle::from_raw(vp)) };
1070 return true;
1071 }
1072
1073 let mut slot = UndefinedValue();
1074 unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1075 rooted!(in(cx) let target = slot.to_object());
1076 unsafe { JS_ForwardGetPropertyTo(cx, target.handle().into(), id, receiver, vp) }
1077}
1078
1079#[expect(unsafe_code)]
1080unsafe extern "C" fn set(
1081 cx: *mut JSContext,
1082 proxy: RawHandleObject,
1083 id: RawHandleId,
1084 v: RawHandleValue,
1085 receiver: RawHandleValue,
1086 res: *mut ObjectOpResult,
1087) -> bool {
1088 if get_array_index_from_id(unsafe { Handle::from_raw(id) }).is_some() {
1089 unsafe { (*res).code_ = JSErrNum::JSMSG_READ_ONLY as ::libc::uintptr_t };
1091 return true;
1092 }
1093
1094 let mut slot = UndefinedValue();
1095 unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1096 rooted!(in(cx) let target = slot.to_object());
1097 unsafe { JS_ForwardSetPropertyTo(cx, target.handle().into(), id, v, receiver, res) }
1098}
1099
1100#[expect(unsafe_code)]
1101unsafe extern "C" fn get_prototype_if_ordinary(
1102 _: *mut JSContext,
1103 _: RawHandleObject,
1104 is_ordinary: *mut bool,
1105 _: RawMutableHandleObject,
1106) -> bool {
1107 unsafe { *is_ordinary = false };
1120 true
1121}
1122
1123static PROXY_TRAPS: ProxyTraps = ProxyTraps {
1124 enter: None,
1127 getOwnPropertyDescriptor: Some(get_own_property_descriptor),
1128 defineProperty: Some(define_property),
1129 ownPropertyKeys: None,
1130 delete_: None,
1131 enumerate: None,
1132 getPrototypeIfOrdinary: Some(get_prototype_if_ordinary),
1133 getPrototype: None, setPrototype: None,
1135 setImmutablePrototype: None,
1136 preventExtensions: None,
1137 isExtensible: None,
1138 has: Some(has),
1139 get: Some(get),
1140 set: Some(set),
1141 call: None,
1142 construct: None,
1143 hasOwn: None,
1144 getOwnEnumerablePropertyKeys: None,
1145 nativeCall: None,
1146 objectClassIs: None,
1147 className: None,
1148 fun_toString: None,
1149 boxedValue_unbox: None,
1150 defaultValue: None,
1151 trace: Some(trace),
1152 finalize: Some(finalize),
1153 objectMoved: None,
1154 isCallable: None,
1155 isConstructor: None,
1156};
1157
1158pub(crate) struct WindowProxyHandler(*const libc::c_void);
1161
1162impl MallocSizeOf for WindowProxyHandler {
1163 fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize {
1164 0
1166 }
1167}
1168
1169#[expect(unsafe_code)]
1171unsafe impl Send for WindowProxyHandler {}
1172#[expect(unsafe_code)]
1174unsafe impl Sync for WindowProxyHandler {}
1175
1176#[expect(unsafe_code)]
1177impl WindowProxyHandler {
1178 fn new(traps: &ProxyTraps) -> Self {
1179 let ptr = unsafe { CreateWrapperProxyHandler(traps) };
1181 assert!(!ptr.is_null());
1182 Self(ptr)
1183 }
1184
1185 pub(crate) fn x_origin_proxy_handler() -> &'static Self {
1187 use std::sync::OnceLock;
1188 static SINGLETON: OnceLock<WindowProxyHandler> = OnceLock::new();
1194 SINGLETON.get_or_init(|| Self::new(&XORIGIN_PROXY_TRAPS))
1195 }
1196
1197 pub(crate) fn proxy_handler() -> &'static Self {
1199 use std::sync::OnceLock;
1200 static SINGLETON: OnceLock<WindowProxyHandler> = OnceLock::new();
1206 SINGLETON.get_or_init(|| Self::new(&PROXY_TRAPS))
1207 }
1208
1209 pub(crate) fn new_window_proxy(
1212 &self,
1213 cx: &crate::script_runtime::JSContext,
1214 window_jsobject: js::gc::HandleObject,
1215 ) -> *mut JSObject {
1216 let obj = unsafe { NewWindowProxy(**cx, window_jsobject, self.0) };
1217 assert!(!obj.is_null());
1218 obj
1219 }
1220}
1221
1222#[expect(unsafe_code)]
1223impl Drop for WindowProxyHandler {
1224 fn drop(&mut self) {
1225 unsafe {
1228 DeleteWrapperProxyHandler(self.0);
1229 }
1230 }
1231}
1232
1233#[expect(unsafe_code)]
1241fn throw_security_error(cx: SafeJSContext, realm: InRealm) -> bool {
1242 if !unsafe { JS_IsExceptionPending(*cx) } {
1243 let global = unsafe { GlobalScope::from_context(*cx, realm) };
1244 throw_dom_exception(cx, &global, Error::Security(None), CanGc::note());
1245 }
1246 false
1247}
1248
1249#[expect(unsafe_code)]
1250unsafe extern "C" fn has_xorigin(
1251 cx: *mut JSContext,
1252 proxy: RawHandleObject,
1253 id: RawHandleId,
1254 bp: *mut bool,
1255) -> bool {
1256 let mut slot = UndefinedValue();
1257 unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1258 rooted!(in(cx) let target = slot.to_object());
1259 let mut found = false;
1260 unsafe { JS_HasOwnPropertyById(cx, target.handle().into(), id, &mut found) };
1261 if found {
1262 unsafe { *bp = true };
1263 true
1264 } else {
1265 let cx = unsafe { SafeJSContext::from_ptr(cx) };
1266 let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
1267 throw_security_error(cx, InRealm::Already(&in_realm_proof))
1268 }
1269}
1270
1271#[expect(unsafe_code)]
1272unsafe extern "C" fn get_xorigin(
1273 cx: *mut JSContext,
1274 proxy: RawHandleObject,
1275 receiver: RawHandleValue,
1276 id: RawHandleId,
1277 vp: RawMutableHandleValue,
1278) -> bool {
1279 let mut found = false;
1280 unsafe { has_xorigin(cx, proxy, id, &mut found) };
1281 found && unsafe { get(cx, proxy, receiver, id, vp) }
1282}
1283
1284#[expect(unsafe_code)]
1285unsafe extern "C" fn set_xorigin(
1286 cx: *mut JSContext,
1287 _: RawHandleObject,
1288 _: RawHandleId,
1289 _: RawHandleValue,
1290 _: RawHandleValue,
1291 _: *mut ObjectOpResult,
1292) -> bool {
1293 let cx = unsafe { SafeJSContext::from_ptr(cx) };
1294 let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
1295 throw_security_error(cx, InRealm::Already(&in_realm_proof))
1296}
1297
1298#[expect(unsafe_code)]
1299unsafe extern "C" fn delete_xorigin(
1300 cx: *mut JSContext,
1301 _: RawHandleObject,
1302 _: RawHandleId,
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)]
1311#[expect(non_snake_case)]
1312unsafe extern "C" fn getOwnPropertyDescriptor_xorigin(
1313 cx: *mut JSContext,
1314 proxy: RawHandleObject,
1315 id: RawHandleId,
1316 desc: RawMutableHandle<PropertyDescriptor>,
1317 is_none: *mut bool,
1318) -> bool {
1319 let mut found = false;
1320 unsafe { has_xorigin(cx, proxy, id, &mut found) };
1321 found && unsafe { get_own_property_descriptor(cx, proxy, id, desc, is_none) }
1322}
1323
1324#[expect(unsafe_code)]
1325#[expect(non_snake_case)]
1326unsafe extern "C" fn defineProperty_xorigin(
1327 cx: *mut JSContext,
1328 _: RawHandleObject,
1329 _: RawHandleId,
1330 _: RawHandle<PropertyDescriptor>,
1331 _: *mut ObjectOpResult,
1332) -> bool {
1333 let cx = unsafe { SafeJSContext::from_ptr(cx) };
1334 let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
1335 throw_security_error(cx, InRealm::Already(&in_realm_proof))
1336}
1337
1338#[expect(unsafe_code)]
1339#[expect(non_snake_case)]
1340unsafe extern "C" fn preventExtensions_xorigin(
1341 cx: *mut JSContext,
1342 _: RawHandleObject,
1343 _: *mut ObjectOpResult,
1344) -> bool {
1345 let cx = unsafe { SafeJSContext::from_ptr(cx) };
1346 let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
1347 throw_security_error(cx, InRealm::Already(&in_realm_proof))
1348}
1349
1350static XORIGIN_PROXY_TRAPS: ProxyTraps = ProxyTraps {
1351 enter: None,
1352 getOwnPropertyDescriptor: Some(getOwnPropertyDescriptor_xorigin),
1353 defineProperty: Some(defineProperty_xorigin),
1354 ownPropertyKeys: None,
1355 delete_: Some(delete_xorigin),
1356 enumerate: None,
1357 getPrototypeIfOrdinary: None,
1358 getPrototype: None,
1359 setPrototype: None,
1360 setImmutablePrototype: None,
1361 preventExtensions: Some(preventExtensions_xorigin),
1362 isExtensible: None,
1363 has: Some(has_xorigin),
1364 get: Some(get_xorigin),
1365 set: Some(set_xorigin),
1366 call: None,
1367 construct: None,
1368 hasOwn: Some(has_xorigin),
1369 getOwnEnumerablePropertyKeys: None,
1370 nativeCall: None,
1371 objectClassIs: None,
1372 className: None,
1373 fun_toString: None,
1374 boxedValue_unbox: None,
1375 defaultValue: None,
1376 trace: Some(trace),
1377 finalize: Some(finalize),
1378 objectMoved: None,
1379 isCallable: None,
1380 isConstructor: None,
1381};
1382
1383#[expect(unsafe_code)]
1386unsafe extern "C" fn finalize(_fop: *mut GCContext, obj: *mut JSObject) {
1387 let mut slot = UndefinedValue();
1388 unsafe { GetProxyReservedSlot(obj, 0, &mut slot) };
1389 let this = slot.to_private() as *mut WindowProxy;
1390 if this.is_null() {
1391 return;
1393 }
1394 let jsobject = unsafe { (*this).reflector.get_jsobject().get() };
1395 debug!(
1396 "WindowProxy finalize: {:p}, with reflector {:p} from {:p}.",
1397 this, jsobject, obj
1398 );
1399 let _ = unsafe { Box::from_raw(this) };
1400}
1401
1402#[expect(unsafe_code)]
1403unsafe extern "C" fn trace(trc: *mut JSTracer, obj: *mut JSObject) {
1404 let mut slot = UndefinedValue();
1405 unsafe { GetProxyReservedSlot(obj, 0, &mut slot) };
1406 let this = slot.to_private() as *const WindowProxy;
1407 if this.is_null() {
1408 return;
1410 }
1411 unsafe { (*this).trace(trc) };
1412}