diff --git a/components/compositing/compositor.rs b/components/compositing/compositor.rs index cfa5f68a4e0..3c4b5419303 100644 --- a/components/compositing/compositor.rs +++ b/components/compositing/compositor.rs @@ -629,6 +629,7 @@ impl IOCompositor { transform: Matrix4::identity(), perspective: Matrix4::identity(), establishes_3d_context: true, + scrolls_overflow_area: false, }; let root_layer = CompositorData::new_layer(pipeline.id, @@ -740,10 +741,21 @@ impl IOCompositor { if let Some(parent_layer) = self.find_layer_with_pipeline_and_layer_id(pipeline_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, layer_properties, - WantsScrollEventsFlag::DoesntWantScrollEvents, + wants_scroll_events, parent_layer.tile_size); + + if layer_properties.scrolls_overflow_area { + *new_layer.masks_to_bounds.borrow_mut() = true + } + parent_layer.add_child(new_layer); } } diff --git a/components/gfx/display_list/mod.rs b/components/gfx/display_list/mod.rs index eda83adf795..6f2ebed0851 100644 --- a/components/gfx/display_list/mod.rs +++ b/components/gfx/display_list/mod.rs @@ -252,6 +252,9 @@ pub struct StackingContext { /// Whether this stacking context creates a new 3d rendering context. pub establishes_3d_context: bool, + + /// Whether this stacking context scrolls its overflow area. + pub scrolls_overflow_area: bool, } impl StackingContext { @@ -266,7 +269,8 @@ impl StackingContext { layer: Option, transform: Matrix4, perspective: Matrix4, - establishes_3d_context: bool) + establishes_3d_context: bool, + scrolls_overflow_area: bool) -> StackingContext { StackingContext { display_list: display_list, @@ -279,6 +283,7 @@ impl StackingContext { transform: transform, perspective: perspective, establishes_3d_context: establishes_3d_context, + scrolls_overflow_area: scrolls_overflow_area, } } diff --git a/components/gfx/paint_task.rs b/components/gfx/paint_task.rs index ebe86de75e6..e386ea011ad 100644 --- a/components/gfx/paint_task.rs +++ b/components/gfx/paint_task.rs @@ -342,29 +342,27 @@ impl PaintTask where C: PaintListener + Send + 'static { transform: &Matrix4, perspective: &Matrix4, parent_id: Option) { - let transform = transform.mul(&stacking_context.transform); let perspective = perspective.mul(&stacking_context.perspective); let (next_parent_id, page_position, transform, perspective) = match stacking_context.layer { Some(ref paint_layer) => { - // Layers start at the top left of their overflow rect, as far as the info we - // give to the compositor is concerned. + let overflow_size = + 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 + stacking_context.bounds.origin + stacking_context.overflow.origin; - let layer_position = - Rect::new(Point2D::new(overflow_relative_page_position.x.to_nearest_px() as - f32, - overflow_relative_page_position.y.to_nearest_px() as - 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; + let layer_position = Rect::new( + Point2D::new(overflow_relative_page_position.x.to_nearest_px() as f32, + overflow_relative_page_position.y.to_nearest_px() as f32), + overflow_size); properties.push(LayerProperties { id: paint_layer.id, @@ -375,6 +373,7 @@ impl PaintTask where C: PaintListener + Send + 'static { transform: transform, perspective: perspective, establishes_3d_context: establishes_3d_context, + scrolls_overflow_area: scrolls_overflow_area, }); // When there is a new layer, the transforms and origin diff --git a/components/layout/block.rs b/components/layout/block.rs index c8f960dc7cc..e044c04d3c0 100644 --- a/components/layout/block.rs +++ b/components/layout/block.rs @@ -1756,6 +1756,17 @@ impl Flow for BlockFlow { } 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, container_size); @@ -1892,8 +1903,10 @@ impl Flow for BlockFlow { .absolute_position_info .relative_containing_block_mode, CoordinateSystem::Own); - let clip = self.fragment.clipping_region_for_children(&clip_in_child_coordinate_system, - &stacking_relative_border_box); + let clip = self.fragment.clipping_region_for_children( + &clip_in_child_coordinate_system, + &stacking_relative_border_box, + self.base.flags.contains(IS_ABSOLUTELY_POSITIONED)); // Process children. for kid in self.base.child_iter() { diff --git a/components/layout/display_list_builder.rs b/components/layout/display_list_builder.rs index e30a7d08635..00dbb8b2570 100644 --- a/components/layout/display_list_builder.rs +++ b/components/layout/display_list_builder.rs @@ -62,6 +62,11 @@ use util::geometry::{Au, ZERO_POINT}; use util::logical_geometry::{LogicalPoint, LogicalRect, LogicalSize, WritingMode}; 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 pub enum StackingContextLayer { Existing(PaintLayer), @@ -208,10 +213,11 @@ pub trait FragmentDisplayListBuilding { offset: Point2D, 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, current_clip: &ClippingRegion, - stacking_relative_border_box: &Rect) + stacking_relative_border_box: &Rect, + is_absolutely_positioned: bool) -> ClippingRegion; /// Calculates the clipping rectangle for a fragment, taking the `clip` property into account @@ -253,7 +259,8 @@ pub trait FragmentDisplayListBuilding { base_flow: &BaseFlow, display_list: Box, layout_context: &LayoutContext, - layer: StackingContextLayer) + layer: StackingContextLayer, + mode: StackingContextCreationMode) -> Arc; } @@ -1129,17 +1136,37 @@ impl FragmentDisplayListBuilding for Fragment { base_flow: &BaseFlow, display_list: Box, layout_context: &LayoutContext, - layer: StackingContextLayer) + layer: StackingContextLayer, + mode: StackingContextCreationMode) -> Arc { - let border_box = 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); + // FIXME(pcwalton): Is this vertical-writing-direction-safe? + let margin = self.margin.to_physical(base_flow.writing_mode); + + let border_box = match mode { + StackingContextCreationMode::Normal | + 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(); - if let Some(ref operations) = self.style().get_effects().transform.0 { let transform_origin = self.style().get_effects().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. let effects = self.style().get_effects(); 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 establishes_3d_context = scrolls_overflow_area || + transform_style == transform_style::T::flat; Arc::new(StackingContext::new(display_list, &border_box, @@ -1258,7 +1284,8 @@ impl FragmentDisplayListBuilding for Fragment { layer, transform, perspective, - transform_style == transform_style::T::flat)) + establishes_3d_context, + scrolls_overflow_area)) } #[inline(never)] @@ -1284,7 +1311,8 @@ impl FragmentDisplayListBuilding for Fragment { fn clipping_region_for_children(&self, current_clip: &ClippingRegion, - stacking_relative_border_box: &Rect) + stacking_relative_border_box: &Rect, + is_absolutely_positioned: bool) -> ClippingRegion { // Don't clip if we're text. 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`. // - // 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 // impossible with the computed value rules as they are to have `overflow-x: visible` with // `overflow-y: ` or vice versa! - match self.style.get_box().overflow_x { - overflow_x::T::hidden | overflow_x::T::auto | overflow_x::T::scroll => { + match (self.style.get_box().overflow_x, is_absolutely_positioned) { + (overflow_x::T::hidden, _) | + (overflow_x::T::auto, false) | + (overflow_x::T::scroll, false) => { let mut bounds = current_clip.bounding_rect(); 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); @@ -1311,8 +1341,10 @@ impl FragmentDisplayListBuilding for Fragment { } _ => {} } - match self.style.get_box().overflow_y.0 { - overflow_x::T::hidden | overflow_x::T::auto | overflow_x::T::scroll => { + match (self.style.get_box().overflow_y.0, is_absolutely_positioned) { + (overflow_x::T::hidden, _) | + (overflow_x::T::auto, false) | + (overflow_x::T::scroll, false) => { let mut bounds = current_clip.bounding_rect(); 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); @@ -1527,17 +1559,21 @@ impl BlockFlowDisplayListBuilding for BlockFlow { let paint_layer = PaintLayer::new(self.layer_id(0), color::transparent(), scroll_policy); let layer = StackingContextLayer::Existing(paint_layer); - let stacking_context = self.fragment.create_stacking_context(&self.base, - display_list, - layout_context, - layer); + let stacking_context = self.fragment.create_stacking_context( + &self.base, + display_list, + layout_context, + layer, + StackingContextCreationMode::Normal); DisplayListBuildingResult::StackingContext(stacking_context) } else { DisplayListBuildingResult::StackingContext( - self.fragment.create_stacking_context(&self.base, - display_list, - layout_context, - StackingContextLayer::IfCanvas(self.layer_id(0)))) + self.fragment.create_stacking_context( + &self.base, + display_list, + layout_context, + StackingContextLayer::IfCanvas(self.layer_id(0)), + StackingContextCreationMode::Normal)) } } else { match self.fragment.style.get_box().position { @@ -1560,19 +1596,57 @@ impl BlockFlowDisplayListBuilding for BlockFlow { mut display_list: Box, layout_context: &LayoutContext, border_painting_mode: BorderPaintingMode) { - self.build_display_list_for_block_base(&mut *display_list, - layout_context, - border_painting_mode, - BackgroundAndBorderLevel::RootOfStackingContext); + // If `overflow: scroll` is in effect, we add this fragment's display items to a new + // stacking context. + let outer_display_list_for_overflow_scroll = + 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() { // We didn't need a layer. self.base.display_list_building_result = DisplayListBuildingResult::StackingContext( - self.fragment.create_stacking_context(&self.base, - display_list, - layout_context, - StackingContextLayer::IfCanvas(self.layer_id(0)))); + self.fragment.create_stacking_context( + &self.base, + display_list, + layout_context, + StackingContextLayer::IfCanvas(self.layer_id(0)), + StackingContextCreationMode::Normal)); return } @@ -1583,13 +1657,44 @@ impl BlockFlowDisplayListBuilding for BlockFlow { ScrollPolicy::Scrollable }; - let paint_layer = PaintLayer::new(self.layer_id(0), color::transparent(), scroll_policy); - let stacking_context = self.fragment.create_stacking_context(&self.base, - display_list, - layout_context, - StackingContextLayer::Existing(paint_layer)); + let stacking_context_creation_mode = if outer_display_list_for_overflow_scroll.is_some() { + StackingContextCreationMode::InnerScrollWrapper + } else { + StackingContextCreationMode::Normal + }; + + 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 = - DisplayListBuildingResult::StackingContext(stacking_context) + DisplayListBuildingResult::StackingContext(outermost_stacking_context) } 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() { DisplayListBuildingResult::StackingContext( - self.fragment.create_stacking_context(&self.base, - display_list, - layout_context, - StackingContextLayer::IfCanvas(self.layer_id(0)))) + self.fragment.create_stacking_context( + &self.base, + display_list, + layout_context, + StackingContextLayer::IfCanvas(self.layer_id(0)), + StackingContextCreationMode::Normal)) } else { DisplayListBuildingResult::Normal(display_list) } @@ -1702,10 +1809,12 @@ impl InlineFlowDisplayListBuilding for InlineFlow { self.base.display_list_building_result = if has_stacking_context { DisplayListBuildingResult::StackingContext( - self.fragments.fragments[0].create_stacking_context(&self.base, - display_list, - layout_context, - StackingContextLayer::IfCanvas(self.layer_id(0)))) + self.fragments.fragments[0].create_stacking_context( + &self.base, + display_list, + layout_context, + StackingContextLayer::IfCanvas(self.layer_id(0)), + StackingContextCreationMode::Normal)) } else { DisplayListBuildingResult::Normal(display_list) }; @@ -1895,3 +2004,10 @@ pub enum BorderPaintingMode<'a> { Hidden, } +#[derive(Copy, Clone, PartialEq)] +pub enum StackingContextCreationMode { + Normal, + OuterScrollWrapper, + InnerScrollWrapper, +} + diff --git a/components/layout/inline.rs b/components/layout/inline.rs index b293478656e..359660e05ed 100644 --- a/components/layout/inline.rs +++ b/components/layout/inline.rs @@ -1627,7 +1627,8 @@ impl Flow for InlineFlow { .relative_containing_block_mode, CoordinateSystem::Parent); let clip = fragment.clipping_region_for_children(&self.base.clip, - &stacking_relative_border_box); + &stacking_relative_border_box, + false); match fragment.specific { SpecificFragmentInfo::InlineBlock(ref mut info) => { flow::mut_base(&mut *info.flow_ref).clip = clip; diff --git a/components/layout/layout_task.rs b/components/layout/layout_task.rs index 690b95edfa9..76baafc8013 100644 --- a/components/layout/layout_task.rs +++ b/components/layout/layout_task.rs @@ -1046,7 +1046,8 @@ impl LayoutTask { Some(paint_layer), Matrix4::identity(), Matrix4::identity(), - true)); + true, + false)); if opts::get().dump_display_list { println!("#### start printing display list."); diff --git a/components/msg/compositor_msg.rs b/components/msg/compositor_msg.rs index a3e42af041d..59a3ed3f3df 100644 --- a/components/msg/compositor_msg.rs +++ b/components/msg/compositor_msg.rs @@ -88,6 +88,8 @@ pub struct LayerProperties { pub perspective: Matrix4, /// Whether this layer establishes a new 3d rendering context. 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