Auto merge of #25560 - mrobinson:scrollable-overflow-v1, r=SimonSapin

Add initial scrolling support to layout_2020

This adds very early scrolling support to layout_2020. Only the root element scrolls for now, but the idea is to continue to polish this.

---
<!-- 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
- [ ] These changes fix #___ (GitHub issue number if applicable)

<!-- Either: -->
- [x] There are tests for these changes OR
- [ ] These changes do not require tests because ___

<!-- Also, please make sure that "Allow edits from maintainers" checkbox is checked, so that we can help you if you get stuck somewhere along the way.-->

<!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. -->
This commit is contained in:
bors-servo 2020-01-21 13:03:27 -05:00 committed by GitHub
commit 35eb6dd074
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 286 additions and 65 deletions

View file

@ -426,8 +426,13 @@ where
node = child; node = child;
continue; continue;
} }
} else if node == self {
// If this is the root of the subtree and we aren't descending
// into our children return now.
return;
} }
} }
let mut next_is_a_sibling_of = node; let mut next_is_a_sibling_of = node;
node = loop { node = loop {
if let Some(sibling) = next_is_a_sibling_of.next_sibling() { if let Some(sibling) = next_is_a_sibling_of.next_sibling() {

View file

@ -339,11 +339,12 @@ impl Lines {
block: line_block_size, block: line_block_size,
}; };
self.next_line_block_position += size.block; self.next_line_block_position += size.block;
self.fragments.push(Fragment::Anonymous(AnonymousFragment { self.fragments
children: line_contents, .push(Fragment::Anonymous(AnonymousFragment::new(
rect: Rect { start_corner, size }, Rect { start_corner, size },
mode: containing_block.style.writing_mode, line_contents,
})) containing_block.style.writing_mode,
)))
} }
} }
@ -402,22 +403,24 @@ impl<'box_tree> PartialInlineBoxFragment<'box_tree> {
inline_position: &mut Length, inline_position: &mut Length,
at_line_break: bool, at_line_break: bool,
) { ) {
let mut fragment = BoxFragment { let content_rect = Rect {
tag: self.tag, size: Vec2 {
style: self.style.clone(), inline: *inline_position - self.start_corner.inline,
children: std::mem::take(&mut nesting_level.fragments_so_far), block: nesting_level.max_block_size_of_fragments_so_far,
content_rect: Rect {
size: Vec2 {
inline: *inline_position - self.start_corner.inline,
block: nesting_level.max_block_size_of_fragments_so_far,
},
start_corner: self.start_corner.clone(),
}, },
padding: self.padding.clone(), start_corner: self.start_corner.clone(),
border: self.border.clone(),
margin: self.margin.clone(),
block_margins_collapsed_with_children: CollapsedBlockMargins::zero(),
}; };
let mut fragment = BoxFragment::new(
self.tag,
self.style.clone(),
std::mem::take(&mut nesting_level.fragments_so_far),
content_rect,
self.padding.clone(),
self.border.clone(),
self.margin.clone(),
CollapsedBlockMargins::zero(),
);
let last_fragment = self.last_box_tree_fragment && !at_line_break; let last_fragment = self.last_box_tree_fragment && !at_line_break;
if last_fragment { if last_fragment {
*inline_position += fragment.padding.inline_end + *inline_position += fragment.padding.inline_end +
@ -470,16 +473,16 @@ fn layout_atomic<'box_tree>(
let size = replaced.used_size_as_if_inline_element(ifc.containing_block, &atomic.style); let size = replaced.used_size_as_if_inline_element(ifc.containing_block, &atomic.style);
let fragments = replaced.make_fragments(&atomic.style, size.clone()); let fragments = replaced.make_fragments(&atomic.style, size.clone());
let content_rect = Rect { start_corner, size }; let content_rect = Rect { start_corner, size };
BoxFragment { BoxFragment::new(
tag: atomic.tag, atomic.tag,
style: atomic.style.clone(), atomic.style.clone(),
children: fragments, fragments,
content_rect, content_rect,
padding, padding,
border, border,
margin, margin,
block_margins_collapsed_with_children: CollapsedBlockMargins::zero(), CollapsedBlockMargins::zero(),
} )
}, },
Err(non_replaced) => { Err(non_replaced) => {
let box_size = atomic.style.box_size(); let box_size = atomic.style.box_size();
@ -545,16 +548,16 @@ fn layout_atomic<'box_tree>(
inline: inline_size, inline: inline_size,
}, },
}; };
BoxFragment { BoxFragment::new(
tag: atomic.tag, atomic.tag,
style: atomic.style.clone(), atomic.style.clone(),
children: independent_layout.fragments, independent_layout.fragments,
content_rect, content_rect,
padding, padding,
border, border,
margin, margin,
block_margins_collapsed_with_children: CollapsedBlockMargins::zero(), CollapsedBlockMargins::zero(),
} )
}, },
}; };

