| /* This Source Code Form is subject to the terms of the Mozilla Public |
| * License, v. 2.0. If a copy of the MPL was not distributed with this |
| * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ |
| |
| use std::collections::{HashMap, hash_map}; |
| |
| use base::id::{BrowsingContextId, PipelineId}; |
| |
| use crate::dom::bindings::inheritance::Castable; |
| use crate::dom::bindings::root::{Dom, DomRoot}; |
| use crate::dom::bindings::trace::HashMapTracedValues; |
| use crate::dom::document::Document; |
| use crate::dom::globalscope::GlobalScope; |
| use crate::dom::html::htmliframeelement::HTMLIFrameElement; |
| use crate::dom::window::Window; |
| |
| /// The collection of all [`Document`]s managed by the [`crate::script_thread::ScriptThread`]. |
| /// This is stored as a mapping of [`PipelineId`] to [`Document`], but for updating the |
| /// rendering, [`Document`]s should be processed in order via [`Self::documents_in_order`]. |
| #[derive(JSTraceable)] |
| #[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] |
| pub(crate) struct DocumentCollection { |
| map: HashMapTracedValues<PipelineId, Dom<Document>>, |
| } |
| |
| impl DocumentCollection { |
| pub(crate) fn insert(&mut self, pipeline_id: PipelineId, doc: &Document) { |
| self.map.insert(pipeline_id, Dom::from_ref(doc)); |
| } |
| |
| pub(crate) fn remove(&mut self, pipeline_id: PipelineId) -> Option<DomRoot<Document>> { |
| self.map |
| .remove(&pipeline_id) |
| .map(|ref doc| DomRoot::from_ref(&**doc)) |
| } |
| |
| pub(crate) fn find_document(&self, pipeline_id: PipelineId) -> Option<DomRoot<Document>> { |
| self.map |
| .get(&pipeline_id) |
| .map(|doc| DomRoot::from_ref(&**doc)) |
| } |
| |
| pub(crate) fn find_window(&self, pipeline_id: PipelineId) -> Option<DomRoot<Window>> { |
| self.find_document(pipeline_id) |
| .map(|doc| DomRoot::from_ref(doc.window())) |
| } |
| |
| pub(crate) fn find_global(&self, pipeline_id: PipelineId) -> Option<DomRoot<GlobalScope>> { |
| self.find_window(pipeline_id) |
| .map(|window| DomRoot::from_ref(window.upcast())) |
| } |
| |
| pub(crate) fn find_iframe( |
| &self, |
| pipeline_id: PipelineId, |
| browsing_context_id: BrowsingContextId, |
| ) -> Option<DomRoot<HTMLIFrameElement>> { |
| self.find_document(pipeline_id).and_then(|document| { |
| document |
| .iframes() |
| .get(browsing_context_id) |
| .map(|iframe| iframe.element.as_rooted()) |
| }) |
| } |
| |
| pub(crate) fn iter(&self) -> DocumentsIter<'_> { |
| DocumentsIter { |
| iter: self.map.iter(), |
| } |
| } |
| |
| /// Return the documents managed by this [`crate::script_thread::ScriptThread`] in the |
| /// order specified by the *[update the rendering][update-the-rendering]* step of the |
| /// HTML specification: |
| /// |
| /// > Let docs be all fully active Document objects whose relevant agent's event loop is |
| /// > eventLoop, sorted arbitrarily except that the following conditions must be met: |
| /// > |
| /// > Any Document B whose container document is A must be listed after A in the list. |
| /// > |
| /// > If there are two documents A and B that both have the same non-null container |
| /// > document C, then the order of A and B in the list must match the shadow-including |
| /// > tree order of their respective navigable containers in C's node tree. |
| /// > |
| /// > In the steps below that iterate over docs, each Document must be processed in the |
| /// > order it is found in the list. |
| /// |
| /// [update-the-rendering]: https://html.spec.whatwg.org/multipage/#update-the-rendering |
| pub(crate) fn documents_in_order(&self) -> Vec<PipelineId> { |
| DocumentTree::new(self).documents_in_order() |
| } |
| } |
| |
| impl Default for DocumentCollection { |
| #[cfg_attr(crown, allow(crown::unrooted_must_root))] |
| fn default() -> Self { |
| Self { |
| map: HashMapTracedValues::new(), |
| } |
| } |
| } |
| |
| #[cfg_attr(crown, allow(crown::unrooted_must_root))] |
| pub(crate) struct DocumentsIter<'a> { |
| iter: hash_map::Iter<'a, PipelineId, Dom<Document>>, |
| } |
| |
| impl Iterator for DocumentsIter<'_> { |
| type Item = (PipelineId, DomRoot<Document>); |
| |
| fn next(&mut self) -> Option<(PipelineId, DomRoot<Document>)> { |
| self.iter |
| .next() |
| .map(|(id, doc)| (*id, DomRoot::from_ref(&**doc))) |
| } |
| } |
| |
| #[derive(Default)] |
| struct DocumentTreeNode { |
| parent: Option<PipelineId>, |
| children: Vec<PipelineId>, |
| } |
| |
| /// A tree representation of [`Document`]s managed by the [`ScriptThread`][st], which is used |
| /// to generate an ordered set of [`Document`]s for the *update the rendering* step of the |
| /// HTML5 specification. |
| /// |
| /// FIXME: The [`ScriptThread`][st] only has a view of [`Document`]s managed by itself, |
| /// so if there are interceding iframes managed by other `ScriptThread`s, then the |
| /// order of the [`Document`]s may not be correct. Perhaps the Constellation could |
| /// ensure that every [`ScriptThread`][st] has the full view of the frame tree. |
| /// |
| /// [st]: crate::script_thread::ScriptThread |
| #[derive(Default)] |
| struct DocumentTree { |
| tree: HashMap<PipelineId, DocumentTreeNode>, |
| } |
| |
| impl DocumentTree { |
| fn new(documents: &DocumentCollection) -> Self { |
| let mut tree = DocumentTree::default(); |
| for (id, document) in documents.iter() { |
| let children: Vec<PipelineId> = document |
| .iframes() |
| .iter() |
| .filter_map(|iframe| iframe.pipeline_id()) |
| .filter(|iframe_pipeline_id| documents.find_document(*iframe_pipeline_id).is_some()) |
| .collect(); |
| for child in &children { |
| tree.tree.entry(*child).or_default().parent = Some(id); |
| } |
| tree.tree.entry(id).or_default().children = children; |
| } |
| tree |
| } |
| |
| fn documents_in_order(&self) -> Vec<PipelineId> { |
| let mut list = Vec::new(); |
| for (id, node) in self.tree.iter() { |
| if node.parent.is_none() { |
| self.process_node_for_documents_in_order(*id, &mut list); |
| } |
| } |
| list |
| } |
| |
| fn process_node_for_documents_in_order(&self, id: PipelineId, list: &mut Vec<PipelineId>) { |
| list.push(id); |
| for child in self |
| .tree |
| .get(&id) |
| .expect("Should have found child node") |
| .children |
| .iter() |
| { |
| self.process_node_for_documents_in_order(*child, list); |
| } |
| } |
| } |