From e6ff135c01e55ea0ca5a7aade3b0c0afb9b63290 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Mon, 3 Jun 2013 21:49:58 -0700 Subject: [PATCH 1/4] test_slam_layout, a new layout perf test case --- src/test/html/test_slam_layout.css | 10 ++++++++++ src/test/html/test_slam_layout.html | 7 +++++++ src/test/html/test_slam_layout.js | 14 ++++++++++++++ 3 files changed, 31 insertions(+) create mode 100644 src/test/html/test_slam_layout.css create mode 100644 src/test/html/test_slam_layout.html create mode 100644 src/test/html/test_slam_layout.js diff --git a/src/test/html/test_slam_layout.css b/src/test/html/test_slam_layout.css new file mode 100644 index 00000000000..fed7aa97a3a --- /dev/null +++ b/src/test/html/test_slam_layout.css @@ -0,0 +1,10 @@ +#ohhi { + background-color: red; + padding: 6px; +} + +#mark { + background-color: blue; + padding: 12px; +} + diff --git a/src/test/html/test_slam_layout.html b/src/test/html/test_slam_layout.html new file mode 100644 index 00000000000..55adc8109b0 --- /dev/null +++ b/src/test/html/test_slam_layout.html @@ -0,0 +1,7 @@ + + + + + +
+ diff --git a/src/test/html/test_slam_layout.js b/src/test/html/test_slam_layout.js new file mode 100644 index 00000000000..5d1726b904e --- /dev/null +++ b/src/test/html/test_slam_layout.js @@ -0,0 +1,14 @@ +var divs = document.getElementsByTagName("div"); +var div = divs[0]; + +var count = 1000; +var start = new Date(); +for (var i = 0; i < count; i++) { + if (i % 2 == 0) + div.setAttribute('id', 'ohhi'); + else + div.setAttribute('id', 'mark'); + div.getBoundingClientRect(); +} +var stop = new Date(); +window.alert((stop - start) / count * 1e6 + " ns/layout"); From 8d3b6aefa8d6c558012cc633bf7002ca72db1326 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Tue, 4 Jun 2013 11:43:52 -0700 Subject: [PATCH 2/4] Stop rendering when script queries layout --- src/components/main/layout/layout_task.rs | 42 ++++++++++++----------- src/components/script/layout_interface.rs | 11 ++++++ src/components/script/script_task.rs | 17 ++++----- 3 files changed, 42 insertions(+), 28 deletions(-) diff --git a/src/components/main/layout/layout_task.rs b/src/components/main/layout/layout_task.rs index 7034593fbe2..69d1e6a031b 100644 --- a/src/components/main/layout/layout_task.rs +++ b/src/components/main/layout/layout_task.rs @@ -36,7 +36,7 @@ use script::layout_interface::{AddStylesheetMsg, BuildData, BuildMsg, ContentBox use script::layout_interface::{HitTestQuery, ContentBoxResponse, HitTestResponse}; use script::layout_interface::{ContentBoxesQuery, ContentBoxesResponse, ExitMsg, LayoutQuery}; use script::layout_interface::{LayoutResponse, LayoutTask, MatchSelectorsDamage, Msg, NoDamage}; -use script::layout_interface::{QueryMsg, ReflowDamage}; +use script::layout_interface::{QueryMsg, ReflowDamage, ReflowForDisplay}; use script::script_task::{ScriptMsg, SendEventMsg}; use servo_net::image_cache_task::{ImageCacheTask, ImageResponseMsg}; use servo_net::local_image_cache::LocalImageCache; @@ -227,29 +227,31 @@ impl Layout { }; } - // Build the display list, and send it to the renderer. - do profile(time::LayoutDispListBuildCategory, self.profiler_chan.clone()) { - let builder = DisplayListBuilder { - ctx: &layout_ctx, - }; + // Build the display list if necessary, and send it to the renderer. + if data.goal == ReflowForDisplay { + do profile(time::LayoutDispListBuildCategory, self.profiler_chan.clone()) { + let builder = DisplayListBuilder { + ctx: &layout_ctx, + }; - let display_list = @Cell(DisplayList::new()); - - // TODO: Set options on the builder before building. - // TODO: Be smarter about what needs painting. - layout_root.build_display_list(&builder, &layout_root.position(), display_list); + let display_list = @Cell(DisplayList::new()); - let root_size = do layout_root.with_base |base| { - base.position.size - }; + // TODO: Set options on the builder before building. + // TODO: Be smarter about what needs painting. + layout_root.build_display_list(&builder, &layout_root.position(), display_list); - let render_layer = RenderLayer { - display_list: display_list.take(), - size: Size2D(root_size.width.to_px() as uint, root_size.height.to_px() as uint) - }; + let root_size = do layout_root.with_base |base| { + base.position.size + }; - self.render_task.channel.send(RenderMsg(render_layer)); - } // time(layout: display list building) + let render_layer = RenderLayer { + display_list: display_list.take(), + size: Size2D(root_size.width.to_px() as uint, root_size.height.to_px() as uint) + }; + + self.render_task.channel.send(RenderMsg(render_layer)); + } // time(layout: display list building) + } // Tell script that we're done. data.script_join_chan.send(()); diff --git a/src/components/script/layout_interface.rs b/src/components/script/layout_interface.rs index 86077244ecd..b4570f2a972 100644 --- a/src/components/script/layout_interface.rs +++ b/src/components/script/layout_interface.rs @@ -86,11 +86,22 @@ impl Damage { } } +/// Why we're doing reflow. +#[deriving(Eq)] +pub enum ReflowGoal { + /// We're reflowing in order to send a display list to the screen. + ReflowForDisplay, + /// We're reflowing in order to satisfy a script query. No display list will be created. + ReflowForScriptQuery, +} + /// Information needed for a reflow. pub struct BuildData { node: AbstractNode, /// What reflow needs to be done. damage: Damage, + /// The goal of reflow: either to render to the screen or to flush layout info for script. + goal: ReflowGoal, /// The URL of the page. url: Url, /// The channel through which messages can be sent back to the script task. diff --git a/src/components/script/script_task.rs b/src/components/script/script_task.rs index 094336bf1ab..d82f4e0895e 100644 --- a/src/components/script/script_task.rs +++ b/src/components/script/script_task.rs @@ -12,7 +12,7 @@ use dom::node::define_bindings; use dom::window::Window; use layout_interface::{AddStylesheetMsg, BuildData, BuildMsg, Damage, LayoutQuery, HitTestQuery}; use layout_interface::{LayoutResponse, HitTestResponse, LayoutTask, MatchSelectorsDamage, NoDamage}; -use layout_interface::{QueryMsg, ReflowDamage}; +use layout_interface::{QueryMsg, ReflowDamage, ReflowForDisplay, ReflowForScriptQuery, ReflowGoal}; use layout_interface; use core::cast::transmute; @@ -283,7 +283,7 @@ impl ScriptContext { null(), &rval); - self.relayout() + self.relayout(ReflowForScriptQuery) } /// Handles a request to exit the script task and shut down layout. @@ -349,7 +349,7 @@ impl ScriptContext { // Perform the initial reflow. self.damage.add(MatchSelectorsDamage); - self.relayout(); + self.relayout(ReflowForDisplay); // Define debug functions. self.js_compartment.define_functions(debug_fns); @@ -383,10 +383,10 @@ impl ScriptContext { } } - /// Initiate an asynchronous relayout operation + /// Initiate an asynchronous relayout operation to handle a script layout query. pub fn trigger_relayout(&mut self, damage: Damage) { self.damage.add(damage); - self.relayout(); + self.relayout(ReflowForScriptQuery); } /// This method will wait until the layout task has completed its current action, join the @@ -394,7 +394,7 @@ impl ScriptContext { /// computation to finish. /// /// This function fails if there is no root frame. - fn relayout(&mut self) { + fn relayout(&mut self, goal: ReflowGoal) { debug!("script: performing relayout"); // Now, join the layout so that they will see the latest changes we have made. @@ -411,6 +411,7 @@ impl ScriptContext { let data = ~BuildData { node: root_frame.document.root, url: copy root_frame.url, + goal: goal, script_chan: self.script_chan.clone(), window_size: self.window_size, script_join_chan: join_chan, @@ -445,7 +446,7 @@ impl ScriptContext { self.window_size = Size2D(new_width, new_height); if self.root_frame.is_some() { - self.relayout() + self.relayout(ReflowForDisplay) } response_chan.send(()) @@ -457,7 +458,7 @@ impl ScriptContext { self.damage.add(MatchSelectorsDamage); if self.root_frame.is_some() { - self.relayout() + self.relayout(ReflowForDisplay) } } From 40a69fc51758356b9ecd9c5e3e3b2754cbb85fe7 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Tue, 4 Jun 2013 15:23:18 -0700 Subject: [PATCH 3/4] Address review comments --- src/components/main/layout/block.rs | 97 ++++++++++++++--------- src/components/main/layout/context.rs | 4 +- src/components/main/layout/layout_task.rs | 10 +-- src/components/main/layout/model.rs | 33 +++++--- 4 files changed, 85 insertions(+), 59 deletions(-) diff --git a/src/components/main/layout/block.rs b/src/components/main/layout/block.rs index 48626c7f087..07759121f4e 100644 --- a/src/components/main/layout/block.rs +++ b/src/components/main/layout/block.rs @@ -102,38 +102,39 @@ impl BlockFlowData { } } - /* if not an anonymous block context, add in block box's widths. - these widths will not include child elements, just padding etc. */ - self.box.map(|&box| { - //Can compute border width here since it doesn't depend on anything + // If not an anonymous block context, add in the block box's widths. These widths will not + // include child elements, just padding etc. + for self.box.each |&box| { + // Can compute border width here since it doesn't depend on anything. let style = box.style(); do box.with_model |model| { model.compute_borders(style) } min_width = min_width.add(&box.get_min_width(ctx)); pref_width = pref_width.add(&box.get_pref_width(ctx)); - }); + } self.common.min_width = min_width; self.common.pref_width = pref_width; } /// Computes left and right margins and width based on CSS 2.1 secion 10.3.3. - /// Requires borders and padding to already be computed - priv fn compute_horiz( &self, - width: MaybeAuto, - left_margin: MaybeAuto, - right_margin: MaybeAuto, - available_width: Au) -> (Au, Au, Au) { - - //If width is not 'auto', and width + margins > available_width, all 'auto' margins are treated as '0' - let (left_margin, right_margin) = match width{ + /// Requires borders and padding to already be computed. + fn compute_horiz(&self, + width: MaybeAuto, + left_margin: MaybeAuto, + right_margin: MaybeAuto, + available_width: Au) + -> (Au, Au, Au) { + // If width is not 'auto', and width + margins > available_width, all 'auto' margins are + // treated as '0'. + let (left_margin, right_margin) = match width { Auto => (left_margin, right_margin), Specified(width) => { let left = left_margin.spec_or_default(Au(0)); let right = right_margin.spec_or_default(Au(0)); - if((left + right + width) > available_width) { + if (left + right + width) > available_width { (Specified(left), Specified(right)) } else { (left_margin, right_margin) @@ -141,31 +142,43 @@ impl BlockFlowData { } }; - //Invariant: left_margin_Au + width_Au + right_margin_Au == available_width + // Invariant: left_margin_Au + width_Au + right_margin_Au == available_width let (left_margin_Au, width_Au, right_margin_Au) = match (left_margin, width, right_margin) { - //If all have a computed value other than 'auto', the system is over-constrained and we need to discard a margin. - //if direction is ltr, ignore the specified right margin and solve for it. If it is rtl, ignore the specified - //left margin. FIXME(eatkinson): this assumes the direction is ltr - (Specified(margin_l), Specified(width), Specified(margin_r)) => (margin_l, width, available_width - (margin_l + width )), + // If all have a computed value other than 'auto', the system is over-constrained and + // we need to discard a margin. If direction is ltr, ignore the specified right margin + // and solve for it. If it is rtl, ignore the specified left margin. + // + // FIXME(eatkinson): this assumes the direction is ltr + (Specified(margin_l), Specified(width), Specified(_)) => { + (margin_l, width, available_width - (margin_l + width)) + } - //If exactly one value is 'auto', solve for it - (Auto, Specified(width), Specified(margin_r)) => (available_width - (width + margin_r), width, margin_r), - (Specified(margin_l), Auto, Specified(margin_r)) => (margin_l, available_width - (margin_l + margin_r), margin_r), - (Specified(margin_l), Specified(width), Auto) => (margin_l, width, available_width - (margin_l + width)), + // If exactly one value is 'auto', solve for it + (Auto, Specified(width), Specified(margin_r)) => { + (available_width - (width + margin_r), width, margin_r) + } + (Specified(margin_l), Auto, Specified(margin_r)) => { + (margin_l, available_width - (margin_l + margin_r), margin_r) + } + (Specified(margin_l), Specified(width), Auto) => { + (margin_l, width, available_width - (margin_l + width)) + } - //If width is set to 'auto', any other 'auto' value becomes '0', and width is solved for + // If width is set to 'auto', any other 'auto' value becomes '0', and width is solved + // for. (Auto, Auto, Specified(margin_r)) => (Au(0), available_width - margin_r, margin_r), (Specified(margin_l), Auto, Auto) => (margin_l, available_width - margin_l, Au(0)), (Auto, Auto, Auto) => (Au(0), available_width, Au(0)), - //If left and right margins are auto, they become equal + // If left and right margins are auto, they become equal. (Auto, Specified(width), Auto) => { let margin = (available_width - width).scale_by(0.5); (margin, width, margin) } }; - //return values in same order as params + + // Return values in same order as params. (width_Au, left_margin_Au, right_margin_Au) } @@ -184,25 +197,31 @@ impl BlockFlowData { let mut remaining_width = self.common.position.size.width; let mut x_offset = Au(0); - self.box.map(|&box| { + for self.box.each |&box| { let style = box.style(); do box.with_model |model| { - //Can compute padding here since we know containing block width + // Can compute padding here since we know containing block width. model.compute_padding(style, remaining_width); - //Margins are 0 right now so model.noncontent_width() is just borders + padding. + // Margins are 0 right now so model.noncontent_width() is just borders + padding. let available_width = remaining_width - model.noncontent_width(); - //Top and bottom margins for blocks are 0 if auto - let margin_top = MaybeAuto::from_margin(style.margin_top()).spec_or_default(Au(0)); - let margin_bottom = MaybeAuto::from_margin(style.margin_bottom()).spec_or_default(Au(0)); + // Top and bottom margins for blocks are 0 if auto. + let margin_top = MaybeAuto::from_margin(style.margin_top()); + let margin_top = margin_top.spec_or_default(Au(0)); + let margin_bottom = MaybeAuto::from_margin(style.margin_bottom()); + let margin_bottom = margin_bottom.spec_or_default(Au(0)); - let (width, margin_left, margin_right) = (MaybeAuto::from_width(style.width()), - MaybeAuto::from_margin(style.margin_left()), - MaybeAuto::from_margin(style.margin_right())); + let (width, margin_left, margin_right) = + (MaybeAuto::from_width(style.width()), + MaybeAuto::from_margin(style.margin_left()), + MaybeAuto::from_margin(style.margin_right())); - let (width, margin_left, margin_right) = - self.compute_horiz(width, margin_left, margin_right, available_width); + // FIXME(pcwalton): We discard the width here. Is that correct? + let (_, margin_left, margin_right) = self.compute_horiz(width, + margin_left, + margin_right, + available_width); model.margin.top = margin_top; model.margin.right = margin_right; @@ -221,7 +240,7 @@ impl BlockFlowData { base.model.border.left + base.model.border.right; base.position.size.width = remaining_width + pb; } - }); + } for BlockFlow(self).each_child |kid| { assert!(kid.starts_block_flow() || kid.starts_inline_flow()); diff --git a/src/components/main/layout/context.rs b/src/components/main/layout/context.rs index 8646b9faabc..e0ad6bcad7e 100644 --- a/src/components/main/layout/context.rs +++ b/src/components/main/layout/context.rs @@ -8,12 +8,10 @@ use geom::rect::Rect; use gfx::font_context::FontContext; use gfx::geometry::Au; use servo_net::local_image_cache::LocalImageCache; -use std::net::url::Url; /// Data needed by the layout task. pub struct LayoutContext { font_ctx: @mut FontContext, image_cache: @mut LocalImageCache, - doc_url: Url, - screen_size: Rect + screen_size: Rect, } diff --git a/src/components/main/layout/layout_task.rs b/src/components/main/layout/layout_task.rs index 69d1e6a031b..659272fa62f 100644 --- a/src/components/main/layout/layout_task.rs +++ b/src/components/main/layout/layout_task.rs @@ -115,12 +115,10 @@ impl Layout { let image_cache = self.local_image_cache; let font_ctx = self.font_ctx; let screen_size = self.screen_size.unwrap(); - let doc_url = self.doc_url.clone(); LayoutContext { image_cache: image_cache, font_ctx: font_ctx, - doc_url: doc_url.unwrap(), screen_size: Rect(Point2D(Au(0), Au(0)), screen_size), } } @@ -348,10 +346,12 @@ impl Layout { let display_list = &display_list.take().list; for display_list.each_reverse |display_item| { let bounds = display_item.bounds(); + + // FIXME(pcwalton): Move this to be a method on Rect. if x <= bounds.origin.x + bounds.size.width && - bounds.origin.x <= x && - y < bounds.origin.y + bounds.size.height && - bounds.origin.y < y { + x >= bounds.origin.x && + y < bounds.origin.y + bounds.size.height && + y >= bounds.origin.y { resp = Ok(HitTestResponse(display_item.base().extra.node())); break; } diff --git a/src/components/main/layout/model.rs b/src/components/main/layout/model.rs index 4871ddb5c85..8f222814dc1 100644 --- a/src/components/main/layout/model.rs +++ b/src/components/main/layout/model.rs @@ -22,12 +22,17 @@ use newcss::values::{CSSBorderWidthThick, CSSBorderWidthThin}; use newcss::values::{CSSWidth, CSSWidthLength, CSSWidthPercentage, CSSWidthAuto}; use newcss::values::{CSSMargin, CSSMarginLength, CSSMarginPercentage, CSSMarginAuto}; use newcss::values::{CSSPadding, CSSPaddingLength, CSSPaddingPercentage}; + /// Encapsulates the borders, padding, and margins, which we collectively call the "box model". pub struct BoxModel { + /// The size of the borders. border: SideOffsets2D, + /// The size of the padding. padding: SideOffsets2D, + /// The size of the margins. margin: SideOffsets2D, - cb_width: Au, + /// The width of the content box. + content_box_width: Au, } /// Useful helper data type when computing values for blocks and positioned elements. @@ -75,7 +80,7 @@ impl Zero for BoxModel { border: Zero::zero(), padding: Zero::zero(), margin: Zero::zero(), - cb_width: Zero::zero(), + content_box_width: Zero::zero(), } } @@ -85,7 +90,7 @@ impl Zero for BoxModel { } impl BoxModel { - /// Populates the box model parameters from the given computed style. + /// Populates the box model border parameters from the given computed style. pub fn compute_borders(&mut self, style: CompleteStyle) { // Compute the borders. self.border.top = self.compute_border_width(style.border_top_width()); @@ -94,11 +99,16 @@ impl BoxModel { self.border.left = self.compute_border_width(style.border_left_width()); } - pub fn compute_padding(&mut self, style: CompleteStyle, cb_width: Au){ - self.padding.top = self.compute_padding_length(style.padding_top(), cb_width); - self.padding.right = self.compute_padding_length(style.padding_right(), cb_width); - self.padding.bottom = self.compute_padding_length(style.padding_bottom(), cb_width); - self.padding.left = self.compute_padding_length(style.padding_left(), cb_width); + /// Populates the box model padding parameters from the given computed style. + pub fn compute_padding(&mut self, style: CompleteStyle, content_box_width: Au) { + self.padding.top = self.compute_padding_length(style.padding_top(), + content_box_width); + self.padding.right = self.compute_padding_length(style.padding_right(), + content_box_width); + self.padding.bottom = self.compute_padding_length(style.padding_bottom(), + content_box_width); + self.padding.left = self.compute_padding_length(style.padding_left(), + content_box_width); } pub fn noncontent_width(&self) -> Au { @@ -112,7 +122,7 @@ impl BoxModel { } /// Helper function to compute the border width in app units from the CSS border width. - priv fn compute_border_width(&self, width: CSSBorderWidth) -> Au { + fn compute_border_width(&self, width: CSSBorderWidth) -> Au { match width { CSSBorderWidthLength(Px(v)) | CSSBorderWidthLength(Em(v)) | @@ -126,7 +136,7 @@ impl BoxModel { } } - priv fn compute_padding_length(&self, padding: CSSPadding, cb_width: Au) -> Au { + fn compute_padding_length(&self, padding: CSSPadding, content_box_width: Au) -> Au { match padding { CSSPaddingLength(Px(v)) | CSSPaddingLength(Pt(v)) | @@ -134,10 +144,9 @@ impl BoxModel { // FIXME(eatkinson): Handle 'em' and 'pt' correctly Au::from_frac_px(v) } - CSSPaddingPercentage(p) => cb_width.scale_by(p) + CSSPaddingPercentage(p) => content_box_width.scale_by(p) } } - } // From 7a435fc6edf3c261c56f083b01b132aa6a37724f Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Tue, 4 Jun 2013 22:00:33 -0700 Subject: [PATCH 4/4] Refactor document damage to distinguish it from layout/style damage. Also, standardize on the name "reflow" instead of "relayout" or "build". --- src/components/main/layout/layout_task.rs | 21 ++--- src/components/script/dom/window.rs | 4 +- src/components/script/layout_interface.rs | 46 ++++++----- src/components/script/script_task.rs | 93 ++++++++++++++++------- 4 files changed, 106 insertions(+), 58 deletions(-) diff --git a/src/components/main/layout/layout_task.rs b/src/components/main/layout/layout_task.rs index 659272fa62f..19580507eeb 100644 --- a/src/components/main/layout/layout_task.rs +++ b/src/components/main/layout/layout_task.rs @@ -32,11 +32,12 @@ use newcss::stylesheet::Stylesheet; use newcss::types::OriginAuthor; use script::dom::event::ReflowEvent; use script::dom::node::{AbstractNode, LayoutView}; -use script::layout_interface::{AddStylesheetMsg, BuildData, BuildMsg, ContentBoxQuery}; +use script::layout_interface::{AddStylesheetMsg, ContentBoxQuery}; use script::layout_interface::{HitTestQuery, ContentBoxResponse, HitTestResponse}; use script::layout_interface::{ContentBoxesQuery, ContentBoxesResponse, ExitMsg, LayoutQuery}; -use script::layout_interface::{LayoutResponse, LayoutTask, MatchSelectorsDamage, Msg, NoDamage}; -use script::layout_interface::{QueryMsg, ReflowDamage, ReflowForDisplay}; +use script::layout_interface::{LayoutResponse, LayoutTask, MatchSelectorsDocumentDamage, Msg}; +use script::layout_interface::{QueryMsg, Reflow, ReflowDocumentDamage, ReflowForDisplay}; +use script::layout_interface::{ReflowMsg}; use script::script_task::{ScriptMsg, SendEventMsg}; use servo_net::image_cache_task::{ImageCacheTask, ImageResponseMsg}; use servo_net::local_image_cache::LocalImageCache; @@ -126,11 +127,11 @@ impl Layout { fn handle_request(&mut self) -> bool { match self.from_script.recv() { AddStylesheetMsg(sheet) => self.handle_add_stylesheet(sheet), - BuildMsg(data) => { + ReflowMsg(data) => { let data = Cell(data); do profile(time::LayoutPerformCategory, self.profiler_chan.clone()) { - self.handle_build(data.take()); + self.handle_reflow(data.take()); } } QueryMsg(query, chan) => { @@ -154,10 +155,10 @@ impl Layout { } /// The high-level routine that performs layout tasks. - fn handle_build(&mut self, data: &BuildData) { + fn handle_reflow(&mut self, data: &Reflow) { // FIXME: Isolate this transmutation into a "bridge" module. let node: &AbstractNode = unsafe { - transmute(&data.node) + transmute(&data.document_root) }; // FIXME: Bad copy! @@ -187,9 +188,9 @@ impl Layout { } // Perform CSS selector matching if necessary. - match data.damage { - NoDamage | ReflowDamage => {} - MatchSelectorsDamage => { + match data.damage.level { + ReflowDocumentDamage => {} + MatchSelectorsDocumentDamage => { do profile(time::LayoutSelectorMatchCategory, self.profiler_chan.clone()) { node.restyle_subtree(self.css_select_ctx); } diff --git a/src/components/script/dom/window.rs b/src/components/script/dom/window.rs index c94e6488a67..a5904b642c7 100644 --- a/src/components/script/dom/window.rs +++ b/src/components/script/dom/window.rs @@ -4,7 +4,7 @@ use dom::bindings::utils::WrapperCache; use dom::bindings::window; -use layout_interface::MatchSelectorsDamage; +use layout_interface::ReflowForScriptQuery; use script_task::{ExitMsg, FireTimerMsg, ScriptMsg, ScriptContext}; use core::comm::{Chan, SharedChan}; @@ -83,7 +83,7 @@ pub impl Window { fn content_changed(&self) { unsafe { - (*self.script_context).trigger_relayout(MatchSelectorsDamage); + (*self.script_context).reflow_all(ReflowForScriptQuery) } } diff --git a/src/components/script/layout_interface.rs b/src/components/script/layout_interface.rs index b4570f2a972..f9ab0a8eb2e 100644 --- a/src/components/script/layout_interface.rs +++ b/src/components/script/layout_interface.rs @@ -25,9 +25,7 @@ pub enum Msg { AddStylesheetMsg(Stylesheet), /// Requests a reflow. - /// - /// FIXME(pcwalton): Call this `reflow` instead? - BuildMsg(~BuildData), + ReflowMsg(~Reflow), /// Performs a synchronous layout request. /// @@ -61,31 +59,37 @@ pub enum LayoutResponse { HitTestResponse(AbstractNode), } -/// Dirty bits for layout. -pub enum Damage { - /// The document is clean; nothing needs to be done. - NoDamage, - /// Reflow, but do not perform CSS selector matching. - ReflowDamage, +/// Determines which part of the +pub enum DocumentDamageLevel { /// Perform CSS selector matching and reflow. - MatchSelectorsDamage, + MatchSelectorsDocumentDamage, + /// Reflow, but do not perform CSS selector matching. + ReflowDocumentDamage, } -impl Damage { +impl DocumentDamageLevel { /// Sets this damage to the maximum of this damage and the given damage. /// /// FIXME(pcwalton): This could be refactored to use `max` and the `Ord` trait, and this /// function removed. - fn add(&mut self, new_damage: Damage) { + fn add(&mut self, new_damage: DocumentDamageLevel) { match (*self, new_damage) { - (NoDamage, _) => *self = new_damage, - (ReflowDamage, NoDamage) => *self = ReflowDamage, - (ReflowDamage, new_damage) => *self = new_damage, - (MatchSelectorsDamage, _) => *self = MatchSelectorsDamage + (ReflowDocumentDamage, new_damage) => *self = new_damage, + (MatchSelectorsDocumentDamage, _) => *self = MatchSelectorsDocumentDamage, } } } +/// 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 +pub struct DocumentDamage { + /// The topmost node in the tree that has changed. + root: AbstractNode, + /// The amount of damage that occurred. + level: DocumentDamageLevel, +} + /// Why we're doing reflow. #[deriving(Eq)] pub enum ReflowGoal { @@ -96,10 +100,11 @@ pub enum ReflowGoal { } /// Information needed for a reflow. -pub struct BuildData { - node: AbstractNode, - /// What reflow needs to be done. - damage: Damage, +pub struct Reflow { + /// The document node. + document_root: AbstractNode, + /// The style changes that need to be done. + damage: DocumentDamage, /// The goal of reflow: either to render to the screen or to flush layout info for script. goal: ReflowGoal, /// The URL of the page. @@ -108,6 +113,7 @@ pub struct BuildData { script_chan: SharedChan, /// The current window size. window_size: Size2D, + /// The channel that we send a notification to. script_join_chan: Chan<()>, } diff --git a/src/components/script/script_task.rs b/src/components/script/script_task.rs index d82f4e0895e..66a4e9e4727 100644 --- a/src/components/script/script_task.rs +++ b/src/components/script/script_task.rs @@ -8,11 +8,12 @@ use dom::bindings::utils::GlobalStaticData; use dom::document::Document; use dom::event::{Event, ResizeEvent, ReflowEvent, ClickEvent}; -use dom::node::define_bindings; +use dom::node::{AbstractNode, ScriptView, define_bindings}; use dom::window::Window; -use layout_interface::{AddStylesheetMsg, BuildData, BuildMsg, Damage, LayoutQuery, HitTestQuery}; -use layout_interface::{LayoutResponse, HitTestResponse, LayoutTask, MatchSelectorsDamage, NoDamage}; -use layout_interface::{QueryMsg, ReflowDamage, ReflowForDisplay, ReflowForScriptQuery, ReflowGoal}; +use layout_interface::{AddStylesheetMsg, DocumentDamage, DocumentDamageLevel, HitTestQuery}; +use layout_interface::{HitTestResponse, LayoutQuery, LayoutResponse, LayoutTask}; +use layout_interface::{MatchSelectorsDocumentDamage, QueryMsg, Reflow, ReflowDocumentDamage}; +use layout_interface::{ReflowForDisplay, ReflowForScriptQuery, ReflowGoal, ReflowMsg}; use layout_interface; use core::cast::transmute; @@ -132,8 +133,8 @@ pub struct ScriptContext { /// The current size of the window, in pixels. window_size: Size2D, - /// What parts of layout are dirty. - damage: Damage, + /// What parts of the document are dirty, if any. + damage: Option, } fn global_script_context_key(_: @ScriptContext) {} @@ -199,7 +200,7 @@ impl ScriptContext { root_frame: None, window_size: Size2D(800, 600), - damage: MatchSelectorsDamage, + damage: None, }; // Indirection for Rust Issue #6248, dynamic freeze scope artifically extended let script_context_ptr = { @@ -283,7 +284,7 @@ impl ScriptContext { null(), &rval); - self.relayout(ReflowForScriptQuery) + self.reflow(ReflowForScriptQuery) } /// Handles a request to exit the script task and shut down layout. @@ -348,8 +349,11 @@ impl ScriptContext { }); // Perform the initial reflow. - self.damage.add(MatchSelectorsDamage); - self.relayout(ReflowForDisplay); + self.damage = Some(DocumentDamage { + root: root_node, + level: MatchSelectorsDocumentDamage, + }); + self.reflow(ReflowForDisplay); // Define debug functions. self.js_compartment.define_functions(debug_fns); @@ -383,19 +387,13 @@ impl ScriptContext { } } - /// Initiate an asynchronous relayout operation to handle a script layout query. - pub fn trigger_relayout(&mut self, damage: Damage) { - self.damage.add(damage); - self.relayout(ReflowForScriptQuery); - } - /// This method will wait until the layout task has completed its current action, join the /// layout task, and then request a new layout run. It won't wait for the new layout /// computation to finish. /// /// This function fails if there is no root frame. - fn relayout(&mut self, goal: ReflowGoal) { - debug!("script: performing relayout"); + fn reflow(&mut self, goal: ReflowGoal) { + debug!("script: performing reflow"); // Now, join the layout so that they will see the latest changes we have made. self.join_layout(); @@ -408,23 +406,36 @@ impl ScriptContext { None => fail!(~"Tried to relayout with no root frame!"), Some(ref root_frame) => { // Send new document and relevant styles to layout. - let data = ~BuildData { - node: root_frame.document.root, + let reflow = ~Reflow { + document_root: root_frame.document.root, url: copy root_frame.url, goal: goal, script_chan: self.script_chan.clone(), window_size: self.window_size, script_join_chan: join_chan, - damage: replace(&mut self.damage, NoDamage), + damage: replace(&mut self.damage, None).unwrap(), }; - self.layout_task.chan.send(BuildMsg(data)) + self.layout_task.chan.send(ReflowMsg(reflow)) } } debug!("script: layout forked") } + /// Reflows the entire document. + /// + /// FIXME: This should basically never be used. + pub fn reflow_all(&mut self, goal: ReflowGoal) { + for self.root_frame.each |root_frame| { + ScriptContext::damage(&mut self.damage, + root_frame.document.root, + MatchSelectorsDocumentDamage) + } + + self.reflow(goal) + } + /// Sends the given query to layout. pub fn query_layout(&mut self, query: LayoutQuery) -> Result { self.join_layout(); @@ -434,6 +445,26 @@ impl ScriptContext { response_port.recv() } + /// Adds the given damage. + fn damage(damage: &mut Option, + root: AbstractNode, + level: DocumentDamageLevel) { + match *damage { + None => {} + Some(ref mut damage) => { + // FIXME(pcwalton): This is wrong. We should trace up to the nearest ancestor. + damage.root = root; + damage.level.add(level); + return + } + } + + *damage = Some(DocumentDamage { + root: root, + level: level, + }) + } + /// This is the main entry point for receiving and dispatching DOM events. /// /// TODO: Actually perform DOM event dispatch. @@ -442,23 +473,33 @@ impl ScriptContext { ResizeEvent(new_width, new_height, response_chan) => { debug!("script got resize event: %u, %u", new_width, new_height); - self.damage.add(ReflowDamage); self.window_size = Size2D(new_width, new_height); + for self.root_frame.each |root_frame| { + ScriptContext::damage(&mut self.damage, + root_frame.document.root, + ReflowDocumentDamage); + } + if self.root_frame.is_some() { - self.relayout(ReflowForDisplay) + self.reflow(ReflowForDisplay) } response_chan.send(()) } + // FIXME(pcwalton): This reflows the entire document and is not incremental-y. ReflowEvent => { debug!("script got reflow event"); - self.damage.add(MatchSelectorsDamage); + for self.root_frame.each |root_frame| { + ScriptContext::damage(&mut self.damage, + root_frame.document.root, + MatchSelectorsDocumentDamage); + } if self.root_frame.is_some() { - self.relayout(ReflowForDisplay) + self.reflow(ReflowForDisplay) } }