Skip to main content

script/
iframe_collection.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::default::Default;
6
7use embedder_traits::ViewportDetails;
8use layout_api::IFrameSizes;
9use paint_api::PinchZoomInfos;
10use script_bindings::script_runtime::CanGc;
11use servo_base::id::BrowsingContextId;
12use servo_constellation_traits::{IFrameSizeMsg, ScriptToConstellationMessage, WindowSizeType};
13
14use crate::dom::NodeTraits;
15use crate::dom::bindings::inheritance::Castable;
16use crate::dom::bindings::root::{Dom, DomRoot};
17use crate::dom::html::htmliframeelement::HTMLIFrameElement;
18use crate::dom::iterators::ShadowIncluding;
19use crate::dom::node::Node;
20use crate::dom::types::Window;
21use crate::script_thread::with_script_thread;
22
23#[derive(JSTraceable, MallocSizeOf)]
24#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
25pub(crate) struct IFrame {
26    pub(crate) element: Dom<HTMLIFrameElement>,
27    #[no_trace]
28    pub(crate) size: Option<ViewportDetails>,
29}
30
31#[derive(Default, JSTraceable, MallocSizeOf)]
32#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
33pub(crate) struct IFrameCollection {
34    /// The `<iframe>`s in the collection. These are kept in DOM tree order to ensure that
35    /// requestAnimationFrame callbacks respect that order.
36    iframes: Vec<IFrame>,
37}
38
39impl IFrameCollection {
40    pub(crate) fn new() -> Self {
41        Self {
42            iframes: Default::default(),
43        }
44    }
45
46    pub(crate) fn add(&mut self, iframe_element: &HTMLIFrameElement) {
47        let iframe_node = iframe_element.upcast::<Node>();
48
49        // During `moveBefore`, nodes are attached to the tree again without detaching
50        // them in order to preserve state. Here we remove any pre-existing entry for
51        // this iframe element from the collection and preserve its old size.
52        let size = self.remove(iframe_element);
53
54        // Look forward for the next `<iframe>` in the document in order to find the new
55        // insertion point in the DOM-ordered list of frames. This optimizes for the parser
56        // case where the `<iframe>` is likely being inserted at the end of the DOM and there
57        // are very few subsequent nodes.
58        let insertion_index = iframe_node
59            .following_nodes(
60                iframe_element.owner_document().upcast::<Node>(),
61                ShadowIncluding::Yes,
62            )
63            .find_map(DomRoot::downcast::<HTMLIFrameElement>)
64            .and_then(|following_iframe| {
65                self.iframes
66                    .iter()
67                    .position(|iframe| *iframe.element == *following_iframe)
68            })
69            .unwrap_or(self.iframes.len());
70
71        self.iframes.insert(
72            insertion_index,
73            IFrame {
74                element: Dom::from_ref(iframe_element),
75                size,
76            },
77        );
78    }
79
80    pub(crate) fn remove(&mut self, iframe_element: &HTMLIFrameElement) -> Option<ViewportDetails> {
81        self.iframes
82            .iter()
83            .position(|iframe| &*iframe.element == iframe_element)
84            .and_then(|index| self.iframes.remove(index).size)
85    }
86
87    pub(crate) fn get(&self, browsing_context_id: BrowsingContextId) -> Option<&IFrame> {
88        self.iframes
89            .iter()
90            .find(|iframe| iframe.element.browsing_context_id() == Some(browsing_context_id))
91    }
92
93    pub(crate) fn get_mut(
94        &mut self,
95        browsing_context_id: BrowsingContextId,
96    ) -> Option<&mut IFrame> {
97        self.iframes
98            .iter_mut()
99            .find(|iframe| iframe.element.browsing_context_id() == Some(browsing_context_id))
100    }
101
102    /// Set the size of an `<iframe>` in the collection given its `BrowsingContextId` and
103    /// the new size. Returns the old size.
104    pub(crate) fn set_viewport_details(
105        &mut self,
106        browsing_context_id: BrowsingContextId,
107        new_size: ViewportDetails,
108    ) -> Option<ViewportDetails> {
109        self.get_mut(browsing_context_id)
110            .expect("Tried to set a size for an unknown <iframe>")
111            .size
112            .replace(new_size)
113    }
114
115    /// Update the recorded iframe sizes of the contents of layout. Return a
116    /// [`Vec<IFrameSizeMsg>`] containing the messages to send to the `Constellation`. A
117    /// message is only sent when the size actually changes.
118    pub(crate) fn handle_new_iframe_sizes_after_layout(
119        &mut self,
120        window: &Window,
121        new_iframe_sizes: IFrameSizes,
122    ) {
123        if new_iframe_sizes.is_empty() {
124            return;
125        }
126
127        let size_messages: Vec<_> = new_iframe_sizes
128            .into_iter()
129            .filter_map(|(browsing_context_id, iframe_size)| {
130                // Batch resize message to any local `Pipeline`s now, rather than waiting for them
131                // to filter asynchronously through the `Constellation`. This allows the new value
132                // to be reflected immediately in layout.
133                let viewport_details = iframe_size.viewport_details;
134                with_script_thread(|script_thread| {
135                    script_thread.handle_resize_message(
136                        iframe_size.pipeline_id,
137                        viewport_details,
138                        WindowSizeType::Resize,
139                    );
140                    // Additionally, update the `VisualViewport` of the `Iframe`. This allows us
141                    // to process the resize for `VisualViewport` in the corrent timing. Note that
142                    // `VisualViewport` for iframes would practically follow layout viewport.
143                    script_thread.handle_update_pinch_zoom_infos(
144                        iframe_size.pipeline_id,
145                        PinchZoomInfos::new_from_viewport_size(viewport_details.size),
146                        // Theoritically it wouldn't do GC since it is impossible to initialize
147                        // the `VisualViewport` interface here.
148                        CanGc::deprecated_note(),
149                    )
150                });
151
152                let old_viewport_details =
153                    self.set_viewport_details(browsing_context_id, viewport_details);
154                // The `Constellation` should be up-to-date even when the in-ScriptThread pipelines
155                // might not be.
156                if old_viewport_details == Some(viewport_details) {
157                    return None;
158                }
159
160                let size_type = match old_viewport_details {
161                    Some(_) => WindowSizeType::Resize,
162                    None => WindowSizeType::Initial,
163                };
164
165                Some(IFrameSizeMsg {
166                    browsing_context_id,
167                    size: viewport_details,
168                    type_: size_type,
169                })
170            })
171            .collect();
172
173        if !size_messages.is_empty() {
174            window.send_to_constellation(ScriptToConstellationMessage::IFrameSizes(size_messages));
175        }
176    }
177
178    pub(crate) fn iter(&self) -> impl Iterator<Item = DomRoot<HTMLIFrameElement>> + use<'_> {
179        self.iframes.iter().map(|iframe| iframe.element.as_rooted())
180    }
181}