mirror of
https://github.com/servo/servo.git
synced 2025-08-06 14:10:11 +01:00
Incremental Style Recalc
This patch puts in the initial framework for incremental reflow. Nodes' styles are no longer recalculated unless the node has changed. I've been hacking on the general problem of incremental reflow for the past couple weeks, and I've yet to get a full implementation that actually passes all the reftests + wikipedia + cnn. Therefore, I'm going to try to land the different parts of it one by one. This patch only does incremental style recalc, without incremental flow construction, inline-size bubbling, reflow, or display lists. Those will be coming in that order as I finish them. At least with this strategy, I can land a working version of incremental reflow, even if not yet complete. r? @pcwalton
This commit is contained in:
parent
510f8a817f
commit
d12c6e7383
31 changed files with 641 additions and 424 deletions
|
@ -49,8 +49,10 @@ use http::headers::response::HeaderCollection as ResponseHeaderCollection;
|
|||
use http::headers::request::HeaderCollection as RequestHeaderCollection;
|
||||
use http::method::Method;
|
||||
use std::io::timer::Timer;
|
||||
use script_traits::UntrustedNodeAddress;
|
||||
use servo_msg::compositor_msg::ScriptListener;
|
||||
use servo_msg::constellation_msg::ConstellationChan;
|
||||
use servo_util::smallvec::{SmallVec1, SmallVec};
|
||||
use layout_interface::{LayoutRPC, LayoutChan};
|
||||
use dom::bindings::utils::WindowProxyHandler;
|
||||
|
||||
|
@ -148,6 +150,17 @@ impl<T: JSTraceable> JSTraceable for Vec<T> {
|
|||
}
|
||||
}
|
||||
|
||||
// XXXManishearth Check if the following three are optimized to no-ops
|
||||
// if e.trace() is a no-op (e.g it is an untraceable type)
|
||||
impl<T: JSTraceable + 'static> JSTraceable for SmallVec1<T> {
|
||||
#[inline]
|
||||
fn trace(&self, trc: *mut JSTracer) {
|
||||
for e in self.iter() {
|
||||
e.trace(trc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: JSTraceable> JSTraceable for Option<T> {
|
||||
#[inline]
|
||||
fn trace(&self, trc: *mut JSTracer) {
|
||||
|
@ -192,6 +205,7 @@ untraceable!(ResponseHeaderCollection, RequestHeaderCollection, Method)
|
|||
untraceable!(ConstellationChan)
|
||||
untraceable!(LayoutChan)
|
||||
untraceable!(WindowProxyHandler)
|
||||
untraceable!(UntrustedNodeAddress)
|
||||
|
||||
impl<'a> JSTraceable for &'a str {
|
||||
#[inline]
|
||||
|
|
|
@ -54,7 +54,6 @@ use dom::uievent::UIEvent;
|
|||
use dom::window::{Window, WindowHelpers};
|
||||
use html::hubbub_html_parser::build_element_from_tag;
|
||||
use hubbub::hubbub::{QuirksMode, NoQuirks, LimitedQuirks, FullQuirks};
|
||||
use layout_interface::{DocumentDamageLevel, ContentChangedDocumentDamage};
|
||||
use servo_util::namespace;
|
||||
use servo_util::str::{DOMString, split_html_space_chars};
|
||||
|
||||
|
@ -165,8 +164,8 @@ pub trait DocumentHelpers<'a> {
|
|||
fn set_quirks_mode(self, mode: QuirksMode);
|
||||
fn set_last_modified(self, value: DOMString);
|
||||
fn set_encoding_name(self, name: DOMString);
|
||||
fn content_changed(self);
|
||||
fn damage_and_reflow(self, damage: DocumentDamageLevel);
|
||||
fn content_changed(self, node: JSRef<Node>);
|
||||
fn reflow(self);
|
||||
fn wait_until_safe_to_modify_dom(self);
|
||||
fn unregister_named_element(self, to_unregister: JSRef<Element>, id: Atom);
|
||||
fn register_named_element(self, element: JSRef<Element>, id: Atom);
|
||||
|
@ -195,19 +194,19 @@ impl<'a> DocumentHelpers<'a> for JSRef<'a, Document> {
|
|||
*self.encoding_name.borrow_mut() = name;
|
||||
}
|
||||
|
||||
fn content_changed(self) {
|
||||
self.damage_and_reflow(ContentChangedDocumentDamage);
|
||||
fn content_changed(self, node: JSRef<Node>) {
|
||||
node.dirty();
|
||||
self.reflow();
|
||||
}
|
||||
|
||||
fn damage_and_reflow(self, damage: DocumentDamageLevel) {
|
||||
self.window.root().damage_and_reflow(damage);
|
||||
fn reflow(self) {
|
||||
self.window.root().reflow();
|
||||
}
|
||||
|
||||
fn wait_until_safe_to_modify_dom(self) {
|
||||
self.window.root().wait_until_safe_to_modify_dom();
|
||||
}
|
||||
|
||||
|
||||
/// Remove any existing association between the provided id and any elements in this document.
|
||||
fn unregister_named_element(self,
|
||||
to_unregister: JSRef<Element>,
|
||||
|
|
|
@ -28,8 +28,6 @@ use dom::node::{ElementNodeTypeId, Node, NodeHelpers, NodeIterator, document_fro
|
|||
use dom::node::{window_from_node, LayoutNodeHelpers};
|
||||
use dom::nodelist::NodeList;
|
||||
use dom::virtualmethods::{VirtualMethods, vtable_for};
|
||||
use layout_interface::ContentChangedDocumentDamage;
|
||||
use layout_interface::MatchSelectorsDocumentDamage;
|
||||
use devtools_traits::AttrInfo;
|
||||
use style::{matches, parse_selector_list_from_str};
|
||||
use style;
|
||||
|
@ -323,6 +321,7 @@ pub trait AttributeHandlers {
|
|||
fn remove_attribute(self, namespace: Namespace, name: &str);
|
||||
fn notify_attribute_changed(self, local_name: &Atom);
|
||||
fn has_class(&self, name: &str) -> bool;
|
||||
fn notify_attribute_removed(self);
|
||||
|
||||
fn set_atomic_attribute(self, name: &str, value: DOMString);
|
||||
|
||||
|
@ -436,19 +435,24 @@ impl<'a> AttributeHandlers for JSRef<'a, Element> {
|
|||
}
|
||||
|
||||
self.attrs.borrow_mut().remove(idx);
|
||||
self.notify_attribute_removed();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn notify_attribute_changed(self, local_name: &Atom) {
|
||||
fn notify_attribute_changed(self, _local_name: &Atom) {
|
||||
let node: JSRef<Node> = NodeCast::from_ref(self);
|
||||
if node.is_in_doc() {
|
||||
let damage = match local_name.as_slice() {
|
||||
"style" | "id" | "class" => MatchSelectorsDocumentDamage,
|
||||
_ => ContentChangedDocumentDamage
|
||||
};
|
||||
let document = node.owner_doc().root();
|
||||
document.damage_and_reflow(damage);
|
||||
document.content_changed(node);
|
||||
}
|
||||
}
|
||||
|
||||
fn notify_attribute_removed(self) {
|
||||
let node: JSRef<Node> = NodeCast::from_ref(self);
|
||||
if node.is_in_doc() {
|
||||
let document = node.owner_doc().root();
|
||||
document.content_changed(node);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -175,7 +175,8 @@ fn broadcast_radio_checked(broadcaster: JSRef<HTMLInputElement>, group: Option<&
|
|||
impl<'a> HTMLInputElementHelpers for JSRef<'a, HTMLInputElement> {
|
||||
fn force_relayout(self) {
|
||||
let doc = document_from_node(self).root();
|
||||
doc.content_changed()
|
||||
let node: JSRef<Node> = NodeCast::from_ref(self);
|
||||
doc.content_changed(node)
|
||||
}
|
||||
|
||||
fn radio_group_updated(self, group: Option<&str>) {
|
||||
|
|
|
@ -46,8 +46,9 @@ use dom::window::Window;
|
|||
use geom::rect::Rect;
|
||||
use html::hubbub_html_parser::build_element_from_tag;
|
||||
use layout_interface::{ContentBoxResponse, ContentBoxesResponse, LayoutRPC,
|
||||
LayoutChan, ReapLayoutDataMsg, TrustedNodeAddress, UntrustedNodeAddress};
|
||||
LayoutChan, ReapLayoutDataMsg, TrustedNodeAddress};
|
||||
use devtools_traits::NodeInfo;
|
||||
use script_traits::UntrustedNodeAddress;
|
||||
use servo_util::geometry::Au;
|
||||
use servo_util::str::{DOMString, null_str_as_empty};
|
||||
use style::{parse_selector_list_from_str, matches};
|
||||
|
@ -56,7 +57,7 @@ use js::jsapi::{JSContext, JSObject, JSTracer, JSRuntime};
|
|||
use js::jsfriendapi;
|
||||
use libc;
|
||||
use libc::uintptr_t;
|
||||
use std::cell::{RefCell, Ref, RefMut};
|
||||
use std::cell::{Cell, RefCell, Ref, RefMut};
|
||||
use std::default::Default;
|
||||
use std::iter::{Map, Filter};
|
||||
use std::mem;
|
||||
|
@ -101,7 +102,7 @@ pub struct Node {
|
|||
child_list: MutNullableJS<NodeList>,
|
||||
|
||||
/// A bitfield of flags for node items.
|
||||
flags: RefCell<NodeFlags>,
|
||||
flags: Cell<NodeFlags>,
|
||||
|
||||
/// Layout information. Only the layout task may touch this data.
|
||||
///
|
||||
|
@ -132,14 +133,19 @@ bitflags! {
|
|||
#[doc = "Specifies whether this node is in disabled state."]
|
||||
static InDisabledState = 0x04,
|
||||
#[doc = "Specifies whether this node is in enabled state."]
|
||||
static InEnabledState = 0x08
|
||||
static InEnabledState = 0x08,
|
||||
#[doc = "Specifies whether this node has changed since the last reflow."]
|
||||
static IsDirty = 0x10,
|
||||
#[doc = "Specifies whether this node has descendants (inclusive of itself) which \
|
||||
have changed since the last reflow."]
|
||||
static HasDirtyDescendants = 0x20,
|
||||
}
|
||||
}
|
||||
|
||||
impl NodeFlags {
|
||||
pub fn new(type_id: NodeTypeId) -> NodeFlags {
|
||||
match type_id {
|
||||
DocumentNodeTypeId => IsInDoc,
|
||||
DocumentNodeTypeId => IsInDoc | IsDirty,
|
||||
// The following elements are enabled by default.
|
||||
ElementNodeTypeId(HTMLButtonElementTypeId) |
|
||||
ElementNodeTypeId(HTMLInputElementTypeId) |
|
||||
|
@ -148,8 +154,8 @@ impl NodeFlags {
|
|||
ElementNodeTypeId(HTMLOptGroupElementTypeId) |
|
||||
ElementNodeTypeId(HTMLOptionElementTypeId) |
|
||||
//ElementNodeTypeId(HTMLMenuItemElementTypeId) |
|
||||
ElementNodeTypeId(HTMLFieldSetElementTypeId) => InEnabledState,
|
||||
_ => NodeFlags::empty(),
|
||||
ElementNodeTypeId(HTMLFieldSetElementTypeId) => InEnabledState | IsDirty,
|
||||
_ => IsDirty,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -271,7 +277,7 @@ impl<'a> PrivateNodeHelpers for JSRef<'a, Node> {
|
|||
let parent = self.parent_node().root();
|
||||
parent.map(|parent| vtable_for(&*parent).child_inserted(self));
|
||||
|
||||
document.content_changed();
|
||||
document.content_changed(self);
|
||||
}
|
||||
|
||||
// http://dom.spec.whatwg.org/#node-is-removed
|
||||
|
@ -283,7 +289,7 @@ impl<'a> PrivateNodeHelpers for JSRef<'a, Node> {
|
|||
vtable_for(&node).unbind_from_tree(parent_in_doc);
|
||||
}
|
||||
|
||||
document.content_changed();
|
||||
document.content_changed(self);
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -395,6 +401,9 @@ pub trait NodeHelpers<'a> {
|
|||
fn is_text(self) -> bool;
|
||||
fn is_anchor_element(self) -> bool;
|
||||
|
||||
fn get_flag(self, flag: NodeFlags) -> bool;
|
||||
fn set_flag(self, flag: NodeFlags, value: bool);
|
||||
|
||||
fn get_hover_state(self) -> bool;
|
||||
fn set_hover_state(self, state: bool);
|
||||
|
||||
|
@ -404,6 +413,17 @@ pub trait NodeHelpers<'a> {
|
|||
fn get_enabled_state(self) -> bool;
|
||||
fn set_enabled_state(self, state: bool);
|
||||
|
||||
fn get_is_dirty(self) -> bool;
|
||||
fn set_is_dirty(self, state: bool);
|
||||
|
||||
fn get_has_dirty_descendants(self) -> bool;
|
||||
fn set_has_dirty_descendants(self, state: bool);
|
||||
|
||||
/// Marks the given node as `IsDirty`, its siblings as `IsDirty` (to deal
|
||||
/// with sibling selectors), its ancestors as `HasDirtyDescendants`, and its
|
||||
/// descendants as `IsDirty`.
|
||||
fn dirty(self);
|
||||
|
||||
fn dump(self);
|
||||
fn dump_indent(self, indent: uint);
|
||||
fn debug_str(self) -> String;
|
||||
|
@ -426,6 +446,7 @@ pub trait NodeHelpers<'a> {
|
|||
fn summarize(self) -> NodeInfo;
|
||||
}
|
||||
|
||||
|
||||
impl<'a> NodeHelpers<'a> for JSRef<'a, Node> {
|
||||
/// Dumps the subtree rooted at this node, for debugging.
|
||||
fn dump(self) {
|
||||
|
@ -454,7 +475,7 @@ impl<'a> NodeHelpers<'a> for JSRef<'a, Node> {
|
|||
}
|
||||
|
||||
fn is_in_doc(self) -> bool {
|
||||
self.flags.borrow().contains(IsInDoc)
|
||||
self.deref().flags.get().contains(IsInDoc)
|
||||
}
|
||||
|
||||
/// Returns the type ID of this node. Fails if this node is borrowed mutably.
|
||||
|
@ -512,39 +533,98 @@ impl<'a> NodeHelpers<'a> for JSRef<'a, Node> {
|
|||
self.type_id == TextNodeTypeId
|
||||
}
|
||||
|
||||
fn get_flag(self, flag: NodeFlags) -> bool {
|
||||
self.flags.get().contains(flag)
|
||||
}
|
||||
|
||||
fn set_flag(self, flag: NodeFlags, value: bool) {
|
||||
let mut flags = self.flags.get();
|
||||
|
||||
if value {
|
||||
flags.insert(flag);
|
||||
} else {
|
||||
flags.remove(flag);
|
||||
}
|
||||
|
||||
self.flags.set(flags);
|
||||
}
|
||||
|
||||
fn get_hover_state(self) -> bool {
|
||||
self.flags.borrow().contains(InHoverState)
|
||||
self.get_flag(InHoverState)
|
||||
}
|
||||
|
||||
fn set_hover_state(self, state: bool) {
|
||||
if state {
|
||||
self.flags.borrow_mut().insert(InHoverState);
|
||||
} else {
|
||||
self.flags.borrow_mut().remove(InHoverState);
|
||||
}
|
||||
self.set_flag(InHoverState, state)
|
||||
}
|
||||
|
||||
fn get_disabled_state(self) -> bool {
|
||||
self.flags.borrow().contains(InDisabledState)
|
||||
self.get_flag(InDisabledState)
|
||||
}
|
||||
|
||||
fn set_disabled_state(self, state: bool) {
|
||||
if state {
|
||||
self.flags.borrow_mut().insert(InDisabledState);
|
||||
} else {
|
||||
self.flags.borrow_mut().remove(InDisabledState);
|
||||
}
|
||||
self.set_flag(InDisabledState, state)
|
||||
}
|
||||
|
||||
fn get_enabled_state(self) -> bool {
|
||||
self.flags.borrow().contains(InEnabledState)
|
||||
self.get_flag(InEnabledState)
|
||||
}
|
||||
|
||||
fn set_enabled_state(self, state: bool) {
|
||||
if state {
|
||||
self.flags.borrow_mut().insert(InEnabledState);
|
||||
} else {
|
||||
self.flags.borrow_mut().remove(InEnabledState);
|
||||
self.set_flag(InEnabledState, state)
|
||||
}
|
||||
|
||||
fn get_is_dirty(self) -> bool {
|
||||
self.get_flag(IsDirty)
|
||||
}
|
||||
|
||||
fn set_is_dirty(self, state: bool) {
|
||||
self.set_flag(IsDirty, state)
|
||||
}
|
||||
|
||||
fn get_has_dirty_descendants(self) -> bool {
|
||||
self.get_flag(HasDirtyDescendants)
|
||||
}
|
||||
|
||||
fn set_has_dirty_descendants(self, state: bool) {
|
||||
self.set_flag(HasDirtyDescendants, state)
|
||||
}
|
||||
|
||||
fn dirty(self) {
|
||||
// 1. Dirty descendants.
|
||||
fn dirty_subtree(node: JSRef<Node>) {
|
||||
node.set_is_dirty(true);
|
||||
|
||||
let mut has_dirty_descendants = false;
|
||||
|
||||
for kid in node.children() {
|
||||
dirty_subtree(kid);
|
||||
has_dirty_descendants = true;
|
||||
}
|
||||
|
||||
if has_dirty_descendants {
|
||||
node.set_has_dirty_descendants(true);
|
||||
}
|
||||
}
|
||||
dirty_subtree(self);
|
||||
|
||||
// 2. Dirty siblings.
|
||||
//
|
||||
// TODO(cgaebel): This is a very conservative way to account for sibling
|
||||
// selectors. Maybe we can do something smarter in the future.
|
||||
let parent =
|
||||
match self.parent_node() {
|
||||
None => return,
|
||||
Some(parent) => parent,
|
||||
};
|
||||
|
||||
for sibling in parent.root().children() {
|
||||
sibling.set_is_dirty(true);
|
||||
}
|
||||
|
||||
// 3. Dirty ancestors.
|
||||
for ancestor in self.ancestors() {
|
||||
if ancestor.get_has_dirty_descendants() { break }
|
||||
ancestor.set_has_dirty_descendants(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -734,6 +814,7 @@ impl<'a> NodeHelpers<'a> for JSRef<'a, Node> {
|
|||
incompleteValue: false, //FIXME: reflect truncation
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// If the given untrusted node address represents a valid DOM node in the given runtime,
|
||||
|
@ -764,6 +845,8 @@ pub trait LayoutNodeHelpers {
|
|||
unsafe fn owner_doc_for_layout(&self) -> JS<Document>;
|
||||
|
||||
unsafe fn is_element_for_layout(&self) -> bool;
|
||||
unsafe fn get_flag(self, flag: NodeFlags) -> bool;
|
||||
unsafe fn set_flag(self, flag: NodeFlags, value: bool);
|
||||
}
|
||||
|
||||
impl LayoutNodeHelpers for JS<Node> {
|
||||
|
@ -806,6 +889,25 @@ impl LayoutNodeHelpers for JS<Node> {
|
|||
unsafe fn owner_doc_for_layout(&self) -> JS<Document> {
|
||||
(*self.unsafe_get()).owner_doc.get_inner().unwrap()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn get_flag(self, flag: NodeFlags) -> bool {
|
||||
(*self.unsafe_get()).flags.get().contains(flag)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn set_flag(self, flag: NodeFlags, value: bool) {
|
||||
let this = self.unsafe_get();
|
||||
let mut flags = (*this).flags.get();
|
||||
|
||||
if value {
|
||||
flags.insert(flag);
|
||||
} else {
|
||||
flags.remove(flag);
|
||||
}
|
||||
|
||||
(*this).flags.set(flags);
|
||||
}
|
||||
}
|
||||
|
||||
pub trait RawLayoutNodeHelpers {
|
||||
|
@ -1034,8 +1136,7 @@ impl Node {
|
|||
prev_sibling: Default::default(),
|
||||
owner_doc: MutNullableJS::new(doc),
|
||||
child_list: Default::default(),
|
||||
|
||||
flags: RefCell::new(NodeFlags::new(type_id)),
|
||||
flags: Cell::new(NodeFlags::new(type_id)),
|
||||
|
||||
layout_data: LayoutDataRef::new(),
|
||||
|
||||
|
@ -1236,11 +1337,13 @@ impl Node {
|
|||
parent.add_child(*node, child);
|
||||
let is_in_doc = parent.is_in_doc();
|
||||
for kid in node.traverse_preorder() {
|
||||
let mut flags = kid.flags.get();
|
||||
if is_in_doc {
|
||||
kid.flags.borrow_mut().insert(IsInDoc);
|
||||
flags.insert(IsInDoc);
|
||||
} else {
|
||||
kid.flags.borrow_mut().remove(IsInDoc);
|
||||
flags.remove(IsInDoc);
|
||||
}
|
||||
kid.flags.set(flags);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1326,7 +1429,7 @@ impl Node {
|
|||
// Step 8.
|
||||
parent.remove_child(node);
|
||||
|
||||
node.flags.borrow_mut().remove(IsInDoc);
|
||||
node.set_flag(IsInDoc, false);
|
||||
|
||||
// Step 9.
|
||||
match suppress_observers {
|
||||
|
@ -1660,7 +1763,7 @@ impl<'a> NodeMethods for JSRef<'a, Node> {
|
|||
|
||||
// Notify the document that the content of this node is different
|
||||
let document = self.owner_doc().root();
|
||||
document.content_changed();
|
||||
document.content_changed(self);
|
||||
}
|
||||
DoctypeNodeTypeId |
|
||||
DocumentNodeTypeId => {}
|
||||
|
@ -2120,6 +2223,12 @@ impl<'a> style::TNode<'a, JSRef<'a, Element>> for JSRef<'a, Node> {
|
|||
assert!(elem.is_some());
|
||||
elem.unwrap().html_element_in_html_document()
|
||||
}
|
||||
|
||||
fn is_dirty(self) -> bool { self.get_is_dirty() }
|
||||
unsafe fn set_dirty(self, value: bool) { self.set_is_dirty(value) }
|
||||
|
||||
fn has_dirty_descendants(self) -> bool { self.get_has_dirty_descendants() }
|
||||
unsafe fn set_dirty_descendants(self, value: bool) { self.set_has_dirty_descendants(value) }
|
||||
}
|
||||
|
||||
pub trait DisabledStateHelpers {
|
||||
|
|
|
@ -18,7 +18,7 @@ use dom::location::Location;
|
|||
use dom::navigator::Navigator;
|
||||
use dom::performance::Performance;
|
||||
use dom::screen::Screen;
|
||||
use layout_interface::{ReflowGoal, DocumentDamageLevel};
|
||||
use layout_interface::{ReflowGoal, ReflowForDisplay};
|
||||
use page::Page;
|
||||
use script_task::{ExitWindowMsg, FireTimerMsg, ScriptChan, TriggerLoadMsg, TriggerFragmentMsg};
|
||||
use script_traits::ScriptControlChan;
|
||||
|
@ -366,7 +366,7 @@ impl Reflectable for Window {
|
|||
}
|
||||
|
||||
pub trait WindowHelpers {
|
||||
fn damage_and_reflow(self, damage: DocumentDamageLevel);
|
||||
fn reflow(self);
|
||||
fn flush_layout(self, goal: ReflowGoal);
|
||||
fn wait_until_safe_to_modify_dom(self);
|
||||
fn init_browser_context(self, doc: JSRef<Document>);
|
||||
|
@ -399,9 +399,12 @@ impl<'a> WindowHelpers for JSRef<'a, Window> {
|
|||
})
|
||||
}
|
||||
|
||||
fn damage_and_reflow(self, damage: DocumentDamageLevel) {
|
||||
self.page().damage(damage);
|
||||
self.page().avoided_reflows.set(self.page().avoided_reflows.get() + 1);
|
||||
fn reflow(self) {
|
||||
self.page().damage();
|
||||
// FIXME This should probably be ReflowForQuery, not Display. All queries currently
|
||||
// currently rely on the display list, which means we can't destroy it by
|
||||
// doing a query reflow.
|
||||
self.page().reflow(ReflowForDisplay, self.control_chan.clone(), &*self.compositor);
|
||||
}
|
||||
|
||||
fn flush_layout(self, goal: ReflowGoal) {
|
||||
|
|
|
@ -14,11 +14,10 @@ use geom::point::Point2D;
|
|||
use geom::rect::Rect;
|
||||
use js::jsapi::JSTracer;
|
||||
use libc::c_void;
|
||||
use script_traits::{ScriptControlChan, OpaqueScriptLayoutChannel};
|
||||
use script_traits::{ScriptControlChan, OpaqueScriptLayoutChannel, UntrustedNodeAddress};
|
||||
use servo_msg::constellation_msg::WindowSizeData;
|
||||
use servo_util::geometry::Au;
|
||||
use std::any::{Any, AnyRefExt};
|
||||
use std::cmp;
|
||||
use std::comm::{channel, Receiver, Sender};
|
||||
use std::owned::BoxAny;
|
||||
use style::Stylesheet;
|
||||
|
@ -85,47 +84,13 @@ impl JSTraceable for TrustedNodeAddress {
|
|||
}
|
||||
}
|
||||
|
||||
/// The address of a node. Layout sends these back. They must be validated via
|
||||
/// `from_untrusted_node_address` before they can be used, because we do not trust layout.
|
||||
pub type UntrustedNodeAddress = *const c_void;
|
||||
|
||||
pub struct ContentBoxResponse(pub Rect<Au>);
|
||||
pub struct ContentBoxesResponse(pub Vec<Rect<Au>>);
|
||||
pub struct HitTestResponse(pub UntrustedNodeAddress);
|
||||
pub struct MouseOverResponse(pub Vec<UntrustedNodeAddress>);
|
||||
|
||||
/// Determines which part of the
|
||||
#[deriving(PartialEq, PartialOrd, Eq, Ord)]
|
||||
#[jstraceable]
|
||||
pub enum DocumentDamageLevel {
|
||||
/// Reflow, but do not perform CSS selector matching.
|
||||
ReflowDocumentDamage,
|
||||
/// Perform CSS selector matching and reflow.
|
||||
MatchSelectorsDocumentDamage,
|
||||
/// Content changed; set full style damage and do the above.
|
||||
ContentChangedDocumentDamage,
|
||||
}
|
||||
|
||||
impl DocumentDamageLevel {
|
||||
/// Sets this damage to the maximum of this damage and the given damage.
|
||||
pub fn add(&mut self, new_damage: DocumentDamageLevel) {
|
||||
*self = cmp::max(*self, new_damage);
|
||||
}
|
||||
}
|
||||
|
||||
/// What parts of the document have changed, as far as the script task can tell.
|
||||
///
|
||||
/// Note that this is fairly coarse-grained and is separate from layout's notion of the document
|
||||
#[jstraceable]
|
||||
pub struct DocumentDamage {
|
||||
/// The topmost node in the tree that has changed.
|
||||
pub root: TrustedNodeAddress,
|
||||
/// The amount of damage that occurred.
|
||||
pub level: DocumentDamageLevel,
|
||||
}
|
||||
|
||||
/// Why we're doing reflow.
|
||||
#[deriving(PartialEq)]
|
||||
#[deriving(PartialEq, Show)]
|
||||
pub enum ReflowGoal {
|
||||
/// We're reflowing in order to send a display list to the screen.
|
||||
ReflowForDisplay,
|
||||
|
@ -137,8 +102,6 @@ pub enum ReflowGoal {
|
|||
pub struct Reflow {
|
||||
/// The document node.
|
||||
pub document_root: TrustedNodeAddress,
|
||||
/// The style changes that need to be done.
|
||||
pub damage: DocumentDamage,
|
||||
/// The goal of reflow: either to render to the screen or to flush layout info for script.
|
||||
pub goal: ReflowGoal,
|
||||
/// The URL of the page.
|
||||
|
@ -190,21 +153,3 @@ impl ScriptLayoutChan for OpaqueScriptLayoutChannel {
|
|||
*receiver.downcast::<Receiver<Msg>>().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_damage() {
|
||||
fn assert_add(mut a: DocumentDamageLevel, b: DocumentDamageLevel,
|
||||
result: DocumentDamageLevel) {
|
||||
a.add(b);
|
||||
assert!(a == result);
|
||||
}
|
||||
|
||||
assert_add(ReflowDocumentDamage, ReflowDocumentDamage, ReflowDocumentDamage);
|
||||
assert_add(ContentChangedDocumentDamage, ContentChangedDocumentDamage, ContentChangedDocumentDamage);
|
||||
assert_add(ReflowDocumentDamage, MatchSelectorsDocumentDamage, MatchSelectorsDocumentDamage);
|
||||
assert_add(MatchSelectorsDocumentDamage, ReflowDocumentDamage, MatchSelectorsDocumentDamage);
|
||||
assert_add(ReflowDocumentDamage, ContentChangedDocumentDamage, ContentChangedDocumentDamage);
|
||||
assert_add(ContentChangedDocumentDamage, ReflowDocumentDamage, ContentChangedDocumentDamage);
|
||||
assert_add(MatchSelectorsDocumentDamage, ContentChangedDocumentDamage, ContentChangedDocumentDamage);
|
||||
assert_add(ContentChangedDocumentDamage, MatchSelectorsDocumentDamage, ContentChangedDocumentDamage);
|
||||
}
|
||||
|
|
|
@ -10,12 +10,11 @@ use dom::document::{Document, DocumentHelpers};
|
|||
use dom::element::Element;
|
||||
use dom::node::{Node, NodeHelpers};
|
||||
use dom::window::Window;
|
||||
use layout_interface::{DocumentDamage, ReflowForDisplay};
|
||||
use layout_interface::{DocumentDamageLevel, HitTestResponse, MouseOverResponse};
|
||||
use layout_interface::{ReflowForDisplay};
|
||||
use layout_interface::{HitTestResponse, MouseOverResponse};
|
||||
use layout_interface::{GetRPCMsg, LayoutChan, LayoutRPC};
|
||||
use layout_interface::{Reflow, ReflowGoal, ReflowMsg};
|
||||
use layout_interface::UntrustedNodeAddress;
|
||||
use script_traits::ScriptControlChan;
|
||||
use script_traits::{UntrustedNodeAddress, ScriptControlChan};
|
||||
|
||||
use geom::point::Point2D;
|
||||
use js::rust::Cx;
|
||||
|
@ -25,6 +24,7 @@ use servo_msg::constellation_msg::{ConstellationChan, WindowSizeData};
|
|||
use servo_msg::constellation_msg::{PipelineId, SubpageId};
|
||||
use servo_net::resource_task::ResourceTask;
|
||||
use servo_util::str::DOMString;
|
||||
use servo_util::smallvec::{SmallVec1, SmallVec};
|
||||
use std::cell::{Cell, RefCell, Ref, RefMut};
|
||||
use std::comm::{channel, Receiver, Empty, Disconnected};
|
||||
use std::mem::replace;
|
||||
|
@ -55,9 +55,6 @@ pub struct Page {
|
|||
/// The port that we will use to join layout. If this is `None`, then layout is not running.
|
||||
pub layout_join_port: RefCell<Option<Receiver<()>>>,
|
||||
|
||||
/// What parts of the document are dirty, if any.
|
||||
damage: RefCell<Option<DocumentDamage>>,
|
||||
|
||||
/// The current size of the window, in pixels.
|
||||
pub window_size: Cell<WindowSizeData>,
|
||||
|
||||
|
@ -74,6 +71,9 @@ pub struct Page {
|
|||
/// Pending resize event, if any.
|
||||
pub resize_event: Cell<Option<WindowSizeData>>,
|
||||
|
||||
/// Any nodes that need to be dirtied before the next reflow.
|
||||
pub pending_dirty_nodes: RefCell<SmallVec1<UntrustedNodeAddress>>,
|
||||
|
||||
/// Pending scroll to fragment event, if any
|
||||
pub fragment_name: RefCell<Option<String>>,
|
||||
|
||||
|
@ -86,6 +86,9 @@ pub struct Page {
|
|||
// Child Pages.
|
||||
pub children: RefCell<Vec<Rc<Page>>>,
|
||||
|
||||
/// Whether layout needs to be run at all.
|
||||
pub damaged: Cell<bool>,
|
||||
|
||||
/// Number of pending reflows that were sent while layout was active.
|
||||
pub pending_reflows: Cell<int>,
|
||||
|
||||
|
@ -143,25 +146,25 @@ impl Page {
|
|||
layout_chan: layout_chan,
|
||||
layout_rpc: layout_rpc,
|
||||
layout_join_port: RefCell::new(None),
|
||||
damage: RefCell::new(None),
|
||||
window_size: Cell::new(window_size),
|
||||
js_info: RefCell::new(Some(js_info)),
|
||||
url: RefCell::new(None),
|
||||
next_subpage_id: Cell::new(SubpageId(0)),
|
||||
resize_event: Cell::new(None),
|
||||
pending_dirty_nodes: RefCell::new(SmallVec1::new()),
|
||||
fragment_name: RefCell::new(None),
|
||||
last_reflow_id: Cell::new(0),
|
||||
resource_task: resource_task,
|
||||
constellation_chan: constellation_chan,
|
||||
children: RefCell::new(vec!()),
|
||||
damaged: Cell::new(false),
|
||||
pending_reflows: Cell::new(0),
|
||||
avoided_reflows: Cell::new(0),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn flush_layout(&self, goal: ReflowGoal) {
|
||||
let damaged = self.damage.borrow().is_some();
|
||||
if damaged {
|
||||
if self.damaged.get() {
|
||||
let frame = self.frame();
|
||||
let window = frame.as_ref().unwrap().window.root();
|
||||
self.reflow(goal, window.control_chan.clone(), &*window.compositor);
|
||||
|
@ -255,35 +258,6 @@ impl Page {
|
|||
subpage_id
|
||||
}
|
||||
|
||||
/// Adds the given damage.
|
||||
pub fn damage(&self, level: DocumentDamageLevel) {
|
||||
let root = match *self.frame() {
|
||||
None => return,
|
||||
Some(ref frame) => frame.document.root().GetDocumentElement()
|
||||
};
|
||||
match root.root() {
|
||||
None => {},
|
||||
Some(root) => {
|
||||
let root: JSRef<Node> = NodeCast::from_ref(*root);
|
||||
let mut damage = *self.damage.borrow_mut();
|
||||
match damage {
|
||||
None => {}
|
||||
Some(ref mut damage) => {
|
||||
// FIXME(pcwalton): This is wrong. We should trace up to the nearest ancestor.
|
||||
damage.root = root.to_trusted_node_address();
|
||||
damage.level.add(level);
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
*self.damage.borrow_mut() = Some(DocumentDamage {
|
||||
root: root.to_trusted_node_address(),
|
||||
level: level,
|
||||
})
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn get_url(&self) -> Url {
|
||||
self.url().as_ref().unwrap().ref0().clone()
|
||||
}
|
||||
|
@ -360,8 +334,9 @@ impl Page {
|
|||
last_reflow_id.set(last_reflow_id.get() + 1);
|
||||
|
||||
let root: JSRef<Node> = NodeCast::from_ref(*root);
|
||||
let mut damage = self.damage.borrow_mut();
|
||||
|
||||
let window_size = self.window_size.get();
|
||||
self.damaged.set(false);
|
||||
|
||||
// Send new document and relevant styles to layout.
|
||||
let reflow = box Reflow {
|
||||
|
@ -372,7 +347,6 @@ impl Page {
|
|||
window_size: window_size,
|
||||
script_chan: script_chan,
|
||||
script_join_chan: join_chan,
|
||||
damage: replace(&mut *damage, None).unwrap(),
|
||||
id: last_reflow_id.get(),
|
||||
};
|
||||
|
||||
|
@ -384,6 +358,10 @@ impl Page {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn damage(&self) {
|
||||
self.damaged.set(true);
|
||||
}
|
||||
|
||||
/// Attempt to find a named element in this page's document.
|
||||
pub fn find_fragment_node(&self, fragid: DOMString) -> Option<Temporary<Element>> {
|
||||
let document = self.frame().as_ref().unwrap().document.root();
|
||||
|
|
|
@ -29,9 +29,7 @@ use dom::worker::{Worker, TrustedWorkerAddress};
|
|||
use dom::xmlhttprequest::{TrustedXHRAddress, XMLHttpRequest, XHRProgress};
|
||||
use html::hubbub_html_parser::{InputString, InputUrl, HtmlParserResult, HtmlDiscoveredScript};
|
||||
use html::hubbub_html_parser;
|
||||
use layout_interface::{ScriptLayoutChan, LayoutChan, MatchSelectorsDocumentDamage};
|
||||
use layout_interface::{ReflowDocumentDamage, ReflowForDisplay};
|
||||
use layout_interface::ContentChangedDocumentDamage;
|
||||
use layout_interface::{ScriptLayoutChan, LayoutChan, ReflowForDisplay};
|
||||
use layout_interface;
|
||||
use page::{Page, IterablePage, Frame};
|
||||
|
||||
|
@ -52,6 +50,7 @@ use servo_msg::constellation_msg;
|
|||
use servo_net::image_cache_task::ImageCacheTask;
|
||||
use servo_net::resource_task::ResourceTask;
|
||||
use servo_util::geometry::to_frac_px;
|
||||
use servo_util::smallvec::{SmallVec1, SmallVec};
|
||||
use servo_util::task::spawn_named_with_send_on_failure;
|
||||
|
||||
use geom::point::Point2D;
|
||||
|
@ -66,6 +65,7 @@ use url::Url;
|
|||
use libc::size_t;
|
||||
use std::any::{Any, AnyRefExt};
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashSet;
|
||||
use std::comm::{channel, Sender, Receiver, Select};
|
||||
use std::mem::replace;
|
||||
use std::rc::Rc;
|
||||
|
@ -445,7 +445,9 @@ impl ScriptTask {
|
|||
}
|
||||
};
|
||||
|
||||
// Squash any pending resize events in the queue.
|
||||
let mut needs_reflow = HashSet::new();
|
||||
|
||||
// Squash any pending resize and reflow events in the queue.
|
||||
loop {
|
||||
match event {
|
||||
// This has to be handled before the ResizeMsg below,
|
||||
|
@ -459,6 +461,13 @@ impl ScriptTask {
|
|||
let page = page.find(id).expect("resize sent to nonexistent pipeline");
|
||||
page.resize_event.set(Some(size));
|
||||
}
|
||||
FromConstellation(SendEventMsg(id, ReflowEvent(node_addresses))) => {
|
||||
let mut page = self.page.borrow_mut();
|
||||
let inner_page = page.find(id).expect("Reflow sent to nonexistent pipeline");
|
||||
let mut pending = inner_page.pending_dirty_nodes.borrow_mut();
|
||||
pending.push_all_move(node_addresses);
|
||||
needs_reflow.insert(id);
|
||||
}
|
||||
_ => {
|
||||
sequential.push(event);
|
||||
}
|
||||
|
@ -507,6 +516,11 @@ impl ScriptTask {
|
|||
}
|
||||
}
|
||||
|
||||
// Now process any pending reflows.
|
||||
for id in needs_reflow.into_iter() {
|
||||
self.handle_event(id, ReflowEvent(SmallVec1::new()));
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
|
@ -632,8 +646,7 @@ impl ScriptTask {
|
|||
|
||||
if page.pending_reflows.get() > 0 {
|
||||
page.pending_reflows.set(0);
|
||||
page.damage(MatchSelectorsDocumentDamage);
|
||||
page.reflow(ReflowForDisplay, self.control_chan.clone(), &*self.compositor);
|
||||
self.force_reflow(&*page);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -711,8 +724,7 @@ impl ScriptTask {
|
|||
Some((ref loaded, needs_reflow)) if *loaded == url => {
|
||||
*page.mut_url() = Some((loaded.clone(), false));
|
||||
if needs_reflow {
|
||||
page.damage(ContentChangedDocumentDamage);
|
||||
page.reflow(ReflowForDisplay, self.control_chan.clone(), &*self.compositor);
|
||||
self.force_reflow(&*page);
|
||||
}
|
||||
return;
|
||||
},
|
||||
|
@ -796,7 +808,11 @@ impl ScriptTask {
|
|||
|
||||
// Kick off the initial reflow of the page.
|
||||
debug!("kicking off initial reflow of {}", url);
|
||||
document.content_changed();
|
||||
{
|
||||
let document_js_ref = (&*document).clone();
|
||||
let document_as_node = NodeCast::from_ref(document_js_ref);
|
||||
document.content_changed(document_as_node);
|
||||
}
|
||||
window.flush_layout(ReflowForDisplay);
|
||||
|
||||
{
|
||||
|
@ -856,6 +872,21 @@ impl ScriptTask {
|
|||
self.compositor.scroll_fragment_point(pipeline_id, LayerId::null(), point);
|
||||
}
|
||||
|
||||
fn force_reflow(&self, page: &Page) {
|
||||
{
|
||||
let mut pending = page.pending_dirty_nodes.borrow_mut();
|
||||
let js_runtime = self.js_runtime.deref().ptr;
|
||||
|
||||
for untrusted_node in pending.into_iter() {
|
||||
let node = node::from_untrusted_node_address(js_runtime, untrusted_node).root();
|
||||
node.dirty();
|
||||
}
|
||||
}
|
||||
|
||||
page.damage();
|
||||
page.reflow(ReflowForDisplay, self.control_chan.clone(), &*self.compositor);
|
||||
}
|
||||
|
||||
/// This is the main entry point for receiving and dispatching DOM events.
|
||||
///
|
||||
/// TODO: Actually perform DOM event dispatch.
|
||||
|
@ -870,8 +901,7 @@ impl ScriptTask {
|
|||
|
||||
let frame = page.frame();
|
||||
if frame.is_some() {
|
||||
page.damage(ReflowDocumentDamage);
|
||||
page.reflow(ReflowForDisplay, self.control_chan.clone(), &*self.compositor)
|
||||
self.force_reflow(&*page);
|
||||
}
|
||||
|
||||
let fragment_node =
|
||||
|
@ -906,8 +936,9 @@ impl ScriptTask {
|
|||
}
|
||||
|
||||
// FIXME(pcwalton): This reflows the entire document and is not incremental-y.
|
||||
ReflowEvent => {
|
||||
ReflowEvent(to_dirty) => {
|
||||
debug!("script got reflow event");
|
||||
assert_eq!(to_dirty.len(), 0);
|
||||
let page = get_page(&*self.page.borrow(), pipeline_id);
|
||||
let frame = page.frame();
|
||||
if frame.is_some() {
|
||||
|
@ -915,8 +946,7 @@ impl ScriptTask {
|
|||
if in_layout {
|
||||
page.pending_reflows.set(page.pending_reflows.get() + 1);
|
||||
} else {
|
||||
page.damage(MatchSelectorsDocumentDamage);
|
||||
page.reflow(ReflowForDisplay, self.control_chan.clone(), &*self.compositor)
|
||||
self.force_reflow(&*page);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1021,8 +1051,7 @@ impl ScriptTask {
|
|||
|
||||
if target_compare {
|
||||
if mouse_over_targets.is_some() {
|
||||
page.damage(MatchSelectorsDocumentDamage);
|
||||
page.reflow(ReflowForDisplay, self.control_chan.clone(), &*self.compositor);
|
||||
self.force_reflow(&*page);
|
||||
}
|
||||
*mouse_over_targets = Some(target_list);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue