Auto merge of #8098 - bholley:dirty_from_layout, r=jdm

Track event state changes on Document and do the dirtying from layout

This is a first step in fixing #6942.

<!-- Reviewable:start -->
[<img src="https://reviewable.io/review_button.png" height=40 alt="Review on Reviewable"/>](https://reviewable.io/reviews/servo/servo/8098)
<!-- Reviewable:end -->
This commit is contained in:
bors-servo 2015-10-29 00:24:53 +05:30
commit 285e29c066
8 changed files with 179 additions and 44 deletions

View file

@ -52,6 +52,7 @@ use query::{LayoutRPCImpl, process_content_box_request, process_content_boxes_re
use query::{MarginPadding, MarginRetrievingFragmentBorderBoxIterator, PositionProperty};
use query::{PositionRetrievingFragmentBorderBoxIterator, Side};
use script::dom::bindings::js::LayoutJS;
use script::dom::document::Document;
use script::dom::node::{LayoutData, Node};
use script::layout_interface::Animation;
use script::layout_interface::{LayoutChan, LayoutRPC, OffsetParentResponse};
@ -67,7 +68,7 @@ use std::cell::Cell;
use std::collections::HashMap;
use std::collections::hash_state::DefaultState;
use std::mem::transmute;
use std::ops::{Deref, DerefMut};
use std::ops::{Deref, DerefMut, Drop};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::mpsc::{channel, Sender, Receiver};
use std::sync::{Arc, Mutex, MutexGuard};
@ -89,8 +90,7 @@ use util::opts;
use util::task::spawn_named_with_send_on_failure;
use util::task_state;
use util::workqueue::WorkQueue;
use wrapper::LayoutNode;
use wrapper::ThreadSafeLayoutNode;
use wrapper::{LayoutDocument, LayoutNode, ThreadSafeLayoutNode};
/// The number of screens of data we're allowed to generate display lists for in each direction.
pub const DISPLAY_PORT_SIZE_FACTOR: i32 = 8;
@ -1125,14 +1125,28 @@ impl LayoutTask {
fn handle_reflow<'a>(&'a self,
data: &ScriptReflow,
possibly_locked_rw_data: &mut Option<MutexGuard<'a, LayoutTaskData>>) {
// FIXME: Isolate this transmutation into a "bridge" module.
// FIXME(rust#16366): The following line had to be moved because of a
// rustc bug. It should be in the next unsafe block.
let mut node: LayoutJS<Node> = unsafe {
LayoutJS::from_trusted_node_address(data.document_root)
// Make sure that every return path from this method joins the script task,
// otherwise the script task will panic.
struct AutoJoinScriptTask<'a> { data: &'a ScriptReflow };
impl<'a> Drop for AutoJoinScriptTask<'a> {
fn drop(&mut self) {
self.data.script_join_chan.send(()).unwrap();
}
};
let node: &mut LayoutNode = unsafe {
transmute(&mut node)
let _ajst = AutoJoinScriptTask { data: data };
// FIXME: Isolate this transmutation into a "bridge" module.
let mut doc: LayoutJS<Document> = unsafe {
LayoutJS::from_trusted_node_address(data.document).downcast::<Document>().unwrap()
};
let doc: &mut LayoutDocument = unsafe {
transmute(&mut doc)
};
let mut node: LayoutNode = match doc.root_node() {
None => return,
Some(x) => x,
};
debug!("layout: received layout request for: {}", self.url.serialize());
@ -1176,15 +1190,23 @@ impl LayoutTask {
let needs_reflow = screen_size_changed && !needs_dirtying;
unsafe {
if needs_dirtying {
LayoutTask::dirty_all_nodes(node);
LayoutTask::dirty_all_nodes(&mut node);
}
}
if needs_reflow {
if let Some(mut flow) = self.try_get_layout_root(*node) {
if let Some(mut flow) = self.try_get_layout_root(node) {
LayoutTask::reflow_all_nodes(flow_ref::deref_mut(&mut flow));
}
}
let event_state_changes = doc.drain_event_state_changes();
if !needs_dirtying {
for &(el, state) in event_state_changes.iter() {
assert!(!state.is_empty());
el.note_event_state_change();
}
}
// Create a layout context for use throughout the following passes.
let mut shared_layout_context = self.build_shared_layout_context(&*rw_data,
screen_size_changed,
@ -1202,16 +1224,16 @@ impl LayoutTask {
let rw_data = &mut *rw_data;
match rw_data.parallel_traversal {
None => {
sequential::traverse_dom_preorder(*node, &shared_layout_context);
sequential::traverse_dom_preorder(node, &shared_layout_context);
}
Some(ref mut traversal) => {
parallel::traverse_dom_preorder(*node, &shared_layout_context, traversal);
parallel::traverse_dom_preorder(node, &shared_layout_context, traversal);
}
}
});
// Retrieve the (possibly rebuilt) root flow.
rw_data.root_flow = self.try_get_layout_root((*node).clone());
rw_data.root_flow = self.try_get_layout_root(node.clone());
}
// Send new canvas renderers to the paint task
@ -1245,9 +1267,6 @@ impl LayoutTask {
ReflowQueryType::NoQuery => {}
}
}
// Tell script that we're done.
data.script_join_chan.send(()).unwrap();
}
fn set_visible_rects<'a>(&'a self,

View file

@ -43,8 +43,9 @@ use script::dom::bindings::codegen::InheritTypes::{HTMLElementTypeId, NodeTypeId
use script::dom::bindings::conversions::Castable;
use script::dom::bindings::js::LayoutJS;
use script::dom::characterdata::LayoutCharacterDataHelpers;
use script::dom::document::{Document, LayoutDocumentHelpers};
use script::dom::element;
use script::dom::element::{Element, LayoutElementHelpers, RawLayoutElementHelpers};
use script::dom::element::{Element, EventState, LayoutElementHelpers, RawLayoutElementHelpers};
use script::dom::htmlcanvaselement::{LayoutHTMLCanvasElementHelpers, HTMLCanvasData};
use script::dom::htmliframeelement::HTMLIFrameElement;
use script::dom::htmlimageelement::LayoutHTMLImageElementHelpers;
@ -58,6 +59,7 @@ use selectors::parser::{AttrSelector, NamespaceConstraint};
use smallvec::VecLike;
use std::borrow::ToOwned;
use std::cell::{Ref, RefMut};
use std::iter::FromIterator;
use std::marker::PhantomData;
use std::mem;
use std::sync::Arc;
@ -105,6 +107,12 @@ impl<'ln> LayoutNode<'ln> {
}
}
pub fn is_element(&self) -> bool {
unsafe {
self.node.is_element_for_layout()
}
}
pub fn dump(self) {
self.dump_indent(0);
}
@ -336,6 +344,41 @@ impl<'a> Iterator for LayoutTreeIterator<'a> {
}
}
// A wrapper around documents that ensures ayout can only ever access safe properties.
#[derive(Copy, Clone)]
pub struct LayoutDocument<'le> {
document: LayoutJS<Document>,
chain: PhantomData<&'le ()>,
}
impl<'le> LayoutDocument<'le> {
pub fn as_node(&self) -> LayoutNode<'le> {
LayoutNode {
node: self.document.upcast(),
chain: PhantomData,
}
}
pub fn root_node(&self) -> Option<LayoutNode<'le>> {
let mut node = self.as_node().first_child();
while node.is_some() && !node.unwrap().is_element() {
node = node.unwrap().next_sibling();
}
node
}
pub fn drain_event_state_changes(&self) -> Vec<(LayoutElement, EventState)> {
unsafe {
let changes = self.document.drain_event_state_changes();
Vec::from_iter(changes.iter().map(|&(el, state)|
(LayoutElement {
element: el,
chain: PhantomData,
}, state)))
}
}
}
/// A wrapper around elements that ensures layout can only ever access safe properties.
#[derive(Copy, Clone)]
pub struct LayoutElement<'le> {
@ -356,6 +399,41 @@ impl<'le> LayoutElement<'le> {
chain: PhantomData,
}
}
/// Properly marks nodes as dirty in response to event state changes.
///
/// Currently this implementation is very conservative, and basically mirrors node::dirty_impl.
/// With restyle hints, we can do less work here.
pub fn note_event_state_change(&self) {
let node = self.as_node();
// Bail out if we're already dirty. This won't be valid when we start doing more targeted
// dirtying with restyle hints.
if node.is_dirty() { return }
// Dirty descendants.
fn dirty_subtree(node: LayoutNode) {
// Stop if this subtree is already dirty. This won't be valid with restyle hints, see above.
if node.is_dirty() { return }
unsafe {
node.set_dirty(true);
node.set_dirty_descendants(true);
}
for kid in node.children() {
dirty_subtree(kid);
}
}
dirty_subtree(node);
let mut curr = node;
while let Some(parent) = curr.parent_node() {
if parent.has_dirty_descendants() { break }
unsafe { parent.set_dirty_descendants(true); }
curr = parent;
}
}
}
fn as_element<'le>(node: LayoutJS<Node>) -> Option<LayoutElement<'le>> {