mirror of
https://github.com/servo/servo.git
synced 2025-08-03 20:50:07 +01:00
gfx: Rewrite display list construction to make stacking-contexts more
first-class. This implements the scheme described here: https://groups.google.com/forum/#!topic/mozilla.dev.servo/sZVPSfPVfkg This commit changes Servo to generate one display list per stacking context instead of one display list per layer. This is purely a refactoring; there are no functional changes. Performance is essentially the same as before. However, there should be numerous future benefits that this is intended to allow for: * It makes the code simpler to understand because the "new layer needed" vs. "no new layer needed" code paths are more consolidated. * It makes it easy to support CSS properties that did not fit into our previous flat display list model (without unconditionally layerizing them): o `opacity` should be easy to support because the stacking context provides the higher-level grouping of display items to which opacity is to be applied. o `transform` can be easily supported because the stacking context provides a place to stash the transformation matrix. This has the side benefit of nicely separating the transformation matrix from the clipping regions. * The `flatten` logic is now O(1) instead of O(n) and now only needs to be invoked for pseudo-stacking contexts (right now: just floats), instead of for every stacking context. * Layers are now a proper tree instead of a flat list as far as layout is concerned, bringing us closer to a production-quality compositing/layers framework. * This commit opens the door to incremental display list construction at the level of stacking contexts. Future performance improvements could come from optimizing allocation of display list items, and, of course, incremental display list construction.
This commit is contained in:
parent
0ab70dd539
commit
a4a9a46a87
13 changed files with 812 additions and 550 deletions
|
@ -15,7 +15,8 @@
|
||||||
//! low-level drawing primitives.
|
//! low-level drawing primitives.
|
||||||
|
|
||||||
use color::Color;
|
use color::Color;
|
||||||
use render_context::RenderContext;
|
use display_list::optimizer::DisplayListOptimizer;
|
||||||
|
use render_context::{RenderContext, ToAzureRect};
|
||||||
use text::glyph::CharIndex;
|
use text::glyph::CharIndex;
|
||||||
use text::TextRun;
|
use text::TextRun;
|
||||||
|
|
||||||
|
@ -23,11 +24,16 @@ use azure::azure::AzFloat;
|
||||||
use collections::dlist::{mod, DList};
|
use collections::dlist::{mod, DList};
|
||||||
use geom::{Point2D, Rect, SideOffsets2D, Size2D, Matrix2D};
|
use geom::{Point2D, Rect, SideOffsets2D, Size2D, Matrix2D};
|
||||||
use libc::uintptr_t;
|
use libc::uintptr_t;
|
||||||
|
use render_task::RenderLayer;
|
||||||
|
use script_traits::UntrustedNodeAddress;
|
||||||
|
use servo_msg::compositor_msg::LayerId;
|
||||||
use servo_net::image::base::Image;
|
use servo_net::image::base::Image;
|
||||||
use servo_util::dlist as servo_dlist;
|
use servo_util::dlist as servo_dlist;
|
||||||
use servo_util::geometry::Au;
|
use servo_util::geometry::{mod, Au};
|
||||||
use servo_util::range::Range;
|
use servo_util::range::Range;
|
||||||
|
use servo_util::smallvec::{SmallVec, SmallVec8};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
use std::mem;
|
||||||
use std::slice::Items;
|
use std::slice::Items;
|
||||||
use style::computed_values::border_style;
|
use style::computed_values::border_style;
|
||||||
use sync::Arc;
|
use sync::Arc;
|
||||||
|
@ -55,240 +61,308 @@ impl OpaqueNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// "Steps" as defined by CSS 2.1 § E.2.
|
/// Display items that make up a stacking context. "Steps" here refer to the steps in CSS 2.1
|
||||||
#[deriving(Clone, PartialEq, Show)]
|
/// Appendix E.
|
||||||
pub enum StackingLevel {
|
///
|
||||||
/// The border and backgrounds for the root of this stacking context: steps 1 and 2.
|
/// TODO(pcwalton): We could reduce the size of this structure with a more "skip list"-like
|
||||||
BackgroundAndBordersStackingLevel,
|
/// structure, omitting several pointers and lengths.
|
||||||
/// Borders and backgrounds for block-level descendants: step 4.
|
|
||||||
BlockBackgroundsAndBordersStackingLevel,
|
|
||||||
/// Floats: step 5. These are treated as pseudo-stacking contexts.
|
|
||||||
FloatStackingLevel,
|
|
||||||
/// All other content.
|
|
||||||
ContentStackingLevel,
|
|
||||||
/// Positioned descendant stacking contexts, along with their `z-index` levels.
|
|
||||||
///
|
|
||||||
/// TODO(pcwalton): `z-index` should be the actual CSS property value in order to handle
|
|
||||||
/// `auto`, not just an integer.
|
|
||||||
PositionedDescendantStackingLevel(i32)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StackingLevel {
|
|
||||||
#[inline]
|
|
||||||
pub fn from_background_and_border_level(level: BackgroundAndBorderLevel) -> StackingLevel {
|
|
||||||
match level {
|
|
||||||
RootOfStackingContextLevel => BackgroundAndBordersStackingLevel,
|
|
||||||
BlockLevel => BlockBackgroundsAndBordersStackingLevel,
|
|
||||||
ContentLevel => ContentStackingLevel,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct StackingContext {
|
|
||||||
/// The border and backgrounds for the root of this stacking context: steps 1 and 2.
|
|
||||||
pub background_and_borders: DisplayList,
|
|
||||||
/// Borders and backgrounds for block-level descendants: step 4.
|
|
||||||
pub block_backgrounds_and_borders: DisplayList,
|
|
||||||
/// Floats: step 5. These are treated as pseudo-stacking contexts.
|
|
||||||
pub floats: DisplayList,
|
|
||||||
/// All other content.
|
|
||||||
pub content: DisplayList,
|
|
||||||
/// Positioned descendant stacking contexts, along with their `z-index` levels.
|
|
||||||
pub positioned_descendants: Vec<(i32, DisplayList)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StackingContext {
|
|
||||||
/// Creates a new empty stacking context.
|
|
||||||
#[inline]
|
|
||||||
fn new() -> StackingContext {
|
|
||||||
StackingContext {
|
|
||||||
background_and_borders: DisplayList::new(),
|
|
||||||
block_backgrounds_and_borders: DisplayList::new(),
|
|
||||||
floats: DisplayList::new(),
|
|
||||||
content: DisplayList::new(),
|
|
||||||
positioned_descendants: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Initializes a stacking context from a display list, consuming that display list in the
|
|
||||||
/// process.
|
|
||||||
fn init_from_list(&mut self, list: &mut DisplayList) {
|
|
||||||
while !list.list.is_empty() {
|
|
||||||
let mut head = DisplayList::from_list(servo_dlist::split(&mut list.list));
|
|
||||||
match head.front().unwrap().base().level {
|
|
||||||
BackgroundAndBordersStackingLevel => {
|
|
||||||
self.background_and_borders.append_from(&mut head)
|
|
||||||
}
|
|
||||||
BlockBackgroundsAndBordersStackingLevel => {
|
|
||||||
self.block_backgrounds_and_borders.append_from(&mut head)
|
|
||||||
}
|
|
||||||
FloatStackingLevel => self.floats.append_from(&mut head),
|
|
||||||
ContentStackingLevel => self.content.append_from(&mut head),
|
|
||||||
PositionedDescendantStackingLevel(z_index) => {
|
|
||||||
match self.positioned_descendants.iter_mut().find(|& &(z, _)| z_index == z) {
|
|
||||||
Some(&(_, ref mut my_list)) => {
|
|
||||||
my_list.append_from(&mut head);
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
None => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.positioned_descendants.push((z_index, head))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Which level to place backgrounds and borders in.
|
|
||||||
pub enum BackgroundAndBorderLevel {
|
|
||||||
RootOfStackingContextLevel,
|
|
||||||
BlockLevel,
|
|
||||||
ContentLevel,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A list of rendering operations to be performed.
|
|
||||||
#[deriving(Clone, Show)]
|
|
||||||
pub struct DisplayList {
|
pub struct DisplayList {
|
||||||
pub list: DList<DisplayItem>,
|
/// The border and backgrounds for the root of this stacking context: steps 1 and 2.
|
||||||
}
|
pub background_and_borders: DList<DisplayItem>,
|
||||||
|
/// Borders and backgrounds for block-level descendants: step 4.
|
||||||
pub enum DisplayListIterator<'a> {
|
pub block_backgrounds_and_borders: DList<DisplayItem>,
|
||||||
EmptyDisplayListIterator,
|
/// Floats: step 5. These are treated as pseudo-stacking contexts.
|
||||||
ParentDisplayListIterator(Items<'a,DisplayList>),
|
pub floats: DList<DisplayItem>,
|
||||||
}
|
/// All other content.
|
||||||
|
pub content: DList<DisplayItem>,
|
||||||
impl<'a> Iterator<&'a DisplayList> for DisplayListIterator<'a> {
|
/// Child stacking contexts.
|
||||||
#[inline]
|
pub children: DList<Arc<StackingContext>>,
|
||||||
fn next(&mut self) -> Option<&'a DisplayList> {
|
|
||||||
match *self {
|
|
||||||
EmptyDisplayListIterator => None,
|
|
||||||
ParentDisplayListIterator(ref mut subiterator) => subiterator.next(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DisplayList {
|
impl DisplayList {
|
||||||
/// Creates a new display list.
|
/// Creates a new, empty display list.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn new() -> DisplayList {
|
pub fn new() -> DisplayList {
|
||||||
DisplayList {
|
DisplayList {
|
||||||
list: DList::new(),
|
background_and_borders: DList::new(),
|
||||||
|
block_backgrounds_and_borders: DList::new(),
|
||||||
|
floats: DList::new(),
|
||||||
|
content: DList::new(),
|
||||||
|
children: DList::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new display list from the given list of display items.
|
/// Appends all display items from `other` into `self`, preserving stacking order and emptying
|
||||||
fn from_list(list: DList<DisplayItem>) -> DisplayList {
|
/// `other` in the process.
|
||||||
DisplayList {
|
|
||||||
list: list,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Appends the given item to the display list.
|
|
||||||
#[inline]
|
|
||||||
pub fn push(&mut self, item: DisplayItem) {
|
|
||||||
self.list.push_back(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Appends the items in the given display list to this one, removing them in the process.
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn append_from(&mut self, other: &mut DisplayList) {
|
pub fn append_from(&mut self, other: &mut DisplayList) {
|
||||||
servo_dlist::append_from(&mut self.list, &mut other.list)
|
servo_dlist::append_from(&mut self.background_and_borders,
|
||||||
|
&mut other.background_and_borders);
|
||||||
|
servo_dlist::append_from(&mut self.block_backgrounds_and_borders,
|
||||||
|
&mut other.block_backgrounds_and_borders);
|
||||||
|
servo_dlist::append_from(&mut self.floats, &mut other.floats);
|
||||||
|
servo_dlist::append_from(&mut self.content, &mut other.content);
|
||||||
|
servo_dlist::append_from(&mut self.children, &mut other.children);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the first display item in this list.
|
/// Merges all display items from all non-float stacking levels to the `float` stacking level.
|
||||||
#[inline]
|
#[inline]
|
||||||
fn front(&self) -> Option<&DisplayItem> {
|
pub fn form_float_pseudo_stacking_context(&mut self) {
|
||||||
self.list.front()
|
servo_dlist::prepend_from(&mut self.floats, &mut self.content);
|
||||||
|
servo_dlist::prepend_from(&mut self.floats, &mut self.block_backgrounds_and_borders);
|
||||||
|
servo_dlist::prepend_from(&mut self.floats, &mut self.background_and_borders);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn debug(&self) {
|
/// Returns a list of all items in this display list concatenated together. This is extremely
|
||||||
for item in self.list.iter() {
|
/// inefficient and should only be used for debugging.
|
||||||
item.debug_with_level(0);
|
pub fn all_display_items(&self) -> Vec<DisplayItem> {
|
||||||
|
let mut result = Vec::new();
|
||||||
|
for display_item in self.background_and_borders.iter() {
|
||||||
|
result.push((*display_item).clone())
|
||||||
}
|
}
|
||||||
}
|
for display_item in self.block_backgrounds_and_borders.iter() {
|
||||||
|
result.push((*display_item).clone())
|
||||||
/// Draws the display list into the given render context. The display list must be flattened
|
|
||||||
/// first for correct painting.
|
|
||||||
pub fn draw_into_context(&self,
|
|
||||||
render_context: &mut RenderContext,
|
|
||||||
current_transform: &Matrix2D<AzFloat>,
|
|
||||||
current_clip_stack: &mut Vec<Rect<Au>>) {
|
|
||||||
debug!("Beginning display list.");
|
|
||||||
for item in self.list.iter() {
|
|
||||||
item.draw_into_context(render_context, current_transform, current_clip_stack)
|
|
||||||
}
|
}
|
||||||
debug!("Ending display list.");
|
for display_item in self.floats.iter() {
|
||||||
|
result.push((*display_item).clone())
|
||||||
|
}
|
||||||
|
for display_item in self.content.iter() {
|
||||||
|
result.push((*display_item).clone())
|
||||||
|
}
|
||||||
|
result
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns a preorder iterator over the given display list.
|
/// Represents one CSS stacking context, which may or may not have a hardware layer.
|
||||||
#[inline]
|
pub struct StackingContext {
|
||||||
pub fn iter<'a>(&'a self) -> DisplayItemIterator<'a> {
|
/// The display items that make up this stacking context.
|
||||||
ParentDisplayItemIterator(self.list.iter())
|
pub display_list: Box<DisplayList>,
|
||||||
}
|
/// The layer for this stacking context, if there is one.
|
||||||
|
pub layer: Option<Arc<RenderLayer>>,
|
||||||
|
/// The position and size of this stacking context.
|
||||||
|
pub bounds: Rect<Au>,
|
||||||
|
/// The clipping rect for this stacking context, in the coordinate system of the *parent*
|
||||||
|
/// stacking context.
|
||||||
|
pub clip_rect: Rect<Au>,
|
||||||
|
/// The `z-index` for this stacking context.
|
||||||
|
pub z_index: i32,
|
||||||
|
}
|
||||||
|
|
||||||
/// Flattens a display list into a display list with a single stacking level according to the
|
impl StackingContext {
|
||||||
/// steps in CSS 2.1 § E.2.
|
/// Creates a new stacking context.
|
||||||
///
|
///
|
||||||
/// This must be called before `draw_into_context()` is for correct results.
|
/// TODO(pcwalton): Stacking contexts should not always be clipped to their bounds, to handle
|
||||||
pub fn flatten(&mut self, resulting_level: StackingLevel) {
|
/// overflow properly.
|
||||||
// Fast paths:
|
#[inline]
|
||||||
if self.list.len() == 0 {
|
pub fn new(display_list: Box<DisplayList>,
|
||||||
return
|
bounds: Rect<Au>,
|
||||||
}
|
z_index: i32,
|
||||||
if self.list.len() == 1 {
|
layer: Option<Arc<RenderLayer>>)
|
||||||
self.set_stacking_level(resulting_level);
|
-> StackingContext {
|
||||||
return
|
StackingContext {
|
||||||
|
display_list: display_list,
|
||||||
|
layer: layer,
|
||||||
|
bounds: bounds,
|
||||||
|
clip_rect: bounds,
|
||||||
|
z_index: z_index,
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut stacking_context = StackingContext::new();
|
/// Draws the stacking context in the proper order according to the steps in CSS 2.1 § E.2.
|
||||||
stacking_context.init_from_list(self);
|
pub fn optimize_and_draw_into_context(&self,
|
||||||
debug_assert!(self.list.is_empty());
|
render_context: &mut RenderContext,
|
||||||
|
tile_bounds: &Rect<AzFloat>,
|
||||||
// Steps 1 and 2: Borders and background for the root.
|
current_transform: &Matrix2D<AzFloat>,
|
||||||
self.append_from(&mut stacking_context.background_and_borders);
|
current_clip_stack: &mut Vec<Rect<Au>>) {
|
||||||
|
// Optimize the display list to throw out out-of-bounds display items and so forth.
|
||||||
|
let display_list = DisplayListOptimizer::new(tile_bounds).optimize(&*self.display_list);
|
||||||
|
|
||||||
// Sort positioned children according to z-index.
|
// Sort positioned children according to z-index.
|
||||||
stacking_context.positioned_descendants.sort_by(|&(z_index_a, _), &(z_index_b, _)| {
|
let mut positioned_children = SmallVec8::new();
|
||||||
z_index_a.cmp(&z_index_b)
|
for kid in display_list.children.iter() {
|
||||||
});
|
positioned_children.push((*kid).clone());
|
||||||
|
}
|
||||||
|
positioned_children.as_slice_mut().sort_by(|this, other| this.z_index.cmp(&other.z_index));
|
||||||
|
|
||||||
|
// Steps 1 and 2: Borders and background for the root.
|
||||||
|
for display_item in display_list.background_and_borders.iter() {
|
||||||
|
display_item.draw_into_context(render_context, current_transform, current_clip_stack)
|
||||||
|
}
|
||||||
|
|
||||||
// Step 3: Positioned descendants with negative z-indices.
|
// Step 3: Positioned descendants with negative z-indices.
|
||||||
for &(ref mut z_index, ref mut list) in stacking_context.positioned_descendants.iter_mut() {
|
for positioned_kid in positioned_children.iter() {
|
||||||
if *z_index < 0 {
|
if positioned_kid.z_index >= 0 {
|
||||||
self.append_from(list)
|
break
|
||||||
|
}
|
||||||
|
if positioned_kid.layer.is_none() {
|
||||||
|
let new_transform =
|
||||||
|
current_transform.translate(positioned_kid.bounds.origin.x.to_nearest_px()
|
||||||
|
as AzFloat,
|
||||||
|
positioned_kid.bounds.origin.y.to_nearest_px()
|
||||||
|
as AzFloat);
|
||||||
|
let new_tile_rect =
|
||||||
|
self.compute_tile_rect_for_child_stacking_context(tile_bounds,
|
||||||
|
&**positioned_kid);
|
||||||
|
positioned_kid.optimize_and_draw_into_context(render_context,
|
||||||
|
&new_tile_rect,
|
||||||
|
&new_transform,
|
||||||
|
current_clip_stack);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 4: Block backgrounds and borders.
|
// Step 4: Block backgrounds and borders.
|
||||||
self.append_from(&mut stacking_context.block_backgrounds_and_borders);
|
for display_item in display_list.block_backgrounds_and_borders.iter() {
|
||||||
|
display_item.draw_into_context(render_context, current_transform, current_clip_stack)
|
||||||
|
}
|
||||||
|
|
||||||
// Step 5: Floats.
|
// Step 5: Floats.
|
||||||
self.append_from(&mut stacking_context.floats);
|
for display_item in display_list.floats.iter() {
|
||||||
|
display_item.draw_into_context(render_context, current_transform, current_clip_stack)
|
||||||
|
}
|
||||||
|
|
||||||
// TODO(pcwalton): Step 6: Inlines that generate stacking contexts.
|
// TODO(pcwalton): Step 6: Inlines that generate stacking contexts.
|
||||||
|
|
||||||
// Step 7: Content.
|
// Step 7: Content.
|
||||||
self.append_from(&mut stacking_context.content);
|
for display_item in display_list.content.iter() {
|
||||||
|
display_item.draw_into_context(render_context, current_transform, current_clip_stack)
|
||||||
|
}
|
||||||
|
|
||||||
// Steps 8 and 9: Positioned descendants with nonnegative z-indices.
|
// Steps 8 and 9: Positioned descendants with nonnegative z-indices.
|
||||||
for &(ref mut z_index, ref mut list) in stacking_context.positioned_descendants.iter_mut() {
|
for positioned_kid in positioned_children.iter() {
|
||||||
if *z_index >= 0 {
|
if positioned_kid.z_index < 0 {
|
||||||
self.append_from(list)
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if positioned_kid.layer.is_none() {
|
||||||
|
let new_transform =
|
||||||
|
current_transform.translate(positioned_kid.bounds.origin.x.to_nearest_px()
|
||||||
|
as AzFloat,
|
||||||
|
positioned_kid.bounds.origin.y.to_nearest_px()
|
||||||
|
as AzFloat);
|
||||||
|
let new_tile_rect =
|
||||||
|
self.compute_tile_rect_for_child_stacking_context(tile_bounds,
|
||||||
|
&**positioned_kid);
|
||||||
|
positioned_kid.optimize_and_draw_into_context(render_context,
|
||||||
|
&new_tile_rect,
|
||||||
|
&new_transform,
|
||||||
|
current_clip_stack);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(pcwalton): Step 10: Outlines.
|
// TODO(pcwalton): Step 10: Outlines.
|
||||||
|
|
||||||
self.set_stacking_level(resulting_level);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the stacking level for this display list and all its subitems.
|
/// Translate the given tile rect into the coordinate system of a child stacking context.
|
||||||
fn set_stacking_level(&mut self, new_level: StackingLevel) {
|
fn compute_tile_rect_for_child_stacking_context(&self,
|
||||||
for item in self.list.iter_mut() {
|
tile_bounds: &Rect<AzFloat>,
|
||||||
item.mut_base().level = new_level;
|
child_stacking_context: &StackingContext)
|
||||||
|
-> Rect<AzFloat> {
|
||||||
|
static ZERO_AZURE_RECT: Rect<f32> = Rect {
|
||||||
|
origin: Point2D {
|
||||||
|
x: 0.0,
|
||||||
|
y: 0.0,
|
||||||
|
},
|
||||||
|
size: Size2D {
|
||||||
|
width: 0.0,
|
||||||
|
height: 0.0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let child_stacking_context_bounds = child_stacking_context.bounds.to_azure_rect();
|
||||||
|
let tile_subrect = tile_bounds.intersection(&child_stacking_context_bounds)
|
||||||
|
.unwrap_or(ZERO_AZURE_RECT);
|
||||||
|
let offset = tile_subrect.origin - child_stacking_context_bounds.origin;
|
||||||
|
Rect(offset, tile_subrect.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Places all nodes containing the point of interest into `result`, topmost first. If
|
||||||
|
/// `topmost_only` is true, stops after placing one node into the list. `result` must be empty
|
||||||
|
/// upon entry to this function.
|
||||||
|
pub fn hit_test(&self,
|
||||||
|
point: Point2D<Au>,
|
||||||
|
result: &mut Vec<UntrustedNodeAddress>,
|
||||||
|
topmost_only: bool) {
|
||||||
|
fn hit_test_in_list<'a,I>(point: Point2D<Au>,
|
||||||
|
result: &mut Vec<UntrustedNodeAddress>,
|
||||||
|
topmost_only: bool,
|
||||||
|
mut iterator: I)
|
||||||
|
where I: Iterator<&'a DisplayItem> {
|
||||||
|
for item in iterator {
|
||||||
|
if geometry::rect_contains_point(item.base().clip_rect, point) &&
|
||||||
|
geometry::rect_contains_point(item.bounds(), point) {
|
||||||
|
result.push(item.base().node.to_untrusted_node_address());
|
||||||
|
if topmost_only {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
debug_assert!(!topmost_only || result.is_empty());
|
||||||
|
|
||||||
|
// Iterate through display items in reverse stacking order. Steps here refer to the
|
||||||
|
// painting steps in CSS 2.1 Appendix E.
|
||||||
|
//
|
||||||
|
// Steps 9 and 8: Positioned descendants with nonnegative z-indices.
|
||||||
|
for kid in self.display_list.children.iter().rev() {
|
||||||
|
if kid.z_index < 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
kid.hit_test(point, result, topmost_only);
|
||||||
|
if topmost_only && !result.is_empty() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Steps 7, 5, and 4: Content, floats, and block backgrounds and borders.
|
||||||
|
//
|
||||||
|
// TODO(pcwalton): Step 6: Inlines that generate stacking contexts.
|
||||||
|
for display_list in [
|
||||||
|
&self.display_list.content,
|
||||||
|
&self.display_list.floats,
|
||||||
|
&self.display_list.block_backgrounds_and_borders,
|
||||||
|
].iter() {
|
||||||
|
hit_test_in_list(point, result, topmost_only, display_list.iter().rev());
|
||||||
|
if topmost_only && !result.is_empty() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 3: Positioned descendants with negative z-indices.
|
||||||
|
for kid in self.display_list.children.iter().rev() {
|
||||||
|
if kid.z_index >= 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
kid.hit_test(point, result, topmost_only);
|
||||||
|
if topmost_only && !result.is_empty() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Steps 2 and 1: Borders and background for the root.
|
||||||
|
hit_test_in_list(point,
|
||||||
|
result,
|
||||||
|
topmost_only,
|
||||||
|
self.display_list.background_and_borders.iter().rev())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the stacking context in the given tree of stacking contexts with a specific layer ID.
|
||||||
|
pub fn find_stacking_context_with_layer_id(this: &Arc<StackingContext>, layer_id: LayerId)
|
||||||
|
-> Option<Arc<StackingContext>> {
|
||||||
|
match this.layer {
|
||||||
|
Some(ref layer) if layer.id == layer_id => return Some((*this).clone()),
|
||||||
|
Some(_) | None => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
for kid in this.display_list.children.iter() {
|
||||||
|
match find_stacking_context_with_layer_id(kid, layer_id) {
|
||||||
|
Some(stacking_context) => return Some(stacking_context),
|
||||||
|
None => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// One drawing command in the list.
|
/// One drawing command in the list.
|
||||||
|
@ -318,9 +392,6 @@ pub struct BaseDisplayItem {
|
||||||
/// The originating DOM node.
|
/// The originating DOM node.
|
||||||
pub node: OpaqueNode,
|
pub node: OpaqueNode,
|
||||||
|
|
||||||
/// The stacking level in which this display item lives.
|
|
||||||
pub level: StackingLevel,
|
|
||||||
|
|
||||||
/// The rectangle to clip to.
|
/// The rectangle to clip to.
|
||||||
///
|
///
|
||||||
/// TODO(pcwalton): Eventually, to handle `border-radius`, this will (at least) need to grow
|
/// TODO(pcwalton): Eventually, to handle `border-radius`, this will (at least) need to grow
|
||||||
|
@ -330,12 +401,10 @@ pub struct BaseDisplayItem {
|
||||||
|
|
||||||
impl BaseDisplayItem {
|
impl BaseDisplayItem {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn new(bounds: Rect<Au>, node: OpaqueNode, level: StackingLevel, clip_rect: Rect<Au>)
|
pub fn new(bounds: Rect<Au>, node: OpaqueNode, clip_rect: Rect<Au>) -> BaseDisplayItem {
|
||||||
-> BaseDisplayItem {
|
|
||||||
BaseDisplayItem {
|
BaseDisplayItem {
|
||||||
bounds: bounds,
|
bounds: bounds,
|
||||||
node: node,
|
node: node,
|
||||||
level: level,
|
|
||||||
clip_rect: clip_rect,
|
clip_rect: clip_rect,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -450,9 +519,6 @@ impl DisplayItem {
|
||||||
render_context: &mut RenderContext,
|
render_context: &mut RenderContext,
|
||||||
current_transform: &Matrix2D<AzFloat>,
|
current_transform: &Matrix2D<AzFloat>,
|
||||||
current_clip_stack: &mut Vec<Rect<Au>>) {
|
current_clip_stack: &mut Vec<Rect<Au>>) {
|
||||||
// This should have been flattened to the content stacking level first.
|
|
||||||
assert!(self.base().level == ContentStackingLevel);
|
|
||||||
|
|
||||||
// TODO(pcwalton): This will need some tweaking to deal with more complex clipping regions.
|
// TODO(pcwalton): This will need some tweaking to deal with more complex clipping regions.
|
||||||
let clip_rect = &self.base().clip_rect;
|
let clip_rect = &self.base().clip_rect;
|
||||||
if current_clip_stack.len() == 0 || current_clip_stack.last().unwrap() != clip_rect {
|
if current_clip_stack.len() == 0 || current_clip_stack.last().unwrap() != clip_rect {
|
||||||
|
@ -464,6 +530,8 @@ impl DisplayItem {
|
||||||
current_clip_stack.push(*clip_rect);
|
current_clip_stack.push(*clip_rect);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
render_context.draw_target.set_transform(current_transform);
|
||||||
|
|
||||||
match *self {
|
match *self {
|
||||||
SolidColorDisplayItemClass(ref solid_color) => {
|
SolidColorDisplayItemClass(ref solid_color) => {
|
||||||
render_context.draw_solid_color(&solid_color.base.bounds, solid_color.color)
|
render_context.draw_solid_color(&solid_color.base.bounds, solid_color.color)
|
||||||
|
@ -558,7 +626,7 @@ impl DisplayItem {
|
||||||
|
|
||||||
impl fmt::Show for DisplayItem {
|
impl fmt::Show for DisplayItem {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, "{} @ {} ({:x}) [{}]",
|
write!(f, "{} @ {} ({:x})",
|
||||||
match *self {
|
match *self {
|
||||||
SolidColorDisplayItemClass(_) => "SolidColor",
|
SolidColorDisplayItemClass(_) => "SolidColor",
|
||||||
TextDisplayItemClass(_) => "Text",
|
TextDisplayItemClass(_) => "Text",
|
||||||
|
@ -569,9 +637,25 @@ impl fmt::Show for DisplayItem {
|
||||||
PseudoDisplayItemClass(_) => "Pseudo",
|
PseudoDisplayItemClass(_) => "Pseudo",
|
||||||
},
|
},
|
||||||
self.base().bounds,
|
self.base().bounds,
|
||||||
self.base().node.id(),
|
self.base().node.id()
|
||||||
self.base().level
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait OpaqueNodeMethods {
|
||||||
|
/// Converts this node to an `UntrustedNodeAddress`. An `UntrustedNodeAddress` is just the type
|
||||||
|
/// of node that script expects to receive in a hit test.
|
||||||
|
fn to_untrusted_node_address(&self) -> UntrustedNodeAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl OpaqueNodeMethods for OpaqueNode {
|
||||||
|
fn to_untrusted_node_address(&self) -> UntrustedNodeAddress {
|
||||||
|
unsafe {
|
||||||
|
let OpaqueNode(addr) = *self;
|
||||||
|
let addr: UntrustedNodeAddress = mem::transmute(addr);
|
||||||
|
addr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,52 +2,66 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
use display_list::{DisplayItem, DisplayList};
|
//! Transforms a display list to produce a visually-equivalent, but cheaper-to-render, one.
|
||||||
|
|
||||||
|
use display_list::{DisplayItem, DisplayList, StackingContext};
|
||||||
|
|
||||||
use collections::dlist::DList;
|
use collections::dlist::DList;
|
||||||
use geom::rect::Rect;
|
use geom::rect::Rect;
|
||||||
use servo_util::geometry::Au;
|
use servo_util::geometry::{mod, Au};
|
||||||
use sync::Arc;
|
use sync::Arc;
|
||||||
|
|
||||||
|
/// Transforms a display list to produce a visually-equivalent, but cheaper-to-render, one.
|
||||||
pub struct DisplayListOptimizer {
|
pub struct DisplayListOptimizer {
|
||||||
display_list: Arc<DisplayList>,
|
|
||||||
/// The visible rect in page coordinates.
|
/// The visible rect in page coordinates.
|
||||||
visible_rect: Rect<Au>,
|
visible_rect: Rect<Au>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DisplayListOptimizer {
|
impl DisplayListOptimizer {
|
||||||
/// `visible_rect` specifies the visible rect in page coordinates.
|
/// Creates a new display list optimizer object. `visible_rect` specifies the visible rect in
|
||||||
pub fn new(display_list: Arc<DisplayList>, visible_rect: Rect<Au>) -> DisplayListOptimizer {
|
/// page coordinates.
|
||||||
|
pub fn new(visible_rect: &Rect<f32>) -> DisplayListOptimizer {
|
||||||
DisplayListOptimizer {
|
DisplayListOptimizer {
|
||||||
display_list: display_list,
|
visible_rect: geometry::f32_rect_to_au_rect(*visible_rect),
|
||||||
visible_rect: visible_rect,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn optimize(self) -> DisplayList {
|
/// Optimizes the given display list, returning an equivalent, but cheaper-to-paint, one.
|
||||||
self.process_display_list(&*self.display_list)
|
pub fn optimize(self, display_list: &DisplayList) -> DisplayList {
|
||||||
|
let mut result = DisplayList::new();
|
||||||
|
self.add_in_bounds_display_items(&mut result.background_and_borders,
|
||||||
|
display_list.background_and_borders.iter());
|
||||||
|
self.add_in_bounds_display_items(&mut result.block_backgrounds_and_borders,
|
||||||
|
display_list.block_backgrounds_and_borders.iter());
|
||||||
|
self.add_in_bounds_display_items(&mut result.floats, display_list.floats.iter());
|
||||||
|
self.add_in_bounds_display_items(&mut result.content, display_list.content.iter());
|
||||||
|
self.add_in_bounds_stacking_contexts(&mut result.children, display_list.children.iter());
|
||||||
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_display_list(&self, display_list: &DisplayList) -> DisplayList {
|
/// Adds display items that intersect the visible rect to `result_list`.
|
||||||
let mut result = DList::new();
|
fn add_in_bounds_display_items<'a,I>(&self,
|
||||||
for item in display_list.iter() {
|
result_list: &mut DList<DisplayItem>,
|
||||||
match self.process_display_item(item) {
|
mut display_items: I)
|
||||||
None => {}
|
where I: Iterator<&'a DisplayItem> {
|
||||||
Some(display_item) => result.push_back(display_item),
|
for display_item in display_items {
|
||||||
|
if self.visible_rect.intersects(&display_item.base().bounds) &&
|
||||||
|
self.visible_rect.intersects(&display_item.base().clip_rect) {
|
||||||
|
result_list.push_back((*display_item).clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DisplayList {
|
|
||||||
list: result,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_display_item(&self, display_item: &DisplayItem) -> Option<DisplayItem> {
|
/// Adds child stacking contexts whose boundaries intersect the visible rect to `result_list`.
|
||||||
// Eliminate display items outside the visible region.
|
fn add_in_bounds_stacking_contexts<'a,I>(&self,
|
||||||
if !self.visible_rect.intersects(&display_item.base().bounds) ||
|
result_list: &mut DList<Arc<StackingContext>>,
|
||||||
!self.visible_rect.intersects(&display_item.base().clip_rect) {
|
mut stacking_contexts: I)
|
||||||
None
|
where I: Iterator<&'a Arc<StackingContext>> {
|
||||||
} else {
|
for stacking_context in stacking_contexts {
|
||||||
Some((*display_item).clone())
|
if self.visible_rect.intersects(&stacking_context.bounds) &&
|
||||||
|
self.visible_rect.intersects(&stacking_context.clip_rect) {
|
||||||
|
result_list.push_back((*stacking_context).clone())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ extern crate native;
|
||||||
extern crate rustrt;
|
extern crate rustrt;
|
||||||
extern crate stb_image;
|
extern crate stb_image;
|
||||||
extern crate png;
|
extern crate png;
|
||||||
|
extern crate script_traits;
|
||||||
extern crate serialize;
|
extern crate serialize;
|
||||||
extern crate unicode;
|
extern crate unicode;
|
||||||
#[phase(plugin)]
|
#[phase(plugin)]
|
||||||
|
|
|
@ -467,7 +467,7 @@ impl<'a> RenderContext<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
trait ToAzurePoint {
|
pub trait ToAzurePoint {
|
||||||
fn to_azure_point(&self) -> Point2D<AzFloat>;
|
fn to_azure_point(&self) -> Point2D<AzFloat>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -477,7 +477,7 @@ impl ToAzurePoint for Point2D<Au> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
trait ToAzureRect {
|
pub trait ToAzureRect {
|
||||||
fn to_azure_rect(&self) -> Rect<AzFloat>;
|
fn to_azure_rect(&self) -> Rect<AzFloat>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,7 @@
|
||||||
//! The task that handles all rendering/painting.
|
//! The task that handles all rendering/painting.
|
||||||
|
|
||||||
use buffer_map::BufferMap;
|
use buffer_map::BufferMap;
|
||||||
use display_list::optimizer::DisplayListOptimizer;
|
use display_list::{mod, StackingContext};
|
||||||
use display_list::DisplayList;
|
|
||||||
use font_cache_task::FontCacheTask;
|
use font_cache_task::FontCacheTask;
|
||||||
use font_context::FontContext;
|
use font_context::FontContext;
|
||||||
use render_context::RenderContext;
|
use render_context::RenderContext;
|
||||||
|
@ -27,9 +26,9 @@ use servo_msg::compositor_msg::{LayerMetadata, RenderListener, RenderingRenderSt
|
||||||
use servo_msg::constellation_msg::{ConstellationChan, Failure, FailureMsg, PipelineId};
|
use servo_msg::constellation_msg::{ConstellationChan, Failure, FailureMsg, PipelineId};
|
||||||
use servo_msg::constellation_msg::{RendererReadyMsg};
|
use servo_msg::constellation_msg::{RendererReadyMsg};
|
||||||
use servo_msg::platform::surface::NativeSurfaceAzureMethods;
|
use servo_msg::platform::surface::NativeSurfaceAzureMethods;
|
||||||
use servo_util::geometry;
|
use servo_util::geometry::{Au, ZERO_POINT};
|
||||||
use servo_util::opts;
|
use servo_util::opts;
|
||||||
use servo_util::smallvec::{SmallVec, SmallVec1};
|
use servo_util::smallvec::SmallVec;
|
||||||
use servo_util::task::spawn_named_with_send_on_failure;
|
use servo_util::task::spawn_named_with_send_on_failure;
|
||||||
use servo_util::task_state;
|
use servo_util::task_state;
|
||||||
use servo_util::time::{TimeProfilerChan, profile};
|
use servo_util::time::{TimeProfilerChan, profile};
|
||||||
|
@ -39,21 +38,28 @@ use std::mem;
|
||||||
use std::task::TaskBuilder;
|
use std::task::TaskBuilder;
|
||||||
use sync::Arc;
|
use sync::Arc;
|
||||||
|
|
||||||
/// Information about a layer that layout sends to the painting task.
|
/// Information about a hardware graphics layer that layout sends to the painting task.
|
||||||
#[deriving(Clone)]
|
#[deriving(Clone)]
|
||||||
pub struct RenderLayer {
|
pub struct RenderLayer {
|
||||||
/// A per-pipeline ID describing this layer that should be stable across reflows.
|
/// A per-pipeline ID describing this layer that should be stable across reflows.
|
||||||
pub id: LayerId,
|
pub id: LayerId,
|
||||||
/// The display list describing the contents of this layer.
|
|
||||||
pub display_list: Arc<DisplayList>,
|
|
||||||
/// The position of the layer in pixels.
|
|
||||||
pub position: Rect<uint>,
|
|
||||||
/// The color of the background in this layer. Used for unrendered content.
|
/// The color of the background in this layer. Used for unrendered content.
|
||||||
pub background_color: Color,
|
pub background_color: Color,
|
||||||
/// The scrolling policy of this layer.
|
/// The scrolling policy of this layer.
|
||||||
pub scroll_policy: ScrollPolicy,
|
pub scroll_policy: ScrollPolicy,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl RenderLayer {
|
||||||
|
/// Creates a new `RenderLayer`.
|
||||||
|
pub fn new(id: LayerId, background_color: Color, scroll_policy: ScrollPolicy) -> RenderLayer {
|
||||||
|
RenderLayer {
|
||||||
|
id: id,
|
||||||
|
background_color: background_color,
|
||||||
|
scroll_policy: scroll_policy,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct RenderRequest {
|
pub struct RenderRequest {
|
||||||
pub buffer_requests: Vec<BufferRequest>,
|
pub buffer_requests: Vec<BufferRequest>,
|
||||||
pub scale: f32,
|
pub scale: f32,
|
||||||
|
@ -62,7 +68,7 @@ pub struct RenderRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum Msg {
|
pub enum Msg {
|
||||||
RenderInitMsg(SmallVec1<RenderLayer>),
|
RenderInitMsg(Arc<StackingContext>),
|
||||||
RenderMsg(Vec<RenderRequest>),
|
RenderMsg(Vec<RenderRequest>),
|
||||||
UnusedBufferMsg(Vec<Box<LayerBuffer>>),
|
UnusedBufferMsg(Vec<Box<LayerBuffer>>),
|
||||||
PaintPermissionGranted,
|
PaintPermissionGranted,
|
||||||
|
@ -102,8 +108,8 @@ pub struct RenderTask<C> {
|
||||||
/// The native graphics context.
|
/// The native graphics context.
|
||||||
native_graphics_context: Option<NativePaintingGraphicsContext>,
|
native_graphics_context: Option<NativePaintingGraphicsContext>,
|
||||||
|
|
||||||
/// The layers to be rendered.
|
/// The root stacking context sent to us by the layout thread.
|
||||||
render_layers: SmallVec1<RenderLayer>,
|
root_stacking_context: Option<Arc<StackingContext>>,
|
||||||
|
|
||||||
/// Permission to send paint messages to the compositor
|
/// Permission to send paint messages to the compositor
|
||||||
paint_permission: bool,
|
paint_permission: bool,
|
||||||
|
@ -129,17 +135,36 @@ macro_rules! native_graphics_context(
|
||||||
fn initialize_layers<C>(compositor: &mut C,
|
fn initialize_layers<C>(compositor: &mut C,
|
||||||
pipeline_id: PipelineId,
|
pipeline_id: PipelineId,
|
||||||
epoch: Epoch,
|
epoch: Epoch,
|
||||||
render_layers: &[RenderLayer])
|
root_stacking_context: &StackingContext)
|
||||||
where C: RenderListener {
|
where C: RenderListener {
|
||||||
let metadata = render_layers.iter().map(|render_layer| {
|
let mut metadata = Vec::new();
|
||||||
LayerMetadata {
|
build(&mut metadata, root_stacking_context, &ZERO_POINT);
|
||||||
id: render_layer.id,
|
|
||||||
position: render_layer.position,
|
|
||||||
background_color: render_layer.background_color,
|
|
||||||
scroll_policy: render_layer.scroll_policy,
|
|
||||||
}
|
|
||||||
}).collect();
|
|
||||||
compositor.initialize_layers_for_pipeline(pipeline_id, metadata, epoch);
|
compositor.initialize_layers_for_pipeline(pipeline_id, metadata, epoch);
|
||||||
|
|
||||||
|
fn build(metadata: &mut Vec<LayerMetadata>,
|
||||||
|
stacking_context: &StackingContext,
|
||||||
|
page_position: &Point2D<Au>) {
|
||||||
|
let page_position = stacking_context.bounds.origin + *page_position;
|
||||||
|
match stacking_context.layer {
|
||||||
|
None => {}
|
||||||
|
Some(ref render_layer) => {
|
||||||
|
metadata.push(LayerMetadata {
|
||||||
|
id: render_layer.id,
|
||||||
|
position:
|
||||||
|
Rect(Point2D(page_position.x.to_nearest_px() as uint,
|
||||||
|
page_position.y.to_nearest_px() as uint),
|
||||||
|
Size2D(stacking_context.bounds.size.width.to_nearest_px() as uint,
|
||||||
|
stacking_context.bounds.size.height.to_nearest_px() as uint)),
|
||||||
|
background_color: render_layer.background_color,
|
||||||
|
scroll_policy: render_layer.scroll_policy,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for kid in stacking_context.display_list.children.iter() {
|
||||||
|
build(metadata, &**kid, &page_position)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C> RenderTask<C> where C: RenderListener + Send {
|
impl<C> RenderTask<C> where C: RenderListener + Send {
|
||||||
|
@ -170,11 +195,8 @@ impl<C> RenderTask<C> where C: RenderListener + Send {
|
||||||
compositor: compositor,
|
compositor: compositor,
|
||||||
constellation_chan: constellation_chan,
|
constellation_chan: constellation_chan,
|
||||||
time_profiler_chan: time_profiler_chan,
|
time_profiler_chan: time_profiler_chan,
|
||||||
|
|
||||||
native_graphics_context: native_graphics_context,
|
native_graphics_context: native_graphics_context,
|
||||||
|
root_stacking_context: None,
|
||||||
render_layers: SmallVec1::new(),
|
|
||||||
|
|
||||||
paint_permission: false,
|
paint_permission: false,
|
||||||
epoch: Epoch(0),
|
epoch: Epoch(0),
|
||||||
buffer_map: BufferMap::new(10000000),
|
buffer_map: BufferMap::new(10000000),
|
||||||
|
@ -205,9 +227,9 @@ impl<C> RenderTask<C> where C: RenderListener + Send {
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
match self.port.recv() {
|
match self.port.recv() {
|
||||||
RenderInitMsg(render_layers) => {
|
RenderInitMsg(stacking_context) => {
|
||||||
self.epoch.next();
|
self.epoch.next();
|
||||||
self.render_layers = render_layers;
|
self.root_stacking_context = Some(stacking_context.clone());
|
||||||
|
|
||||||
if !self.paint_permission {
|
if !self.paint_permission {
|
||||||
debug!("render_task: render ready msg");
|
debug!("render_task: render ready msg");
|
||||||
|
@ -219,7 +241,7 @@ impl<C> RenderTask<C> where C: RenderListener + Send {
|
||||||
initialize_layers(&mut self.compositor,
|
initialize_layers(&mut self.compositor,
|
||||||
self.id,
|
self.id,
|
||||||
self.epoch,
|
self.epoch,
|
||||||
self.render_layers.as_slice());
|
&*stacking_context);
|
||||||
}
|
}
|
||||||
RenderMsg(requests) => {
|
RenderMsg(requests) => {
|
||||||
if !self.paint_permission {
|
if !self.paint_permission {
|
||||||
|
@ -254,15 +276,15 @@ impl<C> RenderTask<C> where C: RenderListener + Send {
|
||||||
PaintPermissionGranted => {
|
PaintPermissionGranted => {
|
||||||
self.paint_permission = true;
|
self.paint_permission = true;
|
||||||
|
|
||||||
// Here we assume that the main layer—the layer responsible for the page size—
|
match self.root_stacking_context {
|
||||||
// is the first layer. This is a pretty fragile assumption. It will be fixed
|
None => {}
|
||||||
// once we use the layers-based scrolling infrastructure for all scrolling.
|
Some(ref stacking_context) => {
|
||||||
if self.render_layers.len() > 1 {
|
self.epoch.next();
|
||||||
self.epoch.next();
|
initialize_layers(&mut self.compositor,
|
||||||
initialize_layers(&mut self.compositor,
|
self.id,
|
||||||
self.id,
|
self.epoch,
|
||||||
self.epoch,
|
&**stacking_context);
|
||||||
self.render_layers.as_slice());
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PaintPermissionRevoked => {
|
PaintPermissionRevoked => {
|
||||||
|
@ -327,9 +349,15 @@ impl<C> RenderTask<C> where C: RenderListener + Send {
|
||||||
scale: f32,
|
scale: f32,
|
||||||
layer_id: LayerId) {
|
layer_id: LayerId) {
|
||||||
time::profile(time::PaintingCategory, None, self.time_profiler_chan.clone(), || {
|
time::profile(time::PaintingCategory, None, self.time_profiler_chan.clone(), || {
|
||||||
// Bail out if there is no appropriate render layer.
|
// Bail out if there is no appropriate stacking context.
|
||||||
let render_layer = match self.render_layers.iter().find(|layer| layer.id == layer_id) {
|
let stacking_context = match self.root_stacking_context {
|
||||||
Some(render_layer) => (*render_layer).clone(),
|
Some(ref stacking_context) => {
|
||||||
|
match display_list::find_stacking_context_with_layer_id(stacking_context,
|
||||||
|
layer_id) {
|
||||||
|
Some(stacking_context) => stacking_context,
|
||||||
|
None => return,
|
||||||
|
}
|
||||||
|
}
|
||||||
None => return,
|
None => return,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -342,7 +370,7 @@ impl<C> RenderTask<C> where C: RenderListener + Send {
|
||||||
let layer_buffer = self.find_or_create_layer_buffer_for_tile(&tile, scale);
|
let layer_buffer = self.find_or_create_layer_buffer_for_tile(&tile, scale);
|
||||||
self.worker_threads[thread_id].paint_tile(tile,
|
self.worker_threads[thread_id].paint_tile(tile,
|
||||||
layer_buffer,
|
layer_buffer,
|
||||||
render_layer.clone(),
|
stacking_context.clone(),
|
||||||
scale);
|
scale);
|
||||||
}
|
}
|
||||||
let new_buffers = Vec::from_fn(tile_count, |i| {
|
let new_buffers = Vec::from_fn(tile_count, |i| {
|
||||||
|
@ -397,9 +425,9 @@ impl WorkerThreadProxy {
|
||||||
fn paint_tile(&mut self,
|
fn paint_tile(&mut self,
|
||||||
tile: BufferRequest,
|
tile: BufferRequest,
|
||||||
layer_buffer: Option<Box<LayerBuffer>>,
|
layer_buffer: Option<Box<LayerBuffer>>,
|
||||||
render_layer: RenderLayer,
|
stacking_context: Arc<StackingContext>,
|
||||||
scale: f32) {
|
scale: f32) {
|
||||||
self.sender.send(PaintTileMsgToWorkerThread(tile, layer_buffer, render_layer, scale))
|
self.sender.send(PaintTileMsgToWorkerThread(tile, layer_buffer, stacking_context, scale))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_painted_tile_buffer(&mut self) -> Box<LayerBuffer> {
|
fn get_painted_tile_buffer(&mut self) -> Box<LayerBuffer> {
|
||||||
|
@ -443,8 +471,8 @@ impl WorkerThread {
|
||||||
loop {
|
loop {
|
||||||
match self.receiver.recv() {
|
match self.receiver.recv() {
|
||||||
ExitMsgToWorkerThread => break,
|
ExitMsgToWorkerThread => break,
|
||||||
PaintTileMsgToWorkerThread(tile, layer_buffer, render_layer, scale) => {
|
PaintTileMsgToWorkerThread(tile, layer_buffer, stacking_context, scale) => {
|
||||||
let draw_target = self.optimize_and_paint_tile(&tile, render_layer, scale);
|
let draw_target = self.optimize_and_paint_tile(&tile, stacking_context, scale);
|
||||||
let buffer = self.create_layer_buffer_for_painted_tile(&tile,
|
let buffer = self.create_layer_buffer_for_painted_tile(&tile,
|
||||||
layer_buffer,
|
layer_buffer,
|
||||||
draw_target,
|
draw_target,
|
||||||
|
@ -457,21 +485,9 @@ impl WorkerThread {
|
||||||
|
|
||||||
fn optimize_and_paint_tile(&mut self,
|
fn optimize_and_paint_tile(&mut self,
|
||||||
tile: &BufferRequest,
|
tile: &BufferRequest,
|
||||||
render_layer: RenderLayer,
|
stacking_context: Arc<StackingContext>,
|
||||||
scale: f32)
|
scale: f32)
|
||||||
-> DrawTarget {
|
-> DrawTarget {
|
||||||
// page_rect is in coordinates relative to the layer origin, but all display list
|
|
||||||
// components are relative to the page origin. We make page_rect relative to
|
|
||||||
// the page origin before passing it to the optimizer.
|
|
||||||
let page_rect = tile.page_rect.translate(&Point2D(render_layer.position.origin.x as f32,
|
|
||||||
render_layer.position.origin.y as f32));
|
|
||||||
let page_rect_au = geometry::f32_rect_to_au_rect(page_rect);
|
|
||||||
|
|
||||||
// Optimize the display list for this tile.
|
|
||||||
let optimizer = DisplayListOptimizer::new(render_layer.display_list.clone(),
|
|
||||||
page_rect_au);
|
|
||||||
let display_list = optimizer.optimize();
|
|
||||||
|
|
||||||
let size = Size2D(tile.screen_rect.size.width as i32, tile.screen_rect.size.height as i32);
|
let size = Size2D(tile.screen_rect.size.width as i32, tile.screen_rect.size.height as i32);
|
||||||
let draw_target = if !opts::get().gpu_painting {
|
let draw_target = if !opts::get().gpu_painting {
|
||||||
DrawTarget::new(SkiaBackend, size, B8G8R8A8)
|
DrawTarget::new(SkiaBackend, size, B8G8R8A8)
|
||||||
|
@ -496,10 +512,11 @@ impl WorkerThread {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Apply the translation to render the tile we want.
|
// Apply the translation to render the tile we want.
|
||||||
|
let tile_bounds = tile.page_rect;
|
||||||
let matrix: Matrix2D<AzFloat> = Matrix2D::identity();
|
let matrix: Matrix2D<AzFloat> = Matrix2D::identity();
|
||||||
let matrix = matrix.scale(scale as AzFloat, scale as AzFloat);
|
let matrix = matrix.scale(scale as AzFloat, scale as AzFloat);
|
||||||
let matrix = matrix.translate(-page_rect.origin.x as AzFloat,
|
let matrix = matrix.translate(-tile_bounds.origin.x as AzFloat,
|
||||||
-page_rect.origin.y as AzFloat);
|
-tile_bounds.origin.y as AzFloat);
|
||||||
|
|
||||||
render_context.draw_target.set_transform(&matrix);
|
render_context.draw_target.set_transform(&matrix);
|
||||||
|
|
||||||
|
@ -509,7 +526,10 @@ impl WorkerThread {
|
||||||
// Draw the display list.
|
// Draw the display list.
|
||||||
profile(time::PaintingPerTileCategory, None, self.time_profiler_sender.clone(), || {
|
profile(time::PaintingPerTileCategory, None, self.time_profiler_sender.clone(), || {
|
||||||
let mut clip_stack = Vec::new();
|
let mut clip_stack = Vec::new();
|
||||||
display_list.draw_into_context(&mut render_context, &matrix, &mut clip_stack);
|
stacking_context.optimize_and_draw_into_context(&mut render_context,
|
||||||
|
&tile.page_rect,
|
||||||
|
&matrix,
|
||||||
|
&mut clip_stack);
|
||||||
render_context.draw_target.flush();
|
render_context.draw_target.flush();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -564,7 +584,7 @@ impl WorkerThread {
|
||||||
|
|
||||||
enum MsgToWorkerThread {
|
enum MsgToWorkerThread {
|
||||||
ExitMsgToWorkerThread,
|
ExitMsgToWorkerThread,
|
||||||
PaintTileMsgToWorkerThread(BufferRequest, Option<Box<LayerBuffer>>, RenderLayer, f32),
|
PaintTileMsgToWorkerThread(BufferRequest, Option<Box<LayerBuffer>>, Arc<StackingContext>, f32),
|
||||||
}
|
}
|
||||||
|
|
||||||
enum MsgFromWorkerThread {
|
enum MsgFromWorkerThread {
|
||||||
|
|
|
@ -30,9 +30,9 @@
|
||||||
use construct::FlowConstructor;
|
use construct::FlowConstructor;
|
||||||
use context::LayoutContext;
|
use context::LayoutContext;
|
||||||
use css::node_style::StyledNode;
|
use css::node_style::StyledNode;
|
||||||
use display_list_builder::{BlockFlowDisplayListBuilding, FragmentDisplayListBuilding};
|
use display_list_builder::{BlockFlowDisplayListBuilding, BlockLevel, FragmentDisplayListBuilding};
|
||||||
use floats::{ClearBoth, ClearLeft, ClearRight, FloatKind, FloatLeft, Floats, PlacementInfo};
|
use floats::{ClearBoth, ClearLeft, ClearRight, FloatKind, FloatLeft, Floats, PlacementInfo};
|
||||||
use flow::{BaseFlow, BlockFlowClass, FlowClass, Flow, ImmutableFlowUtils};
|
use flow::{AbsolutePositionInfo, BaseFlow, BlockFlowClass, FlowClass, Flow, ImmutableFlowUtils};
|
||||||
use flow::{MutableFlowUtils, PreorderFlowTraversal, PostorderFlowTraversal, mut_base};
|
use flow::{MutableFlowUtils, PreorderFlowTraversal, PostorderFlowTraversal, mut_base};
|
||||||
use flow;
|
use flow;
|
||||||
use fragment::{Fragment, ImageFragment, InlineBlockFragment, FragmentBoundsIterator};
|
use fragment::{Fragment, ImageFragment, InlineBlockFragment, FragmentBoundsIterator};
|
||||||
|
@ -45,10 +45,9 @@ use table::ColumnInlineSize;
|
||||||
use wrapper::ThreadSafeLayoutNode;
|
use wrapper::ThreadSafeLayoutNode;
|
||||||
|
|
||||||
use geom::Size2D;
|
use geom::Size2D;
|
||||||
use gfx::display_list::BlockLevel;
|
|
||||||
use serialize::{Encoder, Encodable};
|
use serialize::{Encoder, Encodable};
|
||||||
use servo_msg::compositor_msg::LayerId;
|
use servo_msg::compositor_msg::LayerId;
|
||||||
use servo_util::geometry::{Au, MAX_AU};
|
use servo_util::geometry::{Au, MAX_AU, MAX_RECT, ZERO_POINT};
|
||||||
use servo_util::logical_geometry::{LogicalPoint, LogicalRect, LogicalSize};
|
use servo_util::logical_geometry::{LogicalPoint, LogicalRect, LogicalSize};
|
||||||
use servo_util::opts;
|
use servo_util::opts;
|
||||||
use std::cmp::{max, min};
|
use std::cmp::{max, min};
|
||||||
|
@ -1665,24 +1664,26 @@ impl Flow for BlockFlow {
|
||||||
// FIXME(#2795): Get the real container size
|
// FIXME(#2795): Get the real container size
|
||||||
let container_size = Size2D::zero();
|
let container_size = Size2D::zero();
|
||||||
|
|
||||||
|
if self.is_root() {
|
||||||
|
self.base.clip_rect = MAX_RECT
|
||||||
|
}
|
||||||
|
|
||||||
if self.base.flags.is_absolutely_positioned() {
|
if self.base.flags.is_absolutely_positioned() {
|
||||||
let position_start = self.base.position.start.to_physical(self.base.writing_mode,
|
let position_start = self.base.position.start.to_physical(self.base.writing_mode,
|
||||||
container_size);
|
container_size);
|
||||||
self.base.absolute_position_info.absolute_containing_block_position =
|
|
||||||
if self.is_fixed() {
|
|
||||||
// The viewport is initially at (0, 0).
|
|
||||||
position_start
|
|
||||||
} else {
|
|
||||||
// Absolute position of the containing block + position of absolute
|
|
||||||
// flow w.r.t. the containing block.
|
|
||||||
self.base.absolute_position_info.absolute_containing_block_position
|
|
||||||
+ position_start
|
|
||||||
};
|
|
||||||
|
|
||||||
// Set the absolute position, which will be passed down later as part
|
// Compute our position relative to the nearest ancestor stacking context. This will be
|
||||||
// of containing block details for absolute descendants.
|
// passed down later as part of containing block details for absolute descendants.
|
||||||
self.base.abs_position =
|
self.base.stacking_relative_position = if self.is_fixed() {
|
||||||
self.base.absolute_position_info.absolute_containing_block_position;
|
// The viewport is initially at (0, 0).
|
||||||
|
position_start
|
||||||
|
} else {
|
||||||
|
// Absolute position of the containing block + position of absolute
|
||||||
|
// flow w.r.t. the containing block.
|
||||||
|
self.base
|
||||||
|
.absolute_position_info
|
||||||
|
.stacking_relative_position_of_absolute_containing_block + position_start
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// For relatively-positioned descendants, the containing block formed by a block is just
|
// For relatively-positioned descendants, the containing block formed by a block is just
|
||||||
|
@ -1693,32 +1694,52 @@ impl Flow for BlockFlow {
|
||||||
.absolute_position_info
|
.absolute_position_info
|
||||||
.relative_containing_block_size);
|
.relative_containing_block_size);
|
||||||
if self.is_positioned() {
|
if self.is_positioned() {
|
||||||
self.base.absolute_position_info.absolute_containing_block_position =
|
self.base
|
||||||
self.base.abs_position
|
.absolute_position_info
|
||||||
+ (self.generated_containing_block_rect().start
|
.stacking_relative_position_of_absolute_containing_block =
|
||||||
+ relative_offset).to_physical(self.base.writing_mode, container_size)
|
self.base.stacking_relative_position +
|
||||||
|
(self.generated_containing_block_rect().start +
|
||||||
|
relative_offset).to_physical(self.base.writing_mode, container_size)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute absolute position info for children.
|
// Compute absolute position info for children.
|
||||||
let mut absolute_position_info = self.base.absolute_position_info;
|
let absolute_position_info_for_children = AbsolutePositionInfo {
|
||||||
absolute_position_info.relative_containing_block_size = self.fragment.content_box().size;
|
stacking_relative_position_of_absolute_containing_block:
|
||||||
absolute_position_info.layers_needed_for_positioned_flows =
|
if self.fragment.establishes_stacking_context() {
|
||||||
self.base.flags.layers_needed_for_descendants();
|
let logical_border_width = self.fragment.style().logical_border_width();
|
||||||
|
LogicalPoint::new(self.base.writing_mode,
|
||||||
|
logical_border_width.inline_start,
|
||||||
|
logical_border_width.block_start).to_physical(
|
||||||
|
self.base.writing_mode,
|
||||||
|
container_size)
|
||||||
|
} else {
|
||||||
|
self.base
|
||||||
|
.absolute_position_info
|
||||||
|
.stacking_relative_position_of_absolute_containing_block
|
||||||
|
},
|
||||||
|
relative_containing_block_size: self.fragment.content_box().size,
|
||||||
|
layers_needed_for_positioned_flows: self.base.flags.layers_needed_for_descendants(),
|
||||||
|
};
|
||||||
|
|
||||||
// Compute the clipping rectangle for children.
|
// Compute the origin and clipping rectangle for children.
|
||||||
let this_position = self.base.abs_position;
|
let origin_for_children = if self.fragment.establishes_stacking_context() {
|
||||||
let clip_rect = self.fragment.clip_rect_for_children(self.base.clip_rect, this_position);
|
ZERO_POINT
|
||||||
|
} else {
|
||||||
|
self.base.stacking_relative_position
|
||||||
|
};
|
||||||
|
let clip_rect = self.fragment.clip_rect_for_children(self.base.clip_rect,
|
||||||
|
origin_for_children);
|
||||||
|
|
||||||
// Process children.
|
// Process children.
|
||||||
let writing_mode = self.base.writing_mode;
|
let writing_mode = self.base.writing_mode;
|
||||||
for kid in self.base.child_iter() {
|
for kid in self.base.child_iter() {
|
||||||
if !flow::base(kid).flags.is_absolutely_positioned() {
|
if !flow::base(kid).flags.is_absolutely_positioned() {
|
||||||
let kid_base = flow::mut_base(kid);
|
let kid_base = flow::mut_base(kid);
|
||||||
kid_base.abs_position =
|
kid_base.stacking_relative_position =
|
||||||
this_position +
|
origin_for_children +
|
||||||
(kid_base.position.start + relative_offset).to_physical(writing_mode,
|
(kid_base.position.start + relative_offset).to_physical(writing_mode,
|
||||||
container_size);
|
container_size);
|
||||||
kid_base.absolute_position_info = absolute_position_info
|
kid_base.absolute_position_info = absolute_position_info_for_children
|
||||||
}
|
}
|
||||||
|
|
||||||
flow::mut_base(kid).clip_rect = clip_rect
|
flow::mut_base(kid).clip_rect = clip_rect
|
||||||
|
@ -1726,7 +1747,8 @@ impl Flow for BlockFlow {
|
||||||
|
|
||||||
// Process absolute descendant links.
|
// Process absolute descendant links.
|
||||||
for absolute_descendant in self.base.abs_descendants.iter() {
|
for absolute_descendant in self.base.abs_descendants.iter() {
|
||||||
flow::mut_base(absolute_descendant).absolute_position_info = absolute_position_info
|
flow::mut_base(absolute_descendant).absolute_position_info =
|
||||||
|
absolute_position_info_for_children
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1816,9 +1838,10 @@ impl Flow for BlockFlow {
|
||||||
|
|
||||||
fn iterate_through_fragment_bounds(&self, iterator: &mut FragmentBoundsIterator) {
|
fn iterate_through_fragment_bounds(&self, iterator: &mut FragmentBoundsIterator) {
|
||||||
if iterator.should_process(&self.fragment) {
|
if iterator.should_process(&self.fragment) {
|
||||||
let fragment_origin = self.base.child_fragment_absolute_position(&self.fragment);
|
let fragment_origin =
|
||||||
|
self.base.stacking_relative_position_of_child_fragment(&self.fragment);
|
||||||
iterator.process(&self.fragment,
|
iterator.process(&self.fragment,
|
||||||
self.fragment.abs_bounds_from_origin(&fragment_origin));
|
self.fragment.stacking_relative_bounds(&fragment_origin));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,27 +21,22 @@ use fragment::{UnscannedTextFragment};
|
||||||
use model;
|
use model;
|
||||||
use util::{OpaqueNodeMethods, ToGfxColor};
|
use util::{OpaqueNodeMethods, ToGfxColor};
|
||||||
|
|
||||||
use collections::dlist::DList;
|
|
||||||
use geom::approxeq::ApproxEq;
|
use geom::approxeq::ApproxEq;
|
||||||
use geom::{Point2D, Rect, Size2D, SideOffsets2D};
|
use geom::{Point2D, Rect, Size2D, SideOffsets2D};
|
||||||
use gfx::color;
|
use gfx::color;
|
||||||
use gfx::display_list::{BackgroundAndBorderLevel, BaseDisplayItem, BorderDisplayItem};
|
use gfx::display_list::{BaseDisplayItem, BorderDisplayItem, BorderDisplayItemClass, DisplayItem};
|
||||||
use gfx::display_list::{BorderDisplayItemClass, ContentStackingLevel, DisplayList};
|
use gfx::display_list::{DisplayList, GradientDisplayItem, GradientDisplayItemClass, GradientStop};
|
||||||
use gfx::display_list::{FloatStackingLevel, GradientDisplayItem, GradientDisplayItemClass};
|
use gfx::display_list::{ImageDisplayItem, ImageDisplayItemClass, LineDisplayItem};
|
||||||
use gfx::display_list::{GradientStop, ImageDisplayItem, ImageDisplayItemClass, LineDisplayItem};
|
use gfx::display_list::{LineDisplayItemClass, PseudoDisplayItemClass, SidewaysLeft, SidewaysRight};
|
||||||
use gfx::display_list::{LineDisplayItemClass, PositionedDescendantStackingLevel};
|
use gfx::display_list::{SolidColorDisplayItem, SolidColorDisplayItemClass, StackingContext};
|
||||||
use gfx::display_list::{PseudoDisplayItemClass, RootOfStackingContextLevel, SidewaysLeft};
|
use gfx::display_list::{TextDisplayItem, TextDisplayItemClass, Upright};
|
||||||
use gfx::display_list::{SidewaysRight, SolidColorDisplayItem, SolidColorDisplayItemClass};
|
|
||||||
use gfx::display_list::{StackingLevel, TextDisplayItem, TextDisplayItemClass, Upright};
|
|
||||||
use gfx::render_task::RenderLayer;
|
use gfx::render_task::RenderLayer;
|
||||||
use servo_msg::compositor_msg::{FixedPosition, Scrollable};
|
use servo_msg::compositor_msg::{FixedPosition, Scrollable};
|
||||||
use servo_msg::constellation_msg::{ConstellationChan, FrameRectMsg};
|
use servo_msg::constellation_msg::{ConstellationChan, FrameRectMsg};
|
||||||
use servo_net::image::holder::ImageHolder;
|
use servo_net::image::holder::ImageHolder;
|
||||||
use servo_util::dlist;
|
use servo_util::geometry::{mod, Au, ZERO_POINT, ZERO_RECT};
|
||||||
use servo_util::geometry::{mod, Au, ZERO_RECT};
|
|
||||||
use servo_util::logical_geometry::{LogicalRect, WritingMode};
|
use servo_util::logical_geometry::{LogicalRect, WritingMode};
|
||||||
use servo_util::opts;
|
use servo_util::opts;
|
||||||
use std::mem;
|
|
||||||
use style::computed::{AngleAoc, CornerAoc, LP_Length, LP_Percentage, LengthOrPercentage};
|
use style::computed::{AngleAoc, CornerAoc, LP_Length, LP_Percentage, LengthOrPercentage};
|
||||||
use style::computed::{LinearGradient, LinearGradientImage, UrlImage};
|
use style::computed::{LinearGradient, LinearGradientImage, UrlImage};
|
||||||
use style::computed_values::{background_attachment, background_repeat, border_style, overflow};
|
use style::computed_values::{background_attachment, background_repeat, border_style, overflow};
|
||||||
|
@ -50,12 +45,36 @@ use style::{ComputedValues, Bottom, Left, RGBA, Right, Top};
|
||||||
use sync::Arc;
|
use sync::Arc;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
/// The results of display list building for a single flow.
|
||||||
|
pub enum DisplayListBuildingResult {
|
||||||
|
NoDisplayListBuildingResult,
|
||||||
|
StackingContextResult(Arc<StackingContext>),
|
||||||
|
DisplayListResult(Box<DisplayList>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DisplayListBuildingResult {
|
||||||
|
/// Adds the display list items contained within this display list building result to the given
|
||||||
|
/// display list, preserving stacking order. If this display list building result does not
|
||||||
|
/// consist of an entire stacking context, it will be emptied.
|
||||||
|
pub fn add_to(&mut self, display_list: &mut DisplayList) {
|
||||||
|
match *self {
|
||||||
|
NoDisplayListBuildingResult => return,
|
||||||
|
StackingContextResult(ref mut stacking_context) => {
|
||||||
|
display_list.children.push_back((*stacking_context).clone())
|
||||||
|
}
|
||||||
|
DisplayListResult(ref mut source_display_list) => {
|
||||||
|
display_list.append_from(&mut **source_display_list)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub trait FragmentDisplayListBuilding {
|
pub trait FragmentDisplayListBuilding {
|
||||||
/// Adds the display items necessary to paint the background of this fragment to the display
|
/// Adds the display items necessary to paint the background of this fragment to the display
|
||||||
/// list if necessary.
|
/// list if necessary.
|
||||||
fn build_display_list_for_background_if_applicable(&self,
|
fn build_display_list_for_background_if_applicable(&self,
|
||||||
style: &ComputedValues,
|
style: &ComputedValues,
|
||||||
list: &mut DisplayList,
|
display_list: &mut DisplayList,
|
||||||
layout_context: &LayoutContext,
|
layout_context: &LayoutContext,
|
||||||
level: StackingLevel,
|
level: StackingLevel,
|
||||||
absolute_bounds: &Rect<Au>,
|
absolute_bounds: &Rect<Au>,
|
||||||
|
@ -65,7 +84,7 @@ pub trait FragmentDisplayListBuilding {
|
||||||
/// display list at the appropriate stacking level.
|
/// display list at the appropriate stacking level.
|
||||||
fn build_display_list_for_background_image(&self,
|
fn build_display_list_for_background_image(&self,
|
||||||
style: &ComputedValues,
|
style: &ComputedValues,
|
||||||
list: &mut DisplayList,
|
display_list: &mut DisplayList,
|
||||||
layout_context: &LayoutContext,
|
layout_context: &LayoutContext,
|
||||||
level: StackingLevel,
|
level: StackingLevel,
|
||||||
absolute_bounds: &Rect<Au>,
|
absolute_bounds: &Rect<Au>,
|
||||||
|
@ -75,7 +94,7 @@ pub trait FragmentDisplayListBuilding {
|
||||||
/// Adds the display items necessary to paint the background linear gradient of this fragment
|
/// Adds the display items necessary to paint the background linear gradient of this fragment
|
||||||
/// to the display list at the appropriate stacking level.
|
/// to the display list at the appropriate stacking level.
|
||||||
fn build_display_list_for_background_linear_gradient(&self,
|
fn build_display_list_for_background_linear_gradient(&self,
|
||||||
list: &mut DisplayList,
|
display_list: &mut DisplayList,
|
||||||
level: StackingLevel,
|
level: StackingLevel,
|
||||||
absolute_bounds: &Rect<Au>,
|
absolute_bounds: &Rect<Au>,
|
||||||
clip_rect: &Rect<Au>,
|
clip_rect: &Rect<Au>,
|
||||||
|
@ -86,7 +105,7 @@ pub trait FragmentDisplayListBuilding {
|
||||||
/// necessary.
|
/// necessary.
|
||||||
fn build_display_list_for_borders_if_applicable(&self,
|
fn build_display_list_for_borders_if_applicable(&self,
|
||||||
style: &ComputedValues,
|
style: &ComputedValues,
|
||||||
list: &mut DisplayList,
|
display_list: &mut DisplayList,
|
||||||
abs_bounds: &Rect<Au>,
|
abs_bounds: &Rect<Au>,
|
||||||
level: StackingLevel,
|
level: StackingLevel,
|
||||||
clip_rect: &Rect<Au>);
|
clip_rect: &Rect<Au>);
|
||||||
|
@ -102,11 +121,11 @@ pub trait FragmentDisplayListBuilding {
|
||||||
flow_origin: Point2D<Au>,
|
flow_origin: Point2D<Au>,
|
||||||
clip_rect: &Rect<Au>);
|
clip_rect: &Rect<Au>);
|
||||||
|
|
||||||
/// Adds the display items for this fragment to the given stacking context.
|
/// Adds the display items for this fragment to the given display list.
|
||||||
///
|
///
|
||||||
/// Arguments:
|
/// Arguments:
|
||||||
///
|
///
|
||||||
/// * `display_list`: The unflattened display list to add display items to.
|
/// * `display_list`: The display list to add display items to.
|
||||||
/// * `layout_context`: The layout context.
|
/// * `layout_context`: The layout context.
|
||||||
/// * `dirty`: The dirty rectangle in the coordinate system of the owning flow.
|
/// * `dirty`: The dirty rectangle in the coordinate system of the owning flow.
|
||||||
/// * `flow_origin`: Position of the origin of the owning flow wrt the display list root flow.
|
/// * `flow_origin`: Position of the origin of the owning flow wrt the display list root flow.
|
||||||
|
@ -132,7 +151,7 @@ pub trait FragmentDisplayListBuilding {
|
||||||
impl FragmentDisplayListBuilding for Fragment {
|
impl FragmentDisplayListBuilding for Fragment {
|
||||||
fn build_display_list_for_background_if_applicable(&self,
|
fn build_display_list_for_background_if_applicable(&self,
|
||||||
style: &ComputedValues,
|
style: &ComputedValues,
|
||||||
list: &mut DisplayList,
|
display_list: &mut DisplayList,
|
||||||
layout_context: &LayoutContext,
|
layout_context: &LayoutContext,
|
||||||
level: StackingLevel,
|
level: StackingLevel,
|
||||||
absolute_bounds: &Rect<Au>,
|
absolute_bounds: &Rect<Au>,
|
||||||
|
@ -143,10 +162,10 @@ impl FragmentDisplayListBuilding for Fragment {
|
||||||
// doesn't have a fragment".
|
// doesn't have a fragment".
|
||||||
let background_color = style.resolve_color(style.get_background().background_color);
|
let background_color = style.resolve_color(style.get_background().background_color);
|
||||||
if !background_color.alpha.approx_eq(&0.0) {
|
if !background_color.alpha.approx_eq(&0.0) {
|
||||||
list.push(SolidColorDisplayItemClass(box SolidColorDisplayItem {
|
display_list.push(SolidColorDisplayItemClass(box SolidColorDisplayItem {
|
||||||
base: BaseDisplayItem::new(*absolute_bounds, self.node, level, *clip_rect),
|
base: BaseDisplayItem::new(*absolute_bounds, self.node, *clip_rect),
|
||||||
color: background_color.to_gfx_color(),
|
color: background_color.to_gfx_color(),
|
||||||
}));
|
}), level);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The background image is painted on top of the background color.
|
// The background image is painted on top of the background color.
|
||||||
|
@ -156,7 +175,7 @@ impl FragmentDisplayListBuilding for Fragment {
|
||||||
match background.background_image {
|
match background.background_image {
|
||||||
None => {}
|
None => {}
|
||||||
Some(LinearGradientImage(ref gradient)) => {
|
Some(LinearGradientImage(ref gradient)) => {
|
||||||
self.build_display_list_for_background_linear_gradient(list,
|
self.build_display_list_for_background_linear_gradient(display_list,
|
||||||
level,
|
level,
|
||||||
absolute_bounds,
|
absolute_bounds,
|
||||||
clip_rect,
|
clip_rect,
|
||||||
|
@ -165,7 +184,7 @@ impl FragmentDisplayListBuilding for Fragment {
|
||||||
}
|
}
|
||||||
Some(UrlImage(ref image_url)) => {
|
Some(UrlImage(ref image_url)) => {
|
||||||
self.build_display_list_for_background_image(style,
|
self.build_display_list_for_background_image(style,
|
||||||
list,
|
display_list,
|
||||||
layout_context,
|
layout_context,
|
||||||
level,
|
level,
|
||||||
absolute_bounds,
|
absolute_bounds,
|
||||||
|
@ -177,7 +196,7 @@ impl FragmentDisplayListBuilding for Fragment {
|
||||||
|
|
||||||
fn build_display_list_for_background_image(&self,
|
fn build_display_list_for_background_image(&self,
|
||||||
style: &ComputedValues,
|
style: &ComputedValues,
|
||||||
list: &mut DisplayList,
|
display_list: &mut DisplayList,
|
||||||
layout_context: &LayoutContext,
|
layout_context: &LayoutContext,
|
||||||
level: StackingLevel,
|
level: StackingLevel,
|
||||||
absolute_bounds: &Rect<Au>,
|
absolute_bounds: &Rect<Au>,
|
||||||
|
@ -255,16 +274,16 @@ impl FragmentDisplayListBuilding for Fragment {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create the image display item.
|
// Create the image display item.
|
||||||
list.push(ImageDisplayItemClass(box ImageDisplayItem {
|
display_list.push(ImageDisplayItemClass(box ImageDisplayItem {
|
||||||
base: BaseDisplayItem::new(bounds, self.node, level, clip_rect),
|
base: BaseDisplayItem::new(bounds, self.node, clip_rect),
|
||||||
image: image.clone(),
|
image: image.clone(),
|
||||||
stretch_size: Size2D(Au::from_px(image.width as int),
|
stretch_size: Size2D(Au::from_px(image.width as int),
|
||||||
Au::from_px(image.height as int)),
|
Au::from_px(image.height as int)),
|
||||||
}));
|
}), level);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_display_list_for_background_linear_gradient(&self,
|
fn build_display_list_for_background_linear_gradient(&self,
|
||||||
list: &mut DisplayList,
|
display_list: &mut DisplayList,
|
||||||
level: StackingLevel,
|
level: StackingLevel,
|
||||||
absolute_bounds: &Rect<Au>,
|
absolute_bounds: &Rect<Au>,
|
||||||
clip_rect: &Rect<Au>,
|
clip_rect: &Rect<Au>,
|
||||||
|
@ -364,18 +383,18 @@ impl FragmentDisplayListBuilding for Fragment {
|
||||||
absolute_bounds.origin.y + absolute_bounds.size.height / 2);
|
absolute_bounds.origin.y + absolute_bounds.size.height / 2);
|
||||||
|
|
||||||
let gradient_display_item = GradientDisplayItemClass(box GradientDisplayItem {
|
let gradient_display_item = GradientDisplayItemClass(box GradientDisplayItem {
|
||||||
base: BaseDisplayItem::new(*absolute_bounds, self.node, level, clip_rect),
|
base: BaseDisplayItem::new(*absolute_bounds, self.node, clip_rect),
|
||||||
start_point: center - delta,
|
start_point: center - delta,
|
||||||
end_point: center + delta,
|
end_point: center + delta,
|
||||||
stops: stops,
|
stops: stops,
|
||||||
});
|
});
|
||||||
|
|
||||||
list.push(gradient_display_item)
|
display_list.push(gradient_display_item, level)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_display_list_for_borders_if_applicable(&self,
|
fn build_display_list_for_borders_if_applicable(&self,
|
||||||
style: &ComputedValues,
|
style: &ComputedValues,
|
||||||
list: &mut DisplayList,
|
display_list: &mut DisplayList,
|
||||||
abs_bounds: &Rect<Au>,
|
abs_bounds: &Rect<Au>,
|
||||||
level: StackingLevel,
|
level: StackingLevel,
|
||||||
clip_rect: &Rect<Au>) {
|
clip_rect: &Rect<Au>) {
|
||||||
|
@ -390,8 +409,8 @@ impl FragmentDisplayListBuilding for Fragment {
|
||||||
let left_color = style.resolve_color(style.get_border().border_left_color);
|
let left_color = style.resolve_color(style.get_border().border_left_color);
|
||||||
|
|
||||||
// Append the border to the display list.
|
// Append the border to the display list.
|
||||||
list.push(BorderDisplayItemClass(box BorderDisplayItem {
|
display_list.push(BorderDisplayItemClass(box BorderDisplayItem {
|
||||||
base: BaseDisplayItem::new(*abs_bounds, self.node, level, *clip_rect),
|
base: BaseDisplayItem::new(*abs_bounds, self.node, *clip_rect),
|
||||||
border: border.to_physical(style.writing_mode),
|
border: border.to_physical(style.writing_mode),
|
||||||
color: SideOffsets2D::new(top_color.to_gfx_color(),
|
color: SideOffsets2D::new(top_color.to_gfx_color(),
|
||||||
right_color.to_gfx_color(),
|
right_color.to_gfx_color(),
|
||||||
|
@ -401,7 +420,7 @@ impl FragmentDisplayListBuilding for Fragment {
|
||||||
style.get_border().border_right_style,
|
style.get_border().border_right_style,
|
||||||
style.get_border().border_bottom_style,
|
style.get_border().border_bottom_style,
|
||||||
style.get_border().border_left_style)
|
style.get_border().border_left_style)
|
||||||
}));
|
}), level);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_debug_borders_around_text_fragments(&self,
|
fn build_debug_borders_around_text_fragments(&self,
|
||||||
|
@ -418,11 +437,8 @@ impl FragmentDisplayListBuilding for Fragment {
|
||||||
fragment_bounds.size);
|
fragment_bounds.size);
|
||||||
|
|
||||||
// Compute the text fragment bounds and draw a border surrounding them.
|
// Compute the text fragment bounds and draw a border surrounding them.
|
||||||
display_list.push(BorderDisplayItemClass(box BorderDisplayItem {
|
display_list.content.push_back(BorderDisplayItemClass(box BorderDisplayItem {
|
||||||
base: BaseDisplayItem::new(absolute_fragment_bounds,
|
base: BaseDisplayItem::new(absolute_fragment_bounds, self.node, *clip_rect),
|
||||||
self.node,
|
|
||||||
ContentStackingLevel,
|
|
||||||
*clip_rect),
|
|
||||||
border: SideOffsets2D::new_all_same(Au::from_px(1)),
|
border: SideOffsets2D::new_all_same(Au::from_px(1)),
|
||||||
color: SideOffsets2D::new_all_same(color::rgb(0, 0, 200)),
|
color: SideOffsets2D::new_all_same(color::rgb(0, 0, 200)),
|
||||||
style: SideOffsets2D::new_all_same(border_style::solid)
|
style: SideOffsets2D::new_all_same(border_style::solid)
|
||||||
|
@ -437,11 +453,11 @@ impl FragmentDisplayListBuilding for Fragment {
|
||||||
baseline.origin = baseline.origin + flow_origin;
|
baseline.origin = baseline.origin + flow_origin;
|
||||||
|
|
||||||
let line_display_item = box LineDisplayItem {
|
let line_display_item = box LineDisplayItem {
|
||||||
base: BaseDisplayItem::new(baseline, self.node, ContentStackingLevel, *clip_rect),
|
base: BaseDisplayItem::new(baseline, self.node, *clip_rect),
|
||||||
color: color::rgb(0, 200, 0),
|
color: color::rgb(0, 200, 0),
|
||||||
style: border_style::dashed,
|
style: border_style::dashed,
|
||||||
};
|
};
|
||||||
display_list.push(LineDisplayItemClass(line_display_item));
|
display_list.content.push_back(LineDisplayItemClass(line_display_item));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_debug_borders_around_fragment(&self,
|
fn build_debug_borders_around_fragment(&self,
|
||||||
|
@ -457,11 +473,8 @@ impl FragmentDisplayListBuilding for Fragment {
|
||||||
fragment_bounds.size);
|
fragment_bounds.size);
|
||||||
|
|
||||||
// This prints a debug border around the border of this fragment.
|
// This prints a debug border around the border of this fragment.
|
||||||
display_list.push(BorderDisplayItemClass(box BorderDisplayItem {
|
display_list.content.push_back(BorderDisplayItemClass(box BorderDisplayItem {
|
||||||
base: BaseDisplayItem::new(absolute_fragment_bounds,
|
base: BaseDisplayItem::new(absolute_fragment_bounds, self.node, *clip_rect),
|
||||||
self.node,
|
|
||||||
ContentStackingLevel,
|
|
||||||
*clip_rect),
|
|
||||||
border: SideOffsets2D::new_all_same(Au::from_px(1)),
|
border: SideOffsets2D::new_all_same(Au::from_px(1)),
|
||||||
color: SideOffsets2D::new_all_same(color::rgb(0, 0, 200)),
|
color: SideOffsets2D::new_all_same(color::rgb(0, 0, 200)),
|
||||||
style: SideOffsets2D::new_all_same(border_style::solid)
|
style: SideOffsets2D::new_all_same(border_style::solid)
|
||||||
|
@ -474,14 +487,24 @@ impl FragmentDisplayListBuilding for Fragment {
|
||||||
flow_origin: Point2D<Au>,
|
flow_origin: Point2D<Au>,
|
||||||
background_and_border_level: BackgroundAndBorderLevel,
|
background_and_border_level: BackgroundAndBorderLevel,
|
||||||
clip_rect: &Rect<Au>) {
|
clip_rect: &Rect<Au>) {
|
||||||
|
// Compute the fragment position relative to the parent stacking context. If the fragment
|
||||||
|
// itself establishes a stacking context, then the origin of its position will be (0, 0)
|
||||||
|
// for the purposes of this computation.
|
||||||
|
let stacking_relative_flow_origin = if self.establishes_stacking_context() {
|
||||||
|
ZERO_POINT
|
||||||
|
} else {
|
||||||
|
flow_origin
|
||||||
|
};
|
||||||
|
let absolute_fragment_bounds =
|
||||||
|
self.stacking_relative_bounds(&stacking_relative_flow_origin);
|
||||||
|
|
||||||
// FIXME(#2795): Get the real container size
|
// FIXME(#2795): Get the real container size
|
||||||
let container_size = Size2D::zero();
|
let container_size = Size2D::zero();
|
||||||
let rect_to_absolute = |writing_mode: WritingMode, logical_rect: LogicalRect<Au>| {
|
let rect_to_absolute = |writing_mode: WritingMode, logical_rect: LogicalRect<Au>| {
|
||||||
let physical_rect = logical_rect.to_physical(writing_mode, container_size);
|
let physical_rect = logical_rect.to_physical(writing_mode, container_size);
|
||||||
Rect(physical_rect.origin + flow_origin, physical_rect.size)
|
Rect(physical_rect.origin + stacking_relative_flow_origin, physical_rect.size)
|
||||||
};
|
};
|
||||||
// Fragment position wrt to the owning flow.
|
|
||||||
let absolute_fragment_bounds = self.abs_bounds_from_origin(&flow_origin);
|
|
||||||
debug!("Fragment::build_display_list at rel={}, abs={}: {}",
|
debug!("Fragment::build_display_list at rel={}, abs={}: {}",
|
||||||
self.border_box,
|
self.border_box,
|
||||||
absolute_fragment_bounds,
|
absolute_fragment_bounds,
|
||||||
|
@ -512,9 +535,8 @@ impl FragmentDisplayListBuilding for Fragment {
|
||||||
// Add a pseudo-display item for content box queries. This is a very bogus thing to do.
|
// Add a pseudo-display item for content box queries. This is a very bogus thing to do.
|
||||||
let base_display_item = box BaseDisplayItem::new(absolute_fragment_bounds,
|
let base_display_item = box BaseDisplayItem::new(absolute_fragment_bounds,
|
||||||
self.node,
|
self.node,
|
||||||
level,
|
|
||||||
*clip_rect);
|
*clip_rect);
|
||||||
display_list.push(PseudoDisplayItemClass(base_display_item));
|
display_list.push(PseudoDisplayItemClass(base_display_item), level);
|
||||||
|
|
||||||
// Add the background to the list, if applicable.
|
// Add the background to the list, if applicable.
|
||||||
match self.inline_context {
|
match self.inline_context {
|
||||||
|
@ -593,17 +615,15 @@ impl FragmentDisplayListBuilding for Fragment {
|
||||||
};
|
};
|
||||||
|
|
||||||
let metrics = &text_fragment.run.font_metrics;
|
let metrics = &text_fragment.run.font_metrics;
|
||||||
let baseline_origin ={
|
let baseline_origin = {
|
||||||
let mut tmp = content_box.start;
|
let mut content_box_start = content_box.start;
|
||||||
tmp.b = tmp.b + metrics.ascent;
|
content_box_start.b = content_box_start.b + metrics.ascent;
|
||||||
tmp.to_physical(self.style.writing_mode, container_size) + flow_origin
|
content_box_start.to_physical(self.style.writing_mode, container_size)
|
||||||
|
+ flow_origin
|
||||||
};
|
};
|
||||||
|
|
||||||
display_list.push(TextDisplayItemClass(box TextDisplayItem {
|
display_list.content.push_back(TextDisplayItemClass(box TextDisplayItem {
|
||||||
base: BaseDisplayItem::new(absolute_content_box,
|
base: BaseDisplayItem::new(absolute_content_box, self.node, *clip_rect),
|
||||||
self.node,
|
|
||||||
ContentStackingLevel,
|
|
||||||
*clip_rect),
|
|
||||||
text_run: text_fragment.run.clone(),
|
text_run: text_fragment.run.clone(),
|
||||||
range: text_fragment.range,
|
range: text_fragment.range,
|
||||||
text_color: self.style().get_color().color.to_gfx_color(),
|
text_color: self.style().get_color().color.to_gfx_color(),
|
||||||
|
@ -615,19 +635,14 @@ impl FragmentDisplayListBuilding for Fragment {
|
||||||
{
|
{
|
||||||
let line = |maybe_color: Option<RGBA>, rect: || -> LogicalRect<Au>| {
|
let line = |maybe_color: Option<RGBA>, rect: || -> LogicalRect<Au>| {
|
||||||
match maybe_color {
|
match maybe_color {
|
||||||
None => {},
|
None => {}
|
||||||
Some(color) => {
|
Some(color) => {
|
||||||
display_list.push(SolidColorDisplayItemClass(
|
let bounds = rect_to_absolute(self.style.writing_mode, rect());
|
||||||
box SolidColorDisplayItem {
|
display_list.content.push_back(SolidColorDisplayItemClass(
|
||||||
base: BaseDisplayItem::new(
|
box SolidColorDisplayItem {
|
||||||
rect_to_absolute(
|
base: BaseDisplayItem::new(bounds, self.node, *clip_rect),
|
||||||
self.style.writing_mode,
|
color: color.to_gfx_color(),
|
||||||
rect()),
|
}))
|
||||||
self.node,
|
|
||||||
ContentStackingLevel,
|
|
||||||
*clip_rect),
|
|
||||||
color: color.to_gfx_color(),
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -678,10 +693,9 @@ impl FragmentDisplayListBuilding for Fragment {
|
||||||
debug!("(building display list) building image fragment");
|
debug!("(building display list) building image fragment");
|
||||||
|
|
||||||
// Place the image into the display list.
|
// Place the image into the display list.
|
||||||
display_list.push(ImageDisplayItemClass(box ImageDisplayItem {
|
display_list.content.push_back(ImageDisplayItemClass(box ImageDisplayItem {
|
||||||
base: BaseDisplayItem::new(absolute_content_box,
|
base: BaseDisplayItem::new(absolute_content_box,
|
||||||
self.node,
|
self.node,
|
||||||
ContentStackingLevel,
|
|
||||||
*clip_rect),
|
*clip_rect),
|
||||||
image: image.clone(),
|
image: image.clone(),
|
||||||
stretch_size: absolute_content_box.size,
|
stretch_size: absolute_content_box.size,
|
||||||
|
@ -698,7 +712,9 @@ impl FragmentDisplayListBuilding for Fragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts::get().show_debug_fragment_borders {
|
if opts::get().show_debug_fragment_borders {
|
||||||
self.build_debug_borders_around_fragment(display_list, flow_origin, clip_rect)
|
self.build_debug_borders_around_fragment(display_list,
|
||||||
|
flow_origin,
|
||||||
|
clip_rect)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If this is an iframe, then send its position and size up to the constellation.
|
// If this is an iframe, then send its position and size up to the constellation.
|
||||||
|
@ -766,6 +782,10 @@ impl FragmentDisplayListBuilding for Fragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait BlockFlowDisplayListBuilding {
|
pub trait BlockFlowDisplayListBuilding {
|
||||||
|
fn build_display_list_for_block_base(&mut self,
|
||||||
|
display_list: &mut DisplayList,
|
||||||
|
layout_context: &LayoutContext,
|
||||||
|
background_border_level: BackgroundAndBorderLevel);
|
||||||
fn build_display_list_for_block(&mut self,
|
fn build_display_list_for_block(&mut self,
|
||||||
layout_context: &LayoutContext,
|
layout_context: &LayoutContext,
|
||||||
background_border_level: BackgroundAndBorderLevel);
|
background_border_level: BackgroundAndBorderLevel);
|
||||||
|
@ -775,78 +795,93 @@ pub trait BlockFlowDisplayListBuilding {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BlockFlowDisplayListBuilding for BlockFlow {
|
impl BlockFlowDisplayListBuilding for BlockFlow {
|
||||||
fn build_display_list_for_block(&mut self,
|
fn build_display_list_for_block_base(&mut self,
|
||||||
layout_context: &LayoutContext,
|
display_list: &mut DisplayList,
|
||||||
background_border_level: BackgroundAndBorderLevel) {
|
layout_context: &LayoutContext,
|
||||||
|
background_border_level: BackgroundAndBorderLevel) {
|
||||||
// Add the box that starts the block context.
|
// Add the box that starts the block context.
|
||||||
let absolute_fragment_origin = self.base.child_fragment_absolute_position(&self.fragment);
|
let stacking_relative_fragment_origin =
|
||||||
self.fragment.build_display_list(&mut self.base.display_list,
|
self.base.stacking_relative_position_of_child_fragment(&self.fragment);
|
||||||
|
self.fragment.build_display_list(display_list,
|
||||||
layout_context,
|
layout_context,
|
||||||
absolute_fragment_origin,
|
stacking_relative_fragment_origin,
|
||||||
background_border_level,
|
background_border_level,
|
||||||
&self.base.clip_rect);
|
&self.base.clip_rect);
|
||||||
|
|
||||||
self.base.layers = DList::new();
|
|
||||||
for kid in self.base.children.iter_mut() {
|
for kid in self.base.children.iter_mut() {
|
||||||
if flow::base(kid).flags.is_absolutely_positioned() {
|
if flow::base(kid).flags.is_absolutely_positioned() {
|
||||||
// All absolute flows will be handled by their containing block.
|
// All absolute flows will be handled by their containing block.
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
self.base.display_list.append_from(&mut flow::mut_base(kid).display_list);
|
flow::mut_base(kid).display_list_building_result.add_to(display_list);
|
||||||
dlist::append_from(&mut self.base.layers, &mut flow::mut_base(kid).layers)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process absolute descendant links.
|
// Process absolute descendant links.
|
||||||
for abs_descendant_link in self.base.abs_descendants.iter() {
|
for abs_descendant_link in self.base.abs_descendants.iter() {
|
||||||
// TODO(pradeep): Send in our absolute position directly.
|
// TODO(pradeep): Send in our absolute position directly.
|
||||||
self.base
|
flow::mut_base(abs_descendant_link).display_list_building_result.add_to(display_list);
|
||||||
.display_list
|
|
||||||
.append_from(&mut flow::mut_base(abs_descendant_link).display_list);
|
|
||||||
dlist::append_from(&mut self.base.layers,
|
|
||||||
&mut flow::mut_base(abs_descendant_link).layers)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn build_display_list_for_block(&mut self,
|
||||||
|
layout_context: &LayoutContext,
|
||||||
|
background_border_level: BackgroundAndBorderLevel) {
|
||||||
|
let mut display_list = box DisplayList::new();
|
||||||
|
self.build_display_list_for_block_base(&mut *display_list,
|
||||||
|
layout_context,
|
||||||
|
background_border_level);
|
||||||
|
self.base.display_list_building_result = DisplayListResult(display_list);
|
||||||
|
}
|
||||||
|
|
||||||
fn build_display_list_for_absolutely_positioned_block(&mut self,
|
fn build_display_list_for_absolutely_positioned_block(&mut self,
|
||||||
layout_context: &LayoutContext) {
|
layout_context: &LayoutContext) {
|
||||||
self.build_display_list_for_block(layout_context, RootOfStackingContextLevel);
|
let mut display_list = box DisplayList::new();
|
||||||
|
self.build_display_list_for_block_base(&mut *display_list,
|
||||||
|
layout_context,
|
||||||
|
RootOfStackingContextLevel);
|
||||||
|
|
||||||
|
let bounds = Rect(self.base.stacking_relative_position,
|
||||||
|
self.base.overflow.size.to_physical(self.base.writing_mode));
|
||||||
|
let z_index = self.fragment.style().get_box().z_index.number_or_zero();
|
||||||
|
|
||||||
if !self.base.absolute_position_info.layers_needed_for_positioned_flows &&
|
if !self.base.absolute_position_info.layers_needed_for_positioned_flows &&
|
||||||
!self.base.flags.needs_layer() {
|
!self.base.flags.needs_layer() {
|
||||||
// We didn't need a layer.
|
// We didn't need a layer.
|
||||||
let z_index = self.fragment.style().get_box().z_index.number_or_zero();
|
self.base.display_list_building_result =
|
||||||
self.base.display_list.flatten(PositionedDescendantStackingLevel(z_index));
|
StackingContextResult(Arc::new(StackingContext::new(display_list,
|
||||||
|
bounds,
|
||||||
|
z_index,
|
||||||
|
None)));
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we got here, then we need a new layer.
|
// If we got here, then we need a new layer.
|
||||||
let layer_rect = self.base.position.union(&self.base.overflow);
|
|
||||||
let size = Size2D(layer_rect.size.inline.to_nearest_px() as uint,
|
|
||||||
layer_rect.size.block.to_nearest_px() as uint);
|
|
||||||
let origin = Point2D(self.base.abs_position.x.to_nearest_px() as uint,
|
|
||||||
self.base.abs_position.y.to_nearest_px() as uint);
|
|
||||||
|
|
||||||
let scroll_policy = if self.is_fixed() {
|
let scroll_policy = if self.is_fixed() {
|
||||||
FixedPosition
|
FixedPosition
|
||||||
} else {
|
} else {
|
||||||
Scrollable
|
Scrollable
|
||||||
};
|
};
|
||||||
self.base.display_list.flatten(ContentStackingLevel);
|
|
||||||
let new_layer = RenderLayer {
|
let transparent = color::rgba(1.0, 1.0, 1.0, 0.0);
|
||||||
id: self.layer_id(0),
|
let stacking_context =
|
||||||
display_list: Arc::new(mem::replace(&mut self.base.display_list, DisplayList::new())),
|
Arc::new(StackingContext::new(display_list,
|
||||||
position: Rect(origin, size),
|
bounds,
|
||||||
background_color: color::rgba(1.0, 1.0, 1.0, 0.0),
|
z_index,
|
||||||
scroll_policy: scroll_policy,
|
Some(Arc::new(RenderLayer::new(self.layer_id(0),
|
||||||
};
|
transparent,
|
||||||
self.base.layers.push_back(new_layer)
|
scroll_policy)))));
|
||||||
|
|
||||||
|
self.base.display_list_building_result = StackingContextResult(stacking_context)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_display_list_for_floating_block(&mut self, layout_context: &LayoutContext) {
|
fn build_display_list_for_floating_block(&mut self, layout_context: &LayoutContext) {
|
||||||
self.build_display_list_for_block(layout_context, RootOfStackingContextLevel);
|
let mut display_list = box DisplayList::new();
|
||||||
self.base.display_list.flatten(FloatStackingLevel)
|
self.build_display_list_for_block_base(&mut *display_list,
|
||||||
|
layout_context,
|
||||||
|
RootOfStackingContextLevel);
|
||||||
|
display_list.form_float_pseudo_stacking_context();
|
||||||
|
self.base.display_list_building_result = DisplayListResult(display_list);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -873,3 +908,51 @@ fn position_to_offset(position: LengthOrPercentage, Au(total_length): Au) -> f32
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// "Steps" as defined by CSS 2.1 § E.2.
|
||||||
|
#[deriving(Clone, PartialEq, Show)]
|
||||||
|
pub enum StackingLevel {
|
||||||
|
/// The border and backgrounds for the root of this stacking context: steps 1 and 2.
|
||||||
|
BackgroundAndBordersStackingLevel,
|
||||||
|
/// Borders and backgrounds for block-level descendants: step 4.
|
||||||
|
BlockBackgroundsAndBordersStackingLevel,
|
||||||
|
/// All other content.
|
||||||
|
ContentStackingLevel,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StackingLevel {
|
||||||
|
#[inline]
|
||||||
|
pub fn from_background_and_border_level(level: BackgroundAndBorderLevel) -> StackingLevel {
|
||||||
|
match level {
|
||||||
|
RootOfStackingContextLevel => BackgroundAndBordersStackingLevel,
|
||||||
|
BlockLevel => BlockBackgroundsAndBordersStackingLevel,
|
||||||
|
ContentLevel => ContentStackingLevel,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Which level to place backgrounds and borders in.
|
||||||
|
pub enum BackgroundAndBorderLevel {
|
||||||
|
RootOfStackingContextLevel,
|
||||||
|
BlockLevel,
|
||||||
|
ContentLevel,
|
||||||
|
}
|
||||||
|
|
||||||
|
trait StackingContextConstruction {
|
||||||
|
/// Adds the given display item at the specified level to this display list.
|
||||||
|
fn push(&mut self, display_item: DisplayItem, level: StackingLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StackingContextConstruction for DisplayList {
|
||||||
|
fn push(&mut self, display_item: DisplayItem, level: StackingLevel) {
|
||||||
|
match level {
|
||||||
|
BackgroundAndBordersStackingLevel => {
|
||||||
|
self.background_and_borders.push_back(display_item)
|
||||||
|
}
|
||||||
|
BlockBackgroundsAndBordersStackingLevel => {
|
||||||
|
self.block_backgrounds_and_borders.push_back(display_item)
|
||||||
|
}
|
||||||
|
ContentStackingLevel => self.content.push_back(display_item),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,8 @@
|
||||||
use css::node_style::StyledNode;
|
use css::node_style::StyledNode;
|
||||||
use block::BlockFlow;
|
use block::BlockFlow;
|
||||||
use context::LayoutContext;
|
use context::LayoutContext;
|
||||||
|
use display_list_builder::{DisplayListBuildingResult, DisplayListResult};
|
||||||
|
use display_list_builder::{NoDisplayListBuildingResult, StackingContextResult};
|
||||||
use floats::Floats;
|
use floats::Floats;
|
||||||
use flow_list::{FlowList, FlowListIterator, MutFlowListIterator};
|
use flow_list::{FlowList, FlowListIterator, MutFlowListIterator};
|
||||||
use flow_ref::FlowRef;
|
use flow_ref::FlowRef;
|
||||||
|
@ -45,10 +47,7 @@ use table_rowgroup::TableRowGroupFlow;
|
||||||
use table_wrapper::TableWrapperFlow;
|
use table_wrapper::TableWrapperFlow;
|
||||||
use wrapper::ThreadSafeLayoutNode;
|
use wrapper::ThreadSafeLayoutNode;
|
||||||
|
|
||||||
use collections::dlist::DList;
|
|
||||||
use geom::{Point2D, Rect, Size2D};
|
use geom::{Point2D, Rect, Size2D};
|
||||||
use gfx::display_list::DisplayList;
|
|
||||||
use gfx::render_task::RenderLayer;
|
|
||||||
use serialize::{Encoder, Encodable};
|
use serialize::{Encoder, Encodable};
|
||||||
use servo_msg::compositor_msg::LayerId;
|
use servo_msg::compositor_msg::LayerId;
|
||||||
use servo_util::geometry::Au;
|
use servo_util::geometry::Au;
|
||||||
|
@ -681,8 +680,10 @@ pub struct AbsolutePositionInfo {
|
||||||
/// The size of the containing block for relatively-positioned descendants.
|
/// The size of the containing block for relatively-positioned descendants.
|
||||||
pub relative_containing_block_size: LogicalSize<Au>,
|
pub relative_containing_block_size: LogicalSize<Au>,
|
||||||
|
|
||||||
/// The position of the absolute containing block.
|
/// The position of the absolute containing block relative to the nearest ancestor stacking
|
||||||
pub absolute_containing_block_position: Point2D<Au>,
|
/// context. If the absolute containing block establishes the stacking context for this flow,
|
||||||
|
/// and this flow is not itself absolutely-positioned, then this is (0, 0).
|
||||||
|
pub stacking_relative_position_of_absolute_containing_block: Point2D<Au>,
|
||||||
|
|
||||||
/// Whether the absolute containing block forces positioned descendants to be layerized.
|
/// Whether the absolute containing block forces positioned descendants to be layerized.
|
||||||
///
|
///
|
||||||
|
@ -696,7 +697,7 @@ impl AbsolutePositionInfo {
|
||||||
// of the root layer.
|
// of the root layer.
|
||||||
AbsolutePositionInfo {
|
AbsolutePositionInfo {
|
||||||
relative_containing_block_size: LogicalSize::zero(writing_mode),
|
relative_containing_block_size: LogicalSize::zero(writing_mode),
|
||||||
absolute_containing_block_position: Zero::zero(),
|
stacking_relative_position_of_absolute_containing_block: Zero::zero(),
|
||||||
layers_needed_for_positioned_flows: false,
|
layers_needed_for_positioned_flows: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -742,8 +743,9 @@ pub struct BaseFlow {
|
||||||
/// The collapsible margins for this flow, if any.
|
/// The collapsible margins for this flow, if any.
|
||||||
pub collapsible_margins: CollapsibleMargins,
|
pub collapsible_margins: CollapsibleMargins,
|
||||||
|
|
||||||
/// The position of this flow in page coordinates, computed during display list construction.
|
/// The position of this flow relative to the start of the nearest ancestor stacking context.
|
||||||
pub abs_position: Point2D<Au>,
|
/// This is computed during the top-down pass of display list construction.
|
||||||
|
pub stacking_relative_position: Point2D<Au>,
|
||||||
|
|
||||||
/// Details about descendants with position 'absolute' or 'fixed' for which we are the
|
/// Details about descendants with position 'absolute' or 'fixed' for which we are the
|
||||||
/// containing block. This is in tree order. This includes any direct children.
|
/// containing block. This is in tree order. This includes any direct children.
|
||||||
|
@ -779,11 +781,8 @@ pub struct BaseFlow {
|
||||||
/// rectangles.
|
/// rectangles.
|
||||||
pub clip_rect: Rect<Au>,
|
pub clip_rect: Rect<Au>,
|
||||||
|
|
||||||
/// The unflattened display items for this flow.
|
/// The results of display list building for this flow.
|
||||||
pub display_list: DisplayList,
|
pub display_list_building_result: DisplayListBuildingResult,
|
||||||
|
|
||||||
/// Any layers that we're bubbling up, in a linked list.
|
|
||||||
pub layers: DList<RenderLayer>,
|
|
||||||
|
|
||||||
/// The writing mode for this flow.
|
/// The writing mode for this flow.
|
||||||
pub writing_mode: WritingMode,
|
pub writing_mode: WritingMode,
|
||||||
|
@ -806,8 +805,12 @@ impl<E, S: Encoder<E>> Encodable<S, E> for BaseFlow {
|
||||||
fn encode(&self, e: &mut S) -> Result<(), E> {
|
fn encode(&self, e: &mut S) -> Result<(), E> {
|
||||||
e.emit_struct("base", 0, |e| {
|
e.emit_struct("base", 0, |e| {
|
||||||
try!(e.emit_struct_field("id", 0, |e| self.debug_id().encode(e)))
|
try!(e.emit_struct_field("id", 0, |e| self.debug_id().encode(e)))
|
||||||
try!(e.emit_struct_field("abs_position", 1, |e| self.abs_position.encode(e)))
|
try!(e.emit_struct_field("stacking_relative_position",
|
||||||
try!(e.emit_struct_field("intrinsic_inline_sizes", 2, |e| self.intrinsic_inline_sizes.encode(e)))
|
1,
|
||||||
|
|e| self.stacking_relative_position.encode(e)))
|
||||||
|
try!(e.emit_struct_field("intrinsic_inline_sizes",
|
||||||
|
2,
|
||||||
|
|e| self.intrinsic_inline_sizes.encode(e)))
|
||||||
try!(e.emit_struct_field("position", 3, |e| self.position.encode(e)))
|
try!(e.emit_struct_field("position", 3, |e| self.position.encode(e)))
|
||||||
e.emit_struct_field("children", 4, |e| {
|
e.emit_struct_field("children", 4, |e| {
|
||||||
e.emit_seq(self.children.len(), |e| {
|
e.emit_seq(self.children.len(), |e| {
|
||||||
|
@ -893,15 +896,14 @@ impl BaseFlow {
|
||||||
parallel: FlowParallelInfo::new(),
|
parallel: FlowParallelInfo::new(),
|
||||||
floats: Floats::new(writing_mode),
|
floats: Floats::new(writing_mode),
|
||||||
collapsible_margins: CollapsibleMargins::new(),
|
collapsible_margins: CollapsibleMargins::new(),
|
||||||
abs_position: Zero::zero(),
|
stacking_relative_position: Zero::zero(),
|
||||||
abs_descendants: Descendants::new(),
|
abs_descendants: Descendants::new(),
|
||||||
absolute_static_i_offset: Au(0),
|
absolute_static_i_offset: Au(0),
|
||||||
fixed_static_i_offset: Au(0),
|
fixed_static_i_offset: Au(0),
|
||||||
block_container_inline_size: Au(0),
|
block_container_inline_size: Au(0),
|
||||||
block_container_explicit_block_size: None,
|
block_container_explicit_block_size: None,
|
||||||
absolute_cb: ContainingBlockLink::new(),
|
absolute_cb: ContainingBlockLink::new(),
|
||||||
display_list: DisplayList::new(),
|
display_list_building_result: NoDisplayListBuildingResult,
|
||||||
layers: DList::new(),
|
|
||||||
absolute_position_info: AbsolutePositionInfo::new(writing_mode),
|
absolute_position_info: AbsolutePositionInfo::new(writing_mode),
|
||||||
clip_rect: Rect(Zero::zero(), Size2D(Au(0), Au(0))),
|
clip_rect: Rect(Zero::zero(), Size2D(Au(0), Au(0))),
|
||||||
flags: flags,
|
flags: flags,
|
||||||
|
@ -922,13 +924,23 @@ impl BaseFlow {
|
||||||
p as uint
|
p as uint
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Ensures that all display list items generated by this flow are within the flow's overflow
|
||||||
|
/// rect. This should only be used for debugging.
|
||||||
pub fn validate_display_list_geometry(&self) {
|
pub fn validate_display_list_geometry(&self) {
|
||||||
let position_with_overflow = self.position.union(&self.overflow);
|
let position_with_overflow = self.position.union(&self.overflow);
|
||||||
let bounds = Rect(self.abs_position,
|
let bounds = Rect(self.stacking_relative_position,
|
||||||
Size2D(position_with_overflow.size.inline,
|
Size2D(position_with_overflow.size.inline,
|
||||||
position_with_overflow.size.block));
|
position_with_overflow.size.block));
|
||||||
|
|
||||||
for item in self.display_list.iter() {
|
let all_items = match self.display_list_building_result {
|
||||||
|
NoDisplayListBuildingResult => Vec::new(),
|
||||||
|
StackingContextResult(ref stacking_context) => {
|
||||||
|
stacking_context.display_list.all_display_items()
|
||||||
|
}
|
||||||
|
DisplayListResult(ref display_list) => display_list.all_display_items(),
|
||||||
|
};
|
||||||
|
|
||||||
|
for item in all_items.iter() {
|
||||||
let paint_bounds = match item.base().bounds.intersection(&item.base().clip_rect) {
|
let paint_bounds = match item.base().bounds.intersection(&item.base().clip_rect) {
|
||||||
None => continue,
|
None => continue,
|
||||||
Some(rect) => rect,
|
Some(rect) => rect,
|
||||||
|
@ -944,12 +956,15 @@ impl BaseFlow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn child_fragment_absolute_position(&self, fragment: &Fragment) -> Point2D<Au> {
|
/// Returns the position of the given fragment relative to the start of the nearest ancestor
|
||||||
|
/// stacking context. The fragment must be a child fragment of this flow.
|
||||||
|
pub fn stacking_relative_position_of_child_fragment(&self, fragment: &Fragment)
|
||||||
|
-> Point2D<Au> {
|
||||||
let relative_offset =
|
let relative_offset =
|
||||||
fragment.relative_position(&self
|
fragment.relative_position(&self
|
||||||
.absolute_position_info
|
.absolute_position_info
|
||||||
.relative_containing_block_size);
|
.relative_containing_block_size);
|
||||||
self.abs_position.add_size(&relative_offset.to_physical(self.writing_mode))
|
self.stacking_relative_position.add_size(&relative_offset.to_physical(self.writing_mode))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1481,11 +1481,31 @@ impl Fragment {
|
||||||
self.style = (*new_style).clone()
|
self.style = (*new_style).clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn abs_bounds_from_origin(&self, fragment_origin: &Point2D<Au>) -> Rect<Au> {
|
/// Given the stacking-context-relative position of the containing flow, returns the boundaries
|
||||||
|
/// of this fragment relative to the parent stacking context.
|
||||||
|
pub fn stacking_relative_bounds(&self, stacking_relative_flow_origin: &Point2D<Au>)
|
||||||
|
-> Rect<Au> {
|
||||||
// FIXME(#2795): Get the real container size
|
// FIXME(#2795): Get the real container size
|
||||||
let container_size = Size2D::zero();
|
let container_size = Size2D::zero();
|
||||||
self.border_box.to_physical(self.style.writing_mode, container_size)
|
self.border_box
|
||||||
.translate(fragment_origin)
|
.to_physical(self.style.writing_mode, container_size)
|
||||||
|
.translate(stacking_relative_flow_origin)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if this fragment establishes a new stacking context and false otherwise.
|
||||||
|
pub fn establishes_stacking_context(&self) -> bool {
|
||||||
|
match self.style().get_box().position {
|
||||||
|
position::absolute | position::fixed => {
|
||||||
|
// FIXME(pcwalton): This should only establish a new stacking context when
|
||||||
|
// `z-index` is not `auto`. But this matches what we did before.
|
||||||
|
true
|
||||||
|
}
|
||||||
|
position::relative | position::static_ => {
|
||||||
|
// FIXME(pcwalton): `position: relative` establishes a new stacking context if
|
||||||
|
// `z-index` is not `auto`. But this matches what we did before.
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
use css::node_style::StyledNode;
|
use css::node_style::StyledNode;
|
||||||
use context::LayoutContext;
|
use context::LayoutContext;
|
||||||
use display_list_builder::FragmentDisplayListBuilding;
|
use display_list_builder::{ContentLevel, DisplayListResult, FragmentDisplayListBuilding};
|
||||||
use floats::{FloatLeft, Floats, PlacementInfo};
|
use floats::{FloatLeft, Floats, PlacementInfo};
|
||||||
use flow::{BaseFlow, FlowClass, Flow, InlineFlowClass, MutableFlowUtils};
|
use flow::{BaseFlow, FlowClass, Flow, InlineFlowClass, MutableFlowUtils};
|
||||||
use flow;
|
use flow;
|
||||||
|
@ -20,7 +20,7 @@ use text;
|
||||||
|
|
||||||
use collections::{RingBuf};
|
use collections::{RingBuf};
|
||||||
use geom::{Rect, Size2D};
|
use geom::{Rect, Size2D};
|
||||||
use gfx::display_list::ContentLevel;
|
use gfx::display_list::DisplayList;
|
||||||
use gfx::font::FontMetrics;
|
use gfx::font::FontMetrics;
|
||||||
use gfx::font_context::FontContext;
|
use gfx::font_context::FontContext;
|
||||||
use gfx::text::glyph::CharIndex;
|
use gfx::text::glyph::CharIndex;
|
||||||
|
@ -1136,34 +1136,34 @@ impl Flow for InlineFlow {
|
||||||
|
|
||||||
fn compute_absolute_position(&mut self) {
|
fn compute_absolute_position(&mut self) {
|
||||||
for fragment in self.fragments.fragments.iter_mut() {
|
for fragment in self.fragments.fragments.iter_mut() {
|
||||||
let absolute_position = match fragment.specific {
|
let stacking_relative_position = match fragment.specific {
|
||||||
InlineBlockFragment(ref mut info) => {
|
InlineBlockFragment(ref mut info) => {
|
||||||
let block_flow = info.flow_ref.as_block();
|
let block_flow = info.flow_ref.as_block();
|
||||||
// FIXME(#2795): Get the real container size
|
// FIXME(#2795): Get the real container size
|
||||||
let container_size = Size2D::zero();
|
let container_size = Size2D::zero();
|
||||||
|
|
||||||
block_flow.base.abs_position =
|
block_flow.base.stacking_relative_position =
|
||||||
self.base.abs_position +
|
self.base.stacking_relative_position +
|
||||||
fragment.border_box.start.to_physical(self.base.writing_mode,
|
fragment.border_box.start.to_physical(self.base.writing_mode,
|
||||||
container_size);
|
container_size);
|
||||||
block_flow.base.abs_position
|
block_flow.base.stacking_relative_position
|
||||||
}
|
}
|
||||||
InlineAbsoluteHypotheticalFragment(ref mut info) => {
|
InlineAbsoluteHypotheticalFragment(ref mut info) => {
|
||||||
let block_flow = info.flow_ref.as_block();
|
let block_flow = info.flow_ref.as_block();
|
||||||
// FIXME(#2795): Get the real container size
|
// FIXME(#2795): Get the real container size
|
||||||
let container_size = Size2D::zero();
|
let container_size = Size2D::zero();
|
||||||
block_flow.base.abs_position =
|
block_flow.base.stacking_relative_position =
|
||||||
self.base.abs_position +
|
self.base.stacking_relative_position +
|
||||||
fragment.border_box.start.to_physical(self.base.writing_mode,
|
fragment.border_box.start.to_physical(self.base.writing_mode,
|
||||||
container_size);
|
container_size);
|
||||||
block_flow.base.abs_position
|
block_flow.base.stacking_relative_position
|
||||||
|
|
||||||
}
|
}
|
||||||
_ => continue,
|
_ => continue,
|
||||||
};
|
};
|
||||||
|
|
||||||
let clip_rect = fragment.clip_rect_for_children(self.base.clip_rect,
|
let clip_rect = fragment.clip_rect_for_children(self.base.clip_rect,
|
||||||
absolute_position);
|
stacking_relative_position);
|
||||||
|
|
||||||
match fragment.specific {
|
match fragment.specific {
|
||||||
InlineBlockFragment(ref mut info) => {
|
InlineBlockFragment(ref mut info) => {
|
||||||
|
@ -1183,11 +1183,11 @@ impl Flow for InlineFlow {
|
||||||
|
|
||||||
fn build_display_list(&mut self, layout_context: &LayoutContext) {
|
fn build_display_list(&mut self, layout_context: &LayoutContext) {
|
||||||
let size = self.base.position.size.to_physical(self.base.writing_mode);
|
let size = self.base.position.size.to_physical(self.base.writing_mode);
|
||||||
if !Rect(self.base.abs_position, size).intersects(&layout_context.shared.dirty) {
|
if !Rect(self.base.stacking_relative_position, size).intersects(&layout_context.shared
|
||||||
debug!("inline block (abs pos {}, size {}) didn't intersect \
|
.dirty) {
|
||||||
dirty rect two",
|
debug!("inline block (stacking relative pos {}, size {}) didn't intersect dirty rect",
|
||||||
self.base.abs_position,
|
self.base.stacking_relative_position,
|
||||||
size);
|
size);
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1195,9 +1195,10 @@ impl Flow for InlineFlow {
|
||||||
// not recurse on a line if nothing in it can intersect the dirty region.
|
// not recurse on a line if nothing in it can intersect the dirty region.
|
||||||
debug!("Flow: building display list for {:u} inline fragments", self.fragments.len());
|
debug!("Flow: building display list for {:u} inline fragments", self.fragments.len());
|
||||||
|
|
||||||
|
let mut display_list = box DisplayList::new();
|
||||||
for fragment in self.fragments.fragments.iter_mut() {
|
for fragment in self.fragments.fragments.iter_mut() {
|
||||||
let fragment_origin = self.base.child_fragment_absolute_position(fragment);
|
let fragment_origin = self.base.stacking_relative_position_of_child_fragment(fragment);
|
||||||
fragment.build_display_list(&mut self.base.display_list,
|
fragment.build_display_list(&mut *display_list,
|
||||||
layout_context,
|
layout_context,
|
||||||
fragment_origin,
|
fragment_origin,
|
||||||
ContentLevel,
|
ContentLevel,
|
||||||
|
@ -1205,14 +1206,15 @@ impl Flow for InlineFlow {
|
||||||
match fragment.specific {
|
match fragment.specific {
|
||||||
InlineBlockFragment(ref mut block_flow) => {
|
InlineBlockFragment(ref mut block_flow) => {
|
||||||
let block_flow = block_flow.flow_ref.deref_mut();
|
let block_flow = block_flow.flow_ref.deref_mut();
|
||||||
self.base
|
flow::mut_base(block_flow).display_list_building_result
|
||||||
.display_list
|
.add_to(&mut *display_list)
|
||||||
.append_from(&mut flow::mut_base(block_flow).display_list)
|
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.base.display_list_building_result = DisplayListResult(display_list);
|
||||||
|
|
||||||
if opts::get().validate_display_list_geometry {
|
if opts::get().validate_display_list_geometry {
|
||||||
self.base.validate_display_list_geometry();
|
self.base.validate_display_list_geometry();
|
||||||
}
|
}
|
||||||
|
@ -1223,8 +1225,9 @@ impl Flow for InlineFlow {
|
||||||
fn iterate_through_fragment_bounds(&self, iterator: &mut FragmentBoundsIterator) {
|
fn iterate_through_fragment_bounds(&self, iterator: &mut FragmentBoundsIterator) {
|
||||||
for fragment in self.fragments.fragments.iter() {
|
for fragment in self.fragments.fragments.iter() {
|
||||||
if iterator.should_process(fragment) {
|
if iterator.should_process(fragment) {
|
||||||
let fragment_origin = self.base.child_fragment_absolute_position(fragment);
|
let fragment_origin =
|
||||||
iterator.process(fragment, fragment.abs_bounds_from_origin(&fragment_origin));
|
self.base.stacking_relative_position_of_child_fragment(fragment);
|
||||||
|
iterator.process(fragment, fragment.stacking_relative_bounds(&fragment_origin));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,15 +19,13 @@ use sequential;
|
||||||
use util::{LayoutDataAccess, LayoutDataWrapper, OpaqueNodeMethods, ToGfxColor};
|
use util::{LayoutDataAccess, LayoutDataWrapper, OpaqueNodeMethods, ToGfxColor};
|
||||||
use wrapper::{LayoutNode, TLayoutNode, ThreadSafeLayoutNode};
|
use wrapper::{LayoutNode, TLayoutNode, ThreadSafeLayoutNode};
|
||||||
|
|
||||||
use collections::dlist::DList;
|
|
||||||
use encoding::EncodingRef;
|
use encoding::EncodingRef;
|
||||||
use encoding::all::UTF_8;
|
use encoding::all::UTF_8;
|
||||||
use geom::point::Point2D;
|
use geom::point::Point2D;
|
||||||
use geom::rect::Rect;
|
use geom::rect::Rect;
|
||||||
use geom::size::Size2D;
|
use geom::size::Size2D;
|
||||||
use geom::scale_factor::ScaleFactor;
|
use geom::scale_factor::ScaleFactor;
|
||||||
use gfx::display_list::{ContentStackingLevel, DisplayItem, DisplayList};
|
use gfx::display_list::{DisplayList, OpaqueNode, StackingContext};
|
||||||
use gfx::display_list::{OpaqueNode};
|
|
||||||
use gfx::render_task::{RenderInitMsg, RenderChan, RenderLayer};
|
use gfx::render_task::{RenderInitMsg, RenderChan, RenderLayer};
|
||||||
use gfx::{render_task, color};
|
use gfx::{render_task, color};
|
||||||
use layout_traits;
|
use layout_traits;
|
||||||
|
@ -51,7 +49,6 @@ use gfx::font_cache_task::{FontCacheTask};
|
||||||
use servo_net::local_image_cache::{ImageResponder, LocalImageCache};
|
use servo_net::local_image_cache::{ImageResponder, LocalImageCache};
|
||||||
use servo_net::resource_task::{ResourceTask, load_bytes_iter};
|
use servo_net::resource_task::{ResourceTask, load_bytes_iter};
|
||||||
use servo_util::geometry::Au;
|
use servo_util::geometry::Au;
|
||||||
use servo_util::geometry;
|
|
||||||
use servo_util::logical_geometry::LogicalPoint;
|
use servo_util::logical_geometry::LogicalPoint;
|
||||||
use servo_util::opts;
|
use servo_util::opts;
|
||||||
use servo_util::smallvec::{SmallVec, SmallVec1, VecLike};
|
use servo_util::smallvec::{SmallVec, SmallVec1, VecLike};
|
||||||
|
@ -79,8 +76,8 @@ pub struct LayoutTaskData {
|
||||||
/// The size of the viewport.
|
/// The size of the viewport.
|
||||||
pub screen_size: Size2D<Au>,
|
pub screen_size: Size2D<Au>,
|
||||||
|
|
||||||
/// A cached display list.
|
/// The root stacking context.
|
||||||
pub display_list: Option<Arc<DisplayList>>,
|
pub stacking_context: Option<Arc<StackingContext>>,
|
||||||
|
|
||||||
pub stylist: Box<Stylist>,
|
pub stylist: Box<Stylist>,
|
||||||
|
|
||||||
|
@ -277,7 +274,7 @@ impl LayoutTask {
|
||||||
LayoutTaskData {
|
LayoutTaskData {
|
||||||
local_image_cache: local_image_cache,
|
local_image_cache: local_image_cache,
|
||||||
screen_size: screen_size,
|
screen_size: screen_size,
|
||||||
display_list: None,
|
stacking_context: None,
|
||||||
stylist: box Stylist::new(device),
|
stylist: box Stylist::new(device),
|
||||||
parallel_traversal: parallel_traversal,
|
parallel_traversal: parallel_traversal,
|
||||||
dirty: Rect::zero(),
|
dirty: Rect::zero(),
|
||||||
|
@ -621,7 +618,7 @@ impl LayoutTask {
|
||||||
shared_layout_ctx.dirty =
|
shared_layout_ctx.dirty =
|
||||||
flow::base(layout_root.deref()).position.to_physical(writing_mode,
|
flow::base(layout_root.deref()).position.to_physical(writing_mode,
|
||||||
rw_data.screen_size);
|
rw_data.screen_size);
|
||||||
flow::mut_base(layout_root.deref_mut()).abs_position =
|
flow::mut_base(layout_root.deref_mut()).stacking_relative_position =
|
||||||
LogicalPoint::zero(writing_mode).to_physical(writing_mode,
|
LogicalPoint::zero(writing_mode).to_physical(writing_mode,
|
||||||
rw_data.screen_size);
|
rw_data.screen_size);
|
||||||
|
|
||||||
|
@ -643,13 +640,7 @@ impl LayoutTask {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
debug!("Done building display list. Display List = {}",
|
debug!("Done building display list.");
|
||||||
flow::base(layout_root.deref()).display_list);
|
|
||||||
|
|
||||||
flow::mut_base(layout_root.deref_mut()).display_list.flatten(ContentStackingLevel);
|
|
||||||
let display_list =
|
|
||||||
Arc::new(mem::replace(&mut flow::mut_base(layout_root.deref_mut()).display_list,
|
|
||||||
DisplayList::new()));
|
|
||||||
|
|
||||||
// FIXME(pcwalton): This is really ugly and can't handle overflow: scroll. Refactor
|
// FIXME(pcwalton): This is really ugly and can't handle overflow: scroll. Refactor
|
||||||
// it with extreme prejudice.
|
// it with extreme prejudice.
|
||||||
|
@ -678,31 +669,23 @@ impl LayoutTask {
|
||||||
let root_flow = flow::base(layout_root.deref());
|
let root_flow = flow::base(layout_root.deref());
|
||||||
root_flow.position.size.to_physical(root_flow.writing_mode)
|
root_flow.position.size.to_physical(root_flow.writing_mode)
|
||||||
};
|
};
|
||||||
let root_size = Size2D(root_size.width.to_nearest_px() as uint,
|
let mut display_list = box DisplayList::new();
|
||||||
root_size.height.to_nearest_px() as uint);
|
flow::mut_base(layout_root.deref_mut()).display_list_building_result
|
||||||
let render_layer = RenderLayer {
|
.add_to(&mut *display_list);
|
||||||
id: layout_root.layer_id(0),
|
let render_layer = Arc::new(RenderLayer::new(layout_root.layer_id(0),
|
||||||
display_list: display_list.clone(),
|
color,
|
||||||
position: Rect(Point2D(0u, 0u), root_size),
|
Scrollable));
|
||||||
background_color: color,
|
let origin = Rect(Point2D(Au(0), Au(0)), root_size);
|
||||||
scroll_policy: Scrollable,
|
let stacking_context = Arc::new(StackingContext::new(display_list,
|
||||||
};
|
origin,
|
||||||
|
0,
|
||||||
|
Some(render_layer)));
|
||||||
|
|
||||||
rw_data.display_list = Some(display_list);
|
rw_data.stacking_context = Some(stacking_context.clone());
|
||||||
|
|
||||||
// TODO(pcwalton): Eventually, when we have incremental reflow, this will have to
|
|
||||||
// be smarter in order to handle retained layer contents properly from reflow to
|
|
||||||
// reflow.
|
|
||||||
let mut layers = SmallVec1::new();
|
|
||||||
layers.push(render_layer);
|
|
||||||
for layer in mem::replace(&mut flow::mut_base(layout_root.deref_mut()).layers,
|
|
||||||
DList::new()).into_iter() {
|
|
||||||
layers.push(layer)
|
|
||||||
}
|
|
||||||
|
|
||||||
debug!("Layout done!");
|
debug!("Layout done!");
|
||||||
|
|
||||||
self.render_chan.send(RenderInitMsg(layers));
|
self.render_chan.send(RenderInitMsg(stacking_context));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -933,27 +916,23 @@ impl LayoutRPC for LayoutRPCImpl {
|
||||||
ContentBoxesResponse(rw_data.content_boxes_response.clone())
|
ContentBoxesResponse(rw_data.content_boxes_response.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Requests the node containing the point of interest
|
/// Requests the node containing the point of interest.
|
||||||
fn hit_test(&self, _: TrustedNodeAddress, point: Point2D<f32>) -> Result<HitTestResponse, ()> {
|
fn hit_test(&self, _: TrustedNodeAddress, point: Point2D<f32>) -> Result<HitTestResponse, ()> {
|
||||||
fn hit_test<'a,I>(point: Point2D<Au>, mut iterator: I)
|
|
||||||
-> Option<HitTestResponse>
|
|
||||||
where I: Iterator<&'a DisplayItem> {
|
|
||||||
for item in iterator {
|
|
||||||
// TODO(tikue): This check should really be performed by a method of `DisplayItem`.
|
|
||||||
if geometry::rect_contains_point(item.base().clip_rect, point) &&
|
|
||||||
geometry::rect_contains_point(item.bounds(), point) {
|
|
||||||
return Some(HitTestResponse(item.base().node.to_untrusted_node_address()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
let point = Point2D(Au::from_frac_px(point.x as f64), Au::from_frac_px(point.y as f64));
|
let point = Point2D(Au::from_frac_px(point.x as f64), Au::from_frac_px(point.y as f64));
|
||||||
let resp = {
|
let resp = {
|
||||||
let &LayoutRPCImpl(ref rw_data) = self;
|
let &LayoutRPCImpl(ref rw_data) = self;
|
||||||
let rw_data = rw_data.lock();
|
let rw_data = rw_data.lock();
|
||||||
match rw_data.display_list {
|
match rw_data.stacking_context {
|
||||||
None => panic!("no display list!"),
|
None => panic!("no root stacking context!"),
|
||||||
Some(ref display_list) => hit_test(point, display_list.list.iter().rev()),
|
Some(ref stacking_context) => {
|
||||||
|
let mut result = Vec::new();
|
||||||
|
stacking_context.hit_test(point, &mut result, true);
|
||||||
|
if !result.is_empty() {
|
||||||
|
Some(HitTestResponse(result[0]))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -965,29 +944,17 @@ impl LayoutRPC for LayoutRPCImpl {
|
||||||
|
|
||||||
fn mouse_over(&self, _: TrustedNodeAddress, point: Point2D<f32>)
|
fn mouse_over(&self, _: TrustedNodeAddress, point: Point2D<f32>)
|
||||||
-> Result<MouseOverResponse, ()> {
|
-> Result<MouseOverResponse, ()> {
|
||||||
fn mouse_over_test<'a,I>(point: Point2D<Au>,
|
|
||||||
mut iterator: I,
|
|
||||||
result: &mut Vec<UntrustedNodeAddress>)
|
|
||||||
where I: Iterator<&'a DisplayItem> {
|
|
||||||
for item in iterator {
|
|
||||||
// TODO(tikue): This check should really be performed by a method of `DisplayItem`.
|
|
||||||
if geometry::rect_contains_point(item.bounds(), point) {
|
|
||||||
result.push(item.base().node.to_untrusted_node_address())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut mouse_over_list: Vec<UntrustedNodeAddress> = vec!();
|
let mut mouse_over_list: Vec<UntrustedNodeAddress> = vec!();
|
||||||
let point = Point2D(Au::from_frac_px(point.x as f64), Au::from_frac_px(point.y as f64));
|
let point = Point2D(Au::from_frac_px(point.x as f64), Au::from_frac_px(point.y as f64));
|
||||||
{
|
{
|
||||||
let &LayoutRPCImpl(ref rw_data) = self;
|
let &LayoutRPCImpl(ref rw_data) = self;
|
||||||
let rw_data = rw_data.lock();
|
let rw_data = rw_data.lock();
|
||||||
match rw_data.display_list {
|
match rw_data.stacking_context {
|
||||||
None => panic!("no display list!"),
|
None => panic!("no root stacking context!"),
|
||||||
Some(ref display_list) => {
|
Some(ref stacking_context) => {
|
||||||
mouse_over_test(point, display_list.list.iter().rev(), &mut mouse_over_list);
|
stacking_context.hit_test(point, &mut mouse_over_list, false);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if mouse_over_list.is_empty() {
|
if mouse_over_list.is_empty() {
|
||||||
|
|
|
@ -96,3 +96,30 @@ pub fn append_from<T>(this: &mut DList<T>, other: &mut DList<T>) {
|
||||||
other.length = 0;
|
other.length = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Prepends the items in the other list to this one, leaving the other list empty.
|
||||||
|
#[inline]
|
||||||
|
pub fn prepend_from<T>(this: &mut DList<T>, other: &mut DList<T>) {
|
||||||
|
unsafe {
|
||||||
|
let this = mem::transmute::<&mut DList<T>,&mut RawDList<T>>(this);
|
||||||
|
let other = mem::transmute::<&mut DList<T>,&mut RawDList<T>>(other);
|
||||||
|
if this.length == 0 {
|
||||||
|
this.head = mem::replace(&mut other.head, ptr::null_mut());
|
||||||
|
this.tail = mem::replace(&mut other.tail, ptr::null_mut());
|
||||||
|
this.length = mem::replace(&mut other.length, 0);
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let old_other_tail = mem::replace(&mut other.tail, ptr::null_mut());
|
||||||
|
if old_other_tail.is_null() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
(*old_other_tail).next = this.head;
|
||||||
|
(*this.head).prev = old_other_tail;
|
||||||
|
|
||||||
|
this.head = mem::replace(&mut other.head, ptr::null_mut());
|
||||||
|
this.length += other.length;
|
||||||
|
other.length = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -74,6 +74,11 @@ impl Default for Au {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub static ZERO_POINT: Point2D<Au> = Point2D {
|
||||||
|
x: Au(0),
|
||||||
|
y: Au(0),
|
||||||
|
};
|
||||||
|
|
||||||
pub static ZERO_RECT: Rect<Au> = Rect {
|
pub static ZERO_RECT: Rect<Au> = Rect {
|
||||||
origin: Point2D {
|
origin: Point2D {
|
||||||
x: Au(0),
|
x: Au(0),
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue