Auto merge of #20767 - mrobinson:use-reference-frame-for-fixed-position, r=gw3583

Use reference frames explicitly for fixed positioning

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

---
<!-- 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 because they should not change behavior.
- [ ] 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. -->

<!-- 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/20767)
<!-- Reviewable:end -->
This commit is contained in:
bors-servo 2018-06-05 04:16:56 -04:00 committed by GitHub
commit a07c718895
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 160 additions and 50 deletions

View file

@ -192,12 +192,16 @@ pub struct StackingContextCollectionState {
/// recursively building and processing the display list.
pub current_stacking_context_id: StackingContextId,
/// The current stacking real context id, which doesn't include pseudo-stacking contexts.
/// The current reference frame ClipScrollNodeIndex.
pub current_real_stacking_context_id: StackingContextId,
/// The next stacking context id that we will assign to a stacking context.
pub next_stacking_context_id: StackingContextId,
/// The current reference frame id. This is used to assign items to the parent
/// reference frame when we encounter a fixed position stacking context.
pub current_parent_reference_frame_id: ClipScrollNodeIndex,
/// The current clip and scroll info, used to keep track of state when
/// recursively building and processing the display list.
pub current_clipping_and_scrolling: ClippingAndScrolling,
@ -221,19 +225,8 @@ pub struct StackingContextCollectionState {
impl StackingContextCollectionState {
pub fn new(pipeline_id: PipelineId) -> StackingContextCollectionState {
let root_clip_indices = ClippingAndScrolling::simple(ClipScrollNodeIndex(0));
// This is just a dummy node to take up a slot in the array. WebRender
// takes care of adding this root node and it can be ignored during DL conversion.
let root_node = ClipScrollNode {
parent_index: ClipScrollNodeIndex(0),
clip: ClippingRegion::from_rect(LayoutRect::zero()),
content_rect: LayoutRect::zero(),
node_type: ClipScrollNodeType::ScrollFrame(
ScrollSensitivity::ScriptAndInputEvents,
pipeline_id.root_scroll_id(),
),
};
let root_clip_indices =
ClippingAndScrolling::simple(ClipScrollNodeIndex::root_scroll_node());
let mut stacking_context_info = FnvHashMap::default();
stacking_context_info.insert(
@ -241,14 +234,22 @@ impl StackingContextCollectionState {
StackingContextInfo::new(StackingContextId::root()),
);
// We add two empty nodes to represent the WebRender root reference frame and
// root scroll nodes. WebRender adds these automatically and we add them here
// so that the ids in the array match up with the ones we assign during display
// list building. We ignore these two nodes during conversion to WebRender
// display lists.
let clip_scroll_nodes = vec![ClipScrollNode::placeholder(), ClipScrollNode::placeholder()];
StackingContextCollectionState {
pipeline_id: pipeline_id,
root_stacking_context: StackingContext::root(),
stacking_context_info,
clip_scroll_nodes: vec![root_node],
clip_scroll_nodes,
current_stacking_context_id: StackingContextId::root(),
current_real_stacking_context_id: StackingContextId::root(),
next_stacking_context_id: StackingContextId::root().next(),
current_parent_reference_frame_id: ClipScrollNodeIndex::root_reference_frame(),
current_clipping_and_scrolling: root_clip_indices,
containing_block_clipping_and_scrolling: root_clip_indices,
clip_stack: Vec::new(),
@ -291,17 +292,27 @@ impl StackingContextCollectionState {
}
fn add_clip_scroll_node(&mut self, clip_scroll_node: ClipScrollNode) -> ClipScrollNodeIndex {
// We want the scroll root to be defined before any possible item that could use it,
// so we make sure that it is added to the beginning of the parent "real" (non-pseudo)
// stacking context. This ensures that item reordering will not result in an item using
// the scroll root before it is defined.
let is_placeholder = clip_scroll_node.is_placeholder();
self.clip_scroll_nodes.push(clip_scroll_node);
let index = ClipScrollNodeIndex(self.clip_scroll_nodes.len() - 1);
self.stacking_context_info
.get_mut(&self.current_real_stacking_context_id)
.unwrap()
.clip_scroll_nodes
.push(index);
let index = ClipScrollNodeIndex::new(self.clip_scroll_nodes.len() - 1);
// If this node is a placeholder node (currently just reference frames), then don't add
// it to the stacking context list. Placeholder nodes are created automatically by
// WebRender and we don't want to explicitly create them in the display list. The node
// is just there to take up a spot in the global list of ClipScrollNodes.
if !is_placeholder {
// We want the scroll root to be defined before any possible item that could use it,
// so we make sure that it is added to the beginning of the parent "real" (non-pseudo)
// stacking context. This ensures that item reordering will not result in an item using
// the scroll root before it is defined.
self.stacking_context_info
.get_mut(&self.current_real_stacking_context_id)
.unwrap()
.clip_scroll_nodes
.push(index);
}
index
}
}
@ -347,7 +358,6 @@ impl<'a> DisplayListBuildState<'a> {
layout_context: &'a LayoutContext,
state: StackingContextCollectionState,
) -> DisplayListBuildState<'a> {
let root_clip_indices = ClippingAndScrolling::simple(ClipScrollNodeIndex(0));
DisplayListBuildState {
layout_context: layout_context,
root_stacking_context: state.root_stacking_context,
@ -356,7 +366,8 @@ impl<'a> DisplayListBuildState<'a> {
clip_scroll_nodes: state.clip_scroll_nodes,
processing_scrolling_overflow_element: false,
current_stacking_context_id: StackingContextId::root(),
current_clipping_and_scrolling: root_clip_indices,
current_clipping_and_scrolling:
ClippingAndScrolling::simple(ClipScrollNodeIndex::root_scroll_node()),
iframe_sizes: Vec::new(),
indexable_text: IndexableText::default(),
}
@ -374,7 +385,7 @@ impl<'a> DisplayListBuildState<'a> {
return index;
}
self.clip_scroll_nodes[index.0].parent_index
self.clip_scroll_nodes[index.to_index()].parent_index
}
fn is_background_or_border_of_clip_scroll_node(&self, section: DisplayListSection) -> bool {
@ -429,7 +440,7 @@ impl<'a> DisplayListBuildState<'a> {
// stacking context. This ensures that item reordering will not result in an item using
// the scroll root before it is defined.
self.clip_scroll_nodes.push(node);
let index = ClipScrollNodeIndex(self.clip_scroll_nodes.len() - 1);
let index = ClipScrollNodeIndex::new(self.clip_scroll_nodes.len() - 1);
let real_stacking_context_id =
self.stacking_context_info[&self.current_stacking_context_id].real_stacking_context_id;
self.stacking_context_info
@ -751,6 +762,7 @@ pub trait FragmentDisplayListBuilding {
base_flow: &BaseFlow,
scroll_policy: ScrollPolicy,
context_type: StackingContextType,
established_reference_frame: Option<ClipScrollNodeIndex>,
parent_clipping_and_scrolling: ClippingAndScrolling,
) -> StackingContext;
@ -1873,6 +1885,7 @@ impl FragmentDisplayListBuilding for Fragment {
base_flow: &BaseFlow,
scroll_policy: ScrollPolicy,
context_type: StackingContextType,
established_reference_frame: Option<ClipScrollNodeIndex>,
parent_clipping_and_scrolling: ClippingAndScrolling,
) -> StackingContext {
let border_box = self.stacking_relative_border_box(
@ -1916,6 +1929,7 @@ impl FragmentDisplayListBuilding for Fragment {
self.perspective_matrix(&border_box),
scroll_policy,
parent_clipping_and_scrolling,
established_reference_frame,
)
}
@ -2135,6 +2149,7 @@ pub trait BlockFlowDisplayListBuilding {
state: &mut StackingContextCollectionState,
preserved_state: &mut SavedStackingContextCollectionState,
stacking_context_type: Option<StackingContextType>,
established_reference_frame: Option<ClipScrollNodeIndex>,
flags: StackingContextCollectionFlags,
) -> ClippingAndScrolling;
fn setup_clip_scroll_node_for_position(
@ -2164,6 +2179,7 @@ pub trait BlockFlowDisplayListBuilding {
&mut self,
parent_stacking_context_id: StackingContextId,
parent_clipping_and_scrolling: ClippingAndScrolling,
established_reference_frame: Option<ClipScrollNodeIndex>,
state: &mut StackingContextCollectionState,
);
fn build_display_list_for_block(
@ -2187,6 +2203,8 @@ pub trait BlockFlowDisplayListBuilding {
&self,
flags: StackingContextCollectionFlags,
) -> Option<StackingContextType>;
fn is_reference_frame(&self, context_type: Option<StackingContextType>) -> bool;
}
/// This structure manages ensuring that modification to StackingContextCollectionState is
@ -2197,6 +2215,7 @@ pub trait BlockFlowDisplayListBuilding {
pub struct SavedStackingContextCollectionState {
stacking_context_id: StackingContextId,
real_stacking_context_id: StackingContextId,
parent_reference_frame_id: ClipScrollNodeIndex,
clipping_and_scrolling: ClippingAndScrolling,
containing_block_clipping_and_scrolling: ClippingAndScrolling,
clips_pushed: usize,
@ -2209,6 +2228,7 @@ impl SavedStackingContextCollectionState {
SavedStackingContextCollectionState {
stacking_context_id: state.current_stacking_context_id,
real_stacking_context_id: state.current_real_stacking_context_id,
parent_reference_frame_id: state.current_parent_reference_frame_id,
clipping_and_scrolling: state.current_clipping_and_scrolling,
containing_block_clipping_and_scrolling: state.containing_block_clipping_and_scrolling,
clips_pushed: 0,
@ -2230,6 +2250,7 @@ impl SavedStackingContextCollectionState {
fn restore(self, state: &mut StackingContextCollectionState) {
state.current_stacking_context_id = self.stacking_context_id;
state.current_real_stacking_context_id = self.real_stacking_context_id;
state.current_parent_reference_frame_id = self.parent_reference_frame_id;
state.current_clipping_and_scrolling = self.clipping_and_scrolling;
state.containing_block_clipping_and_scrolling =
self.containing_block_clipping_and_scrolling;
@ -2332,6 +2353,16 @@ impl BlockFlowDisplayListBuilding for BlockFlow {
}
}
/// Returns true if this fragment may establish a reference frame and this block
/// creates a stacking context. Both are necessary in order to establish a reference
/// frame.
fn is_reference_frame(&self, context_type: Option<StackingContextType>) -> bool {
match context_type {
Some(StackingContextType::Real) => self.fragment.can_establish_reference_frame(),
_ => false,
}
}
fn collect_stacking_contexts_for_block(
&mut self,
state: &mut StackingContextCollectionState,
@ -2348,8 +2379,17 @@ impl BlockFlowDisplayListBuilding for BlockFlow {
if stacking_context_type == Some(StackingContextType::Real) {
state.current_real_stacking_context_id = self.base.stacking_context_id;
}
let established_reference_frame = if self.is_reference_frame(stacking_context_type) {
// WebRender currently creates reference frames automatically, so just add
// a placeholder node to allocate a ClipScrollNodeIndex for this reference frame.
Some(state.add_clip_scroll_node(ClipScrollNode::placeholder()))
} else {
None
};
// We are getting the id of the scroll root that contains us here, not the id of
// any scroll root that we create. If we create a scroll root, its index will be
// stored in state.current_clipping_and_scrolling. If we create a stacking context,
@ -2358,6 +2398,7 @@ impl BlockFlowDisplayListBuilding for BlockFlow {
state,
&mut preserved_state,
stacking_context_type,
established_reference_frame,
flags,
);
@ -2371,6 +2412,7 @@ impl BlockFlowDisplayListBuilding for BlockFlow {
self.create_real_stacking_context_for_block(
preserved_state.stacking_context_id,
containing_clipping_and_scrolling,
established_reference_frame,
state,
);
},
@ -2392,6 +2434,7 @@ impl BlockFlowDisplayListBuilding for BlockFlow {
state: &mut StackingContextCollectionState,
preserved_state: &mut SavedStackingContextCollectionState,
stacking_context_type: Option<StackingContextType>,
established_reference_frame: Option<ClipScrollNodeIndex>,
flags: StackingContextCollectionFlags,
) -> ClippingAndScrolling {
// If this block is absolutely positioned, we should be clipped and positioned by
@ -2404,13 +2447,23 @@ impl BlockFlowDisplayListBuilding for BlockFlow {
state.containing_block_clipping_and_scrolling
},
StylePosition::Fixed => {
// If we are a fixed positioned stacking context, we want to be scrolled by
// our reference frame instead of the clip scroll node that we are inside.
preserved_state.push_clip(state, Rect::max_rect(), StylePosition::Fixed);
state.current_clipping_and_scrolling.scrolling =
state.current_parent_reference_frame_id;
state.current_clipping_and_scrolling
},
_ => state.current_clipping_and_scrolling,
};
self.base.clipping_and_scrolling = Some(containing_clipping_and_scrolling);
if let Some(reference_frame_index) = established_reference_frame {
let clipping_and_scrolling = ClippingAndScrolling::simple(reference_frame_index);
state.current_clipping_and_scrolling = clipping_and_scrolling;
self.base.clipping_and_scrolling = Some(clipping_and_scrolling);
}
let stacking_relative_border_box = if self.fragment.establishes_stacking_context() {
self.stacking_relative_border_box(CoordinateSystem::Own)
} else {
@ -2658,6 +2711,7 @@ impl BlockFlowDisplayListBuilding for BlockFlow {
&self.base,
ScrollPolicy::Scrollable,
stacking_context_type,
None,
parent_clipping_and_scrolling,
);
state.add_stacking_context(parent_stacking_context_id, new_context);
@ -2683,19 +2737,15 @@ impl BlockFlowDisplayListBuilding for BlockFlow {
&mut self,
parent_stacking_context_id: StackingContextId,
parent_clipping_and_scrolling: ClippingAndScrolling,
established_reference_frame: Option<ClipScrollNodeIndex>,
state: &mut StackingContextCollectionState,
) {
let scroll_policy = if self.is_fixed() {
ScrollPolicy::Fixed
} else {
ScrollPolicy::Scrollable
};
let stacking_context = self.fragment.create_stacking_context(
self.base.stacking_context_id,
&self.base,
scroll_policy,
ScrollPolicy::Scrollable,
StackingContextType::Real,
established_reference_frame,
parent_clipping_and_scrolling,
);
@ -2833,6 +2883,7 @@ impl InlineFlowDisplayListBuilding for InlineFlow {
&self.base,
ScrollPolicy::Scrollable,
StackingContextType::Real,
None,
state.current_clipping_and_scrolling,
);

View file

@ -38,14 +38,25 @@ pub static BLUR_INFLATION_FACTOR: i32 = 3;
/// An index into the vector of ClipScrollNodes. During WebRender conversion these nodes
/// are given ClipIds.
#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
pub struct ClipScrollNodeIndex(pub usize);
pub struct ClipScrollNodeIndex(usize);
impl ClipScrollNodeIndex {
pub fn root_scroll_node() -> ClipScrollNodeIndex {
ClipScrollNodeIndex(1)
}
pub fn root_reference_frame() -> ClipScrollNodeIndex {
ClipScrollNodeIndex(0)
}
pub fn new(index: usize) -> ClipScrollNodeIndex {
assert_ne!(index, 0, "Use the root_reference_frame constructor");
assert_ne!(index, 1, "Use the root_scroll_node constructor");
ClipScrollNodeIndex(index)
}
pub fn is_root_scroll_node(&self) -> bool {
match *self {
ClipScrollNodeIndex(0) => true,
_ => false,
}
*self == Self::root_scroll_node()
}
pub fn to_define_item(&self) -> DisplayItem {
@ -54,6 +65,10 @@ impl ClipScrollNodeIndex {
node_index: *self,
}))
}
pub fn to_index(self) -> usize {
self.0
}
}
/// A set of indices into the clip scroll node vector for a given item.
@ -193,6 +208,9 @@ pub struct StackingContext {
/// The clip and scroll info for this StackingContext.
pub parent_clipping_and_scrolling: ClippingAndScrolling,
/// The index of the reference frame that this stacking context estalishes.
pub established_reference_frame: Option<ClipScrollNodeIndex>,
}
impl StackingContext {
@ -211,6 +229,7 @@ impl StackingContext {
perspective: Option<LayoutTransform>,
scroll_policy: ScrollPolicy,
parent_clipping_and_scrolling: ClippingAndScrolling,
established_reference_frame: Option<ClipScrollNodeIndex>,
) -> StackingContext {
StackingContext {
id,
@ -225,6 +244,7 @@ impl StackingContext {
perspective,
scroll_policy,
parent_clipping_and_scrolling,
established_reference_frame,
}
}
@ -242,7 +262,8 @@ impl StackingContext {
TransformStyle::Flat,
None,
ScrollPolicy::Scrollable,
ClippingAndScrolling::simple(ClipScrollNodeIndex(0)),
ClippingAndScrolling::simple(ClipScrollNodeIndex::root_scroll_node()),
None,
)
}
@ -309,15 +330,16 @@ impl fmt::Debug for StackingContext {
}
}
#[derive(Clone, Debug, Serialize)]
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct StickyFrameData {
pub margins: SideOffsets2D<Option<f32>>,
pub vertical_offset_bounds: StickyOffsetBounds,
pub horizontal_offset_bounds: StickyOffsetBounds,
}
#[derive(Clone, Debug, Serialize)]
#[derive(Clone, Debug, PartialEq, Serialize)]
pub enum ClipScrollNodeType {
Placeholder,
ScrollFrame(ScrollSensitivity, ExternalScrollId),
StickyFrame(StickyFrameData),
Clip,
@ -339,6 +361,21 @@ pub struct ClipScrollNode {
pub node_type: ClipScrollNodeType,
}
impl ClipScrollNode {
pub fn placeholder() -> ClipScrollNode {
ClipScrollNode {
parent_index: ClipScrollNodeIndex(0),
clip: ClippingRegion::from_rect(LayoutRect::zero()),
content_rect: LayoutRect::zero(),
node_type: ClipScrollNodeType::Placeholder,
}
}
pub fn is_placeholder(&self) -> bool {
self.node_type == ClipScrollNodeType::Placeholder
}
}
/// One drawing command in the list.
#[derive(Clone, Serialize)]
pub enum DisplayItem {
@ -412,7 +449,8 @@ impl BaseDisplayItem {
clip_rect: LayoutRect::max_rect(),
section: DisplayListSection::Content,
stacking_context_id: StackingContextId::root(),
clipping_and_scrolling: ClippingAndScrolling::simple(ClipScrollNodeIndex(0)),
clipping_and_scrolling:
ClippingAndScrolling::simple(ClipScrollNodeIndex::root_scroll_node()),
}
}
}

View file

@ -41,7 +41,12 @@ impl WebRenderDisplayListConverter for DisplayList {
let mut clip_ids = Vec::with_capacity(self.clip_scroll_nodes.len());
clip_ids.resize(self.clip_scroll_nodes.len(), None);
clip_ids[0] = Some(ClipId::root_scroll_node(pipeline_id.to_webrender()));
// We need to add the WebRender root reference frame and root scroll node ids
// here manually, because WebRender creates these automatically.
let webrender_pipeline = pipeline_id.to_webrender();
clip_ids[0] = Some(ClipId::root_reference_frame(webrender_pipeline));
clip_ids[1] = Some(ClipId::root_scroll_node(webrender_pipeline));
for item in &self.list {
item.convert_to_webrender(
@ -78,7 +83,7 @@ impl WebRenderDisplayItemConverter for DisplayItem {
current_clip_and_scroll_info: &mut ClipAndScrollInfo,
) {
let get_id = |clip_ids: &[Option<ClipId>], index: ClipScrollNodeIndex| -> ClipId {
match clip_ids[index.0] {
match clip_ids[index.to_index()] {
Some(id) => id,
None => unreachable!("Tried to use WebRender ClipId before it was defined."),
}
@ -214,7 +219,7 @@ impl WebRenderDisplayItemConverter for DisplayItem {
let stacking_context = &item.stacking_context;
debug_assert_eq!(stacking_context.context_type, StackingContextType::Real);
builder.push_stacking_context(
let reference_frame_clip_id = builder.push_stacking_context(
&webrender_api::LayoutPrimitiveInfo::new(stacking_context.bounds),
None,
stacking_context.scroll_policy,
@ -225,10 +230,17 @@ impl WebRenderDisplayItemConverter for DisplayItem {
stacking_context.filters.clone(),
GlyphRasterSpace::Screen,
);
match (reference_frame_clip_id, stacking_context.established_reference_frame) {
(Some(webrender_id), Some(frame_index)) =>
clip_ids[frame_index.to_index()] = Some(webrender_id),
(None, None) => {},
_ => warn!("Mismatch between reference frame establishment!"),
}
},
DisplayItem::PopStackingContext(_) => builder.pop_stacking_context(),
DisplayItem::DefineClipScrollNode(ref item) => {
let node = &clip_scroll_nodes[item.node_index.0];
let node = &clip_scroll_nodes[item.node_index.to_index()];
let parent_id = get_id(clip_ids, node.parent_index);
let item_rect = node.clip.main;
@ -262,9 +274,12 @@ impl WebRenderDisplayItemConverter for DisplayItem {
builder.pop_clip_id();
id
},
ClipScrollNodeType::Placeholder => {
unreachable!("Found DefineClipScrollNode for Placeholder type node.");
}
};
clip_ids[item.node_index.0] = Some(webrender_id);
clip_ids[item.node_index.to_index()] = Some(webrender_id);
},
}
}

View file

@ -2474,6 +2474,12 @@ impl Fragment {
stacking_relative_border_box.size.height - border_padding.vertical()))
}
/// Returns true if this fragment may establish a reference frame.
pub fn can_establish_reference_frame(&self) -> bool {
!self.style().get_box().transform.0.is_empty() ||
self.style().get_box().perspective != Perspective::None
}
/// Returns true if this fragment has a filter, transform, or perspective property set.
pub fn has_filter_transform_or_perspective(&self) -> bool {
!self.style().get_box().transform.0.is_empty() ||