mirror of
https://github.com/servo/servo.git
synced 2025-08-05 05:30:08 +01:00
script: When using WebRender, keep the DOM-side scroll positions for
elements with `overflow: scroll` up to date, and take them into account when doing hit testing. Closes #11648.
This commit is contained in:
parent
ce88b8ed30
commit
041cfe6d0a
14 changed files with 259 additions and 47 deletions
|
@ -487,14 +487,15 @@ impl DisplayList {
|
||||||
/// Places all nodes containing the point of interest into `result`, topmost first. Respects
|
/// 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
|
/// 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.
|
/// 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 {
|
let mut traversal = DisplayListTraversal {
|
||||||
display_list: self,
|
display_list: self,
|
||||||
current_item_index: 0,
|
current_item_index: 0,
|
||||||
last_item_index: self.list.len() - 1,
|
last_item_index: self.list.len() - 1,
|
||||||
};
|
};
|
||||||
let mut result = Vec::new();
|
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.reverse();
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
@ -610,24 +611,38 @@ impl StackingContext {
|
||||||
|
|
||||||
pub fn hit_test<'a>(&self,
|
pub fn hit_test<'a>(&self,
|
||||||
traversal: &mut DisplayListTraversal<'a>,
|
traversal: &mut DisplayListTraversal<'a>,
|
||||||
point: Point2D<Au>,
|
point: &Point2D<Au>,
|
||||||
|
scroll_offsets: &ScrollOffsetMap,
|
||||||
result: &mut Vec<DisplayItemMetadata>) {
|
result: &mut Vec<DisplayItemMetadata>) {
|
||||||
// Convert the point into stacking context local space
|
// Convert the point into stacking context local transform space.
|
||||||
let point = if self.context_type == StackingContextType::Real {
|
let mut point = if self.context_type == StackingContextType::Real {
|
||||||
let point = point - self.bounds.origin;
|
let point = *point - self.bounds.origin;
|
||||||
let inv_transform = self.transform.invert();
|
let inv_transform = self.transform.invert();
|
||||||
let frac_point = inv_transform.transform_point(&Point2D::new(point.x.to_f32_px(),
|
let frac_point = inv_transform.transform_point(&Point2D::new(point.x.to_f32_px(),
|
||||||
point.y.to_f32_px()));
|
point.y.to_f32_px()));
|
||||||
Point2D::new(Au::from_f32_px(frac_point.x), Au::from_f32_px(frac_point.y))
|
Point2D::new(Au::from_f32_px(frac_point.x), Au::from_f32_px(frac_point.y))
|
||||||
} else {
|
} 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() {
|
for child in self.children.iter() {
|
||||||
while let Some(item) = traversal.advance(self) {
|
while let Some(item) = traversal.advance(self) {
|
||||||
item.hit_test(point, result);
|
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) {
|
while let Some(item) = traversal.advance(self) {
|
||||||
|
@ -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>>;
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,20 @@ use euclid::Matrix4D;
|
||||||
use euclid::rect::Rect;
|
use euclid::rect::Rect;
|
||||||
use msg::constellation_msg::PipelineId;
|
use msg::constellation_msg::PipelineId;
|
||||||
use std::fmt::{self, Debug, Formatter};
|
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)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum LayerKind {
|
pub enum LayerKind {
|
||||||
|
@ -159,15 +173,33 @@ pub struct StackingContextId(
|
||||||
);
|
);
|
||||||
|
|
||||||
impl StackingContextId {
|
impl StackingContextId {
|
||||||
#[inline(always)]
|
#[inline]
|
||||||
pub fn new(id: usize) -> StackingContextId {
|
pub fn new(id: usize) -> StackingContextId {
|
||||||
StackingContextId::new_of_type(id, FragmentType::FragmentBody)
|
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 {
|
pub fn new_of_type(id: usize, fragment_type: FragmentType) -> StackingContextId {
|
||||||
debug_assert_eq!(id & fragment_type as usize, 0);
|
debug_assert_eq!(id & (fragment_type as usize), 0);
|
||||||
StackingContextId(id | fragment_type as usize)
|
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]
|
#[inline]
|
||||||
|
@ -179,9 +211,28 @@ impl StackingContextId {
|
||||||
pub fn id(&self) -> usize {
|
pub fn id(&self) -> usize {
|
||||||
self.0 & !3
|
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)]
|
#[derive(Clone, Debug, PartialEq, Eq, Copy, Hash, Deserialize, Serialize, HeapSizeOf)]
|
||||||
pub enum FragmentType {
|
pub enum FragmentType {
|
||||||
/// A StackingContext for the fragment body itself.
|
/// A StackingContext for the fragment body itself.
|
||||||
|
|
|
@ -1686,13 +1686,18 @@ impl BlockFlowDisplayListBuilding for BlockFlow {
|
||||||
return parent_id;
|
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,
|
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;
|
self.base.stacking_context_id = stacking_context_id;
|
||||||
|
|
||||||
let inner_stacking_context_id = if self.has_scrolling_overflow() {
|
let inner_stacking_context_id = if has_scrolling_overflow {
|
||||||
StackingContextId::new_of_type(self.base.flow_id(), self.fragment.fragment_type())
|
StackingContextId::new_of_type(self.fragment.node.id() as usize,
|
||||||
|
self.fragment.fragment_type())
|
||||||
} else {
|
} else {
|
||||||
stacking_context_id
|
stacking_context_id
|
||||||
};
|
};
|
||||||
|
|
|
@ -21,13 +21,13 @@ use euclid::size::Size2D;
|
||||||
use flow::{self, Flow, ImmutableFlowUtils, MutableOwnedFlowUtils};
|
use flow::{self, Flow, ImmutableFlowUtils, MutableOwnedFlowUtils};
|
||||||
use flow_ref::{self, FlowRef};
|
use flow_ref::{self, FlowRef};
|
||||||
use fnv::FnvHasher;
|
use fnv::FnvHasher;
|
||||||
use gfx::display_list::{ClippingRegion, DisplayItemMetadata, DisplayList, LayerInfo};
|
use gfx::display_list::{ClippingRegion, DisplayItemMetadata, DisplayList, LayerInfo, OpaqueNode};
|
||||||
use gfx::display_list::{OpaqueNode, StackingContext, StackingContextType, WebRenderImageInfo};
|
use gfx::display_list::{ScrollOffsetMap, StackingContext, StackingContextType, WebRenderImageInfo};
|
||||||
use gfx::font;
|
use gfx::font;
|
||||||
use gfx::font_cache_thread::FontCacheThread;
|
use gfx::font_cache_thread::FontCacheThread;
|
||||||
use gfx::font_context;
|
use gfx::font_context;
|
||||||
use gfx::paint_thread::LayoutToPaintMsg;
|
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 heapsize::HeapSizeOf;
|
||||||
use incremental::LayoutDamageComputation;
|
use incremental::LayoutDamageComputation;
|
||||||
use incremental::{REPAINT, STORE_OVERFLOW, REFLOW_OUT_OF_FLOW, REFLOW, REFLOW_ENTIRE_DOCUMENT};
|
use incremental::{REPAINT, STORE_OVERFLOW, REFLOW_OUT_OF_FLOW, REFLOW, REFLOW_ENTIRE_DOCUMENT};
|
||||||
|
@ -51,8 +51,8 @@ use script::layout_interface::OpaqueStyleAndLayoutData;
|
||||||
use script::layout_interface::{LayoutRPC, OffsetParentResponse, NodeOverflowResponse, MarginStyleResponse};
|
use script::layout_interface::{LayoutRPC, OffsetParentResponse, NodeOverflowResponse, MarginStyleResponse};
|
||||||
use script::layout_interface::{Msg, NewLayoutThreadInfo, Reflow, ReflowQueryType, ScriptReflow};
|
use script::layout_interface::{Msg, NewLayoutThreadInfo, Reflow, ReflowQueryType, ScriptReflow};
|
||||||
use script::reporter::CSSErrorReporter;
|
use script::reporter::CSSErrorReporter;
|
||||||
use script_traits::StackingContextScrollState;
|
|
||||||
use script_traits::{ConstellationControlMsg, LayoutControlMsg, LayoutMsg as ConstellationMsg};
|
use script_traits::{ConstellationControlMsg, LayoutControlMsg, LayoutMsg as ConstellationMsg};
|
||||||
|
use script_traits::{StackingContextScrollState, UntrustedNodeAddress};
|
||||||
use sequential;
|
use sequential;
|
||||||
use serde_json;
|
use serde_json;
|
||||||
use std::borrow::ToOwned;
|
use std::borrow::ToOwned;
|
||||||
|
@ -133,6 +133,9 @@ pub struct LayoutThreadData {
|
||||||
|
|
||||||
/// A queued response for the offset parent/rect of a node.
|
/// A queued response for the offset parent/rect of a node.
|
||||||
pub margin_style_response: MarginStyleResponse,
|
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.
|
/// Information needed by the layout thread.
|
||||||
|
@ -472,6 +475,7 @@ impl LayoutThread {
|
||||||
resolved_style_response: None,
|
resolved_style_response: None,
|
||||||
offset_parent_response: OffsetParentResponse::empty(),
|
offset_parent_response: OffsetParentResponse::empty(),
|
||||||
margin_style_response: MarginStyleResponse::empty(),
|
margin_style_response: MarginStyleResponse::empty(),
|
||||||
|
stacking_context_scroll_offsets: HashMap::new(),
|
||||||
})),
|
})),
|
||||||
error_reporter: CSSErrorReporter {
|
error_reporter: CSSErrorReporter {
|
||||||
pipelineid: id,
|
pipelineid: id,
|
||||||
|
@ -1172,7 +1176,9 @@ impl LayoutThread {
|
||||||
let point = Point2D::new(Au::from_f32_px(point.x), Au::from_f32_px(point.y));
|
let point = Point2D::new(Au::from_f32_px(point.x), Au::from_f32_px(point.y));
|
||||||
let result = match rw_data.display_list {
|
let result = match rw_data.display_list {
|
||||||
None => panic!("Tried to hit test with no 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 {
|
rw_data.hit_test_response = if result.len() > 0 {
|
||||||
(Some(result[0]), update_cursor)
|
(Some(result[0]), update_cursor)
|
||||||
|
@ -1273,14 +1279,26 @@ impl LayoutThread {
|
||||||
fn set_stacking_context_scroll_states<'a, 'b>(
|
fn set_stacking_context_scroll_states<'a, 'b>(
|
||||||
&mut self,
|
&mut self,
|
||||||
new_scroll_states: Vec<StackingContextScrollState>,
|
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 {
|
for new_scroll_state in &new_scroll_states {
|
||||||
if self.root_flow.is_some() && new_scroll_state.stacking_context_id.id() == 0 {
|
let offset = new_scroll_state.scroll_offset;
|
||||||
let _ = self.script_chan.send(ConstellationControlMsg::SetScrollState(
|
layout_scroll_states.insert(new_scroll_state.stacking_context_id, offset);
|
||||||
self.id,
|
|
||||||
new_scroll_state.scroll_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>) {
|
fn tick_all_animations<'a, 'b>(&mut self, possibly_locked_rw_data: &mut RwData<'a, 'b>) {
|
||||||
|
|
|
@ -91,7 +91,9 @@ impl LayoutRPC for LayoutRPCImpl {
|
||||||
let rw_data = rw_data.lock().unwrap();
|
let rw_data = rw_data.lock().unwrap();
|
||||||
let result = match rw_data.display_list {
|
let result = match rw_data.display_list {
|
||||||
None => panic!("Tried to hit test without a DisplayList"),
|
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
|
result
|
||||||
|
|
|
@ -43,6 +43,7 @@ use dom::bindings::utils::WindowProxyHandler;
|
||||||
use encoding::types::EncodingRef;
|
use encoding::types::EncodingRef;
|
||||||
use euclid::length::Length as EuclidLength;
|
use euclid::length::Length as EuclidLength;
|
||||||
use euclid::matrix2d::Matrix2D;
|
use euclid::matrix2d::Matrix2D;
|
||||||
|
use euclid::point::Point2D;
|
||||||
use euclid::rect::Rect;
|
use euclid::rect::Rect;
|
||||||
use euclid::size::Size2D;
|
use euclid::size::Size2D;
|
||||||
use html5ever::tree_builder::QuirksMode;
|
use html5ever::tree_builder::QuirksMode;
|
||||||
|
@ -279,6 +280,7 @@ no_jsmanaged_fields!(usize, u8, u16, u32, u64);
|
||||||
no_jsmanaged_fields!(isize, i8, i16, i32, i64);
|
no_jsmanaged_fields!(isize, i8, i16, i32, i64);
|
||||||
no_jsmanaged_fields!(Sender<T>);
|
no_jsmanaged_fields!(Sender<T>);
|
||||||
no_jsmanaged_fields!(Receiver<T>);
|
no_jsmanaged_fields!(Receiver<T>);
|
||||||
|
no_jsmanaged_fields!(Point2D<T>);
|
||||||
no_jsmanaged_fields!(Rect<T>);
|
no_jsmanaged_fields!(Rect<T>);
|
||||||
no_jsmanaged_fields!(Size2D<T>);
|
no_jsmanaged_fields!(Size2D<T>);
|
||||||
no_jsmanaged_fields!(Arc<T>);
|
no_jsmanaged_fields!(Arc<T>);
|
||||||
|
|
|
@ -298,6 +298,10 @@ impl Node {
|
||||||
self.owner_doc().content_and_heritage_changed(self, NodeDamage::OtherNodeDamage);
|
self.owner_doc().content_and_heritage_changed(self, NodeDamage::OtherNodeDamage);
|
||||||
child.owner_doc().content_and_heritage_changed(child, 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 {
|
pub struct QuerySelectorIterator {
|
||||||
|
@ -622,7 +626,7 @@ impl Node {
|
||||||
pub fn scroll_offset(&self) -> Point2D<f32> {
|
pub fn scroll_offset(&self) -> Point2D<f32> {
|
||||||
let document = self.owner_doc();
|
let document = self.owner_doc();
|
||||||
let window = document.window();
|
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
|
// https://dom.spec.whatwg.org/#dom-childnode-before
|
||||||
|
|
|
@ -84,14 +84,22 @@ partial interface Element {
|
||||||
DOMRectList getClientRects();
|
DOMRectList getClientRects();
|
||||||
DOMRect getBoundingClientRect();
|
DOMRect getBoundingClientRect();
|
||||||
|
|
||||||
|
[Func="::script_can_initiate_scroll"]
|
||||||
void scroll(optional ScrollToOptions options);
|
void scroll(optional ScrollToOptions options);
|
||||||
|
[Func="::script_can_initiate_scroll"]
|
||||||
void scroll(unrestricted double x, unrestricted double y);
|
void scroll(unrestricted double x, unrestricted double y);
|
||||||
|
|
||||||
|
[Func="::script_can_initiate_scroll"]
|
||||||
void scrollTo(optional ScrollToOptions options);
|
void scrollTo(optional ScrollToOptions options);
|
||||||
|
[Func="::script_can_initiate_scroll"]
|
||||||
void scrollTo(unrestricted double x, unrestricted double y);
|
void scrollTo(unrestricted double x, unrestricted double y);
|
||||||
|
[Func="::script_can_initiate_scroll"]
|
||||||
void scrollBy(optional ScrollToOptions options);
|
void scrollBy(optional ScrollToOptions options);
|
||||||
|
[Func="::script_can_initiate_scroll"]
|
||||||
void scrollBy(unrestricted double x, unrestricted double y);
|
void scrollBy(unrestricted double x, unrestricted double y);
|
||||||
|
[Func="::script_can_initiate_scroll"]
|
||||||
attribute unrestricted double scrollTop;
|
attribute unrestricted double scrollTop;
|
||||||
|
[Func="::script_can_initiate_scroll"]
|
||||||
attribute unrestricted double scrollLeft;
|
attribute unrestricted double scrollLeft;
|
||||||
readonly attribute long scrollWidth;
|
readonly attribute long scrollWidth;
|
||||||
readonly attribute long scrollHeight;
|
readonly attribute long scrollHeight;
|
||||||
|
|
|
@ -136,11 +136,17 @@ partial interface Window {
|
||||||
readonly attribute long pageXOffset;
|
readonly attribute long pageXOffset;
|
||||||
readonly attribute long scrollY;
|
readonly attribute long scrollY;
|
||||||
readonly attribute long pageYOffset;
|
readonly attribute long pageYOffset;
|
||||||
|
[Func="::script_can_initiate_scroll"]
|
||||||
void scroll(optional ScrollToOptions options);
|
void scroll(optional ScrollToOptions options);
|
||||||
|
[Func="::script_can_initiate_scroll"]
|
||||||
void scroll(unrestricted double x, unrestricted double y);
|
void scroll(unrestricted double x, unrestricted double y);
|
||||||
|
[Func="::script_can_initiate_scroll"]
|
||||||
void scrollTo(optional ScrollToOptions options);
|
void scrollTo(optional ScrollToOptions options);
|
||||||
|
[Func="::script_can_initiate_scroll"]
|
||||||
void scrollTo(unrestricted double x, unrestricted double y);
|
void scrollTo(unrestricted double x, unrestricted double y);
|
||||||
|
[Func="::script_can_initiate_scroll"]
|
||||||
void scrollBy(optional ScrollToOptions options);
|
void scrollBy(optional ScrollToOptions options);
|
||||||
|
[Func="::script_can_initiate_scroll"]
|
||||||
void scrollBy(unrestricted double x, unrestricted double y);
|
void scrollBy(unrestricted double x, unrestricted double y);
|
||||||
|
|
||||||
// client
|
// client
|
||||||
|
|
|
@ -12,6 +12,7 @@ use dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull;
|
||||||
use dom::bindings::codegen::Bindings::EventHandlerBinding::OnBeforeUnloadEventHandlerNonNull;
|
use dom::bindings::codegen::Bindings::EventHandlerBinding::OnBeforeUnloadEventHandlerNonNull;
|
||||||
use dom::bindings::codegen::Bindings::EventHandlerBinding::OnErrorEventHandlerNonNull;
|
use dom::bindings::codegen::Bindings::EventHandlerBinding::OnErrorEventHandlerNonNull;
|
||||||
use dom::bindings::codegen::Bindings::FunctionBinding::Function;
|
use dom::bindings::codegen::Bindings::FunctionBinding::Function;
|
||||||
|
use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
|
||||||
use dom::bindings::codegen::Bindings::WindowBinding::{ScrollBehavior, ScrollToOptions};
|
use dom::bindings::codegen::Bindings::WindowBinding::{ScrollBehavior, ScrollToOptions};
|
||||||
use dom::bindings::codegen::Bindings::WindowBinding::{self, FrameRequestCallback, WindowMethods};
|
use dom::bindings::codegen::Bindings::WindowBinding::{self, FrameRequestCallback, WindowMethods};
|
||||||
use dom::bindings::error::{Error, ErrorResult, Fallible, report_pending_exception};
|
use dom::bindings::error::{Error, ErrorResult, Fallible, report_pending_exception};
|
||||||
|
@ -68,7 +69,7 @@ use script_traits::{ScriptMsg as ConstellationMsg, TimerEventRequest, TimerSourc
|
||||||
use std::ascii::AsciiExt;
|
use std::ascii::AsciiExt;
|
||||||
use std::borrow::ToOwned;
|
use std::borrow::ToOwned;
|
||||||
use std::cell::Cell;
|
use std::cell::Cell;
|
||||||
use std::collections::HashSet;
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::default::Default;
|
use std::default::Default;
|
||||||
use std::ffi::CString;
|
use std::ffi::CString;
|
||||||
use std::io::{Write, stderr, stdout};
|
use std::io::{Write, stderr, stdout};
|
||||||
|
@ -264,6 +265,9 @@ pub struct Window {
|
||||||
|
|
||||||
error_reporter: CSSErrorReporter,
|
error_reporter: CSSErrorReporter,
|
||||||
|
|
||||||
|
/// A list of scroll offsets for each scrollable element.
|
||||||
|
scroll_offsets: DOMRefCell<HashMap<UntrustedNodeAddress, Point2D<f32>>>,
|
||||||
|
|
||||||
#[ignore_heap_size_of = "Defined in ipc-channel"]
|
#[ignore_heap_size_of = "Defined in ipc-channel"]
|
||||||
panic_chan: IpcSender<PanicMsg>,
|
panic_chan: IpcSender<PanicMsg>,
|
||||||
}
|
}
|
||||||
|
@ -354,6 +358,13 @@ impl Window {
|
||||||
pub fn css_error_reporter(&self) -> Box<ParseErrorReporter + Send> {
|
pub fn css_error_reporter(&self) -> Box<ParseErrorReporter + Send> {
|
||||||
self.error_reporter.clone()
|
self.error_reporter.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets a new list of scroll offsets.
|
||||||
|
///
|
||||||
|
/// This is called when layout gives us new ones and WebRender is in use.
|
||||||
|
pub fn set_scroll_offsets(&self, offsets: HashMap<UntrustedNodeAddress, Point2D<f32>>) {
|
||||||
|
*self.scroll_offsets.borrow_mut() = offsets
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(target_os = "macos", target_os = "linux"))]
|
#[cfg(any(target_os = "macos", target_os = "linux"))]
|
||||||
|
@ -1243,7 +1254,28 @@ impl Window {
|
||||||
self.layout_rpc.node_overflow().0.unwrap()
|
self.layout_rpc.node_overflow().0.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn scroll_offset_query(&self, node: TrustedNodeAddress) -> Point2D<f32> {
|
pub fn scroll_offset_query(&self, node: &Node) -> Point2D<f32> {
|
||||||
|
// WebRender always keeps the scroll offsets up to date and stored here in the window. So,
|
||||||
|
// if WR is in use, all we need to do is to check our list of scroll offsets and return the
|
||||||
|
// result.
|
||||||
|
if opts::get().use_webrender {
|
||||||
|
let mut node = Root::from_ref(node);
|
||||||
|
loop {
|
||||||
|
if let Some(scroll_offset) = self.scroll_offsets
|
||||||
|
.borrow()
|
||||||
|
.get(&node.to_untrusted_node_address()) {
|
||||||
|
return *scroll_offset
|
||||||
|
}
|
||||||
|
node = match node.GetParentNode() {
|
||||||
|
Some(node) => node,
|
||||||
|
None => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let offset = self.current_viewport.get().origin;
|
||||||
|
return Point2D::new(offset.x.to_f32_px(), offset.y.to_f32_px())
|
||||||
|
}
|
||||||
|
|
||||||
|
let node = node.to_trusted_node_address();
|
||||||
if !self.reflow(ReflowGoal::ForScriptQuery,
|
if !self.reflow(ReflowGoal::ForScriptQuery,
|
||||||
ReflowQueryType::NodeLayerIdQuery(node),
|
ReflowQueryType::NodeLayerIdQuery(node),
|
||||||
ReflowReason::Query) {
|
ReflowReason::Query) {
|
||||||
|
@ -1642,6 +1674,7 @@ impl Window {
|
||||||
webdriver_script_chan: DOMRefCell::new(None),
|
webdriver_script_chan: DOMRefCell::new(None),
|
||||||
ignore_further_async_events: Arc::new(AtomicBool::new(false)),
|
ignore_further_async_events: Arc::new(AtomicBool::new(false)),
|
||||||
error_reporter: error_reporter,
|
error_reporter: error_reporter,
|
||||||
|
scroll_offsets: DOMRefCell::new(HashMap::new()),
|
||||||
panic_chan: panic_chan,
|
panic_chan: panic_chan,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -112,8 +112,9 @@ mod unpremultiplytable;
|
||||||
mod webdriver_handlers;
|
mod webdriver_handlers;
|
||||||
|
|
||||||
use dom::bindings::codegen::RegisterBindings;
|
use dom::bindings::codegen::RegisterBindings;
|
||||||
use js::jsapi::SetDOMProxyInformation;
|
use js::jsapi::{Handle, JSContext, JSObject, SetDOMProxyInformation};
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
|
use util::opts;
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
#[allow(unsafe_code)]
|
#[allow(unsafe_code)]
|
||||||
|
@ -168,3 +169,14 @@ pub fn init() {
|
||||||
|
|
||||||
perform_platform_specific_initialization();
|
perform_platform_specific_initialization();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// FIXME(pcwalton): Currently WebRender cannot handle DOM-initiated scrolls. Remove this when it
|
||||||
|
/// can. See PR #11680 for details.
|
||||||
|
///
|
||||||
|
/// This function is only marked `unsafe` because the `[Func=foo]` WebIDL attribute requires it. It
|
||||||
|
/// shouldn't actually do anything unsafe.
|
||||||
|
#[allow(unsafe_code)]
|
||||||
|
pub unsafe fn script_can_initiate_scroll(_: *mut JSContext, _: Handle<*mut JSObject>) -> bool {
|
||||||
|
!opts::get().use_webrender
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -85,11 +85,12 @@ use script_traits::{CompositorEvent, ConstellationControlMsg, EventResult};
|
||||||
use script_traits::{InitialScriptState, MouseButton, MouseEventType, MozBrowserEvent};
|
use script_traits::{InitialScriptState, MouseButton, MouseEventType, MozBrowserEvent};
|
||||||
use script_traits::{NewLayoutInfo, ScriptMsg as ConstellationMsg};
|
use script_traits::{NewLayoutInfo, ScriptMsg as ConstellationMsg};
|
||||||
use script_traits::{ScriptThreadFactory, TimerEvent, TimerEventRequest, TimerSource};
|
use script_traits::{ScriptThreadFactory, TimerEvent, TimerEventRequest, TimerSource};
|
||||||
use script_traits::{TouchEventType, TouchId};
|
use script_traits::{TouchEventType, TouchId, UntrustedNodeAddress};
|
||||||
use std::borrow::ToOwned;
|
use std::borrow::ToOwned;
|
||||||
use std::cell::{Cell, RefCell};
|
use std::cell::{Cell, RefCell};
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::option::Option;
|
use std::option::Option;
|
||||||
|
use std::ptr;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::result::Result;
|
use std::result::Result;
|
||||||
use std::sync::atomic::{Ordering, AtomicBool};
|
use std::sync::atomic::{Ordering, AtomicBool};
|
||||||
|
@ -728,9 +729,9 @@ impl ScriptThread {
|
||||||
self.handle_viewport(id, rect);
|
self.handle_viewport(id, rect);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
FromConstellation(ConstellationControlMsg::SetScrollState(id, scroll_offset)) => {
|
FromConstellation(ConstellationControlMsg::SetScrollState(id, scroll_state)) => {
|
||||||
self.profile_event(ScriptThreadEventCategory::SetScrollState, || {
|
self.profile_event(ScriptThreadEventCategory::SetScrollState, || {
|
||||||
self.handle_set_scroll_state(id, &scroll_offset);
|
self.handle_set_scroll_state(id, &scroll_state);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
FromConstellation(ConstellationControlMsg::TickAllAnimations(
|
FromConstellation(ConstellationControlMsg::TickAllAnimations(
|
||||||
|
@ -1110,17 +1111,31 @@ impl ScriptThread {
|
||||||
panic!("Page rect message sent to nonexistent pipeline");
|
panic!("Page rect message sent to nonexistent pipeline");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_set_scroll_state(&self, id: PipelineId, scroll_state: &Point2D<f32>) {
|
fn handle_set_scroll_state(&self,
|
||||||
let context = self.browsing_context.get();
|
id: PipelineId,
|
||||||
if let Some(context) = context {
|
scroll_states: &[(UntrustedNodeAddress, Point2D<f32>)]) {
|
||||||
if let Some(inner_context) = context.find(id) {
|
let window = match self.browsing_context.get() {
|
||||||
let window = inner_context.active_window();
|
Some(context) => {
|
||||||
window.update_viewport_for_scroll(-scroll_state.x, -scroll_state.y);
|
match context.find(id) {
|
||||||
return
|
Some(inner_context) => inner_context.active_window(),
|
||||||
|
None => {
|
||||||
|
panic!("Set scroll state message sent to nonexistent pipeline: {:?}", id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
None => panic!("Set scroll state message sent to nonexistent pipeline: {:?}", id),
|
||||||
|
};
|
||||||
|
|
||||||
panic!("Set scroll state message message sent to nonexistent pipeline: {:?}", id);
|
let mut scroll_offsets = HashMap::new();
|
||||||
|
for &(node_address, ref scroll_offset) in scroll_states {
|
||||||
|
if node_address == UntrustedNodeAddress(ptr::null()) {
|
||||||
|
window.update_viewport_for_scroll(-scroll_offset.x, -scroll_offset.y);
|
||||||
|
} else {
|
||||||
|
scroll_offsets.insert(node_address,
|
||||||
|
Point2D::new(-scroll_offset.x, -scroll_offset.y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
window.set_scroll_offsets(scroll_offsets)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_new_layout(&self, new_layout_info: NewLayoutInfo) {
|
fn handle_new_layout(&self, new_layout_info: NewLayoutInfo) {
|
||||||
|
|
|
@ -40,6 +40,7 @@ use euclid::rect::Rect;
|
||||||
use gfx_traits::Epoch;
|
use gfx_traits::Epoch;
|
||||||
use gfx_traits::LayerId;
|
use gfx_traits::LayerId;
|
||||||
use gfx_traits::StackingContextId;
|
use gfx_traits::StackingContextId;
|
||||||
|
use heapsize::HeapSizeOf;
|
||||||
use ipc_channel::ipc::{IpcReceiver, IpcSender};
|
use ipc_channel::ipc::{IpcReceiver, IpcSender};
|
||||||
use libc::c_void;
|
use libc::c_void;
|
||||||
use msg::constellation_msg::{FrameId, FrameType, Key, KeyModifiers, KeyState, LoadData};
|
use msg::constellation_msg::{FrameId, FrameType, Key, KeyModifiers, KeyState, LoadData};
|
||||||
|
@ -52,6 +53,7 @@ use net_traits::bluetooth_thread::BluetoothMethodMsg;
|
||||||
use net_traits::image_cache_thread::ImageCacheThread;
|
use net_traits::image_cache_thread::ImageCacheThread;
|
||||||
use net_traits::response::HttpsState;
|
use net_traits::response::HttpsState;
|
||||||
use profile_traits::mem;
|
use profile_traits::mem;
|
||||||
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::mpsc::{Sender, Receiver};
|
use std::sync::mpsc::{Sender, Receiver};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
@ -61,11 +63,39 @@ pub use script_msg::{LayoutMsg, ScriptMsg, EventResult};
|
||||||
|
|
||||||
/// The address of a node. Layout sends these back. They must be validated via
|
/// The address of a node. Layout sends these back. They must be validated via
|
||||||
/// `from_untrusted_node_address` before they can be used, because we do not trust layout.
|
/// `from_untrusted_node_address` before they can be used, because we do not trust layout.
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
pub struct UntrustedNodeAddress(pub *const c_void);
|
pub struct UntrustedNodeAddress(pub *const c_void);
|
||||||
|
|
||||||
|
impl HeapSizeOf for UntrustedNodeAddress {
|
||||||
|
fn heap_size_of_children(&self) -> usize {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(unsafe_code)]
|
#[allow(unsafe_code)]
|
||||||
unsafe impl Send for UntrustedNodeAddress {}
|
unsafe impl Send for UntrustedNodeAddress {}
|
||||||
|
|
||||||
|
impl Serialize for UntrustedNodeAddress {
|
||||||
|
fn serialize<S: Serializer>(&self, s: &mut S) -> Result<(), S::Error> {
|
||||||
|
(self.0 as usize).serialize(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deserialize for UntrustedNodeAddress {
|
||||||
|
fn deserialize<D: Deserializer>(d: &mut D) -> Result<UntrustedNodeAddress, D::Error> {
|
||||||
|
let value: usize = try!(Deserialize::deserialize(d));
|
||||||
|
Ok(UntrustedNodeAddress::from_id(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UntrustedNodeAddress {
|
||||||
|
/// Creates an `UntrustedNodeAddress` from the given pointer address value.
|
||||||
|
#[inline]
|
||||||
|
pub fn from_id(id: usize) -> UntrustedNodeAddress {
|
||||||
|
UntrustedNodeAddress(id as *const c_void)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Messages sent to the layout thread from the constellation and/or compositor.
|
/// Messages sent to the layout thread from the constellation and/or compositor.
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Deserialize, Serialize)]
|
||||||
pub enum LayoutControlMsg {
|
pub enum LayoutControlMsg {
|
||||||
|
@ -125,8 +155,8 @@ pub enum ConstellationControlMsg {
|
||||||
SendEvent(PipelineId, CompositorEvent),
|
SendEvent(PipelineId, CompositorEvent),
|
||||||
/// Notifies script of the viewport.
|
/// Notifies script of the viewport.
|
||||||
Viewport(PipelineId, Rect<f32>),
|
Viewport(PipelineId, Rect<f32>),
|
||||||
/// Notifies script of a new scroll offset.
|
/// Notifies script of a new set of scroll offsets.
|
||||||
SetScrollState(PipelineId, Point2D<f32>),
|
SetScrollState(PipelineId, Vec<(UntrustedNodeAddress, Point2D<f32>)>),
|
||||||
/// Requests that the script thread immediately send the constellation the title of a pipeline.
|
/// Requests that the script thread immediately send the constellation the title of a pipeline.
|
||||||
GetTitle(PipelineId),
|
GetTitle(PipelineId),
|
||||||
/// Notifies script thread to suspend all its timers
|
/// Notifies script thread to suspend all its timers
|
||||||
|
|
7
tests/html/hit_test_overflow_scroll.html
Normal file
7
tests/html/hit_test_overflow_scroll.html
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html style="overflow: scroll">
|
||||||
|
<body style="overflow: hidden; position: relative;">
|
||||||
|
<p>Scroll down...</p>
|
||||||
|
<p style="padding-top: 1200px"><a href="http://example.com">Mouse over me!</a></p>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Add table
Add a link
Reference in a new issue