Skip to main content

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