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:
Patrick Walton 2016-06-08 18:46:02 -07:00
parent ce88b8ed30
commit 041cfe6d0a
14 changed files with 259 additions and 47 deletions

View file

@ -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<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>);

View file

@ -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<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

View file

@ -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;

View file

@ -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

View file

@ -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<HashMap<UntrustedNodeAddress, Point2D<f32>>>,
#[ignore_heap_size_of = "Defined in ipc-channel"]
panic_chan: IpcSender<PanicMsg>,
}
@ -354,6 +358,13 @@ impl Window {
pub fn css_error_reporter(&self) -> Box<ParseErrorReporter + Send> {
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"))]
@ -1243,7 +1254,28 @@ impl Window {
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,
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,
};

View file

@ -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
}

View file

@ -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<f32>) {
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<f32>)]) {
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),
};
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));
}
}
panic!("Set scroll state message message sent to nonexistent pipeline: {:?}", id);
window.set_scroll_offsets(scroll_offsets)
}
fn handle_new_layout(&self, new_layout_info: NewLayoutInfo) {