mirror of
https://github.com/servo/servo.git
synced 2025-08-05 21:50:18 +01:00
layout: Move the LayoutNode
wrapper from script into layout.
This commit is contained in:
parent
c506e52c7c
commit
9e2b63ddd3
13 changed files with 370 additions and 343 deletions
|
@ -7,9 +7,9 @@
|
||||||
use css::node_style::StyledNode;
|
use css::node_style::StyledNode;
|
||||||
use layout::incremental;
|
use layout::incremental;
|
||||||
use layout::util::LayoutDataAccess;
|
use layout::util::LayoutDataAccess;
|
||||||
|
use layout::wrapper::LayoutNode;
|
||||||
|
|
||||||
use extra::arc::{Arc, RWArc};
|
use extra::arc::{Arc, RWArc};
|
||||||
use script::dom::node::LayoutNode;
|
|
||||||
use std::cast;
|
use std::cast;
|
||||||
use std::cell::Cell;
|
use std::cell::Cell;
|
||||||
use std::comm;
|
use std::comm;
|
||||||
|
|
|
@ -6,10 +6,10 @@
|
||||||
|
|
||||||
use css::node_util::NodeUtil;
|
use css::node_util::NodeUtil;
|
||||||
use layout::incremental::RestyleDamage;
|
use layout::incremental::RestyleDamage;
|
||||||
|
use layout::wrapper::LayoutNode;
|
||||||
|
|
||||||
use extra::arc::Arc;
|
use extra::arc::Arc;
|
||||||
use style::ComputedValues;
|
use style::ComputedValues;
|
||||||
use script::dom::node::LayoutNode;
|
|
||||||
|
|
||||||
/// Node mixin providing `style` method that returns a `NodeStyle`
|
/// Node mixin providing `style` method that returns a `NodeStyle`
|
||||||
pub trait StyledNode {
|
pub trait StyledNode {
|
||||||
|
|
|
@ -4,11 +4,11 @@
|
||||||
|
|
||||||
use layout::incremental::RestyleDamage;
|
use layout::incremental::RestyleDamage;
|
||||||
use layout::util::LayoutDataAccess;
|
use layout::util::LayoutDataAccess;
|
||||||
|
use layout::wrapper::LayoutNode;
|
||||||
|
|
||||||
use extra::arc::Arc;
|
use extra::arc::Arc;
|
||||||
use std::cast;
|
use std::cast;
|
||||||
use style::{ComputedValues, TNode};
|
use style::{ComputedValues, TNode};
|
||||||
use script::dom::node::LayoutNode;
|
|
||||||
|
|
||||||
pub trait NodeUtil {
|
pub trait NodeUtil {
|
||||||
fn get_css_select_results<'a>(&'a self) -> &'a Arc<ComputedValues>;
|
fn get_css_select_results<'a>(&'a self) -> &'a Arc<ComputedValues>;
|
||||||
|
|
|
@ -15,7 +15,6 @@ use gfx::display_list::{TextDisplayItemClass, TextDisplayItemFlags, ClipDisplayI
|
||||||
use gfx::display_list::{ClipDisplayItemClass};
|
use gfx::display_list::{ClipDisplayItemClass};
|
||||||
use gfx::font::{FontStyle, FontWeight300};
|
use gfx::font::{FontStyle, FontWeight300};
|
||||||
use gfx::text::text_run::TextRun;
|
use gfx::text::text_run::TextRun;
|
||||||
use script::dom::node::LayoutNode;
|
|
||||||
use servo_msg::constellation_msg::{FrameRectMsg, PipelineId, SubpageId};
|
use servo_msg::constellation_msg::{FrameRectMsg, PipelineId, SubpageId};
|
||||||
use servo_net::image::holder::ImageHolder;
|
use servo_net::image::holder::ImageHolder;
|
||||||
use servo_net::local_image_cache::LocalImageCache;
|
use servo_net::local_image_cache::LocalImageCache;
|
||||||
|
@ -40,6 +39,7 @@ use layout::flow::Flow;
|
||||||
use layout::flow;
|
use layout::flow;
|
||||||
use layout::model::{MaybeAuto, specified};
|
use layout::model::{MaybeAuto, specified};
|
||||||
use layout::util::OpaqueNode;
|
use layout::util::OpaqueNode;
|
||||||
|
use layout::wrapper::LayoutNode;
|
||||||
|
|
||||||
/// Boxes (`struct Box`) are the leaves of the layout tree. They cannot position themselves. In
|
/// Boxes (`struct Box`) are the leaves of the layout tree. They cannot position themselves. In
|
||||||
/// general, boxes do not have a simple correspondence with CSS boxes in the specification:
|
/// general, boxes do not have a simple correspondence with CSS boxes in the specification:
|
||||||
|
|
|
@ -30,11 +30,11 @@ use layout::flow::{Flow, FlowData, MutableFlowUtils};
|
||||||
use layout::inline::InlineFlow;
|
use layout::inline::InlineFlow;
|
||||||
use layout::text::TextRunScanner;
|
use layout::text::TextRunScanner;
|
||||||
use layout::util::LayoutDataAccess;
|
use layout::util::LayoutDataAccess;
|
||||||
|
use layout::wrapper::{LayoutNode, PostorderNodeMutTraversal};
|
||||||
|
|
||||||
use script::dom::element::{HTMLIframeElementTypeId, HTMLImageElementTypeId};
|
use script::dom::element::{HTMLIframeElementTypeId, HTMLImageElementTypeId};
|
||||||
use script::dom::node::{CommentNodeTypeId, DoctypeNodeTypeId, DocumentFragmentNodeTypeId};
|
use script::dom::node::{CommentNodeTypeId, DoctypeNodeTypeId, DocumentFragmentNodeTypeId};
|
||||||
use script::dom::node::{DocumentNodeTypeId, ElementNodeTypeId, LayoutNode};
|
use script::dom::node::{DocumentNodeTypeId, ElementNodeTypeId, TextNodeTypeId};
|
||||||
use script::dom::node::{PostorderNodeMutTraversal, TextNodeTypeId};
|
|
||||||
use servo_util::slot::Slot;
|
use servo_util::slot::Slot;
|
||||||
use std::util;
|
use std::util;
|
||||||
use style::computed_values::{display, float};
|
use style::computed_values::{display, float};
|
||||||
|
|
|
@ -5,8 +5,7 @@
|
||||||
//! Code for managing the layout data in the DOM.
|
//! Code for managing the layout data in the DOM.
|
||||||
|
|
||||||
use layout::util::{LayoutData, LayoutDataAccess};
|
use layout::util::{LayoutData, LayoutDataAccess};
|
||||||
|
use layout::wrapper::LayoutNode;
|
||||||
use script::dom::node::LayoutNode;
|
|
||||||
|
|
||||||
/// Functionality useful for querying the layout-specific data on DOM nodes.
|
/// Functionality useful for querying the layout-specific data on DOM nodes.
|
||||||
pub trait LayoutAuxMethods {
|
pub trait LayoutAuxMethods {
|
||||||
|
|
|
@ -33,13 +33,13 @@ use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData};
|
||||||
use layout::float_context::{FloatContext, Invalid};
|
use layout::float_context::{FloatContext, Invalid};
|
||||||
use layout::incremental::RestyleDamage;
|
use layout::incremental::RestyleDamage;
|
||||||
use layout::inline::InlineFlow;
|
use layout::inline::InlineFlow;
|
||||||
|
use layout::wrapper::LayoutNode;
|
||||||
|
|
||||||
use extra::dlist::{DList, DListIterator, MutDListIterator};
|
use extra::dlist::{DList, DListIterator, MutDListIterator};
|
||||||
use extra::container::Deque;
|
use extra::container::Deque;
|
||||||
use geom::point::Point2D;
|
use geom::point::Point2D;
|
||||||
use geom::rect::Rect;
|
use geom::rect::Rect;
|
||||||
use gfx::display_list::{ClipDisplayItemClass, DisplayList};
|
use gfx::display_list::{ClipDisplayItemClass, DisplayList};
|
||||||
use script::dom::node::LayoutNode;
|
|
||||||
use servo_util::geometry::Au;
|
use servo_util::geometry::Au;
|
||||||
use std::cast;
|
use std::cast;
|
||||||
use std::cell::Cell;
|
use std::cell::Cell;
|
||||||
|
|
|
@ -17,6 +17,7 @@ use layout::flow::{PostorderFlowTraversal};
|
||||||
use layout::flow;
|
use layout::flow;
|
||||||
use layout::incremental::{RestyleDamage};
|
use layout::incremental::{RestyleDamage};
|
||||||
use layout::util::{LayoutData, LayoutDataAccess, OpaqueNode};
|
use layout::util::{LayoutData, LayoutDataAccess, OpaqueNode};
|
||||||
|
use layout::wrapper::LayoutNode;
|
||||||
|
|
||||||
use extra::arc::{Arc, RWArc, MutexArc};
|
use extra::arc::{Arc, RWArc, MutexArc};
|
||||||
use geom::point::Point2D;
|
use geom::point::Point2D;
|
||||||
|
@ -28,7 +29,7 @@ use gfx::opts::Opts;
|
||||||
use gfx::render_task::{RenderMsg, RenderChan, RenderLayer};
|
use gfx::render_task::{RenderMsg, RenderChan, RenderLayer};
|
||||||
use gfx::{render_task, color};
|
use gfx::{render_task, color};
|
||||||
use script::dom::event::ReflowEvent;
|
use script::dom::event::ReflowEvent;
|
||||||
use script::dom::node::{AbstractNode, ElementNodeTypeId, LayoutDataRef, LayoutNode};
|
use script::dom::node::{AbstractNode, ElementNodeTypeId, LayoutDataRef};
|
||||||
use script::dom::element::{HTMLBodyElementTypeId, HTMLHtmlElementTypeId};
|
use script::dom::element::{HTMLBodyElementTypeId, HTMLHtmlElementTypeId};
|
||||||
use script::layout_interface::{AddStylesheetMsg, ContentBoxQuery};
|
use script::layout_interface::{AddStylesheetMsg, ContentBoxQuery};
|
||||||
use script::layout_interface::{ContentBoxesQuery, ContentBoxesResponse, ExitNowMsg, LayoutQuery};
|
use script::layout_interface::{ContentBoxesQuery, ContentBoxesResponse, ExitNowMsg, LayoutQuery};
|
||||||
|
|
|
@ -3,15 +3,16 @@
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
//! Text layout.
|
//! Text layout.
|
||||||
use extra::arc::Arc;
|
|
||||||
use layout::box::{Box, ScannedTextBox, ScannedTextBoxInfo, UnscannedTextBox};
|
use layout::box::{Box, ScannedTextBox, ScannedTextBoxInfo, UnscannedTextBox};
|
||||||
use layout::context::LayoutContext;
|
use layout::context::LayoutContext;
|
||||||
use layout::flow::Flow;
|
use layout::flow::Flow;
|
||||||
|
|
||||||
|
use extra::arc::Arc;
|
||||||
use gfx::text::text_run::TextRun;
|
use gfx::text::text_run::TextRun;
|
||||||
use gfx::text::util::{CompressWhitespaceNewline, transform_text};
|
use gfx::text::util::{CompressWhitespaceNewline, transform_text};
|
||||||
use std::vec;
|
|
||||||
use servo_util::range::Range;
|
use servo_util::range::Range;
|
||||||
|
use std::vec;
|
||||||
|
|
||||||
/// A stack-allocated object for scanning an inline flow into `TextRun`-containing `TextBox`es.
|
/// A stack-allocated object for scanning an inline flow into `TextRun`-containing `TextBox`es.
|
||||||
struct TextRunScanner {
|
struct TextRunScanner {
|
||||||
|
|
|
@ -4,9 +4,10 @@
|
||||||
|
|
||||||
use layout::box::Box;
|
use layout::box::Box;
|
||||||
use layout::construct::{ConstructionResult, NoConstructionResult};
|
use layout::construct::{ConstructionResult, NoConstructionResult};
|
||||||
|
use layout::wrapper::LayoutNode;
|
||||||
|
|
||||||
use extra::arc::Arc;
|
use extra::arc::Arc;
|
||||||
use script::dom::node::{AbstractNode, LayoutNode};
|
use script::dom::node::AbstractNode;
|
||||||
use servo_util::range::Range;
|
use servo_util::range::Range;
|
||||||
use servo_util::slot::{MutSlotRef, SlotRef};
|
use servo_util::slot::{MutSlotRef, SlotRef};
|
||||||
use std::cast;
|
use std::cast;
|
||||||
|
|
352
src/components/main/layout/wrapper.rs
Normal file
352
src/components/main/layout/wrapper.rs
Normal file
|
@ -0,0 +1,352 @@
|
||||||
|
/* 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 http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
//! A safe wrapper for DOM nodes that prevents layout from mutating the DOM, from letting DOM nodes
|
||||||
|
//! escape, and from generally doing anything that it isn't supposed to. This is accomplished via
|
||||||
|
//! a simple whitelist of allowed operations.
|
||||||
|
//!
|
||||||
|
//! As a security wrapper is only as good as its whitelist, be careful when adding operations to
|
||||||
|
//! this list. The cardinal rules are:
|
||||||
|
//!
|
||||||
|
//! (1) Layout is not allowed to mutate the DOM.
|
||||||
|
//!
|
||||||
|
//! (2) Layout is not allowed to see anything with `Abstract` in the name, because it could hang
|
||||||
|
//! onto these objects and cause use-after-free.
|
||||||
|
|
||||||
|
use extra::url::Url;
|
||||||
|
use script::dom::element::Element;
|
||||||
|
use script::dom::htmliframeelement::HTMLIFrameElement;
|
||||||
|
use script::dom::htmlimageelement::HTMLImageElement;
|
||||||
|
use script::dom::node::{AbstractNode, DocumentNodeTypeId, ElementNodeTypeId, LayoutView, Node};
|
||||||
|
use script::dom::node::{NodeTypeId};
|
||||||
|
use script::dom::text::Text;
|
||||||
|
use servo_msg::constellation_msg::{PipelineId, SubpageId};
|
||||||
|
use std::cast;
|
||||||
|
use style::TNode;
|
||||||
|
|
||||||
|
/// A wrapper so that layout can access only the methods that it should have access to. Layout must
|
||||||
|
/// only ever see these and must never see instances of `AbstractNode`.
|
||||||
|
#[deriving(Clone, Eq)]
|
||||||
|
pub struct LayoutNode<'self> {
|
||||||
|
/// The wrapped node.
|
||||||
|
priv node: AbstractNode,
|
||||||
|
|
||||||
|
/// Being chained to a value prevents `LayoutNode`s from escaping.
|
||||||
|
priv chain: &'self (),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'self> LayoutNode<'self> {
|
||||||
|
/// Creates a new layout node, scoped to the given closure.
|
||||||
|
pub unsafe fn with_layout_node<R>(node: AbstractNode, f: &fn<'a>(LayoutNode<'a>) -> R) -> R {
|
||||||
|
let heavy_iron_ball = ();
|
||||||
|
f(LayoutNode {
|
||||||
|
node: node,
|
||||||
|
chain: &heavy_iron_ball,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new layout node with the same lifetime as this layout node.
|
||||||
|
unsafe fn new_with_this_lifetime(&self, node: AbstractNode) -> LayoutNode<'self> {
|
||||||
|
LayoutNode {
|
||||||
|
node: node,
|
||||||
|
chain: self.chain,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the interior of this node as a `Node`. This is highly unsafe for layout to call
|
||||||
|
/// and as such is marked `unsafe`.
|
||||||
|
pub unsafe fn get<'a>(&'a self) -> &'a Node<LayoutView> {
|
||||||
|
cast::transmute(self.node.node())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the first child of this node.
|
||||||
|
pub fn first_child(&self) -> Option<LayoutNode<'self>> {
|
||||||
|
unsafe {
|
||||||
|
self.node.first_child().map(|node| self.new_with_this_lifetime(node))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the first child of this node.
|
||||||
|
pub fn last_child(&self) -> Option<LayoutNode<'self>> {
|
||||||
|
unsafe {
|
||||||
|
self.node.last_child().map(|node| self.new_with_this_lifetime(node))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterates over this node and all its descendants, in preorder.
|
||||||
|
///
|
||||||
|
/// FIXME(pcwalton): Terribly inefficient. We should use parallelism.
|
||||||
|
pub fn traverse_preorder(&self) -> LayoutTreeIterator<'self> {
|
||||||
|
let mut nodes = ~[];
|
||||||
|
gather_layout_nodes(self, &mut nodes, false);
|
||||||
|
LayoutTreeIterator::new(nodes)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an iterator over this node's children.
|
||||||
|
pub fn children(&self) -> LayoutNodeChildrenIterator<'self> {
|
||||||
|
LayoutNodeChildrenIterator {
|
||||||
|
current_node: self.first_child(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the type ID of this node. Fails if this node is borrowed mutably.
|
||||||
|
pub fn type_id(&self) -> NodeTypeId {
|
||||||
|
self.node.type_id()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If this is an image element, returns its URL. If this is not an image element, fails.
|
||||||
|
///
|
||||||
|
/// FIXME(pcwalton): Don't copy URLs.
|
||||||
|
pub fn image_url(&self) -> Option<Url> {
|
||||||
|
unsafe {
|
||||||
|
self.with_image_element(|image_element| {
|
||||||
|
image_element.image.as_ref().map(|url| (*url).clone())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Downcasts this node to an image element and calls the given closure.
|
||||||
|
///
|
||||||
|
/// FIXME(pcwalton): RAII.
|
||||||
|
unsafe fn with_image_element<R>(self, f: &fn(&HTMLImageElement) -> R) -> R {
|
||||||
|
if !self.node.is_image_element() {
|
||||||
|
fail!(~"node is not an image element");
|
||||||
|
}
|
||||||
|
self.node.transmute(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If this node is an iframe element, returns its pipeline and subpage IDs. If this node is
|
||||||
|
/// not an iframe element, fails.
|
||||||
|
pub fn iframe_pipeline_and_subpage_ids(&self) -> (PipelineId, SubpageId) {
|
||||||
|
unsafe {
|
||||||
|
self.with_iframe_element(|iframe_element| {
|
||||||
|
let size = iframe_element.size.unwrap();
|
||||||
|
(size.pipeline_id, size.subpage_id)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Downcasts this node to an iframe element and calls the given closure.
|
||||||
|
///
|
||||||
|
/// FIXME(pcwalton): RAII.
|
||||||
|
unsafe fn with_iframe_element<R>(self, f: &fn(&HTMLIFrameElement) -> R) -> R {
|
||||||
|
if !self.node.is_iframe_element() {
|
||||||
|
fail!(~"node is not an iframe element");
|
||||||
|
}
|
||||||
|
self.node.transmute(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if this node is a text node or false otherwise.
|
||||||
|
#[inline]
|
||||||
|
pub fn is_text(self) -> bool {
|
||||||
|
self.node.is_text()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if this node consists entirely of ignorable whitespace and false otherwise.
|
||||||
|
/// Ignorable whitespace is defined as whitespace that would be removed per CSS 2.1 § 16.6.1.
|
||||||
|
pub fn is_ignorable_whitespace(&self) -> bool {
|
||||||
|
unsafe {
|
||||||
|
self.is_text() && self.with_text(|text| text.element.data.is_whitespace())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If this is a text node, copies out the text. If this is not a text node, fails.
|
||||||
|
///
|
||||||
|
/// FIXME(pcwalton): Don't copy text. Atomically reference count instead.
|
||||||
|
pub fn text(&self) -> ~str {
|
||||||
|
unsafe {
|
||||||
|
self.with_text(|text| text.element.data.to_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Downcasts this node to a text node and calls the given closure.
|
||||||
|
///
|
||||||
|
/// FIXME(pcwalton): RAII.
|
||||||
|
unsafe fn with_text<R>(self, f: &fn(&Text) -> R) -> R {
|
||||||
|
self.node.with_imm_text(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Dumps this node tree, for debugging.
|
||||||
|
pub fn dump(&self) {
|
||||||
|
self.node.dump()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a string that describes this node, for debugging.
|
||||||
|
pub fn debug_str(&self) -> ~str {
|
||||||
|
self.node.debug_str()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Traverses the tree in postorder.
|
||||||
|
///
|
||||||
|
/// TODO(pcwalton): Offer a parallel version with a compatible API.
|
||||||
|
pub fn traverse_postorder<T:PostorderNodeTraversal>(self, traversal: &T) -> bool {
|
||||||
|
if traversal.should_prune(self) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut opt_kid = self.first_child();
|
||||||
|
loop {
|
||||||
|
match opt_kid {
|
||||||
|
None => break,
|
||||||
|
Some(kid) => {
|
||||||
|
if !kid.traverse_postorder(traversal) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
opt_kid = kid.next_sibling()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
traversal.process(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Traverses the tree in postorder.
|
||||||
|
///
|
||||||
|
/// TODO(pcwalton): Offer a parallel version with a compatible API.
|
||||||
|
pub fn traverse_postorder_mut<T:PostorderNodeMutTraversal>(mut self, traversal: &mut T)
|
||||||
|
-> bool {
|
||||||
|
if traversal.should_prune(self) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut opt_kid = self.first_child();
|
||||||
|
loop {
|
||||||
|
match opt_kid {
|
||||||
|
None => break,
|
||||||
|
Some(kid) => {
|
||||||
|
if !kid.traverse_postorder_mut(traversal) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
opt_kid = kid.next_sibling()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
traversal.process(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'self> TNode<Element> for LayoutNode<'self> {
|
||||||
|
fn parent_node(&self) -> Option<LayoutNode<'self>> {
|
||||||
|
unsafe {
|
||||||
|
self.node.node().parent_node.map(|node| self.new_with_this_lifetime(node))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prev_sibling(&self) -> Option<LayoutNode<'self>> {
|
||||||
|
unsafe {
|
||||||
|
self.node.node().prev_sibling.map(|node| self.new_with_this_lifetime(node))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next_sibling(&self) -> Option<LayoutNode<'self>> {
|
||||||
|
unsafe {
|
||||||
|
self.node.node().next_sibling.map(|node| self.new_with_this_lifetime(node))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_element(&self) -> bool {
|
||||||
|
match self.node.type_id() {
|
||||||
|
ElementNodeTypeId(*) => true,
|
||||||
|
_ => false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_document(&self) -> bool {
|
||||||
|
match self.node.type_id() {
|
||||||
|
DocumentNodeTypeId(*) => true,
|
||||||
|
_ => false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// FIXME(pcwalton): Unsafe!
|
||||||
|
#[inline]
|
||||||
|
fn with_element<R>(&self, f: &fn(&Element) -> R) -> R {
|
||||||
|
self.node.with_imm_element(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct LayoutNodeChildrenIterator<'self> {
|
||||||
|
priv current_node: Option<LayoutNode<'self>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'self> Iterator<LayoutNode<'self>> for LayoutNodeChildrenIterator<'self> {
|
||||||
|
fn next(&mut self) -> Option<LayoutNode<'self>> {
|
||||||
|
let node = self.current_node;
|
||||||
|
self.current_node = do self.current_node.and_then |node| {
|
||||||
|
node.next_sibling()
|
||||||
|
};
|
||||||
|
node
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: Do this without precomputing a vector of refs.
|
||||||
|
// Easy for preorder; harder for postorder.
|
||||||
|
//
|
||||||
|
// FIXME(pcwalton): Parallelism! Eventually this should just be nuked.
|
||||||
|
pub struct LayoutTreeIterator<'self> {
|
||||||
|
priv nodes: ~[LayoutNode<'self>],
|
||||||
|
priv index: uint,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'self> LayoutTreeIterator<'self> {
|
||||||
|
fn new(nodes: ~[LayoutNode<'self>]) -> LayoutTreeIterator<'self> {
|
||||||
|
LayoutTreeIterator {
|
||||||
|
nodes: nodes,
|
||||||
|
index: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'self> Iterator<LayoutNode<'self>> for LayoutTreeIterator<'self> {
|
||||||
|
fn next(&mut self) -> Option<LayoutNode<'self>> {
|
||||||
|
if self.index >= self.nodes.len() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
let v = self.nodes[self.index].clone();
|
||||||
|
self.index += 1;
|
||||||
|
Some(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// FIXME(pcwalton): This is super inefficient.
|
||||||
|
fn gather_layout_nodes<'a>(cur: &LayoutNode<'a>, refs: &mut ~[LayoutNode<'a>], postorder: bool) {
|
||||||
|
if !postorder {
|
||||||
|
refs.push(cur.clone());
|
||||||
|
}
|
||||||
|
for kid in cur.children() {
|
||||||
|
gather_layout_nodes(&kid, refs, postorder)
|
||||||
|
}
|
||||||
|
if postorder {
|
||||||
|
refs.push(cur.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A bottom-up, parallelizable traversal.
|
||||||
|
pub trait PostorderNodeTraversal {
|
||||||
|
/// The operation to perform. Return true to continue or false to stop.
|
||||||
|
fn process<'a>(&'a self, node: LayoutNode<'a>) -> bool;
|
||||||
|
|
||||||
|
/// Returns true if this node should be pruned. If this returns true, we skip the operation
|
||||||
|
/// entirely and do not process any descendant nodes. This is called *before* child nodes are
|
||||||
|
/// visited. The default implementation never prunes any nodes.
|
||||||
|
fn should_prune<'a>(&'a self, _node: LayoutNode<'a>) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A bottom-up, parallelizable traversal.
|
||||||
|
pub trait PostorderNodeMutTraversal {
|
||||||
|
/// The operation to perform. Return true to continue or false to stop.
|
||||||
|
fn process<'a>(&'a mut self, node: LayoutNode<'a>) -> bool;
|
||||||
|
|
||||||
|
/// Returns true if this node should be pruned. If this returns true, we skip the operation
|
||||||
|
/// entirely and do not process any descendant nodes. This is called *before* child nodes are
|
||||||
|
/// visited. The default implementation never prunes any nodes.
|
||||||
|
fn should_prune<'a>(&'a self, _node: LayoutNode<'a>) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -88,6 +88,7 @@ pub mod layout {
|
||||||
pub mod text;
|
pub mod text;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
pub mod incremental;
|
pub mod incremental;
|
||||||
|
pub mod wrapper;
|
||||||
mod extra;
|
mod extra;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,14 +14,12 @@ use dom::documenttype::DocumentType;
|
||||||
use dom::element::{Element, ElementTypeId, HTMLImageElementTypeId, HTMLIframeElementTypeId};
|
use dom::element::{Element, ElementTypeId, HTMLImageElementTypeId, HTMLIframeElementTypeId};
|
||||||
use dom::element::{HTMLAnchorElementTypeId, HTMLStyleElementTypeId};
|
use dom::element::{HTMLAnchorElementTypeId, HTMLStyleElementTypeId};
|
||||||
use dom::eventtarget::{AbstractEventTarget, EventTarget, NodeTypeId};
|
use dom::eventtarget::{AbstractEventTarget, EventTarget, NodeTypeId};
|
||||||
use dom::nodelist::{NodeList};
|
|
||||||
use dom::htmlimageelement::HTMLImageElement;
|
|
||||||
use dom::htmliframeelement::HTMLIFrameElement;
|
use dom::htmliframeelement::HTMLIFrameElement;
|
||||||
|
use dom::htmlimageelement::HTMLImageElement;
|
||||||
|
use dom::nodelist::{NodeList};
|
||||||
use dom::text::Text;
|
use dom::text::Text;
|
||||||
|
|
||||||
use extra::url::Url;
|
|
||||||
use js::jsapi::{JSObject, JSContext};
|
use js::jsapi::{JSObject, JSContext};
|
||||||
use servo_msg::constellation_msg::{PipelineId, SubpageId};
|
|
||||||
use servo_util::slot::{MutSlotRef, Slot, SlotRef};
|
use servo_util::slot::{MutSlotRef, Slot, SlotRef};
|
||||||
use std::cast::transmute;
|
use std::cast::transmute;
|
||||||
use std::cast;
|
use std::cast;
|
||||||
|
@ -29,198 +27,6 @@ use std::unstable::raw::Box;
|
||||||
use std::util;
|
use std::util;
|
||||||
use style::TNode;
|
use style::TNode;
|
||||||
|
|
||||||
//
|
|
||||||
// Layout's immutable, sanitized view of nodes.
|
|
||||||
//
|
|
||||||
|
|
||||||
/// A nonsense constant for layout nodes to point to just to establish a lifetime.
|
|
||||||
///
|
|
||||||
/// FIXME(pcwalton): Phantom lifetimes in Rust would be useful...
|
|
||||||
static HEAVY_IRON_BALL: () = ();
|
|
||||||
|
|
||||||
/// A wrapper so that layout can access only the methods that it should have access to. Layout must
|
|
||||||
/// only ever see these and must never see instances of `AbstractNode`.
|
|
||||||
#[deriving(Clone, Eq)]
|
|
||||||
pub struct LayoutNode<'self> {
|
|
||||||
/// The wrapped node.
|
|
||||||
priv node: AbstractNode,
|
|
||||||
|
|
||||||
/// Being chained to a `HEAVY_IRON_BALL` prevents `LayoutNode`s from escaping.
|
|
||||||
priv chain: &'self (),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'self> LayoutNode<'self> {
|
|
||||||
/// NB: Do not make this public.
|
|
||||||
///
|
|
||||||
/// FIXME(pcwalton): Probably this should be marked `unsafe`.
|
|
||||||
/*PRIVATE-FOR-SECURITY-REASONS*/ fn new(node: AbstractNode) -> LayoutNode<'static> {
|
|
||||||
LayoutNode {
|
|
||||||
node: node,
|
|
||||||
chain: &HEAVY_IRON_BALL,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the interior of this node as a `Node`. This is highly unsafe for layout to call
|
|
||||||
/// and as such is marked `unsafe`.
|
|
||||||
pub unsafe fn get<'a>(&'a self) -> &'a Node<LayoutView> {
|
|
||||||
cast::transmute(self.node.node())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the first child of this node.
|
|
||||||
pub fn first_child(&self) -> Option<LayoutNode<'self>> {
|
|
||||||
self.node.first_child().map(|node| LayoutNode::new(node))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the first child of this node.
|
|
||||||
pub fn last_child(&self) -> Option<LayoutNode<'self>> {
|
|
||||||
self.node.last_child().map(|node| LayoutNode::new(node))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Iterates over this node and all its descendants, in preorder.
|
|
||||||
///
|
|
||||||
/// FIXME(pcwalton): Terribly inefficient. We should use parallelism.
|
|
||||||
pub fn traverse_preorder(&self) -> LayoutTreeIterator<'self> {
|
|
||||||
let mut nodes = ~[];
|
|
||||||
gather_layout_nodes(self, &mut nodes, false);
|
|
||||||
LayoutTreeIterator::new(nodes)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns an iterator over this node's children.
|
|
||||||
pub fn children(&self) -> LayoutNodeChildrenIterator<'self> {
|
|
||||||
LayoutNodeChildrenIterator {
|
|
||||||
current_node: self.first_child(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the type ID of this node. Fails if this node is borrowed mutably.
|
|
||||||
pub fn type_id(&self) -> NodeTypeId {
|
|
||||||
self.node.type_id()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// If this is an image element, returns its URL. If this is not an image element, fails.
|
|
||||||
///
|
|
||||||
/// FIXME(pcwalton): Don't copy URLs.
|
|
||||||
pub fn image_url(&self) -> Option<Url> {
|
|
||||||
self.with_image_element(|image_element| {
|
|
||||||
image_element.image.as_ref().map(|url| (*url).clone())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Downcasts this node to an image element and calls the given closure.
|
|
||||||
///
|
|
||||||
/// NB: Do not make this public, as layout will be able to do unsafe things with `AbstractNode`
|
|
||||||
/// otherwise.
|
|
||||||
///
|
|
||||||
/// FIXME(pcwalton): RAII.
|
|
||||||
/*PRIVATE-FOR-SECURITY-REASONS*/ fn with_image_element<R>(
|
|
||||||
self,
|
|
||||||
f: &fn(&HTMLImageElement) -> R)
|
|
||||||
-> R {
|
|
||||||
if !self.node.is_image_element() {
|
|
||||||
fail!(~"node is not an image element");
|
|
||||||
}
|
|
||||||
self.node.transmute(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// If this node is an iframe element, returns its pipeline and subpage IDs. If this node is
|
|
||||||
/// not an iframe element, fails.
|
|
||||||
pub fn iframe_pipeline_and_subpage_ids(&self) -> (PipelineId, SubpageId) {
|
|
||||||
self.with_iframe_element(|iframe_element| {
|
|
||||||
let size = iframe_element.size.unwrap();
|
|
||||||
(size.pipeline_id, size.subpage_id)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Downcasts this node to an iframe element and calls the given closure.
|
|
||||||
///
|
|
||||||
/// NB: Do not make this public, as layout will be able to do unsafe things with `AbstractNode`
|
|
||||||
/// otherwise.
|
|
||||||
///
|
|
||||||
/// FIXME(pcwalton): RAII.
|
|
||||||
/*PRIVATE-FOR-SECURITY-REASONS*/ fn with_iframe_element<R>(
|
|
||||||
self,
|
|
||||||
f: &fn(&HTMLIFrameElement) -> R)
|
|
||||||
-> R {
|
|
||||||
if !self.node.is_iframe_element() {
|
|
||||||
fail!(~"node is not an iframe element");
|
|
||||||
}
|
|
||||||
self.node.transmute(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true if this node is a text node or false otherwise.
|
|
||||||
#[inline]
|
|
||||||
pub fn is_text(self) -> bool {
|
|
||||||
self.node.is_text()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true if this node consists entirely of ignorable whitespace and false otherwise.
|
|
||||||
/// Ignorable whitespace is defined as whitespace that would be removed per CSS 2.1 § 16.6.1.
|
|
||||||
pub fn is_ignorable_whitespace(&self) -> bool {
|
|
||||||
self.is_text() && self.with_text(|text| text.element.data.is_whitespace())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// If this is a text node, copies out the text. If this is not a text node, fails.
|
|
||||||
///
|
|
||||||
/// FIXME(pcwalton): Don't copy text. Atomically reference count instead.
|
|
||||||
pub fn text(&self) -> ~str {
|
|
||||||
self.with_text(|text| text.element.data.to_str())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Downcasts this node to a text node and calls the given closure.
|
|
||||||
///
|
|
||||||
/// NB: Do not make this public, as layout will be able to do unsafe things with `AbstractNode`
|
|
||||||
/// otherwise.
|
|
||||||
///
|
|
||||||
/// FIXME(pcwalton): RAII.
|
|
||||||
/*PRIVATE-FOR-SECURITY-REASONS*/ fn with_text<R>(self, f: &fn(&Text) -> R) -> R {
|
|
||||||
self.node.with_imm_text(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Dumps this node tree, for debugging.
|
|
||||||
pub fn dump(&self) {
|
|
||||||
self.node.dump()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a string that describes this node, for debugging.
|
|
||||||
pub fn debug_str(&self) -> ~str {
|
|
||||||
self.node.debug_str()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'self> TNode<Element> for LayoutNode<'self> {
|
|
||||||
fn parent_node(&self) -> Option<LayoutNode<'self>> {
|
|
||||||
self.node.node().parent_node.map(|node| LayoutNode::new(node))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn prev_sibling(&self) -> Option<LayoutNode<'self>> {
|
|
||||||
self.node.node().prev_sibling.map(|node| LayoutNode::new(node))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next_sibling(&self) -> Option<LayoutNode<'self>> {
|
|
||||||
self.node.node().next_sibling.map(|node| LayoutNode::new(node))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_element(&self) -> bool {
|
|
||||||
match self.node.type_id() {
|
|
||||||
ElementNodeTypeId(*) => true,
|
|
||||||
_ => false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_document(&self) -> bool {
|
|
||||||
match self.node.type_id() {
|
|
||||||
DocumentNodeTypeId(*) => true,
|
|
||||||
_ => false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// FIXME(pcwalton): Unsafe!
|
|
||||||
#[inline]
|
|
||||||
fn with_element<R>(&self, f: &fn(&Element) -> R) -> R {
|
|
||||||
self.node.with_imm_element(f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// The basic Node structure
|
// The basic Node structure
|
||||||
//
|
//
|
||||||
|
@ -851,20 +657,6 @@ impl Iterator<AbstractNode> for AbstractNodeChildrenIterator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct LayoutNodeChildrenIterator<'self> {
|
|
||||||
priv current_node: Option<LayoutNode<'self>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'self> Iterator<LayoutNode<'self>> for LayoutNodeChildrenIterator<'self> {
|
|
||||||
fn next(&mut self) -> Option<LayoutNode<'self>> {
|
|
||||||
let node = self.current_node;
|
|
||||||
self.current_node = do self.current_node.and_then |node| {
|
|
||||||
node.next_sibling()
|
|
||||||
};
|
|
||||||
node
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct AncestorIterator {
|
pub struct AncestorIterator {
|
||||||
priv current: Option<AbstractNode>,
|
priv current: Option<AbstractNode>,
|
||||||
}
|
}
|
||||||
|
@ -922,49 +714,6 @@ fn gather_abstract_nodes(cur: &AbstractNode, refs: &mut ~[AbstractNode], postord
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: Do this without precomputing a vector of refs.
|
|
||||||
// Easy for preorder; harder for postorder.
|
|
||||||
//
|
|
||||||
// FIXME(pcwalton): Parallelism! Eventually this should just be nuked.
|
|
||||||
pub struct LayoutTreeIterator<'self> {
|
|
||||||
priv nodes: ~[LayoutNode<'self>],
|
|
||||||
priv index: uint,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'self> LayoutTreeIterator<'self> {
|
|
||||||
fn new(nodes: ~[LayoutNode<'self>]) -> LayoutTreeIterator<'self> {
|
|
||||||
LayoutTreeIterator {
|
|
||||||
nodes: nodes,
|
|
||||||
index: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'self> Iterator<LayoutNode<'self>> for LayoutTreeIterator<'self> {
|
|
||||||
fn next(&mut self) -> Option<LayoutNode<'self>> {
|
|
||||||
if self.index >= self.nodes.len() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
let v = self.nodes[self.index].clone();
|
|
||||||
self.index += 1;
|
|
||||||
Some(v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// FIXME(pcwalton): This is super inefficient.
|
|
||||||
fn gather_layout_nodes<'a>(cur: &LayoutNode<'a>, refs: &mut ~[LayoutNode<'a>], postorder: bool) {
|
|
||||||
if !postorder {
|
|
||||||
refs.push(cur.clone());
|
|
||||||
}
|
|
||||||
for kid in cur.children() {
|
|
||||||
gather_layout_nodes(&kid, refs, postorder)
|
|
||||||
}
|
|
||||||
if postorder {
|
|
||||||
refs.push(cur.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AbstractNode {
|
impl AbstractNode {
|
||||||
/// Iterates over all ancestors of this node.
|
/// Iterates over all ancestors of this node.
|
||||||
pub fn ancestors(&self) -> AncestorIterator {
|
pub fn ancestors(&self) -> AncestorIterator {
|
||||||
|
@ -1629,80 +1378,3 @@ impl Reflectable for Node<ScriptView> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A bottom-up, parallelizable traversal.
|
|
||||||
pub trait PostorderNodeTraversal {
|
|
||||||
/// The operation to perform. Return true to continue or false to stop.
|
|
||||||
fn process<'a>(&'a self, node: LayoutNode<'a>) -> bool;
|
|
||||||
|
|
||||||
/// Returns true if this node should be pruned. If this returns true, we skip the operation
|
|
||||||
/// entirely and do not process any descendant nodes. This is called *before* child nodes are
|
|
||||||
/// visited. The default implementation never prunes any nodes.
|
|
||||||
fn should_prune<'a>(&'a self, _node: LayoutNode<'a>) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A bottom-up, parallelizable traversal.
|
|
||||||
pub trait PostorderNodeMutTraversal {
|
|
||||||
/// The operation to perform. Return true to continue or false to stop.
|
|
||||||
fn process<'a>(&'a mut self, node: LayoutNode<'a>) -> bool;
|
|
||||||
|
|
||||||
/// Returns true if this node should be pruned. If this returns true, we skip the operation
|
|
||||||
/// entirely and do not process any descendant nodes. This is called *before* child nodes are
|
|
||||||
/// visited. The default implementation never prunes any nodes.
|
|
||||||
fn should_prune<'a>(&'a self, _node: LayoutNode<'a>) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'self> LayoutNode<'self> {
|
|
||||||
/// Traverses the tree in postorder.
|
|
||||||
///
|
|
||||||
/// TODO(pcwalton): Offer a parallel version with a compatible API.
|
|
||||||
pub fn traverse_postorder<T:PostorderNodeTraversal>(self, traversal: &T) -> bool {
|
|
||||||
if traversal.should_prune(self) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut opt_kid = self.first_child();
|
|
||||||
loop {
|
|
||||||
match opt_kid {
|
|
||||||
None => break,
|
|
||||||
Some(kid) => {
|
|
||||||
if !kid.traverse_postorder(traversal) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
opt_kid = kid.next_sibling()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
traversal.process(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Traverses the tree in postorder.
|
|
||||||
///
|
|
||||||
/// TODO(pcwalton): Offer a parallel version with a compatible API.
|
|
||||||
pub fn traverse_postorder_mut<T:PostorderNodeMutTraversal>(mut self, traversal: &mut T)
|
|
||||||
-> bool {
|
|
||||||
if traversal.should_prune(self) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut opt_kid = self.first_child();
|
|
||||||
loop {
|
|
||||||
match opt_kid {
|
|
||||||
None => break,
|
|
||||||
Some(kid) => {
|
|
||||||
if !kid.traverse_postorder_mut(traversal) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
opt_kid = kid.next_sibling()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
traversal.process(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue