Skip to main content

script/event_loop/
document_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::collections::hash_map;
6
7use rustc_hash::{FxBuildHasher, FxHashMap};
8use servo_base::id::{BrowsingContextId, PipelineId};
9
10use crate::dom::bindings::inheritance::Castable;
11use crate::dom::bindings::root::{Dom, DomRoot};
12use crate::dom::bindings::trace::HashMapTracedValues;
13use crate::dom::document::Document;
14use crate::dom::globalscope::GlobalScope;
15use crate::dom::html::htmliframeelement::HTMLIFrameElement;
16use crate::dom::window::Window;
17
18/// The collection of all [`Document`]s managed by the [`crate::event_loop::script_thread::ScriptThread`].
19/// This is stored as a mapping of [`PipelineId`] to [`Document`], but for updating the
20/// rendering, [`Document`]s should be processed in order via [`Self::documents_in_order`].
21#[derive(JSTraceable)]
22#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
23pub(crate) struct DocumentCollection {
24    map: HashMapTracedValues<PipelineId, Dom<Document>, FxBuildHasher>,
25}
26
27impl DocumentCollection {
28    pub(crate) fn insert(&mut self, pipeline_id: PipelineId, doc: &Document) {
29        self.map.insert(pipeline_id, Dom::from_ref(doc));
30    }
31
32    pub(crate) fn remove(&mut self, pipeline_id: PipelineId) -> Option<DomRoot<Document>> {
33        self.map
34            .remove(&pipeline_id)
35            .map(|ref doc| DomRoot::from_ref(&**doc))
36    }
37
38    pub(crate) fn find_document(&self, pipeline_id: PipelineId) -> Option<DomRoot<Document>> {
39        self.map
40            .get(&pipeline_id)
41            .map(|doc| DomRoot::from_ref(&**doc))
42            .filter(|document| !document.window_detached())
43    }
44
45    pub(crate) fn find_window(&self, pipeline_id: PipelineId) -> Option<DomRoot<Window>> {
46        self.find_document(pipeline_id)
47            .map(|doc| DomRoot::from_ref(doc.window()))
48            .filter(|window| window.pipeline_id() == pipeline_id)
49    }
50
51    pub(crate) fn find_global(&self, pipeline_id: PipelineId) -> Option<DomRoot<GlobalScope>> {
52        self.find_window(pipeline_id)
53            .map(|window| DomRoot::from_ref(window.upcast()))
54    }
55
56    pub(crate) fn find_iframe(
57        &self,
58        pipeline_id: PipelineId,
59        browsing_context_id: BrowsingContextId,
60    ) -> Option<DomRoot<HTMLIFrameElement>> {
61        self.find_document(pipeline_id).and_then(|document| {
62            document
63                .iframes()
64                .get(browsing_context_id)
65                .map(|iframe| iframe.element.as_rooted())
66        })
67    }
68
69    pub(crate) fn iter(&self) -> DocumentsIter<'_> {
70        DocumentsIter {
71            iter: self.map.iter(),
72        }
73    }
74
75    /// Return the documents managed by this [`crate::event_loop::script_thread::ScriptThread`] in the
76    /// order specified by the *[update the rendering][update-the-rendering]* step of the
77    /// HTML specification:
78    ///
79    /// > Let docs be all fully active Document objects whose relevant agent's event loop is
80    /// > eventLoop, sorted arbitrarily except that the following conditions must be met:
81    /// >
82    /// > Any Document B whose container document is A must be listed after A in the list.
83    /// >
84    /// > If there are two documents A and B that both have the same non-null container
85    /// > document C, then the order of A and B in the list must match the shadow-including
86    /// > tree order of their respective navigable containers in C's node tree.
87    /// >
88    /// > In the steps below that iterate over docs, each Document must be processed in the
89    /// > order it is found in the list.
90    ///
91    /// [update-the-rendering]: https://html.spec.whatwg.org/multipage/#update-the-rendering
92    pub(crate) fn documents_in_order(&self) -> Vec<PipelineId> {
93        DocumentTree::new(self).documents_in_order()
94    }
95}
96
97impl Default for DocumentCollection {
98    fn default() -> Self {
99        Self {
100            map: HashMapTracedValues::new_fx(),
101        }
102    }
103}
104
105pub(crate) struct DocumentsIter<'a> {
106    iter: hash_map::Iter<'a, PipelineId, Dom<Document>>,
107}
108
109impl Iterator for DocumentsIter<'_> {
110    type Item = (PipelineId, DomRoot<Document>);
111
112    fn next(&mut self) -> Option<(PipelineId, DomRoot<Document>)> {
113        self.iter
114            .next()
115            .map(|(id, doc)| (*id, DomRoot::from_ref(&**doc)))
116    }
117
118    fn size_hint(&self) -> (usize, Option<usize>) {
119        self.iter.size_hint()
120    }
121}
122
123#[derive(Default)]
124struct DocumentTreeNode {
125    parent: Option<PipelineId>,
126    children: Vec<PipelineId>,
127}
128
129/// A tree representation of [`Document`]s managed by the [`ScriptThread`][st], which is used
130/// to generate an ordered set of [`Document`]s for the *update the rendering* step of the
131/// HTML5 specification.
132///
133/// FIXME: The [`ScriptThread`][st] only has a view of [`Document`]s managed by itself,
134/// so if there are interceding iframes managed by other `ScriptThread`s, then the
135/// order of the [`Document`]s may not be correct. Perhaps the Constellation could
136/// ensure that every [`ScriptThread`][st] has the full view of the frame tree.
137///
138/// [st]: crate::event_loop::script_thread::ScriptThread
139#[derive(Default)]
140struct DocumentTree {
141    tree: FxHashMap<PipelineId, DocumentTreeNode>,
142}
143
144impl DocumentTree {
145    fn new(documents: &DocumentCollection) -> Self {
146        let mut tree = DocumentTree::default();
147        for (id, document) in documents.iter() {
148            let children: Vec<PipelineId> = document
149                .iframes()
150                .iter()
151                .filter_map(|iframe| iframe.pipeline_id())
152                .filter(|iframe_pipeline_id| documents.find_document(*iframe_pipeline_id).is_some())
153                .collect();
154            for child in &children {
155                tree.tree.entry(*child).or_default().parent = Some(id);
156            }
157            tree.tree.entry(id).or_default().children = children;
158        }
159        tree
160    }
161
162    fn documents_in_order(&self) -> Vec<PipelineId> {
163        let mut list = Vec::new();
164        for (id, node) in self.tree.iter() {
165            if node.parent.is_none() {
166                self.process_node_for_documents_in_order(*id, &mut list);
167            }
168        }
169        list
170    }
171
172    fn process_node_for_documents_in_order(&self, id: PipelineId, list: &mut Vec<PipelineId>) {
173        list.push(id);
174        for child in self
175            .tree
176            .get(&id)
177            .expect("Should have found child node")
178            .children
179            .iter()
180        {
181            self.process_node_for_documents_in_order(*child, list);
182        }
183    }
184}