layout: Implement basic overflow: scroll functionality.

Known issues:

* Display list optimization can sometimes optimize out elements that
  should be shown. This affects the Enyo demo.

* The `overflow: scroll` container doesn't clip the inner layer properly
  when borders, border radius, etc. are present.

* `overflow-x: scroll` and `overflow-y: scroll` don't work individually;
  elements are scrolled all at once.

* Scrolling only works on absolutely-positioned elements.
This commit is contained in:
Patrick Walton 2015-08-07 18:10:48 -07:00
parent fc13dd1169
commit df4acbac04
8 changed files with 221 additions and 72 deletions

View file

@ -629,6 +629,7 @@ impl<Window: WindowMethods> IOCompositor<Window> {
transform: Matrix4::identity(), transform: Matrix4::identity(),
perspective: Matrix4::identity(), perspective: Matrix4::identity(),
establishes_3d_context: true, establishes_3d_context: true,
scrolls_overflow_area: false,
}; };
let root_layer = CompositorData::new_layer(pipeline.id, let root_layer = CompositorData::new_layer(pipeline.id,
@ -740,10 +741,21 @@ impl<Window: WindowMethods> IOCompositor<Window> {
if let Some(parent_layer) = self.find_layer_with_pipeline_and_layer_id(pipeline_id, if let Some(parent_layer) = self.find_layer_with_pipeline_and_layer_id(pipeline_id,
parent_id) { parent_id) {
let wants_scroll_events = if layer_properties.scrolls_overflow_area {
WantsScrollEventsFlag::WantsScrollEvents
} else {
WantsScrollEventsFlag::DoesntWantScrollEvents
};
let new_layer = CompositorData::new_layer(pipeline_id, let new_layer = CompositorData::new_layer(pipeline_id,
layer_properties, layer_properties,
WantsScrollEventsFlag::DoesntWantScrollEvents, wants_scroll_events,
parent_layer.tile_size); parent_layer.tile_size);
if layer_properties.scrolls_overflow_area {
*new_layer.masks_to_bounds.borrow_mut() = true
}
parent_layer.add_child(new_layer); parent_layer.add_child(new_layer);
} }
} }

View file

@ -252,6 +252,9 @@ pub struct StackingContext {
/// Whether this stacking context creates a new 3d rendering context. /// Whether this stacking context creates a new 3d rendering context.
pub establishes_3d_context: bool, pub establishes_3d_context: bool,
/// Whether this stacking context scrolls its overflow area.
pub scrolls_overflow_area: bool,
} }
impl StackingContext { impl StackingContext {
@ -266,7 +269,8 @@ impl StackingContext {
layer: Option<PaintLayer>, layer: Option<PaintLayer>,
transform: Matrix4, transform: Matrix4,
perspective: Matrix4, perspective: Matrix4,
establishes_3d_context: bool) establishes_3d_context: bool,
scrolls_overflow_area: bool)
-> StackingContext { -> StackingContext {
StackingContext { StackingContext {
display_list: display_list, display_list: display_list,
@ -279,6 +283,7 @@ impl StackingContext {
transform: transform, transform: transform,
perspective: perspective, perspective: perspective,
establishes_3d_context: establishes_3d_context, establishes_3d_context: establishes_3d_context,
scrolls_overflow_area: scrolls_overflow_area,
} }
} }

View file

@ -342,29 +342,27 @@ impl<C> PaintTask<C> where C: PaintListener + Send + 'static {
transform: &Matrix4, transform: &Matrix4,
perspective: &Matrix4, perspective: &Matrix4,
parent_id: Option<LayerId>) { parent_id: Option<LayerId>) {
let transform = transform.mul(&stacking_context.transform); let transform = transform.mul(&stacking_context.transform);
let perspective = perspective.mul(&stacking_context.perspective); let perspective = perspective.mul(&stacking_context.perspective);
let (next_parent_id, page_position, transform, perspective) = let (next_parent_id, page_position, transform, perspective) =
match stacking_context.layer { match stacking_context.layer {
Some(ref paint_layer) => { Some(ref paint_layer) => {
// Layers start at the top left of their overflow rect, as far as the info we let overflow_size =
// give to the compositor is concerned. Size2D::new(stacking_context.overflow.size.width.to_nearest_px() as f32,
stacking_context.overflow.size.height.to_nearest_px() as f32);
let establishes_3d_context = stacking_context.establishes_3d_context;
let scrolls_overflow_area = stacking_context.scrolls_overflow_area;
// Layers start at the top left of their overflow rect, as far as the info
// we give to the compositor is concerned.
let overflow_relative_page_position = *page_position + let overflow_relative_page_position = *page_position +
stacking_context.bounds.origin + stacking_context.bounds.origin +
stacking_context.overflow.origin; stacking_context.overflow.origin;
let layer_position = let layer_position = Rect::new(
Rect::new(Point2D::new(overflow_relative_page_position.x.to_nearest_px() as Point2D::new(overflow_relative_page_position.x.to_nearest_px() as f32,
f32, overflow_relative_page_position.y.to_nearest_px() as f32),
overflow_relative_page_position.y.to_nearest_px() as overflow_size);
f32),
Size2D::new(stacking_context.overflow.size.width.to_nearest_px()
as f32,
stacking_context.overflow.size.height.to_nearest_px()
as f32));
let establishes_3d_context = stacking_context.establishes_3d_context;
properties.push(LayerProperties { properties.push(LayerProperties {
id: paint_layer.id, id: paint_layer.id,
@ -375,6 +373,7 @@ impl<C> PaintTask<C> where C: PaintListener + Send + 'static {
transform: transform, transform: transform,
perspective: perspective, perspective: perspective,
establishes_3d_context: establishes_3d_context, establishes_3d_context: establishes_3d_context,
scrolls_overflow_area: scrolls_overflow_area,
}); });
// When there is a new layer, the transforms and origin // When there is a new layer, the transforms and origin

View file

@ -1756,6 +1756,17 @@ impl Flow for BlockFlow {
} }
if self.base.flags.contains(IS_ABSOLUTELY_POSITIONED) { if self.base.flags.contains(IS_ABSOLUTELY_POSITIONED) {
// `overflow: auto` and `overflow: scroll` force creation of layers, since we can only
// scroll layers.
match (self.fragment.style().get_box().overflow_x,
self.fragment.style().get_box().overflow_y.0) {
(overflow_x::T::auto, _) | (overflow_x::T::scroll, _) |
(_, overflow_x::T::auto) | (_, overflow_x::T::scroll) => {
self.base.flags.insert(NEEDS_LAYER);
}
_ => {}
}
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);
@ -1892,8 +1903,10 @@ impl Flow for BlockFlow {
.absolute_position_info .absolute_position_info
.relative_containing_block_mode, .relative_containing_block_mode,
CoordinateSystem::Own); CoordinateSystem::Own);
let clip = self.fragment.clipping_region_for_children(&clip_in_child_coordinate_system, let clip = self.fragment.clipping_region_for_children(
&stacking_relative_border_box); &clip_in_child_coordinate_system,
&stacking_relative_border_box,
self.base.flags.contains(IS_ABSOLUTELY_POSITIONED));
// Process children. // Process children.
for kid in self.base.child_iter() { for kid in self.base.child_iter() {

View file

@ -62,6 +62,11 @@ use util::geometry::{Au, ZERO_POINT};
use util::logical_geometry::{LogicalPoint, LogicalRect, LogicalSize, WritingMode}; use util::logical_geometry::{LogicalPoint, LogicalRect, LogicalSize, WritingMode};
use util::opts; use util::opts;
/// The fake fragment ID we use to indicate the inner display list for `overflow: scroll`.
///
/// FIXME(pcwalton): This is pretty ugly. Consider modifying `LayerId` somehow.
const FAKE_FRAGMENT_ID_FOR_OVERFLOW_SCROLL: u32 = 1000000;
/// A possible `PaintLayer` for an stacking context /// A possible `PaintLayer` for an stacking context
pub enum StackingContextLayer { pub enum StackingContextLayer {
Existing(PaintLayer), Existing(PaintLayer),
@ -208,10 +213,11 @@ pub trait FragmentDisplayListBuilding {
offset: Point2D<Au>, offset: Point2D<Au>,
layout_context: &LayoutContext); layout_context: &LayoutContext);
/// Returns the appropriate clipping region for descendants of this flow. /// Returns the appropriate clipping region for descendants of this fragment.
fn clipping_region_for_children(&self, fn clipping_region_for_children(&self,
current_clip: &ClippingRegion, current_clip: &ClippingRegion,
stacking_relative_border_box: &Rect<Au>) stacking_relative_border_box: &Rect<Au>,
is_absolutely_positioned: bool)
-> ClippingRegion; -> ClippingRegion;
/// Calculates the clipping rectangle for a fragment, taking the `clip` property into account /// Calculates the clipping rectangle for a fragment, taking the `clip` property into account
@ -253,7 +259,8 @@ pub trait FragmentDisplayListBuilding {
base_flow: &BaseFlow, base_flow: &BaseFlow,
display_list: Box<DisplayList>, display_list: Box<DisplayList>,
layout_context: &LayoutContext, layout_context: &LayoutContext,
layer: StackingContextLayer) layer: StackingContextLayer,
mode: StackingContextCreationMode)
-> Arc<StackingContext>; -> Arc<StackingContext>;
} }
@ -1129,17 +1136,37 @@ impl FragmentDisplayListBuilding for Fragment {
base_flow: &BaseFlow, base_flow: &BaseFlow,
display_list: Box<DisplayList>, display_list: Box<DisplayList>,
layout_context: &LayoutContext, layout_context: &LayoutContext,
layer: StackingContextLayer) layer: StackingContextLayer,
mode: StackingContextCreationMode)
-> Arc<StackingContext> { -> Arc<StackingContext> {
let border_box = self.stacking_relative_border_box(&base_flow.stacking_relative_position, // FIXME(pcwalton): Is this vertical-writing-direction-safe?
&base_flow.absolute_position_info let margin = self.margin.to_physical(base_flow.writing_mode);
.relative_containing_block_size,
base_flow.absolute_position_info let border_box = match mode {
.relative_containing_block_mode, StackingContextCreationMode::Normal |
CoordinateSystem::Parent); StackingContextCreationMode::OuterScrollWrapper => {
self.stacking_relative_border_box(&base_flow.stacking_relative_position,
&base_flow.absolute_position_info
.relative_containing_block_size,
base_flow.absolute_position_info
.relative_containing_block_mode,
CoordinateSystem::Parent)
}
StackingContextCreationMode::InnerScrollWrapper => {
Rect::new(ZERO_POINT, base_flow.overflow.size)
}
};
let overflow = match mode {
StackingContextCreationMode::Normal => {
base_flow.overflow.translate(&-Point2D::new(margin.left, Au(0)))
}
StackingContextCreationMode::InnerScrollWrapper |
StackingContextCreationMode::OuterScrollWrapper => {
Rect::new(ZERO_POINT, border_box.size)
}
};
let mut transform = Matrix4::identity(); let mut transform = Matrix4::identity();
if let Some(ref operations) = self.style().get_effects().transform.0 { if let Some(ref operations) = self.style().get_effects().transform.0 {
let transform_origin = self.style().get_effects().transform_origin; let transform_origin = self.style().get_effects().transform_origin;
let transform_origin = let transform_origin =
@ -1213,10 +1240,6 @@ impl FragmentDisplayListBuilding for Fragment {
} }
}; };
// FIXME(pcwalton): Is this vertical-writing-direction-safe?
let margin = self.margin.to_physical(base_flow.writing_mode);
let overflow = base_flow.overflow.translate(&-Point2D::new(margin.left, Au(0)));
// Create the filter pipeline. // Create the filter pipeline.
let effects = self.style().get_effects(); let effects = self.style().get_effects();
let mut filters = effects.filter.clone(); let mut filters = effects.filter.clone();
@ -1247,7 +1270,10 @@ impl FragmentDisplayListBuilding for Fragment {
} }
} }
let scrolls_overflow_area = mode == StackingContextCreationMode::OuterScrollWrapper;
let transform_style = self.style().get_used_transform_style(); let transform_style = self.style().get_used_transform_style();
let establishes_3d_context = scrolls_overflow_area ||
transform_style == transform_style::T::flat;
Arc::new(StackingContext::new(display_list, Arc::new(StackingContext::new(display_list,
&border_box, &border_box,
@ -1258,7 +1284,8 @@ impl FragmentDisplayListBuilding for Fragment {
layer, layer,
transform, transform,
perspective, perspective,
transform_style == transform_style::T::flat)) establishes_3d_context,
scrolls_overflow_area))
} }
#[inline(never)] #[inline(never)]
@ -1284,7 +1311,8 @@ impl FragmentDisplayListBuilding for Fragment {
fn clipping_region_for_children(&self, fn clipping_region_for_children(&self,
current_clip: &ClippingRegion, current_clip: &ClippingRegion,
stacking_relative_border_box: &Rect<Au>) stacking_relative_border_box: &Rect<Au>,
is_absolutely_positioned: bool)
-> ClippingRegion { -> ClippingRegion {
// Don't clip if we're text. // Don't clip if we're text.
if self.is_scanned_text_fragment() { if self.is_scanned_text_fragment() {
@ -1297,12 +1325,14 @@ impl FragmentDisplayListBuilding for Fragment {
// Clip according to the values of `overflow-x` and `overflow-y`. // Clip according to the values of `overflow-x` and `overflow-y`.
// //
// TODO(pcwalton): Support scrolling. // TODO(pcwalton): Support scrolling of non-absolutely-positioned elements.
// FIXME(pcwalton): This may be more complex than it needs to be, since it seems to be // FIXME(pcwalton): This may be more complex than it needs to be, since it seems to be
// impossible with the computed value rules as they are to have `overflow-x: visible` with // impossible with the computed value rules as they are to have `overflow-x: visible` with
// `overflow-y: <scrolling>` or vice versa! // `overflow-y: <scrolling>` or vice versa!
match self.style.get_box().overflow_x { match (self.style.get_box().overflow_x, is_absolutely_positioned) {
overflow_x::T::hidden | overflow_x::T::auto | overflow_x::T::scroll => { (overflow_x::T::hidden, _) |
(overflow_x::T::auto, false) |
(overflow_x::T::scroll, false) => {
let mut bounds = current_clip.bounding_rect(); let mut bounds = current_clip.bounding_rect();
let max_x = cmp::min(bounds.max_x(), stacking_relative_border_box.max_x()); let max_x = cmp::min(bounds.max_x(), stacking_relative_border_box.max_x());
bounds.origin.x = cmp::max(bounds.origin.x, stacking_relative_border_box.origin.x); bounds.origin.x = cmp::max(bounds.origin.x, stacking_relative_border_box.origin.x);
@ -1311,8 +1341,10 @@ impl FragmentDisplayListBuilding for Fragment {
} }
_ => {} _ => {}
} }
match self.style.get_box().overflow_y.0 { match (self.style.get_box().overflow_y.0, is_absolutely_positioned) {
overflow_x::T::hidden | overflow_x::T::auto | overflow_x::T::scroll => { (overflow_x::T::hidden, _) |
(overflow_x::T::auto, false) |
(overflow_x::T::scroll, false) => {
let mut bounds = current_clip.bounding_rect(); let mut bounds = current_clip.bounding_rect();
let max_y = cmp::min(bounds.max_y(), stacking_relative_border_box.max_y()); let max_y = cmp::min(bounds.max_y(), stacking_relative_border_box.max_y());
bounds.origin.y = cmp::max(bounds.origin.y, stacking_relative_border_box.origin.y); bounds.origin.y = cmp::max(bounds.origin.y, stacking_relative_border_box.origin.y);
@ -1527,17 +1559,21 @@ impl BlockFlowDisplayListBuilding for BlockFlow {
let paint_layer = PaintLayer::new(self.layer_id(0), color::transparent(), scroll_policy); let paint_layer = PaintLayer::new(self.layer_id(0), color::transparent(), scroll_policy);
let layer = StackingContextLayer::Existing(paint_layer); let layer = StackingContextLayer::Existing(paint_layer);
let stacking_context = self.fragment.create_stacking_context(&self.base, let stacking_context = self.fragment.create_stacking_context(
display_list, &self.base,
layout_context, display_list,
layer); layout_context,
layer,
StackingContextCreationMode::Normal);
DisplayListBuildingResult::StackingContext(stacking_context) DisplayListBuildingResult::StackingContext(stacking_context)
} else { } else {
DisplayListBuildingResult::StackingContext( DisplayListBuildingResult::StackingContext(
self.fragment.create_stacking_context(&self.base, self.fragment.create_stacking_context(
display_list, &self.base,
layout_context, display_list,
StackingContextLayer::IfCanvas(self.layer_id(0)))) layout_context,
StackingContextLayer::IfCanvas(self.layer_id(0)),
StackingContextCreationMode::Normal))
} }
} else { } else {
match self.fragment.style.get_box().position { match self.fragment.style.get_box().position {
@ -1560,19 +1596,57 @@ impl BlockFlowDisplayListBuilding for BlockFlow {
mut display_list: Box<DisplayList>, mut display_list: Box<DisplayList>,
layout_context: &LayoutContext, layout_context: &LayoutContext,
border_painting_mode: BorderPaintingMode) { border_painting_mode: BorderPaintingMode) {
self.build_display_list_for_block_base(&mut *display_list, // If `overflow: scroll` is in effect, we add this fragment's display items to a new
layout_context, // stacking context.
border_painting_mode, let outer_display_list_for_overflow_scroll =
BackgroundAndBorderLevel::RootOfStackingContext); match (self.fragment.style().get_box().overflow_x,
self.fragment.style().get_box().overflow_y.0) {
(overflow_x::T::auto, _) |
(overflow_x::T::scroll, _) |
(_, overflow_x::T::auto) |
(_, overflow_x::T::scroll) => {
// Create a separate display list for our own fragment.
let mut outer_display_list_for_overflow_scroll = box DisplayList::new();
let clip = self.base.clip.translate(&-self.base.stacking_relative_position);
self.fragment.build_display_list(
&mut outer_display_list_for_overflow_scroll,
layout_context,
&self.base.stacking_relative_position,
&self.base.absolute_position_info.relative_containing_block_size,
self.base.absolute_position_info.relative_containing_block_mode,
border_painting_mode,
BackgroundAndBorderLevel::RootOfStackingContext,
&clip,
&self.base.stacking_relative_position_of_display_port);
// Add the fragments of our children to the display list we'll use for the inner
// stacking context.
for kid in self.base.children.iter_mut() {
flow::mut_base(kid).display_list_building_result.add_to(&mut *display_list);
}
Some(outer_display_list_for_overflow_scroll)
}
_ => {
self.build_display_list_for_block_base(
&mut *display_list,
layout_context,
border_painting_mode,
BackgroundAndBorderLevel::RootOfStackingContext);
None
}
};
if !self.will_get_layer() { if !self.will_get_layer() {
// We didn't need a layer. // We didn't need a layer.
self.base.display_list_building_result = self.base.display_list_building_result =
DisplayListBuildingResult::StackingContext( DisplayListBuildingResult::StackingContext(
self.fragment.create_stacking_context(&self.base, self.fragment.create_stacking_context(
display_list, &self.base,
layout_context, display_list,
StackingContextLayer::IfCanvas(self.layer_id(0)))); layout_context,
StackingContextLayer::IfCanvas(self.layer_id(0)),
StackingContextCreationMode::Normal));
return return
} }
@ -1583,13 +1657,44 @@ impl BlockFlowDisplayListBuilding for BlockFlow {
ScrollPolicy::Scrollable ScrollPolicy::Scrollable
}; };
let paint_layer = PaintLayer::new(self.layer_id(0), color::transparent(), scroll_policy); let stacking_context_creation_mode = if outer_display_list_for_overflow_scroll.is_some() {
let stacking_context = self.fragment.create_stacking_context(&self.base, StackingContextCreationMode::InnerScrollWrapper
display_list, } else {
layout_context, StackingContextCreationMode::Normal
StackingContextLayer::Existing(paint_layer)); };
let layer_id = if outer_display_list_for_overflow_scroll.is_some() {
self.layer_id(FAKE_FRAGMENT_ID_FOR_OVERFLOW_SCROLL)
} else {
self.layer_id(0)
};
let paint_layer = PaintLayer::new(layer_id, color::transparent(), scroll_policy);
let stacking_context = self.fragment.create_stacking_context(
&self.base,
display_list,
layout_context,
StackingContextLayer::Existing(paint_layer),
stacking_context_creation_mode);
let outermost_stacking_context = match outer_display_list_for_overflow_scroll {
Some(mut outer_display_list_for_overflow_scroll) => {
outer_display_list_for_overflow_scroll.children.push_back(stacking_context);
let paint_layer = PaintLayer::new(self.layer_id(0),
color::transparent(),
scroll_policy);
self.fragment.create_stacking_context(
&self.base,
outer_display_list_for_overflow_scroll,
layout_context,
StackingContextLayer::Existing(paint_layer),
StackingContextCreationMode::OuterScrollWrapper)
}
None => stacking_context,
};
self.base.display_list_building_result = self.base.display_list_building_result =
DisplayListBuildingResult::StackingContext(stacking_context) DisplayListBuildingResult::StackingContext(outermost_stacking_context)
} }
fn build_display_list_for_floating_block(&mut self, fn build_display_list_for_floating_block(&mut self,
@ -1604,10 +1709,12 @@ impl BlockFlowDisplayListBuilding for BlockFlow {
self.base.display_list_building_result = if self.fragment.establishes_stacking_context() { self.base.display_list_building_result = if self.fragment.establishes_stacking_context() {
DisplayListBuildingResult::StackingContext( DisplayListBuildingResult::StackingContext(
self.fragment.create_stacking_context(&self.base, self.fragment.create_stacking_context(
display_list, &self.base,
layout_context, display_list,
StackingContextLayer::IfCanvas(self.layer_id(0)))) layout_context,
StackingContextLayer::IfCanvas(self.layer_id(0)),
StackingContextCreationMode::Normal))
} else { } else {
DisplayListBuildingResult::Normal(display_list) DisplayListBuildingResult::Normal(display_list)
} }
@ -1702,10 +1809,12 @@ impl InlineFlowDisplayListBuilding for InlineFlow {
self.base.display_list_building_result = if has_stacking_context { self.base.display_list_building_result = if has_stacking_context {
DisplayListBuildingResult::StackingContext( DisplayListBuildingResult::StackingContext(
self.fragments.fragments[0].create_stacking_context(&self.base, self.fragments.fragments[0].create_stacking_context(
display_list, &self.base,
layout_context, display_list,
StackingContextLayer::IfCanvas(self.layer_id(0)))) layout_context,
StackingContextLayer::IfCanvas(self.layer_id(0)),
StackingContextCreationMode::Normal))
} else { } else {
DisplayListBuildingResult::Normal(display_list) DisplayListBuildingResult::Normal(display_list)
}; };
@ -1895,3 +2004,10 @@ pub enum BorderPaintingMode<'a> {
Hidden, Hidden,
} }
#[derive(Copy, Clone, PartialEq)]
pub enum StackingContextCreationMode {
Normal,
OuterScrollWrapper,
InnerScrollWrapper,
}

View file

@ -1627,7 +1627,8 @@ impl Flow for InlineFlow {
.relative_containing_block_mode, .relative_containing_block_mode,
CoordinateSystem::Parent); CoordinateSystem::Parent);
let clip = fragment.clipping_region_for_children(&self.base.clip, let clip = fragment.clipping_region_for_children(&self.base.clip,
&stacking_relative_border_box); &stacking_relative_border_box,
false);
match fragment.specific { match fragment.specific {
SpecificFragmentInfo::InlineBlock(ref mut info) => { SpecificFragmentInfo::InlineBlock(ref mut info) => {
flow::mut_base(&mut *info.flow_ref).clip = clip; flow::mut_base(&mut *info.flow_ref).clip = clip;

View file

@ -1046,7 +1046,8 @@ impl LayoutTask {
Some(paint_layer), Some(paint_layer),
Matrix4::identity(), Matrix4::identity(),
Matrix4::identity(), Matrix4::identity(),
true)); true,
false));
if opts::get().dump_display_list { if opts::get().dump_display_list {
println!("#### start printing display list."); println!("#### start printing display list.");

View file

@ -88,6 +88,8 @@ pub struct LayerProperties {
pub perspective: Matrix4, pub perspective: Matrix4,
/// Whether this layer establishes a new 3d rendering context. /// Whether this layer establishes a new 3d rendering context.
pub establishes_3d_context: bool, pub establishes_3d_context: bool,
/// Whether this layer scrolls its overflow area.
pub scrolls_overflow_area: bool,
} }
/// The interface used by the painter to acquire draw targets for each paint frame and /// The interface used by the painter to acquire draw targets for each paint frame and