diff --git a/servo/Cargo.lock b/servo/Cargo.lock index 8daf46f27be7..776d48d545a2 100644 --- a/servo/Cargo.lock +++ b/servo/Cargo.lock @@ -174,7 +174,7 @@ source = "git+https://github.com/servo/libfreetype2#5b6499164106f094937565595c7b [[package]] name = "geom" version = "0.1.0" -source = "git+https://github.com/servo/rust-geom#b001a76e907befaae1d0d6dd259418a22092da86" +source = "git+https://github.com/servo/rust-geom#e5e74911ac6d3201009879b72499d6c681302611" [[package]] name = "gfx" diff --git a/servo/components/compositing/compositor.rs b/servo/components/compositing/compositor.rs index 50a47af45a43..c55e01932655 100644 --- a/servo/components/compositing/compositor.rs +++ b/servo/components/compositing/compositor.rs @@ -38,10 +38,12 @@ use layers::scene::Scene; use png; use gleam::gl::types::{GLint, GLsizei}; use gleam::gl; +use script_traits::{ViewportMsg, ScriptControlChan}; use servo_msg::compositor_msg::{Blank, Epoch, FinishedLoading, IdleRenderState, LayerId}; use servo_msg::compositor_msg::{ReadyState, RenderingRenderState, RenderState, Scrollable}; -use servo_msg::constellation_msg::{ConstellationChan, ExitMsg, LoadUrlMsg, NavigateMsg}; -use servo_msg::constellation_msg::{LoadData, PipelineId, ResizedWindowMsg, WindowSizeData}; +use servo_msg::constellation_msg::{ConstellationChan, ExitMsg, LoadUrlMsg}; +use servo_msg::constellation_msg::{NavigateMsg, LoadData, PipelineId, ResizedWindowMsg}; +use servo_msg::constellation_msg::{WindowSizeData}; use servo_msg::constellation_msg; use servo_util::geometry::{PagePx, ScreenPx, ViewportPx}; use servo_util::memory::MemoryProfilerChan; @@ -266,6 +268,7 @@ impl IOCompositor { self.set_frame_tree(&frame_tree, response_chan, new_constellation_chan); + self.send_viewport_rects_for_all_layers(); } (FrameTreeUpdateMsg(frame_tree_diff, response_channel), NotShuttingDown) => { @@ -775,6 +778,7 @@ impl IOCompositor { } fn process_pending_scroll_events(&mut self) { + let had_scroll_events = self.pending_scroll_events.len() > 0; for scroll_event in mem::replace(&mut self.pending_scroll_events, Vec::new()).into_iter() { let delta = scroll_event.delta / self.scene.scale; let cursor = scroll_event.cursor.as_f32() / self.scene.scale; @@ -789,6 +793,10 @@ impl IOCompositor { self.start_scrolling_timer_if_necessary(); self.send_buffer_requests_for_all_layers(); } + + if had_scroll_events { + self.send_viewport_rects_for_all_layers(); + } } fn device_pixels_per_screen_px(&self) -> ScaleFactor { @@ -845,6 +853,7 @@ impl IOCompositor { None => { } } + self.send_viewport_rects_for_all_layers(); self.composite_if_necessary(); } @@ -902,6 +911,27 @@ impl IOCompositor { } } + fn send_viewport_rect_for_layer(&self, layer: Rc>) { + if layer.extra_data.borrow().id == LayerId::null() { + let layer_rect = Rect(-layer.extra_data.borrow().scroll_offset.to_untyped(), + layer.bounds.borrow().size.to_untyped()); + let pipeline = &layer.extra_data.borrow().pipeline; + let ScriptControlChan(ref chan) = pipeline.script_chan; + chan.send(ViewportMsg(pipeline.id.clone(), layer_rect)); + } + + for kid in layer.children().iter() { + self.send_viewport_rect_for_layer(kid.clone()); + } + } + + fn send_viewport_rects_for_all_layers(&self) { + match self.scene.root { + Some(ref root) => self.send_viewport_rect_for_layer(root.clone()), + None => {}, + } + } + /// Returns true if any buffer requests were sent or false otherwise. fn send_buffer_requests_for_all_layers(&mut self) -> bool { let mut layers_and_requests = Vec::new(); diff --git a/servo/components/layout/block.rs b/servo/components/layout/block.rs index 8835682644dd..baf80f6ca3ff 100644 --- a/servo/components/layout/block.rs +++ b/servo/components/layout/block.rs @@ -48,7 +48,7 @@ use geom::Size2D; use gfx::display_list::BlockLevel; use serialize::{Encoder, Encodable}; use servo_msg::compositor_msg::LayerId; -use servo_util::geometry::{Au, MAX_AU, MAX_RECT}; +use servo_util::geometry::{Au, MAX_AU}; use servo_util::logical_geometry::{LogicalPoint, LogicalRect, LogicalSize}; use servo_util::opts; use std::cmp::{max, min}; @@ -1665,10 +1665,6 @@ impl Flow for BlockFlow { // FIXME(#2795): Get the real container size let container_size = Size2D::zero(); - if self.is_root() { - self.base.clip_rect = MAX_RECT; - } - if self.base.flags.is_absolutely_positioned() { let position_start = self.base.position.start.to_physical(self.base.writing_mode, container_size); diff --git a/servo/components/layout/display_list_builder.rs b/servo/components/layout/display_list_builder.rs index 4a6da7b514bd..ae2984f80914 100644 --- a/servo/components/layout/display_list_builder.rs +++ b/servo/components/layout/display_list_builder.rs @@ -499,6 +499,10 @@ impl FragmentDisplayListBuilding for Fragment { return } + if !absolute_fragment_bounds.intersects(clip_rect) { + return; + } + debug!("Fragment::build_display_list: intersected. Adding display item..."); if self.is_primary_fragment() { diff --git a/servo/components/layout/layout_task.rs b/servo/components/layout/layout_task.rs index 1ed51197db2a..e1e918251cd7 100644 --- a/servo/components/layout/layout_task.rs +++ b/servo/components/layout/layout_task.rs @@ -625,6 +625,8 @@ impl LayoutTask { LogicalPoint::zero(writing_mode).to_physical(writing_mode, rw_data.screen_size); + flow::mut_base(layout_root.deref_mut()).clip_rect = data.page_clip_rect; + let rw_data = rw_data.deref_mut(); match rw_data.parallel_traversal { None => { diff --git a/servo/components/script/dom/bindings/trace.rs b/servo/components/script/dom/bindings/trace.rs index 94d05e4c4774..1690cb5ddad1 100644 --- a/servo/components/script/dom/bindings/trace.rs +++ b/servo/components/script/dom/bindings/trace.rs @@ -28,37 +28,37 @@ //! a datatype. use dom::bindings::js::JS; -use dom::bindings::utils::{Reflectable, Reflector}; +use dom::bindings::utils::{Reflectable, Reflector, WindowProxyHandler}; +use dom::node::{Node, TrustedNodeAddress}; + +use collections::hash::{Hash, Hasher}; +use geom::rect::Rect; +use html5ever::tree_builder::QuirksMode; +use http::headers::request::HeaderCollection as RequestHeaderCollection; +use http::headers::response::HeaderCollection as ResponseHeaderCollection; +use http::method::Method; use js::jsapi::{JSObject, JSTracer, JS_CallTracer, JSTRACE_OBJECT}; use js::jsval::JSVal; - +use js::rust::Cx; +use layout_interface::{LayoutRPC, LayoutChan}; use libc; -use std::rc::Rc; -use std::cell::{Cell, RefCell}; - -use url::Url; use msg::constellation_msg::{PipelineId, SubpageId, WindowSizeData}; use net::image_cache_task::ImageCacheTask; use script_traits::ScriptControlChan; -use std::collections::hashmap::HashMap; -use collections::hash::{Hash, Hasher}; -use style::PropertyDeclarationBlock; -use std::comm::{Receiver, Sender}; -use string_cache::{Atom, Namespace}; -use js::rust::Cx; -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 servo_util::str::LengthOrPercentageOrAuto; -use layout_interface::{LayoutRPC, LayoutChan}; -use dom::node::{Node, TrustedNodeAddress}; -use dom::bindings::utils::WindowProxyHandler; -use html5ever::tree_builder::QuirksMode; +use std::cell::{Cell, RefCell}; +use std::collections::hashmap::HashMap; +use std::comm::{Receiver, Sender}; +use std::io::timer::Timer; +use std::rc::Rc; +use string_cache::{Atom, Namespace}; +use style::PropertyDeclarationBlock; +use url::Url; + /// A trait to allow tracing (only) DOM objects. pub trait JSTraceable { @@ -200,6 +200,7 @@ no_jsmanaged_fields!(uint, u8, u16, u32, u64) no_jsmanaged_fields!(int, i8, i16, i32, i64) no_jsmanaged_fields!(Sender) no_jsmanaged_fields!(Receiver) +no_jsmanaged_fields!(Rect) no_jsmanaged_fields!(ImageCacheTask, ScriptControlChan) no_jsmanaged_fields!(Atom, Namespace, Timer) no_jsmanaged_fields!(PropertyDeclarationBlock) diff --git a/servo/components/script/layout_interface.rs b/servo/components/script/layout_interface.rs index 2cf64f961fbf..95b422e18eb9 100644 --- a/servo/components/script/layout_interface.rs +++ b/servo/components/script/layout_interface.rs @@ -109,6 +109,8 @@ pub struct Reflow { pub id: uint, /// The type of query if any to perform during this reflow. pub query_type: ReflowQueryType, + /// A clipping rectangle for the page, an enlarged rectangle containing the viewport. + pub page_clip_rect: Rect, } /// Encapsulates a channel to the layout task. diff --git a/servo/components/script/page.rs b/servo/components/script/page.rs index 4e87ebe9050e..b150804b5d33 100644 --- a/servo/components/script/page.rs +++ b/servo/components/script/page.rs @@ -19,19 +19,21 @@ use layout_interface::{ }; use script_traits::{UntrustedNodeAddress, ScriptControlChan}; -use geom::{Point2D, Rect}; +use geom::{Point2D, Rect, Size2D}; use js::rust::Cx; use servo_msg::compositor_msg::PerformingLayout; use servo_msg::compositor_msg::ScriptListener; use servo_msg::constellation_msg::{ConstellationChan, WindowSizeData}; use servo_msg::constellation_msg::{PipelineId, SubpageId}; use servo_net::resource_task::ResourceTask; -use servo_util::geometry::Au; +use servo_util::geometry::{Au, MAX_RECT}; +use servo_util::geometry; use servo_util::str::DOMString; use servo_util::smallvec::{SmallVec1, SmallVec}; use std::cell::Cell; use std::comm::{channel, Receiver, Empty, Disconnected}; use std::mem::replace; +use std::num::abs; use std::rc::Rc; use url::Url; @@ -98,6 +100,10 @@ pub struct Page { /// Number of unnecessary potential reflows that were skipped since the last reflow pub avoided_reflows: Cell, + + /// An enlarged rectangle around the page contents visible in the viewport, used + /// to prevent creating display list items for content that is far away from the viewport. + pub page_clip_rect: Cell>, } pub struct PageIterator { @@ -164,6 +170,7 @@ impl Page { damaged: Cell::new(false), pending_reflows: Cell::new(0), avoided_reflows: Cell::new(0), + page_clip_rect: Cell::new(MAX_RECT), } } @@ -236,6 +243,49 @@ impl Page { } None } + + fn should_move_clip_rect(&self, clip_rect: Rect, new_viewport: Rect) -> bool{ + let clip_rect = Rect(Point2D(geometry::to_frac_px(clip_rect.origin.x) as f32, + geometry::to_frac_px(clip_rect.origin.y) as f32), + Size2D(geometry::to_frac_px(clip_rect.size.width) as f32, + geometry::to_frac_px(clip_rect.size.height) as f32)); + + // We only need to move the clip rect if the viewport is getting near the edge of + // our preexisting clip rect. We use half of the size of the viewport as a heuristic + // for "close." + static VIEWPORT_SCROLL_MARGIN_SIZE: f32 = 0.5; + let viewport_scroll_margin = new_viewport.size * VIEWPORT_SCROLL_MARGIN_SIZE; + + abs(clip_rect.origin.x - new_viewport.origin.x) <= viewport_scroll_margin.width || + abs(clip_rect.max_x() - new_viewport.max_x()) <= viewport_scroll_margin.width || + abs(clip_rect.origin.y - new_viewport.origin.y) <= viewport_scroll_margin.height || + abs(clip_rect.max_y() - new_viewport.max_y()) <= viewport_scroll_margin.height + } + + pub fn set_page_clip_rect_with_new_viewport(&self, viewport: Rect) -> bool { + // We use a clipping rectangle that is five times the size of the of the viewport, + // so that we don't collect display list items for areas too far outside the viewport, + // but also don't trigger reflows every time the viewport changes. + static VIEWPORT_EXPANSION: f32 = 2.0; // 2 lengths on each side plus original length is 5 total. + let proposed_clip_rect = geometry::f32_rect_to_au_rect( + viewport.inflate(viewport.size.width * VIEWPORT_EXPANSION, + viewport.size.height * VIEWPORT_EXPANSION)); + let clip_rect = self.page_clip_rect.get(); + if proposed_clip_rect == clip_rect { + return false; + } + + let had_clip_rect = clip_rect != MAX_RECT; + if had_clip_rect && !self.should_move_clip_rect(clip_rect, viewport) { + return false; + } + + self.page_clip_rect.set(proposed_clip_rect); + + // If we didn't have a clip rect, the previous display doesn't need rebuilding + // because it was built for infinite clip (MAX_RECT). + had_clip_rect + } } impl Iterator> for PageIterator { @@ -375,6 +425,7 @@ impl Page { script_join_chan: join_chan, id: last_reflow_id.get(), query_type: query_type, + page_clip_rect: self.page_clip_rect.get(), }; let LayoutChan(ref chan) = self.layout_chan; diff --git a/servo/components/script/script_task.rs b/servo/components/script/script_task.rs index b234efef6c3d..5630a9763b9a 100644 --- a/servo/components/script/script_task.rs +++ b/servo/components/script/script_task.rs @@ -40,9 +40,9 @@ use devtools_traits::{DevtoolScriptControlMsg, EvaluateJS, EvaluateJSReply, GetD use devtools_traits::{GetChildren, GetLayout}; use script_traits::{CompositorEvent, ResizeEvent, ReflowEvent, ClickEvent, MouseDownEvent}; use script_traits::{MouseMoveEvent, MouseUpEvent, ConstellationControlMsg, ScriptTaskFactory}; -use script_traits::{ResizeMsg, AttachLayoutMsg, LoadMsg, SendEventMsg, ResizeInactiveMsg}; -use script_traits::{ExitPipelineMsg, NewLayoutInfo, OpaqueScriptLayoutChannel, ScriptControlChan}; -use script_traits::{ReflowCompleteMsg, UntrustedNodeAddress}; +use script_traits::{ResizeMsg, AttachLayoutMsg, LoadMsg, ViewportMsg, SendEventMsg}; +use script_traits::{ResizeInactiveMsg, ExitPipelineMsg, NewLayoutInfo, OpaqueScriptLayoutChannel}; +use script_traits::{ScriptControlChan, ReflowCompleteMsg, UntrustedNodeAddress}; use servo_msg::compositor_msg::{FinishedLoading, LayerId, Loading}; use servo_msg::compositor_msg::{ScriptListener}; use servo_msg::constellation_msg::{ConstellationChan, LoadCompleteMsg, LoadUrlMsg, NavigationDirection}; @@ -495,6 +495,13 @@ impl ScriptTask { pending.push_all_move(node_addresses); needs_reflow.insert(id); } + FromConstellation(ViewportMsg(id, rect)) => { + let mut page = self.page.borrow_mut(); + let inner_page = page.find(id).expect("Page rect message sent to nonexistent pipeline"); + if inner_page.set_page_clip_rect_with_new_viewport(rect) { + needs_reflow.insert(id); + } + } _ => { sequential.push(event); } @@ -530,6 +537,7 @@ impl ScriptTask { FromConstellation(ReflowCompleteMsg(id, reflow_id)) => self.handle_reflow_complete_msg(id, reflow_id), FromConstellation(ResizeInactiveMsg(id, new_size)) => self.handle_resize_inactive_msg(id, new_size), FromConstellation(ExitPipelineMsg(id)) => if self.handle_exit_pipeline_msg(id) { return false }, + FromConstellation(ViewportMsg(..)) => fail!("should have handled ViewportMsg already"), FromScript(ExitWindowMsg(id)) => self.handle_exit_window_msg(id), FromConstellation(ResizeMsg(..)) => fail!("should have handled ResizeMsg already"), FromScript(XHRProgressMsg(addr, progress)) => XMLHttpRequest::handle_progress(addr, progress), diff --git a/servo/components/script_traits/lib.rs b/servo/components/script_traits/lib.rs index 0caf54ba341b..51365f0515f3 100644 --- a/servo/components/script_traits/lib.rs +++ b/servo/components/script_traits/lib.rs @@ -32,6 +32,7 @@ use servo_util::smallvec::SmallVec1; use std::any::Any; use geom::point::Point2D; +use geom::rect::Rect; use serialize::{Encodable, Encoder}; @@ -62,6 +63,7 @@ pub enum ConstellationControlMsg { SendEventMsg(PipelineId, CompositorEvent), /// Notifies script that reflow is finished. ReflowCompleteMsg(PipelineId, uint), + ViewportMsg(PipelineId, Rect), } /// Events from the compositor that the script task needs to know about diff --git a/servo/ports/cef/Cargo.lock b/servo/ports/cef/Cargo.lock index 172e07a0c83e..e2afcde210d4 100644 --- a/servo/ports/cef/Cargo.lock +++ b/servo/ports/cef/Cargo.lock @@ -184,7 +184,7 @@ source = "git+https://github.com/servo/libfreetype2#5b6499164106f094937565595c7b [[package]] name = "geom" version = "0.1.0" -source = "git+https://github.com/servo/rust-geom#b001a76e907befaae1d0d6dd259418a22092da86" +source = "git+https://github.com/servo/rust-geom#e5e74911ac6d3201009879b72499d6c681302611" [[package]] name = "gfx"