Skip to main content

script/dom/window/
windowproxy.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::cell::Cell;
6use std::ptr::{self, NonNull};
7use std::rc::Rc;
8
9use content_security_policy::sandboxing_directive::SandboxingFlagSet;
10use dom_struct::dom_struct;
11use html5ever::local_name;
12use indexmap::map::IndexMap;
13use js::JSCLASS_IS_GLOBAL;
14use js::context::JSContext;
15use js::glue::{
16    CreateWrapperProxyHandler, DeleteWrapperProxyHandler, GetProxyPrivate, GetProxyReservedSlot,
17    ProxyTraps, SetProxyReservedSlot,
18};
19use js::jsapi::{
20    GCContext, Handle as RawHandle, HandleId as RawHandleId, HandleObject as RawHandleObject,
21    HandleValue as RawHandleValue, JS_DefinePropertyById, JS_ForwardSetPropertyTo,
22    JSContext as RawJSContext, JSErrNum, JSObject, JSPROP_ENUMERATE, JSPROP_READONLY, JSTracer,
23    MutableHandle as RawMutableHandle, MutableHandleObject as RawMutableHandleObject,
24    MutableHandleValue as RawMutableHandleValue, ObjectOpResult, PropertyDescriptor,
25};
26use js::jsval::{NullValue, PrivateValue, UndefinedValue};
27use js::realm::{AutoRealm, CurrentRealm};
28use js::rust::wrappers2::{
29    JS_ForwardGetPropertyTo, JS_GetOwnPropertyDescriptorById, JS_HasOwnPropertyById,
30    JS_HasPropertyById, JS_IsExceptionPending, JS_TransplantObject, NewWindowProxy, SetWindowProxy,
31};
32use js::rust::{Handle, MutableHandle, MutableHandleValue, get_object_class};
33use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
34use net_traits::ReferrerPolicy;
35use net_traits::request::Referrer;
36use script_bindings::cell::DomRefCell;
37use script_bindings::proxyhandler::set_property_descriptor;
38use script_bindings::reflector::{DomObject, MutDomObject, Reflector};
39use script_traits::NewPipelineInfo;
40use serde::{Deserialize, Serialize};
41use servo_base::generic_channel;
42use servo_base::generic_channel::GenericSend;
43use servo_base::id::{BrowsingContextId, PipelineId, WebViewId};
44use servo_constellation_traits::{
45    AuxiliaryWebViewCreationRequest, LoadData, LoadOrigin, NavigationHistoryBehavior,
46    ScriptToConstellationMessage, TargetSnapshotParams,
47};
48use servo_url::{ImmutableOrigin, ServoUrl};
49use storage_traits::webstorage_thread::WebStorageThreadMsg;
50use style::attr::parse_integer;
51
52use crate::dom::bindings::conversions::{ToJSValConvertible, root_from_handleobject};
53use crate::dom::bindings::error::{Error, Fallible, throw_dom_exception};
54use crate::dom::bindings::inheritance::Castable;
55use crate::dom::bindings::reflector::DomGlobal;
56use crate::dom::bindings::root::{Dom, DomRoot};
57use crate::dom::bindings::str::{DOMString, USVString};
58use crate::dom::bindings::trace::JSTraceable;
59use crate::dom::bindings::utils::get_array_index_from_id;
60use crate::dom::dissimilaroriginwindow::DissimilarOriginWindow;
61use crate::dom::document::Document;
62use crate::dom::element::Element;
63use crate::dom::globalscope::GlobalScope;
64use crate::dom::window::Window;
65use crate::navigation::navigate;
66use crate::script_thread::{ScriptThread, with_script_thread};
67use crate::script_window_proxies::ScriptWindowProxies;
68
69#[dom_struct]
70// NOTE: the browsing context for a window is managed in two places:
71// here, in script, but also in the constellation. The constellation
72// manages the session history, which in script is accessed through
73// History objects, messaging the constellation.
74pub(crate) struct WindowProxy {
75    /// The JS WindowProxy object.
76    /// Unlike other reflectors, we mutate this field because
77    /// we have to brain-transplant the reflector when the WindowProxy
78    /// changes Window.
79    reflector: Reflector,
80
81    /// The id of the browsing context.
82    /// In the case that this is a nested browsing context, this is the id
83    /// of the container.
84    #[no_trace]
85    browsing_context_id: BrowsingContextId,
86
87    // https://html.spec.whatwg.org/multipage/#opener-browsing-context
88    #[no_trace]
89    opener: Option<BrowsingContextId>,
90
91    /// The frame id of the top-level ancestor browsing context.
92    /// In the case that this is a top-level window, this is our id.
93    #[no_trace]
94    webview_id: WebViewId,
95
96    /// The name of the browsing context (sometimes, but not always,
97    /// equal to the name of a container element)
98    name: DomRefCell<DOMString>,
99    /// The pipeline id of the currently active document.
100    /// May be None, when the currently active document is in another script thread.
101    /// We do not try to keep the pipeline id for documents in other threads,
102    /// as this would require the constellation notifying many script threads about
103    /// the change, which could be expensive.
104    #[no_trace]
105    currently_active: Cell<Option<PipelineId>>,
106
107    /// Has the browsing context been discarded?
108    discarded: Cell<bool>,
109
110    /// Has the browsing context been disowned?
111    disowned: Cell<bool>,
112
113    /// <https://html.spec.whatwg.org/multipage/#is-closing>
114    is_closing: Cell<bool>,
115
116    /// If the containing `<iframe>` of this [`WindowProxy`] is from a same-origin page,
117    /// this will be the [`Element`] of the `<iframe>` element in the realm of the
118    /// parent page. Otherwise, it is `None`.
119    frame_element: Option<Dom<Element>>,
120
121    /// The parent browsing context's window proxy, if this is a nested browsing context
122    parent: Option<Dom<WindowProxy>>,
123
124    /// <https://html.spec.whatwg.org/multipage/#delaying-load-events-mode>
125    delaying_load_events_mode: Cell<bool>,
126
127    /// The creator browsing context's url.
128    #[no_trace]
129    creator_url: Option<ServoUrl>,
130
131    /// The creator browsing context's origin.
132    #[no_trace]
133    creator_origin: Option<ImmutableOrigin>,
134
135    /// The window proxies the script thread knows.
136    #[conditional_malloc_size_of]
137    script_window_proxies: Rc<ScriptWindowProxies>,
138}
139
140impl WindowProxy {
141    fn new_inherited(
142        browsing_context_id: BrowsingContextId,
143        webview_id: WebViewId,
144        currently_active: Option<PipelineId>,
145        frame_element: Option<&Element>,
146        parent: Option<&WindowProxy>,
147        opener: Option<BrowsingContextId>,
148        creator: CreatorBrowsingContextInfo,
149    ) -> WindowProxy {
150        let name = frame_element.map_or(DOMString::new(), |e| {
151            e.get_string_attribute(&local_name!("name"))
152        });
153        WindowProxy {
154            reflector: Reflector::new(),
155            browsing_context_id,
156            webview_id,
157            name: DomRefCell::new(name),
158            currently_active: Cell::new(currently_active),
159            discarded: Cell::new(false),
160            disowned: Cell::new(false),
161            is_closing: Cell::new(false),
162            frame_element: frame_element.map(Dom::from_ref),
163            parent: parent.map(Dom::from_ref),
164            delaying_load_events_mode: Cell::new(false),
165            opener,
166            creator_url: creator.url,
167            creator_origin: creator.origin,
168            script_window_proxies: ScriptThread::window_proxies(),
169        }
170    }
171
172    #[expect(unsafe_code)]
173    #[expect(clippy::too_many_arguments)]
174    pub(crate) fn new(
175        cx: &mut JSContext,
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 window_jsobject = window.reflector().get_jsobject();
188            assert!(!window_jsobject.get().is_null());
189            assert_ne!(
190                ((*get_object_class(window_jsobject.get())).flags & JSCLASS_IS_GLOBAL),
191                0
192            );
193
194            let mut realm = AutoRealm::new_from_handle(cx, window_jsobject);
195            let cx = &mut realm;
196
197            // Create a new window proxy.
198            rooted!(&in(cx) let js_proxy = handler.new_window_proxy(cx, window_jsobject));
199            assert!(!js_proxy.is_null());
200
201            // Create a new browsing context.
202
203            let current = Some(window.upcast::<GlobalScope>().pipeline_id());
204            let window_proxy = Box::new(WindowProxy::new_inherited(
205                browsing_context_id,
206                webview_id,
207                current,
208                frame_element,
209                parent,
210                opener,
211                creator,
212            ));
213
214            // The window proxy owns the browsing context.
215            // When we finalize the window proxy, it drops the browsing context it owns.
216            SetProxyReservedSlot(
217                js_proxy.get(),
218                0,
219                &PrivateValue(&raw const (*window_proxy) as *const libc::c_void),
220            );
221
222            // Notify the JS engine about the new window proxy binding.
223            SetWindowProxy(cx, window_jsobject, js_proxy.handle());
224
225            // Set the reflector.
226            debug!(
227                "Initializing reflector of {:p} to {:p}.",
228                window_proxy,
229                js_proxy.get()
230            );
231            window_proxy
232                .reflector
233                .init_reflector::<WindowProxy>(js_proxy.get());
234            DomRoot::from_ref(&*Box::into_raw(window_proxy))
235        }
236    }
237
238    #[expect(unsafe_code)]
239    pub(crate) fn new_dissimilar_origin(
240        cx: &mut JSContext,
241        global_to_clone_from: &GlobalScope,
242        browsing_context_id: BrowsingContextId,
243        webview_id: WebViewId,
244        parent: Option<&WindowProxy>,
245        opener: Option<BrowsingContextId>,
246        creator: CreatorBrowsingContextInfo,
247    ) -> DomRoot<WindowProxy> {
248        unsafe {
249            let handler = WindowProxyHandler::x_origin_proxy_handler();
250
251            // Create a new browsing context.
252            let window_proxy = Box::new(WindowProxy::new_inherited(
253                browsing_context_id,
254                webview_id,
255                None,
256                None,
257                parent,
258                opener,
259                creator,
260            ));
261
262            // Create a new dissimilar-origin window.
263            let window = DissimilarOriginWindow::new(cx, global_to_clone_from, &window_proxy);
264            let window_jsobject = window.reflector().get_jsobject();
265            assert!(!window_jsobject.get().is_null());
266            assert_ne!(
267                ((*get_object_class(window_jsobject.get())).flags & JSCLASS_IS_GLOBAL),
268                0
269            );
270
271            let mut realm = AutoRealm::new_from_handle(cx, window_jsobject);
272            let cx = &mut realm;
273
274            // Create a new window proxy.
275            rooted!(&in(cx) let js_proxy = handler.new_window_proxy(cx, window_jsobject));
276            assert!(!js_proxy.is_null());
277
278            // The window proxy owns the browsing context.
279            // When we finalize the window proxy, it drops the browsing context it owns.
280            SetProxyReservedSlot(
281                js_proxy.get(),
282                0,
283                &PrivateValue(&raw const (*window_proxy) as *const libc::c_void),
284            );
285
286            // Notify the JS engine about the new window proxy binding.
287            SetWindowProxy(cx, window_jsobject, js_proxy.handle());
288
289            // Set the reflector.
290            debug!(
291                "Initializing reflector of {:p} to {:p}.",
292                window_proxy,
293                js_proxy.get()
294            );
295            window_proxy
296                .reflector
297                .init_reflector::<WindowProxy>(js_proxy.get());
298            DomRoot::from_ref(&*Box::into_raw(window_proxy))
299        }
300    }
301
302    /// <https://html.spec.whatwg.org/multipage/#auxiliary-browsing-context>
303    fn create_auxiliary_browsing_context(
304        &self,
305        cx: &mut JSContext,
306        name: DOMString,
307        noopener: bool,
308    ) -> Option<DomRoot<WindowProxy>> {
309        let (response_sender, response_receiver) = generic_channel::channel().unwrap();
310        let window = self
311            .currently_active
312            .get()
313            .and_then(ScriptThread::find_document)
314            .map(|doc| DomRoot::from_ref(doc.window()))
315            .unwrap();
316
317        let document = self
318            .currently_active
319            .get()
320            .and_then(ScriptThread::find_document)
321            .expect("A WindowProxy creating an auxiliary to have an active document");
322
323        // <https://html.spec.whatwg.org/multipage/#navigable-target-names>
324        // > If the user agent has been configured such that in this instance it
325        // > will create a new top-level traversable
326        // >
327        // Step 9. If sandboxingFlagSet's sandbox propagates to auxiliary browsing
328        //   contexts flag is set, then all the flags that are set in sandboxingFlagSet
329        // must be set in chosen's active browsing context's popup sandboxing flag set.
330        let sandboxing_flag_set = document.active_sandboxing_flag_set();
331        let propagate_sandbox = sandboxing_flag_set
332            .contains(SandboxingFlagSet::SANDBOX_PROPOGATES_TO_AUXILIARY_BROWSING_CONTEXTS_FLAG);
333        let sandboxing_flag_set = if propagate_sandbox {
334            sandboxing_flag_set
335        } else {
336            SandboxingFlagSet::empty()
337        };
338
339        let blank_url = ServoUrl::parse("about:blank").ok().unwrap();
340        let load_data = LoadData::new(
341            LoadOrigin::Script(document.origin().snapshot()),
342            blank_url,
343            Some(document.base_url()),
344            // This has the effect of ensuring that the new `about:blank` URL has the
345            // same origin as the `Document` that is creating the new browsing context.
346            Some(window.pipeline_id()),
347            document.global().get_referrer(),
348            document.get_referrer_policy(),
349            None, // Doesn't inherit secure context
350            None,
351            false,
352            sandboxing_flag_set,
353        );
354        let load_info = AuxiliaryWebViewCreationRequest {
355            load_data: load_data.clone(),
356            opener_webview_id: window.webview_id(),
357            opener_pipeline_id: self.currently_active.get().unwrap(),
358            response_sender,
359        };
360        let constellation_msg = ScriptToConstellationMessage::CreateAuxiliaryWebView(load_info);
361        window.send_to_constellation(constellation_msg);
362
363        let response = response_receiver.recv().unwrap()?;
364        let new_browsing_context_id = BrowsingContextId::from(response.new_webview_id);
365        let new_pipeline_info = NewPipelineInfo {
366            parent_info: None,
367            new_pipeline_id: response.new_pipeline_id,
368            browsing_context_id: new_browsing_context_id,
369            webview_id: response.new_webview_id,
370            opener: Some(self.browsing_context_id),
371            load_data,
372            viewport_details: window.viewport_details(),
373            user_content_manager_id: response.user_content_manager_id,
374            // Use the current `WebView`'s theme initially, but the embedder may
375            // change this later.
376            theme: window.theme(),
377            target_snapshot_params: TargetSnapshotParams {
378                sandboxing_flags: sandboxing_flag_set,
379                iframe_element_referrer_policy: ReferrerPolicy::EmptyString,
380            },
381        };
382
383        with_script_thread(|script_thread| {
384            script_thread.spawn_pipeline(cx, new_pipeline_info);
385        });
386
387        let new_window_proxy = ScriptThread::find_document(response.new_pipeline_id)
388            .and_then(|doc| doc.browsing_context())?;
389        if name.to_lowercase() != "_blank" {
390            new_window_proxy.set_name(name);
391        }
392        if noopener {
393            new_window_proxy.disown();
394        } else {
395            // After creating a new auxiliary browsing context and document,
396            // the session storage is copied over.
397            // See https://html.spec.whatwg.org/multipage/#the-sessionstorage-attribute
398
399            let (sender, receiver) = generic_channel::channel().unwrap();
400
401            let msg = WebStorageThreadMsg::Clone {
402                sender,
403                src: window.window_proxy().webview_id(),
404                dest: response.new_webview_id,
405            };
406
407            GenericSend::send(document.global().storage_threads(), msg).unwrap();
408            receiver.recv().unwrap();
409        }
410        Some(new_window_proxy)
411    }
412
413    /// <https://html.spec.whatwg.org/multipage/#delaying-load-events-mode>
414    pub(crate) fn is_delaying_load_events_mode(&self) -> bool {
415        self.delaying_load_events_mode.get()
416    }
417
418    /// <https://html.spec.whatwg.org/multipage/#delaying-load-events-mode>
419    pub(crate) fn start_delaying_load_events_mode(&self) {
420        self.delaying_load_events_mode.set(true);
421    }
422
423    /// <https://html.spec.whatwg.org/multipage/#delaying-load-events-mode>
424    pub(crate) fn stop_delaying_load_events_mode(&self) {
425        self.delaying_load_events_mode.set(false);
426        if let Some(document) = self.document() &&
427            !document.loader().events_inhibited()
428        {
429            ScriptThread::mark_document_with_no_blocked_loads(&document);
430        }
431    }
432
433    // https://html.spec.whatwg.org/multipage/#disowned-its-opener
434    pub(crate) fn disown(&self) {
435        self.disowned.set(true);
436    }
437
438    /// <https://html.spec.whatwg.org/multipage/#dom-window-close>
439    /// Step 3.1, set BCs `is_closing` to true.
440    pub(crate) fn close(&self) {
441        self.is_closing.set(true);
442    }
443
444    /// <https://html.spec.whatwg.org/multipage/#is-closing>
445    pub(crate) fn is_closing(&self) -> bool {
446        self.is_closing.get()
447    }
448
449    // https://html.spec.whatwg.org/multipage/#dom-opener
450    pub(crate) fn opener(&self, cx: &mut CurrentRealm, mut retval: MutableHandleValue) {
451        if self.disowned.get() {
452            return retval.set(NullValue());
453        }
454        let opener_id = match self.opener {
455            Some(opener_browsing_context_id) => opener_browsing_context_id,
456            None => return retval.set(NullValue()),
457        };
458        let parent_browsing_context = self.parent.as_deref();
459        let opener_proxy = match self.script_window_proxies.find_window_proxy(opener_id) {
460            Some(window_proxy) => window_proxy,
461            None => {
462                let sender_pipeline_id = self.currently_active().unwrap();
463                match ScriptThread::get_top_level_for_browsing_context(
464                    self.webview_id(),
465                    sender_pipeline_id,
466                    opener_id,
467                ) {
468                    Some(opener_top_id) => {
469                        let global_to_clone_from = GlobalScope::from_current_realm(cx);
470                        let creator =
471                            CreatorBrowsingContextInfo::from(parent_browsing_context, None);
472                        WindowProxy::new_dissimilar_origin(
473                            cx,
474                            &global_to_clone_from,
475                            opener_id,
476                            opener_top_id,
477                            None,
478                            None,
479                            creator,
480                        )
481                    },
482                    None => return retval.set(NullValue()),
483                }
484            },
485        };
486        if opener_proxy.is_browsing_context_discarded() {
487            return retval.set(NullValue());
488        }
489        opener_proxy.safe_to_jsval(cx, retval);
490    }
491
492    // https://html.spec.whatwg.org/multipage/#window-open-steps
493    pub(crate) fn open(
494        &self,
495        cx: &mut JSContext,
496        url: USVString,
497        target: DOMString,
498        features: DOMString,
499    ) -> Fallible<Option<DomRoot<WindowProxy>>> {
500        // Note: this does not map to the spec,
501        // but it does prevent a panic at the constellation because the browsing context
502        // has already been discarded.
503        // See issue: #39716 for the original problem,
504        // and https://github.com/whatwg/html/issues/11797 for a discussion at the level of the spec.
505        if self.discarded.get() {
506            return Ok(None);
507        }
508        // Step 5. If target is the empty string, then set target to "_blank".
509        let non_empty_target = if target.is_empty() {
510            DOMString::from("_blank")
511        } else {
512            target
513        };
514        // Step 6. Let tokenizedFeatures be the result of tokenizing features.
515        let tokenized_features = tokenize_open_features(features);
516        // Step 7 - 8.
517        // If tokenizedFeatures["noreferrer"] exists, then set noreferrer to
518        // the result of parsing tokenizedFeatures["noreferrer"] as a boolean feature.
519        let noreferrer = parse_open_feature_boolean(&tokenized_features, "noreferrer");
520
521        // Step 9. Let noopener be the result of getting noopener for window
522        // open with sourceDocument, tokenizedFeatures, and urlRecord.
523        let noopener = if noreferrer {
524            true
525        } else {
526            parse_open_feature_boolean(&tokenized_features, "noopener")
527        };
528        // (TODO) Step 10. Remove tokenizedFeatures["noopener"] and tokenizedFeatures["noreferrer"].
529
530        // (TODO) Step 11. Let referrerPolicy be the empty string.
531        // (TODO) Step 12. If noreferrer is true, then set referrerPolicy to "no-referrer".
532
533        // Step 13 - 14
534        // Let targetNavigable and windowType be the result of applying the rules for
535        // choosing a navigable given target, sourceDocument's node navigable, and noopener.
536        // If targetNavigable is null, then return null.
537        let (chosen, new) = match self.choose_browsing_context(cx, non_empty_target, noopener) {
538            (Some(chosen), new) => (chosen, new),
539            (None, _) => return Ok(None),
540        };
541        // TODO Step 15.2, Set up browsing context features for targetNavigable's
542        // active browsing context given tokenizedFeatures.
543        let target_document = match chosen.document() {
544            Some(target_document) => target_document,
545            None => return Ok(None),
546        };
547        let has_trustworthy_ancestor_origin = if new {
548            target_document.has_trustworthy_ancestor_or_current_origin()
549        } else {
550            false
551        };
552        let target_window = target_document.window();
553        // Step 15.3 and 15.4 will have happened elsewhere,
554        // since we've created a new browsing context and loaded it with about:blank.
555        if !url.is_empty() {
556            let existing_document = self
557                .currently_active
558                .get()
559                .and_then(ScriptThread::find_document)
560                .unwrap();
561            let url = match existing_document.url().join(&url) {
562                Ok(url) => url,
563                Err(_) => return Err(Error::Syntax(None)),
564            };
565            let referrer = if noreferrer {
566                Referrer::NoReferrer
567            } else {
568                target_window.as_global_scope().get_referrer()
569            };
570            // Propagate CSP list and about-base-url from opener to new document
571            let csp_list = existing_document.get_csp_list().clone();
572            target_document.set_csp_list(csp_list);
573
574            // Step 15.5 Otherwise, navigate targetNavigable to urlRecord using sourceDocument,
575            // with referrerPolicy set to referrerPolicy and exceptionsEnabled set to true.
576            // FIXME: referrerPolicy may not be used properly here. exceptionsEnabled not used.
577            let mut load_data = LoadData::new(
578                LoadOrigin::Script(existing_document.origin().snapshot()),
579                url,
580                target_document.about_base_url(),
581                Some(target_window.pipeline_id()),
582                referrer,
583                target_document.get_referrer_policy(),
584                Some(target_window.as_global_scope().is_secure_context()),
585                Some(target_document.insecure_requests_policy()),
586                has_trustworthy_ancestor_origin,
587                target_document.creation_sandboxing_flag_set_considering_parent_iframe(),
588            );
589
590            // Handle javascript: URLs specially to report CSP violations to the source window
591            // https://html.spec.whatwg.org/multipage/#navigate-to-a-javascript:-url
592            if load_data.url.scheme() == "javascript" {
593                let existing_global = existing_document.global();
594
595                // Check CSP and report violations to the source (existing) window
596                if !ScriptThread::can_navigate_to_javascript_url(
597                    cx,
598                    &existing_global,
599                    target_window.as_global_scope(),
600                    &mut load_data,
601                    None,
602                ) {
603                    // CSP blocked the navigation, don't proceed
604                    return Ok(target_document.browsing_context());
605                }
606            }
607
608            let history_handling = if new {
609                NavigationHistoryBehavior::Replace
610            } else {
611                NavigationHistoryBehavior::Push
612            };
613            navigate(cx, target_window, history_handling, false, load_data);
614        }
615        // Step 17 (Dis-owning has been done in create_auxiliary_browsing_context).
616        if noopener {
617            return Ok(None);
618        }
619        // Step 18
620        Ok(target_document.browsing_context())
621    }
622
623    // https://html.spec.whatwg.org/multipage/#the-rules-for-choosing-a-browsing-context-given-a-browsing-context-name
624    pub(crate) fn choose_browsing_context(
625        &self,
626        cx: &mut JSContext,
627        name: DOMString,
628        noopener: bool,
629    ) -> (Option<DomRoot<WindowProxy>>, bool) {
630        match name.to_lowercase().as_ref() {
631            "" | "_self" => {
632                // Step 3.
633                (Some(DomRoot::from_ref(self)), false)
634            },
635            "_parent" => {
636                // Step 4
637                if let Some(parent) = self.parent() {
638                    return (Some(DomRoot::from_ref(parent)), false);
639                }
640                (None, false)
641            },
642            "_top" => {
643                // Step 5
644                (Some(DomRoot::from_ref(self.top())), false)
645            },
646            "_blank" => (
647                self.create_auxiliary_browsing_context(cx, name, noopener),
648                true,
649            ),
650            _ => {
651                // Step 6.
652                // TODO: expand the search to all 'familiar' bc,
653                // including auxiliaries familiar by way of their opener.
654                // See https://html.spec.whatwg.org/multipage/#familiar-with
655                match ScriptThread::find_window_proxy_by_name(&name) {
656                    Some(proxy) => (Some(proxy), false),
657                    None => (
658                        self.create_auxiliary_browsing_context(cx, name, noopener),
659                        true,
660                    ),
661                }
662            },
663        }
664    }
665
666    pub(crate) fn is_auxiliary(&self) -> bool {
667        self.opener.is_some()
668    }
669
670    pub(crate) fn discard_browsing_context(&self) {
671        self.discarded.set(true);
672    }
673
674    pub(crate) fn is_browsing_context_discarded(&self) -> bool {
675        self.discarded.get()
676    }
677
678    pub(crate) fn browsing_context_id(&self) -> BrowsingContextId {
679        self.browsing_context_id
680    }
681
682    pub(crate) fn webview_id(&self) -> WebViewId {
683        self.webview_id
684    }
685
686    /// If the containing `<iframe>` of this [`WindowProxy`] is from a same-origin page,
687    /// this will return an [`Element`] of the `<iframe>` element in the realm of the parent
688    /// page.
689    pub(crate) fn frame_element(&self) -> Option<&Element> {
690        self.frame_element.as_deref()
691    }
692
693    pub(crate) fn document(&self) -> Option<DomRoot<Document>> {
694        self.currently_active
695            .get()
696            .and_then(ScriptThread::find_document)
697    }
698
699    pub(crate) fn parent(&self) -> Option<&WindowProxy> {
700        self.parent.as_deref()
701    }
702
703    pub(crate) fn top(&self) -> &WindowProxy {
704        let mut result = self;
705        while let Some(parent) = result.parent() {
706            result = parent;
707        }
708        result
709    }
710
711    pub fn document_origin(&self) -> Option<String> {
712        let pipeline_id = self.currently_active()?;
713        let (result_sender, result_receiver) = generic_channel::channel().unwrap();
714        self.global()
715            .script_to_constellation_chan()
716            .send(ScriptToConstellationMessage::GetDocumentOrigin(
717                pipeline_id,
718                result_sender,
719            ))
720            .ok()?;
721        result_receiver.recv().ok()?
722    }
723
724    #[expect(unsafe_code)]
725    /// Change the Window that this WindowProxy resolves to.
726    // TODO: support setting the window proxy to a dummy value,
727    // to handle the case when the active document is in another script thread.
728    fn set_window(&self, cx: &mut JSContext, window: &GlobalScope, handler: &WindowProxyHandler) {
729        unsafe {
730            debug!("Setting window of {:p}.", self);
731
732            let window_jsobject = window.reflector().get_jsobject();
733            let old_js_proxy = self.reflector.get_jsobject();
734            assert!(!window_jsobject.get().is_null());
735            assert_ne!(
736                ((*get_object_class(window_jsobject.get())).flags & JSCLASS_IS_GLOBAL),
737                0
738            );
739
740            let mut realm = AutoRealm::new_from_handle(cx, window_jsobject);
741            let cx = &mut realm;
742
743            // The old window proxy no longer owns this browsing context.
744            SetProxyReservedSlot(old_js_proxy.get(), 0, &PrivateValue(ptr::null_mut()));
745
746            // Brain transplant the window proxy. Brain transplantation is
747            // usually done to move a window proxy between compartments, but
748            // that's not what we are doing here. We need to do this just
749            // because we want to replace the wrapper's `ProxyTraps`, but we
750            // don't want to update its identity.
751            rooted!(&in(cx) let new_js_proxy = handler.new_window_proxy(cx, window_jsobject));
752            // Explicitly set this slot to a null pointer in case a GC occurs before we
753            // are ready to set it to a real value.
754            SetProxyReservedSlot(new_js_proxy.get(), 0, &PrivateValue(ptr::null_mut()));
755            debug!(
756                "Transplanting proxy from {:p} to {:p}.",
757                old_js_proxy.get(),
758                new_js_proxy.get()
759            );
760            rooted!(&in(cx) let new_js_proxy = JS_TransplantObject(cx, old_js_proxy, new_js_proxy.handle()));
761            debug!("Transplanted proxy is {:p}.", new_js_proxy.get());
762
763            // Transfer ownership of this browsing context from the old window proxy to the new one.
764            SetProxyReservedSlot(
765                new_js_proxy.get(),
766                0,
767                &PrivateValue(self as *const _ as *const libc::c_void),
768            );
769
770            // Notify the JS engine about the new window proxy binding.
771            SetWindowProxy(cx, window_jsobject, new_js_proxy.handle());
772
773            // Update the reflector.
774            debug!(
775                "Setting reflector of {:p} to {:p}.",
776                self,
777                new_js_proxy.get()
778            );
779            self.reflector.rootable().set(new_js_proxy.get());
780        }
781    }
782
783    pub(crate) fn set_currently_active(&self, cx: &mut JSContext, window: &Window) {
784        if let Some(pipeline_id) = self.currently_active() &&
785            pipeline_id == window.pipeline_id()
786        {
787            return debug!(
788                "Attempt to set the currently active window to the currently active window."
789            );
790        }
791
792        let global_scope = window.as_global_scope();
793        self.set_window(cx, global_scope, WindowProxyHandler::proxy_handler());
794        self.currently_active.set(Some(global_scope.pipeline_id()));
795    }
796
797    pub(crate) fn unset_currently_active(&self, cx: &mut JSContext) {
798        if self.currently_active().is_none() {
799            return debug!(
800                "Attempt to unset the currently active window on a windowproxy that does not have one."
801            );
802        }
803        let globalscope = self.global();
804        let window = DissimilarOriginWindow::new(cx, &globalscope, self);
805        self.set_window(
806            cx,
807            window.upcast(),
808            WindowProxyHandler::x_origin_proxy_handler(),
809        );
810        self.currently_active.set(None);
811    }
812
813    pub(crate) fn currently_active(&self) -> Option<PipelineId> {
814        self.currently_active.get()
815    }
816
817    pub(crate) fn get_name(&self) -> DOMString {
818        self.name.borrow().clone()
819    }
820
821    pub(crate) fn set_name(&self, name: DOMString) {
822        *self.name.borrow_mut() = name;
823    }
824}
825
826/// A browsing context can have a creator browsing context, the browsing context that
827/// was responsible for its creation. If a browsing context has a parent browsing context,
828/// then that is its creator browsing context. Otherwise, if the browsing context has an
829/// opener browsing context, then that is its creator browsing context. Otherwise, the
830/// browsing context has no creator browsing context.
831///
832/// If a browsing context A has a creator browsing context, then the Document that was the
833/// active document of that creator browsing context at the time A was created is the creator
834/// Document.
835///
836/// See: <https://html.spec.whatwg.org/multipage/#creating-browsing-contexts>
837#[derive(Debug, Deserialize, Serialize)]
838pub(crate) struct CreatorBrowsingContextInfo {
839    /// Creator document URL.
840    url: Option<ServoUrl>,
841
842    /// Creator document origin.
843    origin: Option<ImmutableOrigin>,
844}
845
846impl CreatorBrowsingContextInfo {
847    pub(crate) fn from(
848        parent: Option<&WindowProxy>,
849        opener: Option<&WindowProxy>,
850    ) -> CreatorBrowsingContextInfo {
851        let creator = match (parent, opener) {
852            (Some(parent), _) => parent.document(),
853            (None, Some(opener)) => opener.document(),
854            (None, None) => None,
855        };
856
857        let url = creator.as_deref().map(|document| document.url());
858        let origin = creator
859            .as_deref()
860            .map(|document| document.origin().immutable().clone());
861
862        CreatorBrowsingContextInfo { url, origin }
863    }
864}
865
866/// <https://html.spec.whatwg.org/multipage/#concept-window-open-features-tokenize>
867fn tokenize_open_features(features: DOMString) -> IndexMap<String, String> {
868    let is_feature_sep = |c: char| c.is_ascii_whitespace() || ['=', ','].contains(&c);
869    // Step 1
870    let mut tokenized_features = IndexMap::new();
871    // Step 2
872    let features = features.str();
873    let mut iter = features.chars();
874    let mut cur = iter.next();
875
876    // Step 3
877    while cur.is_some() {
878        // Step 3.1 & 3.2
879        let mut name = String::new();
880        let mut value = String::new();
881        // Step 3.3
882        while let Some(cur_char) = cur {
883            if !is_feature_sep(cur_char) {
884                break;
885            }
886            cur = iter.next();
887        }
888        // Step 3.4
889        while let Some(cur_char) = cur {
890            if is_feature_sep(cur_char) {
891                break;
892            }
893            name.push(cur_char.to_ascii_lowercase());
894            cur = iter.next();
895        }
896        // Step 3.5
897        let normalized_name = String::from(match name.as_ref() {
898            "screenx" => "left",
899            "screeny" => "top",
900            "innerwidth" => "width",
901            "innerheight" => "height",
902            _ => name.as_ref(),
903        });
904        // Step 3.6
905        while let Some(cur_char) = cur {
906            if cur_char == '=' || cur_char == ',' || !is_feature_sep(cur_char) {
907                break;
908            }
909            cur = iter.next();
910        }
911        // Step 3.7
912        if cur.is_some() && is_feature_sep(cur.unwrap()) {
913            // Step 3.7.1
914            while let Some(cur_char) = cur {
915                if !is_feature_sep(cur_char) || cur_char == ',' {
916                    break;
917                }
918                cur = iter.next();
919            }
920            // Step 3.7.2
921            while let Some(cur_char) = cur {
922                if is_feature_sep(cur_char) {
923                    break;
924                }
925                value.push(cur_char.to_ascii_lowercase());
926                cur = iter.next();
927            }
928        }
929        // Step 3.8
930        if !name.is_empty() {
931            tokenized_features.insert(normalized_name, value);
932        }
933    }
934    // Step 4
935    tokenized_features
936}
937
938/// <https://html.spec.whatwg.org/multipage/#concept-window-open-features-parse-boolean>
939fn parse_open_feature_boolean(tokenized_features: &IndexMap<String, String>, name: &str) -> bool {
940    if let Some(value) = tokenized_features.get(name) {
941        // Step 1 & 2
942        if value.is_empty() || value == "yes" {
943            return true;
944        }
945        // Step 3 & 4
946        if let Ok(int) = parse_integer(value.chars()) {
947            return int != 0;
948        }
949    }
950    // Step 5
951    false
952}
953
954// This is only called from extern functions,
955// there's no use using the lifetimed handles here.
956// https://html.spec.whatwg.org/multipage/#accessing-other-browsing-contexts
957#[expect(unsafe_code)]
958#[expect(non_snake_case)]
959unsafe fn GetSubframeWindowProxy(
960    cx: &mut JSContext,
961    proxy: RawHandleObject,
962    id: RawHandleId,
963) -> Option<(DomRoot<WindowProxy>, u32)> {
964    let index = get_array_index_from_id(unsafe { Handle::from_raw(id) });
965    if let Some(index) = index {
966        let mut slot = UndefinedValue();
967        unsafe { GetProxyPrivate(*proxy, &mut slot) };
968        rooted!(&in(cx) let target = slot.to_object());
969        let script_window_proxies = ScriptThread::window_proxies();
970        if let Ok(win) = root_from_handleobject::<Window>(cx, target.handle()) {
971            let browsing_context_id = win.window_proxy().browsing_context_id();
972            let (result_sender, result_receiver) = generic_channel::channel().unwrap();
973
974            let _ = win.as_global_scope().script_to_constellation_chan().send(
975                ScriptToConstellationMessage::GetChildBrowsingContextId(
976                    browsing_context_id,
977                    index as usize,
978                    result_sender,
979                ),
980            );
981            return result_receiver
982                .recv()
983                .ok()
984                .and_then(|maybe_bcid| maybe_bcid)
985                .and_then(|id| script_window_proxies.find_window_proxy(id))
986                .map(|proxy| (proxy, (JSPROP_ENUMERATE | JSPROP_READONLY) as u32));
987        } else if let Ok(win) =
988            root_from_handleobject::<DissimilarOriginWindow>(cx, target.handle())
989        {
990            let browsing_context_id = win.window_proxy().browsing_context_id();
991            let (result_sender, result_receiver) = generic_channel::channel().unwrap();
992
993            let _ = win.global().script_to_constellation_chan().send(
994                ScriptToConstellationMessage::GetChildBrowsingContextId(
995                    browsing_context_id,
996                    index as usize,
997                    result_sender,
998                ),
999            );
1000            return result_receiver
1001                .recv()
1002                .ok()
1003                .and_then(|maybe_bcid| maybe_bcid)
1004                .and_then(|id| script_window_proxies.find_window_proxy(id))
1005                .map(|proxy| (proxy, JSPROP_READONLY as u32));
1006        }
1007    }
1008
1009    None
1010}
1011
1012#[expect(unsafe_code)]
1013unsafe extern "C" fn get_own_property_descriptor(
1014    cx: *mut RawJSContext,
1015    proxy: RawHandleObject,
1016    id: RawHandleId,
1017    desc: RawMutableHandle<PropertyDescriptor>,
1018    is_none: *mut bool,
1019) -> bool {
1020    let mut cx = unsafe {
1021        // SAFETY: We are in SM hook
1022        JSContext::from_ptr(NonNull::new(cx).expect("JSContext should not be null in SM hook"))
1023    };
1024    let cx = &mut cx;
1025    let window = unsafe { GetSubframeWindowProxy(cx, proxy, id) };
1026    let desc = unsafe { MutableHandle::from_raw(desc) };
1027    if let Some((window, attrs)) = window {
1028        rooted!(&in(cx) let mut val = UndefinedValue());
1029        window.safe_to_jsval(cx, val.handle_mut());
1030        set_property_descriptor(desc, val.handle(), attrs, unsafe { &mut *is_none });
1031        return true;
1032    }
1033
1034    let mut slot = UndefinedValue();
1035    unsafe { GetProxyPrivate(proxy.get(), &mut slot) };
1036    rooted!(&in(cx) let target = slot.to_object());
1037    unsafe {
1038        JS_GetOwnPropertyDescriptorById(cx, target.handle(), Handle::from_raw(id), desc, is_none)
1039    }
1040}
1041
1042#[expect(unsafe_code)]
1043unsafe extern "C" fn define_property(
1044    cx: *mut RawJSContext,
1045    proxy: RawHandleObject,
1046    id: RawHandleId,
1047    desc: RawHandle<PropertyDescriptor>,
1048    res: *mut ObjectOpResult,
1049) -> bool {
1050    if get_array_index_from_id(unsafe { Handle::from_raw(id) }).is_some() {
1051        // Spec says to Reject whether this is a supported index or not,
1052        // since we have no indexed setter or indexed creator.  That means
1053        // throwing in strict mode (FIXME: Bug 828137), doing nothing in
1054        // non-strict mode.
1055        unsafe {
1056            (*res).code_ = JSErrNum::JSMSG_CANT_DEFINE_WINDOW_ELEMENT as ::libc::uintptr_t;
1057        }
1058        return true;
1059    }
1060
1061    let mut slot = UndefinedValue();
1062    unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1063    rooted!(in(cx) let target = slot.to_object());
1064    unsafe { JS_DefinePropertyById(cx, target.handle().into(), id, desc, res) }
1065}
1066
1067#[expect(unsafe_code)]
1068unsafe extern "C" fn has(
1069    cx: *mut RawJSContext,
1070    proxy: RawHandleObject,
1071    id: RawHandleId,
1072    bp: *mut bool,
1073) -> bool {
1074    let mut cx = unsafe {
1075        // SAFETY: We are in SM hook
1076        JSContext::from_ptr(NonNull::new(cx).expect("JSContext should not be null in SM hook"))
1077    };
1078    let cx = &mut cx;
1079    let window = unsafe { GetSubframeWindowProxy(cx, proxy, id) };
1080    if window.is_some() {
1081        unsafe { *bp = true };
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    let mut found = false;
1089    if !unsafe { JS_HasPropertyById(cx, target.handle(), Handle::from_raw(id), &mut found) } {
1090        return false;
1091    }
1092
1093    unsafe { *bp = found };
1094    true
1095}
1096
1097#[expect(unsafe_code)]
1098unsafe extern "C" fn get(
1099    cx: *mut RawJSContext,
1100    proxy: RawHandleObject,
1101    receiver: RawHandleValue,
1102    id: RawHandleId,
1103    vp: RawMutableHandleValue,
1104) -> bool {
1105    let mut cx = unsafe {
1106        // SAFETY: We are in SM hook
1107        JSContext::from_ptr(NonNull::new(cx).expect("JSContext should not be null in SM hook"))
1108    };
1109    let cx = &mut cx;
1110    let window = unsafe { GetSubframeWindowProxy(cx, proxy, id) };
1111    let vp = unsafe { MutableHandle::from_raw(vp) };
1112    if let Some((window, _attrs)) = window {
1113        window.safe_to_jsval(cx, vp);
1114        return true;
1115    }
1116
1117    let mut slot = UndefinedValue();
1118    unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1119    rooted!(&in(cx) let target = slot.to_object());
1120    unsafe {
1121        JS_ForwardGetPropertyTo(
1122            cx,
1123            target.handle(),
1124            Handle::from_raw(id),
1125            Handle::from_raw(receiver),
1126            vp,
1127        )
1128    }
1129}
1130
1131#[expect(unsafe_code)]
1132unsafe extern "C" fn set(
1133    cx: *mut RawJSContext,
1134    proxy: RawHandleObject,
1135    id: RawHandleId,
1136    v: RawHandleValue,
1137    receiver: RawHandleValue,
1138    res: *mut ObjectOpResult,
1139) -> bool {
1140    if get_array_index_from_id(unsafe { Handle::from_raw(id) }).is_some() {
1141        // Reject (which means throw if and only if strict) the set.
1142        unsafe { (*res).code_ = JSErrNum::JSMSG_READ_ONLY as ::libc::uintptr_t };
1143        return true;
1144    }
1145
1146    let mut slot = UndefinedValue();
1147    unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1148    rooted!(in(cx) let target = slot.to_object());
1149    unsafe { JS_ForwardSetPropertyTo(cx, target.handle().into(), id, v, receiver, res) }
1150}
1151
1152#[expect(unsafe_code)]
1153unsafe extern "C" fn get_prototype_if_ordinary(
1154    _: *mut RawJSContext,
1155    _: RawHandleObject,
1156    is_ordinary: *mut bool,
1157    _: RawMutableHandleObject,
1158) -> bool {
1159    // Window's [[GetPrototypeOf]] trap isn't the ordinary definition:
1160    //
1161    //   https://html.spec.whatwg.org/multipage/#windowproxy-getprototypeof
1162    //
1163    // We nonetheless can implement it with a static [[Prototype]], because
1164    // wrapper-class handlers (particularly, XOW in FilteringWrapper.cpp) supply
1165    // all non-ordinary behavior.
1166    //
1167    // But from a spec point of view, it's the exact same object in both cases --
1168    // only the observer's changed.  So this getPrototypeIfOrdinary trap on the
1169    // non-wrapper object *must* report non-ordinary, even if static [[Prototype]]
1170    // usually means ordinary.
1171    unsafe { *is_ordinary = false };
1172    true
1173}
1174
1175static PROXY_TRAPS: ProxyTraps = ProxyTraps {
1176    // TODO: These traps should change their behavior depending on
1177    //       `IsPlatformObjectSameOrigin(this.[[Window]])`
1178    enter: None,
1179    getOwnPropertyDescriptor: Some(get_own_property_descriptor),
1180    defineProperty: Some(define_property),
1181    ownPropertyKeys: None,
1182    delete_: None,
1183    enumerate: None,
1184    getPrototypeIfOrdinary: Some(get_prototype_if_ordinary),
1185    getPrototype: None, // TODO: return `null` if cross origin-domain
1186    setPrototype: None,
1187    setImmutablePrototype: None,
1188    preventExtensions: None,
1189    isExtensible: None,
1190    has: Some(has),
1191    get: Some(get),
1192    set: Some(set),
1193    call: None,
1194    construct: None,
1195    hasOwn: None,
1196    getOwnEnumerablePropertyKeys: None,
1197    nativeCall: None,
1198    objectClassIs: None,
1199    className: None,
1200    fun_toString: None,
1201    boxedValue_unbox: None,
1202    defaultValue: None,
1203    trace: Some(trace),
1204    finalize: Some(finalize),
1205    objectMoved: None,
1206    isCallable: None,
1207    isConstructor: None,
1208};
1209
1210/// Proxy handler for a WindowProxy.
1211/// Has ownership of the inner pointer and deallocates it when it is no longer needed.
1212pub(crate) struct WindowProxyHandler(*const libc::c_void);
1213
1214impl MallocSizeOf for WindowProxyHandler {
1215    fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize {
1216        // FIXME(#6907) this is a pointer to memory allocated by `new` in NewProxyHandler in rust-mozjs.
1217        0
1218    }
1219}
1220
1221// Safety: Send and Sync is guaranteed since the underlying pointer and all its associated methods in C++ are const.
1222#[expect(unsafe_code)]
1223unsafe impl Send for WindowProxyHandler {}
1224// Safety: Send and Sync is guaranteed since the underlying pointer and all its associated methods in C++ are const.
1225#[expect(unsafe_code)]
1226unsafe impl Sync for WindowProxyHandler {}
1227
1228#[expect(unsafe_code)]
1229impl WindowProxyHandler {
1230    fn new(traps: &ProxyTraps) -> Self {
1231        // Safety: Foreign function generated by bindgen. Pointer is freed in drop to prevent memory leak.
1232        let ptr = unsafe { CreateWrapperProxyHandler(traps) };
1233        assert!(!ptr.is_null());
1234        Self(ptr)
1235    }
1236
1237    /// Returns a single, shared WindowProxyHandler that contains XORIGIN_PROXY_TRAPS.
1238    pub(crate) fn x_origin_proxy_handler() -> &'static Self {
1239        use std::sync::OnceLock;
1240        /// We are sharing a single instance for the entire programs here due to lifetime issues.
1241        /// The pointer in self.0 is known to C++ and visited by the GC. Hence, we don't know when
1242        /// it is safe to free it.
1243        /// Sharing a single instance should be fine because all methods on this pointer in C++
1244        /// are const and don't modify its internal state.
1245        static SINGLETON: OnceLock<WindowProxyHandler> = OnceLock::new();
1246        SINGLETON.get_or_init(|| Self::new(&XORIGIN_PROXY_TRAPS))
1247    }
1248
1249    /// Returns a single, shared WindowProxyHandler that contains normal PROXY_TRAPS.
1250    pub(crate) fn proxy_handler() -> &'static Self {
1251        use std::sync::OnceLock;
1252        /// We are sharing a single instance for the entire programs here due to lifetime issues.
1253        /// The pointer in self.0 is known to C++ and visited by the GC. Hence, we don't know when
1254        /// it is safe to free it.
1255        /// Sharing a single instance should be fine because all methods on this pointer in C++
1256        /// are const and don't modify its internal state.
1257        static SINGLETON: OnceLock<WindowProxyHandler> = OnceLock::new();
1258        SINGLETON.get_or_init(|| Self::new(&PROXY_TRAPS))
1259    }
1260
1261    /// Creates a new WindowProxy object on the C++ side and returns the pointer to it.
1262    /// The pointer should be owned by the GC.
1263    fn new_window_proxy(
1264        &self,
1265        cx: &mut JSContext,
1266        window_jsobject: js::gc::HandleObject,
1267    ) -> *mut JSObject {
1268        let obj = unsafe { NewWindowProxy(cx, window_jsobject, self.0) };
1269        assert!(!obj.is_null());
1270        obj
1271    }
1272}
1273
1274#[expect(unsafe_code)]
1275impl Drop for WindowProxyHandler {
1276    fn drop(&mut self) {
1277        // Safety: Pointer is allocated by corresponding C++ function, owned by this
1278        // struct and not accessible from outside.
1279        unsafe {
1280            DeleteWrapperProxyHandler(self.0);
1281        }
1282    }
1283}
1284
1285// The proxy traps for cross-origin windows.
1286// These traps often throw security errors, and only pass on calls to methods
1287// defined in the DissimilarOriginWindow IDL.
1288
1289// TODO: reuse the infrastructure in `proxyhandler.rs`. For starters, the calls
1290//       to this function should be replaced with those to
1291//       `report_cross_origin_denial`.
1292#[expect(unsafe_code)]
1293fn throw_security_error(realm: &mut CurrentRealm) -> bool {
1294    if !unsafe { JS_IsExceptionPending(realm) } {
1295        let global = GlobalScope::from_current_realm(realm);
1296        throw_dom_exception(realm, &global, Error::Security(None));
1297    }
1298    false
1299}
1300
1301#[expect(unsafe_code)]
1302unsafe extern "C" fn has_xorigin(
1303    cx: *mut RawJSContext,
1304    proxy: RawHandleObject,
1305    id: RawHandleId,
1306    bp: *mut bool,
1307) -> bool {
1308    let mut cx = unsafe {
1309        // SAFETY: We are in SM hook
1310        JSContext::from_ptr(NonNull::new(cx).expect("JSContext should not be null in SM hook"))
1311    };
1312    let mut slot = UndefinedValue();
1313    unsafe { GetProxyPrivate(*proxy.ptr, &mut slot) };
1314    rooted!(&in(cx) let target = slot.to_object());
1315    let mut found = false;
1316    unsafe { JS_HasOwnPropertyById(&mut cx, target.handle(), Handle::from_raw(id), &mut found) };
1317    if found {
1318        unsafe { *bp = true };
1319        true
1320    } else {
1321        let mut realm = CurrentRealm::assert(&mut cx);
1322        throw_security_error(&mut realm)
1323    }
1324}
1325
1326#[expect(unsafe_code)]
1327unsafe extern "C" fn get_xorigin(
1328    cx: *mut RawJSContext,
1329    proxy: RawHandleObject,
1330    receiver: RawHandleValue,
1331    id: RawHandleId,
1332    vp: RawMutableHandleValue,
1333) -> bool {
1334    let mut found = false;
1335    unsafe { has_xorigin(cx, proxy, id, &mut found) };
1336    found && unsafe { get(cx, proxy, receiver, id, vp) }
1337}
1338
1339#[expect(unsafe_code)]
1340unsafe extern "C" fn set_xorigin(
1341    cx: *mut RawJSContext,
1342    _: RawHandleObject,
1343    _: RawHandleId,
1344    _: RawHandleValue,
1345    _: RawHandleValue,
1346    _: *mut ObjectOpResult,
1347) -> bool {
1348    let mut cx = unsafe {
1349        // SAFETY: We are in SM hook
1350        JSContext::from_ptr(NonNull::new(cx).expect("JSContext should not be null in SM hook"))
1351    };
1352    let mut realm = CurrentRealm::assert(&mut cx);
1353    throw_security_error(&mut realm)
1354}
1355
1356#[expect(unsafe_code)]
1357unsafe extern "C" fn delete_xorigin(
1358    cx: *mut RawJSContext,
1359    _: RawHandleObject,
1360    _: RawHandleId,
1361    _: *mut ObjectOpResult,
1362) -> bool {
1363    let mut cx = unsafe {
1364        // SAFETY: We are in SM hook
1365        JSContext::from_ptr(NonNull::new(cx).expect("JSContext should not be null in SM hook"))
1366    };
1367    let mut realm = CurrentRealm::assert(&mut cx);
1368    throw_security_error(&mut realm)
1369}
1370
1371#[expect(unsafe_code)]
1372#[expect(non_snake_case)]
1373unsafe extern "C" fn getOwnPropertyDescriptor_xorigin(
1374    cx: *mut RawJSContext,
1375    proxy: RawHandleObject,
1376    id: RawHandleId,
1377    desc: RawMutableHandle<PropertyDescriptor>,
1378    is_none: *mut bool,
1379) -> bool {
1380    let mut found = false;
1381    unsafe { has_xorigin(cx, proxy, id, &mut found) };
1382    found && unsafe { get_own_property_descriptor(cx, proxy, id, desc, is_none) }
1383}
1384
1385#[expect(unsafe_code)]
1386#[expect(non_snake_case)]
1387unsafe extern "C" fn defineProperty_xorigin(
1388    cx: *mut RawJSContext,
1389    _: RawHandleObject,
1390    _: RawHandleId,
1391    _: RawHandle<PropertyDescriptor>,
1392    _: *mut ObjectOpResult,
1393) -> bool {
1394    let mut cx = unsafe {
1395        // SAFETY: We are in SM hook
1396        JSContext::from_ptr(NonNull::new(cx).expect("JSContext should not be null in SM hook"))
1397    };
1398    let mut realm = CurrentRealm::assert(&mut cx);
1399    throw_security_error(&mut realm)
1400}
1401
1402#[expect(unsafe_code)]
1403#[expect(non_snake_case)]
1404unsafe extern "C" fn preventExtensions_xorigin(
1405    cx: *mut RawJSContext,
1406    _: RawHandleObject,
1407    _: *mut ObjectOpResult,
1408) -> bool {
1409    let mut cx = unsafe {
1410        // SAFETY: We are in SM hook
1411        JSContext::from_ptr(NonNull::new(cx).expect("JSContext should not be null in SM hook"))
1412    };
1413    let mut realm = CurrentRealm::assert(&mut cx);
1414    throw_security_error(&mut realm)
1415}
1416
1417static XORIGIN_PROXY_TRAPS: ProxyTraps = ProxyTraps {
1418    enter: None,
1419    getOwnPropertyDescriptor: Some(getOwnPropertyDescriptor_xorigin),
1420    defineProperty: Some(defineProperty_xorigin),
1421    ownPropertyKeys: None,
1422    delete_: Some(delete_xorigin),
1423    enumerate: None,
1424    getPrototypeIfOrdinary: None,
1425    getPrototype: None,
1426    setPrototype: None,
1427    setImmutablePrototype: None,
1428    preventExtensions: Some(preventExtensions_xorigin),
1429    isExtensible: None,
1430    has: Some(has_xorigin),
1431    get: Some(get_xorigin),
1432    set: Some(set_xorigin),
1433    call: None,
1434    construct: None,
1435    hasOwn: Some(has_xorigin),
1436    getOwnEnumerablePropertyKeys: None,
1437    nativeCall: None,
1438    objectClassIs: None,
1439    className: None,
1440    fun_toString: None,
1441    boxedValue_unbox: None,
1442    defaultValue: None,
1443    trace: Some(trace),
1444    finalize: Some(finalize),
1445    objectMoved: None,
1446    isCallable: None,
1447    isConstructor: None,
1448};
1449
1450// How WindowProxy objects are garbage collected.
1451
1452#[expect(unsafe_code)]
1453unsafe extern "C" fn finalize(_fop: *mut GCContext, obj: *mut JSObject) {
1454    let mut slot = UndefinedValue();
1455    unsafe { GetProxyReservedSlot(obj, 0, &mut slot) };
1456    let this = slot.to_private() as *mut WindowProxy;
1457    if this.is_null() {
1458        // GC during obj creation or after transplanting.
1459        return;
1460    }
1461    unsafe {
1462        (*this).reflector.drop_memory(&*this);
1463        let jsobject = (*this).reflector.get_jsobject().get();
1464        debug!(
1465            "WindowProxy finalize: {:p}, with reflector {:p} from {:p}.",
1466            this, jsobject, obj
1467        );
1468        let _ = Box::from_raw(this);
1469    }
1470}
1471
1472#[expect(unsafe_code)]
1473unsafe extern "C" fn trace(trc: *mut JSTracer, obj: *mut JSObject) {
1474    let mut slot = UndefinedValue();
1475    unsafe { GetProxyReservedSlot(obj, 0, &mut slot) };
1476    let this = slot.to_private() as *const WindowProxy;
1477    if this.is_null() {
1478        // GC during obj creation or after transplanting.
1479        return;
1480    }
1481    unsafe { (*this).trace(trc) };
1482}