mirror of
https://github.com/servo/servo.git
synced 2025-08-03 04:30:10 +01:00
compositing: Implement display ports and avoid creating display lists
for items outside it. This improves Servo's performance on large pages.
This commit is contained in:
parent
acb9824229
commit
6a197719b3
26 changed files with 340 additions and 66 deletions
|
@ -14,7 +14,7 @@ use windowing::{MouseWindowEvent, WindowEvent, WindowMethods, WindowNavigateMsg}
|
|||
use geom::point::{Point2D, TypedPoint2D};
|
||||
use geom::rect::{Rect, TypedRect};
|
||||
use geom::scale_factor::ScaleFactor;
|
||||
use geom::size::TypedSize2D;
|
||||
use geom::size::{Size2D, TypedSize2D};
|
||||
use gfx::color;
|
||||
use gfx::paint_task::Msg as PaintMsg;
|
||||
use gfx::paint_task::PaintRequest;
|
||||
|
@ -25,6 +25,7 @@ use layers::layers::{BufferRequest, Layer, LayerBuffer, LayerBufferSet};
|
|||
use layers::rendergl::RenderContext;
|
||||
use layers::rendergl;
|
||||
use layers::scene::Scene;
|
||||
use layout_traits::{LayoutControlChan, LayoutControlMsg};
|
||||
use msg::compositor_msg::{Epoch, FrameTreeId, LayerId};
|
||||
use msg::compositor_msg::{LayerProperties, ScrollPolicy};
|
||||
use msg::constellation_msg::AnimationState;
|
||||
|
@ -45,7 +46,7 @@ use std::sync::mpsc::Sender;
|
|||
use style::viewport::ViewportConstraints;
|
||||
use time::{precise_time_ns, precise_time_s};
|
||||
use url::Url;
|
||||
use util::geometry::{PagePx, ScreenPx, ViewportPx};
|
||||
use util::geometry::{Au, PagePx, ScreenPx, ViewportPx};
|
||||
use util::opts;
|
||||
|
||||
/// Holds the state when running reftests that determines when it is
|
||||
|
@ -434,7 +435,8 @@ impl<Window: WindowMethods> IOCompositor<Window> {
|
|||
self.remove_pipeline_root_layer(pipeline_id);
|
||||
}
|
||||
|
||||
(Msg::ViewportConstrained(pipeline_id, constraints), ShutdownState::NotShuttingDown) => {
|
||||
(Msg::ViewportConstrained(pipeline_id, constraints),
|
||||
ShutdownState::NotShuttingDown) => {
|
||||
self.constrain_viewport(pipeline_id, constraints);
|
||||
}
|
||||
|
||||
|
@ -701,7 +703,7 @@ impl<Window: WindowMethods> IOCompositor<Window> {
|
|||
panic!("Compositor: Tried to scroll to fragment with unknown layer.");
|
||||
}
|
||||
|
||||
self.start_scrolling_timer_if_necessary();
|
||||
self.perform_updates_after_scroll();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -937,9 +939,57 @@ impl<Window: WindowMethods> IOCompositor<Window> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Computes new display ports for each layer, taking the scroll position into account, and
|
||||
/// sends them to layout as necessary. This ultimately triggers a rerender of the content.
|
||||
fn send_updated_display_ports_to_layout(&mut self) {
|
||||
fn process_layer(layer: &Layer<CompositorData>,
|
||||
window_size: &TypedSize2D<LayerPixel, f32>,
|
||||
new_display_ports: &mut HashMap<PipelineId, Vec<(LayerId, Rect<Au>)>>) {
|
||||
let visible_rect =
|
||||
Rect(Point2D::zero(), *window_size).translate(&-*layer.content_offset.borrow())
|
||||
.intersection(&*layer.bounds.borrow())
|
||||
.unwrap_or(Rect::zero())
|
||||
.to_untyped();
|
||||
let visible_rect = Rect(Point2D(Au::from_f32_px(visible_rect.origin.x),
|
||||
Au::from_f32_px(visible_rect.origin.y)),
|
||||
Size2D(Au::from_f32_px(visible_rect.size.width),
|
||||
Au::from_f32_px(visible_rect.size.height)));
|
||||
|
||||
let extra_layer_data = layer.extra_data.borrow();
|
||||
if !new_display_ports.contains_key(&extra_layer_data.pipeline_id) {
|
||||
new_display_ports.insert(extra_layer_data.pipeline_id, Vec::new());
|
||||
}
|
||||
new_display_ports.get_mut(&extra_layer_data.pipeline_id)
|
||||
.unwrap()
|
||||
.push((extra_layer_data.id, visible_rect));
|
||||
|
||||
for kid in layer.children.borrow().iter() {
|
||||
process_layer(&*kid, window_size, new_display_ports)
|
||||
}
|
||||
}
|
||||
|
||||
let dppx = self.page_zoom * self.device_pixels_per_screen_px();
|
||||
let window_size = self.window_size.as_f32() / dppx * ScaleFactor::new(1.0);
|
||||
let mut new_visible_rects = HashMap::new();
|
||||
if let Some(ref layer) = self.scene.root {
|
||||
process_layer(&**layer, &window_size, &mut new_visible_rects)
|
||||
}
|
||||
|
||||
for (pipeline_id, new_visible_rects) in new_visible_rects.iter() {
|
||||
if let Some(pipeline_details) = self.pipeline_details.get(&pipeline_id) {
|
||||
if let Some(ref pipeline) = pipeline_details.pipeline {
|
||||
let LayoutControlChan(ref sender) = pipeline.layout_chan;
|
||||
sender.send(LayoutControlMsg::SetVisibleRects((*new_visible_rects).clone()))
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs buffer requests and starts the scrolling timer or schedules a recomposite as
|
||||
/// necessary.
|
||||
fn perform_updates_after_scroll(&mut self) {
|
||||
self.send_updated_display_ports_to_layout();
|
||||
if self.send_buffer_requests_for_all_layers() {
|
||||
self.start_scrolling_timer_if_necessary();
|
||||
} else {
|
||||
|
|
|
@ -52,6 +52,7 @@ pub struct Pipeline {
|
|||
pub struct CompositionPipeline {
|
||||
pub id: PipelineId,
|
||||
pub script_chan: ScriptControlChan,
|
||||
pub layout_chan: LayoutControlChan,
|
||||
pub paint_chan: PaintChan,
|
||||
}
|
||||
|
||||
|
@ -245,6 +246,7 @@ impl Pipeline {
|
|||
CompositionPipeline {
|
||||
id: self.id.clone(),
|
||||
script_chan: self.script_chan.clone(),
|
||||
layout_chan: self.layout_chan.clone(),
|
||||
paint_chan: self.paint_chan.clone(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -245,7 +245,10 @@ impl<C> PaintTask<C> where C: PaintListener + Send + 'static {
|
|||
}
|
||||
|
||||
debug!("PaintTask: returning surfaces");
|
||||
self.compositor.assign_painted_buffers(self.id, self.current_epoch.unwrap(), replies, frame_tree_id);
|
||||
self.compositor.assign_painted_buffers(self.id,
|
||||
self.current_epoch.unwrap(),
|
||||
replies,
|
||||
frame_tree_id);
|
||||
}
|
||||
Msg::UnusedBuffer(unused_buffers) => {
|
||||
debug!("PaintTask {:?}: Received {} unused buffers", self.id, unused_buffers.len());
|
||||
|
|
|
@ -43,6 +43,7 @@ use flow::{CLEARS_LEFT, CLEARS_RIGHT};
|
|||
use fragment::{CoordinateSystem, Fragment, FragmentBorderBoxIterator, SpecificFragmentInfo};
|
||||
use incremental::{REFLOW, REFLOW_OUT_OF_FLOW};
|
||||
use layout_debug;
|
||||
use layout_task::DISPLAY_PORT_SIZE_FACTOR;
|
||||
use model::{IntrinsicISizes, MarginCollapseInfo};
|
||||
use model::{MaybeAuto, CollapsibleMargins, specified, specified_or_none};
|
||||
use wrapper::ThreadSafeLayoutNode;
|
||||
|
@ -59,7 +60,7 @@ use style::computed_values::{position, text_align};
|
|||
use style::properties::ComputedValues;
|
||||
use style::values::computed::{LengthOrPercentage, LengthOrPercentageOrAuto};
|
||||
use style::values::computed::{LengthOrPercentageOrNone};
|
||||
use util::geometry::{Au, MAX_AU};
|
||||
use util::geometry::{Au, MAX_AU, MAX_RECT};
|
||||
use util::logical_geometry::{LogicalPoint, LogicalRect, LogicalSize, WritingMode};
|
||||
use util::opts;
|
||||
|
||||
|
@ -377,7 +378,9 @@ impl Iterator for CandidateBSizeIterator {
|
|||
Some(max_block_size) if self.candidate_value > max_block_size => {
|
||||
CandidateBSizeIteratorStatus::TryingMax
|
||||
}
|
||||
_ if self.candidate_value < self.min_block_size => CandidateBSizeIteratorStatus::TryingMin,
|
||||
_ if self.candidate_value < self.min_block_size => {
|
||||
CandidateBSizeIteratorStatus::TryingMin
|
||||
}
|
||||
_ => CandidateBSizeIteratorStatus::Found,
|
||||
}
|
||||
}
|
||||
|
@ -718,8 +721,11 @@ impl BlockFlow {
|
|||
return
|
||||
}
|
||||
|
||||
let (block_start_margin_value, block_end_margin_value) = match self.base.collapsible_margins {
|
||||
CollapsibleMargins::CollapseThrough(_) => panic!("Margins unexpectedly collapsed through root flow."),
|
||||
let (block_start_margin_value, block_end_margin_value) =
|
||||
match self.base.collapsible_margins {
|
||||
CollapsibleMargins::CollapseThrough(_) => {
|
||||
panic!("Margins unexpectedly collapsed through root flow.")
|
||||
}
|
||||
CollapsibleMargins::Collapse(block_start_margin, block_end_margin) => {
|
||||
(block_start_margin.collapse(), block_end_margin.collapse())
|
||||
}
|
||||
|
@ -757,7 +763,8 @@ impl BlockFlow {
|
|||
pub fn assign_block_size_block_base<'a>(&mut self,
|
||||
layout_context: &'a LayoutContext<'a>,
|
||||
margins_may_collapse: MarginsMayCollapseFlag) {
|
||||
let _scope = layout_debug_scope!("assign_block_size_block_base {:x}", self.base.debug_id());
|
||||
let _scope = layout_debug_scope!("assign_block_size_block_base {:x}",
|
||||
self.base.debug_id());
|
||||
|
||||
if self.base.restyle_damage.contains(REFLOW) {
|
||||
// Our current border-box position.
|
||||
|
@ -1661,13 +1668,14 @@ impl Flow for BlockFlow {
|
|||
}
|
||||
}
|
||||
|
||||
fn compute_absolute_position(&mut self) {
|
||||
fn compute_absolute_position(&mut self, layout_context: &LayoutContext) {
|
||||
// FIXME (mbrubeck): Get the real container size, taking the container writing mode into
|
||||
// account. Must handle vertical writing modes.
|
||||
let container_size = Size2D(self.base.block_container_inline_size, Au(0));
|
||||
|
||||
if self.is_root() {
|
||||
self.base.clip = ClippingRegion::max()
|
||||
self.base.clip = ClippingRegion::max();
|
||||
self.base.stacking_relative_position_of_display_port = MAX_RECT;
|
||||
}
|
||||
|
||||
if self.base.flags.contains(IS_ABSOLUTELY_POSITIONED) {
|
||||
|
@ -1761,7 +1769,8 @@ impl Flow for BlockFlow {
|
|||
let relative_offset = relative_offset.to_physical(self.base.writing_mode);
|
||||
let origin_for_children;
|
||||
let clip_in_child_coordinate_system;
|
||||
if self.fragment.establishes_stacking_context() {
|
||||
let is_stacking_context = self.fragment.establishes_stacking_context();
|
||||
if is_stacking_context {
|
||||
// We establish a stacking context, so the position of our children is vertically
|
||||
// correct, but has to be adjusted to accommodate horizontal margins. (Note the
|
||||
// calculation involving `position` below and recall that inline-direction flow
|
||||
|
@ -1771,11 +1780,31 @@ impl Flow for BlockFlow {
|
|||
let margin = self.fragment.margin.to_physical(self.base.writing_mode);
|
||||
origin_for_children = Point2D(-margin.left, Au(0)) + relative_offset;
|
||||
clip_in_child_coordinate_system =
|
||||
self.base.clip.translate(&-self.base.stacking_relative_position)
|
||||
self.base.clip.translate(&-self.base.stacking_relative_position);
|
||||
} else {
|
||||
origin_for_children = self.base.stacking_relative_position + relative_offset;
|
||||
clip_in_child_coordinate_system = self.base.clip.clone()
|
||||
clip_in_child_coordinate_system = self.base.clip.clone();
|
||||
}
|
||||
|
||||
let stacking_relative_position_of_display_port_for_children =
|
||||
if (is_stacking_context && self.will_get_layer()) || self.is_root() {
|
||||
let visible_rect =
|
||||
match layout_context.shared.visible_rects.get(&self.layer_id(0)) {
|
||||
Some(visible_rect) => *visible_rect,
|
||||
None => Rect(Point2D::zero(), layout_context.shared.screen_size),
|
||||
};
|
||||
|
||||
let screen_size = layout_context.shared.screen_size;
|
||||
visible_rect.inflate(screen_size.width * DISPLAY_PORT_SIZE_FACTOR,
|
||||
screen_size.height * DISPLAY_PORT_SIZE_FACTOR)
|
||||
} else if is_stacking_context {
|
||||
self.base
|
||||
.stacking_relative_position_of_display_port
|
||||
.translate(&-self.base.stacking_relative_position)
|
||||
} else {
|
||||
self.base.stacking_relative_position_of_display_port
|
||||
};
|
||||
|
||||
let stacking_relative_border_box =
|
||||
self.fragment
|
||||
.stacking_relative_border_box(&self.base.stacking_relative_position,
|
||||
|
@ -1820,7 +1849,9 @@ impl Flow for BlockFlow {
|
|||
}
|
||||
|
||||
flow::mut_base(kid).absolute_position_info = absolute_position_info_for_children;
|
||||
flow::mut_base(kid).clip = clip.clone()
|
||||
flow::mut_base(kid).clip = clip.clone();
|
||||
flow::mut_base(kid).stacking_relative_position_of_display_port =
|
||||
stacking_relative_position_of_display_port_for_children;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,17 +12,21 @@ use geom::{Rect, Size2D};
|
|||
use gfx::display_list::OpaqueNode;
|
||||
use gfx::font_cache_task::FontCacheTask;
|
||||
use gfx::font_context::FontContext;
|
||||
use msg::compositor_msg::LayerId;
|
||||
use msg::constellation_msg::ConstellationChan;
|
||||
use net_traits::image::base::Image;
|
||||
use net_traits::image_cache_task::{ImageCacheChan, ImageCacheTask, ImageState};
|
||||
use script::layout_interface::{Animation, LayoutChan, ReflowGoal};
|
||||
use std::boxed;
|
||||
use std::cell::Cell;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::hash_state::DefaultState;
|
||||
use std::ptr;
|
||||
use std::sync::Arc;
|
||||
use std::sync::mpsc::{channel, Sender};
|
||||
use style::selector_matching::Stylist;
|
||||
use url::Url;
|
||||
use util::fnv::FnvHasher;
|
||||
use util::geometry::Au;
|
||||
use util::opts;
|
||||
|
||||
|
@ -99,6 +103,9 @@ pub struct SharedLayoutContext {
|
|||
/// sent.
|
||||
pub new_animations_sender: Sender<Animation>,
|
||||
|
||||
/// The visible rects for each layer, as reported to us by the compositor.
|
||||
pub visible_rects: Arc<HashMap<LayerId, Rect<Au>, DefaultState<FnvHasher>>>,
|
||||
|
||||
/// Why is this reflow occurring
|
||||
pub goal: ReflowGoal,
|
||||
}
|
||||
|
|
|
@ -176,6 +176,8 @@ pub trait FragmentDisplayListBuilding {
|
|||
/// * `relative_containing_block_size`: The size of the containing block that
|
||||
/// `position: relative` makes use of.
|
||||
/// * `clip`: The region to clip the display items to.
|
||||
/// * `stacking_relative_display_port`: The position and size of the display port with respect
|
||||
/// to the nearest ancestor stacking context.
|
||||
fn build_display_list(&mut self,
|
||||
display_list: &mut DisplayList,
|
||||
layout_context: &LayoutContext,
|
||||
|
@ -184,7 +186,8 @@ pub trait FragmentDisplayListBuilding {
|
|||
relative_containing_block_mode: WritingMode,
|
||||
border_painting_mode: BorderPaintingMode,
|
||||
background_and_border_level: BackgroundAndBorderLevel,
|
||||
clip: &ClippingRegion);
|
||||
clip: &ClippingRegion,
|
||||
stacking_relative_display_port: &Rect<Au>);
|
||||
|
||||
/// Sends the size and position of this iframe fragment to the constellation. This is out of
|
||||
/// line to guide inlining.
|
||||
|
@ -866,7 +869,8 @@ impl FragmentDisplayListBuilding for Fragment {
|
|||
relative_containing_block_mode: WritingMode,
|
||||
border_painting_mode: BorderPaintingMode,
|
||||
background_and_border_level: BackgroundAndBorderLevel,
|
||||
clip: &ClippingRegion) {
|
||||
clip: &ClippingRegion,
|
||||
stacking_relative_display_port: &Rect<Au>) {
|
||||
if self.style().get_inheritedbox().visibility != visibility::T::visible {
|
||||
return
|
||||
}
|
||||
|
@ -888,6 +892,11 @@ impl FragmentDisplayListBuilding for Fragment {
|
|||
stacking_relative_flow_origin,
|
||||
self);
|
||||
|
||||
if !stacking_relative_border_box.intersects(stacking_relative_display_port) {
|
||||
debug!("Fragment::build_display_list: outside display port");
|
||||
return
|
||||
}
|
||||
|
||||
if !stacking_relative_border_box.intersects(&layout_context.shared.dirty) {
|
||||
debug!("Fragment::build_display_list: Did not intersect...");
|
||||
return
|
||||
|
@ -1076,7 +1085,8 @@ impl FragmentDisplayListBuilding for Fragment {
|
|||
let (sender, receiver) = channel::<Vec<u8>>();
|
||||
let canvas_data = match canvas_fragment_info.renderer {
|
||||
Some(ref renderer) => {
|
||||
renderer.lock().unwrap().send(CanvasMsg::Common(CanvasCommonMsg::SendPixelContents(sender))).unwrap();
|
||||
renderer.lock().unwrap().send(CanvasMsg::Common(
|
||||
CanvasCommonMsg::SendPixelContents(sender))).unwrap();
|
||||
receiver.recv().unwrap()
|
||||
},
|
||||
None => repeat(0xFFu8).take(width * height * 4).collect(),
|
||||
|
@ -1364,6 +1374,7 @@ pub trait BlockFlowDisplayListBuilding {
|
|||
display_list: Box<DisplayList>,
|
||||
layout_context: &LayoutContext,
|
||||
border_painting_mode: BorderPaintingMode);
|
||||
fn will_get_layer(&self) -> bool;
|
||||
}
|
||||
|
||||
impl BlockFlowDisplayListBuilding for BlockFlow {
|
||||
|
@ -1386,7 +1397,8 @@ impl BlockFlowDisplayListBuilding for BlockFlow {
|
|||
self.base.absolute_position_info.relative_containing_block_mode,
|
||||
border_painting_mode,
|
||||
background_border_level,
|
||||
&clip);
|
||||
&clip,
|
||||
&self.base.stacking_relative_position_of_display_port);
|
||||
|
||||
// Add children.
|
||||
for kid in self.base.children.iter_mut() {
|
||||
|
@ -1422,6 +1434,11 @@ impl BlockFlowDisplayListBuilding for BlockFlow {
|
|||
}
|
||||
}
|
||||
|
||||
fn will_get_layer(&self) -> bool {
|
||||
self.base.absolute_position_info.layers_needed_for_positioned_flows ||
|
||||
self.base.flags.contains(NEEDS_LAYER)
|
||||
}
|
||||
|
||||
fn build_display_list_for_absolutely_positioned_block(
|
||||
&mut self,
|
||||
mut display_list: Box<DisplayList>,
|
||||
|
@ -1432,8 +1449,7 @@ impl BlockFlowDisplayListBuilding for BlockFlow {
|
|||
border_painting_mode,
|
||||
BackgroundAndBorderLevel::RootOfStackingContext);
|
||||
|
||||
if !self.base.absolute_position_info.layers_needed_for_positioned_flows &&
|
||||
!self.base.flags.contains(NEEDS_LAYER) {
|
||||
if !self.will_get_layer() {
|
||||
// We didn't need a layer.
|
||||
self.base.display_list_building_result =
|
||||
DisplayListBuildingResult::StackingContext(self.fragment.create_stacking_context(
|
||||
|
@ -1524,7 +1540,8 @@ impl InlineFlowDisplayListBuilding for InlineFlow {
|
|||
.relative_containing_block_mode,
|
||||
BorderPaintingMode::Separate,
|
||||
BackgroundAndBorderLevel::Content,
|
||||
&self.base.clip);
|
||||
&self.base.clip,
|
||||
&self.base.stacking_relative_position_of_display_port);
|
||||
|
||||
has_stacking_context = fragment.establishes_stacking_context();
|
||||
match fragment.specific {
|
||||
|
@ -1597,7 +1614,10 @@ impl ListItemFlowDisplayListBuilding for ListItemFlow {
|
|||
.relative_containing_block_mode,
|
||||
BorderPaintingMode::Separate,
|
||||
BackgroundAndBorderLevel::Content,
|
||||
&self.block_flow.base.clip);
|
||||
&self.block_flow.base.clip,
|
||||
&self.block_flow
|
||||
.base
|
||||
.stacking_relative_position_of_display_port);
|
||||
}
|
||||
|
||||
// Draw the rest of the block.
|
||||
|
|
|
@ -271,7 +271,7 @@ pub trait Flow: fmt::Debug + Sync {
|
|||
}
|
||||
|
||||
/// Phase 4 of reflow: computes absolute positions.
|
||||
fn compute_absolute_position(&mut self) {
|
||||
fn compute_absolute_position(&mut self, _: &LayoutContext) {
|
||||
// The default implementation is a no-op.
|
||||
}
|
||||
|
||||
|
@ -859,6 +859,12 @@ pub struct BaseFlow {
|
|||
/// The clipping region for this flow and its descendants, in layer coordinates.
|
||||
pub clip: ClippingRegion,
|
||||
|
||||
/// The stacking-relative position of the display port.
|
||||
///
|
||||
/// FIXME(pcwalton): This might be faster as an Arc, since this varies only
|
||||
/// per-stacking-context.
|
||||
pub stacking_relative_position_of_display_port: Rect<Au>,
|
||||
|
||||
/// The results of display list building for this flow.
|
||||
pub display_list_building_result: DisplayListBuildingResult,
|
||||
|
||||
|
@ -909,10 +915,18 @@ impl Encodable for BaseFlow {
|
|||
FlowClass::Block => c.as_immutable_block().encode(e),
|
||||
FlowClass::Inline => c.as_immutable_inline().encode(e),
|
||||
FlowClass::Table => c.as_immutable_table().encode(e),
|
||||
FlowClass::TableWrapper => c.as_immutable_table_wrapper().encode(e),
|
||||
FlowClass::TableRowGroup => c.as_immutable_table_rowgroup().encode(e),
|
||||
FlowClass::TableRow => c.as_immutable_table_row().encode(e),
|
||||
FlowClass::TableCell => c.as_immutable_table_cell().encode(e),
|
||||
FlowClass::TableWrapper => {
|
||||
c.as_immutable_table_wrapper().encode(e)
|
||||
}
|
||||
FlowClass::TableRowGroup => {
|
||||
c.as_immutable_table_rowgroup().encode(e)
|
||||
}
|
||||
FlowClass::TableRow => {
|
||||
c.as_immutable_table_row().encode(e)
|
||||
}
|
||||
FlowClass::TableCell => {
|
||||
c.as_immutable_table_cell().encode(e)
|
||||
}
|
||||
_ => { Ok(()) } // TODO: Support captions
|
||||
}
|
||||
})
|
||||
|
@ -1024,6 +1038,7 @@ impl BaseFlow {
|
|||
display_list_building_result: DisplayListBuildingResult::None,
|
||||
absolute_position_info: AbsolutePositionInfo::new(writing_mode),
|
||||
clip: ClippingRegion::max(),
|
||||
stacking_relative_position_of_display_port: Rect::zero(),
|
||||
flags: flags,
|
||||
writing_mode: writing_mode,
|
||||
thread_id: 0,
|
||||
|
|
|
@ -1464,7 +1464,7 @@ impl Flow for InlineFlow {
|
|||
self.base.restyle_damage.remove(REFLOW_OUT_OF_FLOW | REFLOW);
|
||||
}
|
||||
|
||||
fn compute_absolute_position(&mut self) {
|
||||
fn compute_absolute_position(&mut self, _: &LayoutContext) {
|
||||
// First, gather up the positions of all the containing blocks (if any).
|
||||
let mut containing_block_positions = Vec::new();
|
||||
let container_size = Size2D(self.base.block_container_inline_size, Au(0));
|
||||
|
@ -1504,14 +1504,18 @@ impl Flow for InlineFlow {
|
|||
let block_flow = info.flow_ref.as_block();
|
||||
block_flow.base.absolute_position_info = self.base.absolute_position_info;
|
||||
block_flow.base.stacking_relative_position =
|
||||
stacking_relative_border_box.origin
|
||||
stacking_relative_border_box.origin;
|
||||
block_flow.base.stacking_relative_position_of_display_port =
|
||||
self.base.stacking_relative_position_of_display_port;
|
||||
}
|
||||
SpecificFragmentInfo::InlineAbsoluteHypothetical(ref mut info) => {
|
||||
flow::mut_base(&mut *info.flow_ref).clip = clip;
|
||||
let block_flow = info.flow_ref.as_block();
|
||||
block_flow.base.absolute_position_info = self.base.absolute_position_info;
|
||||
block_flow.base.stacking_relative_position =
|
||||
stacking_relative_border_box.origin
|
||||
stacking_relative_border_box.origin;
|
||||
block_flow.base.stacking_relative_position_of_display_port =
|
||||
self.base.stacking_relative_position_of_display_port;
|
||||
|
||||
}
|
||||
SpecificFragmentInfo::InlineAbsolute(ref mut info) => {
|
||||
|
@ -1528,7 +1532,9 @@ impl Flow for InlineFlow {
|
|||
stacking_relative_position + *padding_box_origin;
|
||||
|
||||
block_flow.base.stacking_relative_position =
|
||||
stacking_relative_border_box.origin
|
||||
stacking_relative_border_box.origin;
|
||||
block_flow.base.stacking_relative_position_of_display_port =
|
||||
self.base.stacking_relative_position_of_display_port;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
|
|
@ -11,12 +11,12 @@ use animation;
|
|||
use construct::ConstructionResult;
|
||||
use context::{SharedLayoutContext, SharedLayoutContextWrapper};
|
||||
use css::node_style::StyledNode;
|
||||
use data::{LayoutDataAccess, LayoutDataWrapper};
|
||||
use display_list_builder::ToGfxColor;
|
||||
use flow::{self, Flow, ImmutableFlowUtils, MutableFlowUtils, MutableOwnedFlowUtils};
|
||||
use flow_ref::FlowRef;
|
||||
use fragment::{Fragment, FragmentBorderBoxIterator};
|
||||
use incremental::{LayoutDamageComputation, REFLOW, REFLOW_ENTIRE_DOCUMENT, REPAINT};
|
||||
use data::{LayoutDataAccess, LayoutDataWrapper};
|
||||
use layout_debug;
|
||||
use opaque_node::OpaqueNodeMethods;
|
||||
use parallel::{self, UnsafeFlow};
|
||||
|
@ -39,7 +39,7 @@ use gfx::paint_task::Msg as PaintMsg;
|
|||
use gfx::paint_task::{PaintChan, PaintLayer};
|
||||
use layout_traits::{LayoutControlMsg, LayoutTaskFactory};
|
||||
use log;
|
||||
use msg::compositor_msg::{Epoch, ScrollPolicy};
|
||||
use msg::compositor_msg::{Epoch, LayerId, ScrollPolicy};
|
||||
use msg::constellation_msg::Msg as ConstellationMsg;
|
||||
use msg::constellation_msg::{ConstellationChan, Failure, PipelineExitType, PipelineId};
|
||||
use profile_traits::mem::{self, Report, ReportsChan};
|
||||
|
@ -57,6 +57,8 @@ use script_traits::{ConstellationControlMsg, OpaqueScriptLayoutChannel};
|
|||
use script_traits::{ScriptControlChan, StylesheetLoadResponder};
|
||||
use std::borrow::ToOwned;
|
||||
use std::cell::Cell;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::hash_state::DefaultState;
|
||||
use std::mem::transmute;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::ptr;
|
||||
|
@ -69,6 +71,7 @@ use style::selector_matching::Stylist;
|
|||
use style::stylesheets::{Origin, Stylesheet, CSSRuleIteratorExt};
|
||||
use url::Url;
|
||||
use util::cursor::Cursor;
|
||||
use util::fnv::FnvHasher;
|
||||
use util::geometry::{Au, MAX_RECT};
|
||||
use util::logical_geometry::LogicalPoint;
|
||||
use util::mem::HeapSizeOf;
|
||||
|
@ -77,6 +80,12 @@ use util::task::spawn_named_with_send_on_failure;
|
|||
use util::task_state;
|
||||
use util::workqueue::WorkQueue;
|
||||
|
||||
/// The number of screens of data we're allowed to generate display lists for in each direction.
|
||||
pub const DISPLAY_PORT_SIZE_FACTOR: i32 = 8;
|
||||
|
||||
/// The number of screens we have to traverse before we decide to generate new display lists.
|
||||
const DISPLAY_PORT_THRESHOLD_SIZE_FACTOR: i32 = 4;
|
||||
|
||||
/// Mutable data belonging to the LayoutTask.
|
||||
///
|
||||
/// This needs to be protected by a mutex so we can do fast RPCs.
|
||||
|
@ -125,8 +134,12 @@ pub struct LayoutTaskData {
|
|||
/// sent.
|
||||
pub new_animations_sender: Sender<Animation>,
|
||||
|
||||
/// A counter for epoch messages
|
||||
/// A counter for epoch messages.
|
||||
epoch: Epoch,
|
||||
|
||||
/// The position and size of the visible rect for each layer. We do not build display lists
|
||||
/// for any areas more than `DISPLAY_PORT_SIZE_FACTOR` screens away from this area.
|
||||
pub visible_rects: Arc<HashMap<LayerId, Rect<Au>, DefaultState<FnvHasher>>>,
|
||||
}
|
||||
|
||||
/// Information needed by the layout task.
|
||||
|
@ -330,6 +343,7 @@ impl LayoutTask {
|
|||
content_box_response: Rect::zero(),
|
||||
content_boxes_response: Vec::new(),
|
||||
running_animations: Vec::new(),
|
||||
visible_rects: Arc::new(HashMap::with_hash_state(Default::default())),
|
||||
new_animations_receiver: new_animations_receiver,
|
||||
new_animations_sender: new_animations_sender,
|
||||
epoch: Epoch(0),
|
||||
|
@ -365,6 +379,7 @@ impl LayoutTask {
|
|||
url: (*url).clone(),
|
||||
reflow_root: reflow_root.map(|node| OpaqueNodeMethods::from_layout_node(node)),
|
||||
dirty: Rect::zero(),
|
||||
visible_rects: rw_data.visible_rects.clone(),
|
||||
generation: rw_data.generation,
|
||||
new_animations_sender: rw_data.new_animations_sender.clone(),
|
||||
goal: goal,
|
||||
|
@ -406,6 +421,10 @@ impl LayoutTask {
|
|||
match port_to_read {
|
||||
PortToRead::Pipeline => {
|
||||
match self.pipeline_port.recv().unwrap() {
|
||||
LayoutControlMsg::SetVisibleRects(new_visible_rects) => {
|
||||
self.handle_request_helper(Msg::SetVisibleRects(new_visible_rects),
|
||||
possibly_locked_rw_data)
|
||||
}
|
||||
LayoutControlMsg::TickAnimations => {
|
||||
self.handle_request_helper(Msg::TickAnimations, possibly_locked_rw_data)
|
||||
}
|
||||
|
@ -509,6 +528,9 @@ impl LayoutTask {
|
|||
|| self.handle_reflow(&*data, possibly_locked_rw_data));
|
||||
},
|
||||
Msg::TickAnimations => self.tick_all_animations(possibly_locked_rw_data),
|
||||
Msg::SetVisibleRects(new_visible_rects) => {
|
||||
self.set_visible_rects(new_visible_rects, possibly_locked_rw_data);
|
||||
}
|
||||
Msg::ReapLayoutData(dead_layout_data) => {
|
||||
unsafe {
|
||||
self.handle_reap_layout_data(dead_layout_data)
|
||||
|
@ -964,6 +986,64 @@ impl LayoutTask {
|
|||
chan.send(ConstellationControlMsg::ReflowComplete(self.id, data.id)).unwrap();
|
||||
}
|
||||
|
||||
fn set_visible_rects<'a>(&'a self,
|
||||
new_visible_rects: Vec<(LayerId, Rect<Au>)>,
|
||||
possibly_locked_rw_data: &mut Option<MutexGuard<'a, LayoutTaskData>>)
|
||||
-> bool {
|
||||
let mut rw_data = self.lock_rw_data(possibly_locked_rw_data);
|
||||
|
||||
// First, determine if we need to regenerate the display lists. This will happen if the
|
||||
// layers have moved more than `DISPLAY_PORT_THRESHOLD_SIZE_FACTOR` away from their last
|
||||
// positions.
|
||||
let mut must_regenerate_display_lists = false;
|
||||
let mut old_visible_rects = HashMap::with_hash_state(Default::default());
|
||||
let inflation_amount =
|
||||
Size2D(rw_data.screen_size.width * DISPLAY_PORT_THRESHOLD_SIZE_FACTOR,
|
||||
rw_data.screen_size.height * DISPLAY_PORT_THRESHOLD_SIZE_FACTOR);
|
||||
for &(ref layer_id, ref new_visible_rect) in new_visible_rects.iter() {
|
||||
match rw_data.visible_rects.get(layer_id) {
|
||||
None => {
|
||||
old_visible_rects.insert(*layer_id, *new_visible_rect);
|
||||
}
|
||||
Some(old_visible_rect) => {
|
||||
old_visible_rects.insert(*layer_id, *old_visible_rect);
|
||||
|
||||
if !old_visible_rect.inflate(inflation_amount.width, inflation_amount.height)
|
||||
.intersects(new_visible_rect) {
|
||||
must_regenerate_display_lists = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !must_regenerate_display_lists {
|
||||
// Update `visible_rects` in case there are new layers that were discovered.
|
||||
rw_data.visible_rects = Arc::new(old_visible_rects);
|
||||
return true
|
||||
}
|
||||
|
||||
debug!("regenerating display lists!");
|
||||
for &(ref layer_id, ref new_visible_rect) in new_visible_rects.iter() {
|
||||
old_visible_rects.insert(*layer_id, *new_visible_rect);
|
||||
}
|
||||
rw_data.visible_rects = Arc::new(old_visible_rects);
|
||||
|
||||
// Regenerate the display lists.
|
||||
let reflow_info = Reflow {
|
||||
goal: ReflowGoal::ForDisplay,
|
||||
page_clip_rect: MAX_RECT,
|
||||
};
|
||||
|
||||
let mut layout_context = self.build_shared_layout_context(&*rw_data,
|
||||
false,
|
||||
None,
|
||||
&self.url,
|
||||
reflow_info.goal);
|
||||
|
||||
self.perform_post_main_layout_passes(&reflow_info, &mut *rw_data, &mut layout_context);
|
||||
true
|
||||
}
|
||||
|
||||
fn tick_all_animations<'a>(&'a self,
|
||||
possibly_locked_rw_data: &mut Option<MutexGuard<'a,
|
||||
LayoutTaskData>>) {
|
||||
|
@ -1045,7 +1125,15 @@ impl LayoutTask {
|
|||
}
|
||||
});
|
||||
|
||||
self.perform_post_main_layout_passes(data, rw_data, layout_context);
|
||||
}
|
||||
|
||||
fn perform_post_main_layout_passes<'a>(&'a self,
|
||||
data: &Reflow,
|
||||
rw_data: &mut LayoutTaskData,
|
||||
layout_context: &mut SharedLayoutContext) {
|
||||
// Build the display list if necessary, and send it to the painter.
|
||||
let mut root_flow = (*rw_data.root_flow.as_ref().unwrap()).clone();
|
||||
self.compute_abs_pos_and_build_display_list(data,
|
||||
&mut root_flow,
|
||||
&mut *layout_context,
|
||||
|
|
|
@ -117,8 +117,8 @@ impl Flow for ListItemFlow {
|
|||
}
|
||||
}
|
||||
|
||||
fn compute_absolute_position(&mut self) {
|
||||
self.block_flow.compute_absolute_position()
|
||||
fn compute_absolute_position(&mut self, layout_context: &LayoutContext) {
|
||||
self.block_flow.compute_absolute_position(layout_context)
|
||||
}
|
||||
|
||||
fn place_float_if_applicable<'a>(&mut self, layout_context: &'a LayoutContext<'a>) {
|
||||
|
|
|
@ -63,8 +63,8 @@ impl Flow for MulticolFlow {
|
|||
self.block_flow.assign_block_size(ctx);
|
||||
}
|
||||
|
||||
fn compute_absolute_position(&mut self) {
|
||||
self.block_flow.compute_absolute_position()
|
||||
fn compute_absolute_position(&mut self, layout_context: &LayoutContext) {
|
||||
self.block_flow.compute_absolute_position(layout_context)
|
||||
}
|
||||
|
||||
fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) {
|
||||
|
|
|
@ -503,8 +503,8 @@ impl Flow for TableFlow {
|
|||
self.block_flow.assign_block_size_for_table_like_flow(layout_context, vertical_spacing)
|
||||
}
|
||||
|
||||
fn compute_absolute_position(&mut self) {
|
||||
self.block_flow.compute_absolute_position()
|
||||
fn compute_absolute_position(&mut self, layout_context: &LayoutContext) {
|
||||
self.block_flow.compute_absolute_position(layout_context)
|
||||
}
|
||||
|
||||
fn generated_containing_block_size(&self, flow: OpaqueFlow) -> LogicalSize<Au> {
|
||||
|
|
|
@ -59,13 +59,13 @@ impl Flow for TableCaptionFlow {
|
|||
self.block_flow.assign_inline_sizes(ctx);
|
||||
}
|
||||
|
||||
fn assign_block_size<'a>(&mut self, ctx: &'a LayoutContext<'a>) {
|
||||
fn assign_block_size<'a>(&mut self, layout_context: &'a LayoutContext<'a>) {
|
||||
debug!("assign_block_size: assigning block_size for table_caption");
|
||||
self.block_flow.assign_block_size(ctx);
|
||||
self.block_flow.assign_block_size(layout_context);
|
||||
}
|
||||
|
||||
fn compute_absolute_position(&mut self) {
|
||||
self.block_flow.compute_absolute_position()
|
||||
fn compute_absolute_position(&mut self, layout_context: &LayoutContext) {
|
||||
self.block_flow.compute_absolute_position(layout_context)
|
||||
}
|
||||
|
||||
fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) {
|
||||
|
|
|
@ -160,13 +160,13 @@ impl Flow for TableCellFlow {
|
|||
|_, _, _, _, _, _| {});
|
||||
}
|
||||
|
||||
fn assign_block_size<'a>(&mut self, ctx: &'a LayoutContext<'a>) {
|
||||
fn assign_block_size<'a>(&mut self, layout_context: &'a LayoutContext<'a>) {
|
||||
debug!("assign_block_size: assigning block_size for table_cell");
|
||||
self.assign_block_size_table_cell_base(ctx);
|
||||
self.assign_block_size_table_cell_base(layout_context);
|
||||
}
|
||||
|
||||
fn compute_absolute_position(&mut self) {
|
||||
self.block_flow.compute_absolute_position()
|
||||
fn compute_absolute_position(&mut self, layout_context: &LayoutContext) {
|
||||
self.block_flow.compute_absolute_position(layout_context)
|
||||
}
|
||||
|
||||
fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) {
|
||||
|
|
|
@ -403,8 +403,8 @@ impl Flow for TableRowFlow {
|
|||
self.assign_block_size_table_row_base(layout_context);
|
||||
}
|
||||
|
||||
fn compute_absolute_position(&mut self) {
|
||||
self.block_flow.compute_absolute_position()
|
||||
fn compute_absolute_position(&mut self, layout_context: &LayoutContext) {
|
||||
self.block_flow.compute_absolute_position(layout_context)
|
||||
}
|
||||
|
||||
fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) {
|
||||
|
|
|
@ -203,8 +203,8 @@ impl Flow for TableRowGroupFlow {
|
|||
self.spacing.vertical)
|
||||
}
|
||||
|
||||
fn compute_absolute_position(&mut self) {
|
||||
self.block_flow.compute_absolute_position()
|
||||
fn compute_absolute_position(&mut self, layout_context: &LayoutContext) {
|
||||
self.block_flow.compute_absolute_position(layout_context)
|
||||
}
|
||||
|
||||
fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) {
|
||||
|
|
|
@ -381,8 +381,8 @@ impl Flow for TableWrapperFlow {
|
|||
MarginsMayCollapseFlag::MarginsMayNotCollapse);
|
||||
}
|
||||
|
||||
fn compute_absolute_position(&mut self) {
|
||||
self.block_flow.compute_absolute_position()
|
||||
fn compute_absolute_position(&mut self, layout_context: &LayoutContext) {
|
||||
self.block_flow.compute_absolute_position(layout_context)
|
||||
}
|
||||
|
||||
fn place_float_if_applicable<'a>(&mut self, layout_context: &'a LayoutContext<'a>) {
|
||||
|
|
|
@ -364,7 +364,7 @@ pub struct ComputeAbsolutePositions<'a> {
|
|||
impl<'a> PreorderFlowTraversal for ComputeAbsolutePositions<'a> {
|
||||
#[inline]
|
||||
fn process(&self, flow: &mut Flow) {
|
||||
flow.compute_absolute_position();
|
||||
flow.compute_absolute_position(self.layout_context);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,9 @@ authors = ["The Servo Project Developers"]
|
|||
name = "layout_traits"
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies.geom]
|
||||
git = "https://github.com/servo/rust-geom"
|
||||
|
||||
[dependencies.gfx]
|
||||
path = "../gfx"
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
* 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/. */
|
||||
|
||||
extern crate geom;
|
||||
extern crate gfx;
|
||||
extern crate script_traits;
|
||||
extern crate msg;
|
||||
|
@ -15,25 +16,29 @@ extern crate util;
|
|||
// The traits are here instead of in layout so
|
||||
// that these modules won't have to depend on layout.
|
||||
|
||||
use geom::rect::Rect;
|
||||
use gfx::font_cache_task::FontCacheTask;
|
||||
use gfx::paint_task::PaintChan;
|
||||
use msg::compositor_msg::Epoch;
|
||||
use msg::compositor_msg::{Epoch, LayerId};
|
||||
use msg::constellation_msg::{ConstellationChan, Failure, PipelineId, PipelineExitType};
|
||||
use profile_traits::mem;
|
||||
use profile_traits::time;
|
||||
use net_traits::image_cache_task::ImageCacheTask;
|
||||
use url::Url;
|
||||
use script_traits::{ScriptControlChan, OpaqueScriptLayoutChannel};
|
||||
use std::sync::mpsc::{Sender, Receiver};
|
||||
use util::geometry::Au;
|
||||
use url::Url;
|
||||
|
||||
/// Messages sent to the layout task from the constellation
|
||||
/// Messages sent to the layout task from the constellation and/or compositor.
|
||||
pub enum LayoutControlMsg {
|
||||
ExitNow(PipelineExitType),
|
||||
GetCurrentEpoch(Sender<Epoch>),
|
||||
TickAnimations,
|
||||
SetVisibleRects(Vec<(LayerId, Rect<Au>)>),
|
||||
}
|
||||
|
||||
/// A channel wrapper for constellation messages
|
||||
#[derive(Clone)]
|
||||
pub struct LayoutControlChan(pub Sender<LayoutControlMsg>);
|
||||
|
||||
// A static method creating a layout task
|
||||
|
|
|
@ -34,7 +34,7 @@ impl FrameTreeId {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Copy)]
|
||||
#[derive(Clone, PartialEq, Eq, Copy, Hash)]
|
||||
pub struct LayerId(pub usize, pub u32);
|
||||
|
||||
impl Debug for LayerId {
|
||||
|
|
|
@ -11,6 +11,7 @@ use dom::node::LayoutData;
|
|||
use geom::point::Point2D;
|
||||
use geom::rect::Rect;
|
||||
use libc::uintptr_t;
|
||||
use msg::compositor_msg::LayerId;
|
||||
use msg::constellation_msg::{PipelineExitType, WindowSizeData};
|
||||
use msg::compositor_msg::Epoch;
|
||||
use net_traits::PendingAsyncLoad;
|
||||
|
@ -47,6 +48,10 @@ pub enum Msg {
|
|||
/// Requests that the layout task render the next frame of all animations.
|
||||
TickAnimations,
|
||||
|
||||
/// Updates the layout visible rects, affecting the area that display lists will be constructed
|
||||
/// for.
|
||||
SetVisibleRects(Vec<(LayerId, Rect<Au>)>),
|
||||
|
||||
/// Destroys layout data associated with a DOM node.
|
||||
///
|
||||
/// TODO(pcwalton): Maybe think about batching to avoid message traffic.
|
||||
|
|
1
components/servo/Cargo.lock
generated
1
components/servo/Cargo.lock
generated
|
@ -674,6 +674,7 @@ dependencies = [
|
|||
name = "layout_traits"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"geom 0.1.0 (git+https://github.com/servo/rust-geom)",
|
||||
"gfx 0.0.1",
|
||||
"msg 0.0.1",
|
||||
"net_traits 0.0.1",
|
||||
|
|
1
ports/cef/Cargo.lock
generated
1
ports/cef/Cargo.lock
generated
|
@ -676,6 +676,7 @@ dependencies = [
|
|||
name = "layout_traits"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"geom 0.1.0 (git+https://github.com/servo/rust-geom)",
|
||||
"gfx 0.0.1",
|
||||
"msg 0.0.1",
|
||||
"net_traits 0.0.1",
|
||||
|
|
1
ports/gonk/Cargo.lock
generated
1
ports/gonk/Cargo.lock
generated
|
@ -574,6 +574,7 @@ dependencies = [
|
|||
name = "layout_traits"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"geom 0.1.0 (git+https://github.com/servo/rust-geom)",
|
||||
"gfx 0.0.1",
|
||||
"msg 0.0.1",
|
||||
"net_traits 0.0.1",
|
||||
|
|
36
tests/html/lipsum-large.html
Normal file
36
tests/html/lipsum-large.html
Normal file
|
@ -0,0 +1,36 @@
|
|||
<body>
|
||||
<div style="font-size: 72px;">
|
||||
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc volutpat feugiat fermentum. Curabitur luctus consequat urna a tincidunt. Nullam vitae velit eu arcu congue volutpat in a diam. Pellentesque lacus ipsum, gravida et tristique sit amet, varius eu ipsum. Duis venenatis sem id nibh commodo tempor. Curabitur risus tellus, cursus quis pellentesque sed, elementum eget neque. Sed ultricies, orci et adipiscing dapibus, mauris nisi condimentum felis, ac euismod tellus nunc vel felis. Aliquam egestas accumsan turpis, a volutpat dui fermentum id. Aliquam erat volutpat. Nunc vel auctor odio. Donec eu posuere dolor. Donec vitae justo purus, nec bibendum lectus. Quisque elit tortor, tristique vel ultricies sed, euismod sed tellus. Nullam dolor purus, porta et hendrerit id, rhoncus eu lectus. Nam vel lorem at dui mattis pretium eget a nisi.
|
||||
|
||||
Quisque eleifend risus non metus gravida ac semper odio aliquam. Praesent sed risus in tellus congue convallis. Quisque et magna tellus, in ornare leo. Nunc tempor interdum tortor, non blandit risus rhoncus et. Aliquam pulvinar est sed massa ornare sit amet ornare ligula commodo. Nunc porta ultricies tempor. Phasellus gravida accumsan auctor. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Proin quis enim tortor. Curabitur pretium diam a arcu rutrum sed sagittis velit placerat. Ut ut mauris quam, luctus iaculis sem. Nulla quis purus quis lacus cursus imperdiet a quis dolor. Nullam hendrerit eros faucibus ante dignissim vel pharetra ligula viverra. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
|
||||
Nunc gravida massa quis nisl venenatis interdum. Integer non dui tortor, non volutpat nisi. In porta accumsan scelerisque. Nunc elementum urna non metus scelerisque ac condimentum eros volutpat. Donec eget nibh condimentum quam tempor vulputate non vel turpis. Quisque suscipit blandit mi quis iaculis. Ut iaculis, nisi ac tristique fringilla, nulla dui interdum lectus, a consectetur mi sapien a metus. Cras eu dolor non dolor imperdiet faucibus. Vestibulum porta risus vel neque convallis elementum. Aenean auctor, turpis eu molestie ultrices, mauris felis sodales est, at iaculis augue libero eu turpis. Curabitur sit amet est quis orci volutpat viverra dignissim in elit. Cras suscipit tristique quam in tincidunt. Aliquam imperdiet libero id dolor egestas viverra egestas arcu posuere. Pellentesque enim risus, accumsan quis ullamcorper ac, malesuada vitae leo.
|
||||
|
||||
Cras ullamcorper vestibulum eros nec suscipit. Vivamus tincidunt, metus at tristique sodales, metus massa sagittis elit, eu scelerisque tortor ante ut sapien. Curabitur rutrum fringilla purus, eget commodo neque tincidunt ut. Etiam ac leo sed nisi ultricies ultrices sed in urna. Fusce blandit fermentum ipsum, vitae dapibus eros tempus vel. Praesent ullamcorper nulla sed nisl facilisis aliquam. Mauris consequat dui quis elit pulvinar vitae pellentesque sapien tempor. Sed accumsan consequat tortor, eget hendrerit lectus rhoncus nec. Mauris diam neque, congue ut laoreet a, hendrerit sit amet sem. Donec eget diam orci, ac vehicula orci. Ut sollicitudin ultricies arcu ut faucibus. Nulla felis nisl, auctor eget adipiscing sit amet, sagittis et est. Cras elit nisi, placerat quis tincidunt at, dapibus et arcu. Donec fermentum eleifend pretium. Nullam at posuere nulla. Phasellus at urna a arcu aliquam ullamcorper eget et ligula.
|
||||
|
||||
Curabitur fermentum libero vitae libero mattis pulvinar. Vivamus luctus laoreet vulputate. Nunc dictum eros a nulla fringilla quis vestibulum ante malesuada. Suspendisse aliquam vulputate consequat. Duis eu sem eu justo convallis dictum sed ut arcu. Mauris porttitor nulla a augue iaculis posuere euismod mi scelerisque. Maecenas sodales nisi eu turpis fermentum dapibus. Morbi viverra iaculis magna, sed congue justo semper at. Nunc in mi enim. Ut aliquam mi et ligula molestie ac bibendum leo egestas. Nullam molestie, urna ac interdum auctor, dolor diam aliquam ligula, vitae gravida mi orci a tortor. Donec et felis turpis, id lobortis nunc. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Integer feugiat sagittis dictum. Donec mauris ante, feugiat sed ultricies ut, elementum a purus. Sed porta lectus et sem blandit vehicula.
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc volutpat feugiat fermentum. Curabitur luctus consequat urna a tincidunt. Nullam vitae velit eu arcu congue volutpat in a diam. Pellentesque lacus ipsum, gravida et tristique sit amet, varius eu ipsum. Duis venenatis sem id nibh commodo tempor. Curabitur risus tellus, cursus quis pellentesque sed, elementum eget neque. Sed ultricies, orci et adipiscing dapibus, mauris nisi condimentum felis, ac euismod tellus nunc vel felis. Aliquam egestas accumsan turpis, a volutpat dui fermentum id. Aliquam erat volutpat. Nunc vel auctor odio. Donec eu posuere dolor. Donec vitae justo purus, nec bibendum lectus. Quisque elit tortor, tristique vel ultricies sed, euismod sed tellus. Nullam dolor purus, porta et hendrerit id, rhoncus eu lectus. Nam vel lorem at dui mattis pretium eget a nisi.
|
||||
|
||||
Quisque eleifend risus non metus gravida ac semper odio aliquam. Praesent sed risus in tellus congue convallis. Quisque et magna tellus, in ornare leo. Nunc tempor interdum tortor, non blandit risus rhoncus et. Aliquam pulvinar est sed massa ornare sit amet ornare ligula commodo. Nunc porta ultricies tempor. Phasellus gravida accumsan auctor. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Proin quis enim tortor. Curabitur pretium diam a arcu rutrum sed sagittis velit placerat. Ut ut mauris quam, luctus iaculis sem. Nulla quis purus quis lacus cursus imperdiet a quis dolor. Nullam hendrerit eros faucibus ante dignissim vel pharetra ligula viverra. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
|
||||
Nunc gravida massa quis nisl venenatis interdum. Integer non dui tortor, non volutpat nisi. In porta accumsan scelerisque. Nunc elementum urna non metus scelerisque ac condimentum eros volutpat. Donec eget nibh condimentum quam tempor vulputate non vel turpis. Quisque suscipit blandit mi quis iaculis. Ut iaculis, nisi ac tristique fringilla, nulla dui interdum lectus, a consectetur mi sapien a metus. Cras eu dolor non dolor imperdiet faucibus. Vestibulum porta risus vel neque convallis elementum. Aenean auctor, turpis eu molestie ultrices, mauris felis sodales est, at iaculis augue libero eu turpis. Curabitur sit amet est quis orci volutpat viverra dignissim in elit. Cras suscipit tristique quam in tincidunt. Aliquam imperdiet libero id dolor egestas viverra egestas arcu posuere. Pellentesque enim risus, accumsan quis ullamcorper ac, malesuada vitae leo.
|
||||
|
||||
Cras ullamcorper vestibulum eros nec suscipit. Vivamus tincidunt, metus at tristique sodales, metus massa sagittis elit, eu scelerisque tortor ante ut sapien. Curabitur rutrum fringilla purus, eget commodo neque tincidunt ut. Etiam ac leo sed nisi ultricies ultrices sed in urna. Fusce blandit fermentum ipsum, vitae dapibus eros tempus vel. Praesent ullamcorper nulla sed nisl facilisis aliquam. Mauris consequat dui quis elit pulvinar vitae pellentesque sapien tempor. Sed accumsan consequat tortor, eget hendrerit lectus rhoncus nec. Mauris diam neque, congue ut laoreet a, hendrerit sit amet sem. Donec eget diam orci, ac vehicula orci. Ut sollicitudin ultricies arcu ut faucibus. Nulla felis nisl, auctor eget adipiscing sit amet, sagittis et est. Cras elit nisi, placerat quis tincidunt at, dapibus et arcu. Donec fermentum eleifend pretium. Nullam at posuere nulla. Phasellus at urna a arcu aliquam ullamcorper eget et ligula.
|
||||
|
||||
Curabitur fermentum libero vitae libero mattis pulvinar. Vivamus luctus laoreet vulputate. Nunc dictum eros a nulla fringilla quis vestibulum ante malesuada. Suspendisse aliquam vulputate consequat. Duis eu sem eu justo convallis dictum sed ut arcu. Mauris porttitor nulla a augue iaculis posuere euismod mi scelerisque. Maecenas sodales nisi eu turpis fermentum dapibus. Morbi viverra iaculis magna, sed congue justo semper at. Nunc in mi enim. Ut aliquam mi et ligula molestie ac bibendum leo egestas. Nullam molestie, urna ac interdum auctor, dolor diam aliquam ligula, vitae gravida mi orci a tortor. Donec et felis turpis, id lobortis nunc. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Integer feugiat sagittis dictum. Donec mauris ante, feugiat sed ultricies ut, elementum a purus. Sed porta lectus et sem blandit vehicula.
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc volutpat feugiat fermentum. Curabitur luctus consequat urna a tincidunt. Nullam vitae velit eu arcu congue volutpat in a diam. Pellentesque lacus ipsum, gravida et tristique sit amet, varius eu ipsum. Duis venenatis sem id nibh commodo tempor. Curabitur risus tellus, cursus quis pellentesque sed, elementum eget neque. Sed ultricies, orci et adipiscing dapibus, mauris nisi condimentum felis, ac euismod tellus nunc vel felis. Aliquam egestas accumsan turpis, a volutpat dui fermentum id. Aliquam erat volutpat. Nunc vel auctor odio. Donec eu posuere dolor. Donec vitae justo purus, nec bibendum lectus. Quisque elit tortor, tristique vel ultricies sed, euismod sed tellus. Nullam dolor purus, porta et hendrerit id, rhoncus eu lectus. Nam vel lorem at dui mattis pretium eget a nisi.
|
||||
|
||||
Quisque eleifend risus non metus gravida ac semper odio aliquam. Praesent sed risus in tellus congue convallis. Quisque et magna tellus, in ornare leo. Nunc tempor interdum tortor, non blandit risus rhoncus et. Aliquam pulvinar est sed massa ornare sit amet ornare ligula commodo. Nunc porta ultricies tempor. Phasellus gravida accumsan auctor. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Proin quis enim tortor. Curabitur pretium diam a arcu rutrum sed sagittis velit placerat. Ut ut mauris quam, luctus iaculis sem. Nulla quis purus quis lacus cursus imperdiet a quis dolor. Nullam hendrerit eros faucibus ante dignissim vel pharetra ligula viverra. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
|
||||
Nunc gravida massa quis nisl venenatis interdum. Integer non dui tortor, non volutpat nisi. In porta accumsan scelerisque. Nunc elementum urna non metus scelerisque ac condimentum eros volutpat. Donec eget nibh condimentum quam tempor vulputate non vel turpis. Quisque suscipit blandit mi quis iaculis. Ut iaculis, nisi ac tristique fringilla, nulla dui interdum lectus, a consectetur mi sapien a metus. Cras eu dolor non dolor imperdiet faucibus. Vestibulum porta risus vel neque convallis elementum. Aenean auctor, turpis eu molestie ultrices, mauris felis sodales est, at iaculis augue libero eu turpis. Curabitur sit amet est quis orci volutpat viverra dignissim in elit. Cras suscipit tristique quam in tincidunt. Aliquam imperdiet libero id dolor egestas viverra egestas arcu posuere. Pellentesque enim risus, accumsan quis ullamcorper ac, malesuada vitae leo.
|
||||
|
||||
Cras ullamcorper vestibulum eros nec suscipit. Vivamus tincidunt, metus at tristique sodales, metus massa sagittis elit, eu scelerisque tortor ante ut sapien. Curabitur rutrum fringilla purus, eget commodo neque tincidunt ut. Etiam ac leo sed nisi ultricies ultrices sed in urna. Fusce blandit fermentum ipsum, vitae dapibus eros tempus vel. Praesent ullamcorper nulla sed nisl facilisis aliquam. Mauris consequat dui quis elit pulvinar vitae pellentesque sapien tempor. Sed accumsan consequat tortor, eget hendrerit lectus rhoncus nec. Mauris diam neque, congue ut laoreet a, hendrerit sit amet sem. Donec eget diam orci, ac vehicula orci. Ut sollicitudin ultricies arcu ut faucibus. Nulla felis nisl, auctor eget adipiscing sit amet, sagittis et est. Cras elit nisi, placerat quis tincidunt at, dapibus et arcu. Donec fermentum eleifend pretium. Nullam at posuere nulla. Phasellus at urna a arcu aliquam ullamcorper eget et ligula.
|
||||
|
||||
Curabitur fermentum libero vitae libero mattis pulvinar. Vivamus luctus laoreet vulputate. Nunc dictum eros a nulla fringilla quis vestibulum ante malesuada. Suspendisse aliquam vulputate consequat. Duis eu sem eu justo convallis dictum sed ut arcu. Mauris porttitor nulla a augue iaculis posuere euismod mi scelerisque. Maecenas sodales nisi eu turpis fermentum dapibus. Morbi viverra iaculis magna, sed congue justo semper at. Nunc in mi enim. Ut aliquam mi et ligula molestie ac bibendum leo egestas. Nullam molestie, urna ac interdum auctor, dolor diam aliquam ligula, vitae gravida mi orci a tortor. Donec et felis turpis, id lobortis nunc. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Integer feugiat sagittis dictum. Donec mauris ante, feugiat sed ultricies ut, elementum a purus. Sed porta lectus et sem blandit vehicula.
|
||||
|
||||
</div>
|
||||
</body>
|
Loading…
Add table
Add a link
Reference in a new issue