aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMartin Robinson <mrobinson@igalia.com>2016-10-31 20:24:27 +0100
committerMartin Robinson <mrobinson@igalia.com>2016-11-05 18:36:45 +0100
commitef82d772c19f797228d7ed2a9d8804f17588117c (patch)
tree920e575a8b2f638f05655171861f0730c8f385fe
parentf7875dad1a43792ff3869f292990d03d30ebd9eb (diff)
downloadservo-ef82d772c19f797228d7ed2a9d8804f17588117c.tar.gz
servo-ef82d772c19f797228d7ed2a9d8804f17588117c.zip
Don't promote all scrollable regions to stacking contexts
Instead annotate all flows with their owning ScrollRoots. When processing the display list items into a flattened display list, we add PushScrollRoot and PopScrollRoot to signal when scrolling regions start and end. It is possible for content from different scrolling regions to intersect and when they do, the stack of scrolling regions is duplicated. When these duplicated scrolling regions stacks reach WebRender, it will scroll them in tandem. The PushScrollRoot and PopScrollRoot items are currently represented as StackingContexts in WebRender, but eventually these will be replaced with special WebRender display items. Fixes #13529. Fixed #13298.
-rw-r--r--components/gfx/display_list/mod.rs137
-rw-r--r--components/gfx_traits/lib.rs5
-rw-r--r--components/layout/block.rs11
-rw-r--r--components/layout/display_list_builder.rs190
-rw-r--r--components/layout/fragment.rs8
-rw-r--r--components/layout/traversal.rs38
-rw-r--r--components/layout/webrender_helpers.rs61
-rw-r--r--components/layout_thread/lib.rs7
-rw-r--r--tests/wpt/metadata-css/css21_dev/html4/abspos-overflow-010.htm.ini4
-rw-r--r--tests/wpt/metadata-css/css21_dev/html4/abspos-overflow-011.htm.ini3
-rw-r--r--tests/wpt/metadata-css/css21_dev/html4/max-width-106.htm.ini3
-rw-r--r--tests/wpt/mozilla/meta/MANIFEST.json48
-rw-r--r--tests/wpt/mozilla/tests/css/stacking_order_overflow_auto.html31
-rw-r--r--tests/wpt/mozilla/tests/css/stacking_order_overflow_scroll.html31
-rw-r--r--tests/wpt/mozilla/tests/css/stacking_order_ref.html20
15 files changed, 435 insertions, 162 deletions
diff --git a/components/gfx/display_list/mod.rs b/components/gfx/display_list/mod.rs
index 99b1fc0b21c..ab8f1ba7a8f 100644
--- a/components/gfx/display_list/mod.rs
+++ b/components/gfx/display_list/mod.rs
@@ -81,7 +81,15 @@ impl DisplayList {
scroll_offsets,
result);
}
- &DisplayItem::PopStackingContext(_) => return,
+ &DisplayItem::PushScrollRoot(ref item) => {
+ self.hit_test_scroll_root(traversal,
+ &item.scroll_root,
+ *translated_point,
+ client_point,
+ scroll_offsets,
+ result);
+ }
+ &DisplayItem::PopStackingContext(_) | &DisplayItem::PopScrollRoot(_) => return,
_ => {
if let Some(meta) = item.hit_test(*translated_point) {
result.push(meta);
@@ -91,6 +99,26 @@ impl DisplayList {
}
}
+ fn hit_test_scroll_root<'a>(&self,
+ traversal: &mut DisplayListTraversal<'a>,
+ scroll_root: &ScrollRoot,
+ mut translated_point: Point2D<Au>,
+ client_point: &Point2D<Au>,
+ scroll_offsets: &ScrollOffsetMap,
+ result: &mut Vec<DisplayItemMetadata>) {
+ // Adjust the translated point to account for the scroll offset if
+ // necessary. This can only happen when WebRender is in use.
+ //
+ // We don't perform this adjustment on the root stacking context because
+ // the DOM-side code has already translated the point for us (e.g. in
+ // `Window::hit_test_query()`) by now.
+ if let Some(scroll_offset) = scroll_offsets.get(&scroll_root.id) {
+ translated_point.x -= Au::from_f32_px(scroll_offset.x);
+ translated_point.y -= Au::from_f32_px(scroll_offset.y);
+ }
+ self.hit_test_contents(traversal, &translated_point, client_point, scroll_offsets, result);
+ }
+
fn hit_test_stacking_context<'a>(&self,
traversal: &mut DisplayListTraversal<'a>,
stacking_context: &StackingContext,
@@ -102,7 +130,7 @@ impl DisplayList {
// stacking context isn't fixed. If it's fixed, we need to use the client point anyway.
debug_assert!(stacking_context.context_type == StackingContextType::Real);
let is_fixed = stacking_context.scroll_policy == ScrollPolicy::FixedPosition;
- let mut translated_point = if is_fixed {
+ let translated_point = if is_fixed {
*client_point
} else {
let point = *translated_point - stacking_context.bounds.origin;
@@ -112,21 +140,6 @@ impl DisplayList {
Point2D::new(Au::from_f32_px(frac_point.x), Au::from_f32_px(frac_point.y))
};
- // Adjust the translated point to account for the scroll offset if
- // necessary. This can only happen when WebRender is in use.
- //
- // We don't perform this adjustment on the root stacking context because
- // the DOM-side code has already translated the point for us (e.g. in
- // `Window::hit_test_query()`) by now.
- if !is_fixed && stacking_context.id != StackingContextId::root() {
- if let Some(scroll_root_id) = stacking_context.overflow_scroll_id {
- if let Some(scroll_offset) = scroll_offsets.get(&scroll_root_id) {
- translated_point.x -= Au::from_f32_px(scroll_offset.x);
- translated_point.y -= Au::from_f32_px(scroll_offset.y);
- }
- }
- }
-
self.hit_test_contents(traversal, &translated_point, client_point, scroll_offsets, result);
}
@@ -138,9 +151,10 @@ impl DisplayList {
pub fn print_with_tree(&self, print_tree: &mut PrintTree) {
print_tree.new_level("Items".to_owned());
for item in &self.list {
- print_tree.add_item(format!("{:?} StackingContext: {:?}",
+ print_tree.add_item(format!("{:?} StackingContext: {:?} ScrollRoot: {:?}",
item,
- item.base().stacking_context_id));
+ item.base().stacking_context_id,
+ item.scroll_root_id()));
}
print_tree.end_level();
}
@@ -250,6 +264,7 @@ pub enum StackingContextType {
Real,
PseudoPositioned,
PseudoFloat,
+ PseudoScrollingArea,
}
#[derive(Clone, HeapSizeOf, Deserialize, Serialize)]
@@ -291,8 +306,8 @@ pub struct StackingContext {
/// Children of this StackingContext.
pub children: Vec<StackingContext>,
- /// If this StackingContext scrolls its overflow area, this will contain the id.
- pub overflow_scroll_id: Option<ScrollRootId>,
+ /// The id of the parent scrolling area that contains this StackingContext.
+ pub parent_scroll_id: ScrollRootId,
}
impl StackingContext {
@@ -309,7 +324,7 @@ impl StackingContext {
perspective: Matrix4D<f32>,
establishes_3d_context: bool,
scroll_policy: ScrollPolicy,
- scroll_root_id: Option<ScrollRootId>)
+ parent_scroll_id: ScrollRootId)
-> StackingContext {
StackingContext {
id: id,
@@ -324,7 +339,7 @@ impl StackingContext {
establishes_3d_context: establishes_3d_context,
scroll_policy: scroll_policy,
children: Vec::new(),
- overflow_scroll_id: scroll_root_id,
+ parent_scroll_id: parent_scroll_id,
}
}
@@ -341,7 +356,7 @@ impl StackingContext {
Matrix4D::identity(),
true,
ScrollPolicy::Scrollable,
- None)
+ ScrollRootId::root())
}
pub fn add_child(&mut self, mut child: StackingContext) {
@@ -453,21 +468,40 @@ impl fmt::Debug for StackingContext {
"Pseudo-StackingContext"
};
- let scrollable_string = if self.overflow_scroll_id.is_some() {
- " (scrolls overflow area)"
- } else {
- ""
- };
-
- write!(f, "{}{} at {:?} with overflow {:?}: {:?}",
+ write!(f, "{} at {:?} with overflow {:?}: {:?}",
type_string,
- scrollable_string,
self.bounds,
self.overflow,
self.id)
}
}
+/// Defines a stacking context.
+#[derive(Clone, Debug, HeapSizeOf, Deserialize, Serialize)]
+pub struct ScrollRoot {
+ /// The unique ID of this ScrollRoot.
+ pub id: ScrollRootId,
+
+ /// The unique ID of the parent of this ScrollRoot.
+ pub parent_id: ScrollRootId,
+
+ /// The position of this scroll root's frame in the parent stacking context.
+ pub clip: Rect<Au>,
+
+ /// The size of the contents that can be scrolled inside of the scroll root.
+ pub size: Size2D<Au>,
+}
+
+impl ScrollRoot {
+ pub fn to_push(&self) -> DisplayItem {
+ DisplayItem::PushScrollRoot(box PushScrollRootItem {
+ base: BaseDisplayItem::empty(),
+ scroll_root: self.clone(),
+ })
+ }
+}
+
+
/// One drawing command in the list.
#[derive(Clone, Deserialize, HeapSizeOf, Serialize)]
pub enum DisplayItem {
@@ -482,6 +516,8 @@ pub enum DisplayItem {
Iframe(Box<IframeDisplayItem>),
PushStackingContext(Box<PushStackingContextItem>),
PopStackingContext(Box<PopStackingContextItem>),
+ PushScrollRoot(Box<PushScrollRootItem>),
+ PopScrollRoot(Box<BaseDisplayItem>),
}
/// Information common to all display items.
@@ -501,6 +537,9 @@ pub struct BaseDisplayItem {
/// The id of the stacking context this item belongs to.
pub stacking_context_id: StackingContextId,
+
+ /// The id of the scroll root this item belongs to.
+ pub scroll_root_id: ScrollRootId,
}
impl BaseDisplayItem {
@@ -509,7 +548,8 @@ impl BaseDisplayItem {
metadata: DisplayItemMetadata,
clip: &ClippingRegion,
section: DisplayListSection,
- stacking_context_id: StackingContextId)
+ stacking_context_id: StackingContextId,
+ scroll_root_id: ScrollRootId)
-> BaseDisplayItem {
// Detect useless clipping regions here and optimize them to `ClippingRegion::max()`.
// The painting backend may want to optimize out clipping regions and this makes it easier
@@ -524,6 +564,7 @@ impl BaseDisplayItem {
},
section: section,
stacking_context_id: stacking_context_id,
+ scroll_root_id: scroll_root_id,
}
}
@@ -538,6 +579,7 @@ impl BaseDisplayItem {
clip: ClippingRegion::max(),
section: DisplayListSection::Content,
stacking_context_id: StackingContextId::root(),
+ scroll_root_id: ScrollRootId::root(),
}
}
}
@@ -981,6 +1023,15 @@ pub struct PopStackingContextItem {
pub stacking_context_id: StackingContextId,
}
+/// Starts a group of items inside a particular scroll root.
+#[derive(Clone, HeapSizeOf, Deserialize, Serialize)]
+pub struct PushScrollRootItem {
+ /// Fields common to all display items.
+ pub base: BaseDisplayItem,
+
+ /// The scroll root that this item starts.
+ pub scroll_root: ScrollRoot,
+}
/// How a box shadow should be clipped.
#[derive(Clone, Copy, Debug, PartialEq, HeapSizeOf, Deserialize, Serialize)]
@@ -1009,9 +1060,15 @@ impl DisplayItem {
DisplayItem::Iframe(ref iframe) => &iframe.base,
DisplayItem::PushStackingContext(ref stacking_context) => &stacking_context.base,
DisplayItem::PopStackingContext(ref item) => &item.base,
+ DisplayItem::PushScrollRoot(ref item) => &item.base,
+ DisplayItem::PopScrollRoot(ref base) => &base,
}
}
+ pub fn scroll_root_id(&self) -> ScrollRootId {
+ self.base().scroll_root_id
+ }
+
pub fn stacking_context_id(&self) -> StackingContextId {
self.base().stacking_context_id
}
@@ -1090,6 +1147,14 @@ impl fmt::Debug for DisplayItem {
return write!(f, "PopStackingContext({:?}", item.stacking_context_id);
}
+ if let DisplayItem::PushScrollRoot(ref item) = *self {
+ return write!(f, "PushScrollRoot({:?}", item.scroll_root);
+ }
+
+ if let DisplayItem::PopScrollRoot(_) = *self {
+ return write!(f, "PopScrollRoot");
+ }
+
write!(f, "{} @ {:?} {:?}",
match *self {
DisplayItem::SolidColor(ref solid_color) =>
@@ -1106,8 +1171,10 @@ impl fmt::Debug for DisplayItem {
DisplayItem::Line(_) => "Line".to_owned(),
DisplayItem::BoxShadow(_) => "BoxShadow".to_owned(),
DisplayItem::Iframe(_) => "Iframe".to_owned(),
- DisplayItem::PushStackingContext(_) => "".to_owned(),
- DisplayItem::PopStackingContext(_) => "".to_owned(),
+ DisplayItem::PushStackingContext(_) |
+ DisplayItem::PopStackingContext(_) |
+ DisplayItem::PushScrollRoot(_) |
+ DisplayItem::PopScrollRoot(_) => "".to_owned(),
},
self.bounds(),
self.base().clip
diff --git a/components/gfx_traits/lib.rs b/components/gfx_traits/lib.rs
index 09593189512..9925326da7e 100644
--- a/components/gfx_traits/lib.rs
+++ b/components/gfx_traits/lib.rs
@@ -211,6 +211,11 @@ impl ScrollRootId {
pub fn fragment_type(&self) -> FragmentType {
FragmentType::from_usize(self.0 & 3)
}
+
+ #[inline]
+ pub fn to_stacking_context_id(&self) -> StackingContextId {
+ StackingContextId(self.0)
+ }
}
/// The type of fragment that a stacking context represents.
diff --git a/components/layout/block.rs b/components/layout/block.rs
index c60b103e608..992be0a55a2 100644
--- a/components/layout/block.rs
+++ b/components/layout/block.rs
@@ -1942,17 +1942,6 @@ 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.clip = ClippingRegion::max();
- }
- _ => {}
- }
-
let position_start = self.base.position.start.to_physical(self.base.writing_mode,
container_size);
diff --git a/components/layout/display_list_builder.rs b/components/layout/display_list_builder.rs
index 17b3e07f716..b22a1bda2e1 100644
--- a/components/layout/display_list_builder.rs
+++ b/components/layout/display_list_builder.rs
@@ -15,6 +15,7 @@ use block::{BlockFlow, BlockStackingContextType};
use canvas_traits::{CanvasData, CanvasMsg, FromLayoutMsg};
use context::SharedLayoutContext;
use euclid::{Matrix4D, Point2D, Radians, Rect, SideOffsets2D, Size2D};
+use euclid::point::TypedPoint2D;
use flex::FlexFlow;
use flow::{BaseFlow, Flow, IS_ABSOLUTELY_POSITIONED};
use flow_ref::FlowRef;
@@ -25,7 +26,7 @@ use gfx::display_list::{BorderRadii, BoxShadowClipMode, BoxShadowDisplayItem, Cl
use gfx::display_list::{DisplayItem, DisplayItemMetadata, DisplayList, DisplayListSection};
use gfx::display_list::{GradientDisplayItem, IframeDisplayItem, ImageDisplayItem};
use gfx::display_list::{LineDisplayItem, OpaqueNode};
-use gfx::display_list::{SolidColorDisplayItem, StackingContext, StackingContextType};
+use gfx::display_list::{SolidColorDisplayItem, ScrollRoot, StackingContext, StackingContextType};
use gfx::display_list::{TextDisplayItem, TextOrientation, WebGLDisplayItem, WebRenderImageInfo};
use gfx_traits::{ScrollPolicy, ScrollRootId, StackingContextId};
use inline::{FIRST_FRAGMENT_OF_ELEMENT, InlineFlow, LAST_FRAGMENT_OF_ELEMENT};
@@ -43,8 +44,8 @@ use std::mem;
use std::sync::Arc;
use style::computed_values::{background_attachment, background_clip, background_origin};
use style::computed_values::{background_repeat, background_size, border_style};
-use style::computed_values::{cursor, image_rendering, overflow_x, pointer_events, position};
-use style::computed_values::{transform, transform_style, visibility};
+use style::computed_values::{cursor, image_rendering, overflow_x};
+use style::computed_values::{pointer_events, position, transform, transform_style, visibility};
use style::computed_values::_servo_overflow_clip_box as overflow_clip_box;
use style::computed_values::filter::Filter;
use style::computed_values::text_shadow::TextShadow;
@@ -95,6 +96,7 @@ pub struct DisplayListBuildState<'a> {
pub shared_layout_context: &'a SharedLayoutContext,
pub root_stacking_context: StackingContext,
pub items: HashMap<StackingContextId, Vec<DisplayItem>>,
+ pub scroll_roots: HashMap<ScrollRootId, ScrollRoot>,
pub stacking_context_id_stack: Vec<StackingContextId>,
pub scroll_root_id_stack: Vec<ScrollRootId>,
}
@@ -107,6 +109,7 @@ impl<'a> DisplayListBuildState<'a> {
shared_layout_context: shared_layout_context,
root_stacking_context: StackingContext::root(),
items: HashMap::new(),
+ scroll_roots: HashMap::new(),
stacking_context_id_stack: vec!(stacking_context_id),
scroll_root_id_stack: vec!(ScrollRootId::root()),
}
@@ -117,6 +120,11 @@ impl<'a> DisplayListBuildState<'a> {
items.push(display_item);
}
+ fn add_scroll_root(&mut self, scroll_root: ScrollRoot) {
+ debug_assert!(!self.scroll_roots.contains_key(&scroll_root.id));
+ self.scroll_roots.insert(scroll_root.id, scroll_root);
+ }
+
pub fn stacking_context_id(&self) -> StackingContextId {
self.stacking_context_id_stack.last().unwrap().clone()
}
@@ -126,14 +134,19 @@ impl<'a> DisplayListBuildState<'a> {
}
pub fn pop_stacking_context_id(&mut self) {
- self.stacking_context_id_stack.pop();
assert!(!self.stacking_context_id_stack.is_empty());
+ self.stacking_context_id_stack.pop();
}
- pub fn scroll_root_id(&mut self) -> ScrollRootId {
+ pub fn scroll_root_id(&self) -> ScrollRootId {
self.scroll_root_id_stack.last().unwrap().clone()
}
+ pub fn parent_scroll_root_id(&self) -> ScrollRootId {
+ debug_assert!(self.scroll_root_id_stack.len() > 1);
+ self.scroll_root_id_stack[self.scroll_root_id_stack.len() - 2]
+ }
+
pub fn push_scroll_root_id(&mut self, id: ScrollRootId) {
self.scroll_root_id_stack.push(id);
}
@@ -157,14 +170,18 @@ impl<'a> DisplayListBuildState<'a> {
},
&clip,
section,
- self.stacking_context_id())
+ self.stacking_context_id(),
+ self.scroll_root_id())
}
pub fn to_display_list(mut self) -> DisplayList {
+ let mut scroll_root_stack = Vec::new();
+ scroll_root_stack.push(ScrollRootId::root());
+
let mut list = Vec::new();
let root_context = mem::replace(&mut self.root_stacking_context, StackingContext::root());
- self.to_display_list_for_stacking_context(&mut list, root_context);
+ self.to_display_list_for_stacking_context(&mut list, root_context, &mut scroll_root_stack);
DisplayList {
list: list,
@@ -173,7 +190,8 @@ impl<'a> DisplayListBuildState<'a> {
fn to_display_list_for_stacking_context(&mut self,
list: &mut Vec<DisplayItem>,
- mut stacking_context: StackingContext) {
+ mut stacking_context: StackingContext,
+ scroll_root_stack: &mut Vec<ScrollRootId>) {
let mut child_items = self.items.remove(&stacking_context.id).unwrap_or(Vec::new());
child_items.sort_by(|a, b| a.base().section.cmp(&b.base().section));
child_items.reverse();
@@ -186,63 +204,114 @@ impl<'a> DisplayListBuildState<'a> {
if !real_stacking_context {
self.to_display_list_for_items(list,
child_items,
- child_stacking_contexts);
+ child_stacking_contexts,
+ scroll_root_stack);
return;
}
+ let mut scroll_root_stack = Vec::new();
+ scroll_root_stack.push(stacking_context.parent_scroll_id);
+
let (push_item, pop_item) = stacking_context.to_display_list_items();
list.push(push_item);
self.to_display_list_for_items(list,
child_items,
- child_stacking_contexts);
+ child_stacking_contexts,
+ &mut scroll_root_stack);
list.push(pop_item);
}
fn to_display_list_for_items(&mut self,
list: &mut Vec<DisplayItem>,
mut child_items: Vec<DisplayItem>,
- child_stacking_contexts: Vec<StackingContext>) {
+ child_stacking_contexts: Vec<StackingContext>,
+ scroll_root_stack: &mut Vec<ScrollRootId>) {
// Properly order display items that make up a stacking context. "Steps" here
// refer to the steps in CSS 2.1 Appendix E.
// Steps 1 and 2: Borders and background for the root.
while child_items.last().map_or(false,
|child| child.section() == DisplayListSection::BackgroundAndBorders) {
- list.push(child_items.pop().unwrap());
+ let item = child_items.pop().unwrap();
+ self.switch_scroll_roots(list, item.scroll_root_id(), scroll_root_stack);
+ list.push(item);
}
// Step 3: Positioned descendants with negative z-indices.
let mut child_stacking_contexts = child_stacking_contexts.into_iter().peekable();
while child_stacking_contexts.peek().map_or(false, |child| child.z_index < 0) {
let context = child_stacking_contexts.next().unwrap();
- self.to_display_list_for_stacking_context(list, context);
+ self.switch_scroll_roots(list, context.parent_scroll_id, scroll_root_stack);
+ self.to_display_list_for_stacking_context(list, context, scroll_root_stack);
}
// Step 4: Block backgrounds and borders.
while child_items.last().map_or(false,
|child| child.section() == DisplayListSection::BlockBackgroundsAndBorders) {
- list.push(child_items.pop().unwrap());
+ let item = child_items.pop().unwrap();
+ self.switch_scroll_roots(list, item.scroll_root_id(), scroll_root_stack);
+ list.push(item);
}
// Step 5: Floats.
while child_stacking_contexts.peek().map_or(false,
|child| child.context_type == StackingContextType::PseudoFloat) {
let context = child_stacking_contexts.next().unwrap();
- self.to_display_list_for_stacking_context(list, context);
+ self.switch_scroll_roots(list, context.parent_scroll_id, scroll_root_stack);
+ self.to_display_list_for_stacking_context(list, context, scroll_root_stack);
}
// Step 6 & 7: Content and inlines that generate stacking contexts.
while child_items.last().map_or(false,
|child| child.section() == DisplayListSection::Content) {
- list.push(child_items.pop().unwrap());
+ let item = child_items.pop().unwrap();
+ self.switch_scroll_roots(list, item.scroll_root_id(), scroll_root_stack);
+ list.push(item);
}
// Step 8 & 9: Positioned descendants with nonnegative, numeric z-indices.
for child in child_stacking_contexts {
- self.to_display_list_for_stacking_context(list, child);
+ self.switch_scroll_roots(list, child.parent_scroll_id, scroll_root_stack);
+ self.to_display_list_for_stacking_context(list, child, scroll_root_stack);
}
// Step 10: Outlines.
- list.extend(child_items);
+ for item in child_items.drain(..) {
+ self.switch_scroll_roots(list, item.scroll_root_id(), scroll_root_stack);
+ list.push(item);
+ }
+
+ for _ in scroll_root_stack.drain(1..) {
+ list.push(DisplayItem::PopScrollRoot(Box::new(BaseDisplayItem::empty())));
+ }
+ }
+
+ fn switch_scroll_roots(&self,
+ list: &mut Vec<DisplayItem>,
+ new_id: ScrollRootId,
+ scroll_root_stack: &mut Vec<ScrollRootId>) {
+ if new_id == *scroll_root_stack.last().unwrap() {
+ return;
+ }
+
+ if new_id == *scroll_root_stack.first().unwrap() {
+ for _ in scroll_root_stack.drain(1..) {
+ list.push(DisplayItem::PopScrollRoot(Box::new(BaseDisplayItem::empty())));
+ }
+ return;
+ }
+
+ // We never want to reach the root of the ScrollRoot tree without encountering the
+ // containing scroll root of this StackingContext. If this does happen we've tried to
+ // switch to a ScrollRoot that does not contain our current stacking context or isn't
+ // itself a direct child of our current stacking context. This should never happen
+ // due to CSS stacking rules.
+ debug_assert!(new_id != ScrollRootId::root());
+
+ let scroll_root = self.scroll_roots.get(&new_id).unwrap();
+ self.switch_scroll_roots(list, scroll_root.parent_id, scroll_root_stack);
+
+ scroll_root_stack.push(new_id);
+ list.push(scroll_root.to_push());
}
}
@@ -419,7 +488,7 @@ pub trait FragmentDisplayListBuilding {
base_flow: &BaseFlow,
scroll_policy: ScrollPolicy,
mode: StackingContextCreationMode,
- scroll_root_id: Option<ScrollRootId>)
+ parent_scroll_id: ScrollRootId)
-> StackingContext;
/// Returns the 4D matrix representing this fragment's transform.
@@ -1476,9 +1545,8 @@ impl FragmentDisplayListBuilding for Fragment {
base_flow: &BaseFlow,
scroll_policy: ScrollPolicy,
mode: StackingContextCreationMode,
- scroll_root_id: Option<ScrollRootId>)
+ parent_scroll_id: ScrollRootId)
-> StackingContext {
- let scrolls_overflow_area = mode == StackingContextCreationMode::ScrollWrapper;
let border_box =
self.stacking_relative_border_box(&base_flow.stacking_relative_position,
&base_flow.early_absolute_position_info
@@ -1486,16 +1554,12 @@ impl FragmentDisplayListBuilding for Fragment {
base_flow.early_absolute_position_info
.relative_containing_block_mode,
CoordinateSystem::Parent);
- let overflow = if scrolls_overflow_area {
- Rect::new(Point2D::zero(), base_flow.overflow.scroll.size)
- } else {
- // First, compute the offset of our border box (including relative positioning)
- // from our flow origin, since that is what `BaseFlow::overflow` is relative to.
- let border_box_offset =
- border_box.translate(&-base_flow.stacking_relative_position).origin;
- // Then, using that, compute our overflow region relative to our border box.
- base_flow.overflow.paint.translate(&-border_box_offset)
- };
+ // First, compute the offset of our border box (including relative positioning)
+ // from our flow origin, since that is what `BaseFlow::overflow` is relative to.
+ let border_box_offset =
+ border_box.translate(&-base_flow.stacking_relative_position).origin;
+ // Then, using that, compute our overflow region relative to our border box.
+ let overflow = base_flow.overflow.paint.translate(&-border_box_offset);
let transform = self.transform_matrix(&border_box);
let perspective = match self.style().get_effects().perspective {
@@ -1531,8 +1595,7 @@ impl FragmentDisplayListBuilding for Fragment {
}
let transform_style = self.style().get_used_transform_style();
- let establishes_3d_context = scrolls_overflow_area ||
- transform_style == transform_style::T::flat;
+ let establishes_3d_context = transform_style == transform_style::T::flat;
let context_type = match mode {
StackingContextCreationMode::PseudoFloat => StackingContextType::PseudoFloat,
@@ -1551,7 +1614,7 @@ impl FragmentDisplayListBuilding for Fragment {
perspective,
establishes_3d_context,
scroll_policy,
- scroll_root_id)
+ parent_scroll_id)
}
fn adjust_clipping_region_for_children(&self,
@@ -1829,18 +1892,7 @@ impl BlockFlowDisplayListBuilding for BlockFlow {
fn collect_stacking_contexts_for_block(&mut self,
parent: &mut StackingContext,
parent_scroll_root_id: ScrollRootId) {
- let block_stacking_context_type = self.block_stacking_context_type();
- if block_stacking_context_type == BlockStackingContextType::NonstackingContext {
- self.base.stacking_context_id = parent.id;
- self.base.collect_stacking_contexts_for_children(parent, parent_scroll_root_id);
- return;
- }
-
- let stacking_context_id = StackingContextId::new_of_type(self.fragment.node.id() as usize,
- self.fragment.fragment_type());
-
- let has_scrolling_overflow = self.has_scrolling_overflow();
- let scroll_root_id = if has_scrolling_overflow {
+ let scroll_root_id = if self.has_scrolling_overflow() {
ScrollRootId::new_of_type(self.fragment.node.id() as usize,
self.fragment.fragment_type())
} else {
@@ -1848,7 +1900,15 @@ impl BlockFlowDisplayListBuilding for BlockFlow {
};
self.base.scroll_root_id = scroll_root_id;
+ let block_stacking_context_type = self.block_stacking_context_type();
+ if block_stacking_context_type == BlockStackingContextType::NonstackingContext {
+ self.base.stacking_context_id = parent.id;
+ self.base.collect_stacking_contexts_for_children(parent, scroll_root_id);
+ return;
+ }
+ let stacking_context_id = StackingContextId::new_of_type(self.fragment.node.id() as usize,
+ self.fragment.fragment_type());
self.base.stacking_context_id = stacking_context_id;
if block_stacking_context_type == BlockStackingContextType::PseudoStackingContext {
@@ -1864,7 +1924,7 @@ impl BlockFlowDisplayListBuilding for BlockFlow {
&self.base,
ScrollPolicy::Scrollable,
creation_mode,
- None);
+ parent_scroll_root_id);
self.base.collect_stacking_contexts_for_children(&mut new_context, scroll_root_id);
let new_children: Vec<StackingContext> = new_context.children.drain(..).collect();
@@ -1888,20 +1948,13 @@ impl BlockFlowDisplayListBuilding for BlockFlow {
ScrollPolicy::Scrollable
};
- let (creation_mode, internal_id) = if has_scrolling_overflow {
- (StackingContextCreationMode::ScrollWrapper, Some(self.base.scroll_root_id))
- } else {
- (StackingContextCreationMode::Normal, None)
- };
-
let mut stacking_context = self.fragment.create_stacking_context(
stacking_context_id,
&self.base,
scroll_policy,
- creation_mode,
- internal_id);
+ StackingContextCreationMode::Normal,
+ parent_scroll_root_id);
self.base.collect_stacking_contexts_for_children(&mut stacking_context, scroll_root_id);
-
parent.add_child(stacking_context);
}
@@ -1921,6 +1974,28 @@ impl BlockFlowDisplayListBuilding for BlockFlow {
DisplayListSection::BlockBackgroundsAndBorders
};
+ if self.has_scrolling_overflow() {
+ let size = self.base.overflow.scroll.size;
+ let mut clip = self.fragment.stacking_relative_border_box(
+ &self.base.stacking_relative_position,
+ &self.base.early_absolute_position_info.relative_containing_block_size,
+ self.base.early_absolute_position_info.relative_containing_block_mode,
+ CoordinateSystem::Parent);
+ if establishes_stacking_context {
+ clip = Rect::new(TypedPoint2D::zero(), clip.size);
+ }
+
+ let parent_id = state.parent_scroll_root_id();
+ state.add_scroll_root(
+ ScrollRoot {
+ id: self.base.scroll_root_id,
+ parent_id: parent_id,
+ clip: clip,
+ size: size,
+ }
+ );
+ }
+
// Add the box that starts the block context.
self.fragment
.build_display_list(state,
@@ -2025,7 +2100,7 @@ impl InlineFlowDisplayListBuilding for InlineFlow {
&self.base,
ScrollPolicy::Scrollable,
StackingContextCreationMode::Normal,
- None));
+ parent_scroll_root_id));
}
_ => fragment.stacking_context_id = parent.id,
}
@@ -2229,7 +2304,6 @@ pub enum BorderPaintingMode<'a> {
#[derive(Copy, Clone, PartialEq)]
pub enum StackingContextCreationMode {
Normal,
- ScrollWrapper,
PseudoPositioned,
PseudoFloat,
}
diff --git a/components/layout/fragment.rs b/components/layout/fragment.rs
index e4eb0ea81f4..839e1438eb3 100644
--- a/components/layout/fragment.rs
+++ b/components/layout/fragment.rs
@@ -2594,8 +2594,6 @@ impl Fragment {
transform_style::T::auto => {}
}
- // FIXME(pcwalton): Don't unconditionally form stacking contexts for `overflow_x: scroll`
- // and `overflow_y: scroll`. This needs multiple layers per stacking context.
match (self.style().get_box().position,
self.style().get_position().z_index,
self.style().get_box().overflow_x,
@@ -2614,11 +2612,7 @@ impl Fragment {
overflow_x::T::visible) => false,
(position::T::absolute, _, _, _) |
(position::T::fixed, _, _, _) |
- (position::T::relative, _, _, _) |
- (_, _, overflow_x::T::auto, _) |
- (_, _, overflow_x::T::scroll, _) |
- (_, _, _, overflow_x::T::auto) |
- (_, _, _, overflow_x::T::scroll) => true,
+ (position::T::relative, _, _, _) => true,
(position::T::static_, _, _, _) => false
}
}
diff --git a/components/layout/traversal.rs b/components/layout/traversal.rs
index c897838875a..1792f1134ff 100644
--- a/components/layout/traversal.rs
+++ b/components/layout/traversal.rs
@@ -270,34 +270,34 @@ pub struct BuildDisplayList<'a> {
impl<'a> BuildDisplayList<'a> {
#[inline]
pub fn traverse(&mut self, flow: &mut Flow) {
- if self.should_process() {
- let new_stacking_context =
- flow::base(flow).stacking_context_id != self.state.stacking_context_id();
- if new_stacking_context {
- self.state.push_stacking_context_id(flow::base(flow).stacking_context_id);
- }
+ let new_stacking_context =
+ flow::base(flow).stacking_context_id != self.state.stacking_context_id();
+ if new_stacking_context {
+ self.state.push_stacking_context_id(flow::base(flow).stacking_context_id);
+ }
- let new_scroll_root =
- flow::base(flow).scroll_root_id != self.state.scroll_root_id();
- if new_scroll_root {
- self.state.push_scroll_root_id(flow::base(flow).scroll_root_id);
- }
+ let new_scroll_root =
+ flow::base(flow).scroll_root_id != self.state.scroll_root_id();
+ if new_scroll_root {
+ self.state.push_scroll_root_id(flow::base(flow).scroll_root_id);
+ }
+ if self.should_process() {
flow.build_display_list(&mut self.state);
flow::mut_base(flow).restyle_damage.remove(REPAINT);
-
- if new_stacking_context {
- self.state.pop_stacking_context_id();
- }
-
- if new_scroll_root {
- self.state.pop_scroll_root_id();
- }
}
for kid in flow::child_iter_mut(flow) {
self.traverse(kid);
}
+
+ if new_stacking_context {
+ self.state.pop_stacking_context_id();
+ }
+
+ if new_scroll_root {
+ self.state.pop_scroll_root_id();
+ }
}
#[inline]
diff --git a/components/layout/webrender_helpers.rs b/components/layout/webrender_helpers.rs
index cf239f79258..07979940314 100644
--- a/components/layout/webrender_helpers.rs
+++ b/components/layout/webrender_helpers.rs
@@ -8,7 +8,7 @@
// completely converting layout to directly generate WebRender display lists, for example.
use app_units::Au;
-use euclid::{Point2D, Rect, Size2D};
+use euclid::{Matrix4D, Point2D, Rect, Size2D, TypedRect};
use gfx::display_list::{BorderRadii, BoxShadowClipMode, ClippingRegion};
use gfx::display_list::{DisplayItem, DisplayList, DisplayListTraversal};
use gfx::display_list::{StackingContext, StackingContextType};
@@ -24,7 +24,8 @@ trait WebRenderStackingContextConverter {
api: &mut webrender_traits::RenderApi,
pipeline_id: webrender_traits::PipelineId,
epoch: webrender_traits::Epoch,
- frame_builder: &mut WebRenderFrameBuilder)
+ frame_builder: &mut WebRenderFrameBuilder,
+ scroll_layer_id: Option<webrender_traits::ScrollLayerId>)
-> webrender_traits::StackingContextId;
fn convert_children_to_webrender<'a>(&self,
@@ -235,17 +236,39 @@ impl WebRenderStackingContextConverter for StackingContext {
&DisplayItem::PushStackingContext(ref stacking_context_item) => {
let stacking_context = &stacking_context_item.stacking_context;
debug_assert!(stacking_context.context_type == StackingContextType::Real);
-
- let stacking_context_id =
+ builder.push_stacking_context(
stacking_context.convert_to_webrender(traversal,
api,
pipeline_id,
epoch,
- frame_builder);
- builder.push_stacking_context(stacking_context_id);
-
+ frame_builder,
+ None));
}
- &DisplayItem::PopStackingContext(_) => return,
+ &DisplayItem::PushScrollRoot(ref item) => {
+ let stacking_context = StackingContext::new(
+ StackingContextId::new(0),
+ StackingContextType::Real,
+ &item.scroll_root.clip,
+ &TypedRect::new(Point2D::zero(), item.scroll_root.size),
+ 0,
+ filter::T::new(Vec::new()),
+ mix_blend_mode::T::normal,
+ Matrix4D::identity(),
+ Matrix4D::identity(),
+ true,
+ ScrollPolicy::Scrollable,
+ ScrollRootId::root());
+ let scroll_layer_id =
+ Some(frame_builder.next_scroll_layer_id(item.scroll_root.id));
+ builder.push_stacking_context(
+ stacking_context.convert_to_webrender(traversal,
+ api,
+ pipeline_id,
+ epoch,
+ frame_builder,
+ scroll_layer_id));
+ }
+ &DisplayItem::PopStackingContext(_) | &DisplayItem::PopScrollRoot(_) => return,
_ => item.convert_to_webrender(builder, frame_builder),
}
}
@@ -256,21 +279,14 @@ impl WebRenderStackingContextConverter for StackingContext {
api: &mut webrender_traits::RenderApi,
pipeline_id: webrender_traits::PipelineId,
epoch: webrender_traits::Epoch,
- frame_builder: &mut WebRenderFrameBuilder)
+ frame_builder: &mut WebRenderFrameBuilder,
+ scroll_layer_id: Option<webrender_traits::ScrollLayerId>)
-> webrender_traits::StackingContextId {
let webrender_scroll_policy = match self.scroll_policy {
ScrollPolicy::Scrollable => webrender_traits::ScrollPolicy::Scrollable,
ScrollPolicy::FixedPosition => webrender_traits::ScrollPolicy::Fixed,
};
- let scroll_layer_id = if let Some(scroll_root_id) = self.overflow_scroll_id {
- Some(frame_builder.next_scroll_layer_id(scroll_root_id))
- } else if self.id == StackingContextId::root() {
- Some(frame_builder.next_scroll_layer_id(ScrollRootId::root()))
- } else {
- None
- };
-
let mut sc =
webrender_traits::StackingContext::new(scroll_layer_id,
webrender_scroll_policy,
@@ -310,11 +326,14 @@ impl WebRenderDisplayListConverter for DisplayList {
match item {
Some(&DisplayItem::PushStackingContext(ref stacking_context_item)) => {
let stacking_context = &stacking_context_item.stacking_context;
+ let scroll_layer_id =
+ Some(frame_builder.next_scroll_layer_id(ScrollRootId::root()));
stacking_context.convert_to_webrender(&mut traversal,
api,
pipeline_id,
epoch,
- frame_builder)
+ frame_builder,
+ scroll_layer_id)
}
_ => unreachable!("DisplayList did not start with StackingContext."),
@@ -453,7 +472,11 @@ impl WebRenderDisplayItemConverter for DisplayItem {
item.base.clip.to_clip_region(frame_builder),
pipeline_id);
}
- DisplayItem::PushStackingContext(_) | DisplayItem::PopStackingContext(_) => {}
+ DisplayItem::PushStackingContext(_) |
+ DisplayItem::PopStackingContext(_) |
+ DisplayItem::PushScrollRoot(_) |
+ DisplayItem::PopScrollRoot(_) =>
+ unreachable!("Tried to convert a scroll root or stacking context structure item."),
}
}
}
diff --git a/components/layout_thread/lib.rs b/components/layout_thread/lib.rs
index 537d5935596..c09c1412c2b 100644
--- a/components/layout_thread/lib.rs
+++ b/components/layout_thread/lib.rs
@@ -50,7 +50,8 @@ use euclid::rect::Rect;
use euclid::scale_factor::ScaleFactor;
use euclid::size::Size2D;
use fnv::FnvHasher;
-use gfx::display_list::{ClippingRegion, OpaqueNode, WebRenderImageInfo};
+use gfx::display_list::{ClippingRegion, OpaqueNode};
+use gfx::display_list::WebRenderImageInfo;
use gfx::font;
use gfx::font_cache_thread::FontCacheThread;
use gfx::font_context;
@@ -906,8 +907,8 @@ impl LayoutThread {
match (data.goal, display_list_needed) {
(ReflowGoal::ForDisplay, _) | (ReflowGoal::ForScriptQuery, true) => {
let mut build_state =
- sequential::build_display_list_for_subtree(layout_root,
- shared_layout_context);
+ sequential::build_display_list_for_subtree(layout_root,
+ shared_layout_context);
debug!("Done building display list.");
diff --git a/tests/wpt/metadata-css/css21_dev/html4/abspos-overflow-010.htm.ini b/tests/wpt/metadata-css/css21_dev/html4/abspos-overflow-010.htm.ini
deleted file mode 100644
index 5504b7061bf..00000000000
--- a/tests/wpt/metadata-css/css21_dev/html4/abspos-overflow-010.htm.ini
+++ /dev/null
@@ -1,4 +0,0 @@
-[abspos-overflow-010.htm]
- type: reftest
- expected:
- if os == "linux": FAIL
diff --git a/tests/wpt/metadata-css/css21_dev/html4/abspos-overflow-011.htm.ini b/tests/wpt/metadata-css/css21_dev/html4/abspos-overflow-011.htm.ini
deleted file mode 100644
index 6ef62736b3c..00000000000
--- a/tests/wpt/metadata-css/css21_dev/html4/abspos-overflow-011.htm.ini
+++ /dev/null
@@ -1,3 +0,0 @@
-[abspos-overflow-011.htm]
- type: reftest
- expected: FAIL
diff --git a/tests/wpt/metadata-css/css21_dev/html4/max-width-106.htm.ini b/tests/wpt/metadata-css/css21_dev/html4/max-width-106.htm.ini
deleted file mode 100644
index 4aea4cf359c..00000000000
--- a/tests/wpt/metadata-css/css21_dev/html4/max-width-106.htm.ini
+++ /dev/null
@@ -1,3 +0,0 @@
-[max-width-106.htm]
- type: reftest
- expected: FAIL
diff --git a/tests/wpt/mozilla/meta/MANIFEST.json b/tests/wpt/mozilla/meta/MANIFEST.json
index 626f628c173..078eae8297b 100644
--- a/tests/wpt/mozilla/meta/MANIFEST.json
+++ b/tests/wpt/mozilla/meta/MANIFEST.json
@@ -5016,6 +5016,30 @@
"url": "/_mozilla/css/stacking_context_rtl.html"
}
],
+ "css/stacking_order_overflow_auto.html": [
+ {
+ "path": "css/stacking_order_overflow_auto.html",
+ "references": [
+ [
+ "/_mozilla/css/stacking_order_ref.html",
+ "=="
+ ]
+ ],
+ "url": "/_mozilla/css/stacking_order_overflow_auto.html"
+ }
+ ],
+ "css/stacking_order_overflow_scroll.html": [
+ {
+ "path": "css/stacking_order_overflow_scroll.html",
+ "references": [
+ [
+ "/_mozilla/css/stacking_order_ref.html",
+ "=="
+ ]
+ ],
+ "url": "/_mozilla/css/stacking_order_overflow_scroll.html"
+ }
+ ],
"css/style_is_in_doc.html": [
{
"path": "css/style_is_in_doc.html",
@@ -19854,6 +19878,30 @@
"url": "/_mozilla/css/stacking_context_rtl.html"
}
],
+ "css/stacking_order_overflow_auto.html": [
+ {
+ "path": "css/stacking_order_overflow_auto.html",
+ "references": [
+ [
+ "/_mozilla/css/stacking_order_ref.html",
+ "=="
+ ]
+ ],
+ "url": "/_mozilla/css/stacking_order_overflow_auto.html"
+ }
+ ],
+ "css/stacking_order_overflow_scroll.html": [
+ {
+ "path": "css/stacking_order_overflow_scroll.html",
+ "references": [
+ [
+ "/_mozilla/css/stacking_order_ref.html",
+ "=="
+ ]
+ ],
+ "url": "/_mozilla/css/stacking_order_overflow_scroll.html"
+ }
+ ],
"css/style_is_in_doc.html": [
{
"path": "css/style_is_in_doc.html",
diff --git a/tests/wpt/mozilla/tests/css/stacking_order_overflow_auto.html b/tests/wpt/mozilla/tests/css/stacking_order_overflow_auto.html
new file mode 100644
index 00000000000..9da6457ce1f
--- /dev/null
+++ b/tests/wpt/mozilla/tests/css/stacking_order_overflow_auto.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>The position:absolute element should be on top of the overflow:auto element</title>
+ <link rel='match' href='stacking_order_ref.html'>
+ <style>
+ body {
+ margin: 0;
+ }
+
+ .scrollbox {
+ margin: 50px;
+ width: 50px;
+ height: 50px;
+ overflow: auto;
+ background: red;
+ }
+
+ .overlay {
+ position: absolute;
+ left: 50px;
+ top: 50px;
+ width: 50px;
+ height: 50px;
+ background: green;
+ }
+ </style>
+
+<div class="overlay"></div>
+<div class="scrollbox"></div>
diff --git a/tests/wpt/mozilla/tests/css/stacking_order_overflow_scroll.html b/tests/wpt/mozilla/tests/css/stacking_order_overflow_scroll.html
new file mode 100644
index 00000000000..c8e3729dce3
--- /dev/null
+++ b/tests/wpt/mozilla/tests/css/stacking_order_overflow_scroll.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>The position:absolute element should be on top of the overflow:scroll element</title>
+ <link rel='match' href='stacking_order_ref.html'>
+ <style>
+ body {
+ margin: 0;
+ }
+
+ .scrollbox {
+ margin: 50px;
+ width: 50px;
+ height: 50px;
+ overflow: scroll;
+ background: red;
+ }
+
+ .overlay {
+ position: absolute;
+ left: 50px;
+ top: 50px;
+ width: 50px;
+ height: 50px;
+ background: green;
+ }
+ </style>
+
+<div class="overlay"></div>
+<div class="scrollbox"></div>
diff --git a/tests/wpt/mozilla/tests/css/stacking_order_ref.html b/tests/wpt/mozilla/tests/css/stacking_order_ref.html
new file mode 100644
index 00000000000..908f43b1295
--- /dev/null
+++ b/tests/wpt/mozilla/tests/css/stacking_order_ref.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <style>
+ body {
+ margin: 0;
+ }
+
+ .box {
+ position: absolute;
+ left: 50px;
+ top: 50px;
+ width: 50px;
+ height: 50px;
+ background: green;
+ }
+ </style>
+
+<div class="box"></div>