layout: Replace AbstractNodes in layout with OpaqueNode, preventing

DOM access during reflow.
This commit is contained in:
Patrick Walton 2013-12-13 18:29:30 -08:00
parent 550c370c4f
commit 89af9017ef
5 changed files with 96 additions and 64 deletions

View file

@ -40,6 +40,7 @@ use layout::float_context::{ClearType, ClearLeft, ClearRight, ClearBoth};
use layout::flow::Flow; use layout::flow::Flow;
use layout::flow; use layout::flow;
use layout::model::{MaybeAuto, specified}; use layout::model::{MaybeAuto, specified};
use layout::util::OpaqueNode;
/// 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:
@ -63,8 +64,8 @@ use layout::model::{MaybeAuto, specified};
/// FIXME(pcwalton): This can be slimmed down quite a bit. /// FIXME(pcwalton): This can be slimmed down quite a bit.
#[deriving(Clone)] #[deriving(Clone)]
pub struct Box { pub struct Box {
/// The DOM node that this `Box` originates from. /// An opaque reference to the DOM node that this `Box` originates from.
node: AbstractNode<LayoutView>, node: OpaqueNode,
/// The CSS style of this box. /// The CSS style of this box.
style: Arc<ComputedValues>, style: Arc<ComputedValues>,
@ -250,7 +251,7 @@ impl Box {
} }
Box { Box {
node: node, node: OpaqueNode::from_node(&node),
style: (*nearest_ancestor_element.style()).clone(), style: (*nearest_ancestor_element.style()).clone(),
position: Slot::init(Au::zero_rect()), position: Slot::init(Au::zero_rect()),
border: Slot::init(Zero::zero()), border: Slot::init(Zero::zero()),
@ -288,7 +289,7 @@ impl Box {
/// CSS 2.1. /// CSS 2.1.
fn guess_width(&self) -> Au { fn guess_width(&self) -> Au {
match self.specific { match self.specific {
GenericBox | ImageBox(_) => {} GenericBox | IframeBox(_) | ImageBox(_) => {}
ScannedTextBox(_) | UnscannedTextBox(_) => return Au(0), ScannedTextBox(_) | UnscannedTextBox(_) => return Au(0),
} }

View file

@ -6,26 +6,20 @@
use layout::box::Box; use layout::box::Box;
use layout::context::LayoutContext; use layout::context::LayoutContext;
use std::cast::transmute; use layout::util::OpaqueNode;
use script::dom::node::AbstractNode;
use gfx; use gfx;
use style; use style;
/// Display list data is usually an AbstractNode with view () to indicate
/// that nodes in this view shoud not really be touched. The idea is to
/// store the nodes in the display list and have layout transmute them.
pub trait ExtraDisplayListData { pub trait ExtraDisplayListData {
fn new(box: &Box) -> Self; fn new(box: &Box) -> Self;
} }
pub type Nothing = (); pub type Nothing = ();
impl ExtraDisplayListData for AbstractNode<()> { impl ExtraDisplayListData for OpaqueNode {
fn new(box: &Box) -> AbstractNode<()> { fn new(box: &Box) -> OpaqueNode {
unsafe { box.node
transmute(box.node)
}
} }
} }

View file

@ -16,7 +16,7 @@ use layout::flow::{Flow, ImmutableFlowUtils, MutableFlowUtils, PreorderFlowTrave
use layout::flow::{PostorderFlowTraversal}; use layout::flow::{PostorderFlowTraversal};
use layout::flow; use layout::flow;
use layout::incremental::{RestyleDamage}; use layout::incremental::{RestyleDamage};
use layout::util::{LayoutData, LayoutDataAccess}; use layout::util::{LayoutData, LayoutDataAccess, OpaqueNode};
use extra::arc::{Arc, RWArc, MutexArc}; use extra::arc::{Arc, RWArc, MutexArc};
use geom::point::Point2D; use geom::point::Point2D;
@ -69,7 +69,7 @@ struct LayoutTask {
script_chan: ScriptChan, script_chan: ScriptChan,
/// The channel on which messages can be sent to the painting task. /// The channel on which messages can be sent to the painting task.
render_chan: RenderChan<AbstractNode<()>>, render_chan: RenderChan<OpaqueNode>,
/// The channel on which messages can be sent to the image cache. /// The channel on which messages can be sent to the image cache.
image_cache_task: ImageCacheTask, image_cache_task: ImageCacheTask,
@ -81,7 +81,7 @@ struct LayoutTask {
screen_size: Option<Size2D<Au>>, screen_size: Option<Size2D<Au>>,
/// A cached display list. /// A cached display list.
display_list: Option<Arc<DisplayList<AbstractNode<()>>>>, display_list: Option<Arc<DisplayList<OpaqueNode>>>,
stylist: RWArc<Stylist>, stylist: RWArc<Stylist>,
@ -204,7 +204,7 @@ impl LayoutTask {
port: Port<Msg>, port: Port<Msg>,
constellation_chan: ConstellationChan, constellation_chan: ConstellationChan,
script_chan: ScriptChan, script_chan: ScriptChan,
render_chan: RenderChan<AbstractNode<()>>, render_chan: RenderChan<OpaqueNode>,
img_cache_task: ImageCacheTask, img_cache_task: ImageCacheTask,
opts: Opts, opts: Opts,
profiler_chan: ProfilerChan, profiler_chan: ProfilerChan,
@ -232,7 +232,7 @@ impl LayoutTask {
port: Port<Msg>, port: Port<Msg>,
constellation_chan: ConstellationChan, constellation_chan: ConstellationChan,
script_chan: ScriptChan, script_chan: ScriptChan,
render_chan: RenderChan<AbstractNode<()>>, render_chan: RenderChan<OpaqueNode>,
image_cache_task: ImageCacheTask, image_cache_task: ImageCacheTask,
opts: &Opts, opts: &Opts,
profiler_chan: ProfilerChan) profiler_chan: ProfilerChan)
@ -478,14 +478,12 @@ impl LayoutTask {
if data.goal == ReflowForDisplay { if data.goal == ReflowForDisplay {
do profile(time::LayoutDispListBuildCategory, self.profiler_chan.clone()) { do profile(time::LayoutDispListBuildCategory, self.profiler_chan.clone()) {
let root_size = flow::base(layout_root).position.size; let root_size = flow::base(layout_root).position.size;
let display_list= ~Cell::new(DisplayList::<AbstractNode<()>>::new()); let display_list = ~Cell::new(DisplayList::<OpaqueNode>::new());
let dirty = flow::base(layout_root).position.clone(); let dirty = flow::base(layout_root).position.clone();
layout_root.build_display_list( let display_list_builder = DisplayListBuilder {
&DisplayListBuilder {
ctx: &layout_ctx, ctx: &layout_ctx,
}, };
&dirty, layout_root.build_display_list(&display_list_builder, &dirty, display_list);
display_list);
let display_list = Arc::new(display_list.take()); let display_list = Arc::new(display_list.take());
@ -494,9 +492,13 @@ impl LayoutTask {
for child in node.traverse_preorder() { for child in node.traverse_preorder() {
if child.type_id() == ElementNodeTypeId(HTMLHtmlElementTypeId) || if child.type_id() == ElementNodeTypeId(HTMLHtmlElementTypeId) ||
child.type_id() == ElementNodeTypeId(HTMLBodyElementTypeId) { child.type_id() == ElementNodeTypeId(HTMLBodyElementTypeId) {
let element_bg_color = child.style().get().resolve_color( let element_bg_color = child.style()
child.style().get().Background.background_color .get()
).to_gfx_color(); .resolve_color(child.style()
.get()
.Background
.background_color)
.to_gfx_color();
match element_bg_color { match element_bg_color {
color::rgba(0., 0., 0., 0.) => {} color::rgba(0., 0., 0., 0.) => {}
_ => { _ => {
@ -532,16 +534,15 @@ impl LayoutTask {
/// `getClientRects()` or `getBoundingClientRect()` ultimately invoke. /// `getClientRects()` or `getBoundingClientRect()` ultimately invoke.
fn handle_query(&self, query: LayoutQuery) { fn handle_query(&self, query: LayoutQuery) {
match query { match query {
// The neat thing here is that in order to answer the following two queries we only
// need to compare nodes for equality. Thus we can safely work only with `OpaqueNode`.
ContentBoxQuery(node, reply_chan) => { ContentBoxQuery(node, reply_chan) => {
// FIXME: Isolate this transmutation into a single "bridge" module. let node = OpaqueNode::from_node(&node);
let node: AbstractNode<()> = unsafe {
transmute(node)
};
fn union_boxes_for_node<'a>( fn union_boxes_for_node<'a>(
accumulator: &mut Option<Rect<Au>>, accumulator: &mut Option<Rect<Au>>,
mut iter: DisplayItemIterator<'a,AbstractNode<()>>, mut iter: DisplayItemIterator<'a,OpaqueNode>,
node: AbstractNode<()>) { node: OpaqueNode) {
for item in iter { for item in iter {
union_boxes_for_node(accumulator, item.children(), node); union_boxes_for_node(accumulator, item.children(), node);
if item.base().extra == node { if item.base().extra == node {
@ -559,15 +560,12 @@ impl LayoutTask {
reply_chan.send(ContentBoxResponse(rect.unwrap_or(Au::zero_rect()))) reply_chan.send(ContentBoxResponse(rect.unwrap_or(Au::zero_rect())))
} }
ContentBoxesQuery(node, reply_chan) => { ContentBoxesQuery(node, reply_chan) => {
// FIXME: Isolate this transmutation into a single "bridge" module. let node = OpaqueNode::from_node(&node);
let node: AbstractNode<()> = unsafe {
transmute(node)
};
fn add_boxes_for_node<'a>( fn add_boxes_for_node<'a>(
accumulator: &mut ~[Rect<Au>], accumulator: &mut ~[Rect<Au>],
mut iter: DisplayItemIterator<'a,AbstractNode<()>>, mut iter: DisplayItemIterator<'a,OpaqueNode>,
node: AbstractNode<()>) { node: OpaqueNode) {
for item in iter { for item in iter {
add_boxes_for_node(accumulator, item.children(), node); add_boxes_for_node(accumulator, item.children(), node);
if item.base().extra == node { if item.base().extra == node {
@ -582,7 +580,7 @@ impl LayoutTask {
reply_chan.send(ContentBoxesResponse(boxes)) reply_chan.send(ContentBoxesResponse(boxes))
} }
HitTestQuery(_, point, reply_chan) => { HitTestQuery(_, point, reply_chan) => {
fn hit_test(x: Au, y: Au, list: &[DisplayItem<AbstractNode<()>>]) fn hit_test(x: Au, y: Au, list: &[DisplayItem<OpaqueNode>])
-> Option<HitTestResponse> { -> Option<HitTestResponse> {
for item in list.rev_iter() { for item in list.rev_iter() {
match *item { match *item {
@ -602,13 +600,19 @@ impl LayoutTask {
_ => {} _ => {}
} }
let bounds = item.bounds(); let bounds = item.bounds();
// TODO this check should really be performed by a method of DisplayItem
// TODO(tikue): This check should really be performed by a method of
// DisplayItem.
if x < bounds.origin.x + bounds.size.width && if x < bounds.origin.x + bounds.size.width &&
bounds.origin.x <= x && bounds.origin.x <= x &&
y < bounds.origin.y + bounds.size.height && y < bounds.origin.y + bounds.size.height &&
bounds.origin.y <= y { bounds.origin.y <= y {
// FIXME(pcwalton): This `unsafe` block is too unsafe, since incorrect
// incremental flow construction could create this. Paranoid validation
// against the set of valid nodes should occur in the script task to
// ensure that this is a valid address instead of transmuting here.
let node: AbstractNode<LayoutView> = unsafe { let node: AbstractNode<LayoutView> = unsafe {
transmute(item.base().extra) item.base().extra.to_node()
}; };
let resp = Some(HitTestResponse(node)); let resp = Some(HitTestResponse(node));
return resp; return resp;

View file

@ -12,6 +12,7 @@ use servo_util::slot::{MutSlotRef, SlotRef};
use servo_util::tree::TreeNodeRef; use servo_util::tree::TreeNodeRef;
use std::cast; use std::cast;
use std::iter::Enumerate; use std::iter::Enumerate;
use std::libc::uintptr_t;
use std::vec::VecIterator; use std::vec::VecIterator;
use style::{ComputedValues, PropertyDeclaration}; use style::{ComputedValues, PropertyDeclaration};
@ -182,3 +183,35 @@ impl LayoutDataAccess for AbstractNode<LayoutView> {
} }
} }
/// An opaque handle to a node. The only safe operation that can be performed on this node is to
/// compare it to another opaque handle or to another node.
///
/// Because the script task's GC does not trace layout, node data cannot be safely stored in layout
/// data structures. Also, layout code tends to be faster when the DOM is not being accessed, for
/// locality reasons. Using `OpaqueNode` enforces this invariant.
#[deriving(Clone, Eq)]
pub struct OpaqueNode(uintptr_t);
impl<T> Equiv<AbstractNode<T>> for OpaqueNode {
fn equiv(&self, node: &AbstractNode<T>) -> bool {
unsafe {
**self == cast::transmute_copy::<AbstractNode<T>,uintptr_t>(node)
}
}
}
impl OpaqueNode {
/// Converts a DOM node to an `OpaqueNode`.
pub fn from_node<T>(node: &AbstractNode<T>) -> OpaqueNode {
unsafe {
OpaqueNode(cast::transmute_copy(node))
}
}
/// Unsafely converts an `OpaqueNode` to a DOM node. Use this only if you absolutely know what
/// you're doing.
pub unsafe fn to_node<T>(&self) -> AbstractNode<T> {
cast::transmute(**self)
}
}

View file

@ -9,7 +9,7 @@ use extra::url::Url;
use gfx::opts::Opts; use gfx::opts::Opts;
use gfx::render_task::{PaintPermissionGranted, PaintPermissionRevoked}; use gfx::render_task::{PaintPermissionGranted, PaintPermissionRevoked};
use gfx::render_task::{RenderChan, RenderTask}; use gfx::render_task::{RenderChan, RenderTask};
use script::dom::node::AbstractNode; use layout::util::OpaqueNode;
use script::layout_interface::LayoutChan; use script::layout_interface::LayoutChan;
use script::script_task::LoadMsg; use script::script_task::LoadMsg;
use script::script_task::{AttachLayoutMsg, NewLayoutInfo, ScriptTask, ScriptChan}; use script::script_task::{AttachLayoutMsg, NewLayoutInfo, ScriptTask, ScriptChan};
@ -27,19 +27,19 @@ pub struct Pipeline {
subpage_id: Option<SubpageId>, subpage_id: Option<SubpageId>,
script_chan: ScriptChan, script_chan: ScriptChan,
layout_chan: LayoutChan, layout_chan: LayoutChan,
render_chan: RenderChan<AbstractNode<()>>, render_chan: RenderChan<OpaqueNode>,
layout_shutdown_port: Port<()>, layout_shutdown_port: Port<()>,
render_shutdown_port: Port<()>, render_shutdown_port: Port<()>,
/// The most recently loaded url /// The most recently loaded url
url: Option<Url>, url: Option<Url>,
} }
/// A subset of the Pipeline nthat is eeded for layer composition /// The subset of the pipeline that is needed for layer composition.
#[deriving(Clone)] #[deriving(Clone)]
pub struct CompositionPipeline { pub struct CompositionPipeline {
id: PipelineId, id: PipelineId,
script_chan: ScriptChan, script_chan: ScriptChan,
render_chan: RenderChan<AbstractNode<()>>, render_chan: RenderChan<OpaqueNode>,
} }
impl Pipeline { impl Pipeline {
@ -182,7 +182,7 @@ impl Pipeline {
subpage_id: Option<SubpageId>, subpage_id: Option<SubpageId>,
script_chan: ScriptChan, script_chan: ScriptChan,
layout_chan: LayoutChan, layout_chan: LayoutChan,
render_chan: RenderChan<AbstractNode<()>>, render_chan: RenderChan<OpaqueNode>,
layout_shutdown_port: Port<()>, layout_shutdown_port: Port<()>,
render_shutdown_port: Port<()>) render_shutdown_port: Port<()>)
-> Pipeline { -> Pipeline {