Skip to main content

script/dom/
dissimilaroriginwindow.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 dom_struct::dom_struct;
6use js::context::JSContext;
7use js::jsapi::{Heap, JSObject};
8use js::jsval::UndefinedValue;
9use js::rust::{CustomAutoRooter, CustomAutoRooterGuard, HandleValue, MutableHandleValue};
10use net_traits::response::HttpsState;
11use servo_base::id::PipelineId;
12use servo_constellation_traits::{
13    RemoteFocusOperation, ScriptToConstellationMessage, StructuredSerializedData,
14};
15use servo_url::ServoUrl;
16
17use crate::dom::bindings::codegen::Bindings::DissimilarOriginWindowBinding;
18use crate::dom::bindings::codegen::Bindings::DissimilarOriginWindowBinding::DissimilarOriginWindowMethods;
19use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowPostMessageOptions;
20use crate::dom::bindings::error::{Error, ErrorResult};
21use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
22use crate::dom::bindings::str::USVString;
23use crate::dom::bindings::structuredclone;
24use crate::dom::bindings::trace::RootedTraceableBox;
25use crate::dom::dissimilaroriginlocation::DissimilarOriginLocation;
26use crate::dom::globalscope::GlobalScope;
27use crate::dom::windowproxy::WindowProxy;
28use crate::script_runtime::CanGc;
29
30/// Represents a dissimilar-origin `Window` that exists in another script thread.
31///
32/// Since the `Window` is in a different script thread, we cannot access it
33/// directly, but some of its accessors (for example `window.parent`)
34/// still need to function.
35///
36/// In `windowproxy.rs`, we create a custom window proxy for these windows,
37/// that throws security exceptions for most accessors. This is not a replacement
38/// for XOWs, but provides belt-and-braces security.
39#[dom_struct]
40pub(crate) struct DissimilarOriginWindow {
41    /// The global for this window.
42    globalscope: GlobalScope,
43
44    /// The window proxy for this window.
45    window_proxy: Dom<WindowProxy>,
46
47    /// The location of this window, initialized lazily.
48    location: MutNullableDom<DissimilarOriginLocation>,
49}
50
51impl DissimilarOriginWindow {
52    pub(crate) fn new(
53        cx: &mut js::context::JSContext,
54        global_to_clone_from: &GlobalScope,
55        window_proxy: &WindowProxy,
56    ) -> DomRoot<Self> {
57        let win = Box::new(Self {
58            globalscope: GlobalScope::new_inherited(
59                PipelineId::new(),
60                global_to_clone_from.devtools_chan().cloned(),
61                global_to_clone_from.mem_profiler_chan().clone(),
62                global_to_clone_from.time_profiler_chan().clone(),
63                global_to_clone_from.script_to_constellation_chan().clone(),
64                global_to_clone_from.script_to_embedder_chan().clone(),
65                global_to_clone_from.resource_threads().clone(),
66                global_to_clone_from.storage_threads().clone(),
67                global_to_clone_from.origin().clone(),
68                global_to_clone_from.creation_url(),
69                global_to_clone_from.top_level_creation_url().clone(),
70                #[cfg(feature = "webgpu")]
71                global_to_clone_from.wgpu_id_hub(),
72                Some(global_to_clone_from.is_secure_context()),
73                false,
74                global_to_clone_from.font_context().cloned(),
75                HttpsState::None,
76            ),
77            window_proxy: Dom::from_ref(window_proxy),
78            location: Default::default(),
79        });
80        DissimilarOriginWindowBinding::Wrap::<crate::DomTypeHolder>(cx, win)
81    }
82
83    pub(crate) fn window_proxy(&self) -> DomRoot<WindowProxy> {
84        DomRoot::from_ref(&*self.window_proxy)
85    }
86}
87
88impl DissimilarOriginWindowMethods<crate::DomTypeHolder> for DissimilarOriginWindow {
89    /// <https://html.spec.whatwg.org/multipage/#dom-window>
90    fn Window(&self) -> DomRoot<WindowProxy> {
91        self.window_proxy()
92    }
93
94    /// <https://html.spec.whatwg.org/multipage/#dom-self>
95    fn Self_(&self) -> DomRoot<WindowProxy> {
96        self.window_proxy()
97    }
98
99    /// <https://html.spec.whatwg.org/multipage/#dom-frames>
100    fn Frames(&self) -> DomRoot<WindowProxy> {
101        self.window_proxy()
102    }
103
104    /// <https://html.spec.whatwg.org/multipage/#dom-parent>
105    fn GetParent(&self) -> Option<DomRoot<WindowProxy>> {
106        // Steps 1-3.
107        if self.window_proxy.is_browsing_context_discarded() {
108            return None;
109        }
110        // Step 4.
111        if let Some(parent) = self.window_proxy.parent() {
112            return Some(DomRoot::from_ref(parent));
113        }
114        // Step 5.
115        Some(DomRoot::from_ref(&*self.window_proxy))
116    }
117
118    /// <https://html.spec.whatwg.org/multipage/#dom-top>
119    fn GetTop(&self) -> Option<DomRoot<WindowProxy>> {
120        // Steps 1-3.
121        if self.window_proxy.is_browsing_context_discarded() {
122            return None;
123        }
124        // Steps 4-5.
125        Some(DomRoot::from_ref(self.window_proxy.top()))
126    }
127
128    /// <https://html.spec.whatwg.org/multipage/#dom-length>
129    fn Length(&self) -> u32 {
130        // TODO: Implement x-origin length
131        0
132    }
133
134    /// <https://html.spec.whatwg.org/multipage/#dom-window-close>
135    fn Close(&self) {
136        // TODO: Implement x-origin close
137    }
138
139    /// <https://html.spec.whatwg.org/multipage/#dom-window-closed>
140    fn Closed(&self) -> bool {
141        // TODO: Implement x-origin close
142        false
143    }
144
145    /// <https://html.spec.whatwg.org/multipage/#dom-window-postmessage>
146    fn PostMessage(
147        &self,
148        cx: &mut JSContext,
149        message: HandleValue,
150        target_origin: USVString,
151        transfer: CustomAutoRooterGuard<Vec<*mut JSObject>>,
152    ) -> ErrorResult {
153        self.post_message_impl(&target_origin, cx, message, transfer)
154    }
155
156    /// <https://html.spec.whatwg.org/multipage/#dom-window-postmessage-options>
157    fn PostMessage_(
158        &self,
159        cx: &mut JSContext,
160        message: HandleValue,
161        options: RootedTraceableBox<WindowPostMessageOptions>,
162    ) -> ErrorResult {
163        let mut rooted = CustomAutoRooter::new(
164            options
165                .parent
166                .transfer
167                .iter()
168                .map(|js: &RootedTraceableBox<Heap<*mut JSObject>>| js.get())
169                .collect(),
170        );
171        #[expect(unsafe_code)]
172        let transfer = unsafe { CustomAutoRooterGuard::new(cx.raw_cx(), &mut rooted) };
173
174        self.post_message_impl(&options.targetOrigin, cx, message, transfer)
175    }
176
177    /// <https://html.spec.whatwg.org/multipage/#dom-opener>
178    fn Opener(&self, _: &mut JSContext, mut retval: MutableHandleValue) {
179        // TODO: Implement x-origin opener
180        retval.set(UndefinedValue());
181    }
182
183    /// <https://html.spec.whatwg.org/multipage/#dom-opener>
184    fn SetOpener(&self, _: &mut JSContext, _: HandleValue) {
185        // TODO: Implement x-origin opener
186    }
187
188    /// <https://html.spec.whatwg.org/multipage/#dom-window-blur>
189    fn Blur(&self) {
190        // > User agents are encouraged to ignore calls to this `blur()` method
191        // > entirely.
192    }
193
194    /// <https://html.spec.whatwg.org/multipage/#dom-window-focus>
195    fn Focus(&self) {
196        let browsing_context_id = self.window_proxy.browsing_context_id();
197        debug!("Initiating a focus operation for {browsing_context_id:?}");
198        let _ = self.globalscope.script_to_constellation_chan().send(
199            ScriptToConstellationMessage::FocusRemoteBrowsingContext(
200                browsing_context_id,
201                RemoteFocusOperation::Viewport,
202            ),
203        );
204    }
205
206    /// <https://html.spec.whatwg.org/multipage/#dom-location>
207    fn Location(&self, can_gc: CanGc) -> DomRoot<DissimilarOriginLocation> {
208        self.location
209            .or_init(|| DissimilarOriginLocation::new(self, can_gc))
210    }
211}
212
213impl DissimilarOriginWindow {
214    /// <https://html.spec.whatwg.org/multipage/#window-post-message-steps>
215    fn post_message_impl(
216        &self,
217        target_origin: &USVString,
218        cx: &mut JSContext,
219        message: HandleValue,
220        transfer: CustomAutoRooterGuard<Vec<*mut JSObject>>,
221    ) -> ErrorResult {
222        // Step 6-7.
223        let data = structuredclone::write(cx.into(), message, Some(transfer))?;
224
225        self.post_message(target_origin, data)
226    }
227
228    /// <https://html.spec.whatwg.org/multipage/#window-post-message-steps>
229    pub(crate) fn post_message(
230        &self,
231        target_origin: &USVString,
232        data: StructuredSerializedData,
233    ) -> ErrorResult {
234        // Step 1.
235        let target = self.window_proxy.browsing_context_id();
236        // Step 2.
237        let incumbent = match GlobalScope::incumbent() {
238            None => panic!("postMessage called with no incumbent global"),
239            Some(incumbent) => incumbent,
240        };
241
242        let source_origin = incumbent.origin().immutable().clone();
243
244        // Step 3-5.
245        let target_origin = match target_origin.0[..].as_ref() {
246            "*" => None,
247            "/" => Some(source_origin.clone()),
248            url => match ServoUrl::parse(url) {
249                Ok(url) => Some(url.origin()),
250                Err(_) => return Err(Error::Syntax(None)),
251            },
252        };
253        let msg = ScriptToConstellationMessage::PostMessage {
254            target,
255            source: incumbent.pipeline_id(),
256            source_origin,
257            target_origin,
258            data,
259        };
260        // Step 8
261        let _ = incumbent.script_to_constellation_chan().send(msg);
262        Ok(())
263    }
264}