View file

@ -495,16 +495,16 @@ fn layout_in_flow_non_replaced_block_level<'a>(
inline: inline_size, inline: inline_size,
}, },
}; };
BoxFragment { BoxFragment::new(
tag, tag,
style: style.clone(), style.clone(),
children: fragments, fragments,
content_rect, content_rect,
padding, padding,
border, border,
margin, margin,
block_margins_collapsed_with_children, block_margins_collapsed_with_children,
} )
} }
/// https://drafts.csswg.org/css2/visudet.html#block-replaced-width /// https://drafts.csswg.org/css2/visudet.html#block-replaced-width
@ -545,16 +545,17 @@ fn layout_in_flow_replaced_block_level<'a>(
}, },
size, size,
}; };
BoxFragment { let block_margins_collapsed_with_children = CollapsedBlockMargins::from_margin(&margin);
BoxFragment::new(
tag, tag,
style: style.clone(), style.clone(),
children: fragments, fragments,
content_rect, content_rect,
padding, padding,
border, border,
block_margins_collapsed_with_children: CollapsedBlockMargins::from_margin(&margin),
margin, margin,
} block_margins_collapsed_with_children,
)
} }
fn solve_inline_margins_for_in_flow_block_level( fn solve_inline_margins_for_in_flow_block_level(

View file

@ -11,12 +11,15 @@ use crate::formatting_contexts::IndependentFormattingContext;
use crate::fragments::Fragment; use crate::fragments::Fragment;
use crate::geom; use crate::geom;
use crate::geom::flow_relative::Vec2; use crate::geom::flow_relative::Vec2;
use crate::geom::physical;
use crate::positioned::AbsolutelyPositionedBox; use crate::positioned::AbsolutelyPositionedBox;
use crate::positioned::PositioningContext; use crate::positioned::PositioningContext;
use crate::replaced::ReplacedContent; use crate::replaced::ReplacedContent;
use crate::sizing::ContentSizesRequest; use crate::sizing::ContentSizesRequest;
use crate::style_ext::{Display, DisplayGeneratingBox, DisplayInside}; use crate::style_ext::{Display, DisplayGeneratingBox, DisplayInside};
use crate::DefiniteContainingBlock; use crate::DefiniteContainingBlock;
use app_units::Au;
use euclid::default::{Point2D, Rect, Size2D};
use gfx_traits::print_tree::PrintTree; use gfx_traits::print_tree::PrintTree;
use script_layout_interface::wrapper_traits::LayoutNode; use script_layout_interface::wrapper_traits::LayoutNode;
use servo_arc::Arc; use servo_arc::Arc;
@ -26,7 +29,17 @@ use style::Zero;
use style_traits::CSSPixel; use style_traits::CSSPixel;
pub struct BoxTreeRoot(BlockFormattingContext); pub struct BoxTreeRoot(BlockFormattingContext);
pub struct FragmentTreeRoot(Vec<Fragment>);
pub struct FragmentTreeRoot {
/// The children of the root of the fragment tree.
children: Vec<Fragment>,
/// The scrollable overflow of the root of the fragment tree.
scrollable_overflow: physical::Rect<Length>,
/// The axis-aligned bounding box of the border box of all child fragments
bounding_box_of_border_boxes: physical::Rect<Length>,
}
impl BoxTreeRoot { impl BoxTreeRoot {
pub fn construct<'dom, Node>(context: &LayoutContext, root_element: Node) -> Self pub fn construct<'dom, Node>(context: &LayoutContext, root_element: Node) -> Self
@ -131,7 +144,58 @@ impl BoxTreeRoot {
&mut independent_layout.fragments, &mut independent_layout.fragments,
); );
FragmentTreeRoot(independent_layout.fragments) // FIXME(mrobinson, bug 25564): We should be using the containing block
// here to properly convert scrollable overflow to physical geometry.
let scrollable_overflow =
independent_layout
.fragments
.iter()
.fold(physical::Rect::zero(), |acc, child| {
let child_overflow = child.scrollable_overflow();
// https://drafts.csswg.org/css-overflow/#scrolling-direction
// We want to clip scrollable overflow on box-start and inline-start
// sides of the scroll container.
//
// FIXME(mrobinson, bug 25564): This should take into account writing
// mode.
let child_overflow = physical::Rect {
top_left: physical::Vec2::zero(),
size: physical::Vec2 {
x: child_overflow.size.x + child_overflow.top_left.x,
y: child_overflow.size.y + child_overflow.top_left.y,
},
};
acc.axis_aligned_bounding_box(&child_overflow)
});
let containing_block = physical::Rect::zero();
let bounding_box_of_border_boxes =
independent_layout
.fragments
.iter()
.fold(physical::Rect::zero(), |acc, child| {
acc.axis_aligned_bounding_box(&match child {
Fragment::Box(fragment) => fragment
.border_rect()
.to_physical(fragment.style.writing_mode, &containing_block),
Fragment::Anonymous(fragment) => {
fragment.rect.to_physical(fragment.mode, &containing_block)
},
Fragment::Text(fragment) => fragment
.rect
.to_physical(fragment.parent_style.writing_mode, &containing_block),
Fragment::Image(fragment) => fragment
.rect
.to_physical(fragment.style.writing_mode, &containing_block),
})
});
FragmentTreeRoot {
children: independent_layout.fragments,
scrollable_overflow,
bounding_box_of_border_boxes,
}
} }
} }
@ -151,15 +215,34 @@ impl FragmentTreeRoot {
y: Length::new(viewport_size.height), y: Length::new(viewport_size.height),
}, },
}; };
for fragment in &self.0 { for fragment in &self.children {
fragment.build_display_list(builder, &containing_block) fragment.build_display_list(builder, &containing_block)
} }
} }
pub fn print(&self) { pub fn print(&self) {
let mut print_tree = PrintTree::new("Fragment Tree".to_string()); let mut print_tree = PrintTree::new("Fragment Tree".to_string());
for fragment in &self.0 { for fragment in &self.children {
fragment.print(&mut print_tree); fragment.print(&mut print_tree);
} }
} }
pub fn scrollable_overflow(&self) -> webrender_api::units::LayoutSize {
webrender_api::units::LayoutSize::from_untyped(Size2D::new(
self.scrollable_overflow.size.x.px(),
self.scrollable_overflow.size.y.px(),
))
}
pub fn bounding_box_of_border_boxes(&self) -> Rect<Au> {
let origin = Point2D::new(
Au::from_f32_px(self.bounding_box_of_border_boxes.top_left.x.px()),
Au::from_f32_px(self.bounding_box_of_border_boxes.top_left.y.px()),
);
let size = Size2D::new(
Au::from_f32_px(self.bounding_box_of_border_boxes.size.x.px()),
Au::from_f32_px(self.bounding_box_of_border_boxes.size.y.px()),
);
Rect::new(origin, size)
}
} }

