Auto merge of #12843 - emilio:transforms, r=glennw

layout: Make the stacking context take into account the children transform when calculating overflow areas.

<!-- Please describe your changes on the following line: -->

This is a potential fix for #12842. I have done only the math to handle simple transforms because it's three AM, but I'd like @pcwalton to verify my approach, or suggest an alternative.

---
<!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: -->
- [x] `./mach build -d` does not report any errors
- [x] `./mach test-tidy` does not report any errors
- [x] These changes partially fix #12842 (github issue number if applicable).

<!-- Either: -->
- [x] There are tests for these changes.

<!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. -->

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/12843)
<!-- Reviewable:end -->
This commit is contained in:
bors-servo 2016-08-17 00:35:16 -05:00 committed by GitHub
commit 5f169581dc
10 changed files with 253 additions and 32 deletions

View file

@ -360,7 +360,7 @@ impl DisplayList {
return Some(stacking_context);
}
for kid in stacking_context.children.iter() {
for kid in stacking_context.children() {
let result = find_stacking_context_in_stacking_context(kid, stacking_context_id);
if result.is_some() {
return result;
@ -449,8 +449,9 @@ impl DisplayList {
let old_transform = paint_context.draw_target.get_transform();
let pixels_per_px = paint_context.screen_pixels_per_px();
let (transform, subpixel_offset) = match stacking_context.layer_info {
// If this stacking context starts a layer, the offset and transformation are handled
// by layer position within the compositor.
// If this stacking context starts a layer, the offset and
// transformation are handled by layer position within the
// compositor.
Some(..) => (*transform, *subpixel_offset),
None => {
let origin = stacking_context.bounds.origin + *subpixel_offset;
@ -606,7 +607,7 @@ pub struct StackingContext {
pub layer_info: Option<LayerInfo>,
/// Children of this StackingContext.
pub children: Vec<Box<StackingContext>>,
children: Vec<Box<StackingContext>>,
}
impl StackingContext {
@ -642,6 +643,73 @@ impl StackingContext {
}
}
pub fn set_children(&mut self, children: Vec<Box<StackingContext>>) {
debug_assert!(self.children.is_empty());
// We need to take into account the possible transformations of the
// child stacking contexts.
for child in &children {
self.update_overflow_for_new_child(&child);
}
self.children = children;
}
pub fn add_child(&mut self, child: Box<StackingContext>) {
self.update_overflow_for_new_child(&child);
self.children.push(child);
}
pub fn add_children(&mut self, children: Vec<Box<StackingContext>>) {
if self.children.is_empty() {
return self.set_children(children);
}
for child in children {
self.add_child(child);
}
}
pub fn child_at_mut(&mut self, index: usize) -> &mut StackingContext {
&mut *self.children[index]
}
pub fn children(&self) -> &[Box<StackingContext>] {
&self.children
}
fn update_overflow_for_new_child(&mut self, child: &StackingContext) {
if self.context_type == StackingContextType::Real &&
child.context_type == StackingContextType::Real &&
!self.scrolls_overflow_area {
// This child might be transformed, so we need to take into account
// its transformed overflow rect too, but at the correct position.
let overflow =
child.overflow_rect_in_parent_space();
self.overflow = self.overflow.union(&overflow);
}
}
fn overflow_rect_in_parent_space(&self) -> Rect<Au> {
// Transform this stacking context to get it into the same space as
// the parent stacking context.
//
// TODO: Take into account 3d transforms, even though it's a fairly
// uncommon case.
let origin_x = self.bounds.origin.x.to_f32_px();
let origin_y = self.bounds.origin.y.to_f32_px();
let transform = Matrix4D::identity().translate(origin_x, origin_y, 0.0)
.mul(&self.transform);
let transform_2d = Matrix2D::new(transform.m11, transform.m12,
transform.m21, transform.m22,
transform.m41, transform.m42);
let overflow = geometry::au_rect_to_f32_rect(self.overflow);
let overflow = transform_2d.transform_rect(&overflow);
geometry::f32_rect_to_au_rect(overflow)
}
pub fn hit_test<'a>(&self,
traversal: &mut DisplayListTraversal<'a>,
translated_point: &Point2D<Au>,
@ -679,7 +747,7 @@ impl StackingContext {
}
}
for child in self.children.iter() {
for child in self.children() {
while let Some(item) = traversal.advance(self) {
if let Some(meta) = item.hit_test(point) {
result.push(meta);
@ -697,13 +765,13 @@ impl StackingContext {
pub fn print_with_tree(&self, print_tree: &mut PrintTree) {
print_tree.new_level(format!("{:?}", self));
for kid in self.children.iter() {
for kid in self.children() {
kid.print_with_tree(print_tree);
}
print_tree.end_level();
}
pub fn intersects_rect_in_parent_context(&self, rect: Option<Rect<Au>>) -> bool {
fn intersects_rect_in_parent_context(&self, rect: Option<Rect<Au>>) -> bool {
// We only do intersection checks for real stacking contexts, since
// pseudo stacking contexts might not have proper position information.
if self.context_type != StackingContextType::Real {
@ -715,24 +783,7 @@ impl StackingContext {
None => return true,
};
// Transform this stacking context to get it into the same space as
// the parent stacking context.
let origin_x = self.bounds.origin.x.to_f32_px();
let origin_y = self.bounds.origin.y.to_f32_px();
let transform = Matrix4D::identity().translate(origin_x,
origin_y,
0.0)
.mul(&self.transform);
let transform_2d = Matrix2D::new(transform.m11, transform.m12,
transform.m21, transform.m22,
transform.m41, transform.m42);
let overflow = geometry::au_rect_to_f32_rect(self.overflow);
let overflow = transform_2d.transform_rect(&overflow);
let overflow = geometry::f32_rect_to_au_rect(overflow);
rect.intersects(&overflow)
self.overflow_rect_in_parent_space().intersects(rect)
}
}

View file

@ -254,7 +254,7 @@ impl LayerCreator {
parent_origin: &Point2D<Au>,
transform: &Matrix4D<f32>,
perspective: &Matrix4D<f32>) {
for kid in stacking_context.children.iter() {
for kid in stacking_context.children() {
while let Some(item) = traversal.advance(stacking_context) {
self.create_layers_for_item(item,
parent_origin,

View file

@ -1729,7 +1729,7 @@ impl BlockFlowDisplayListBuilding for BlockFlow {
}
}
contexts[stacking_context_index].children = floating;
contexts[stacking_context_index].set_children(floating);
return stacking_context_id;
}
@ -1745,14 +1745,14 @@ impl BlockFlowDisplayListBuilding for BlockFlow {
&self.base,
scroll_policy,
StackingContextCreationMode::InnerScrollWrapper);
inner_stacking_context.children = child_contexts;
inner_stacking_context.set_children(child_contexts);
let mut outer_stacking_context = self.fragment.create_stacking_context(
stacking_context_id,
&self.base,
scroll_policy,
StackingContextCreationMode::OuterScrollWrapper);
outer_stacking_context.children.push(inner_stacking_context);
outer_stacking_context.add_child(inner_stacking_context);
outer_stacking_context
} else {
let mut stacking_context = self.fragment.create_stacking_context(
@ -1760,7 +1760,7 @@ impl BlockFlowDisplayListBuilding for BlockFlow {
&self.base,
scroll_policy,
StackingContextCreationMode::Normal);
stacking_context.children = child_contexts;
stacking_context.set_children(child_contexts);
stacking_context
};

View file

@ -84,8 +84,10 @@ pub fn build_display_list_for_subtree(root: &mut FlowRef,
let flow_root = flow_ref::deref_mut(root);
let layout_context = LayoutContext::new(shared_layout_context);
flow_root.traverse_preorder(&ComputeAbsolutePositions { layout_context: &layout_context });
let mut children = vec![];
flow_root.collect_stacking_contexts(root_stacking_context.id,
&mut root_stacking_context.children);
&mut children);
root_stacking_context.add_children(children);
let mut build_display_list = BuildDisplayList {
state: DisplayListBuildState::new(&layout_context,
flow::base(&*flow_root).stacking_context_id),

View file

@ -260,7 +260,7 @@ impl WebRenderStackingContextConverter for StackingContext {
builder: &mut webrender_traits::DisplayListBuilder,
frame_builder: &mut WebRenderFrameBuilder,
_force_positioned_stacking_level: bool) {
for child in self.children.iter() {
for child in self.children() {
while let Some(item) = traversal.advance(self) {
item.convert_to_webrender(builder, frame_builder);
}

View file

@ -3960,6 +3960,30 @@
"url": "/_mozilla/css/overflow_simple_a.html"
}
],
"css/overflow_transformed_sc.html": [
{
"path": "css/overflow_transformed_sc.html",
"references": [
[
"/_mozilla/css/overflow_transformed_sc_ref.html",
"=="
]
],
"url": "/_mozilla/css/overflow_transformed_sc.html"
}
],
"css/overflow_transformed_sc_rotate.html": [
{
"path": "css/overflow_transformed_sc_rotate.html",
"references": [
[
"/_mozilla/css/overflow_transformed_sc_rotate_ref.html",
"=="
]
],
"url": "/_mozilla/css/overflow_transformed_sc_rotate.html"
}
],
"css/overflow_wrap_a.html": [
{
"path": "css/overflow_wrap_a.html",
@ -13192,6 +13216,30 @@
"url": "/_mozilla/css/overflow_simple_a.html"
}
],
"css/overflow_transformed_sc.html": [
{
"path": "css/overflow_transformed_sc.html",
"references": [
[
"/_mozilla/css/overflow_transformed_sc_ref.html",
"=="
]
],
"url": "/_mozilla/css/overflow_transformed_sc.html"
}
],
"css/overflow_transformed_sc_rotate.html": [
{
"path": "css/overflow_transformed_sc_rotate.html",
"references": [
[
"/_mozilla/css/overflow_transformed_sc_rotate_ref.html",
"=="
]
],
"url": "/_mozilla/css/overflow_transformed_sc_rotate.html"
}
],
"css/overflow_wrap_a.html": [
{
"path": "css/overflow_wrap_a.html",

View file

@ -0,0 +1,31 @@
<!doctype html>
<meta charset="utf-8">
<title>Inner stacking contexts' transforms are taken into account for overflow computation</title>
<link rel="match" href="overflow_transformed_sc_ref.html">
<style>
.outer-sc {
transform: translate(-50px, -50px);
position: absolute;
width: 250px;
height: 250px;
top: 50px;
left: 50px;
background: blue;
}
.inner-sc {
position: absolute;
width: 250px;
height: 250px;
top: 0;
left: 0;
transform: translate(250px, 250px);
background: green;
}
html, body { margin: 0; padding: 0; }
</style>
<div class="outer-sc">
<div class="inner-sc">
</div>
</div>

View file

@ -0,0 +1,28 @@
<!doctype html>
<meta charset="utf-8">
<title>CSS test reference</title>
<style>
.outer-sc {
position: absolute;
width: 250px;
height: 250px;
top: 0;
left: 0;
background: blue;
}
.inner-sc {
position: absolute;
width: 250px;
height: 250px;
top: 250px;
left: 250px;
background: green;
}
html, body { margin: 0; padding: 0; }
</style>
<div class="outer-sc">
<div class="inner-sc">
</div>
</div>

View file

@ -0,0 +1,31 @@
<!doctype html>
<meta charset="utf-8">
<title>Inner stacking contexts' transforms are taken into account for overflow computation</title>
<link rel="match" href="overflow_transformed_sc_rotate_ref.html">
<style>
.outer-sc {
transform: translate(-50px, -50px);
position: absolute;
width: 250px;
height: 250px;
top: 50px;
left: 50px;
background: blue;
}
.inner-sc {
position: absolute;
width: 250px;
height: 250px;
top: 0;
left: 0;
transform: translate(250px, 250px) rotate(45deg);
background: green;
}
html, body { margin: 0; padding: 0; }
</style>
<div class="outer-sc">
<div class="inner-sc">
</div>
</div>

View file

@ -0,0 +1,30 @@
<!doctype html>
<meta charset="utf-8">
<title>CSS test reference.</title>
<style>
.outer-sc {
transform: translate(-50px, -50px);
position: absolute;
width: 250px;
height: 250px;
top: 50px;
left: 50px;
background: blue;
}
.inner-sc {
position: absolute;
width: 250px;
height: 250px;
top: 0;
left: 0;
transform: translate(250px, 250px) rotate(45deg);
background: green;
}
html, body { margin: 0; padding: 0; }
</style>
<div class="outer-sc">
</div>
<div class="inner-sc">
</div>