From 4ed15a8853fc49d0940ed24d939fd0c407ee80a9 Mon Sep 17 00:00:00 2001 From: Matt Brubeck Date: Mon, 10 Aug 2015 20:55:19 -0700 Subject: [PATCH 1/4] Add bindings for TouchEvent DOM interfaces --- components/script/dom/bindings/num.rs | 2 +- components/script/dom/mod.rs | 3 + components/script/dom/touch.rs | 80 ++++++++++++ components/script/dom/touchevent.rs | 117 ++++++++++++++++++ components/script/dom/touchlist.rs | 50 ++++++++ components/script/dom/webidls/Touch.webidl | 21 ++++ .../script/dom/webidls/TouchEvent.webidl | 16 +++ .../script/dom/webidls/TouchList.webidl | 11 ++ .../wpt/mozilla/tests/mozilla/interfaces.html | 3 + 9 files changed, 302 insertions(+), 1 deletion(-) create mode 100644 components/script/dom/touch.rs create mode 100644 components/script/dom/touchevent.rs create mode 100644 components/script/dom/touchlist.rs create mode 100644 components/script/dom/webidls/Touch.webidl create mode 100644 components/script/dom/webidls/TouchEvent.webidl create mode 100644 components/script/dom/webidls/TouchList.webidl diff --git a/components/script/dom/bindings/num.rs b/components/script/dom/bindings/num.rs index 1169ccdec7f..ed5d78cdb6c 100644 --- a/components/script/dom/bindings/num.rs +++ b/components/script/dom/bindings/num.rs @@ -9,7 +9,7 @@ use num::Float; use std::ops::Deref; /// Encapsulates the IDL restricted float type. -#[derive(JSTraceable, Clone, Eq, PartialEq)] +#[derive(JSTraceable, Clone, Copy, Eq, PartialEq)] pub struct Finite(T); unsafe impl Zeroable for Finite {} diff --git a/components/script/dom/mod.rs b/components/script/dom/mod.rs index ae4524cd05e..b248e2dac1c 100644 --- a/components/script/dom/mod.rs +++ b/components/script/dom/mod.rs @@ -345,6 +345,9 @@ pub mod testbindingproxy; pub mod text; pub mod textdecoder; pub mod textencoder; +pub mod touch; +pub mod touchevent; +pub mod touchlist; pub mod treewalker; pub mod uievent; pub mod url; diff --git a/components/script/dom/touch.rs b/components/script/dom/touch.rs new file mode 100644 index 00000000000..7fc9f42e23d --- /dev/null +++ b/components/script/dom/touch.rs @@ -0,0 +1,80 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use dom::bindings::codegen::Bindings::TouchBinding; +use dom::bindings::codegen::Bindings::TouchBinding::TouchMethods; +use dom::bindings::global::GlobalRef; +use dom::bindings::js::{JS, MutHeap, Root}; +use dom::bindings::num::Finite; +use dom::bindings::utils::{Reflector, reflect_dom_object}; +use dom::eventtarget::EventTarget; +use dom::window::Window; + +#[dom_struct] +pub struct Touch { + reflector_: Reflector, + identifier: i32, + target: MutHeap>, + screen_x: f64, + screen_y: f64, + client_x: f64, + client_y: f64, +} + +impl Touch { + fn new_inherited(identifier: i32, target: &EventTarget, + screen_x: Finite, screen_y: Finite, + client_x: Finite, client_y: Finite) -> Touch { + Touch { + reflector_: Reflector::new(), + identifier: identifier, + target: MutHeap::new(target), + screen_x: *screen_x, + screen_y: *screen_y, + client_x: *client_x, + client_y: *client_y, + } + } + + pub fn new(window: &Window, identifier: i32, target: &EventTarget, + screen_x: Finite, screen_y: Finite, + client_x: Finite, client_y: Finite) -> Root { + reflect_dom_object(box Touch::new_inherited(identifier, target, + screen_x, screen_y, + client_x, client_y), + GlobalRef::Window(window), TouchBinding::Wrap) + } +} + +impl TouchMethods for Touch { + /// https://w3c.github.io/touch-events/#widl-Touch-identifier + fn Identifier(&self) -> i32 { + self.identifier + } + + /// https://w3c.github.io/touch-events/#widl-Touch-target + fn Target(&self) -> Root { + self.target.get() + } + + /// https://w3c.github.io/touch-events/#widl-Touch-screenX + fn ScreenX(&self) -> Finite { + Finite::wrap(self.screen_x) + } + + /// https://w3c.github.io/touch-events/#widl-Touch-screenY + fn ScreenY(&self) -> Finite { + Finite::wrap(self.screen_y) + } + + /// https://w3c.github.io/touch-events/#widl-Touch-clientX + fn ClientX(&self) -> Finite { + Finite::wrap(self.client_x) + } + + /// https://w3c.github.io/touch-events/#widl-Touch-clientY + fn ClientY(&self) -> Finite { + Finite::wrap(self.client_y) + } +} diff --git a/components/script/dom/touchevent.rs b/components/script/dom/touchevent.rs new file mode 100644 index 00000000000..99bac602bd6 --- /dev/null +++ b/components/script/dom/touchevent.rs @@ -0,0 +1,117 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use dom::bindings::codegen::Bindings::TouchEventBinding; +use dom::bindings::codegen::Bindings::TouchEventBinding::TouchEventMethods; +use dom::bindings::codegen::Bindings::UIEventBinding::UIEventMethods; +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::touchlist::TouchList; +use dom::uievent::UIEvent; +use dom::window::Window; +use std::cell::Cell; +use util::str::DOMString; + +#[dom_struct] +pub struct TouchEvent { + uievent: UIEvent, + touches: MutHeap>, + target_touches: MutHeap>, + changed_touches: MutHeap>, + alt_key: Cell, + meta_key: Cell, + ctrl_key: Cell, + shift_key: Cell, +} + +impl TouchEvent { + fn new_inherited(touches: &TouchList, + changed_touches: &TouchList, + target_touches: &TouchList) -> TouchEvent { + TouchEvent { + uievent: UIEvent::new_inherited(), + touches: MutHeap::new(touches), + target_touches: MutHeap::new(target_touches), + changed_touches: MutHeap::new(changed_touches), + ctrl_key: Cell::new(false), + shift_key: Cell::new(false), + alt_key: Cell::new(false), + meta_key: Cell::new(false), + } + } + + pub fn new_uninitialized(window: &Window, + touches: &TouchList, + changed_touches: &TouchList, + target_touches: &TouchList) -> Root { + reflect_dom_object(box TouchEvent::new_inherited(touches, changed_touches, target_touches), + GlobalRef::Window(window), + TouchEventBinding::Wrap) + } + + pub fn new(window: &Window, + type_: DOMString, + canBubble: EventBubbles, + cancelable: EventCancelable, + view: Option<&Window>, + detail: i32, + touches: &TouchList, + changed_touches: &TouchList, + target_touches: &TouchList, + ctrlKey: bool, + altKey: bool, + shiftKey: bool, + metaKey: bool) -> Root { + let ev = TouchEvent::new_uninitialized(window, touches, changed_touches, target_touches); + ev.upcast::().InitUIEvent(type_, + canBubble == EventBubbles::Bubbles, + cancelable == EventCancelable::Cancelable, + view, detail); + ev.ctrl_key.set(ctrlKey); + ev.alt_key.set(altKey); + ev.shift_key.set(shiftKey); + ev.meta_key.set(metaKey); + ev + } +} + +impl<'a> TouchEventMethods for &'a TouchEvent { + /// https://w3c.github.io/touch-events/#widl-TouchEvent-ctrlKey + fn CtrlKey(&self) -> bool { + self.ctrl_key.get() + } + + /// https://w3c.github.io/touch-events/#widl-TouchEvent-shiftKey + fn ShiftKey(&self) -> bool { + self.shift_key.get() + } + + /// https://w3c.github.io/touch-events/#widl-TouchEvent-altKey + fn AltKey(&self) -> bool { + self.alt_key.get() + } + + /// https://w3c.github.io/touch-events/#widl-TouchEvent-metaKey + fn MetaKey(&self) -> bool { + self.meta_key.get() + } + + /// https://w3c.github.io/touch-events/#widl-TouchEventInit-touches + fn Touches(&self) -> Root { + self.touches.get() + } + + /// https://w3c.github.io/touch-events/#widl-TouchEvent-targetTouches + fn TargetTouches(&self) -> Root { + self.target_touches.get() + } + + /// https://w3c.github.io/touch-events/#widl-TouchEvent-changedTouches + fn ChangedTouches(&self) -> Root { + self.changed_touches.get() + } +} diff --git a/components/script/dom/touchlist.rs b/components/script/dom/touchlist.rs new file mode 100644 index 00000000000..5806f945008 --- /dev/null +++ b/components/script/dom/touchlist.rs @@ -0,0 +1,50 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use dom::bindings::codegen::Bindings::TouchListBinding; +use dom::bindings::codegen::Bindings::TouchListBinding::TouchListMethods; +use dom::bindings::global::GlobalRef; +use dom::bindings::js::{JS, Root}; +use dom::bindings::utils::{Reflector, reflect_dom_object}; +use dom::touch::Touch; +use dom::window::Window; + +#[dom_struct] +pub struct TouchList { + reflector_: Reflector, + touches: Vec>, +} + +impl TouchList { + fn new_inherited(touches: &[&Touch]) -> TouchList { + TouchList { + reflector_: Reflector::new(), + touches: touches.iter().map(|touch| JS::from_ref(*touch)).collect(), + } + } + + pub fn new(window: &Window, touches: &[&Touch]) -> Root { + reflect_dom_object(box TouchList::new_inherited(touches), + GlobalRef::Window(window), TouchListBinding::Wrap) + } +} + +impl TouchListMethods for TouchList { + /// https://w3c.github.io/touch-events/#widl-TouchList-length + fn Length(&self) -> u32 { + self.touches.len() as u32 + } + + /// https://w3c.github.io/touch-events/#widl-TouchList-item-getter-Touch-unsigned-long-index + fn Item(&self, index: u32) -> Option> { + self.touches.get(index as usize).map(JS::root) + } + + /// https://w3c.github.io/touch-events/#widl-TouchList-item-getter-Touch-unsigned-long-index + fn IndexedGetter(&self, index: u32, found: &mut bool) -> Option> { + let item = self.Item(index); + *found = item.is_some(); + item + } +} diff --git a/components/script/dom/webidls/Touch.webidl b/components/script/dom/webidls/Touch.webidl new file mode 100644 index 00000000000..2504c11a2bf --- /dev/null +++ b/components/script/dom/webidls/Touch.webidl @@ -0,0 +1,21 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// http://w3c.github.io/touch-events/#idl-def-Touch + +interface Touch { + readonly attribute long identifier; + readonly attribute EventTarget target; + readonly attribute double screenX; + readonly attribute double screenY; + readonly attribute double clientX; + readonly attribute double clientY; + // readonly attribute double pageX; + // readonly attribute double pageY; + // readonly attribute float radiusX; + // readonly attribute float radiusY; + // readonly attribute float rotationAngle; + // readonly attribute float force; +}; diff --git a/components/script/dom/webidls/TouchEvent.webidl b/components/script/dom/webidls/TouchEvent.webidl new file mode 100644 index 00000000000..b291c23be16 --- /dev/null +++ b/components/script/dom/webidls/TouchEvent.webidl @@ -0,0 +1,16 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// http://w3c.github.io/touch-events/#idl-def-TouchEvent + +interface TouchEvent : UIEvent { + readonly attribute TouchList touches; + readonly attribute TouchList targetTouches; + readonly attribute TouchList changedTouches; + readonly attribute boolean altKey; + readonly attribute boolean metaKey; + readonly attribute boolean ctrlKey; + readonly attribute boolean shiftKey; +}; diff --git a/components/script/dom/webidls/TouchList.webidl b/components/script/dom/webidls/TouchList.webidl new file mode 100644 index 00000000000..f75cc4c9530 --- /dev/null +++ b/components/script/dom/webidls/TouchList.webidl @@ -0,0 +1,11 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// http://w3c.github.io/touch-events/#idl-def-TouchList + +interface TouchList { + readonly attribute unsigned long length; + getter Touch? item (unsigned long index); +}; diff --git a/tests/wpt/mozilla/tests/mozilla/interfaces.html b/tests/wpt/mozilla/tests/mozilla/interfaces.html index ef3ae24d9fe..43a1cb8e6a5 100644 --- a/tests/wpt/mozilla/tests/mozilla/interfaces.html +++ b/tests/wpt/mozilla/tests/mozilla/interfaces.html @@ -198,6 +198,9 @@ var interfaceNamesInGlobalScope = [ "Text", "TextDecoder", "TextEncoder", + "Touch", + "TouchEvent", + "TouchList", "TreeWalker", "UIEvent", "URL", From fe7460f34d20d5f17d21d60e1053028b21c63ebc Mon Sep 17 00:00:00 2001 From: Matt Brubeck Date: Fri, 14 Aug 2015 07:24:50 -0700 Subject: [PATCH 2/4] 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. --- components/compositing/compositor.rs | 152 ++++++++++++++++++++- components/compositing/compositor_layer.rs | 24 ++-- components/compositing/compositor_task.rs | 9 +- components/compositing/headless.rs | 1 + components/msg/compositor_msg.rs | 7 + components/script/dom/document.rs | 53 +++++++ components/script/dom/touchevent.rs | 2 +- components/script/script_task.rs | 35 ++++- 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 { /// The application window. @@ -156,6 +177,15 @@ pub struct IOCompositor { /// Pending scroll to fragment event, if any fragment_point: Option>, + /// Touch input state machine + touch_gesture_state: TouchState, + + /// When tracking a touch for gesture detection, the point where it started + first_touch_point: Option>, + + /// When tracking a touch for gesture detection, its most recent point + last_touch_point: Option>, + /// Pending scroll events. pending_scroll_events: Vec, @@ -295,6 +325,9 @@ impl IOCompositor { 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 IOCompositor { } } + (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 IOCompositor { } } + fn on_touch_down(&mut self, identifier: i32, point: TypedPoint2D) { + 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) { + 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 = 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) { + // 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) { + 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, cursor: TypedPoint2D) { 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) where Window: WindowMethods; + fn send_event(&self, + compositor: &IOCompositor, + event: CompositorEvent) + where Window: WindowMethods; + fn clamp_scroll_offset_and_scroll_layer(&self, new_offset: TypedPoint2D) -> ScrollEventResult; @@ -372,22 +378,22 @@ impl CompositorLayer for Layer { 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(&self, compositor: &IOCompositor, cursor: TypedPoint2D) where Window: WindowMethods { - let message = MouseMoveEvent(cursor.to_untyped()); + self.send_event(compositor, MouseMoveEvent(cursor.to_untyped())); + } + + fn send_event(&self, + compositor: &IOCompositor, + 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 { 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, Point2D)>), MoveTo(Point2D), ResizeTo(Size2D), + 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, + 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::() { + Some(el) => Root::from_ref(el), + None => { + let parent = node.r().GetParentNode(); + match parent.and_then(Root::downcast::) { + Some(parent) => parent, + None => return false + } + }, + }; + let target = el.upcast::(); + + 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::(); + 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, + 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), /// The mouse was moved over a point. MouseMoveEvent(Point2D), + /// A touch began at a point. + TouchDownEvent(i32, Point2D), + /// A touch was moved over a point. + TouchMoveEvent(i32, Point2D), + /// A touch ended at a point. + TouchUpEvent(i32, Point2D), /// A key was pressed. KeyEvent(Key, KeyState, KeyModifiers), } From 817eed22d13f8ebe3059fee6692ccfd96a71abbc Mon Sep 17 00:00:00 2001 From: Matt Brubeck Date: Fri, 14 Aug 2015 07:25:43 -0700 Subject: [PATCH 3/4] Add a "-Z convert-mouse-to-touch" debug argument. This is enabled by default on Android, because Glutin currently sends mouse events instead of touch events on Android. It's also useful for testing on non-touch platforms. --- components/compositing/compositor.rs | 18 ++++++++++++++++-- components/util/opts.rs | 16 ++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/components/compositing/compositor.rs b/components/compositing/compositor.rs index 0b44a715e88..3482d730be4 100644 --- a/components/compositing/compositor.rs +++ b/components/compositing/compositor.rs @@ -1123,7 +1123,16 @@ impl IOCompositor { chan.send(msg).unwrap() } - fn on_mouse_window_event_class(&self, mouse_window_event: MouseWindowEvent) { + fn on_mouse_window_event_class(&mut self, mouse_window_event: MouseWindowEvent) { + if opts::get().convert_mouse_to_touch { + match mouse_window_event { + MouseWindowEvent::Click(_, _) => {} + MouseWindowEvent::MouseDown(_, p) => self.on_touch_down(0, p), + MouseWindowEvent::MouseUp(_, p) => self.on_touch_up(0, p), + } + return + } + let point = match mouse_window_event { MouseWindowEvent::Click(_, p) => p, MouseWindowEvent::MouseDown(_, p) => p, @@ -1135,7 +1144,12 @@ impl IOCompositor { } } - fn on_mouse_window_move_event_class(&self, cursor: TypedPoint2D) { + fn on_mouse_window_move_event_class(&mut self, cursor: TypedPoint2D) { + if opts::get().convert_mouse_to_touch { + self.on_touch_move(0, cursor); + return + } + match self.find_topmost_layer_at_point(cursor / self.scene.scale) { Some(result) => result.layer.send_mouse_move_event(self, result.point), None => {}, diff --git a/components/util/opts.rs b/components/util/opts.rs index ea1d9609264..785f5909240 100644 --- a/components/util/opts.rs +++ b/components/util/opts.rs @@ -168,6 +168,9 @@ pub struct Opts { /// Whether to run absolute position calculation and display list construction in parallel. pub parallel_display_list_building: bool, + /// Translate mouse input into touch events. + pub convert_mouse_to_touch: bool, + /// True to exit after the page load (`-x`). pub exit_after_load: bool, @@ -247,6 +250,9 @@ pub struct DebugOptions { /// Build display lists in parallel. pub parallel_display_list_building: bool, + /// Translate mouse input into touch events. + pub convert_mouse_to_touch: bool, + /// Replace unpaires surrogates in DOM strings with U+FFFD. /// See https://github.com/servo/servo/issues/6564 pub replace_surrogates: bool, @@ -260,6 +266,12 @@ impl DebugOptions { pub fn new(debug_string: &str) -> Result { let mut debug_options = DebugOptions::default(); + // FIXME: Glutin currently converts touch input to mouse events on Android. + // Convert it back to touch events. + if cfg!(target_os = "android") { + debug_options.convert_mouse_to_touch = true; + } + for option in debug_string.split(',') { match option { "help" => debug_options.help = true, @@ -283,6 +295,7 @@ impl DebugOptions { "validate-display-list-geometry" => debug_options.validate_display_list_geometry = true, "disable-share-style-cache" => debug_options.disable_share_style_cache = true, "parallel-display-list-building" => debug_options.parallel_display_list_building = true, + "convert-mouse-to-touch" => debug_options.convert_mouse_to_touch = true, "replace-surrogates" => debug_options.replace_surrogates = true, "gc-profile" => debug_options.gc_profile = true, "" => {}, @@ -326,6 +339,7 @@ pub fn print_debug_usage(app: &str) -> ! { print_option("replace-surrogates", "Replace unpaires surrogates in DOM strings with U+FFFD. \ See https://github.com/servo/servo/issues/6564"); print_option("gc-profile", "Log GC passes and their durations."); + print_option("convert-mouse-to-touch", "Send touch events instead of mouse events"); println!(""); @@ -422,6 +436,7 @@ pub fn default_opts() -> Opts { sniff_mime_types: false, disable_share_style_cache: false, parallel_display_list_building: false, + convert_mouse_to_touch: false, exit_after_load: false, no_native_titlebar: false, } @@ -628,6 +643,7 @@ pub fn from_cmdline_args(args: &[String]) { sniff_mime_types: opt_match.opt_present("sniff-mime-types"), disable_share_style_cache: debug_options.disable_share_style_cache, parallel_display_list_building: debug_options.parallel_display_list_building, + convert_mouse_to_touch: debug_options.convert_mouse_to_touch, exit_after_load: opt_match.opt_present("x"), no_native_titlebar: opt_match.opt_present("b"), }; From 316802e20684fe90cc302529c39a025a8a9020e3 Mon Sep 17 00:00:00 2001 From: Matt Brubeck Date: Thu, 22 Oct 2015 09:19:29 -0700 Subject: [PATCH 4/4] Implement Document.createTouch --- components/script/dom/document.rs | 32 +++++++++++++++---- components/script/dom/touch.rs | 23 +++++++++++-- components/script/dom/webidls/Document.webidl | 13 ++++++++ components/script/dom/webidls/Touch.webidl | 4 +-- tests/wpt/include.ini | 2 ++ .../create-touch-touchlist.html.ini | 10 ++++++ 6 files changed, 73 insertions(+), 11 deletions(-) create mode 100644 tests/wpt/metadata/touch-events/create-touch-touchlist.html.ini diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index f987d73747c..e8c23b5994c 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -594,7 +594,7 @@ impl Document { EventCancelable::Cancelable, Some(&self.window), clickCount, - x, y, x, y, + x, y, x, y, // TODO: Get real screen coordinates? false, false, false, false, 0i16, None); @@ -723,13 +723,18 @@ impl Document { }, }; let target = el.upcast::(); - - 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 client_x = Finite::wrap(point.x as f64); + let client_y = Finite::wrap(point.y as f64); + let page_x = Finite::wrap(point.x as f64 + window.PageXOffset() as f64); + let page_y = Finite::wrap(point.y as f64 + window.PageYOffset() as f64); + + let touch = Touch::new(window.r(), identifier, target, + client_x, client_y, // TODO: Get real screen coordinates? + client_x, client_y, + page_x, page_y); + let mut touches = RootedVec::new(); touches.push(JS::from_rooted(&touch)); let touches = TouchList::new(window.r(), touches.r()); @@ -1414,6 +1419,21 @@ impl DocumentMethods for Document { NodeIterator::new(self, root, whatToShow, filter) } + // https://w3c.github.io/touch-events/#idl-def-Document + fn CreateTouch(&self, + window: &Window, + target: &EventTarget, + identifier: i32, + pageX: Finite, + pageY: Finite, + screenX: Finite, + screenY: Finite) + -> Root { + let clientX = Finite::wrap(*pageX - window.PageXOffset() as f64); + let clientY = Finite::wrap(*pageY - window.PageYOffset() as f64); + Touch::new(window, identifier, target, screenX, screenY, clientX, clientY, pageX, pageY) + } + // https://dom.spec.whatwg.org/#dom-document-createtreewalker fn CreateTreeWalker(&self, root: &Node, whatToShow: u32, filter: Option>) -> Root { diff --git a/components/script/dom/touch.rs b/components/script/dom/touch.rs index 7fc9f42e23d..9e25dacf422 100644 --- a/components/script/dom/touch.rs +++ b/components/script/dom/touch.rs @@ -20,12 +20,15 @@ pub struct Touch { screen_y: f64, client_x: f64, client_y: f64, + page_x: f64, + page_y: f64, } impl Touch { fn new_inherited(identifier: i32, target: &EventTarget, screen_x: Finite, screen_y: Finite, - client_x: Finite, client_y: Finite) -> Touch { + client_x: Finite, client_y: Finite, + page_x: Finite, page_y: Finite) -> Touch { Touch { reflector_: Reflector::new(), identifier: identifier, @@ -34,15 +37,19 @@ impl Touch { screen_y: *screen_y, client_x: *client_x, client_y: *client_y, + page_x: *page_x, + page_y: *page_y, } } pub fn new(window: &Window, identifier: i32, target: &EventTarget, screen_x: Finite, screen_y: Finite, - client_x: Finite, client_y: Finite) -> Root { + client_x: Finite, client_y: Finite, + page_x: Finite, page_y: Finite) -> Root { reflect_dom_object(box Touch::new_inherited(identifier, target, screen_x, screen_y, - client_x, client_y), + client_x, client_y, + page_x, page_y), GlobalRef::Window(window), TouchBinding::Wrap) } } @@ -77,4 +84,14 @@ impl TouchMethods for Touch { fn ClientY(&self) -> Finite { Finite::wrap(self.client_y) } + + /// https://w3c.github.io/touch-events/#widl-Touch-clientX + fn PageX(&self) -> Finite { + Finite::wrap(self.page_x) + } + + /// https://w3c.github.io/touch-events/#widl-Touch-clientY + fn PageY(&self) -> Finite { + Finite::wrap(self.page_y) + } } diff --git a/components/script/dom/webidls/Document.webidl b/components/script/dom/webidls/Document.webidl index edf2fb59b06..5ccd72d087d 100644 --- a/components/script/dom/webidls/Document.webidl +++ b/components/script/dom/webidls/Document.webidl @@ -159,3 +159,16 @@ partial interface Document { // Tracking issue for document.all: https://github.com/servo/servo/issues/7396 // readonly attribute HTMLAllCollection all; }; + +// http://w3c.github.io/touch-events/#idl-def-Document +partial interface Document { + Touch createTouch(Window/*Proxy*/ view, + EventTarget target, + long identifier, + double pageX, + double pageY, + double screenX, + double screenY); + // FIXME (#8159): + // TouchList createTouchList(Touch... touches); +}; diff --git a/components/script/dom/webidls/Touch.webidl b/components/script/dom/webidls/Touch.webidl index 2504c11a2bf..9805121446e 100644 --- a/components/script/dom/webidls/Touch.webidl +++ b/components/script/dom/webidls/Touch.webidl @@ -12,8 +12,8 @@ interface Touch { readonly attribute double screenY; readonly attribute double clientX; readonly attribute double clientY; - // readonly attribute double pageX; - // readonly attribute double pageY; + readonly attribute double pageX; + readonly attribute double pageY; // readonly attribute float radiusX; // readonly attribute float radiusY; // readonly attribute float rotationAngle; diff --git a/tests/wpt/include.ini b/tests/wpt/include.ini index d59d547498b..6e061ea725e 100644 --- a/tests/wpt/include.ini +++ b/tests/wpt/include.ini @@ -15,6 +15,8 @@ skip: true skip: false [url] skip: false +[touch-events] + skip: false [workers] skip: false [XMLHttpRequest] diff --git a/tests/wpt/metadata/touch-events/create-touch-touchlist.html.ini b/tests/wpt/metadata/touch-events/create-touch-touchlist.html.ini new file mode 100644 index 00000000000..35632d4a9e0 --- /dev/null +++ b/tests/wpt/metadata/touch-events/create-touch-touchlist.html.ini @@ -0,0 +1,10 @@ +[create-touch-touchlist.html] + type: testharness + [document.createTouchList exists and correctly creates a TouchList from zero Touch objects] + expected: FAIL + + [document.createTouchList exists and correctly creates a TouchList from a single Touch] + expected: FAIL + + [document.createTouchList exists and correctly creates a TouchList from two Touch objects] + expected: FAIL