View file

@ -3,6 +3,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use crate::geom::flow_relative::{Rect, Sides, Vec2}; use crate::geom::flow_relative::{Rect, Sides, Vec2};
use crate::geom::physical;
use gfx::text::glyph::GlyphStore; use gfx::text::glyph::GlyphStore;
use gfx_traits::print_tree::PrintTree; use gfx_traits::print_tree::PrintTree;
use servo_arc::Arc as ServoArc; use servo_arc::Arc as ServoArc;
@ -36,6 +37,9 @@ pub(crate) struct BoxFragment {
pub margin: Sides<Length>, pub margin: Sides<Length>,
pub block_margins_collapsed_with_children: CollapsedBlockMargins, pub block_margins_collapsed_with_children: CollapsedBlockMargins,
/// The scrollable overflow of this box fragment.
pub scrollable_overflow: physical::Rect<Length>,
} }
pub(crate) struct CollapsedBlockMargins { pub(crate) struct CollapsedBlockMargins {
@ -55,6 +59,9 @@ pub(crate) struct AnonymousFragment {
pub rect: Rect<Length>, pub rect: Rect<Length>,
pub children: Vec<Fragment>, pub children: Vec<Fragment>,
pub mode: WritingMode, pub mode: WritingMode,
/// The scrollable overflow of this anonymous fragment's children.
pub scrollable_overflow: physical::Rect<Length>,
} }
pub(crate) struct TextFragment { pub(crate) struct TextFragment {
@ -90,6 +97,21 @@ impl Fragment {
Fragment::Image(fragment) => fragment.print(tree), Fragment::Image(fragment) => fragment.print(tree),
} }
} }
pub fn scrollable_overflow(&self) -> physical::Rect<Length> {
// FIXME(mrobinson, bug 25564): We should be using the containing block
// here to properly convert scrollable overflow to physical geometry.
match self {
Fragment::Box(fragment) => fragment.scrollable_overflow.clone(),
Fragment::Anonymous(fragment) => fragment.scrollable_overflow.clone(),
Fragment::Text(fragment) => fragment
.rect
.to_physical(fragment.parent_style.writing_mode, &physical::Rect::zero()),
Fragment::Image(fragment) => fragment
.rect
.to_physical(fragment.style.writing_mode, &physical::Rect::zero()),
}
}
} }
impl AnonymousFragment { impl AnonymousFragment {
@ -98,6 +120,21 @@ impl AnonymousFragment {
children: vec![], children: vec![],
rect: Rect::zero(), rect: Rect::zero(),
mode, mode,
scrollable_overflow: physical::Rect::zero(),
}
}
pub fn new(rect: Rect<Length>, children: Vec<Fragment>, mode: WritingMode) -> Self {
// FIXME(mrobinson, bug 25564): We should be using the containing block
// here to properly convert scrollable overflow to physical geometry.
let scrollable_overflow = children.iter().fold(physical::Rect::zero(), |acc, child| {
acc.axis_aligned_bounding_box(&child.scrollable_overflow())
});
AnonymousFragment {
rect,
children,
mode,
scrollable_overflow,
} }
} }
@ -116,6 +153,37 @@ impl AnonymousFragment {
} }
impl BoxFragment { impl BoxFragment {
pub fn new(
tag: OpaqueNode,
style: ServoArc<ComputedValues>,
children: Vec<Fragment>,
content_rect: Rect<Length>,
padding: Sides<Length>,
border: Sides<Length>,
margin: Sides<Length>,
block_margins_collapsed_with_children: CollapsedBlockMargins,
) -> BoxFragment {
// FIXME(mrobinson, bug 25564): We should be using the containing block
// here to properly convert scrollable overflow to physical geometry.
let scrollable_overflow = children.iter().fold(
content_rect
.inflate(&border)
.to_physical(style.writing_mode, &physical::Rect::zero()),
|acc, child| acc.axis_aligned_bounding_box(&child.scrollable_overflow()),
);
BoxFragment {
tag,
style,
children,
content_rect,
padding,
border,
margin,
block_margins_collapsed_with_children,
scrollable_overflow,
}
}
pub fn padding_rect(&self) -> Rect<Length> { pub fn padding_rect(&self) -> Rect<Length> {
self.content_rect.inflate(&self.padding) self.content_rect.inflate(&self.padding)
} }

View file

@ -60,6 +60,15 @@ pub(crate) mod flow_relative {
} }
} }
impl<T: Zero> physical::Vec2<T> {
pub fn zero() -> Self {
Self {
x: T::zero(),
y: T::zero(),
}
}
}
impl<T: fmt::Debug> fmt::Debug for physical::Vec2<T> { impl<T: fmt::Debug> fmt::Debug for physical::Vec2<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// Not using f.debug_struct on purpose here, to keep {:?} output somewhat compact // Not using f.debug_struct on purpose here, to keep {:?} output somewhat compact
@ -387,6 +396,33 @@ impl<T> physical::Rect<T> {
} }
} }
impl physical::Rect<Length> {
pub fn axis_aligned_bounding_box(&self, other: &Self) -> Self {
let top_left = physical::Vec2 {
x: self.top_left.x.min(other.top_left.x),
y: self.top_left.y.min(other.top_left.y),
};
let bottom_corner_x = (self.top_left.x + self.size.x).max(other.top_left.x + other.size.x);
let bottom_corner_y = (self.top_left.y + self.size.y).max(other.top_left.y + other.size.y);
let size = physical::Vec2 {
x: bottom_corner_x - top_left.x,
y: bottom_corner_y - top_left.y,
};
Self { top_left, size }
}
}
impl<T: Zero> physical::Rect<T> {
pub fn zero() -> Self {
Self {
top_left: physical::Vec2::zero(),
size: physical::Vec2::zero(),
}
}
}
impl From<physical::Rect<Length>> for Rect<CSSPixel> { impl From<physical::Rect<Length>> for Rect<CSSPixel> {
fn from(r: physical::Rect<Length>) -> Self { fn from(r: physical::Rect<Length>) -> Self {
Rect { Rect {

View file

@ -290,11 +290,11 @@ impl<'box_tree> PositioningContext<'box_tree> {
); );
positioned_box_fragment positioned_box_fragment
.children .children
.push(Fragment::Anonymous(AnonymousFragment { .push(Fragment::Anonymous(AnonymousFragment::new(
padding_rect,
children, children,
rect: padding_rect, positioned_box_fragment.style.writing_mode,
mode: positioned_box_fragment.style.writing_mode, )))
}))
} }
} }
} }
@ -470,16 +470,16 @@ impl<'box_tree> HoistedAbsolutelyPositionedBox<'box_tree> {
size, size,
}; };
BoxFragment { BoxFragment::new(
tag: self.absolutely_positioned_box.contents.tag, self.absolutely_positioned_box.contents.tag,
style: style.clone(), style.clone(),
children: fragments, fragments,
content_rect, content_rect,
padding, padding,
border, border,
margin, margin,
block_margins_collapsed_with_children: CollapsedBlockMargins::zero(), CollapsedBlockMargins::zero(),
} )
}) })
} }
} }

