From 041cfe6d0a07882819ae35380c286e7fe09c1013 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Wed, 8 Jun 2016 18:46:02 -0700 Subject: [PATCH] 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. --- components/gfx/display_list/mod.rs | 35 ++++++++--- components/gfx_traits/lib.rs | 61 ++++++++++++++++++-- components/layout/display_list_builder.rs | 13 +++-- components/layout/layout_thread.rs | 38 ++++++++---- components/layout/query.rs | 4 +- components/script/dom/bindings/trace.rs | 2 + components/script/dom/node.rs | 6 +- components/script/dom/webidls/Element.webidl | 8 +++ components/script/dom/webidls/Window.webidl | 6 ++ components/script/dom/window.rs | 37 +++++++++++- components/script/lib.rs | 14 ++++- components/script/script_thread.rs | 39 +++++++++---- components/script_traits/lib.rs | 36 +++++++++++- tests/html/hit_test_overflow_scroll.html | 7 +++ 14 files changed, 259 insertions(+), 47 deletions(-) create mode 100644 tests/html/hit_test_overflow_scroll.html diff --git a/components/gfx/display_list/mod.rs b/components/gfx/display_list/mod.rs index f60a5245cb11..1ec5aaf0d14a 100644 --- a/components/gfx/display_list/mod.rs +++ b/components/gfx/display_list/mod.rs @@ -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) -> Vec { + pub fn hit_test(&self, point: &Point2D, scroll_offsets: &ScrollOffsetMap) + -> Vec { 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 } @@ -610,24 +611,38 @@ impl StackingContext { pub fn hit_test<'a>(&self, traversal: &mut DisplayListTraversal<'a>, - point: Point2D, + point: &Point2D, + scroll_offsets: &ScrollOffsetMap, result: &mut Vec) { - // 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) { @@ -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>; + diff --git a/components/gfx_traits/lib.rs b/components/gfx_traits/lib.rs index c1a8155a7989..eb832c1c7883 100644 --- a/components/gfx_traits/lib.rs +++ b/components/gfx_traits/lib.rs @@ -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 { @@ -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] @@ -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. diff --git a/components/layout/display_list_builder.rs b/components/layout/display_list_builder.rs index c5e32ab77827..945968446846 100644 --- a/components/layout/display_list_builder.rs +++ b/components/layout/display_list_builder.rs @@ -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 }; diff --git a/components/layout/layout_thread.rs b/components/layout/layout_thread.rs index e858ac5fcee8..bedc72791c6d 100644 --- a/components/layout/layout_thread.rs +++ b/components/layout/layout_thread.rs @@ -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}; @@ -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; @@ -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. @@ -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, @@ -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) @@ -1273,14 +1279,26 @@ impl LayoutThread { fn set_stacking_context_scroll_states<'a, 'b>( &mut self, new_scroll_states: Vec, - _: &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>) { diff --git a/components/layout/query.rs b/components/layout/query.rs index 7f2b7a44fb22..21b0998b1d81 100644 --- a/components/layout/query.rs +++ b/components/layout/query.rs @@ -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 diff --git a/components/script/dom/bindings/trace.rs b/components/script/dom/bindings/trace.rs index 2a9f0e521346..34ec7d19e140 100644 --- a/components/script/dom/bindings/trace.rs +++ b/components/script/dom/bindings/trace.rs @@ -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; @@ -279,6 +280,7 @@ no_jsmanaged_fields!(usize, u8, u16, u32, u64); no_jsmanaged_fields!(isize, i8, i16, i32, i64); no_jsmanaged_fields!(Sender); no_jsmanaged_fields!(Receiver); +no_jsmanaged_fields!(Point2D); no_jsmanaged_fields!(Rect); no_jsmanaged_fields!(Size2D); no_jsmanaged_fields!(Arc); diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs index 3032abe0685f..a3289cca56eb 100644 --- a/components/script/dom/node.rs +++ b/components/script/dom/node.rs @@ -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 { @@ -622,7 +626,7 @@ impl Node { pub fn scroll_offset(&self) -> Point2D { 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 diff --git a/components/script/dom/webidls/Element.webidl b/components/script/dom/webidls/Element.webidl index 48aeed7fbbb0..d09f4846c984 100644 --- a/components/script/dom/webidls/Element.webidl +++ b/components/script/dom/webidls/Element.webidl @@ -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; diff --git a/components/script/dom/webidls/Window.webidl b/components/script/dom/webidls/Window.webidl index fcac82ea0c57..1bf0f7f2c50f 100644 --- a/components/script/dom/webidls/Window.webidl +++ b/components/script/dom/webidls/Window.webidl @@ -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 diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs index 4bfbb1993d13..34f7c1acdb0e 100644 --- a/components/script/dom/window.rs +++ b/components/script/dom/window.rs @@ -12,6 +12,7 @@ use dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull; use dom::bindings::codegen::Bindings::EventHandlerBinding::OnBeforeUnloadEventHandlerNonNull; use dom::bindings::codegen::Bindings::EventHandlerBinding::OnErrorEventHandlerNonNull; 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::{self, FrameRequestCallback, WindowMethods}; 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::borrow::ToOwned; use std::cell::Cell; -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use std::default::Default; use std::ffi::CString; use std::io::{Write, stderr, stdout}; @@ -264,6 +265,9 @@ pub struct Window { error_reporter: CSSErrorReporter, + /// A list of scroll offsets for each scrollable element. + scroll_offsets: DOMRefCell>>, + #[ignore_heap_size_of = "Defined in ipc-channel"] panic_chan: IpcSender, } @@ -354,6 +358,13 @@ impl Window { pub fn css_error_reporter(&self) -> Box { 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>) { + *self.scroll_offsets.borrow_mut() = offsets + } } #[cfg(any(target_os = "macos", target_os = "linux"))] @@ -1243,7 +1254,28 @@ impl Window { self.layout_rpc.node_overflow().0.unwrap() } - pub fn scroll_offset_query(&self, node: TrustedNodeAddress) -> Point2D { + pub fn scroll_offset_query(&self, node: &Node) -> Point2D { + // 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, ReflowQueryType::NodeLayerIdQuery(node), ReflowReason::Query) { @@ -1642,6 +1674,7 @@ impl Window { webdriver_script_chan: DOMRefCell::new(None), ignore_further_async_events: Arc::new(AtomicBool::new(false)), error_reporter: error_reporter, + scroll_offsets: DOMRefCell::new(HashMap::new()), panic_chan: panic_chan, }; diff --git a/components/script/lib.rs b/components/script/lib.rs index 50b0c47ac6b8..71e53141d9ac 100644 --- a/components/script/lib.rs +++ b/components/script/lib.rs @@ -112,8 +112,9 @@ mod unpremultiplytable; mod webdriver_handlers; use dom::bindings::codegen::RegisterBindings; -use js::jsapi::SetDOMProxyInformation; +use js::jsapi::{Handle, JSContext, JSObject, SetDOMProxyInformation}; use std::ptr; +use util::opts; #[cfg(target_os = "linux")] #[allow(unsafe_code)] @@ -168,3 +169,14 @@ pub fn init() { 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 +} + diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index 51460cd4bb94..7684eb9e3361 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -85,11 +85,12 @@ use script_traits::{CompositorEvent, ConstellationControlMsg, EventResult}; use script_traits::{InitialScriptState, MouseButton, MouseEventType, MozBrowserEvent}; use script_traits::{NewLayoutInfo, ScriptMsg as ConstellationMsg}; use script_traits::{ScriptThreadFactory, TimerEvent, TimerEventRequest, TimerSource}; -use script_traits::{TouchEventType, TouchId}; +use script_traits::{TouchEventType, TouchId, UntrustedNodeAddress}; use std::borrow::ToOwned; use std::cell::{Cell, RefCell}; use std::collections::{HashMap, HashSet}; use std::option::Option; +use std::ptr; use std::rc::Rc; use std::result::Result; use std::sync::atomic::{Ordering, AtomicBool}; @@ -728,9 +729,9 @@ impl ScriptThread { self.handle_viewport(id, rect); }) } - FromConstellation(ConstellationControlMsg::SetScrollState(id, scroll_offset)) => { + FromConstellation(ConstellationControlMsg::SetScrollState(id, scroll_state)) => { self.profile_event(ScriptThreadEventCategory::SetScrollState, || { - self.handle_set_scroll_state(id, &scroll_offset); + self.handle_set_scroll_state(id, &scroll_state); }) } FromConstellation(ConstellationControlMsg::TickAllAnimations( @@ -1110,17 +1111,31 @@ impl ScriptThread { panic!("Page rect message sent to nonexistent pipeline"); } - fn handle_set_scroll_state(&self, id: PipelineId, scroll_state: &Point2D) { - let context = self.browsing_context.get(); - if let Some(context) = context { - if let Some(inner_context) = context.find(id) { - let window = inner_context.active_window(); - window.update_viewport_for_scroll(-scroll_state.x, -scroll_state.y); - return + fn handle_set_scroll_state(&self, + id: PipelineId, + scroll_states: &[(UntrustedNodeAddress, Point2D)]) { + let window = match self.browsing_context.get() { + Some(context) => { + match context.find(id) { + 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) { diff --git a/components/script_traits/lib.rs b/components/script_traits/lib.rs index 82795e4a43c4..07cfaccb84c2 100644 --- a/components/script_traits/lib.rs +++ b/components/script_traits/lib.rs @@ -40,6 +40,7 @@ use euclid::rect::Rect; use gfx_traits::Epoch; use gfx_traits::LayerId; use gfx_traits::StackingContextId; +use heapsize::HeapSizeOf; use ipc_channel::ipc::{IpcReceiver, IpcSender}; use libc::c_void; 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::response::HttpsState; use profile_traits::mem; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::collections::HashMap; use std::sync::mpsc::{Sender, Receiver}; 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 /// `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); + +impl HeapSizeOf for UntrustedNodeAddress { + fn heap_size_of_children(&self) -> usize { + 0 + } +} + #[allow(unsafe_code)] unsafe impl Send for UntrustedNodeAddress {} +impl Serialize for UntrustedNodeAddress { + fn serialize(&self, s: &mut S) -> Result<(), S::Error> { + (self.0 as usize).serialize(s) + } +} + +impl Deserialize for UntrustedNodeAddress { + fn deserialize(d: &mut D) -> Result { + 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. #[derive(Deserialize, Serialize)] pub enum LayoutControlMsg { @@ -125,8 +155,8 @@ pub enum ConstellationControlMsg { SendEvent(PipelineId, CompositorEvent), /// Notifies script of the viewport. Viewport(PipelineId, Rect), - /// Notifies script of a new scroll offset. - SetScrollState(PipelineId, Point2D), + /// Notifies script of a new set of scroll offsets. + SetScrollState(PipelineId, Vec<(UntrustedNodeAddress, Point2D)>), /// Requests that the script thread immediately send the constellation the title of a pipeline. GetTitle(PipelineId), /// Notifies script thread to suspend all its timers diff --git a/tests/html/hit_test_overflow_scroll.html b/tests/html/hit_test_overflow_scroll.html new file mode 100644 index 000000000000..f75a215e5692 --- /dev/null +++ b/tests/html/hit_test_overflow_scroll.html @@ -0,0 +1,7 @@ + + + +

Scroll down...

+

Mouse over me!

+ +