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

@ -94,6 +94,13 @@ impl<T> DOMRefCell<T> {
_ => None,
}
}
/// Version of the above that we use during restyle while the script task
/// is blocked.
pub fn borrow_mut_for_layout(&self) -> RefMut<T> {
debug_assert!(task_state::get().is_layout());
self.value.borrow_mut()
}
}
impl<T: JSTraceable> JSTraceable for DOMRefCell<T> {

View file

@ -35,6 +35,7 @@ use layout_interface::TrustedNodeAddress;
use script_task::STACK_ROOTS;
use std::cell::UnsafeCell;
use std::default::Default;
use std::hash::{Hash, Hasher};
use std::mem;
use std::ops::Deref;
use std::ptr;
@ -147,12 +148,24 @@ impl<T> PartialEq for JS<T> {
}
}
impl<T> Eq for JS<T> {}
impl<T> PartialEq for LayoutJS<T> {
fn eq(&self, other: &LayoutJS<T>) -> bool {
self.ptr == other.ptr
}
}
impl<T> Eq for LayoutJS<T> {}
impl<T> Hash for JS<T> {
fn hash<H: Hasher>(&self, state: &mut H) { self.ptr.hash(state) }
}
impl<T> Hash for LayoutJS<T> {
fn hash<H: Hasher>(&self, state: &mut H) { self.ptr.hash(state) }
}
impl <T> Clone for JS<T> {
#[inline]
#[allow(unrooted_must_root)]

View file

@ -32,7 +32,7 @@ use dom::customevent::CustomEvent;
use dom::documentfragment::DocumentFragment;
use dom::documenttype::DocumentType;
use dom::domimplementation::DOMImplementation;
use dom::element::{Element, ElementCreator};
use dom::element::{Element, ElementCreator, EventState};
use dom::event::{Event, EventBubbles, EventCancelable};
use dom::eventtarget::{EventTarget};
use dom::htmlanchorelement::HTMLAnchorElement;
@ -174,6 +174,8 @@ pub struct Document {
/// This field is set to the document itself for inert documents.
/// https://html.spec.whatwg.org/multipage/#appropriate-template-contents-owner-document
appropriate_template_contents_owner_document: MutNullableHeap<JS<Document>>,
// The collection of EventStates that have been changed since the last restyle.
event_state_changes: DOMRefCell<HashMap<JS<Element>, EventState>>,
}
impl PartialEq for Document {
@ -301,6 +303,11 @@ impl Document {
}
}
pub fn needs_reflow(&self) -> bool {
self.GetDocumentElement().is_some() &&
(self.upcast::<Node>().get_has_dirty_descendants() || !self.event_state_changes.borrow().is_empty())
}
/// Returns the first `base` element in the DOM that has an `href` attribute.
pub fn base_element(&self) -> Option<Root<HTMLBaseElement>> {
self.base_element.get_rooted()
@ -1175,18 +1182,27 @@ pub enum DocumentSource {
NotFromParser,
}
#[allow(unsafe_code)]
pub trait LayoutDocumentHelpers {
#[allow(unsafe_code)]
unsafe fn is_html_document_for_layout(&self) -> bool;
unsafe fn drain_event_state_changes(&self) -> Vec<(LayoutJS<Element>, EventState)>;
}
#[allow(unsafe_code)]
impl LayoutDocumentHelpers for LayoutJS<Document> {
#[allow(unrooted_must_root)]
#[inline]
#[allow(unsafe_code)]
unsafe fn is_html_document_for_layout(&self) -> bool {
(*self.unsafe_get()).is_html_document
}
#[inline]
#[allow(unrooted_must_root)]
unsafe fn drain_event_state_changes(&self) -> Vec<(LayoutJS<Element>, EventState)> {
let mut changes = (*self.unsafe_get()).event_state_changes.borrow_mut_for_layout();
let drain = changes.drain();
let layout_drain = drain.map(|(k, v)| (k.to_layout(), v));
Vec::from_iter(layout_drain)
}
}
impl Document {
@ -1252,6 +1268,7 @@ impl Document {
reflow_timeout: Cell::new(None),
base_element: Default::default(),
appropriate_template_contents_owner_document: Default::default(),
event_state_changes: DOMRefCell::new(HashMap::new()),
}
}
@ -1316,6 +1333,20 @@ impl Document {
pub fn get_element_by_id(&self, id: &Atom) -> Option<Root<Element>> {
self.idmap.borrow().get(&id).map(|ref elements| (*elements)[0].root())
}
pub fn record_event_state_change(&self, el: &Element, which: EventState) {
let mut map = self.event_state_changes.borrow_mut();
let empty;
{
let states = map.entry(JS::from_ref(el))
.or_insert(EventState::empty());
states.toggle(which);
empty = states.is_empty();
}
if empty {
map.remove(&JS::from_ref(el));
}
}
}

View file

@ -1836,6 +1836,9 @@ impl Element {
fn set_state(&self, which: EventState, value: bool) {
let mut state = self.event_state.get();
if state.contains(which) == value {
return
}
match value {
true => state.insert(which),
false => state.remove(which),
@ -1843,7 +1846,7 @@ impl Element {
self.event_state.set(state);
let node = self.upcast::<Node>();
node.dirty(NodeDamage::NodeStyleDamaged);
node.owner_doc().record_event_state_change(self, which);
}
pub fn get_active_state(&self) -> bool {

View file

@ -885,14 +885,6 @@ impl Window {
///
/// TODO(pcwalton): Only wait for style recalc, since we have off-main-thread layout.
pub fn force_reflow(&self, goal: ReflowGoal, query_type: ReflowQueryType, reason: ReflowReason) {
let document = self.Document();
let root = document.r().GetDocumentElement();
let root = match root.r() {
Some(root) => root,
None => return,
};
let root = root.upcast::<Node>();
let window_size = match self.window_size.get() {
Some(window_size) => window_size,
None => return,
@ -923,7 +915,7 @@ impl Window {
goal: goal,
page_clip_rect: self.page_clip_rect.get(),
},
document_root: root.to_trusted_node_address(),
document: self.Document().r().upcast::<Node>().to_trusted_node_address(),
window_size: window_size,
script_chan: self.control_chan.clone(),
script_join_chan: join_chan,
@ -965,16 +957,8 @@ impl Window {
///
/// TODO(pcwalton): Only wait for style recalc, since we have off-main-thread layout.
pub fn reflow(&self, goal: ReflowGoal, query_type: ReflowQueryType, reason: ReflowReason) {
let document = self.Document();
let root = document.r().GetDocumentElement();
let root = match root.r() {
Some(root) => root,
None => return,
};
let root = root.upcast::<Node>();
if query_type == ReflowQueryType::NoQuery && !root.get_has_dirty_descendants() {
debug!("root has no dirty descendants; avoiding reflow (reason {:?})", reason);
if query_type == ReflowQueryType::NoQuery && !self.Document().needs_reflow() {
debug!("Document doesn't need reflow - skipping it (reason {:?})", reason);
return
}

View file

@ -174,7 +174,7 @@ pub struct ScriptReflow {
/// General reflow data.
pub reflow_info: Reflow,
/// The document node.
pub document_root: TrustedNodeAddress,
pub document: TrustedNodeAddress,
/// The channel through which messages can be sent back to the script task.
pub script_chan: Sender<ConstellationControlMsg>,
/// The current window size.