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 itertools::Either;
14use js::JSCLASS_IS_GLOBAL;
15use js::context::JSContext;
16use js::gc::{HandleId, HandleObject, HandleValue, MutableHandleObject};
17use js::glue::{
18    CreateWrapperProxyHandler, DeleteWrapperProxyHandler, GetProxyPrivate, GetProxyReservedSlot,
19    ProxyTraps, SetProxyReservedSlot,
20};
21use js::jsapi::{
22    GCContext, Handle as RawHandle, HandleId as RawHandleId, HandleObject as RawHandleObject,
23    HandleValue as RawHandleValue, JS_DefinePropertyById, JS_DeletePropertyById,
24    JS_ForwardSetPropertyTo, JSContext as RawJSContext, JSErrNum, JSITER_HIDDEN, JSITER_OWNONLY,
25    JSITER_SYMBOLS, JSObject, JSPROP_ENUMERATE, JSPROP_READONLY, JSTracer,
26    MutableHandle as RawMutableHandle, MutableHandleIdVector as RawMutableHandleIdVector,
27    MutableHandleObject as RawMutableHandleObject, MutableHandleValue as RawMutableHandleValue,
28    ObjectOpResult, PropertyDescriptor, jsid,
29};
30use js::jsval::{NullValue, PrivateValue, UndefinedValue};
31use js::realm::{AutoRealm, CurrentRealm};
32use js::rust::wrappers2::{
33    AppendToIdVector, GetPropertyKeys, JS_ForwardGetPropertyTo, JS_GetOwnPropertyDescriptorById,
34    JS_HasOwnPropertyById, JS_HasPropertyById, JS_TransplantObject, NewWindowProxy, SetWindowProxy,
35    int_to_jsid,
36};
37use js::rust::{Handle, MutableHandle, MutableHandleValue, get_object_class};
38use js::typedarray::JSObjectStorage;
39use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
40use net_traits::ReferrerPolicy;
41use net_traits::request::Referrer;
42use script_bindings::cell::DomRefCell;
43use script_bindings::codegen::GenericBindings::DissimilarOriginWindowBinding::{
44    self, DissimilarOriginWindowMethods,
45};
46use script_bindings::codegen::GenericBindings::HTMLIFrameElementBinding::HTMLIFrameElementMethods;
47use script_bindings::codegen::GenericBindings::WindowBinding::{
48    self, GetProtoObject, WindowMethods,
49};
50use script_bindings::conversions::jsid_to_string;
51use script_bindings::proxyhandler::{
52    self, CrossOriginProperties, cross_origin_get_own_property_helper,
53    cross_origin_own_property_keys, cross_origin_property_fallback, cross_origin_set,
54    is_extensible, is_platform_object_same_origin, maybe_cross_origin_get_prototype,
55    maybe_cross_origin_set_prototype_rawcx, prevent_extensions, report_cross_origin_denial,
56    set_property_descriptor,
57};
58use script_bindings::reflector::{DomObject, MutDomObject, Reflector};
59use script_traits::NewPipelineInfo;
60use serde::{Deserialize, Serialize};
61use servo_base::generic_channel;
62use servo_base::generic_channel::GenericSend;
63use servo_base::id::{BrowsingContextId, PipelineId, WebViewId};
64use servo_constellation_traits::{
65    AuxiliaryWebViewCreationRequest, LoadData, LoadOrigin, NavigationHistoryBehavior,
66    ScriptToConstellationMessage, TargetSnapshotParams,
67};
68use servo_url::{ImmutableOrigin, OriginSnapshot, ServoUrl};
69use storage_traits::webstorage_thread::WebStorageThreadMsg;
70use style::attr::parse_integer;
71
72use crate::DomTypeHolder;
73use crate::dom::bindings::conversions::{ToJSValConvertible, root_from_handleobject};
74use crate::dom::bindings::error::{Error, Fallible};
75use crate::dom::bindings::inheritance::Castable;
76use crate::dom::bindings::reflector::DomGlobal;
77use crate::dom::bindings::root::{Dom, DomRoot};
78use crate::dom::bindings::settings_stack::maybe_entry_global;
79use crate::dom::bindings::str::{DOMString, USVString};
80use crate::dom::bindings::trace::JSTraceable;
81use crate::dom::bindings::utils::get_array_index_from_id;
82use crate::dom::dissimilaroriginwindow::DissimilarOriginWindow;
83use crate::dom::document::Document;
84use crate::dom::element::Element;
85use crate::dom::globalscope::GlobalScope;
86use crate::dom::node::node::NodeTraits;
87use crate::dom::window::Window;
88use crate::event_loop::script_thread::{ScriptThread, with_script_thread};
89use crate::event_loop::script_window_proxies::ScriptWindowProxies;
90use crate::navigation::navigate;
91
92#[dom_struct]
93// NOTE: the browsing context for a window is managed in two places:
94// here, in script, but also in the constellation. The constellation
95// manages the session history, which in script is accessed through
96// History objects, messaging the constellation.
97pub(crate) struct WindowProxy {
98    /// The JS WindowProxy object.
99    /// Unlike other reflectors, we mutate this field because
100    /// we have to brain-transplant the reflector when the WindowProxy
101    /// changes Window.
102    reflector: Reflector,
103
104    /// The id of the browsing context.
105    /// In the case that this is a nested browsing context, this is the id
106    /// of the container.
107    #[no_trace]
108    browsing_context_id: BrowsingContextId,
109
110    // https://html.spec.whatwg.org/multipage/#opener-browsing-context
111    #[no_trace]
112    opener: Option<BrowsingContextId>,
113
114    /// The frame id of the top-level ancestor browsing context.
115    /// In the case that this is a top-level window, this is our id.
116    #[no_trace]
117    webview_id: WebViewId,
118
119    /// The name of the browsing context (sometimes, but not always,
120    /// equal to the name of a container element)
121    name: DomRefCell<DOMString>,
122    /// The pipeline id of the currently active document.
123    /// May be None, when the currently active document is in another script thread.
124    /// We do not try to keep the pipeline id for documents in other threads,
125    /// as this would require the constellation notifying many script threads about
126    /// the change, which could be expensive.
127    #[no_trace]
128    currently_active: Cell<Option<PipelineId>>,
129
130    /// Has the browsing context been discarded?
131    discarded: Cell<bool>,
132
133    /// Has the browsing context been disowned?
134    disowned: Cell<bool>,
135
136    /// <https://html.spec.whatwg.org/multipage/#is-closing>
137    is_closing: Cell<bool>,
138
139    /// If the containing `<iframe>` of this [`WindowProxy`] is from a same-origin page,
140    /// this will be the [`Element`] of the `<iframe>` element in the realm of the
141    /// parent page. Otherwise, it is `None`.
142    frame_element: Option<Dom<Element>>,
143
144    /// The parent browsing context's window proxy, if this is a nested browsing context
145    parent: Option<Dom<WindowProxy>>,
146
147    /// <https://html.spec.whatwg.org/multipage/#delaying-load-events-mode>
148    delaying_load_events_mode: Cell<bool>,
149
150    /// The creator browsing context's url.
151    #[no_trace]
152    creator_url: Option<ServoUrl>,
153
154    /// The creator browsing context's origin.
155    #[no_trace]
156    creator_origin: Option<ImmutableOrigin>,
157
158    /// The window proxies the script thread knows.
159    #[conditional_malloc_size_of]
160    script_window_proxies: Rc<ScriptWindowProxies>,
161}
162
163impl WindowProxy {
164    fn new_inherited(
165        browsing_context_id: BrowsingContextId,
166        webview_id: WebViewId,
167        currently_active: Option<PipelineId>,
168        frame_element: Option<&Element>,
169        parent: Option<&WindowProxy>,
170        opener: Option<BrowsingContextId>,
171        creator: CreatorBrowsingContextInfo,
172    ) -> WindowProxy {
173        let name = frame_element.map_or(DOMString::new(), |e| {
174            e.get_string_attribute(&local_name!("name"))
175        });
176        WindowProxy {
177            reflector: Reflector::new(),
178            browsing_context_id,
179            webview_id,
180            name: DomRefCell::new(name),
181            currently_active: Cell::new(currently_active),
182            discarded: Cell::new(false),
183            disowned: Cell::new(false),
184            is_closing: Cell::new(false),
185            frame_element: frame_element.map(Dom::from_ref),
186            parent: parent.map(Dom::from_ref),
187            delaying_load_events_mode: Cell::new(false),
188            opener,
189            creator_url: creator.url,
190            creator_origin: creator.origin,
191            script_window_proxies: ScriptThread::window_proxies(),
192        }
193    }
194
195    #[expect(unsafe_code)]
196    #[expect(clippy::too_many_arguments)]
197    pub(crate) fn new(
198        cx: &mut JSContext,
199        window: &Window,
200        browsing_context_id: BrowsingContextId,
201        webview_id: WebViewId,
202        frame_element: Option<&Element>,
203        parent: Option<&WindowProxy>,
204        opener: Option<BrowsingContextId>,
205        creator: CreatorBrowsingContextInfo,
206    ) -> DomRoot<WindowProxy> {
207        unsafe {
208            let handler = window.windowproxy_handler();
209
210            let window_jsobject = window.reflector().get_jsobject();
211            assert!(!window_jsobject.get().is_null());
212            assert_ne!(
213                ((*get_object_class(window_jsobject.get())).flags & JSCLASS_IS_GLOBAL),
214                0
215            );
216
217            let mut realm = AutoRealm::new_from_handle(cx, window_jsobject);
218            let cx = &mut realm;
219
220            // Create a new window proxy.
221            rooted!(&in(cx) let js_proxy = handler.new_window_proxy(cx, window_jsobject));
222            assert!(!js_proxy.is_null());
223
224            // Create a new browsing context.
225
226            let current = Some(window.upcast::<GlobalScope>().pipeline_id());
227            let window_proxy = Box::new(WindowProxy::new_inherited(
228                browsing_context_id,
229                webview_id,
230                current,
231                frame_element,
232                parent,
233                opener,
234                creator,
235            ));
236
237            // The window proxy owns the browsing context.
238            // When we finalize the window proxy, it drops the browsing context it owns.
239            SetProxyReservedSlot(
240                js_proxy.get(),
241                0,
242                &PrivateValue(&raw const (*window_proxy) as *const libc::c_void),
243            );
244
245            // Notify the JS engine about the new window proxy binding.
246            SetWindowProxy(cx, window_jsobject, js_proxy.handle());
247
248            // Set the reflector.
249            debug!(
250                "Initializing reflector of {:p} to {:p}.",
251                window_proxy,
252                js_proxy.get()
253            );
254            window_proxy
255                .reflector
256                .init_reflector::<WindowProxy>(js_proxy.get());
257            DomRoot::from_ref(&*Box::into_raw(window_proxy))
258        }
259    }
260
261    #[expect(unsafe_code)]
262    pub(crate) fn new_dissimilar_origin(
263        cx: &mut JSContext,
264        global_to_clone_from: &GlobalScope,
265        browsing_context_id: BrowsingContextId,
266        webview_id: WebViewId,
267        parent: Option<&WindowProxy>,
268        opener: Option<BrowsingContextId>,
269        creator: CreatorBrowsingContextInfo,
270    ) -> DomRoot<WindowProxy> {
271        unsafe {
272            // Create a new browsing context.
273            let window_proxy = Box::new(WindowProxy::new_inherited(
274                browsing_context_id,
275                webview_id,
276                None,
277                None,
278                parent,
279                opener,
280                creator,
281            ));
282
283            // Create a new dissimilar-origin window.
284            let window = DissimilarOriginWindow::new(cx, global_to_clone_from, &window_proxy);
285            let window_jsobject = window.reflector().get_jsobject();
286            assert!(!window_jsobject.get().is_null());
287            assert_ne!(
288                ((*get_object_class(window_jsobject.get())).flags & JSCLASS_IS_GLOBAL),
289                0
290            );
291
292            let mut realm = AutoRealm::new_from_handle(cx, window_jsobject);
293            let cx = &mut realm;
294
295            // Create a new window proxy.
296            let handler = WindowProxyHandler::proxy_handler();
297            rooted!(&in(cx) let js_proxy = handler.new_window_proxy(cx, window_jsobject));
298            assert!(!js_proxy.is_null());
299
300            // The window proxy owns the browsing context.
301            // When we finalize the window proxy, it drops the browsing context it owns.
302            SetProxyReservedSlot(
303                js_proxy.get(),
304                0,
305                &PrivateValue(&raw const (*window_proxy) as *const libc::c_void),
306            );
307
308            // Notify the JS engine about the new window proxy binding.
309            SetWindowProxy(cx, window_jsobject, js_proxy.handle());
310
311            // Set the reflector.
312            debug!(
313                "Initializing reflector of {:p} to {:p}.",
314                window_proxy,
315                js_proxy.get()
316            );
317            window_proxy
318                .reflector
319                .init_reflector::<WindowProxy>(js_proxy.get());
320            DomRoot::from_ref(&*Box::into_raw(window_proxy))
321        }
322    }
323
324    /// <https://html.spec.whatwg.org/multipage/#auxiliary-browsing-context>
325    fn create_auxiliary_browsing_context(
326        &self,
327        cx: &mut JSContext,
328        name: DOMString,
329        noopener: bool,
330    ) -> Option<DomRoot<WindowProxy>> {
331        let (response_sender, response_receiver) = generic_channel::channel().unwrap();
332        let window = self
333            .currently_active
334            .get()
335            .and_then(ScriptThread::find_document)
336            .map(|doc| DomRoot::from_ref(doc.window()))
337            .unwrap();
338
339        let document = self
340            .currently_active
341            .get()
342            .and_then(ScriptThread::find_document)
343            .expect("A WindowProxy creating an auxiliary to have an active document");
344
345        // <https://html.spec.whatwg.org/multipage/#navigable-target-names>
346        // > If the user agent has been configured such that in this instance it
347        // > will create a new top-level traversable
348        // >
349        // Step 9. If sandboxingFlagSet's sandbox propagates to auxiliary browsing
350        //   contexts flag is set, then all the flags that are set in sandboxingFlagSet
351        // must be set in chosen's active browsing context's popup sandboxing flag set.
352        let sandboxing_flag_set = document.active_sandboxing_flag_set();
353        let propagate_sandbox = sandboxing_flag_set
354            .contains(SandboxingFlagSet::SANDBOX_PROPOGATES_TO_AUXILIARY_BROWSING_CONTEXTS_FLAG);
355        let sandboxing_flag_set = if propagate_sandbox {
356            sandboxing_flag_set
357        } else {
358            SandboxingFlagSet::empty()
359        };
360
361        let blank_url = ServoUrl::parse("about:blank").ok().unwrap();
362        let mut load_data = LoadData::new(
363            LoadOrigin::Script(document.origin().snapshot()),
364            blank_url,
365            Some(document.base_url()),
366            // This has the effect of ensuring that the new `about:blank` URL has the
367            // same origin as the `Document` that is creating the new browsing context.
368            Some(window.pipeline_id()),
369            document.global().get_referrer(),
370            document.get_referrer_policy(),
371            None, // Doesn't inherit secure context
372            None,
373            false,
374            sandboxing_flag_set,
375        );
376        load_data.is_initial_about_blank = true;
377        let load_info = AuxiliaryWebViewCreationRequest {
378            load_data: load_data.clone(),
379            opener_webview_id: window.webview_id(),
380            opener_pipeline_id: self.currently_active.get().unwrap(),
381            response_sender,
382        };
383        let constellation_msg = ScriptToConstellationMessage::CreateAuxiliaryWebView(load_info);
384        window.send_to_constellation(constellation_msg);
385
386        let response = response_receiver.recv().unwrap()?;
387        let new_browsing_context_id = BrowsingContextId::from(response.new_webview_id);
388        let new_pipeline_info = NewPipelineInfo {
389            parent_info: None,
390            new_pipeline_id: response.new_pipeline_id,
391            browsing_context_id: new_browsing_context_id,
392            webview_id: response.new_webview_id,
393            opener: Some(self.browsing_context_id),
394            load_data,
395            viewport_details: window.viewport_details(),
396            user_content_manager_id: response.user_content_manager_id,
397            // Use the current `WebView`'s theme initially, but the embedder may
398            // change this later.
399            embedder_theme: window.embedder_theme(),
400            target_snapshot_params: TargetSnapshotParams {
401                sandboxing_flags: sandboxing_flag_set,
402                iframe_element_referrer_policy: ReferrerPolicy::EmptyString,
403            },
404            frame_name: None,
405        };
406
407        with_script_thread(|script_thread| {
408            script_thread.spawn_pipeline(cx, new_pipeline_info);
409        });
410
411        let new_window_proxy = ScriptThread::find_document(response.new_pipeline_id)
412            .and_then(|doc| doc.browsing_context())?;
413        if !name.eq_ignore_ascii_case("_blank") {
414            new_window_proxy.set_name(name);
415        }
416        if noopener {
417            new_window_proxy.disown();
418        } else {
419            // After creating a new auxiliary browsing context and document,
420            // the session storage is copied over.
421            // See https://html.spec.whatwg.org/multipage/#the-sessionstorage-attribute
422
423            let (sender, receiver) = generic_channel::channel().unwrap();
424
425            let msg = WebStorageThreadMsg::Clone {
426                sender,
427                src: window.window_proxy().webview_id(),
428                dest: response.new_webview_id,
429            };
430
431            GenericSend::send(document.global().storage_threads(), msg).unwrap();
432            receiver.recv().unwrap();
433        }
434        Some(new_window_proxy)
435    }
436
437    /// <https://html.spec.whatwg.org/multipage/#delaying-load-events-mode>
438    pub(crate) fn start_delaying_load_events_mode(&self) {
439        self.delaying_load_events_mode.set(true);
440    }
441
442    /// <https://html.spec.whatwg.org/multipage/#delaying-load-events-mode>
443    pub(crate) fn stop_delaying_load_events_mode(&self) {
444        self.delaying_load_events_mode.set(false);
445    }
446
447    // https://html.spec.whatwg.org/multipage/#disowned-its-opener
448    pub(crate) fn disown(&self) {
449        self.disowned.set(true);
450    }
451
452    /// <https://html.spec.whatwg.org/multipage/#dom-window-close>
453    /// Step 3.1, set BCs `is_closing` to true.
454    pub(crate) fn close(&self) {
455        self.is_closing.set(true);
456    }
457
458    /// <https://html.spec.whatwg.org/multipage/#is-closing>
459    pub(crate) fn is_closing(&self) -> bool {
460        self.is_closing.get()
461    }
462
463    // https://html.spec.whatwg.org/multipage/#dom-opener
464    pub(crate) fn opener(&self, cx: &mut CurrentRealm, mut retval: MutableHandleValue) {
465        if self.disowned.get() {
466            return retval.set(NullValue());
467        }
468        let opener_id = match self.opener {
469            Some(opener_browsing_context_id) => opener_browsing_context_id,
470            None => return retval.set(NullValue()),
471        };
472        let parent_browsing_context = self.parent.as_deref();
473        let opener_proxy = match self.script_window_proxies.find_window_proxy(opener_id) {
474            Some(window_proxy) => window_proxy,
475            None => {
476                let sender_pipeline_id = self.currently_active().unwrap();
477                match ScriptThread::get_top_level_for_browsing_context(
478                    self.webview_id(),
479                    sender_pipeline_id,
480                    opener_id,
481                ) {
482                    Some(opener_top_id) => {
483                        let global_to_clone_from = GlobalScope::from_current_realm(cx);
484                        let creator =
485                            CreatorBrowsingContextInfo::from(parent_browsing_context, None);
486                        WindowProxy::new_dissimilar_origin(
487                            cx,
488                            &global_to_clone_from,
489                            opener_id,
490                            opener_top_id,
491                            None,
492                            None,
493                            creator,
494                        )
495                    },
496                    None => return retval.set(NullValue()),
497                }
498            },
499        };
500        if opener_proxy.is_browsing_context_discarded() {
501            return retval.set(NullValue());
502        }
503        opener_proxy.to_jsval(cx, retval);
504    }
505
506    // https://html.spec.whatwg.org/multipage/#window-open-steps
507    pub(crate) fn open(
508        &self,
509        cx: &mut JSContext,
510        url: USVString,
511        target: DOMString,
512        features: DOMString,
513    ) -> Fallible<Option<DomRoot<WindowProxy>>> {
514        // Note: this does not map to the spec,
515        // but it does prevent a panic at the constellation because the browsing context
516        // has already been discarded.
517        // See issue: #39716 for the original problem,
518        // and https://github.com/whatwg/html/issues/11797 for a discussion at the level of the spec.
519        if self.discarded.get() {
520            return Ok(None);
521        }
522        // Step 2. Let sourceDocument be the entry global object's associated Document.
523        //
524        // It's possible to end up in a situation where JS code is executing but
525        // we have not had a chance to push an entry global (e.g. via WASM instantiation).
526        // If that happens, fall back to the active document of this browsing context.
527        let source_document = maybe_entry_global()
528            .map(|global| global.as_window().Document())
529            .or_else(|| self.document())
530            .expect("Must have an entry global or active document");
531        // Step 4. If url is not the empty string:
532        let url_record = if !url.is_empty() {
533            // Step 4.1. Set urlRecord to the result of encoding-parsing a URL given url, relative to sourceDocument.
534            let Ok(url) = source_document.encoding_parse_a_url(&url) else {
535                // Step 4.2. If urlRecord is failure, then throw a "SyntaxError" DOMException.
536                return Err(Error::Syntax(Some(format!(
537                    "Error parsing URL '{url}' relative to '{}'",
538                    source_document.url()
539                ))));
540            };
541            Some(url)
542        } else {
543            // Step 3. Let urlRecord be null.
544            None
545        };
546        // Step 5. If target is the empty string, then set target to "_blank".
547        let non_empty_target = if target.is_empty() {
548            DOMString::from_static("_blank")
549        } else {
550            target
551        };
552        // Step 6. Let tokenizedFeatures be the result of tokenizing features.
553        let tokenized_features = tokenize_open_features(features);
554        // Step 7 - 8.
555        // If tokenizedFeatures["noreferrer"] exists, then set noreferrer to
556        // the result of parsing tokenizedFeatures["noreferrer"] as a boolean feature.
557        let noreferrer = parse_open_feature_boolean(&tokenized_features, "noreferrer");
558
559        // Step 9. Let noopener be the result of getting noopener for window
560        // open with sourceDocument, tokenizedFeatures, and urlRecord.
561        let noopener = if noreferrer {
562            true
563        } else {
564            parse_open_feature_boolean(&tokenized_features, "noopener")
565        };
566        // (TODO) Step 10. Remove tokenizedFeatures["noopener"] and tokenizedFeatures["noreferrer"].
567
568        // (TODO) Step 11. Let referrerPolicy be the empty string.
569        // (TODO) Step 12. If noreferrer is true, then set referrerPolicy to "no-referrer".
570
571        // Step 13 - 14
572        // Let targetNavigable and windowType be the result of applying the rules for
573        // choosing a navigable given target, sourceDocument's node navigable, and noopener.
574        // If targetNavigable is null, then return null.
575        let (chosen, new) = match self.choose_a_navigable(cx, non_empty_target, noopener) {
576            (Some(chosen), new) => (chosen, new),
577            (None, _) => return Ok(None),
578        };
579        // TODO Step 15.2, Set up browsing context features for targetNavigable's
580        // active browsing context given tokenizedFeatures.
581        let target_document = match chosen.document() {
582            Some(target_document) => target_document,
583            None => return Ok(None),
584        };
585        let has_trustworthy_ancestor_origin = if new {
586            target_document.has_trustworthy_ancestor_or_current_origin()
587        } else {
588            false
589        };
590        let target_window = target_document.window();
591        // Step 15.3. If urlRecord is null, then set urlRecord to a URL record representing about:blank.
592        let url_record = url_record.unwrap_or(ServoUrl::parse("about:blank").unwrap());
593        // Step 15.4. If urlRecord matches about:blank, then perform the URL and history update steps given targetNavigable's active document and urlRecord.
594        //
595        // This happened in the constellation as part of creating the auxiliary browsing context.
596        if !url_record.matches_about_blank() {
597            let referrer = if noreferrer {
598                Referrer::NoReferrer
599            } else {
600                target_window.as_global_scope().get_referrer()
601            };
602            // Propagate CSP list and about-base-url from opener to new document
603            let csp_list = source_document.get_csp_list().clone();
604            target_document.set_csp_list(csp_list);
605
606            // Step 15.5 Otherwise, navigate targetNavigable to urlRecord using sourceDocument,
607            // with referrerPolicy set to referrerPolicy and exceptionsEnabled set to true.
608            // FIXME: referrerPolicy may not be used properly here. exceptionsEnabled not used.
609            let mut load_data = LoadData::new(
610                LoadOrigin::Script(source_document.origin().snapshot()),
611                url_record,
612                target_document.about_base_url(),
613                Some(target_window.pipeline_id()),
614                referrer,
615                target_document.get_referrer_policy(),
616                Some(target_window.as_global_scope().is_secure_context()),
617                Some(target_document.insecure_requests_policy()),
618                has_trustworthy_ancestor_origin,
619                target_document.creation_sandboxing_flag_set_considering_parent_iframe(),
620            );
621
622            // Handle javascript: URLs specially to report CSP violations to the source window
623            // https://html.spec.whatwg.org/multipage/#navigate-to-a-javascript:-url
624            if load_data.url.scheme() == "javascript" {
625                let existing_global = source_document.global();
626
627                // Check CSP and report violations to the source (existing) window
628                if !ScriptThread::can_navigate_to_javascript_url(
629                    cx,
630                    &existing_global,
631                    target_window.as_global_scope(),
632                    &mut load_data,
633                    None,
634                ) {
635                    // CSP blocked the navigation, don't proceed
636                    return Ok(target_document.browsing_context());
637                }
638            }
639
640            let history_handling = if new {
641                NavigationHistoryBehavior::Replace
642            } else {
643                NavigationHistoryBehavior::Push
644            };
645            navigate(cx, target_window, history_handling, false, load_data);
646        }
647        // Step 17 (Dis-owning has been done in create_auxiliary_browsing_context).
648        if noopener {
649            return Ok(None);
650        }
651        // Step 18
652        Ok(target_document.browsing_context())
653    }
654
655    /// <https://html.spec.whatwg.org/multipage/#the-rules-for-choosing-a-navigable>
656    pub(crate) fn choose_a_navigable(
657        &self,
658        cx: &mut JSContext,
659        name: DOMString,
660        noopener: bool,
661    ) -> (Option<DomRoot<WindowProxy>>, bool) {
662        // Step 1. Let chosen be null.
663        // Step 2. Let windowType be "existing or none".
664
665        // Step 3. Let sandboxingFlagSet be currentNavigable's active document's active
666        // sandboxing flag set.
667        let sandboxing_flag_set = self
668            .document()
669            .map(|document| document.active_sandboxing_flag_set())
670            .unwrap_or_default();
671
672        let chosen = if name.is_empty() || name.eq_ignore_ascii_case("_self") {
673            // Step 4. If name is the empty string or an ASCII case-insensitive match for
674            // "_self", then set chosen to currentNavigable.
675            Some((Some(DomRoot::from_ref(self)), false))
676        } else if name.eq_ignore_ascii_case("_parent") {
677            // Step 5. Otherwise, if name is an ASCII case-insensitive match for
678            // "_parent", set chosen to currentNavigable's parent, if any, and
679            // currentNavigable otherwise.
680            Some(
681                self.parent()
682                    .map(|parent| (Some(DomRoot::from_ref(parent)), false))
683                    .unwrap_or_else(|| (Some(DomRoot::from_ref(self)), false)),
684            )
685        } else if name.eq_ignore_ascii_case("_top") {
686            // Step 6. Otherwise, if name is an ASCII case-insensitive match for "_top",
687            // set chosen to currentNavigable's traversable navigable.
688            Some((Some(DomRoot::from_ref(self.top())), false))
689        } else if !name.eq_ignore_ascii_case("_blank") {
690            // Step 7. Otherwise, if name is not an ASCII case-insensitive match for
691            // "_blank" and noopener is false, then set chosen to the result of finding a
692            // navigable by target name given name and currentNavigable.
693            //
694            // Note: The noopener==false condition here seems to break WPT tests and
695            // is likely a specification bug.
696            // See <https://github.com/whatwg/html/issues/12839>
697            self.find_navigable_by_target_name(&name)
698                .map(|proxy| (Some(proxy), false))
699        } else {
700            None
701        };
702
703        if let Some(chosen) = chosen {
704            return chosen;
705        }
706
707        // Step 8. If chosen is null, then a new top-level traversable is being requested,
708        // and what happens depends on the user agent's configuration and abilities — it
709        // is determined by the rules given for the first applicable option from the
710        // following list:
711        //
712        // ↪ If currentNavigable's active window does not have transient
713        //   activation and the user agent has been configured to not show popups
714        //   (i.e., the user agent has a "popup blocker" enabled)
715        //    - The user agent may inform the user that a popup has been blocked.
716        // TODO: Implement this.
717        //
718        // ↪ If sandboxingFlagSet has the sandboxed auxiliary navigation browsing context flag set
719        //   - The user agent may report to a developer console that a popup has been blocked.
720        if sandboxing_flag_set
721            .contains(SandboxingFlagSet::SANDBOXED_AUXILIARY_NAVIGATION_BROWSING_CONTEXT_FLAG)
722        {
723            (None, false)
724        }
725        // ↪ If the user agent has been configured such that in this instance it
726        // will create a new top-level traversable
727        // TODO: Integrate `create_auxiliary_browsing_context` here and have it follow the spec.
728        else {
729            (
730                self.create_auxiliary_browsing_context(cx, name, noopener),
731                true,
732            )
733        }
734    }
735
736    /// <https://html.spec.whatwg.org/multipage/#find-a-navigable-by-target-name>
737    fn find_navigable_by_target_name(&self, name: &DOMString) -> Option<DomRoot<WindowProxy>> {
738        // Step 1. Let currentDocument be currentNavigable's active document.
739        //
740        // Step 2. Let sourceSnapshotParams be the result of snapshotting source snapshot
741        // params given currentDocument.
742        // TODO: This is unimplemented.
743        //
744        // Step 3. Let subtreesToSearch be an implementation-defined choice of one of the
745        // following:
746        //     - « currentNavigable's traversable navigable, currentNavigable »
747        //     - the inclusive ancestor navigables of currentDocument
748        //
749        // From <https://github.com/whatwg/html/issues/10848>:
750        // > WebKit and Chromium search the requesting window's subtree then search from
751        // > the top. Firefox iterates and searches from each ancestor. If there's a way to
752        // > express in the spec that the implementation-defined behavior is fixed for the
753        // > instance of the user agent, then that'd be appropriate here.
754        //
755        // We use the Webkit and Chrome approach here and the reversal is done here
756        // rather than below.
757        let top = self.top();
758        let subtrees_to_search = if top != self {
759            Either::Left([self, top])
760        } else {
761            Either::Right([self])
762        };
763
764        // Step 4. For each subtreeToSearch of subtreesToSearch, in reverse order:
765        for subtree_to_search in subtrees_to_search.into_iter() {
766            // Step 4.1. Let documentToSearch be subtreeToSearch's active document.
767            // Step 4.2. For each navigable of the inclusive descendant navigables of
768            // documentToSearch:
769            if let Some(result) =
770                subtree_to_search.find_navigable_by_target_name_in_descendants(name)
771            {
772                return Some(result);
773            }
774        }
775
776        // Step 5. Let currentTopLevelBrowsingContext be currentNavigable's active
777        // browsing context's top-level browsing context.
778        // Step 6. Let group be currentTopLevelBrowsingContext's group.
779        //
780        // TODO: Servo doesn't have a concept of the browsing context group in script, so
781        // we just look through all top-level WindowProxy instances.
782        let mut top_level_window_proxies = self.script_window_proxies.top_level_window_proxies();
783
784        // Step 7. For each topLevelBrowsingContext of group's browsing context set, in an
785        // implementation-defined order (the user agent should pick a consistent ordering,
786        // such as the most recently opened, most recently focused, or more closely
787        // related):
788        //
789        // Sorting by `BrowsingContextId` is an attempt to make the order consistent.
790        // This also ensures that newer `BrowsingContextId`s are sorted first.
791        top_level_window_proxies
792            .sort_by_key(|proxy| std::cmp::Reverse(proxy.browsing_context_id()));
793
794        for window_proxy in top_level_window_proxies {
795            // Step 7.1. If currentTopLevelBrowsingContext is topLevelBrowsingContext, then
796            // continue.
797            if &*window_proxy == top {
798                continue;
799            }
800            // Step 7.2. Let documentToSearch be topLevelBrowsingContext's active document.
801            // Step 7.3. For each navigable of the inclusive descendant navigables of
802            // documentToSearch:
803            //
804            // Step 7.3.1 If currentNavigable's active browsing context is not familiar
805            // with navigable's active browsing context, then continue.
806            //
807            // TODO: Servo does not implement the concept of "familiar with".
808            // See: <https://html.spec.whatwg.org/multipage/#familiar-with>
809            // TODO: Properly support navigables in other script threads and with
810            // dissimilar origins, which requires that they be accessible here and
811            // WindowProxy::get_name() returns something useful.
812            //
813            // Step 7.3.2. If currentNavigable is not allowed by sandboxing to navigate
814            // navigable given sourceSnapshotParams, then optionally continue.
815            // Step 7.3.3 If navigable's target name is name, then return navigable.
816            // These steps are handled by `find_navigable_by_target_name_in_descendants`.
817            if let Some(result) = window_proxy.find_navigable_by_target_name_in_descendants(name) {
818                return Some(result);
819            }
820        }
821
822        // Step 8. Return null.
823        None
824    }
825
826    /// <https://html.spec.whatwg.org/multipage/#find-a-navigable-by-target-name> step 4 and 7 substeps.
827    fn find_navigable_by_target_name_in_descendants(
828        &self,
829        name: &DOMString,
830    ) -> Option<DomRoot<WindowProxy>> {
831        // Never traverse or return a `WindowProxy` with a discarded browsing context.
832        if self.is_browsing_context_discarded() {
833            return None;
834        }
835
836        // Step 4.2.1 If currentNavigable is not allowed by sandboxing to navigate
837        // navigable given sourceSnapshotParams, then optionally continue.
838        // TODO: This is unimplemented.
839
840        // Step 4.2.2. If navigable's target name is name, then return navigable.
841        if self.get_name() == *name {
842            return Some(DomRoot::from_ref(self));
843        }
844
845        let document = self.document()?;
846        let iframes: Vec<_> = document.iframes().iter().collect();
847        iframes.iter().find_map(|iframe| {
848            iframe
849                .browsing_context_id()
850                .and_then(|browsing_context_id| {
851                    self.script_window_proxies
852                        .find_window_proxy(browsing_context_id)?
853                        .find_navigable_by_target_name_in_descendants(name)
854                })
855        })
856    }
857
858    pub(crate) fn is_auxiliary(&self) -> bool {
859        self.opener.is_some()
860    }
861
862    pub(crate) fn discard_browsing_context(&self) {
863        self.discarded.set(true);
864    }
865
866    pub(crate) fn is_browsing_context_discarded(&self) -> bool {
867        self.discarded.get()
868    }
869
870    pub(crate) fn browsing_context_id(&self) -> BrowsingContextId {
871        self.browsing_context_id
872    }
873
874    pub(crate) fn webview_id(&self) -> WebViewId {
875        self.webview_id
876    }
877
878    /// If the containing `<iframe>` of this [`WindowProxy`] is from a same-origin page,
879    /// this will return an [`Element`] of the `<iframe>` element in the realm of the parent
880    /// page.
881    pub(crate) fn frame_element(&self) -> Option<&Element> {
882        self.frame_element.as_deref()
883    }
884
885    pub(crate) fn document(&self) -> Option<DomRoot<Document>> {
886        self.currently_active
887            .get()
888            .and_then(ScriptThread::find_document)
889    }
890
891    pub(crate) fn parent(&self) -> Option<&WindowProxy> {
892        self.parent.as_deref()
893    }
894
895    pub(crate) fn top(&self) -> &WindowProxy {
896        let mut result = self;
897        while let Some(parent) = result.parent() {
898            result = parent;
899        }
900        result
901    }
902
903    pub(crate) fn document_origin_and_internal_ancestor_origin_objects_list(
904        &self,
905    ) -> Option<(OriginSnapshot, Vec<ImmutableOrigin>)> {
906        let pipeline_id = self.currently_active()?;
907        let (result_sender, result_receiver) = generic_channel::channel().unwrap();
908        self.global()
909            .script_to_constellation_chan()
910            .send(ScriptToConstellationMessage::GetDocumentOriginDetails(
911                pipeline_id,
912                result_sender,
913            ))
914            .ok()?;
915        result_receiver.recv().ok()?
916    }
917
918    /// <https://html.spec.whatwg.org/multipage/#internal-ancestor-origin-objects-list-creation-steps>
919    pub(crate) fn parent_origin_and_internal_ancestor_origin_objects_list(
920        &self,
921    ) -> Option<(OriginSnapshot, Vec<ImmutableOrigin>)> {
922        if let Some(frame_element) = self.frame_element() {
923            let parent_document = frame_element.owner_document();
924            // Step 4. Assert: parentDoc is fully active.
925            // TODO(47417): Once "creating a new browsing context" properly exists, remove this check
926            if !parent_document.is_fully_active() {
927                return None;
928            }
929            Some((
930                parent_document.origin().snapshot(),
931                parent_document
932                    .internal_ancestor_origin_objects_list()
933                    .clone()
934                    .expect("Must always be fully active"),
935            ))
936        } else if let Some(parent_proxy) = self.parent() {
937            // Step 4. Assert: parentDoc is fully active.
938            assert!(parent_proxy.currently_active().is_some());
939            parent_proxy.document_origin_and_internal_ancestor_origin_objects_list()
940        } else {
941            None
942        }
943    }
944
945    #[expect(unsafe_code)]
946    /// Change the Window that this WindowProxy resolves to.
947    // TODO: support setting the window proxy to a dummy value,
948    // to handle the case when the active document is in another script thread.
949    fn set_window(&self, cx: &mut JSContext, window: &GlobalScope) {
950        unsafe {
951            debug!("Setting window of {:p}.", self);
952
953            let window_jsobject = window.reflector().get_jsobject();
954            let old_js_proxy = self.reflector.get_jsobject();
955            assert!(!window_jsobject.get().is_null());
956            assert_ne!(
957                ((*get_object_class(window_jsobject.get())).flags & JSCLASS_IS_GLOBAL),
958                0
959            );
960
961            let mut realm = AutoRealm::new_from_handle(cx, window_jsobject);
962            let cx = &mut realm;
963
964            // The old window proxy no longer owns this browsing context.
965            SetProxyReservedSlot(old_js_proxy.get(), 0, &PrivateValue(ptr::null_mut()));
966
967            // Brain transplant the window proxy. Brain transplantation is
968            // usually done to move a window proxy between compartments, but
969            // that's not what we are doing here. We need to do this to retarget
970            // the proxy at a different global without updating its identity.
971            rooted!(&in(cx) let new_js_proxy = WindowProxyHandler::proxy_handler().new_window_proxy(cx, window_jsobject));
972            // Explicitly set this slot to a null pointer in case a GC occurs before we
973            // are ready to set it to a real value.
974            SetProxyReservedSlot(new_js_proxy.get(), 0, &PrivateValue(ptr::null_mut()));
975            debug!(
976                "Transplanting proxy from {:p} to {:p}.",
977                old_js_proxy.get(),
978                new_js_proxy.get()
979            );
980            rooted!(&in(cx) let new_js_proxy = JS_TransplantObject(cx, old_js_proxy, new_js_proxy.handle()));
981            debug!("Transplanted proxy is {:p}.", new_js_proxy.get());
982
983            // Transfer ownership of this browsing context from the old window proxy to the new one.
984            SetProxyReservedSlot(
985                new_js_proxy.get(),
986                0,
987                &PrivateValue(self as *const _ as *const libc::c_void),
988            );
989
990            // Notify the JS engine about the new window proxy binding.
991            SetWindowProxy(cx, window_jsobject, new_js_proxy.handle());
992
993            // Update the reflector.
994            debug!(
995                "Setting reflector of {:p} to {:p}.",
996                self,
997                new_js_proxy.get()
998            );
999            self.reflector.rootable().set(new_js_proxy.get());
1000        }
1001    }
1002
1003    pub(crate) fn set_pipeline_id(&self, pipeline_id: PipelineId) {
1004        self.currently_active.set(Some(pipeline_id));
1005    }
1006
1007    pub(crate) fn set_currently_active(&self, cx: &mut JSContext, window: &Window) {
1008        if let Some(pipeline_id) = self.currently_active() &&
1009            pipeline_id == window.pipeline_id()
1010        {
1011            return debug!(
1012                "Attempt to set the currently active window to the currently active window."
1013            );
1014        }
1015
1016        let global_scope = window.as_global_scope();
1017        self.set_window(cx, global_scope);
1018        self.currently_active.set(Some(global_scope.pipeline_id()));
1019    }
1020
1021    pub(crate) fn unset_currently_active(&self, cx: &mut JSContext) {
1022        if self.currently_active().is_none() {
1023            return debug!(
1024                "Attempt to unset the currently active window on a windowproxy that does not have one."
1025            );
1026        }
1027        let globalscope = self.global();
1028        let window = DissimilarOriginWindow::new(cx, &globalscope, self);
1029        self.set_window(cx, window.upcast());
1030        self.currently_active.set(None);
1031    }
1032
1033    pub(crate) fn currently_active(&self) -> Option<PipelineId> {
1034        self.currently_active.get()
1035    }
1036
1037    pub(crate) fn get_name(&self) -> DOMString {
1038        self.name.borrow().clone()
1039    }
1040
1041    pub(crate) fn set_name(&self, name: DOMString) {
1042        *self.name.borrow_mut() = name;
1043    }
1044}
1045
1046/// A browsing context can have a creator browsing context, the browsing context that
1047/// was responsible for its creation. If a browsing context has a parent browsing context,
1048/// then that is its creator browsing context. Otherwise, if the browsing context has an
1049/// opener browsing context, then that is its creator browsing context. Otherwise, the
1050/// browsing context has no creator browsing context.
1051///
1052/// If a browsing context A has a creator browsing context, then the Document that was the
1053/// active document of that creator browsing context at the time A was created is the creator
1054/// Document.
1055///
1056/// See: <https://html.spec.whatwg.org/multipage/#creating-browsing-contexts>
1057#[derive(Debug, Deserialize, Serialize)]
1058pub(crate) struct CreatorBrowsingContextInfo {
1059    /// Creator document URL.
1060    url: Option<ServoUrl>,
1061
1062    /// Creator document origin.
1063    origin: Option<ImmutableOrigin>,
1064}
1065
1066impl CreatorBrowsingContextInfo {
1067    pub(crate) fn from(
1068        parent: Option<&WindowProxy>,
1069        opener: Option<&WindowProxy>,
1070    ) -> CreatorBrowsingContextInfo {
1071        let creator = match (parent, opener) {
1072            (Some(parent), _) => parent.document(),
1073            (None, Some(opener)) => opener.document(),
1074            (None, None) => None,
1075        };
1076
1077        let url = creator.as_deref().map(|document| document.url());
1078        let origin = creator
1079            .as_deref()
1080            .map(|document| document.origin().immutable().clone());
1081
1082        CreatorBrowsingContextInfo { url, origin }
1083    }
1084}
1085
1086/// <https://html.spec.whatwg.org/multipage/#concept-window-open-features-tokenize>
1087fn tokenize_open_features(features: DOMString) -> IndexMap<String, String> {
1088    let is_feature_sep = |c: char| c.is_ascii_whitespace() || ['=', ','].contains(&c);
1089    // Step 1
1090    let mut tokenized_features = IndexMap::new();
1091    // Step 2
1092    let features = features.str();
1093    let mut iter = features.chars();
1094    let mut cur = iter.next();
1095
1096    // Step 3
1097    while cur.is_some() {
1098        // Step 3.1 & 3.2
1099        let mut name = String::new();
1100        let mut value = String::new();
1101        // Step 3.3
1102        while let Some(cur_char) = cur {
1103            if !is_feature_sep(cur_char) {
1104                break;
1105            }
1106            cur = iter.next();
1107        }
1108        // Step 3.4
1109        while let Some(cur_char) = cur {
1110            if is_feature_sep(cur_char) {
1111                break;
1112            }
1113            name.push(cur_char.to_ascii_lowercase());
1114            cur = iter.next();
1115        }
1116        // Step 3.5
1117        let normalized_name = String::from(match name.as_ref() {
1118            "screenx" => "left",
1119            "screeny" => "top",
1120            "innerwidth" => "width",
1121            "innerheight" => "height",
1122            _ => name.as_ref(),
1123        });
1124        // Step 3.6
1125        while let Some(cur_char) = cur {
1126            if cur_char == '=' || cur_char == ',' || !is_feature_sep(cur_char) {
1127                break;
1128            }
1129            cur = iter.next();
1130        }
1131        // Step 3.7
1132        if cur.is_some() && is_feature_sep(cur.unwrap()) {
1133            // Step 3.7.1
1134            while let Some(cur_char) = cur {
1135                if !is_feature_sep(cur_char) || cur_char == ',' {
1136                    break;
1137                }
1138                cur = iter.next();
1139            }
1140            // Step 3.7.2
1141            while let Some(cur_char) = cur {
1142                if is_feature_sep(cur_char) {
1143                    break;
1144                }
1145                value.push(cur_char.to_ascii_lowercase());
1146                cur = iter.next();
1147            }
1148        }
1149        // Step 3.8
1150        if !name.is_empty() {
1151            tokenized_features.insert(normalized_name, value);
1152        }
1153    }
1154    // Step 4
1155    tokenized_features
1156}
1157
1158/// <https://html.spec.whatwg.org/multipage/#concept-window-open-features-parse-boolean>
1159fn parse_open_feature_boolean(tokenized_features: &IndexMap<String, String>, name: &str) -> bool {
1160    if let Some(value) = tokenized_features.get(name) {
1161        // Step 1 & 2
1162        if value.is_empty() || value == "yes" {
1163            return true;
1164        }
1165        // Step 3 & 4
1166        if let Ok(int) = parse_integer(value.chars()) {
1167            return int != 0;
1168        }
1169    }
1170    // Step 5
1171    false
1172}
1173
1174#[expect(unsafe_code)]
1175fn window_proxy_target(proxy: HandleObject) -> *mut JSObject {
1176    let mut slot = UndefinedValue();
1177    unsafe { GetProxyPrivate(proxy.as_raw(), &mut slot) };
1178    slot.to_object()
1179}
1180
1181/// <https://html.spec.whatwg.org/multipage/#windowproxy-getownproperty>
1182#[expect(unsafe_code)]
1183unsafe extern "C" fn get_own_property_descriptor(
1184    cx: *mut RawJSContext,
1185    proxy: RawHandleObject,
1186    id: RawHandleId,
1187    property_descriptor: RawMutableHandle<PropertyDescriptor>,
1188    is_none: *mut bool,
1189) -> bool {
1190    let mut cx = unsafe {
1191        // SAFETY: We are in a SpiderMonkey hook, so it is always safe to convert a raw context into
1192        // a mozjs context.
1193        JSContext::from_ptr(NonNull::new(cx).expect("JSContext should not be null in SM hook"))
1194    };
1195    let mut cx = CurrentRealm::assert(&mut cx);
1196    let cx = &mut cx;
1197    let proxy = unsafe { Handle::from_raw(proxy) };
1198    let id = unsafe { Handle::from_raw(id) };
1199    let mut property_descriptor = unsafe { MutableHandle::from_raw(property_descriptor) };
1200    let is_none = unsafe { &mut *is_none };
1201
1202    // Step 1. Let W be the value of the [[Window]] internal slot of this.
1203    rooted!(&in(cx) let target = window_proxy_target(proxy));
1204    let window = WindowOrDissimilarOriginWindow::new(cx, target.handle());
1205
1206    // Step 2. If P is an array index property name:
1207    // Step 2.1. Let index be ! ToUint32(P).
1208    if let Some(index) = get_array_index_from_id(id) {
1209        // Step 2.2. Let children be the document-tree child navigables of W's associated Document.
1210        // Step 2.3. Let value be undefined.
1211        // Step 2.4. If index is less than children's size:
1212        if let Some(window_proxy) = window.window_proxy_for_child_navigable_at_index(index) {
1213            // Step 2.4.1. Sort children in ascending order, with navigableA being less than
1214            // navigableB if navigableA's container was inserted into W's
1215            // associated Document earlier than navigableB's container was.
1216            // Step 2.4.2. Set value to children[index]'s active WindowProxy.
1217            //
1218            // Note: These are handled by `window_proxy_for_child_navigable_at_index`.
1219            rooted!(&in(cx) let mut window_proxy_jsval = UndefinedValue());
1220            window_proxy.to_jsval(cx, window_proxy_jsval.handle_mut());
1221
1222            // 2.6. Return PropertyDescriptor { [[Value]]: value, [[Writable]]:
1223            // false, [[Enumerable]]: true, [[Configurable]]: true }.
1224            set_property_descriptor(
1225                property_descriptor,
1226                window_proxy_jsval.handle(),
1227                (JSPROP_ENUMERATE | JSPROP_READONLY) as u32,
1228                is_none,
1229            );
1230            return true;
1231        } else {
1232            // Step 2.5. If value is undefined:
1233            *is_none = true;
1234            // Step 2.5.1 If IsPlatformObjectSameOrigin(W) is true, then return
1235            // undefined.
1236            if is_platform_object_same_origin(cx, proxy) {
1237                return true;
1238            }
1239            // Step 2.5.2 Throw a "SecurityError" DOMException.
1240            return report_cross_origin_denial::<DomTypeHolder>(cx, id, "get");
1241        }
1242    }
1243
1244    // Step 3. If IsPlatformObjectSameOrigin(W) is true, then return ! OrdinaryGetOwnProperty(W, P).
1245    if is_platform_object_same_origin(cx, proxy) {
1246        return unsafe {
1247            JS_GetOwnPropertyDescriptorById(cx, target.handle(), id, property_descriptor, is_none)
1248        };
1249    }
1250
1251    // Step 4. Let property be CrossOriginGetOwnPropertyHelper(W, P).
1252    if !cross_origin_get_own_property_helper(
1253        cx,
1254        proxy,
1255        window.cross_origin_properties(),
1256        id,
1257        property_descriptor.reborrow(),
1258        is_none,
1259    ) {
1260        return false;
1261    }
1262
1263    // Step 5. If property is not undefined, then return property.
1264    if !*is_none {
1265        return true;
1266    }
1267
1268    // Step 6. If property is undefined and P is in W's document-tree child navigable target name
1269    // property set:
1270    if let Some(named_child_navigable) = window.named_child_navigable(cx, id) {
1271        // Step 6.1. Let value be the active WindowProxy of the named object of W with the name P.
1272        rooted!(&in(cx) let mut window_proxy_value = UndefinedValue());
1273        named_child_navigable.to_jsval(cx, window_proxy_value.handle_mut());
1274        // Step 6.2 Return PropertyDescriptor { [[Value]]: value, [[Enumerable]]: false,
1275        // [[Writable]]: false, [[Configurable]]: true }.
1276        set_property_descriptor(
1277            property_descriptor.reborrow(),
1278            window_proxy_value.handle(),
1279            JSPROP_READONLY as u32,
1280            is_none,
1281        );
1282        return true;
1283    }
1284
1285    // Step 7. Return ? CrossOriginPropertyFallback(P).
1286    cross_origin_property_fallback::<DomTypeHolder>(
1287        cx,
1288        proxy,
1289        id,
1290        property_descriptor.reborrow(),
1291        is_none,
1292    )
1293}
1294
1295/// <https://html.spec.whatwg.org/multipage/#windowproxy-defineownproperty>
1296#[expect(unsafe_code)]
1297unsafe extern "C" fn define_property(
1298    cx: *mut RawJSContext,
1299    proxy: RawHandleObject,
1300    id: RawHandleId,
1301    desc: RawHandle<PropertyDescriptor>,
1302    res: *mut ObjectOpResult,
1303) -> bool {
1304    let mut cx = unsafe {
1305        // SAFETY: We are in a SpiderMonkey hook, so it is always safe to convert a raw context into
1306        // a mozjs context.
1307        JSContext::from_ptr(NonNull::new(cx).expect("JSContext should not be null in SM hook"))
1308    };
1309    let mut cx = CurrentRealm::assert(&mut cx);
1310    let cx = &mut cx;
1311    let id = unsafe { Handle::from_raw(id) };
1312    let proxy = unsafe { Handle::from_raw(proxy) };
1313
1314    // Step 1. Let W be the value of the [[Window]] internal slot of this.
1315    // Note: This is the `proxy` argument.
1316
1317    // Step 2. If IsPlatformObjectSameOrigin(W) is true:
1318    if is_platform_object_same_origin(cx, proxy) {
1319        // Step 2.1. If P is an array index property name, return false.
1320        if get_array_index_from_id(id).is_some() {
1321            // Spec says to Reject whether this is a supported index or not,
1322            // since we have no indexed setter or indexed creator.  That means
1323            // throwing in strict mode (FIXME: Bug 828137), doing nothing in
1324            // non-strict mode.
1325            unsafe {
1326                (*res).code_ = JSErrNum::JSMSG_CANT_DEFINE_WINDOW_ELEMENT as usize;
1327            }
1328            return true;
1329        }
1330
1331        // Step 2.2. Return ? OrdinaryDefineOwnProperty(W, P, Desc).
1332        rooted!(&in(cx) let target = window_proxy_target(proxy));
1333        return unsafe {
1334            JS_DefinePropertyById(cx.raw_cx(), target.handle().into(), id.into(), desc, res)
1335        };
1336    }
1337
1338    // Step 3. Throw a "SecurityError" DOMException.
1339    report_cross_origin_denial::<DomTypeHolder>(cx, id, "define")
1340}
1341
1342#[expect(unsafe_code)]
1343unsafe extern "C" fn has(
1344    cx: *mut RawJSContext,
1345    proxy: RawHandleObject,
1346    id: RawHandleId,
1347    bp: *mut bool,
1348) -> bool {
1349    unsafe { has_or_has_own(cx, proxy, id, bp, IncludePrototypes::Yes) }
1350}
1351
1352#[expect(unsafe_code)]
1353unsafe extern "C" fn has_own(
1354    cx: *mut RawJSContext,
1355    proxy: RawHandleObject,
1356    id: RawHandleId,
1357    bp: *mut bool,
1358) -> bool {
1359    unsafe { has_or_has_own(cx, proxy, id, bp, IncludePrototypes::No) }
1360}
1361
1362enum IncludePrototypes {
1363    Yes,
1364    No,
1365}
1366
1367#[expect(unsafe_code)]
1368unsafe fn has_or_has_own(
1369    cx: *mut RawJSContext,
1370    proxy: RawHandleObject,
1371    id: RawHandleId,
1372    bp: *mut bool,
1373    include_prototypes: IncludePrototypes,
1374) -> bool {
1375    let mut cx = unsafe { JSContext::from_ptr(ptr::NonNull::new(cx).unwrap()) };
1376    let mut cx = CurrentRealm::assert(&mut cx);
1377    let cx = &mut cx;
1378    let proxy = unsafe { Handle::from_raw(proxy) };
1379    let id = unsafe { Handle::from_raw(id) };
1380
1381    rooted!(&in(cx) let target = window_proxy_target(proxy));
1382    let window = WindowOrDissimilarOriginWindow::new(cx, target.handle());
1383
1384    let (success, found) = if is_platform_object_same_origin(cx, proxy) {
1385        if let Some(array_index) = get_array_index_from_id(id) &&
1386            window
1387                .window_proxy_for_child_navigable_at_index(array_index)
1388                .is_some()
1389        {
1390            unsafe { *bp = true };
1391            return true;
1392        }
1393
1394        let mut found = false;
1395        let success = match include_prototypes {
1396            IncludePrototypes::Yes => unsafe {
1397                JS_HasPropertyById(cx, target.handle(), id, &mut found)
1398            },
1399            IncludePrototypes::No => unsafe {
1400                JS_HasOwnPropertyById(cx, target.handle(), id, &mut found)
1401            },
1402        };
1403        (success, found)
1404    } else {
1405        rooted!(&in(cx) let mut property_descriptor = PropertyDescriptor::default());
1406        let mut is_none = false;
1407        let success = unsafe {
1408            get_own_property_descriptor(
1409                cx.raw_cx(),
1410                proxy.into(),
1411                id.into(),
1412                property_descriptor.handle_mut().into(),
1413                &mut is_none,
1414            )
1415        };
1416        (success, !is_none)
1417    };
1418
1419    if !success {
1420        return false;
1421    }
1422    unsafe { *bp = found };
1423    true
1424}
1425
1426/// <https://html.spec.whatwg.org/multipage/#windowproxy-get>
1427#[expect(unsafe_code)]
1428unsafe extern "C" fn get(
1429    cx: *mut RawJSContext,
1430    proxy: RawHandleObject,
1431    receiver: RawHandleValue,
1432    id: RawHandleId,
1433    return_value: RawMutableHandleValue,
1434) -> bool {
1435    let mut cx = unsafe {
1436        // SAFETY: We are in SM hook
1437        JSContext::from_ptr(NonNull::new(cx).expect("JSContext should not be null in SM hook"))
1438    };
1439    let mut cx = CurrentRealm::assert(&mut cx);
1440    let cx = &mut cx;
1441    let proxy = unsafe { Handle::from_raw(proxy) };
1442    let receiver = unsafe { Handle::from_raw(receiver) };
1443    let id = unsafe { Handle::from_raw(id) };
1444    let return_value = unsafe { MutableHandle::from_raw(return_value) };
1445
1446    // Step 1. Let W be the value of the [[Window]] internal slot of this.
1447    rooted!(&in(cx) let target = window_proxy_target(proxy));
1448    let window = WindowOrDissimilarOriginWindow::new(cx, target.handle());
1449
1450    // Step 2. Check if an access between two browsing contexts should be reported, given the
1451    // current global object's browsing context, W's browsing context, P, and the current settings
1452    // object.
1453    // TODO: Implement this.
1454
1455    // Step 3. If IsPlatformObjectSameOrigin(W) is true, then return ? OrdinaryGet(this, P, Receiver).
1456    if is_platform_object_same_origin(cx, proxy) {
1457        if let Some(index) = get_array_index_from_id(id) &&
1458            let Some(window_proxy) = window.window_proxy_for_child_navigable_at_index(index)
1459        {
1460            window_proxy.to_jsval(cx, return_value);
1461            return true;
1462        }
1463
1464        return unsafe { JS_ForwardGetPropertyTo(cx, target.handle(), id, receiver, return_value) };
1465    }
1466
1467    // Step 4. Return ? CrossOriginGet(this, P, Receiver).
1468    proxyhandler::cross_origin_get::<DomTypeHolder>(cx, proxy, receiver, id, return_value)
1469}
1470
1471/// <https://html.spec.whatwg.org/multipage/#windowproxy-set>
1472#[expect(unsafe_code)]
1473unsafe extern "C" fn set(
1474    cx: *mut RawJSContext,
1475    proxy: RawHandleObject,
1476    id: RawHandleId,
1477    v: RawHandleValue,
1478    receiver: RawHandleValue,
1479    res: *mut ObjectOpResult,
1480) -> bool {
1481    let mut cx = unsafe {
1482        // SAFETY: We are in SM hook
1483        JSContext::from_ptr(NonNull::new(cx).expect("JSContext should not be null in SM hook"))
1484    };
1485    let mut cx = CurrentRealm::assert(&mut cx);
1486    let cx = &mut cx;
1487
1488    // Step 1. Let W be the value of the [[Window]] internal slot of this.
1489    // Note: This is the `proxy` argument.
1490
1491    // Step 2. Check if an access between two browsing contexts should be reported, given the
1492    // current global object's browsing context, W's browsing context, P, and the current settings
1493    // object.
1494    // TODO: Implement this.
1495
1496    let proxy = unsafe { Handle::from_raw(proxy) };
1497    let id = unsafe { Handle::from_raw(id) };
1498
1499    // Step 3. If IsPlatformObjectSameOrigin(W) is true:
1500    if is_platform_object_same_origin(cx, proxy) {
1501        // Step 3.1. If P is an array index property name, then return false.
1502        if get_array_index_from_id(id).is_some() {
1503            // Reject (which means throw if and only if strict) the set.
1504            unsafe { (*res).code_ = JSErrNum::JSMSG_READ_ONLY as usize };
1505            return true;
1506        }
1507
1508        // Step 3.2. Return ? OrdinarySet(W, P, V, Receiver).
1509        rooted!(&in(cx) let target = window_proxy_target(proxy));
1510        return unsafe {
1511            JS_ForwardSetPropertyTo(
1512                cx.raw_cx(),
1513                target.handle().into(),
1514                id.into(),
1515                v,
1516                receiver,
1517                res,
1518            )
1519        };
1520    }
1521
1522    // Step 4. Return ? CrossOriginSet(this, P, V, Receiver).
1523    let receiver = unsafe { HandleValue::from_raw(receiver) };
1524    unsafe { cross_origin_set::<DomTypeHolder>(cx, proxy, id, v, receiver, res) }
1525}
1526
1527#[expect(unsafe_code)]
1528unsafe extern "C" fn get_prototype_if_ordinary(
1529    _: *mut RawJSContext,
1530    _: RawHandleObject,
1531    is_ordinary: *mut bool,
1532    _: RawMutableHandleObject,
1533) -> bool {
1534    // Window's [[GetPrototypeOf]] trap isn't the ordinary definition:
1535    //
1536    //   https://html.spec.whatwg.org/multipage/#windowproxy-getprototypeof
1537    //
1538    // We nonetheless can implement it with a static [[Prototype]], because
1539    // wrapper-class handlers (particularly, XOW in FilteringWrapper.cpp) supply
1540    // all non-ordinary behavior.
1541    //
1542    // But from a spec point of view, it's the exact same object in both cases --
1543    // only the observer's changed.  So this getPrototypeIfOrdinary trap on the
1544    // non-wrapper object *must* report non-ordinary, even if static [[Prototype]]
1545    // usually means ordinary.
1546    unsafe { *is_ordinary = false };
1547    true
1548}
1549
1550/// <https://html.spec.whatwg.org/multipage/#windowproxy-getprototypeof>
1551#[expect(unsafe_code)]
1552unsafe extern "C" fn get_prototype(
1553    cx: *mut RawJSContext,
1554    proxy: RawHandleObject,
1555    result: RawMutableHandleObject,
1556) -> bool {
1557    let mut cx = unsafe { JSContext::from_ptr(ptr::NonNull::new(cx).unwrap()) };
1558    let mut realm = CurrentRealm::assert(&mut cx);
1559    let proxy = unsafe { Handle::from_raw(proxy) };
1560    let result = unsafe { MutableHandleObject::from_raw(result) };
1561    maybe_cross_origin_get_prototype::<DomTypeHolder>(
1562        &mut realm,
1563        proxy,
1564        GetProtoObject::<DomTypeHolder>,
1565        result,
1566    )
1567}
1568
1569/// <https://html.spec.whatwg.org/multipage/#windowproxy-delete>
1570#[expect(unsafe_code)]
1571unsafe extern "C" fn delete(
1572    cx: *mut RawJSContext,
1573    proxy: RawHandleObject,
1574    id: RawHandleId,
1575    result: *mut ObjectOpResult,
1576) -> bool {
1577    let mut cx = unsafe { JSContext::from_ptr(ptr::NonNull::new(cx).unwrap()) };
1578    let mut cx = CurrentRealm::assert(&mut cx);
1579    let cx = &mut cx;
1580    let proxy = unsafe { Handle::from_raw(proxy) };
1581    let id = unsafe { Handle::from_raw(id) };
1582
1583    // Step 1. Let W be the value of the [[Window]] internal slot of this.
1584    rooted!(&in(cx) let target = window_proxy_target(proxy));
1585    let window = WindowOrDissimilarOriginWindow::new(cx, target.handle());
1586
1587    // Step 2. If IsPlatformObjectSameOrigin(W) is true:
1588    if is_platform_object_same_origin(cx, proxy) {
1589        // Step 2.1 If P is an array index property name:
1590        if let Some(array_index) = get_array_index_from_id(id) {
1591            // Step 2.1.1. Let desc be ! this.[[GetOwnProperty]](P).
1592            let code = if window
1593                .window_proxy_for_child_navigable_at_index(array_index)
1594                .is_none()
1595            {
1596                // Step 2.1.2. If desc is undefined, then return true.
1597                0 /* OkCode */
1598            } else {
1599                // Step 2.1.3. Return false.
1600                JSErrNum::JSMSG_CANT_DELETE_WINDOW_ELEMENT as usize
1601            };
1602            unsafe { (*result).code_ = code };
1603            return true;
1604        }
1605        // Step 2.2. Return ? OrdinaryDelete(W, P).
1606        rooted!(&in(cx) let target = window_proxy_target(proxy));
1607        return unsafe {
1608            JS_DeletePropertyById(cx.raw_cx(), target.handle().into(), id.into(), result)
1609        };
1610    }
1611
1612    // Step 3. Throw a "SecurityError" DOMException.
1613    report_cross_origin_denial::<DomTypeHolder>(cx, id, "delete")
1614}
1615
1616/// <https://html.spec.whatwg.org/multipage#windowproxy-ownpropertykeys>
1617#[expect(unsafe_code)]
1618unsafe extern "C" fn own_property_keys(
1619    cx: *mut RawJSContext,
1620    proxy: RawHandleObject,
1621    property_keys: RawMutableHandleIdVector,
1622) -> bool {
1623    let mut cx = unsafe { JSContext::from_ptr(ptr::NonNull::new(cx).unwrap()) };
1624    let mut cx = CurrentRealm::assert(&mut cx);
1625    let cx = &mut cx;
1626    let proxy = unsafe { Handle::from_raw(proxy) };
1627
1628    // Step 1. Let W be the value of the [[Window]] internal slot of this.
1629    rooted!(&in(cx) let target = window_proxy_target(proxy));
1630    let window = WindowOrDissimilarOriginWindow::new(cx, target.handle());
1631
1632    // Step 2. Let maxProperties be W's associated Document's document-tree child navigables's
1633    // size.
1634    //
1635    // TODO: DissimilarOriginWindow currently always returns 0 for the length,
1636    // so this has the effect of not exposing any indexable attributes.
1637    let max_properties = window.iframe_count();
1638
1639    // Step 3. Let keys be the range 0 to maxProperties, exclusive.
1640    rooted!(&in(cx) let mut rooted_index_jsid: jsid);
1641    for index in 0..max_properties {
1642        unsafe { int_to_jsid(index as i32, rooted_index_jsid.handle_mut()) };
1643        unsafe { AppendToIdVector(property_keys, rooted_index_jsid.handle()) };
1644    }
1645
1646    if is_platform_object_same_origin(cx, proxy) {
1647        // Step 4. If IsPlatformObjectSameOrigin(W) is true, then return the concatenation of keys and
1648        // OrdinaryOwnPropertyKeys(W).
1649        return unsafe {
1650            GetPropertyKeys(
1651                cx,
1652                target.handle(),
1653                JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS,
1654                property_keys,
1655            )
1656        };
1657    }
1658
1659    // Step 5. Return the concatenation of keys and ! CrossOriginOwnPropertyKeys(W).
1660    cross_origin_own_property_keys(cx, proxy, window.cross_origin_properties(), property_keys)
1661}
1662
1663static PROXY_TRAPS: ProxyTraps = ProxyTraps {
1664    enter: None,
1665    getOwnPropertyDescriptor: Some(get_own_property_descriptor),
1666    defineProperty: Some(define_property),
1667    ownPropertyKeys: Some(own_property_keys),
1668    delete_: Some(delete),
1669    enumerate: None,
1670    getPrototypeIfOrdinary: Some(get_prototype_if_ordinary),
1671    getPrototype: Some(get_prototype),
1672    setPrototype: Some(maybe_cross_origin_set_prototype_rawcx),
1673    setImmutablePrototype: None,
1674    preventExtensions: Some(prevent_extensions),
1675    isExtensible: Some(is_extensible),
1676    has: Some(has),
1677    get: Some(get),
1678    set: Some(set),
1679    call: None,
1680    construct: None,
1681    hasOwn: Some(has_own),
1682    getOwnEnumerablePropertyKeys: None,
1683    nativeCall: None,
1684    objectClassIs: None,
1685    className: None,
1686    fun_toString: None,
1687    boxedValue_unbox: None,
1688    defaultValue: None,
1689    trace: Some(trace),
1690    finalize: Some(finalize),
1691    objectMoved: None,
1692    isCallable: None,
1693    isConstructor: None,
1694};
1695
1696/// Proxy handler for a WindowProxy.
1697/// Has ownership of the inner pointer and deallocates it when it is no longer needed.
1698pub(crate) struct WindowProxyHandler(*const libc::c_void);
1699
1700impl MallocSizeOf for WindowProxyHandler {
1701    fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize {
1702        // FIXME(#6907) this is a pointer to memory allocated by `new` in NewProxyHandler in rust-mozjs.
1703        0
1704    }
1705}
1706
1707// Safety: Send and Sync is guaranteed since the underlying pointer and all its associated methods in C++ are const.
1708#[expect(unsafe_code)]
1709unsafe impl Send for WindowProxyHandler {}
1710// Safety: Send and Sync is guaranteed since the underlying pointer and all its associated methods in C++ are const.
1711#[expect(unsafe_code)]
1712unsafe impl Sync for WindowProxyHandler {}
1713
1714#[expect(unsafe_code)]
1715impl WindowProxyHandler {
1716    fn new(traps: &ProxyTraps) -> Self {
1717        // Safety: Foreign function generated by bindgen. Pointer is freed in drop to prevent memory leak.
1718        let ptr = unsafe { CreateWrapperProxyHandler(traps) };
1719        assert!(!ptr.is_null());
1720        Self(ptr)
1721    }
1722
1723    /// Returns a single, shared WindowProxyHandler that contains normal PROXY_TRAPS.
1724    pub(crate) fn proxy_handler() -> &'static Self {
1725        use std::sync::OnceLock;
1726        /// We are sharing a single instance for the entire programs here due to lifetime issues.
1727        /// The pointer in self.0 is known to C++ and visited by the GC. Hence, we don't know when
1728        /// it is safe to free it.
1729        /// Sharing a single instance should be fine because all methods on this pointer in C++
1730        /// are const and don't modify its internal state.
1731        static SINGLETON: OnceLock<WindowProxyHandler> = OnceLock::new();
1732        SINGLETON.get_or_init(|| Self::new(&PROXY_TRAPS))
1733    }
1734
1735    /// Creates a new WindowProxy object on the C++ side and returns the pointer to it.
1736    /// The pointer should be owned by the GC.
1737    fn new_window_proxy(
1738        &self,
1739        cx: &mut JSContext,
1740        window_jsobject: js::gc::HandleObject,
1741    ) -> *mut JSObject {
1742        let obj = unsafe { NewWindowProxy(cx, window_jsobject, self.0) };
1743        assert!(!obj.is_null());
1744        obj
1745    }
1746}
1747
1748#[expect(unsafe_code)]
1749impl Drop for WindowProxyHandler {
1750    fn drop(&mut self) {
1751        // Safety: Pointer is allocated by corresponding C++ function, owned by this
1752        // struct and not accessible from outside.
1753        unsafe {
1754            DeleteWrapperProxyHandler(self.0);
1755        }
1756    }
1757}
1758
1759// How WindowProxy objects are garbage collected.
1760
1761#[expect(unsafe_code)]
1762unsafe extern "C" fn finalize(_fop: *mut GCContext, obj: *mut JSObject) {
1763    let mut slot = UndefinedValue();
1764    unsafe { GetProxyReservedSlot(obj, 0, &mut slot) };
1765    let this = slot.to_private() as *mut WindowProxy;
1766    if this.is_null() {
1767        // GC during obj creation or after transplanting.
1768        return;
1769    }
1770    unsafe {
1771        (*this).reflector.drop_memory(&*this);
1772        let jsobject = (*this).reflector.get_jsobject().get();
1773        debug!(
1774            "WindowProxy finalize: {:p}, with reflector {:p} from {:p}.",
1775            this, jsobject, obj
1776        );
1777        let _ = Box::from_raw(this);
1778    }
1779}
1780
1781#[expect(unsafe_code)]
1782unsafe extern "C" fn trace(trc: *mut JSTracer, obj: *mut JSObject) {
1783    let mut slot = UndefinedValue();
1784    unsafe { GetProxyReservedSlot(obj, 0, &mut slot) };
1785    let this = slot.to_private() as *const WindowProxy;
1786    if this.is_null() {
1787        // GC during obj creation or after transplanting.
1788        return;
1789    }
1790    unsafe { (*this).trace(trc) };
1791}
1792
1793/// A wrapper class for either a [`Window`] or [`DissimilarOriginWindow`] that
1794/// exposes a consistent interface for both of them.
1795enum WindowOrDissimilarOriginWindow {
1796    Window(DomRoot<Window>),
1797    DissimilarOriginWindow(DomRoot<DissimilarOriginWindow>),
1798}
1799
1800impl WindowOrDissimilarOriginWindow {
1801    fn new(cx: &mut JSContext, handle_object: HandleObject) -> Self {
1802        if let Ok(window) = root_from_handleobject::<Window>(cx, handle_object) {
1803            Self::Window(window)
1804        } else if let Ok(window) =
1805            root_from_handleobject::<DissimilarOriginWindow>(cx, handle_object)
1806        {
1807            Self::DissimilarOriginWindow(window)
1808        } else {
1809            unreachable!("WindowProxy should always be backed by some kind of window");
1810        }
1811    }
1812
1813    fn global_scope(&self) -> DomRoot<GlobalScope> {
1814        match self {
1815            WindowOrDissimilarOriginWindow::Window(window) => window.global(),
1816            WindowOrDissimilarOriginWindow::DissimilarOriginWindow(window) => window.global(),
1817        }
1818    }
1819
1820    fn window_proxy(&self) -> DomRoot<WindowProxy> {
1821        match self {
1822            WindowOrDissimilarOriginWindow::Window(window) => window.window_proxy(),
1823            WindowOrDissimilarOriginWindow::DissimilarOriginWindow(window) => window.window_proxy(),
1824        }
1825    }
1826
1827    #[expect(unsafe_code)]
1828    fn cross_origin_properties(&self) -> &'static CrossOriginProperties {
1829        match self {
1830            WindowOrDissimilarOriginWindow::Window(..) => unsafe {
1831                WindowBinding::CROSS_ORIGIN_PROPERTIES.get()
1832            },
1833            WindowOrDissimilarOriginWindow::DissimilarOriginWindow(..) => unsafe {
1834                DissimilarOriginWindowBinding::CROSS_ORIGIN_PROPERTIES.get()
1835            },
1836        }
1837    }
1838
1839    fn iframe_count(&self) -> u32 {
1840        match self {
1841            WindowOrDissimilarOriginWindow::Window(window) => window.Length(),
1842            WindowOrDissimilarOriginWindow::DissimilarOriginWindow(window) => window.Length(),
1843        }
1844    }
1845
1846    fn window_proxy_for_child_navigable_at_index(
1847        &self,
1848        index: u32,
1849    ) -> Option<DomRoot<WindowProxy>> {
1850        let browsing_context_id = self.window_proxy().browsing_context_id();
1851        let (result_sender, result_receiver) = generic_channel::channel().unwrap();
1852        let _ = self.global_scope().script_to_constellation_chan().send(
1853            ScriptToConstellationMessage::GetChildBrowsingContextId(
1854                browsing_context_id,
1855                index as usize,
1856                result_sender,
1857            ),
1858        );
1859        result_receiver
1860            .recv()
1861            .ok()
1862            .flatten()
1863            .and_then(|id| ScriptThread::window_proxies().find_window_proxy(id))
1864    }
1865
1866    /// <https://html.spec.whatwg.org/multipage/#document-tree-child-navigable-target-name-property-set>
1867    ///
1868    /// > The document-tree child navigable target name property set of a Window object window is the
1869    /// > return value of running these steps:
1870    ///
1871    /// > Step 1. Let children be the document-tree child navigables of window's associated Document.
1872    /// > Step 2. Let firstNamedChildren be an empty ordered set.
1873    /// > Step 3. For each navigable of children:
1874    /// > Step 3.1. Let name be navigable's target name.
1875    /// > Step 3.2. If name is the empty string, then continue.
1876    /// > Step 3.3. If firstNamedChildren contains a navigable whose target name is name, then continue.
1877    /// > Step 3.4. Append navigable to firstNamedChildren.
1878    /// > Step 4. Let names be an empty ordered set.
1879    /// > Step 5. For each navigable of firstNamedChildren:
1880    /// > Step 5.1. Let name be navigable's target name.
1881    /// > Step 5.2. If navigable's active document's origin is same origin with window's relevant settings
1882    /// > object's origin, then append name to names.
1883    /// > Step 6. Return names.
1884    ///
1885    /// We don't implement these steps exactly, but we effectively do them using iterators below.
1886    fn named_child_navigable(
1887        &self,
1888        cx: &mut CurrentRealm,
1889        id: HandleId,
1890    ) -> Option<DomRoot<WindowProxy>> {
1891        // TODO: We cannot yet enumerate cross-origin child navigable target names.
1892        let Self::Window(window) = &self else {
1893            return None;
1894        };
1895
1896        let name = jsid_to_string(cx, id)?;
1897        if name.is_empty() {
1898            return None;
1899        }
1900        window
1901            .Document()
1902            .iframes()
1903            .iter()
1904            .filter_map(|iframe| iframe.GetContentWindow())
1905            .find(|window_proxy| window_proxy.get_name() == name)
1906    }
1907}