diff --git a/servo/Cargo.lock b/servo/Cargo.lock index 6e05b1e2a253..da791ade38c3 100644 --- a/servo/Cargo.lock +++ b/servo/Cargo.lock @@ -416,6 +416,7 @@ dependencies = [ "msg 0.0.1", "net 0.0.1", "url 0.1.0 (git+https://github.com/servo/rust-url#29f70a47230c2aa736e263977247c786e0b2c243)", + "util 0.0.1", ] [[package]] diff --git a/servo/components/gfx/display_list/mod.rs b/servo/components/gfx/display_list/mod.rs index 00a79c9d9796..0614fc24a76f 100644 --- a/servo/components/gfx/display_list/mod.rs +++ b/servo/components/gfx/display_list/mod.rs @@ -293,7 +293,7 @@ pub enum BackgroundAndBorderLevel { } /// A list of rendering operations to be performed. -#[deriving(Clone)] +#[deriving(Clone, Show)] pub struct DisplayList { pub list: DList, } @@ -357,8 +357,9 @@ impl DisplayList { } /// Returns true if this list is empty and false otherwise. + #[inline] fn is_empty(&self) -> bool { - self.list.len() == 0 + self.list.is_empty() } /// Flattens a display list into a display list with a single stacking level according to the diff --git a/servo/components/layout/construct.rs b/servo/components/layout/construct.rs index 826a59a7eadd..c45c26d781c3 100644 --- a/servo/components/layout/construct.rs +++ b/servo/components/layout/construct.rs @@ -607,8 +607,7 @@ impl<'a> FlowConstructor<'a> { abs_descendants.push_descendants(kid_abs_descendants); } ConstructionItemConstructionResult(WhitespaceConstructionItem(whitespace_node, - whitespace_style)) - => { + whitespace_style)) => { // Instantiate the whitespace fragment. let fragment_info = UnscannedTextFragment(UnscannedTextFragmentInfo::from_text(" ".to_string())); let mut fragment = Fragment::from_opaque_node_and_style(whitespace_node, @@ -1196,12 +1195,13 @@ impl FlowConstructionUtils for FlowRef { /// /// This must not be public because only the layout constructor can do this. fn add_new_child(&mut self, mut new_child: FlowRef) { + let base = flow::mut_base(self.get_mut()); + { let kid_base = flow::mut_base(new_child.get_mut()); kid_base.parallel.parent = parallel::mut_owned_flow_to_unsafe_flow(self); } - let base = flow::mut_base(self.get_mut()); base.children.push_back(new_child); let _ = base.parallel.children_count.fetch_add(1, Relaxed); let _ = base.parallel.children_and_absolute_descendant_count.fetch_add(1, Relaxed); diff --git a/servo/components/layout/context.rs b/servo/components/layout/context.rs index 8792e99d2a6d..6b0a61247d82 100644 --- a/servo/components/layout/context.rs +++ b/servo/components/layout/context.rs @@ -11,6 +11,7 @@ use gfx::display_list::OpaqueNode; use gfx::font_context::FontContext; use gfx::font_cache_task::FontCacheTask; use script::layout_interface::LayoutChan; +use script_traits::UntrustedNodeAddress; use servo_msg::constellation_msg::ConstellationChan; use servo_net::local_image_cache::LocalImageCache; use servo_util::geometry::Au; @@ -49,7 +50,7 @@ fn create_or_get_local_context(shared_layout_context: &SharedLayoutContext) -> * pub struct SharedLayoutContext { /// The local image cache. - pub image_cache: Arc>, + pub image_cache: Arc>>, /// The current screen size. pub screen_size: Size2D, diff --git a/servo/components/layout/css/matching.rs b/servo/components/layout/css/matching.rs index 4ff49518edd4..d618851a175b 100644 --- a/servo/components/layout/css/matching.rs +++ b/servo/components/layout/css/matching.rs @@ -469,7 +469,9 @@ impl<'ln> MatchMethods for LayoutNode<'ln> { Some(shared_style) => { // Yay, cache hit. Share the style. let mut layout_data_ref = self.mutate_layout_data(); - layout_data_ref.as_mut().unwrap().shared_data.style = Some(shared_style); + let shared_data = &mut layout_data_ref.as_mut().unwrap().shared_data; + let style = &mut shared_data.style; + *style = Some(shared_style); return StyleWasShared(i) } None => {} @@ -632,24 +634,27 @@ impl<'ln> MatchMethods for LayoutNode<'ln> { layout_data.shared_data.style = Some(cloned_parent_style); } _ => { - self.cascade_node_pseudo_element(parent_style, - applicable_declarations.normal.as_slice(), - &mut layout_data.shared_data.style, - applicable_declarations_cache, - applicable_declarations.normal_shareable); + self.cascade_node_pseudo_element( + parent_style, + applicable_declarations.normal.as_slice(), + &mut layout_data.shared_data.style, + applicable_declarations_cache, + applicable_declarations.normal_shareable); if applicable_declarations.before.len() > 0 { - self.cascade_node_pseudo_element(Some(layout_data.shared_data.style.as_ref().unwrap()), - applicable_declarations.before.as_slice(), - &mut layout_data.data.before_style, - applicable_declarations_cache, - false); + self.cascade_node_pseudo_element( + Some(layout_data.shared_data.style.as_ref().unwrap()), + applicable_declarations.before.as_slice(), + &mut layout_data.data.before_style, + applicable_declarations_cache, + false); } if applicable_declarations.after.len() > 0 { - self.cascade_node_pseudo_element(Some(layout_data.shared_data.style.as_ref().unwrap()), - applicable_declarations.after.as_slice(), - &mut layout_data.data.after_style, - applicable_declarations_cache, - false); + self.cascade_node_pseudo_element( + Some(layout_data.shared_data.style.as_ref().unwrap()), + applicable_declarations.after.as_slice(), + &mut layout_data.data.after_style, + applicable_declarations_cache, + false); } } } diff --git a/servo/components/layout/css/node_style.rs b/servo/components/layout/css/node_style.rs index e201b0050aa9..8512429442a2 100644 --- a/servo/components/layout/css/node_style.rs +++ b/servo/components/layout/css/node_style.rs @@ -14,7 +14,8 @@ use sync::Arc; /// Node mixin providing `style` method that returns a `NodeStyle` pub trait StyledNode { fn style<'a>(&'a self) -> &'a Arc; - fn restyle_damage(&self) -> RestyleDamage; + fn restyle_damage(self) -> RestyleDamage; + fn set_restyle_damage(self, damage: RestyleDamage); } impl<'ln> StyledNode for ThreadSafeLayoutNode<'ln> { @@ -23,8 +24,14 @@ impl<'ln> StyledNode for ThreadSafeLayoutNode<'ln> { self.get_css_select_results() } - fn restyle_damage(&self) -> RestyleDamage { + fn restyle_damage(self) -> RestyleDamage { self.get_restyle_damage() } -} + fn set_restyle_damage(self, damage: RestyleDamage) { + fn doit(n: N, damage: RestyleDamage) { + n.set_restyle_damage(damage); + } + doit(self, damage); + } +} diff --git a/servo/components/layout/css/node_util.rs b/servo/components/layout/css/node_util.rs index b16e1af589cb..17d181b9937c 100644 --- a/servo/components/layout/css/node_util.rs +++ b/servo/components/layout/css/node_util.rs @@ -4,7 +4,7 @@ use incremental::RestyleDamage; use util::LayoutDataAccess; -use wrapper::{TLayoutNode, ThreadSafeLayoutNode}; +use wrapper::ThreadSafeLayoutNode; use wrapper::{After, Before, Normal}; use std::mem; use style::ComputedValues; @@ -14,8 +14,8 @@ pub trait NodeUtil { fn get_css_select_results<'a>(&'a self) -> &'a Arc; fn have_css_select_results(&self) -> bool; - fn get_restyle_damage(&self) -> RestyleDamage; - fn set_restyle_damage(&self, damage: RestyleDamage); + fn get_restyle_damage(self) -> RestyleDamage; + fn set_restyle_damage(self, damage: RestyleDamage); } impl<'ln> NodeUtil for ThreadSafeLayoutNode<'ln> { @@ -62,28 +62,19 @@ impl<'ln> NodeUtil for ThreadSafeLayoutNode<'ln> { /// Get the description of how to account for recent style changes. /// This is a simple bitfield and fine to copy by value. - fn get_restyle_damage(&self) -> RestyleDamage { - // For DOM elements, if we haven't computed damage yet, assume the worst. - // Other nodes don't have styles. - let default = if self.node_is_element() { - RestyleDamage::all() - } else { - RestyleDamage::empty() - }; - + fn get_restyle_damage(self) -> RestyleDamage { let layout_data_ref = self.borrow_layout_data(); layout_data_ref .as_ref().unwrap() .data .restyle_damage - .unwrap_or(default) } /// Set the restyle damage field. - fn set_restyle_damage(&self, damage: RestyleDamage) { + fn set_restyle_damage(self, damage: RestyleDamage) { let mut layout_data_ref = self.mutate_layout_data(); match &mut *layout_data_ref { - &Some(ref mut layout_data) => layout_data.data.restyle_damage = Some(damage), + &Some(ref mut layout_data) => layout_data.data.restyle_damage = damage, _ => fail!("no layout data for this node"), } } diff --git a/servo/components/layout/floats.rs b/servo/components/layout/floats.rs index 8254053f1d2a..e531fc497985 100644 --- a/servo/components/layout/floats.rs +++ b/servo/components/layout/floats.rs @@ -190,6 +190,10 @@ impl Floats { } } + pub fn len(&self) -> uint { + self.list.list.as_ref().map(|list| list.floats.len()).unwrap_or(0) + } + /// Returns a rectangle that encloses the region from block-start to block-start + block-size, with inline-size small /// enough that it doesn't collide with any floats. max_x is the x-coordinate beyond which /// floats have no effect. (Generally this is the containing block inline-size.) @@ -437,4 +441,3 @@ impl Floats { clearance } } - diff --git a/servo/components/layout/flow.rs b/servo/components/layout/flow.rs index 912d1bd6b1f7..aeda813c1b3d 100644 --- a/servo/components/layout/flow.rs +++ b/servo/components/layout/flow.rs @@ -471,6 +471,13 @@ pub trait PreorderFlowTraversal { /// The operation to perform. Return true to continue or false to stop. fn process(&mut self, flow: &mut Flow) -> bool; + /// Returns true if this node must be processed in-order. If this returns false, + /// we skip the operation for this node, but continue processing the descendants. + /// This is called *after* parent nodes are visited. + fn should_process(&mut self, _flow: &mut Flow) -> bool { + true + } + /// 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. @@ -1031,6 +1038,10 @@ impl<'a> MutableFlowUtils for &'a mut Flow + 'a { return true } + if !traversal.should_process(self) { + return true + } + if !traversal.process(self) { return false } @@ -1240,4 +1251,3 @@ impl ContainingBlockLink { } } } - diff --git a/servo/components/layout/fragment.rs b/servo/components/layout/fragment.rs index aa7e586542f2..835d5037b43d 100644 --- a/servo/components/layout/fragment.rs +++ b/servo/components/layout/fragment.rs @@ -35,13 +35,14 @@ use gfx::display_list::{Upright, SidewaysLeft, SidewaysRight}; use gfx::font::FontStyle; use gfx::text::glyph::CharIndex; use gfx::text::text_run::TextRun; +use script_traits::UntrustedNodeAddress; use serialize::{Encodable, Encoder}; use servo_msg::constellation_msg::{ConstellationChan, FrameRectMsg, PipelineId, SubpageId}; use servo_net::image::holder::ImageHolder; use servo_net::local_image_cache::LocalImageCache; use servo_util::geometry::Au; use servo_util::geometry; -use servo_util::logical_geometry::{LogicalRect, LogicalSize, LogicalMargin}; +use servo_util::logical_geometry::{LogicalRect, LogicalSize, LogicalMargin, WritingMode}; use servo_util::range::*; use servo_util::smallvec::SmallVec; use servo_util::str::is_whitespace; @@ -202,7 +203,8 @@ impl InputFragmentInfo { #[deriving(Clone)] pub struct ImageFragmentInfo { /// The image held within this fragment. - pub image: ImageHolder, + pub image: ImageHolder, + pub for_node: UntrustedNodeAddress, pub computed_inline_size: Option, pub computed_block_size: Option, pub dom_inline_size: Option, @@ -217,7 +219,7 @@ impl ImageFragmentInfo { /// me. pub fn new(node: &ThreadSafeLayoutNode, image_url: Url, - local_image_cache: Arc>) + local_image_cache: Arc>>) -> ImageFragmentInfo { fn convert_length(node: &ThreadSafeLayoutNode, name: &str) -> Option { let element = node.as_element(); @@ -230,8 +232,13 @@ impl ImageFragmentInfo { let is_vertical = node.style().writing_mode.is_vertical(); let dom_width = convert_length(node, "width"); let dom_height = convert_length(node, "height"); + + let opaque_node: OpaqueNode = OpaqueNodeMethods::from_thread_safe_layout_node(node); + let untrusted_node: UntrustedNodeAddress = opaque_node.to_untrusted_node_address(); + ImageFragmentInfo { image: ImageHolder::new(image_url, local_image_cache), + for_node: untrusted_node, computed_inline_size: None, computed_block_size: None, dom_inline_size: if is_vertical { dom_height } else { dom_width }, @@ -252,13 +259,13 @@ impl ImageFragmentInfo { /// Returns the original inline-size of the image. pub fn image_inline_size(&mut self) -> Au { - let size = self.image.get_size().unwrap_or(Size2D::zero()); + let size = self.image.get_size(self.for_node).unwrap_or(Size2D::zero()); Au::from_px(if self.writing_mode_is_vertical { size.height } else { size.width }) } /// Returns the original block-size of the image. pub fn image_block_size(&mut self) -> Au { - let size = self.image.get_size().unwrap_or(Size2D::zero()); + let size = self.image.get_size(self.for_node).unwrap_or(Size2D::zero()); Au::from_px(if self.writing_mode_is_vertical { size.width } else { size.height }) } @@ -805,7 +812,7 @@ impl Fragment { }; let mut holder = ImageHolder::new(image_url.clone(), layout_context.shared.image_cache.clone()); - let image = match holder.get_image() { + let image = match holder.get_image(self.node.to_untrusted_node_address()) { None => { // No image data at all? Do nothing. // @@ -987,7 +994,7 @@ impl Fragment { /// * `layout_context`: The layout context. /// * `dirty`: The dirty rectangle in the coordinate system of the owning flow. /// * `flow_origin`: Position of the origin of the owning flow wrt the display list root flow. - pub fn build_display_list(&self, + pub fn build_display_list(&mut self, display_list: &mut DisplayList, layout_context: &LayoutContext, flow_origin: Point2D, @@ -995,12 +1002,12 @@ impl Fragment { -> ChildDisplayListAccumulator { // FIXME(#2795): Get the real container size let container_size = Size2D::zero(); - let rect_to_absolute = |logical_rect: LogicalRect| { - let physical_rect = logical_rect.to_physical(self.style.writing_mode, container_size); + let rect_to_absolute = |writing_mode: WritingMode, logical_rect: LogicalRect| { + let physical_rect = logical_rect.to_physical(writing_mode, container_size); Rect(physical_rect.origin + flow_origin, physical_rect.size) }; // Fragment position wrt to the owning flow. - let absolute_fragment_bounds = rect_to_absolute(self.border_box); + let absolute_fragment_bounds = rect_to_absolute(self.style.writing_mode, self.border_box); debug!("Fragment::build_display_list at rel={}, abs={}: {}", self.border_box, absolute_fragment_bounds, @@ -1088,7 +1095,7 @@ impl Fragment { } let content_box = self.content_box(); - let absolute_content_box = rect_to_absolute(content_box); + let absolute_content_box = rect_to_absolute(self.style.writing_mode, content_box); // Create special per-fragment-type display items. match self.specific { @@ -1133,7 +1140,7 @@ impl Fragment { accumulator.push(display_list, SolidColorDisplayItemClass( box SolidColorDisplayItem { base: BaseDisplayItem::new( - rect_to_absolute(rect()), + rect_to_absolute(self.style.writing_mode, rect()), self.node, ContentStackingLevel), color: color.to_gfx_color(), } @@ -1182,9 +1189,9 @@ impl Fragment { } ImageFragment(_) => { match self.specific { - ImageFragment(ref image_fragment) => { - let image_ref = &image_fragment.image; - match image_ref.get_image_if_present() { + ImageFragment(ref mut image_fragment) => { + let image_ref = &mut image_fragment.image; + match image_ref.get_image(self.node.to_untrusted_node_address()) { Some(image) => { debug!("(building display list) building image fragment"); @@ -1437,7 +1444,7 @@ impl Fragment { let advance = metrics.advance_width; let should_continue; - if advance <= remaining_inline_size { + if advance <= remaining_inline_size || glyphs.is_whitespace() { should_continue = true; if starts_line && pieces_processed_count == 0 && glyphs.is_whitespace() { @@ -1452,21 +1459,8 @@ impl Fragment { // The advance is more than the remaining inline-size. should_continue = false; let slice_begin = offset + slice_range.begin(); - let slice_end = offset + slice_range.end(); - if glyphs.is_whitespace() { - // If there are still things after the trimmable whitespace, create the - // inline-end chunk. - if slice_end < text_fragment_info.range.end() { - debug!("split_to_inline_size: case=skipping trimmable trailing \ - whitespace, then split remainder"); - let inline_end_range_end = text_fragment_info.range.end() - slice_end; - inline_end_range = Some(Range::new(slice_end, inline_end_range_end)); - } else { - debug!("split_to_inline_size: case=skipping trimmable trailing \ - whitespace"); - } - } else if slice_begin < text_fragment_info.range.end() { + if slice_begin < text_fragment_info.range.end() { // There are still some things inline-start over at the end of the line. Create // the inline-end chunk. let inline_end_range_end = text_fragment_info.range.end() - slice_begin; diff --git a/servo/components/layout/incremental.rs b/servo/components/layout/incremental.rs index d04c068b6aa7..959647df85a4 100644 --- a/servo/components/layout/incremental.rs +++ b/servo/components/layout/incremental.rs @@ -2,6 +2,8 @@ * 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/. */ +use std::fmt; +use std::sync::Arc; use style::ComputedValues; bitflags! { @@ -35,6 +37,32 @@ impl RestyleDamage { } } +impl fmt::Show for RestyleDamage { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::FormatError> { + let mut first_elem = true; + + let to_iter = + [ (Repaint, "Repaint") + , (BubbleISizes, "BubbleISizes") + , (Reflow, "Reflow") + ]; + + for &(damage, damage_str) in to_iter.iter() { + if self.contains(damage) { + if !first_elem { try!(write!(f, " | ")); } + try!(write!(f, "{}", damage_str)); + first_elem = false; + } + } + + if first_elem { + try!(write!(f, "NoDamage")); + } + + Ok(()) + } +} + // NB: We need the braces inside the RHS due to Rust #8012. This particular // version of this macro might be safe anyway, but we want to avoid silent // breakage on modifications. @@ -47,7 +75,13 @@ macro_rules! add_if_not_equal( }) ) -pub fn compute_damage(old: &ComputedValues, new: &ComputedValues) -> RestyleDamage { +pub fn compute_damage(old: &Option>, new: &ComputedValues) -> RestyleDamage { + let old: &ComputedValues = + match old.as_ref() { + None => return Repaint | BubbleISizes | Reflow, + Some(cv) => &**cv, + }; + let mut damage = RestyleDamage::empty(); // This checks every CSS property, as enumerated in diff --git a/servo/components/layout/inline.rs b/servo/components/layout/inline.rs index b9901a3f13b1..f6bcc0a33946 100644 --- a/servo/components/layout/inline.rs +++ b/servo/components/layout/inline.rs @@ -494,7 +494,7 @@ impl LineBreaker { self.push_fragment_to_line(in_fragment); true } else { - debug!("LineBreaker: Found a new-line character, so splitting theline."); + debug!("LineBreaker: Found a new-line character, so splitting the line."); let (inline_start, inline_end, run) = in_fragment.find_split_info_by_new_line() .expect("LineBreaker: This split case makes no sense!"); @@ -621,7 +621,7 @@ impl LineBreaker { true }, Some((None, None)) => { - error!("LineBreaker: This split case makes no sense!"); + debug!("LineBreaker: Nothing to do."); true }, } @@ -1273,4 +1273,3 @@ impl InlineMetrics { } } } - diff --git a/servo/components/layout/layout_task.rs b/servo/components/layout/layout_task.rs index 5e2fb1b44262..b2653496a6ba 100644 --- a/servo/components/layout/layout_task.rs +++ b/servo/components/layout/layout_task.rs @@ -13,7 +13,6 @@ use flow::{Flow, ImmutableFlowUtils, MutableFlowUtils, MutableOwnedFlowUtils}; use flow::{PreorderFlowTraversal, PostorderFlowTraversal}; use flow; use flow_ref::FlowRef; -use incremental::RestyleDamage; use layout_debug; use parallel::UnsafeFlow; use parallel; @@ -38,10 +37,11 @@ use script::dom::element::{HTMLBodyElementTypeId, HTMLHtmlElementTypeId}; use script::layout_interface::{AddStylesheetMsg, LoadStylesheetMsg, ScriptLayoutChan}; use script::layout_interface::{TrustedNodeAddress, ContentBoxesResponse, ExitNowMsg}; use script::layout_interface::{ContentBoxResponse, HitTestResponse, MouseOverResponse}; -use script::layout_interface::{ContentChangedDocumentDamage, LayoutChan, Msg, PrepareToExitMsg}; -use script::layout_interface::{GetRPCMsg, LayoutRPC, ReapLayoutDataMsg, Reflow, UntrustedNodeAddress}; +use script::layout_interface::{LayoutChan, Msg, PrepareToExitMsg}; +use script::layout_interface::{GetRPCMsg, LayoutRPC, ReapLayoutDataMsg, Reflow}; use script::layout_interface::{ReflowForDisplay, ReflowMsg}; -use script_traits::{SendEventMsg, ReflowEvent, ReflowCompleteMsg, OpaqueScriptLayoutChannel, ScriptControlChan}; +use script_traits::{SendEventMsg, ReflowEvent, ReflowCompleteMsg, OpaqueScriptLayoutChannel}; +use script_traits::{ScriptControlChan, UntrustedNodeAddress}; use servo_msg::compositor_msg::Scrollable; use servo_msg::constellation_msg::{ConstellationChan, PipelineId, Failure, FailureMsg}; use servo_net::image_cache_task::{ImageCacheTask, ImageResponseMsg}; @@ -53,7 +53,7 @@ use servo_util::geometry::Au; use servo_util::geometry; use servo_util::logical_geometry::LogicalPoint; use servo_util::opts::Opts; -use servo_util::smallvec::{SmallVec, SmallVec1}; +use servo_util::smallvec::{SmallVec, SmallVec1, VecLike}; use servo_util::task::spawn_named_with_send_on_failure; use servo_util::time::{TimeProfilerChan, profile}; use servo_util::time; @@ -63,7 +63,7 @@ use std::comm::{channel, Sender, Receiver, Select}; use std::mem; use std::ptr; use style; -use style::{AuthorOrigin, Stylesheet, Stylist}; +use style::{TNode, AuthorOrigin, Stylesheet, Stylist}; use style::iter_font_face_rules; use sync::{Arc, Mutex, MutexGuard}; use url::Url; @@ -73,7 +73,7 @@ use url::Url; /// This needs to be protected by a mutex so we can do fast RPCs. pub struct LayoutTaskData { /// The local image cache. - pub local_image_cache: Arc>, + pub local_image_cache: Arc>>, /// The size of the viewport. pub screen_size: Size2D, @@ -92,6 +92,10 @@ pub struct LayoutTaskData { /// Starts at zero, and increased by one every time a layout completes. /// This can be used to easily check for invalid stale data. pub generation: uint, + + /// True if a style sheet was added since the last reflow. Currently, this causes all nodes to + /// be dirtied at the next reflow. + pub stylesheet_dirty: bool, } /// Information needed by the layout task. @@ -142,47 +146,6 @@ pub struct LayoutTask { pub rw_data: Arc>, } -/// The damage computation traversal. -#[deriving(Clone)] -struct ComputeDamageTraversal; - -impl PostorderFlowTraversal for ComputeDamageTraversal { - #[inline] - fn process(&mut self, flow: &mut Flow) -> bool { - let mut damage = flow::base(flow).restyle_damage; - for child in flow::child_iter(flow) { - damage.insert(flow::base(child).restyle_damage.propagate_up()) - } - flow::mut_base(flow).restyle_damage = damage; - true - } -} - -/// Propagates restyle damage up and down the tree as appropriate. -/// -/// FIXME(pcwalton): Merge this with flow tree building and/or other traversals. -struct PropagateDamageTraversal { - all_style_damage: bool, -} - -impl PreorderFlowTraversal for PropagateDamageTraversal { - #[inline] - fn process(&mut self, flow: &mut Flow) -> bool { - if self.all_style_damage { - flow::mut_base(flow).restyle_damage.insert(RestyleDamage::all()) - } - debug!("restyle damage = {:?}", flow::base(flow).restyle_damage); - - let prop = flow::base(flow).restyle_damage.propagate_down(); - if !prop.is_empty() { - for kid_ctx in flow::child_iter(flow) { - flow::mut_base(kid_ctx).restyle_damage.insert(prop) - } - } - true - } -} - /// The flow tree verification traversal. This is only on in debug builds. #[cfg(debug)] struct FlowTreeVerificationTraversal; @@ -290,14 +253,17 @@ struct LayoutImageResponder { script_chan: ScriptControlChan, } -impl ImageResponder for LayoutImageResponder { - fn respond(&self) -> proc(ImageResponseMsg):Send { +impl ImageResponder for LayoutImageResponder { + fn respond(&self) -> proc(ImageResponseMsg, UntrustedNodeAddress):Send { let id = self.id.clone(); let script_chan = self.script_chan.clone(); - let f: proc(ImageResponseMsg):Send = proc(_) { - let ScriptControlChan(chan) = script_chan; - drop(chan.send_opt(SendEventMsg(id.clone(), ReflowEvent))) - }; + let f: proc(ImageResponseMsg, UntrustedNodeAddress):Send = + proc(_, node_address) { + let ScriptControlChan(chan) = script_chan; + let mut nodes = SmallVec1::new(); + nodes.vec_push(node_address); + drop(chan.send_opt(SendEventMsg(id.clone(), ReflowEvent(nodes)))) + }; f } } @@ -418,6 +384,7 @@ impl LayoutTask { parallel_traversal: parallel_traversal, dirty: Rect::zero(), generation: 0, + stylesheet_dirty: false, })), } } @@ -610,6 +577,7 @@ impl LayoutTask { }); let mut rw_data = self.lock_rw_data(possibly_locked_rw_data); rw_data.stylist.add_stylesheet(sheet, AuthorOrigin); + rw_data.stylesheet_dirty = true; LayoutTask::return_rw_data(possibly_locked_rw_data, rw_data); } @@ -739,23 +707,13 @@ impl LayoutTask { local_image_cache.next_round(self.make_on_image_available_cb()); } - // true => Do the reflow with full style damage, because content - // changed or the window was resized. - let mut all_style_damage = match data.damage.level { - ContentChangedDocumentDamage => true, - _ => false - }; - // TODO: Calculate the "actual viewport": // http://www.w3.org/TR/css-device-adapt/#actual-viewport let viewport_size = data.window_size.initial_viewport; let current_screen_size = Size2D(Au::from_frac32_px(viewport_size.width.get()), Au::from_frac32_px(viewport_size.height.get())); - if rw_data.screen_size != current_screen_size { - all_style_damage = true - } - rw_data.screen_size = current_screen_size; + let old_screen_size = mem::replace(&mut rw_data.screen_size, current_screen_size); // Create a layout context for use throughout the following passes. let mut shared_layout_ctx = @@ -764,6 +722,20 @@ impl LayoutTask { node, &data.url); + // Handle conditions where the entire flow tree is invalid. + let mut needs_dirtying = false; + + needs_dirtying |= current_screen_size != old_screen_size; + needs_dirtying |= rw_data.stylesheet_dirty; + + unsafe { + if needs_dirtying { + LayoutTask::dirty_all_nodes(node); + } + } + + rw_data.stylesheet_dirty = false; + let mut layout_root = profile(time::LayoutStyleRecalcCategory, Some((&data.url, data.iframe, @@ -804,15 +776,6 @@ impl LayoutTask { layout_root.get_mut().dump(); } - // Propagate damage. - profile(time::LayoutDamagePropagateCategory, Some((&data.url, data.iframe, self.first_reflow.get())), - self.time_profiler_chan.clone(), || { - layout_root.get_mut().traverse_preorder(&mut PropagateDamageTraversal { - all_style_damage: all_style_damage - }); - layout_root.get_mut().traverse_postorder(&mut ComputeDamageTraversal.clone()); - }); - // Perform the primary layout passes over the flow tree to compute the locations of all // the boxes. profile(time::LayoutMainCategory, Some((&data.url, data.iframe, self.first_reflow.get())), @@ -860,6 +823,9 @@ impl LayoutTask { } } + debug!("Done building display list. Display List = {}", + flow::base(layout_root.get()).display_list); + let root_display_list = mem::replace(&mut flow::mut_base(layout_root.get_mut()).display_list, DisplayList::new()); @@ -939,13 +905,27 @@ impl LayoutTask { chan.send(ReflowCompleteMsg(self.id, data.id)); } + unsafe fn dirty_all_nodes(node: &mut LayoutNode) { + node.set_dirty(true); + + let mut has_children = false; + + for mut kid in node.children() { + LayoutTask::dirty_all_nodes(&mut kid); + has_children = true; + } + + if has_children { + node.set_dirty_descendants(true); + } + } // When images can't be loaded in time to display they trigger // this callback in some task somewhere. This will send a message // to the script task, and ultimately cause the image to be // re-requested. We probably don't need to go all the way back to // the script task for this. - fn make_on_image_available_cb(&self) -> Box { + fn make_on_image_available_cb(&self) -> Box+Send> { // This has a crazy signature because the image cache needs to // make multiple copies of the callback, and the dom event // channel is not a copyable type, so this is actually a @@ -953,7 +933,7 @@ impl LayoutTask { box LayoutImageResponder { id: self.id.clone(), script_chan: self.script_chan.clone(), - } as Box + } as Box+Send> } /// Handles a message to destroy layout data. Layout data must be destroyed on *this* task diff --git a/servo/components/layout/parallel.rs b/servo/components/layout/parallel.rs index 03d3b02af7d9..87478be93203 100644 --- a/servo/components/layout/parallel.rs +++ b/servo/components/layout/parallel.rs @@ -6,12 +6,14 @@ //! //! This code is highly unsafe. Keep this file small and easy to audit. +use css::node_style::StyledNode; use css::matching::{ApplicableDeclarations, CannotShare, MatchMethods, StyleWasShared}; use construct::FlowConstructor; use context::{LayoutContext, SharedLayoutContext}; use flow::{Flow, MutableFlowUtils, PreorderFlowTraversal, PostorderFlowTraversal}; use flow; use flow_ref::FlowRef; +use incremental::RestyleDamage; use layout_task::{AssignBSizesAndStoreOverflowTraversal, AssignISizesTraversal}; use layout_task::{BubbleISizesTraversal}; use url::Url; @@ -179,8 +181,10 @@ trait ParallelPreorderFlowTraversal : PreorderFlowTraversal { // Get a real flow. let flow: &mut FlowRef = mem::transmute(&unsafe_flow); - // Perform the appropriate traversal. - self.process(flow.get_mut()); + if self.should_process(flow.get_mut()) { + // Perform the appropriate traversal. + self.process(flow.get_mut()); + } // Possibly enqueue the children. for kid in flow::child_iter(flow.get_mut()) { @@ -296,7 +300,7 @@ fn insert_ancestors_into_bloom_filter( ancestors += 1; n.insert_into_bloom_filter(bf); - n = match parent_node(&n, layout_context) { + n = match n.layout_parent_node(layout_context.shared) { None => break, Some(p) => p, }; @@ -304,15 +308,6 @@ fn insert_ancestors_into_bloom_filter( debug!("[{}] Inserted {} ancestors.", tid(), ancestors); } -fn parent_node<'ln>(node: &LayoutNode<'ln>, layout_context: &LayoutContext) -> Option> { - let opaque_node: OpaqueNode = OpaqueNodeMethods::from_layout_node(node); - if opaque_node == layout_context.shared.reflow_root { - None - } else { - node.parent_node() - } -} - fn recalc_style_for_node(mut unsafe_layout_node: UnsafeLayoutNode, proxy: &mut WorkerProxy<*const SharedLayoutContext,UnsafeLayoutNode>) { let shared_layout_context = unsafe { &**proxy.user_data() }; @@ -330,45 +325,46 @@ fn recalc_style_for_node(mut unsafe_layout_node: UnsafeLayoutNode, node.initialize_layout_data(layout_context.shared.layout_chan.clone()); // Get the parent node. - let parent_opt = parent_node(&node, &layout_context); + let parent_opt = node.layout_parent_node(layout_context.shared); // Get the style bloom filter. let bf = take_task_local_bloom_filter(parent_opt, &layout_context); - // First, check to see whether we can share a style with someone. - let style_sharing_candidate_cache = layout_context.style_sharing_candidate_cache(); - let sharing_result = unsafe { - node.share_style_if_possible(style_sharing_candidate_cache, - parent_opt.clone()) - }; - // Just needs to be wrapped in an option for `match_node`. let some_bf = Some(bf); - // Otherwise, match and cascade selectors. - match sharing_result { - CannotShare(mut shareable) => { - let mut applicable_declarations = ApplicableDeclarations::new(); + if node.is_dirty() { + // First, check to see whether we can share a style with someone. + let style_sharing_candidate_cache = layout_context.style_sharing_candidate_cache(); + let sharing_result = unsafe { + node.share_style_if_possible(style_sharing_candidate_cache, + parent_opt.clone()) + }; + // Otherwise, match and cascade selectors. + match sharing_result { + CannotShare(mut shareable) => { + let mut applicable_declarations = ApplicableDeclarations::new(); - if node.is_element() { - // Perform the CSS selector matching. - let stylist = unsafe { &*layout_context.shared.stylist }; - node.match_node(stylist, &some_bf, &mut applicable_declarations, &mut shareable); - } + if node.is_element() { + // Perform the CSS selector matching. + let stylist = unsafe { &*layout_context.shared.stylist }; + node.match_node(stylist, &some_bf, &mut applicable_declarations, &mut shareable); + } - // Perform the CSS cascade. - unsafe { - node.cascade_node(parent_opt, - &applicable_declarations, - layout_context.applicable_declarations_cache()); - } + // Perform the CSS cascade. + unsafe { + node.cascade_node(parent_opt, + &applicable_declarations, + layout_context.applicable_declarations_cache()); + } - // Add ourselves to the LRU cache. - if shareable { - style_sharing_candidate_cache.insert_if_possible(&node); + // Add ourselves to the LRU cache. + if shareable { + style_sharing_candidate_cache.insert_if_possible(&node); + } } + StyleWasShared(index) => style_sharing_candidate_cache.touch(index), } - StyleWasShared(index) => style_sharing_candidate_cache.touch(index), } // Prepare for flow construction by counting the node's children and storing that count. @@ -427,8 +423,18 @@ fn construct_flows<'a>(unsafe_layout_node: &mut UnsafeLayoutNode, // Construct flows for this node. { + let node = ThreadSafeLayoutNode::new(&node); let mut flow_constructor = FlowConstructor::new(layout_context); - flow_constructor.process(&ThreadSafeLayoutNode::new(&node)); + flow_constructor.process(&node); + + // Reset the layout damage in this node. It's been propagated to the + // flow by the flow constructor. + node.set_restyle_damage(RestyleDamage::empty()); + } + + unsafe { + node.set_dirty(false); + node.set_dirty_descendants(false); } // Reset the count of children for the next traversal. diff --git a/servo/components/layout/util.rs b/servo/components/layout/util.rs index 6f29005e8390..198607473f10 100644 --- a/servo/components/layout/util.rs +++ b/servo/components/layout/util.rs @@ -13,7 +13,8 @@ use libc::uintptr_t; use script::dom::bindings::js::JS; use script::dom::bindings::utils::Reflectable; use script::dom::node::{Node, SharedLayoutData}; -use script::layout_interface::{LayoutChan, UntrustedNodeAddress, TrustedNodeAddress}; +use script::layout_interface::{LayoutChan, TrustedNodeAddress}; +use script_traits::UntrustedNodeAddress; use std::mem; use std::cell::{Ref, RefMut}; use style::ComputedValues; @@ -29,7 +30,7 @@ pub struct PrivateLayoutData { pub after_style: Option>, /// Description of how to account for recent style changes. - pub restyle_damage: Option, + pub restyle_damage: RestyleDamage, /// The current results of flow construction for this node. This is either a flow or a /// `ConstructionItem`. See comments in `construct.rs` for more details. @@ -49,7 +50,7 @@ impl PrivateLayoutData { PrivateLayoutData { before_style: None, after_style: None, - restyle_damage: None, + restyle_damage: RestyleDamage::empty(), flow_construction_result: NoConstructionResult, before_flow_construction_result: NoConstructionResult, after_flow_construction_result: NoConstructionResult, @@ -161,4 +162,3 @@ impl ToGfxColor for style::computed_values::RGBA { gfx::color::rgba(self.red, self.green, self.blue, self.alpha) } } - diff --git a/servo/components/layout/wrapper.rs b/servo/components/layout/wrapper.rs index 579727617d0f..2cc58e6e7b0c 100644 --- a/servo/components/layout/wrapper.rs +++ b/servo/components/layout/wrapper.rs @@ -33,9 +33,11 @@ //! o Instead of `html_element_in_html_document()`, use //! `html_element_in_html_document_for_layout()`. +use context::SharedLayoutContext; use css::node_style::StyledNode; -use util::{LayoutDataAccess, LayoutDataWrapper, PrivateLayoutData}; +use util::{LayoutDataAccess, LayoutDataWrapper, PrivateLayoutData, OpaqueNodeMethods}; +use gfx::display_list::OpaqueNode; use script::dom::bindings::codegen::InheritTypes::{HTMLIFrameElementDerived, HTMLInputElementDerived}; use script::dom::bindings::codegen::InheritTypes::{HTMLImageElementDerived, TextDerived}; use script::dom::bindings::js::JS; @@ -46,6 +48,7 @@ use script::dom::htmlimageelement::{HTMLImageElement, LayoutHTMLImageElementHelp use script::dom::htmlinputelement::{HTMLInputElement, LayoutHTMLInputElementHelpers}; use script::dom::node::{DocumentNodeTypeId, ElementNodeTypeId, Node, NodeTypeId}; use script::dom::node::{LayoutNodeHelpers, RawLayoutNodeHelpers, SharedLayoutData, TextNodeTypeId}; +use script::dom::node::{IsDirty, HasDirtyDescendants}; use script::dom::text::Text; use script::layout_interface::LayoutChan; use servo_msg::constellation_msg::{PipelineId, SubpageId}; @@ -158,7 +161,6 @@ impl<'a> PartialEq for LayoutNode<'a> { } } - impl<'ln> TLayoutNode for LayoutNode<'ln> { unsafe fn new_with_this_lifetime(&self, node: &JS) -> LayoutNode<'ln> { LayoutNode { @@ -249,6 +251,21 @@ impl<'ln> LayoutNode<'ln> { Some(_) => {} } } + + pub fn has_children(&self) -> bool { + self.first_child().is_some() + } + + /// While doing a reflow, the node at the root has no parent, as far as we're + /// concerned. This method returns `None` at the reflow root. + pub fn layout_parent_node(&self, shared: &SharedLayoutContext) -> Option> { + let opaque_node: OpaqueNode = OpaqueNodeMethods::from_layout_node(self); + if opaque_node == shared.reflow_root { + None + } else { + self.parent_node() + } + } } impl<'ln> TNode<'ln, LayoutElement<'ln>> for LayoutNode<'ln> { @@ -325,6 +342,22 @@ impl<'ln> TNode<'ln, LayoutElement<'ln>> for LayoutNode<'ln> { } } } + + fn is_dirty(self) -> bool { + unsafe { self.node.get_flag(IsDirty) } + } + + unsafe fn set_dirty(self, value: bool) { + self.node.set_flag(IsDirty, value) + } + + fn has_dirty_descendants(self) -> bool { + unsafe { self.node.get_flag(HasDirtyDescendants) } + } + + unsafe fn set_dirty_descendants(self, value: bool) { + self.node.set_flag(HasDirtyDescendants, value) + } } pub struct LayoutNodeChildrenIterator<'a> { @@ -855,4 +888,3 @@ pub unsafe fn layout_node_from_unsafe_layout_node(node: &UnsafeLayoutNode) -> La let (node, _) = *node; mem::transmute(node) } - diff --git a/servo/components/net/image/holder.rs b/servo/components/net/image/holder.rs index 11f055aad9d5..989021c3789e 100644 --- a/servo/components/net/image/holder.rs +++ b/servo/components/net/image/holder.rs @@ -18,15 +18,16 @@ use url::Url; /// A struct to store image data. The image will be loaded once the first time it is requested, /// and an Arc will be stored. Clones of this Arc are given out on demand. #[deriving(Clone)] -pub struct ImageHolder { +pub struct ImageHolder { url: Url, image: Option>>, cached_size: Size2D, - local_image_cache: Arc>, + local_image_cache: Arc>>, } -impl ImageHolder { - pub fn new(url: Url, local_image_cache: Arc>) -> ImageHolder { +impl ImageHolder { + pub fn new(url: Url, local_image_cache: Arc>>) + -> ImageHolder { debug!("ImageHolder::new() {}", url.serialize()); let holder = ImageHolder { url: url, @@ -60,9 +61,9 @@ impl ImageHolder { } /// Query and update the current image size. - pub fn get_size(&mut self) -> Option> { + pub fn get_size(&mut self, node_address: NodeAddress) -> Option> { debug!("get_size() {}", self.url.serialize()); - self.get_image().map(|img| { + self.get_image(node_address).map(|img| { self.cached_size = Size2D(img.width as int, img.height as int); self.cached_size.clone() @@ -74,7 +75,7 @@ impl ImageHolder { self.image.clone() } - pub fn get_image(&mut self) -> Option>> { + pub fn get_image(&mut self, node_address: NodeAddress) -> Option>> { debug!("get_image() {}", self.url.serialize()); // If this is the first time we've called this function, load @@ -83,7 +84,7 @@ impl ImageHolder { let port = { let val = self.local_image_cache.lock(); let mut local_image_cache = val; - local_image_cache.get_image(&self.url) + local_image_cache.get_image(node_address, &self.url) }; match port.recv() { ImageReady(image) => { @@ -105,5 +106,8 @@ impl ImageHolder { return result; } -} + pub fn url(&self) -> &Url { + &self.url + } +} diff --git a/servo/components/net/image_cache_task.rs b/servo/components/net/image_cache_task.rs index cf0cb9a0bd27..e5fbf856fe7b 100644 --- a/servo/components/net/image_cache_task.rs +++ b/servo/components/net/image_cache_task.rs @@ -227,8 +227,8 @@ impl ImageCache { } } - fn get_state(&self, url: Url) -> ImageState { - match self.state_map.find(&url) { + fn get_state(&self, url: &Url) -> ImageState { + match self.state_map.find(url) { Some(state) => state.clone(), None => Init } @@ -239,7 +239,7 @@ impl ImageCache { } fn prefetch(&mut self, url: Url) { - match self.get_state(url.clone()) { + match self.get_state(&url) { Init => { let to_cache = self.chan.clone(); let resource_task = self.resource_task.clone(); @@ -270,7 +270,7 @@ impl ImageCache { } fn store_prefetched_image_data(&mut self, url: Url, data: Result, ()>) { - match self.get_state(url.clone()) { + match self.get_state(&url) { Prefetching(next_step) => { match data { Ok(data) => { @@ -298,7 +298,7 @@ impl ImageCache { } fn decode(&mut self, url: Url) { - match self.get_state(url.clone()) { + match self.get_state(&url) { Init => fail!("decoding image before prefetch"), Prefetching(DoNotDecode) => { @@ -338,7 +338,7 @@ impl ImageCache { fn store_image(&mut self, url: Url, image: Option>>) { - match self.get_state(url.clone()) { + match self.get_state(&url) { Decoding => { match image { Some(image) => { @@ -376,7 +376,7 @@ impl ImageCache { } fn get_image(&self, url: Url, response: Sender) { - match self.get_state(url.clone()) { + match self.get_state(&url) { Init => fail!("request for image before prefetch"), Prefetching(DoDecode) => response.send(ImageNotReady), Prefetching(DoNotDecode) | Prefetched(..) => fail!("request for image before decode"), @@ -387,7 +387,7 @@ impl ImageCache { } fn wait_for_image(&mut self, url: Url, response: Sender) { - match self.get_state(url.clone()) { + match self.get_state(&url) { Init => fail!("request for image before prefetch"), Prefetching(DoNotDecode) | Prefetched(..) => fail!("request for image before decode"), diff --git a/servo/components/net/local_image_cache.rs b/servo/components/net/local_image_cache.rs index 1427c8316548..079bf69b3e8a 100644 --- a/servo/components/net/local_image_cache.rs +++ b/servo/components/net/local_image_cache.rs @@ -16,19 +16,19 @@ use std::collections::hashmap::HashMap; use servo_util::task::spawn_named; use url::Url; -pub trait ImageResponder { - fn respond(&self) -> proc(ImageResponseMsg):Send; +pub trait ImageResponder { + fn respond(&self) -> proc(ImageResponseMsg, NodeAddress):Send; } -pub struct LocalImageCache { +pub struct LocalImageCache { image_cache_task: ImageCacheTask, round_number: uint, - on_image_available: Option>, + on_image_available: Option+Send>>, state_map: HashMap } -impl LocalImageCache { - pub fn new(image_cache_task: ImageCacheTask) -> LocalImageCache { +impl LocalImageCache { + pub fn new(image_cache_task: ImageCacheTask) -> LocalImageCache { LocalImageCache { image_cache_task: image_cache_task, round_number: 1, @@ -46,10 +46,10 @@ struct ImageState { last_response: ImageResponseMsg } -impl LocalImageCache { +impl LocalImageCache { /// The local cache will only do a single remote request for a given /// URL in each 'round'. Layout should call this each time it begins - pub fn next_round(&mut self, on_image_available: Box) { + pub fn next_round(&mut self, on_image_available: Box + Send>) { self.round_number += 1; self.on_image_available = Some(on_image_available); } @@ -80,7 +80,7 @@ impl LocalImageCache { } // FIXME: Should return a Future - pub fn get_image(&mut self, url: &Url) -> Receiver { + pub fn get_image(&mut self, node_address: NodeAddress, url: &Url) -> Receiver { { let round_number = self.round_number; let state = self.get_state(url); @@ -127,12 +127,13 @@ impl LocalImageCache { // on the image to load and triggering layout let image_cache_task = self.image_cache_task.clone(); assert!(self.on_image_available.is_some()); - let on_image_available: proc(ImageResponseMsg):Send = self.on_image_available.as_ref().unwrap().respond(); + let on_image_available: proc(ImageResponseMsg, NodeAddress):Send = + self.on_image_available.as_ref().unwrap().respond(); let url = (*url).clone(); spawn_named("LocalImageCache", proc() { let (response_chan, response_port) = channel(); - image_cache_task.send(WaitForImage(url.clone(), response_chan)); - on_image_available(response_port.recv()); + image_cache_task.send(WaitForImage(url, response_chan)); + on_image_available(response_port.recv(), node_address); }); } _ => () diff --git a/servo/components/script/dom/bindings/trace.rs b/servo/components/script/dom/bindings/trace.rs index 808f62ecf499..ba3642e79af5 100644 --- a/servo/components/script/dom/bindings/trace.rs +++ b/servo/components/script/dom/bindings/trace.rs @@ -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 JSTraceable for Vec { } } +// 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 JSTraceable for SmallVec1 { + #[inline] + fn trace(&self, trc: *mut JSTracer) { + for e in self.iter() { + e.trace(trc); + } + } +} + impl JSTraceable for Option { #[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] diff --git a/servo/components/script/dom/document.rs b/servo/components/script/dom/document.rs index 406b1fd9dfff..99dde0e9f0c7 100644 --- a/servo/components/script/dom/document.rs +++ b/servo/components/script/dom/document.rs @@ -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); + fn reflow(self); fn wait_until_safe_to_modify_dom(self); fn unregister_named_element(self, to_unregister: JSRef, id: Atom); fn register_named_element(self, element: JSRef, 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.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, diff --git a/servo/components/script/dom/element.rs b/servo/components/script/dom/element.rs index f8b2fa73a60b..ce1371f74ef6 100644 --- a/servo/components/script/dom/element.rs +++ b/servo/components/script/dom/element.rs @@ -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 = 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 = NodeCast::from_ref(self); + if node.is_in_doc() { + let document = node.owner_doc().root(); + document.content_changed(node); } } diff --git a/servo/components/script/dom/htmlinputelement.rs b/servo/components/script/dom/htmlinputelement.rs index ee3c15fd85bb..9908807a713a 100644 --- a/servo/components/script/dom/htmlinputelement.rs +++ b/servo/components/script/dom/htmlinputelement.rs @@ -175,7 +175,8 @@ fn broadcast_radio_checked(broadcaster: JSRef, 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 = NodeCast::from_ref(self); + doc.content_changed(node) } fn radio_group_updated(self, group: Option<&str>) { diff --git a/servo/components/script/dom/node.rs b/servo/components/script/dom/node.rs index 987063886662..9ef523ba9e9e 100644 --- a/servo/components/script/dom/node.rs +++ b/servo/components/script/dom/node.rs @@ -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, /// A bitfield of flags for node items. - flags: RefCell, + flags: Cell, /// 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.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; 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 { @@ -806,6 +889,25 @@ impl LayoutNodeHelpers for JS { unsafe fn owner_doc_for_layout(&self) -> JS { (*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 { diff --git a/servo/components/script/dom/window.rs b/servo/components/script/dom/window.rs index 0c29b1be8157..02166f328e4f 100644 --- a/servo/components/script/dom/window.rs +++ b/servo/components/script/dom/window.rs @@ -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); @@ -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) { diff --git a/servo/components/script/layout_interface.rs b/servo/components/script/layout_interface.rs index 7de7260d8c55..a3c8de5a66a7 100644 --- a/servo/components/script/layout_interface.rs +++ b/servo/components/script/layout_interface.rs @@ -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); pub struct ContentBoxesResponse(pub Vec>); pub struct HitTestResponse(pub UntrustedNodeAddress); pub struct MouseOverResponse(pub Vec); -/// 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::>().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); -} diff --git a/servo/components/script/page.rs b/servo/components/script/page.rs index a2788cfdec0f..f8a2248d447d 100644 --- a/servo/components/script/page.rs +++ b/servo/components/script/page.rs @@ -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>>, - /// What parts of the document are dirty, if any. - damage: RefCell>, - /// The current size of the window, in pixels. pub window_size: Cell, @@ -74,6 +71,9 @@ pub struct Page { /// Pending resize event, if any. pub resize_event: Cell>, + /// Any nodes that need to be dirtied before the next reflow. + pub pending_dirty_nodes: RefCell>, + /// Pending scroll to fragment event, if any pub fragment_name: RefCell>, @@ -86,6 +86,9 @@ pub struct Page { // Child Pages. pub children: RefCell>>, + /// Whether layout needs to be run at all. + pub damaged: Cell, + /// Number of pending reflows that were sent while layout was active. pub pending_reflows: Cell, @@ -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 = 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 = 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> { let document = self.frame().as_ref().unwrap().document.root(); diff --git a/servo/components/script/script_task.rs b/servo/components/script/script_task.rs index 7ae23dba2a21..e01a8caff39d 100644 --- a/servo/components/script/script_task.rs +++ b/servo/components/script/script_task.rs @@ -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); } diff --git a/servo/components/script_traits/Cargo.toml b/servo/components/script_traits/Cargo.toml index 4183b3909dee..b5ca1ca03892 100644 --- a/servo/components/script_traits/Cargo.toml +++ b/servo/components/script_traits/Cargo.toml @@ -13,6 +13,9 @@ path = "../msg" [dependencies.net] path = "../net" +[dependencies.util] +path = "../util" + [dependencies.devtools_traits] path = "../devtools_traits" diff --git a/servo/components/script_traits/lib.rs b/servo/components/script_traits/lib.rs index d5efa73af0fd..8bbbb8df1a81 100644 --- a/servo/components/script_traits/lib.rs +++ b/servo/components/script_traits/lib.rs @@ -9,8 +9,10 @@ extern crate devtools_traits; extern crate geom; +extern crate libc; extern crate "msg" as servo_msg; extern crate "net" as servo_net; +extern crate "util" as servo_util; extern crate url; extern crate serialize; @@ -20,11 +22,13 @@ extern crate serialize; // that these modules won't have to depend on script. use devtools_traits::DevtoolsControlChan; +use libc::c_void; use servo_msg::constellation_msg::{ConstellationChan, PipelineId, Failure, WindowSizeData}; use servo_msg::constellation_msg::SubpageId; use servo_msg::compositor_msg::ScriptListener; use servo_net::image_cache_task::ImageCacheTask; use servo_net::resource_task::ResourceTask; +use servo_util::smallvec::SmallVec1; use std::any::Any; use url::Url; @@ -32,6 +36,10 @@ use geom::point::Point2D; use serialize::{Encodable, Encoder}; +/// 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 NewLayoutInfo { pub old_pipeline_id: PipelineId, pub new_pipeline_id: PipelineId, @@ -60,7 +68,7 @@ pub enum ConstellationControlMsg { /// Events from the compositor that the script task needs to know about pub enum CompositorEvent { ResizeEvent(WindowSizeData), - ReflowEvent, + ReflowEvent(SmallVec1), ClickEvent(uint, Point2D), MouseDownEvent(uint, Point2D), MouseUpEvent(uint, Point2D), diff --git a/servo/components/style/node.rs b/servo/components/style/node.rs index 450e6655e35d..8a8930fdc816 100644 --- a/servo/components/style/node.rs +++ b/servo/components/style/node.rs @@ -19,6 +19,12 @@ pub trait TNode<'a, E: TElement<'a>> : Clone + Copy { fn as_element(self) -> E; fn match_attr(self, attr: &AttrSelector, test: |&str| -> bool) -> bool; fn is_html_element_in_html_document(self) -> bool; + + fn is_dirty(self) -> bool; + unsafe fn set_dirty(self, value: bool); + + fn has_dirty_descendants(self) -> bool; + unsafe fn set_dirty_descendants(self, value: bool); } pub trait TElement<'a> : Copy {