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