mirror of
https://github.com/servo/servo.git
synced 2025-07-31 03:00:29 +01:00
Refactor mouseover code to be more performant
This increases mouseover/out performance drastically on my machine.
This commit is contained in:
parent
73eff81e5d
commit
3662491d8f
3 changed files with 121 additions and 110 deletions
|
@ -798,81 +798,33 @@ impl Document {
|
|||
pub fn handle_mouse_move_event(&self,
|
||||
js_runtime: *mut JSRuntime,
|
||||
client_point: Option<Point2D<f32>>,
|
||||
prev_mouse_over_targets: &mut RootedVec<JS<Element>>) {
|
||||
// Build a list of elements that are currently under the mouse.
|
||||
let mouse_over_address = client_point.as_ref().map(|client_point| {
|
||||
let page_point = Point2D::new(client_point.x + self.window.PageXOffset() as f32,
|
||||
client_point.y + self.window.PageYOffset() as f32);
|
||||
self.hit_test(&page_point, true)
|
||||
}).unwrap_or(None);
|
||||
prev_mouse_over_target: &MutNullableHeap<JS<Element>>) {
|
||||
let page_point = match client_point {
|
||||
None => {
|
||||
// If there's no point, there's no target under the mouse
|
||||
// FIXME: dispatch mouseout here. We have no point.
|
||||
prev_mouse_over_target.set(None);
|
||||
return;
|
||||
}
|
||||
Some(ref client_point) => {
|
||||
Point2D::new(client_point.x + self.window.PageXOffset() as f32,
|
||||
client_point.y + self.window.PageYOffset() as f32)
|
||||
}
|
||||
};
|
||||
|
||||
let mut mouse_over_targets = RootedVec::<JS<Element>>::new();
|
||||
let client_point = client_point.unwrap();
|
||||
|
||||
if let Some(address) = mouse_over_address {
|
||||
let maybe_new_target = self.hit_test(&page_point, true).and_then(|address| {
|
||||
let node = node::from_untrusted_node_address(js_runtime, address);
|
||||
for node in node.inclusive_ancestors()
|
||||
.filter_map(Root::downcast::<Element>) {
|
||||
mouse_over_targets.push(JS::from_rooted(&node));
|
||||
}
|
||||
}
|
||||
|
||||
// Werther the topmost element we are hovering now is diffrent than the previous
|
||||
let is_different_topmost_target = mouse_over_targets.is_empty() ||
|
||||
prev_mouse_over_targets.is_empty() ||
|
||||
prev_mouse_over_targets[0].upcast::<Node>().to_trusted_node_address() !=
|
||||
mouse_over_targets[0].upcast::<Node>().to_trusted_node_address();
|
||||
|
||||
// Remove hover from any elements in the previous list that are no longer
|
||||
// under the mouse.
|
||||
for (index, target) in prev_mouse_over_targets.iter().enumerate() {
|
||||
// Hover state is reset as appropiate later
|
||||
target.set_hover_state(false);
|
||||
|
||||
// https://www.w3.org/TR/uievents/#event-type-mouseout
|
||||
//
|
||||
// mouseout must be dispatched when the mouse moves off an element or when pointer
|
||||
// mouse moves from an element onto the boundaries of one of its descendent elements.
|
||||
let has_to_dispatch_mouse_out = index == 0 && is_different_topmost_target;
|
||||
|
||||
if has_to_dispatch_mouse_out {
|
||||
let target = target.upcast();
|
||||
if let Some(client_point) = client_point {
|
||||
self.fire_mouse_event(client_point, &target, "mouseout".to_owned());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set hover state for any elements in the current mouse over list.
|
||||
// Check if any of them changed state to determine whether to
|
||||
// force a reflow below.
|
||||
for (index, target) in mouse_over_targets.r().iter().enumerate() {
|
||||
target.set_hover_state(true);
|
||||
|
||||
// https://www.w3.org/TR/uievents/#event-type-mouseover
|
||||
//
|
||||
// Mouseover must be fired when a pointing device is moved onto the boundaries of an
|
||||
// element (we only fire it in the first because it bubbles), or when the pointer has
|
||||
// moved from our children to ours.
|
||||
//
|
||||
// The below condition adresses both situations.
|
||||
let has_to_dispatch_mouse_over = index == 0 && is_different_topmost_target;
|
||||
|
||||
if has_to_dispatch_mouse_over {
|
||||
let target = target.upcast();
|
||||
if let Some(client_point) = client_point {
|
||||
self.fire_mouse_event(client_point, target, "mouseover".to_owned());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Send mousemove event to topmost target
|
||||
if let Some(address) = mouse_over_address {
|
||||
let top_most_node = node::from_untrusted_node_address(js_runtime,
|
||||
address);
|
||||
let client_point = client_point.unwrap(); // Must succeed because hit test succeeded.
|
||||
node.inclusive_ancestors()
|
||||
.filter_map(Root::downcast::<Element>)
|
||||
.next()
|
||||
});
|
||||
|
||||
// Send mousemove event to topmost target, and forward it if it's an iframe
|
||||
if let Some(ref new_target) = maybe_new_target {
|
||||
// If the target is an iframe, forward the event to the child document.
|
||||
if let Some(iframe) = top_most_node.downcast::<HTMLIFrameElement>() {
|
||||
if let Some(iframe) = new_target.downcast::<HTMLIFrameElement>() {
|
||||
if let Some(pipeline_id) = iframe.pipeline_id() {
|
||||
let rect = iframe.upcast::<Element>().GetBoundingClientRect();
|
||||
let child_origin = Point2D::new(rect.X() as f32, rect.Y() as f32);
|
||||
|
@ -884,13 +836,59 @@ impl Document {
|
|||
return;
|
||||
}
|
||||
|
||||
let target = top_most_node.upcast();
|
||||
self.fire_mouse_event(client_point, target, "mousemove".to_owned());
|
||||
self.fire_mouse_event(client_point, new_target.upcast(), "mousemove".to_owned());
|
||||
}
|
||||
|
||||
// Store the current mouse over targets for next frame
|
||||
prev_mouse_over_targets.clear();
|
||||
prev_mouse_over_targets.append(&mut *mouse_over_targets);
|
||||
// Nothing more to do here, mousemove is sent,
|
||||
// and the element under the mouse hasn't changed.
|
||||
if maybe_new_target == prev_mouse_over_target.get() {
|
||||
return;
|
||||
}
|
||||
|
||||
let old_target_is_ancestor_of_new_target = match (prev_mouse_over_target.get(), maybe_new_target.as_ref()) {
|
||||
(Some(old_target), Some(new_target))
|
||||
=> old_target.upcast::<Node>().is_ancestor_of(new_target.upcast::<Node>()),
|
||||
_ => false,
|
||||
};
|
||||
|
||||
// Here we know the target has changed, so we must update the state,
|
||||
// dispatch mouseout to the previous one, mouseover to the new one,
|
||||
if let Some(old_target) = prev_mouse_over_target.get() {
|
||||
// If the old target is an ancestor of the new target, this can be skipped
|
||||
// completely, since the node's hover state will be reseted below.
|
||||
if !old_target_is_ancestor_of_new_target {
|
||||
for element in old_target.upcast::<Node>()
|
||||
.inclusive_ancestors()
|
||||
.filter_map(Root::downcast::<Element>) {
|
||||
element.set_hover_state(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove hover state to old target and its parents
|
||||
self.fire_mouse_event(client_point, old_target.upcast(), "mouseout".to_owned());
|
||||
|
||||
// TODO: Fire mouseleave here only if the old target is
|
||||
// not an ancestor of the new target.
|
||||
}
|
||||
|
||||
if let Some(ref new_target) = maybe_new_target {
|
||||
for element in new_target.upcast::<Node>()
|
||||
.inclusive_ancestors()
|
||||
.filter_map(Root::downcast::<Element>) {
|
||||
if element.get_hover_state() {
|
||||
break;
|
||||
}
|
||||
|
||||
element.set_hover_state(true);
|
||||
}
|
||||
|
||||
self.fire_mouse_event(client_point, &new_target.upcast(), "mouseover".to_owned());
|
||||
|
||||
// TODO: Fire mouseenter here.
|
||||
}
|
||||
|
||||
// Store the current mouse over target for next frame.
|
||||
prev_mouse_over_target.set(maybe_new_target.as_ref().map(|target| target.r()));
|
||||
|
||||
self.window.reflow(ReflowGoal::ForDisplay,
|
||||
ReflowQueryType::NoQuery,
|
||||
|
|
|
@ -518,7 +518,11 @@ impl Node {
|
|||
}
|
||||
|
||||
pub fn is_inclusive_ancestor_of(&self, parent: &Node) -> bool {
|
||||
self == parent || parent.ancestors().any(|ancestor| ancestor.r() == self)
|
||||
self == parent || self.is_ancestor_of(parent)
|
||||
}
|
||||
|
||||
pub fn is_ancestor_of(&self, parent: &Node) -> bool {
|
||||
parent.ancestors().any(|ancestor| ancestor.r() == self)
|
||||
}
|
||||
|
||||
pub fn following_siblings(&self) -> NodeSiblingIterator {
|
||||
|
|
|
@ -27,10 +27,10 @@ use dom::bindings::codegen::Bindings::DocumentBinding::{DocumentMethods, Documen
|
|||
use dom::bindings::conversions::{FromJSValConvertible, StringificationBehavior};
|
||||
use dom::bindings::global::GlobalRef;
|
||||
use dom::bindings::inheritance::Castable;
|
||||
use dom::bindings::js::{JS, RootCollection, trace_roots};
|
||||
use dom::bindings::js::{JS, MutNullableHeap, Root, RootCollection, trace_roots};
|
||||
use dom::bindings::js::{RootCollectionPtr, RootedReference};
|
||||
use dom::bindings::refcounted::{LiveDOMReferences, Trusted, TrustedReference, trace_refcounted_objects};
|
||||
use dom::bindings::trace::{JSTraceable, RootedVec, trace_traceables};
|
||||
use dom::bindings::trace::{JSTraceable, trace_traceables};
|
||||
use dom::bindings::utils::{DOM_CALLBACKS, WRAP_CALLBACKS};
|
||||
use dom::browsingcontext::BrowsingContext;
|
||||
use dom::document::{Document, DocumentProgressHandler, DocumentSource, FocusType, IsHTMLDocument};
|
||||
|
@ -91,7 +91,6 @@ use std::cell::{Cell, RefCell};
|
|||
use std::collections::HashSet;
|
||||
use std::io::{Write, stdout};
|
||||
use std::marker::PhantomData;
|
||||
use std::mem as std_mem;
|
||||
use std::option::Option;
|
||||
use std::ptr;
|
||||
use std::rc::Rc;
|
||||
|
@ -543,7 +542,8 @@ pub struct ScriptThread {
|
|||
/// The JavaScript runtime.
|
||||
js_runtime: Rc<Runtime>,
|
||||
|
||||
mouse_over_targets: DOMRefCell<Vec<JS<Element>>>,
|
||||
/// The topmost element over the mouse.
|
||||
topmost_mouse_over_target: MutNullableHeap<JS<Element>>,
|
||||
|
||||
/// List of pipelines that have been owned and closed by this script thread.
|
||||
closed_pipelines: DOMRefCell<HashSet<PipelineId>>,
|
||||
|
@ -789,7 +789,7 @@ impl ScriptThread {
|
|||
devtools_sender: ipc_devtools_sender,
|
||||
|
||||
js_runtime: Rc::new(runtime),
|
||||
mouse_over_targets: DOMRefCell::new(vec!()),
|
||||
topmost_mouse_over_target: MutNullableHeap::new(Default::default()),
|
||||
closed_pipelines: DOMRefCell::new(HashSet::new()),
|
||||
|
||||
scheduler_chan: state.scheduler_chan,
|
||||
|
@ -1979,46 +1979,55 @@ impl ScriptThread {
|
|||
let page = get_page(&self.root_page(), pipeline_id);
|
||||
let document = page.document();
|
||||
|
||||
let mut prev_mouse_over_targets: RootedVec<JS<Element>> = RootedVec::new();
|
||||
for target in &*self.mouse_over_targets.borrow_mut() {
|
||||
prev_mouse_over_targets.push(target.clone());
|
||||
// Get the previous target temporarily
|
||||
let prev_mouse_over_target = self.topmost_mouse_over_target.get();
|
||||
|
||||
document.handle_mouse_move_event(self.js_runtime.rt(), point,
|
||||
&self.topmost_mouse_over_target);
|
||||
|
||||
// Short-circuit if nothing changed
|
||||
if self.topmost_mouse_over_target.get() == prev_mouse_over_target {
|
||||
return;
|
||||
}
|
||||
|
||||
// We temporarily steal the list of targets over which the mouse is to pass it to
|
||||
// handle_mouse_move_event() in a safe RootedVec container.
|
||||
let mut mouse_over_targets = RootedVec::new();
|
||||
std_mem::swap(&mut *self.mouse_over_targets.borrow_mut(), &mut *mouse_over_targets);
|
||||
document.handle_mouse_move_event(self.js_runtime.rt(), point, &mut mouse_over_targets);
|
||||
|
||||
// Notify Constellation about anchors that are no longer mouse over targets.
|
||||
for target in &*prev_mouse_over_targets {
|
||||
if !mouse_over_targets.contains(target) && target.is::<HTMLAnchorElement>() {
|
||||
let event = ConstellationMsg::NodeStatus(None);
|
||||
let ConstellationChan(ref chan) = self.constellation_chan;
|
||||
chan.send(event).unwrap();
|
||||
break;
|
||||
}
|
||||
}
|
||||
let mut state_already_changed = false;
|
||||
|
||||
// Notify Constellation about the topmost anchor mouse over target.
|
||||
for target in &*mouse_over_targets {
|
||||
if target.is::<HTMLAnchorElement>() {
|
||||
let status = target.get_attribute(&ns!(), &atom!("href"))
|
||||
.and_then(|href| {
|
||||
let value = href.value();
|
||||
let url = document.url();
|
||||
url.join(&value).map(|url| url.serialize()).ok()
|
||||
});
|
||||
if let Some(target) = self.topmost_mouse_over_target.get() {
|
||||
if let Some(anchor) = target.upcast::<Node>()
|
||||
.inclusive_ancestors()
|
||||
.filter_map(Root::downcast::<HTMLAnchorElement>)
|
||||
.next() {
|
||||
let status = anchor.upcast::<Element>()
|
||||
.get_attribute(&ns!(), &atom!("href"))
|
||||
.and_then(|href| {
|
||||
let value = href.value();
|
||||
let url = document.url();
|
||||
url.join(&value).map(|url| url.serialize()).ok()
|
||||
});
|
||||
|
||||
let event = ConstellationMsg::NodeStatus(status);
|
||||
let ConstellationChan(ref chan) = self.constellation_chan;
|
||||
chan.send(event).unwrap();
|
||||
break;
|
||||
|
||||
state_already_changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
std_mem::swap(&mut *self.mouse_over_targets.borrow_mut(), &mut *mouse_over_targets);
|
||||
// We might have to reset the anchor state
|
||||
if !state_already_changed {
|
||||
if let Some(target) = prev_mouse_over_target {
|
||||
if let Some(_) = target.upcast::<Node>()
|
||||
.inclusive_ancestors()
|
||||
.filter_map(Root::downcast::<HTMLAnchorElement>)
|
||||
.next() {
|
||||
let event = ConstellationMsg::NodeStatus(None);
|
||||
let ConstellationChan(ref chan) = self.constellation_chan;
|
||||
chan.send(event).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TouchEvent(event_type, identifier, point) => {
|
||||
let handled = self.handle_touch_event(pipeline_id, event_type, identifier, point);
|
||||
match event_type {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue