Skip to content

Commit

Permalink
script: When using WebRender, keep the DOM-side scroll positions for
Browse files Browse the repository at this point in the history
elements with `overflow: scroll` up to date, and take them into account
when doing hit testing.

Closes #11648.
  • Loading branch information
pcwalton committed Jun 11, 2016
1 parent ce88b8e commit 041cfe6
Show file tree
Hide file tree
Showing 14 changed files with 259 additions and 47 deletions.
35 changes: 27 additions & 8 deletions components/gfx/display_list/mod.rs
Expand Up @@ -487,14 +487,15 @@ impl DisplayList {
/// Places all nodes containing the point of interest into `result`, topmost first. Respects
/// the `pointer-events` CSS property If `topmost_only` is true, stops after placing one node
/// into the list. `result` must be empty upon entry to this function.
pub fn hit_test(&self, point: Point2D<Au>) -> Vec<DisplayItemMetadata> {
pub fn hit_test(&self, point: &Point2D<Au>, scroll_offsets: &ScrollOffsetMap)
-> Vec<DisplayItemMetadata> {
let mut traversal = DisplayListTraversal {
display_list: self,
current_item_index: 0,
last_item_index: self.list.len() - 1,
};
let mut result = Vec::new();
self.root_stacking_context.hit_test(&mut traversal, point, &mut result);
self.root_stacking_context.hit_test(&mut traversal, point, scroll_offsets, &mut result);
result.reverse();
result
}
Expand Down Expand Up @@ -610,24 +611,38 @@ impl StackingContext {

pub fn hit_test<'a>(&self,
traversal: &mut DisplayListTraversal<'a>,
point: Point2D<Au>,
point: &Point2D<Au>,
scroll_offsets: &ScrollOffsetMap,
result: &mut Vec<DisplayItemMetadata>) {
// Convert the point into stacking context local space
let point = if self.context_type == StackingContextType::Real {
let point = point - self.bounds.origin;
// Convert the point into stacking context local transform space.
let mut point = if self.context_type == StackingContextType::Real {
let point = *point - self.bounds.origin;
let inv_transform = self.transform.invert();
let frac_point = inv_transform.transform_point(&Point2D::new(point.x.to_f32_px(),
point.y.to_f32_px()));
Point2D::new(Au::from_f32_px(frac_point.x), Au::from_f32_px(frac_point.y))
} else {
point
*point
};

// Adjust the 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 `Document::handle_mouse_move_event()`)
// by now.
if self.id != StackingContextId::root() {
if let Some(scroll_offset) = scroll_offsets.get(&self.id) {
point.x -= Au::from_f32_px(scroll_offset.x);
point.y -= Au::from_f32_px(scroll_offset.y);
}
}

for child in self.children.iter() {
while let Some(item) = traversal.advance(self) {
item.hit_test(point, result);
}
child.hit_test(traversal, point, result);
child.hit_test(traversal, &point, scroll_offsets, result);
}

while let Some(item) = traversal.advance(self) {
Expand Down Expand Up @@ -1415,3 +1430,7 @@ impl WebRenderImageInfo {
}
}
}

/// The type of the scroll offset list. This is only populated if WebRender is in use.
pub type ScrollOffsetMap = HashMap<StackingContextId, Point2D<f32>>;

61 changes: 56 additions & 5 deletions components/gfx_traits/lib.rs
Expand Up @@ -26,6 +26,20 @@ use euclid::Matrix4D;
use euclid::rect::Rect;
use msg::constellation_msg::PipelineId;
use std::fmt::{self, Debug, Formatter};
use std::sync::atomic::{ATOMIC_USIZE_INIT, AtomicUsize, Ordering};

/// The next ID that will be used for a special stacking context.
///
/// A special stacking context is a stacking context that is one of (a) the outer stacking context
/// of an element with `overflow: scroll`; (b) generated content; (c) both (a) and (b).
static NEXT_SPECIAL_STACKING_CONTEXT_ID: AtomicUsize = ATOMIC_USIZE_INIT;

/// If none of the bits outside this mask are set, the stacking context is a special stacking
/// context.
///
/// Note that we assume that the top 16 bits of the address space are unused on the platform.
const SPECIAL_STACKING_CONTEXT_ID_MASK: usize = 0xffff;


#[derive(Clone, Copy, Debug, PartialEq)]
pub enum LayerKind {
Expand Down Expand Up @@ -159,15 +173,33 @@ pub struct StackingContextId(
);

impl StackingContextId {
#[inline(always)]
#[inline]
pub fn new(id: usize) -> StackingContextId {
StackingContextId::new_of_type(id, FragmentType::FragmentBody)
}

#[inline(always)]
/// Returns a new stacking context ID for a special stacking context.
fn next_special_id() -> usize {
// We shift this left by 2 to make room for the fragment type ID.
((NEXT_SPECIAL_STACKING_CONTEXT_ID.fetch_add(1, Ordering::SeqCst) + 1) << 2) &
SPECIAL_STACKING_CONTEXT_ID_MASK
}

#[inline]
pub fn new_of_type(id: usize, fragment_type: FragmentType) -> StackingContextId {
debug_assert_eq!(id & fragment_type as usize, 0);
StackingContextId(id | fragment_type as usize)
debug_assert_eq!(id & (fragment_type as usize), 0);
if fragment_type == FragmentType::FragmentBody {
StackingContextId(id)
} else {
StackingContextId(StackingContextId::next_special_id() | (fragment_type as usize))
}
}

/// Returns an ID for the stacking context that forms the outer stacking context of an element
/// with `overflow: scroll`.
#[inline(always)]
pub fn new_outer(fragment_type: FragmentType) -> StackingContextId {
StackingContextId(StackingContextId::next_special_id() | (fragment_type as usize))
}

#[inline]
Expand All @@ -179,9 +211,28 @@ impl StackingContextId {
pub fn id(&self) -> usize {
self.0 & !3
}
}

/// Returns the stacking context ID for the outer document/layout root.
#[inline]
pub fn root() -> StackingContextId {
StackingContextId(0)
}

/// Returns true if this is a special stacking context.
///
/// A special stacking context is a stacking context that is one of (a) the outer stacking
/// context of an element with `overflow: scroll`; (b) generated content; (c) both (a) and (b).
#[inline]
pub fn is_special(&self) -> bool {
(self.0 & !SPECIAL_STACKING_CONTEXT_ID_MASK) == 0
}
}

/// The type of fragment that a stacking context represents.
///
/// This can only ever grow to maximum 4 entries. That's because we cram the value of this enum
/// into the lower 2 bits of the `StackingContextId`, which otherwise contains a 32-bit-aligned
/// heap address.
#[derive(Clone, Debug, PartialEq, Eq, Copy, Hash, Deserialize, Serialize, HeapSizeOf)]
pub enum FragmentType {
/// A StackingContext for the fragment body itself.
Expand Down
13 changes: 9 additions & 4 deletions components/layout/display_list_builder.rs
Expand Up @@ -1686,13 +1686,18 @@ impl BlockFlowDisplayListBuilding for BlockFlow {
return parent_id;
}

let stacking_context_id =
let has_scrolling_overflow = self.has_scrolling_overflow();
let stacking_context_id = if has_scrolling_overflow {
StackingContextId::new_outer(self.fragment.fragment_type())
} else {
StackingContextId::new_of_type(self.fragment.node.id() as usize,
self.fragment.fragment_type());
self.fragment.fragment_type())
};
self.base.stacking_context_id = stacking_context_id;

let inner_stacking_context_id = if self.has_scrolling_overflow() {
StackingContextId::new_of_type(self.base.flow_id(), self.fragment.fragment_type())
let inner_stacking_context_id = if has_scrolling_overflow {
StackingContextId::new_of_type(self.fragment.node.id() as usize,
self.fragment.fragment_type())
} else {
stacking_context_id
};
Expand Down
38 changes: 28 additions & 10 deletions components/layout/layout_thread.rs
Expand Up @@ -21,13 +21,13 @@ use euclid::size::Size2D;
use flow::{self, Flow, ImmutableFlowUtils, MutableOwnedFlowUtils};
use flow_ref::{self, FlowRef};
use fnv::FnvHasher;
use gfx::display_list::{ClippingRegion, DisplayItemMetadata, DisplayList, LayerInfo};
use gfx::display_list::{OpaqueNode, StackingContext, StackingContextType, WebRenderImageInfo};
use gfx::display_list::{ClippingRegion, DisplayItemMetadata, DisplayList, LayerInfo, OpaqueNode};
use gfx::display_list::{ScrollOffsetMap, StackingContext, StackingContextType, WebRenderImageInfo};
use gfx::font;
use gfx::font_cache_thread::FontCacheThread;
use gfx::font_context;
use gfx::paint_thread::LayoutToPaintMsg;
use gfx_traits::{color, Epoch, LayerId, ScrollPolicy, StackingContextId};
use gfx_traits::{color, Epoch, FragmentType, LayerId, ScrollPolicy, StackingContextId};
use heapsize::HeapSizeOf;
use incremental::LayoutDamageComputation;
use incremental::{REPAINT, STORE_OVERFLOW, REFLOW_OUT_OF_FLOW, REFLOW, REFLOW_ENTIRE_DOCUMENT};
Expand All @@ -51,8 +51,8 @@ use script::layout_interface::OpaqueStyleAndLayoutData;
use script::layout_interface::{LayoutRPC, OffsetParentResponse, NodeOverflowResponse, MarginStyleResponse};
use script::layout_interface::{Msg, NewLayoutThreadInfo, Reflow, ReflowQueryType, ScriptReflow};
use script::reporter::CSSErrorReporter;
use script_traits::StackingContextScrollState;
use script_traits::{ConstellationControlMsg, LayoutControlMsg, LayoutMsg as ConstellationMsg};
use script_traits::{StackingContextScrollState, UntrustedNodeAddress};
use sequential;
use serde_json;
use std::borrow::ToOwned;
Expand Down Expand Up @@ -133,6 +133,9 @@ pub struct LayoutThreadData {

/// A queued response for the offset parent/rect of a node.
pub margin_style_response: MarginStyleResponse,

/// Scroll offsets of stacking contexts. This will only be populated if WebRender is in use.
pub stacking_context_scroll_offsets: ScrollOffsetMap,
}

/// Information needed by the layout thread.
Expand Down Expand Up @@ -472,6 +475,7 @@ impl LayoutThread {
resolved_style_response: None,
offset_parent_response: OffsetParentResponse::empty(),
margin_style_response: MarginStyleResponse::empty(),
stacking_context_scroll_offsets: HashMap::new(),
})),
error_reporter: CSSErrorReporter {
pipelineid: id,
Expand Down Expand Up @@ -1172,7 +1176,9 @@ impl LayoutThread {
let point = Point2D::new(Au::from_f32_px(point.x), Au::from_f32_px(point.y));
let result = match rw_data.display_list {
None => panic!("Tried to hit test with no display list"),
Some(ref dl) => dl.hit_test(point),
Some(ref display_list) => {
display_list.hit_test(&point, &rw_data.stacking_context_scroll_offsets)
}
};
rw_data.hit_test_response = if result.len() > 0 {
(Some(result[0]), update_cursor)
Expand Down Expand Up @@ -1273,14 +1279,26 @@ impl LayoutThread {
fn set_stacking_context_scroll_states<'a, 'b>(
&mut self,
new_scroll_states: Vec<StackingContextScrollState>,
_: &mut RwData<'a, 'b>) {
possibly_locked_rw_data: &mut RwData<'a, 'b>) {
let mut rw_data = possibly_locked_rw_data.lock();
let mut script_scroll_states = vec![];
let mut layout_scroll_states = HashMap::new();
for new_scroll_state in &new_scroll_states {
if self.root_flow.is_some() && new_scroll_state.stacking_context_id.id() == 0 {
let _ = self.script_chan.send(ConstellationControlMsg::SetScrollState(
self.id,
new_scroll_state.scroll_offset));
let offset = new_scroll_state.scroll_offset;
layout_scroll_states.insert(new_scroll_state.stacking_context_id, offset);

if new_scroll_state.stacking_context_id == StackingContextId::root() {
script_scroll_states.push((UntrustedNodeAddress::from_id(0), offset))
} else if !new_scroll_state.stacking_context_id.is_special() &&
new_scroll_state.stacking_context_id.fragment_type() ==
FragmentType::FragmentBody {
let id = new_scroll_state.stacking_context_id.id();
script_scroll_states.push((UntrustedNodeAddress::from_id(id), offset))
}
}
let _ = self.script_chan
.send(ConstellationControlMsg::SetScrollState(self.id, script_scroll_states));
rw_data.stacking_context_scroll_offsets = layout_scroll_states
}

fn tick_all_animations<'a, 'b>(&mut self, possibly_locked_rw_data: &mut RwData<'a, 'b>) {
Expand Down
4 changes: 3 additions & 1 deletion components/layout/query.rs
Expand Up @@ -91,7 +91,9 @@ impl LayoutRPC for LayoutRPCImpl {
let rw_data = rw_data.lock().unwrap();
let result = match rw_data.display_list {
None => panic!("Tried to hit test without a DisplayList"),
Some(ref display_list) => display_list.hit_test(point),
Some(ref display_list) => {
display_list.hit_test(&point, &rw_data.stacking_context_scroll_offsets)
}
};

result
Expand Down
2 changes: 2 additions & 0 deletions components/script/dom/bindings/trace.rs
Expand Up @@ -43,6 +43,7 @@ use dom::bindings::utils::WindowProxyHandler;
use encoding::types::EncodingRef;
use euclid::length::Length as EuclidLength;
use euclid::matrix2d::Matrix2D;
use euclid::point::Point2D;
use euclid::rect::Rect;
use euclid::size::Size2D;
use html5ever::tree_builder::QuirksMode;
Expand Down Expand Up @@ -279,6 +280,7 @@ no_jsmanaged_fields!(usize, u8, u16, u32, u64);
no_jsmanaged_fields!(isize, i8, i16, i32, i64);
no_jsmanaged_fields!(Sender<T>);
no_jsmanaged_fields!(Receiver<T>);
no_jsmanaged_fields!(Point2D<T>);
no_jsmanaged_fields!(Rect<T>);
no_jsmanaged_fields!(Size2D<T>);
no_jsmanaged_fields!(Arc<T>);
Expand Down
6 changes: 5 additions & 1 deletion components/script/dom/node.rs
Expand Up @@ -298,6 +298,10 @@ impl Node {
self.owner_doc().content_and_heritage_changed(self, NodeDamage::OtherNodeDamage);
child.owner_doc().content_and_heritage_changed(child, NodeDamage::OtherNodeDamage);
}

pub fn to_untrusted_node_address(&self) -> UntrustedNodeAddress {
UntrustedNodeAddress(self.reflector().get_jsobject().get() as *const c_void)
}
}

pub struct QuerySelectorIterator {
Expand Down Expand Up @@ -622,7 +626,7 @@ impl Node {
pub fn scroll_offset(&self) -> Point2D<f32> {
let document = self.owner_doc();
let window = document.window();
window.scroll_offset_query(self.to_trusted_node_address())
window.scroll_offset_query(self)
}

// https://dom.spec.whatwg.org/#dom-childnode-before
Expand Down
8 changes: 8 additions & 0 deletions components/script/dom/webidls/Element.webidl
Expand Up @@ -84,14 +84,22 @@ partial interface Element {
DOMRectList getClientRects();
DOMRect getBoundingClientRect();

[Func="::script_can_initiate_scroll"]
void scroll(optional ScrollToOptions options);
[Func="::script_can_initiate_scroll"]
void scroll(unrestricted double x, unrestricted double y);

[Func="::script_can_initiate_scroll"]
void scrollTo(optional ScrollToOptions options);
[Func="::script_can_initiate_scroll"]
void scrollTo(unrestricted double x, unrestricted double y);
[Func="::script_can_initiate_scroll"]
void scrollBy(optional ScrollToOptions options);
[Func="::script_can_initiate_scroll"]
void scrollBy(unrestricted double x, unrestricted double y);
[Func="::script_can_initiate_scroll"]
attribute unrestricted double scrollTop;
[Func="::script_can_initiate_scroll"]
attribute unrestricted double scrollLeft;
readonly attribute long scrollWidth;
readonly attribute long scrollHeight;
Expand Down
6 changes: 6 additions & 0 deletions components/script/dom/webidls/Window.webidl
Expand Up @@ -136,11 +136,17 @@ partial interface Window {
readonly attribute long pageXOffset;
readonly attribute long scrollY;
readonly attribute long pageYOffset;
[Func="::script_can_initiate_scroll"]
void scroll(optional ScrollToOptions options);
[Func="::script_can_initiate_scroll"]
void scroll(unrestricted double x, unrestricted double y);
[Func="::script_can_initiate_scroll"]
void scrollTo(optional ScrollToOptions options);
[Func="::script_can_initiate_scroll"]
void scrollTo(unrestricted double x, unrestricted double y);
[Func="::script_can_initiate_scroll"]
void scrollBy(optional ScrollToOptions options);
[Func="::script_can_initiate_scroll"]
void scrollBy(unrestricted double x, unrestricted double y);

// client
Expand Down

0 comments on commit 041cfe6

Please sign in to comment.