diff options
author | Matt Brubeck <mbrubeck@limpet.net> | 2015-08-14 07:24:50 -0700 |
---|---|---|
committer | Matt Brubeck <mbrubeck@limpet.net> | 2015-10-22 10:37:03 -0700 |
commit | fe7460f34d20d5f17d21d60e1053028b21c63ebc (patch) | |
tree | 6cc401775f503ce01b528021cc87bc7580fc7140 | |
parent | 4ed15a8853fc49d0940ed24d939fd0c407ee80a9 (diff) | |
download | servo-fe7460f34d20d5f17d21d60e1053028b21c63ebc.tar.gz servo-fe7460f34d20d5f17d21d60e1053028b21c63ebc.zip |
Dispatch touch events and perform default touch actions.
This is currently limited to simple single-touch actions. It does not include
momentum scrolling or pinch zooming.
-rw-r--r-- | components/compositing/compositor.rs | 152 | ||||
-rw-r--r-- | components/compositing/compositor_layer.rs | 24 | ||||
-rw-r--r-- | components/compositing/compositor_task.rs | 9 | ||||
-rw-r--r-- | components/compositing/headless.rs | 1 | ||||
-rw-r--r-- | components/msg/compositor_msg.rs | 7 | ||||
-rw-r--r-- | components/script/dom/document.rs | 53 | ||||
-rw-r--r-- | components/script/dom/touchevent.rs | 2 | ||||
-rw-r--r-- | components/script/script_task.rs | 35 | ||||
-rw-r--r-- | components/script_traits/lib.rs | 8 |
9 files changed, 276 insertions, 15 deletions
diff --git a/components/compositing/compositor.rs b/components/compositing/compositor.rs index 0097d70b9de..0b44a715e88 100644 --- a/components/compositing/compositor.rs +++ b/components/compositing/compositor.rs @@ -26,7 +26,7 @@ use layers::rendergl; use layers::rendergl::RenderContext; use layers::scene::Scene; use layout_traits::LayoutControlChan; -use msg::compositor_msg::{Epoch, FrameTreeId, LayerId, LayerKind}; +use msg::compositor_msg::{Epoch, EventResult, FrameTreeId, LayerId, LayerKind}; use msg::compositor_msg::{LayerProperties, ScrollPolicy}; use msg::constellation_msg::Msg as ConstellationMsg; use msg::constellation_msg::{AnimationState, Image, PixelFormat}; @@ -35,7 +35,8 @@ use msg::constellation_msg::{NavigationDirection, PipelineId, WindowSizeData}; use pipeline::CompositionPipeline; use profile_traits::mem::{self, ReportKind, Reporter, ReporterRequest}; use profile_traits::time::{self, ProfilerCategory, profile}; -use script_traits::{ConstellationControlMsg, LayoutControlMsg}; +use script_traits::CompositorEvent::{TouchDownEvent, TouchMoveEvent, TouchUpEvent}; +use script_traits::{ConstellationControlMsg, LayoutControlMsg, MouseButton}; use scrolling::ScrollingTimerProxy; use std::collections::hash_map::Entry::{Occupied, Vacant}; use std::collections::{HashMap, HashSet}; @@ -59,6 +60,9 @@ const BUFFER_MAP_SIZE: usize = 10000000; const MAX_ZOOM: f32 = 8.0; const MIN_ZOOM: f32 = 0.1; +/// Minimum number of ScreenPx to begin touch scrolling. +const TOUCH_PAN_MIN_SCREEN_PX: f32 = 20.0; + /// Holds the state when running reftests that determines when it is /// safe to save the output image. #[derive(Copy, Clone, PartialEq)] @@ -68,6 +72,23 @@ enum ReadyState { ReadyToSaveImage, } +/// The states of the touch input state machine. +/// +/// TODO: Currently Add support for "flinging" (scrolling inertia), pinch zooming, better +/// support for multiple touch points. +enum TouchState { + /// Not tracking any touch point + Nothing, + /// A touchstart event was dispatched to the page, but the response wasn't received yet. + WaitingForScript, + /// Script is consuming the current touch point; don't perform default actions. + DefaultPrevented, + /// A single touch point is active and may perform click or pan default actions. + Touching, + /// A single touch point is active and has started panning. + Panning, +} + /// NB: Never block on the constellation, because sometimes the constellation blocks on us. pub struct IOCompositor<Window: WindowMethods> { /// The application window. @@ -156,6 +177,15 @@ pub struct IOCompositor<Window: WindowMethods> { /// Pending scroll to fragment event, if any fragment_point: Option<Point2D<f32>>, + /// Touch input state machine + touch_gesture_state: TouchState, + + /// When tracking a touch for gesture detection, the point where it started + first_touch_point: Option<TypedPoint2D<DevicePixel, f32>>, + + /// When tracking a touch for gesture detection, its most recent point + last_touch_point: Option<TypedPoint2D<DevicePixel, f32>>, + /// Pending scroll events. pending_scroll_events: Vec<ScrollEvent>, @@ -295,6 +325,9 @@ impl<Window: WindowMethods> IOCompositor<Window> { channel_to_self: state.sender.clone_compositor_proxy(), scrolling_timer: ScrollingTimerProxy::new(state.sender), composition_request: CompositionRequest::NoCompositingNecessary, + touch_gesture_state: TouchState::Nothing, + first_touch_point: None, + last_touch_point: None, pending_scroll_events: Vec::new(), composite_target: composite_target, shutdown_state: ShutdownState::NotShuttingDown, @@ -502,6 +535,18 @@ impl<Window: WindowMethods> IOCompositor<Window> { } } + (Msg::TouchEventProcessed(result), ShutdownState::NotShuttingDown) => { + match self.touch_gesture_state { + TouchState::WaitingForScript => { + self.touch_gesture_state = match result { + EventResult::DefaultAllowed => TouchState::Touching, + EventResult::DefaultPrevented => TouchState::DefaultPrevented, + }; + } + _ => {} + } + } + (Msg::SetCursor(cursor), ShutdownState::NotShuttingDown) => { self.window.set_cursor(cursor) } @@ -1097,6 +1142,109 @@ impl<Window: WindowMethods> IOCompositor<Window> { } } + fn on_touch_down(&mut self, identifier: i32, point: TypedPoint2D<DevicePixel, f32>) { + match self.touch_gesture_state { + TouchState::Nothing => { + // TODO: Don't wait for script if we know the page has no touch event listeners. + self.first_touch_point = Some(point); + self.last_touch_point = Some(point); + self.touch_gesture_state = TouchState::WaitingForScript; + } + TouchState::WaitingForScript => { + // TODO: Queue events while waiting for script? + } + TouchState::DefaultPrevented => {} + TouchState::Touching => {} + TouchState::Panning => {} + } + if let Some(result) = self.find_topmost_layer_at_point(point / self.scene.scale) { + result.layer.send_event(self, TouchDownEvent(identifier, result.point.to_untyped())); + } + } + + fn on_touch_move(&mut self, identifier: i32, point: TypedPoint2D<DevicePixel, f32>) { + match self.touch_gesture_state { + TouchState::Nothing => warn!("Got unexpected touch move event"), + + TouchState::WaitingForScript => { + // TODO: Queue events while waiting for script? + } + TouchState::Touching => { + match self.first_touch_point { + Some(p0) => { + let delta = point - p0; + let px: TypedPoint2D<ScreenPx, _> = delta / self.device_pixels_per_screen_px(); + let px = px.to_untyped(); + + if px.x.abs() > TOUCH_PAN_MIN_SCREEN_PX || + px.y.abs() > TOUCH_PAN_MIN_SCREEN_PX + { + self.touch_gesture_state = TouchState::Panning; + self.on_scroll_window_event(delta, point.cast().unwrap()); + } + } + None => warn!("first_touch_point not set") + } + } + TouchState::Panning => { + match self.last_touch_point { + Some(p0) => { + let delta = point - p0; + self.on_scroll_window_event(delta, point.cast().unwrap()); + } + None => warn!("last_touch_point not set") + } + } + TouchState::DefaultPrevented => { + // Send the event to script. + if let Some(result) = self.find_topmost_layer_at_point(point / self.scene.scale) { + result.layer.send_event(self, + TouchMoveEvent(identifier, result.point.to_untyped())); + } + } + } + self.last_touch_point = Some(point); + } + + fn on_touch_up(&mut self, identifier: i32, point: TypedPoint2D<DevicePixel, f32>) { + // TODO: Track the number of active touch points, and don't reset stuff until it is zero. + self.first_touch_point = None; + self.last_touch_point = None; + + // Send the event to script. + if let Some(result) = self.find_topmost_layer_at_point(point / self.scene.scale) { + result.layer.send_event(self, TouchUpEvent(identifier, result.point.to_untyped())); + } + + match self.touch_gesture_state { + TouchState::Nothing => warn!("Got unexpected touch up event"), + + TouchState::WaitingForScript => {} + TouchState::Touching => { + // TODO: If the duration exceeds some threshold, send a contextmenu event instead. + // TODO: Don't send a click if preventDefault is called on the touchend event. + self.simulate_mouse_click(point); + } + TouchState::Panning => {} + TouchState::DefaultPrevented => {} + } + self.touch_gesture_state = TouchState::Nothing; + } + + /// http://w3c.github.io/touch-events/#mouse-events + fn simulate_mouse_click(&self, p: TypedPoint2D<DevicePixel, f32>) { + match self.find_topmost_layer_at_point(p / self.scene.scale) { + Some(HitTestResult { layer, point }) => { + let button = MouseButton::Left; + layer.send_mouse_move_event(self, point); + layer.send_mouse_event(self, MouseWindowEvent::MouseDown(button, p), point); + layer.send_mouse_event(self, MouseWindowEvent::MouseUp(button, p), point); + layer.send_mouse_event(self, MouseWindowEvent::Click(button, p), point); + } + None => {}, + } + } + fn on_scroll_window_event(&mut self, delta: TypedPoint2D<DevicePixel, f32>, cursor: TypedPoint2D<DevicePixel, i32>) { diff --git a/components/compositing/compositor_layer.rs b/components/compositing/compositor_layer.rs index 811f704f52d..8e1330b9344 100644 --- a/components/compositing/compositor_layer.rs +++ b/components/compositing/compositor_layer.rs @@ -12,7 +12,8 @@ use layers::color::Color; use layers::geometry::LayerPixel; use layers::layers::{Layer, LayerBufferSet}; use msg::compositor_msg::{Epoch, LayerId, LayerProperties, ScrollPolicy}; -use msg::constellation_msg::{PipelineId}; +use msg::constellation_msg::PipelineId; +use script_traits::CompositorEvent; use script_traits::CompositorEvent::{ClickEvent, MouseDownEvent, MouseMoveEvent, MouseUpEvent}; use script_traits::ConstellationControlMsg; use std::rc::Rc; @@ -132,6 +133,11 @@ pub trait CompositorLayer { cursor: TypedPoint2D<LayerPixel, f32>) where Window: WindowMethods; + fn send_event<Window>(&self, + compositor: &IOCompositor<Window>, + event: CompositorEvent) + where Window: WindowMethods; + fn clamp_scroll_offset_and_scroll_layer(&self, new_offset: TypedPoint2D<LayerPixel, f32>) -> ScrollEventResult; @@ -372,22 +378,22 @@ impl CompositorLayer for Layer<CompositorData> { MouseWindowEvent::MouseUp(button, _) => MouseUpEvent(button, event_point), }; - - if let Some(pipeline) = compositor.pipeline(self.pipeline_id()) { - pipeline.script_chan - .send(ConstellationControlMsg::SendEvent(pipeline.id.clone(), message)) - .unwrap(); - } + self.send_event(compositor, message); } fn send_mouse_move_event<Window>(&self, compositor: &IOCompositor<Window>, cursor: TypedPoint2D<LayerPixel, f32>) where Window: WindowMethods { - let message = MouseMoveEvent(cursor.to_untyped()); + self.send_event(compositor, MouseMoveEvent(cursor.to_untyped())); + } + + fn send_event<Window>(&self, + compositor: &IOCompositor<Window>, + event: CompositorEvent) where Window: WindowMethods { if let Some(pipeline) = compositor.pipeline(self.pipeline_id()) { let _ = pipeline.script_chan - .send(ConstellationControlMsg::SendEvent(pipeline.id.clone(), message)); + .send(ConstellationControlMsg::SendEvent(pipeline.id.clone(), event)); } } diff --git a/components/compositing/compositor_task.rs b/components/compositing/compositor_task.rs index cff97842653..b9d73c6b25d 100644 --- a/components/compositing/compositor_task.rs +++ b/components/compositing/compositor_task.rs @@ -10,7 +10,7 @@ use headless; use ipc_channel::ipc::{IpcReceiver, IpcSender}; use layers::layers::{BufferRequest, LayerBufferSet}; use layers::platform::surface::{NativeDisplay, NativeSurface}; -use msg::compositor_msg::{Epoch, FrameTreeId, LayerId, LayerProperties}; +use msg::compositor_msg::{Epoch, EventResult, FrameTreeId, LayerId, LayerProperties}; use msg::compositor_msg::{PaintListener, ScriptToCompositorMsg}; use msg::constellation_msg::{AnimationState, ConstellationChan, PipelineId}; use msg::constellation_msg::{Image, Key, KeyModifiers, KeyState}; @@ -94,6 +94,10 @@ pub fn run_script_listener_thread(compositor_proxy: Box<CompositorProxy + 'stati ScriptToCompositorMsg::SendKeyEvent(key, key_state, key_modifiers) => { compositor_proxy.send(Msg::KeyEvent(key, key_state, key_modifiers)) } + + ScriptToCompositorMsg::TouchEventProcessed(result) => { + compositor_proxy.send(Msg::TouchEventProcessed(result)) + } } } } @@ -189,6 +193,8 @@ pub enum Msg { RecompositeAfterScroll, /// Sends an unconsumed key event back to the compositor. KeyEvent(Key, KeyState, KeyModifiers), + /// Script has handled a touch event, and either prevented or allowed default actions. + TouchEventProcessed(EventResult), /// Changes the cursor. SetCursor(Cursor), /// Composite to a PNG file and return the Image over a passed channel. @@ -238,6 +244,7 @@ impl Debug for Msg { Msg::ScrollTimeout(..) => write!(f, "ScrollTimeout"), Msg::RecompositeAfterScroll => write!(f, "RecompositeAfterScroll"), Msg::KeyEvent(..) => write!(f, "KeyEvent"), + Msg::TouchEventProcessed(..) => write!(f, "TouchEventProcessed"), Msg::SetCursor(..) => write!(f, "SetCursor"), Msg::CreatePng(..) => write!(f, "CreatePng"), Msg::PaintTaskExited(..) => write!(f, "PaintTaskExited"), diff --git a/components/compositing/headless.rs b/components/compositing/headless.rs index d322ef77445..1db4d7b7ad5 100644 --- a/components/compositing/headless.rs +++ b/components/compositing/headless.rs @@ -120,6 +120,7 @@ impl CompositorEventListener for NullCompositor { Msg::ChangePageTitle(..) | Msg::ChangePageUrl(..) | Msg::KeyEvent(..) | + Msg::TouchEventProcessed(..) | Msg::SetCursor(..) | Msg::ViewportConstrained(..) => {} Msg::CreatePng(..) | diff --git a/components/msg/compositor_msg.rs b/components/msg/compositor_msg.rs index e7612351724..a0548d30873 100644 --- a/components/msg/compositor_msg.rs +++ b/components/msg/compositor_msg.rs @@ -160,5 +160,12 @@ pub enum ScriptToCompositorMsg { GetClientWindow(IpcSender<(Size2D<u32>, Point2D<i32>)>), MoveTo(Point2D<i32>), ResizeTo(Size2D<u32>), + TouchEventProcessed(EventResult), Exit, } + +#[derive(Deserialize, Serialize)] +pub enum EventResult { + DefaultAllowed, + DefaultPrevented, +} diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index 835351cdc0d..f987d73747c 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -21,6 +21,7 @@ use dom::bindings::error::{Error, ErrorResult, Fallible}; use dom::bindings::global::GlobalRef; use dom::bindings::js::RootedReference; use dom::bindings::js::{JS, LayoutJS, MutNullableHeap, Root}; +use dom::bindings::num::Finite; use dom::bindings::refcounted::Trusted; use dom::bindings::trace::RootedVec; use dom::bindings::utils::XMLName::InvalidXMLName; @@ -60,6 +61,9 @@ use dom::processinginstruction::ProcessingInstruction; use dom::range::Range; use dom::servohtmlparser::ServoHTMLParser; use dom::text::Text; +use dom::touch::Touch; +use dom::touchevent::TouchEvent; +use dom::touchlist::TouchList; use dom::treewalker::TreeWalker; use dom::uievent::UIEvent; use dom::window::{ReflowReason, Window}; @@ -699,6 +703,55 @@ impl Document { ReflowReason::MouseEvent); } + pub fn handle_touch_event(&self, + js_runtime: *mut JSRuntime, + identifier: i32, + point: Point2D<f32>, + event_name: String) -> bool { + let node = match self.hit_test(&point) { + Some(node_address) => node::from_untrusted_node_address(js_runtime, node_address), + None => return false + }; + let el = match node.downcast::<Element>() { + Some(el) => Root::from_ref(el), + None => { + let parent = node.r().GetParentNode(); + match parent.and_then(Root::downcast::<Element>) { + Some(parent) => parent, + None => return false + } + }, + }; + let target = el.upcast::<EventTarget>(); + + let x = Finite::wrap(point.x as f64); + let y = Finite::wrap(point.y as f64); + + let window = self.window.root(); + + let touch = Touch::new(window.r(), identifier, target, x, y, x, y); + let mut touches = RootedVec::new(); + touches.push(JS::from_rooted(&touch)); + let touches = TouchList::new(window.r(), touches.r()); + + let event = TouchEvent::new(window.r(), + event_name, + EventBubbles::Bubbles, + EventCancelable::Cancelable, + Some(window.r()), + 0i32, + &touches, &touches, &touches, + // FIXME: modifier keys + false, false, false, false); + let event = event.upcast::<Event>(); + let result = event.fire(target); + + window.r().reflow(ReflowGoal::ForDisplay, + ReflowQueryType::NoQuery, + ReflowReason::MouseEvent); + result + } + /// The entry point for all key processing for web content pub fn dispatch_key_event(&self, key: Key, diff --git a/components/script/dom/touchevent.rs b/components/script/dom/touchevent.rs index 99bac602bd6..5244e4c9e35 100644 --- a/components/script/dom/touchevent.rs +++ b/components/script/dom/touchevent.rs @@ -9,7 +9,7 @@ use dom::bindings::conversions::Castable; use dom::bindings::global::GlobalRef; use dom::bindings::js::{JS, MutHeap, Root}; use dom::bindings::utils::reflect_dom_object; -use dom::event::{Event, EventBubbles, EventCancelable}; +use dom::event::{EventBubbles, EventCancelable}; use dom::touchlist::TouchList; use dom::uievent::UIEvent; use dom::window::Window; diff --git a/components/script/script_task.rs b/components/script/script_task.rs index a090d1c91ef..2b6f4c252d6 100644 --- a/components/script/script_task.rs +++ b/components/script/script_task.rs @@ -60,7 +60,7 @@ use layout_interface::{ReflowQueryType}; use layout_interface::{self, LayoutChan, NewLayoutTaskInfo, ReflowGoal, ScriptLayoutChan}; use libc; use mem::heap_size_of_self_and_children; -use msg::compositor_msg::{LayerId, ScriptToCompositorMsg}; +use msg::compositor_msg::{EventResult, LayerId, ScriptToCompositorMsg}; use msg::constellation_msg::Msg as ConstellationMsg; use msg::constellation_msg::{ConstellationChan, FocusType, LoadData}; use msg::constellation_msg::{MozBrowserEvent, PipelineExitType, PipelineId}; @@ -79,6 +79,7 @@ use profile_traits::time::{self, ProfilerCategory, profile}; use script_traits::CompositorEvent::{ClickEvent, ResizeEvent}; use script_traits::CompositorEvent::{KeyEvent, MouseMoveEvent}; use script_traits::CompositorEvent::{MouseDownEvent, MouseUpEvent}; +use script_traits::CompositorEvent::{TouchDownEvent, TouchMoveEvent, TouchUpEvent}; use script_traits::{CompositorEvent, ConstellationControlMsg}; use script_traits::{InitialScriptState, MouseButton, NewLayoutInfo}; use script_traits::{OpaqueScriptLayoutChannel, ScriptState, ScriptTaskFactory}; @@ -1784,6 +1785,27 @@ impl ScriptTask { std_mem::swap(&mut *self.mouse_over_targets.borrow_mut(), &mut *mouse_over_targets); } + TouchDownEvent(identifier, point) => { + let default_action_allowed = + self.handle_touch_event(pipeline_id, identifier, point, "touchstart"); + if default_action_allowed { + // TODO: Wait to see if preventDefault is called on the first touchmove event. + self.compositor.borrow_mut().send(ScriptToCompositorMsg::TouchEventProcessed( + EventResult::DefaultAllowed)).unwrap(); + } else { + self.compositor.borrow_mut().send(ScriptToCompositorMsg::TouchEventProcessed( + EventResult::DefaultPrevented)).unwrap(); + } + } + + TouchMoveEvent(identifier, point) => { + self.handle_touch_event(pipeline_id, identifier, point, "touchmove"); + } + + TouchUpEvent(identifier, point) => { + self.handle_touch_event(pipeline_id, identifier, point, "touchend"); + } + KeyEvent(key, state, modifiers) => { let page = get_page(&self.root_page(), pipeline_id); let document = page.document(); @@ -1803,6 +1825,17 @@ impl ScriptTask { document.r().handle_mouse_event(self.js_runtime.rt(), button, point, mouse_event_type); } + fn handle_touch_event(&self, + pipeline_id: PipelineId, + identifier: i32, + point: Point2D<f32>, + event_name: &str) -> bool { + let page = get_page(&self.root_page(), pipeline_id); + let document = page.document(); + document.r().handle_touch_event(self.js_runtime.rt(), identifier, point, + event_name.to_owned()) + } + /// https://html.spec.whatwg.org/multipage/#navigating-across-documents /// The entry point for content to notify that a new load has been requested /// for the given pipeline (specifically the "navigate" algorithm). diff --git a/components/script_traits/lib.rs b/components/script_traits/lib.rs index 3c04fa36dda..8aa32cdcaae 100644 --- a/components/script_traits/lib.rs +++ b/components/script_traits/lib.rs @@ -150,7 +150,7 @@ pub enum ConstellationControlMsg { } /// The mouse button involved in the event. -#[derive(Clone, Debug)] +#[derive(Clone, Copy, Debug)] pub enum MouseButton { /// The left mouse button. Left, @@ -172,6 +172,12 @@ pub enum CompositorEvent { MouseUpEvent(MouseButton, Point2D<f32>), /// The mouse was moved over a point. MouseMoveEvent(Point2D<f32>), + /// A touch began at a point. + TouchDownEvent(i32, Point2D<f32>), + /// A touch was moved over a point. + TouchMoveEvent(i32, Point2D<f32>), + /// A touch ended at a point. + TouchUpEvent(i32, Point2D<f32>), /// A key was pressed. KeyEvent(Key, KeyState, KeyModifiers), } |