View file

@ -5,6 +5,7 @@
//! Utilities for querying the layout, as needed by the layout thread. //! Utilities for querying the layout, as needed by the layout thread.
use crate::context::LayoutContext; use crate::context::LayoutContext;
use crate::flow::FragmentTreeRoot;
use app_units::Au; use app_units::Au;
use euclid::default::{Point2D, Rect}; use euclid::default::{Point2D, Rect};
use euclid::Size2D; use euclid::Size2D;
@ -163,8 +164,15 @@ impl LayoutRPC for LayoutRPCImpl {
} }
} }
pub fn process_content_box_request(_requested_node: OpaqueNode) -> Option<Rect<Au>> { pub fn process_content_box_request(
None _requested_node: OpaqueNode,
fragment_tree_root: Option<&FragmentTreeRoot>,
) -> Option<Rect<Au>> {
let fragment_tree_root = match fragment_tree_root {
Some(fragment_tree_root) => fragment_tree_root,
None => return None,
};
Some(fragment_tree_root.bounding_box_of_border_boxes())
} }
pub fn process_content_boxes_request(_requested_node: OpaqueNode) -> Vec<Rect<Au>> { pub fn process_content_boxes_request(_requested_node: OpaqueNode) -> Vec<Rect<Au>> {

View file

@ -46,7 +46,7 @@ use layout::query::{
process_text_index_request, process_text_index_request,
}; };
use layout::traversal::RecalcStyle; use layout::traversal::RecalcStyle;
use layout::BoxTreeRoot; use layout::{BoxTreeRoot, FragmentTreeRoot};
use layout_traits::LayoutThreadFactory; use layout_traits::LayoutThreadFactory;
use libc::c_void; use libc::c_void;
use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
@ -172,10 +172,10 @@ pub struct LayoutThread {
outstanding_web_fonts: Arc<AtomicUsize>, outstanding_web_fonts: Arc<AtomicUsize>,
/// The root of the box tree. /// The root of the box tree.
box_tree_root: RefCell<Option<layout::BoxTreeRoot>>, box_tree_root: RefCell<Option<BoxTreeRoot>>,
/// The root of the fragment tree. /// The root of the fragment tree.
fragment_tree_root: RefCell<Option<layout::FragmentTreeRoot>>, fragment_tree_root: RefCell<Option<FragmentTreeRoot>>,
/// The document-specific shared lock used for author-origin stylesheets /// The document-specific shared lock used for author-origin stylesheets
document_shared_lock: Option<SharedRwLock>, document_shared_lock: Option<SharedRwLock>,
@ -1218,7 +1218,10 @@ impl LayoutThread {
match *reflow_goal { match *reflow_goal {
ReflowGoal::LayoutQuery(ref querymsg, _) => match querymsg { ReflowGoal::LayoutQuery(ref querymsg, _) => match querymsg {
&QueryMsg::ContentBoxQuery(node) => { &QueryMsg::ContentBoxQuery(node) => {
rw_data.content_box_response = process_content_box_request(node); rw_data.content_box_response = process_content_box_request(
node,
(&*self.fragment_tree_root.borrow()).as_ref(),
);
}, },
&QueryMsg::ContentBoxesQuery(node) => { &QueryMsg::ContentBoxesQuery(node) => {
rw_data.content_boxes_response = process_content_boxes_request(node); rw_data.content_boxes_response = process_content_boxes_request(node);
@ -1355,7 +1358,7 @@ impl LayoutThread {
fn perform_post_style_recalc_layout_passes( fn perform_post_style_recalc_layout_passes(
&self, &self,
fragment_tree: &layout::FragmentTreeRoot, fragment_tree: &FragmentTreeRoot,
reflow_goal: &ReflowGoal, reflow_goal: &ReflowGoal,
document: Option<&ServoLayoutDocument>, document: Option<&ServoLayoutDocument>,
context: &mut LayoutContext, context: &mut LayoutContext,
@ -1374,12 +1377,16 @@ impl LayoutThread {
document.will_paint(); document.will_paint();
} }
let mut display_list = DisplayListBuilder::new(
self.id.to_webrender(),
context,
fragment_tree.scrollable_overflow(),
);
let viewport_size = webrender_api::units::LayoutSize::from_untyped(Size2D::new( let viewport_size = webrender_api::units::LayoutSize::from_untyped(Size2D::new(
self.viewport_size.width.to_f32_px(), self.viewport_size.width.to_f32_px(),
self.viewport_size.height.to_f32_px(), self.viewport_size.height.to_f32_px(),
)); ));
let mut display_list =
DisplayListBuilder::new(self.id.to_webrender(), context, viewport_size);
fragment_tree.build_display_list(&mut display_list, viewport_size); fragment_tree.build_display_list(&mut display_list, viewport_size);
if self.dump_flow_tree { if self.dump_flow_tree {

View file

@ -19368,11 +19368,11 @@
"testharness" "testharness"
], ],
"mozilla/scroll_root.html": [ "mozilla/scroll_root.html": [
"b1a9cb590b0fcce9c883f99e17fa029a999b699b", "5896eb3957da1eb85a26680053823d3f3bf9af49",
"reftest" "reftest"
], ],
"mozilla/scroll_root_ref.html": [ "mozilla/scroll_root_ref.html": [
"6503ad5d5265c0698f61fc607e2e4e017b31cb6f", "c7d4cfec7b9d9b5daf32841172721ddac3e4d071",
"support" "support"
], ],
"mozilla/scroll_top_null_target.html": [ "mozilla/scroll_top_null_target.html": [

View file

@ -7,6 +7,9 @@
<style> <style>
body { body {
background: green; background: green;
/* FIXME(mrobinson): Remove this workaround when #25559 is fixed. */
margin: 0;
} }
</style> </style>
</head> </head>

View file

@ -5,7 +5,14 @@
<style> <style>
body { body {
background: green; background: green;
/* FIXME(mrobinson): Remove this workaround when #25559 is fixed. */
margin: 0;
} }
</style> </style>
</head> </head>
<body>
<!-- FIXME(mrobinson): Remove this workaround div when #25559 is fixed. -->
<div style="height: 10000px;"></div>
</body>
</html> </html>