servo: Merge #18704 - Switch to using WebRender hit testing (from mrobinson:wr-hit-testing); r=jdm,glennw,mbrubeck
authorMartin Robinson <mrobinson@igalia.com>
Tue, 17 Oct 2017 17:09:25 -0500
changeset 386859 edc055534a733ff284e8b0e05f2c27560e55b2d4
parent 386858 513578be7da23c6259c78d1351bc02d0c424514e
child 386860 2dd18979613dd819e16fda8fa65a5d287b1a3f66
push id96311
push userarchaeopteryx@coole-files.de
push dateWed, 18 Oct 2017 09:52:02 +0000
treeherdermozilla-inbound@a8a1e8cc1980 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjdm, glennw, mbrubeck
milestone58.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
servo: Merge #18704 - Switch to using WebRender hit testing (from mrobinson:wr-hit-testing); r=jdm,glennw,mbrubeck This trades quite a bit of complicated code in Servo for few more messages and a significant performance improvement. In particular, WebRender can search the entire display list at once instead of ping-ponging down the pipeline tree. This allows us to send mouse events to the correct pipeline immediately. <!-- Please describe your changes on the following line: --> --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: --> - [x] `./mach build -d` does not report any errors - [x] `./mach test-tidy` does not report any errors - [ ] These changes fix #__ (github issue number if applicable). <!-- Either: --> - [ ] There are tests for these changes OR - [x] These changes do not require tests because they should not change behavior. <!-- Also, please make sure that "Allow edits from maintainers" checkbox is checked, so that we can help you if you get stuck somewhere along the way.--> <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. --> Source-Repo: https://github.com/servo/servo Source-Revision: ca08271345f78fa881c174545f5b69a8ccb78143
servo/Cargo.lock
servo/components/compositing/Cargo.toml
servo/components/compositing/compositor.rs
servo/components/compositing/compositor_thread.rs
servo/components/compositing/lib.rs
servo/components/constellation/constellation.rs
servo/components/gfx/display_list/mod.rs
servo/components/layout/query.rs
servo/components/layout/webrender_helpers.rs
servo/components/layout_thread/Cargo.toml
servo/components/layout_thread/lib.rs
servo/components/msg/constellation_msg.rs
servo/components/script/dom/document.rs
servo/components/script/dom/window.rs
servo/components/script/script_thread.rs
servo/components/script_layout_interface/message.rs
servo/components/script_layout_interface/rpc.rs
servo/components/script_traits/lib.rs
servo/components/script_traits/script_msg.rs
servo/components/style_traits/cursor.rs
--- a/servo/Cargo.lock
+++ b/servo/Cargo.lock
@@ -470,16 +470,17 @@ dependencies = [
 name = "compositing"
 version = "0.0.1"
 dependencies = [
  "euclid 0.15.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "gfx_traits 0.0.1",
  "gleam 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "image 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "ipc-channel 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "msg 0.0.1",
  "net_traits 0.0.1",
  "nonzero 0.0.1",
  "profile_traits 0.0.1",
  "script_traits 0.0.1",
  "servo_config 0.0.1",
  "servo_geometry 0.0.1",
@@ -1562,16 +1563,17 @@ dependencies = [
  "gfx 0.0.1",
  "gfx_traits 0.0.1",
  "heapsize 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "html5ever 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "ipc-channel 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "layout 0.0.1",
  "layout_traits 0.0.1",
  "lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "metrics 0.0.1",
  "msg 0.0.1",
  "net_traits 0.0.1",
  "nonzero 0.0.1",
  "parking_lot 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "profile_traits 0.0.1",
  "range 0.0.1",
--- a/servo/components/compositing/Cargo.toml
+++ b/servo/components/compositing/Cargo.toml
@@ -10,16 +10,17 @@ name = "compositing"
 path = "lib.rs"
 
 [dependencies]
 euclid = "0.15"
 gfx_traits = {path = "../gfx_traits"}
 gleam = "0.4"
 image = "0.16"
 ipc-channel = "0.9"
+libc = "0.2"
 log = "0.3.5"
 msg = {path = "../msg"}
 net_traits = {path = "../net_traits"}
 nonzero = {path = "../nonzero"}
 profile_traits = {path = "../profile_traits"}
 script_traits = {path = "../script_traits"}
 servo_config = {path = "../config"}
 servo_geometry = {path = "../geometry"}
--- a/servo/components/compositing/compositor.rs
+++ b/servo/components/compositing/compositor.rs
@@ -1,45 +1,46 @@
 /* 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 CompositionPipeline;
 use SendableFrameTree;
 use compositor_thread::{CompositorProxy, CompositorReceiver};
 use compositor_thread::{InitialCompositorState, Msg, RenderListener};
-use euclid::{Point2D, TypedPoint2D, TypedVector2D, ScaleFactor};
+use euclid::{TypedPoint2D, TypedVector2D, ScaleFactor};
 use gfx_traits::Epoch;
 use gleam::gl;
 use image::{DynamicImage, ImageFormat, RgbImage};
 use ipc_channel::ipc::{self, IpcSharedMemory};
+use libc::c_void;
 use msg::constellation_msg::{PipelineId, PipelineIndex, PipelineNamespaceId};
 use net_traits::image::base::{Image, PixelFormat};
 use nonzero::NonZero;
 use profile_traits::time::{self, ProfilerCategory, profile};
-use script_traits::{AnimationState, AnimationTickType, ConstellationControlMsg};
-use script_traits::{ConstellationMsg, LayoutControlMsg, MouseButton};
-use script_traits::{MouseEventType, ScrollState};
-use script_traits::{TouchpadPressurePhase, TouchEventType, TouchId, WindowSizeData, WindowSizeType};
-use script_traits::CompositorEvent::{self, MouseMoveEvent, MouseButtonEvent, TouchEvent, TouchpadPressureEvent};
+use script_traits::{AnimationState, AnimationTickType, ConstellationMsg, LayoutControlMsg};
+use script_traits::{MouseButton, MouseEventType, ScrollState, TouchEventType, TouchId};
+use script_traits::{TouchpadPressurePhase, UntrustedNodeAddress, WindowSizeData, WindowSizeType};
+use script_traits::CompositorEvent::{MouseMoveEvent, MouseButtonEvent, TouchEvent, TouchpadPressureEvent};
 use servo_config::opts;
 use servo_config::prefs::PREFS;
 use servo_geometry::DeviceIndependentPixel;
 use std::collections::HashMap;
 use std::fs::File;
 use std::rc::Rc;
 use std::sync::mpsc::Sender;
 use std::time::{Duration, Instant};
 use style_traits::{CSSPixel, DevicePixel, PinchZoomFactor};
+use style_traits::cursor::Cursor;
 use style_traits::viewport::ViewportConstraints;
 use time::{precise_time_ns, precise_time_s};
 use touch::{TouchHandler, TouchAction};
 use webrender;
-use webrender_api::{self, ClipId, DeviceUintRect, DeviceUintSize, LayoutPoint, LayoutVector2D};
-use webrender_api::{ScrollEventPhase, ScrollLocation, ScrollClamping};
+use webrender_api::{self, DeviceUintRect, DeviceUintSize, HitTestFlags, HitTestResult};
+use webrender_api::{LayoutVector2D, ScrollEventPhase, ScrollLocation};
 use windowing::{self, MouseWindowEvent, WebRenderDebugOption, WindowMethods};
 
 #[derive(Debug, PartialEq)]
 enum UnableToComposite {
     WindowUnprepared,
     NotReadyToPaintImage(NotReadyToPaint),
 }
 
@@ -459,21 +460,16 @@ impl<Window: WindowMethods> IOCompositor
             }
 
             (Msg::SetFrameTree(frame_tree),
              ShutdownState::NotShuttingDown) => {
                 self.set_frame_tree(&frame_tree);
                 self.send_viewport_rects();
             }
 
-            (Msg::ScrollFragmentPoint(scroll_root_id, point, _),
-             ShutdownState::NotShuttingDown) => {
-                self.scroll_fragment_to_point(scroll_root_id, point);
-            }
-
             (Msg::Recomposite(reason), ShutdownState::NotShuttingDown) => {
                 self.composition_request = CompositionRequest::CompositeNow(reason)
             }
 
 
             (Msg::TouchEventProcessed(result), ShutdownState::NotShuttingDown) => {
                 self.touch_handler.on_event_processed(result);
             }
@@ -651,23 +647,16 @@ impl<Window: WindowMethods> IOCompositor
         };
         let msg = ConstellationMsg::WindowSize(top_level_browsing_context_id, data, size_type);
 
         if let Err(e) = self.constellation_chan.send(msg) {
             warn!("Sending window resize to constellation failed ({}).", e);
         }
     }
 
-    fn scroll_fragment_to_point(&mut self, id: ClipId, point: Point2D<f32>) {
-        self.webrender_api.scroll_node_with_id(self.webrender_document,
-                                               LayoutPoint::from_untyped(&point),
-                                               id,
-                                               ScrollClamping::ToContentBounds);
-    }
-
     pub fn on_resize_window_event(&mut self, new_size: DeviceUintSize) {
         debug!("compositor resizing to {:?}", new_size.to_untyped());
 
         // A size change could also mean a resolution change.
         let new_scale_factor = self.window.hidpi_factor();
         if self.scale_factor != new_scale_factor {
             self.scale_factor = new_scale_factor;
             self.update_zoom_transform();
@@ -702,42 +691,57 @@ impl<Window: WindowMethods> IOCompositor
 
     fn dispatch_mouse_window_event_class(&mut self, mouse_window_event: MouseWindowEvent) {
         let point = match mouse_window_event {
             MouseWindowEvent::Click(_, p) => p,
             MouseWindowEvent::MouseDown(_, p) => p,
             MouseWindowEvent::MouseUp(_, p) => p,
         };
 
-        let root_pipeline_id = match self.get_root_pipeline_id() {
-            Some(root_pipeline_id) => root_pipeline_id,
+        let results = self.hit_test_at_point(point);
+        let result = match results.items.first() {
+            Some(result) => result,
             None => return,
         };
 
-        if let Some(pipeline) = self.pipeline(root_pipeline_id) {
-            let dppx = self.page_zoom * self.hidpi_factor();
-            let translated_point = (point / dppx).to_untyped();
-            let event_to_send = match mouse_window_event {
-                MouseWindowEvent::Click(button, _) => {
-                    MouseButtonEvent(MouseEventType::Click, button, translated_point)
-                }
-                MouseWindowEvent::MouseDown(button, _) => {
-                    MouseButtonEvent(MouseEventType::MouseDown, button, translated_point)
-                }
-                MouseWindowEvent::MouseUp(button, _) => {
-                    MouseButtonEvent(MouseEventType::MouseUp, button, translated_point)
-                }
-            };
-            let msg = ConstellationControlMsg::SendEvent(root_pipeline_id, event_to_send);
-            if let Err(e) = pipeline.script_chan.send(msg) {
-                warn!("Sending control event to script failed ({}).", e);
+        let point = result.point_in_viewport.to_untyped();
+        let node_address = Some(UntrustedNodeAddress(result.tag.0 as *const c_void));
+        let event_to_send = match mouse_window_event {
+            MouseWindowEvent::Click(button, _) => {
+                MouseButtonEvent(MouseEventType::Click, button, point, node_address)
+            }
+            MouseWindowEvent::MouseDown(button, _) => {
+                MouseButtonEvent(MouseEventType::MouseDown, button, point, node_address)
+            }
+            MouseWindowEvent::MouseUp(button, _) => {
+                MouseButtonEvent(MouseEventType::MouseUp, button, point, node_address)
             }
+        };
+
+        let pipeline_id = PipelineId::from_webrender(result.pipeline);
+        let msg = ConstellationMsg::ForwardEvent(pipeline_id, event_to_send);
+        if let Err(e) = self.constellation_chan.send(msg) {
+            warn!("Sending event to constellation failed ({}).", e);
         }
     }
 
+    fn hit_test_at_point(&self, point: TypedPoint2D<f32, DevicePixel>) -> HitTestResult {
+        let dppx = self.page_zoom * self.hidpi_factor();
+        let scaled_point = (point / dppx).to_untyped();
+
+        let world_cursor = webrender_api::WorldPoint::from_untyped(&scaled_point);
+        self.webrender_api.hit_test(
+            self.webrender_document,
+            None,
+            world_cursor,
+            HitTestFlags::empty()
+        )
+
+    }
+
     pub fn on_mouse_window_move_event_class(&mut self, cursor: TypedPoint2D<f32, DevicePixel>) {
         if opts::get().convert_mouse_to_touch {
             self.on_touch_move(TouchId(0), cursor);
             return
         }
 
         self.dispatch_mouse_window_move_event_class(cursor);
     }
@@ -746,36 +750,53 @@ impl<Window: WindowMethods> IOCompositor
         let root_pipeline_id = match self.get_root_pipeline_id() {
             Some(root_pipeline_id) => root_pipeline_id,
             None => return,
         };
         if self.pipeline(root_pipeline_id).is_none() {
             return;
         }
 
-        let dppx = self.page_zoom * self.hidpi_factor();
-        let event_to_send = MouseMoveEvent(Some((cursor / dppx).to_untyped()));
-        let msg = ConstellationControlMsg::SendEvent(root_pipeline_id, event_to_send);
-        if let Some(pipeline) = self.pipeline(root_pipeline_id) {
-            if let Err(e) = pipeline.script_chan.send(msg) {
-                warn!("Sending mouse control event to script failed ({}).", e);
+        let results = self.hit_test_at_point(cursor);
+        if let Some(item) = results.items.first() {
+            let node_address = Some(UntrustedNodeAddress(item.tag.0 as *const c_void));
+            let event = MouseMoveEvent(Some(item.point_in_viewport.to_untyped()), node_address);
+            let pipeline_id = PipelineId::from_webrender(item.pipeline);
+            let msg = ConstellationMsg::ForwardEvent(pipeline_id, event);
+            if let Err(e) = self.constellation_chan.send(msg) {
+                warn!("Sending event to constellation failed ({}).", e);
+            }
+
+            if let Some(cursor) =  Cursor::from_u8(item.tag.1).ok() {
+                let msg = ConstellationMsg::SetCursor(cursor);
+                if let Err(e) = self.constellation_chan.send(msg) {
+                    warn!("Sending event to constellation failed ({}).", e);
+                }
             }
         }
     }
 
-    fn send_event_to_root_pipeline(&self, event: CompositorEvent) {
-        let root_pipeline_id = match self.get_root_pipeline_id() {
-            Some(root_pipeline_id) => root_pipeline_id,
-            None => return,
-        };
-
-        if let Some(pipeline) = self.pipeline(root_pipeline_id) {
-            let msg = ConstellationControlMsg::SendEvent(root_pipeline_id, event);
-            if let Err(e) = pipeline.script_chan.send(msg) {
-                warn!("Sending control event to script failed ({}).", e);
+    fn send_touch_event(
+        &self,
+        event_type: TouchEventType,
+        identifier: TouchId,
+        point: TypedPoint2D<f32, DevicePixel>)
+    {
+        let results = self.hit_test_at_point(point);
+        if let Some(item) = results.items.first() {
+            let event = TouchEvent(
+                event_type,
+                identifier,
+                item.point_in_viewport.to_untyped(),
+                Some(UntrustedNodeAddress(item.tag.0 as *const c_void)),
+            );
+            let pipeline_id = PipelineId::from_webrender(item.pipeline);
+            let msg = ConstellationMsg::ForwardEvent(pipeline_id, event);
+            if let Err(e) = self.constellation_chan.send(msg) {
+                warn!("Sending event to constellation failed ({}).", e);
             }
         }
     }
 
     pub fn on_touch_event(&mut self,
                           event_type: TouchEventType,
                           identifier: TouchId,
                           location: TypedPoint2D<f32, DevicePixel>) {
@@ -784,21 +805,17 @@ impl<Window: WindowMethods> IOCompositor
             TouchEventType::Move => self.on_touch_move(identifier, location),
             TouchEventType::Up => self.on_touch_up(identifier, location),
             TouchEventType::Cancel => self.on_touch_cancel(identifier, location),
         }
     }
 
     fn on_touch_down(&mut self, identifier: TouchId, point: TypedPoint2D<f32, DevicePixel>) {
         self.touch_handler.on_touch_down(identifier, point);
-        let dppx = self.page_zoom * self.hidpi_factor();
-        let translated_point = (point / dppx).to_untyped();
-        self.send_event_to_root_pipeline(TouchEvent(TouchEventType::Down,
-                                                    identifier,
-                                                    translated_point));
+        self.send_touch_event(TouchEventType::Down, identifier, point);
     }
 
     fn on_touch_move(&mut self, identifier: TouchId, point: TypedPoint2D<f32, DevicePixel>) {
         match self.touch_handler.on_touch_move(identifier, point) {
             TouchAction::Scroll(delta) => {
                 match point.cast() {
                     Some(point) => self.on_scroll_window_event(
                         ScrollLocation::Delta(
@@ -816,57 +833,58 @@ impl<Window: WindowMethods> IOCompositor
                     scroll_location: ScrollLocation::Delta(webrender_api::LayoutVector2D::from_untyped(
                                                            &scroll_delta.to_untyped())),
                     cursor: cursor,
                     phase: ScrollEventPhase::Move(true),
                     event_count: 1,
                 });
             }
             TouchAction::DispatchEvent => {
-                let dppx = self.page_zoom * self.hidpi_factor();
-                let translated_point = (point / dppx).to_untyped();
-                self.send_event_to_root_pipeline(TouchEvent(TouchEventType::Move,
-                                                            identifier,
-                                                            translated_point));
+                self.send_touch_event(TouchEventType::Move, identifier, point);
             }
             _ => {}
         }
     }
 
     fn on_touch_up(&mut self, identifier: TouchId, point: TypedPoint2D<f32, DevicePixel>) {
-        let dppx = self.page_zoom * self.hidpi_factor();
-        let translated_point = (point / dppx).to_untyped();
-        self.send_event_to_root_pipeline(TouchEvent(TouchEventType::Up,
-                                                    identifier,
-                                                    translated_point));
+        self.send_touch_event(TouchEventType::Up, identifier, point);
+
         if let TouchAction::Click = self.touch_handler.on_touch_up(identifier, point) {
             self.simulate_mouse_click(point);
         }
     }
 
     fn on_touch_cancel(&mut self, identifier: TouchId, point: TypedPoint2D<f32, DevicePixel>) {
         // Send the event to script.
         self.touch_handler.on_touch_cancel(identifier, point);
-        let dppx = self.page_zoom * self.hidpi_factor();
-        let translated_point = (point / dppx).to_untyped();
-        self.send_event_to_root_pipeline(TouchEvent(TouchEventType::Cancel,
-                                                    identifier,
-                                                    translated_point));
+        self.send_touch_event(TouchEventType::Cancel, identifier, point);
     }
 
     pub fn on_touchpad_pressure_event(&self,
                                   point: TypedPoint2D<f32, DevicePixel>,
                                   pressure: f32,
                                   phase: TouchpadPressurePhase) {
-        if let Some(true) = PREFS.get("dom.forcetouch.enabled").as_boolean() {
-            let dppx = self.page_zoom * self.hidpi_factor();
-            let translated_point = (point / dppx).to_untyped();
-            self.send_event_to_root_pipeline(TouchpadPressureEvent(translated_point,
-                                                                   pressure,
-                                                                   phase));
+        match PREFS.get("dom.forcetouch.enabled").as_boolean() {
+            Some(true) => {},
+            _ => return,
+        }
+
+        let results = self.hit_test_at_point(point);
+        if let Some(item) = results.items.first() {
+            let event = TouchpadPressureEvent(
+                item.point_in_viewport.to_untyped(),
+                pressure,
+                phase,
+                Some(UntrustedNodeAddress(item.tag.0 as *const c_void)),
+            );
+            let pipeline_id = PipelineId::from_webrender(item.pipeline);
+            let msg = ConstellationMsg::ForwardEvent(pipeline_id, event);
+            if let Err(e) = self.constellation_chan.send(msg) {
+                warn!("Sending event to constellation failed ({}).", e);
+            }
         }
     }
 
     /// <http://w3c.github.io/touch-events/#mouse-events>
     fn simulate_mouse_click(&mut self, p: TypedPoint2D<f32, DevicePixel>) {
         let button = MouseButton::Left;
         self.dispatch_mouse_window_move_event_class(p);
         self.dispatch_mouse_window_event_class(MouseWindowEvent::MouseDown(button, p));
--- a/servo/components/compositing/compositor_thread.rs
+++ b/servo/components/compositing/compositor_thread.rs
@@ -153,18 +153,16 @@ pub enum Msg {
     /// Requests that the compositor shut down.
     Exit,
 
     /// Informs the compositor that the constellation has completed shutdown.
     /// Required because the constellation can have pending calls to make
     /// (e.g. SetFrameTree) at the time that we send it an ExitMsg.
     ShutdownComplete,
 
-    /// Scroll a page in a window
-    ScrollFragmentPoint(webrender_api::ClipId, Point2D<f32>, bool),
     /// Alerts the compositor that the given pipeline has changed whether it is running animations.
     ChangeRunningAnimationsState(PipelineId, AnimationState),
     /// Replaces the current frame tree, typically called during main frame navigation.
     SetFrameTree(SendableFrameTree),
     /// Composite.
     Recomposite(CompositingReason),
     /// Script has handled a touch event, and either prevented or allowed default actions.
     TouchEventProcessed(EventResult),
@@ -190,24 +188,24 @@ pub enum Msg {
     /// Required to allow WGL GLContext sharing in Windows.
     Dispatch(Box<Fn() + Send>),
     /// Indicates to the compositor that it needs to record the time when the frame with
     /// the given ID (epoch) is painted and report it to the layout thread of the given
     /// pipeline ID.
     PendingPaintMetric(PipelineId, Epoch),
     /// The load of a page has completed
     LoadComplete(TopLevelBrowsingContextId),
+
 }
 
 impl Debug for Msg {
     fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
         match *self {
             Msg::Exit => write!(f, "Exit"),
             Msg::ShutdownComplete => write!(f, "ShutdownComplete"),
-            Msg::ScrollFragmentPoint(..) => write!(f, "ScrollFragmentPoint"),
             Msg::ChangeRunningAnimationsState(..) => write!(f, "ChangeRunningAnimationsState"),
             Msg::SetFrameTree(..) => write!(f, "SetFrameTree"),
             Msg::Recomposite(..) => write!(f, "Recomposite"),
             Msg::TouchEventProcessed(..) => write!(f, "TouchEventProcessed"),
             Msg::CreatePng(..) => write!(f, "CreatePng"),
             Msg::ViewportConstrained(..) => write!(f, "ViewportConstrained"),
             Msg::IsReadyToSaveImageReply(..) => write!(f, "IsReadyToSaveImageReply"),
             Msg::PipelineVisibilityChanged(..) => write!(f, "PipelineVisibilityChanged"),
--- a/servo/components/compositing/lib.rs
+++ b/servo/components/compositing/lib.rs
@@ -4,16 +4,17 @@
 
 #![deny(unsafe_code)]
 
 extern crate euclid;
 extern crate gfx_traits;
 extern crate gleam;
 extern crate image;
 extern crate ipc_channel;
+extern crate libc;
 #[macro_use]
 extern crate log;
 extern crate msg;
 extern crate net_traits;
 extern crate nonzero;
 extern crate profile_traits;
 extern crate script_traits;
 extern crate servo_config;
--- a/servo/components/constellation/constellation.rs
+++ b/servo/components/constellation/constellation.rs
@@ -1077,16 +1077,22 @@ impl<Message, LTF, STF> Constellation<Me
             }
             FromCompositorMsg::LogEntry(top_level_browsing_context_id, thread_name, entry) => {
                 self.handle_log_entry(top_level_browsing_context_id, thread_name, entry);
             }
             FromCompositorMsg::WebVREvents(pipeline_ids, events) => {
                 debug!("constellation got {:?} WebVR events", events.len());
                 self.handle_webvr_events(pipeline_ids, events);
             }
+            FromCompositorMsg::ForwardEvent(destination_pipeline_id, event) => {
+                self.forward_event(destination_pipeline_id, event);
+            }
+            FromCompositorMsg::SetCursor(cursor) => {
+                self.handle_set_cursor_msg(cursor)
+            }
         }
     }
 
     fn handle_request_from_script(&mut self, message: (PipelineId, FromScriptMsg)) {
         let (source_pipeline_id, content) = message;
         let source_top_ctx_id = match self.pipelines.get(&source_pipeline_id)
             .map(|pipeline| pipeline.top_level_browsing_context_id) {
                 None => return warn!("ScriptMsg from closed pipeline {:?}.", source_pipeline_id),
@@ -1170,25 +1176,18 @@ impl<Message, LTF, STF> Constellation<Me
             FromScriptMsg::MozBrowserEvent(pipeline_id, event) => {
                 debug!("constellation got mozbrowser event message");
                 self.handle_mozbrowser_event_msg(pipeline_id, source_top_ctx_id, event);
             }
             FromScriptMsg::Focus => {
                 debug!("constellation got focus message");
                 self.handle_focus_msg(source_pipeline_id);
             }
-            FromScriptMsg::ForwardEvent(dest_id, event) => {
-                let msg = ConstellationControlMsg::SendEvent(dest_id, event);
-                let result = match self.pipelines.get(&dest_id) {
-                    None => { debug!("Pipeline {:?} got event after closure.", dest_id); return; }
-                    Some(pipeline) => pipeline.event_loop.send(msg),
-                };
-                if let Err(e) = result {
-                    self.handle_send_error(dest_id, e);
-                }
+            FromScriptMsg::ForwardEvent(destination_pipeline_id, event) => {
+                self.forward_event(destination_pipeline_id, event);
             }
             FromScriptMsg::GetClipboardContents(sender) => {
                 let contents = match self.clipboard_ctx {
                     Some(ref mut ctx) => match ctx.get_contents() {
                         Ok(c) => c,
                         Err(e) => {
                             warn!("Error getting clipboard contents ({}), defaulting to empty string", e);
                             "".to_owned()
@@ -1245,23 +1244,16 @@ impl<Message, LTF, STF> Constellation<Me
             FromScriptMsg::SetDocumentState(state) => {
                 debug!("constellation got SetDocumentState message");
                 self.document_states.insert(source_pipeline_id, state);
             }
             FromScriptMsg::Alert(message, sender) => {
                 debug!("constellation got Alert message");
                 self.handle_alert(source_top_ctx_id, message, sender);
             }
-
-            FromScriptMsg::ScrollFragmentPoint(scroll_root_id, point, smooth) => {
-                self.compositor_proxy.send(ToCompositorMsg::ScrollFragmentPoint(scroll_root_id,
-                                                                                point,
-                                                                                smooth));
-            }
-
             FromScriptMsg::GetClientWindow(send) => {
                 self.embedder_proxy.send(EmbedderMsg::GetClientWindow(source_top_ctx_id, send));
             }
 
             FromScriptMsg::MoveTo(point) => {
                 self.embedder_proxy.send(EmbedderMsg::MoveTo(source_top_ctx_id, point));
             }
 
@@ -1587,16 +1579,30 @@ impl<Message, LTF, STF> Constellation<Me
                     // Notify script thread
                     let _ = pipeline.event_loop.send(ConstellationControlMsg::WebVREvents(id, events.clone()));
                 },
                 None => warn!("constellation got webvr event for dead pipeline")
             }
         }
     }
 
+    fn forward_event(&mut self, destination_pipeline_id: PipelineId, event: CompositorEvent) {
+        let msg = ConstellationControlMsg::SendEvent(destination_pipeline_id, event);
+        let result = match self.pipelines.get(&destination_pipeline_id) {
+            None => {
+                debug!("Pipeline {:?} got event after closure.", destination_pipeline_id);
+                return;
+            }
+            Some(pipeline) => pipeline.event_loop.send(msg),
+        };
+        if let Err(e) = result {
+            self.handle_send_error(destination_pipeline_id, e);
+        }
+    }
+
     fn handle_new_top_level_browsing_context(&mut self, url: ServoUrl, reply: IpcSender<TopLevelBrowsingContextId>) {
         let window_size = self.window_size.initial_viewport;
         let pipeline_id = PipelineId::new();
         let top_level_browsing_context_id = TopLevelBrowsingContextId::new();
         if let Err(e) = reply.send(top_level_browsing_context_id) {
             warn!("Failed to send newly created top level browsing context ({}).", e);
         }
         let browsing_context_id = BrowsingContextId::from(top_level_browsing_context_id);
--- a/servo/components/gfx/display_list/mod.rs
+++ b/servo/components/gfx/display_list/mod.rs
@@ -201,89 +201,16 @@ impl DisplayList {
                 offset_lookup.new_for_reference_frame(clip_id, &transform, &mut point) {
                 self.text_index_contents(node, traversal, &point, sublookup, result);
             }
         } else {
             self.text_index_contents(node, traversal, &point, offset_lookup, result);
         }
     }
 
-    // Return all nodes containing the point of interest, bottommost first, and
-    // respecting the `pointer-events` CSS property.
-    pub fn hit_test(&self,
-                    point: &Point2D<Au>,
-                    scroll_offsets: &ScrollOffsetMap)
-                    -> Vec<DisplayItemMetadata> {
-        let mut result = Vec::new();
-        let mut traversal = DisplayListTraversal::new(self);
-        self.hit_test_contents(&mut traversal,
-                               point,
-                               &mut ScrollOffsetLookup::new(&mut HashMap::new(), scroll_offsets),
-                               &mut result);
-        result
-    }
-
-    fn hit_test_contents<'a>(&self,
-                             traversal: &mut DisplayListTraversal<'a>,
-                             point: &Point2D<Au>,
-                             offset_lookup: &mut ScrollOffsetLookup,
-                             result: &mut Vec<DisplayItemMetadata>) {
-        while let Some(item) = traversal.next() {
-            match item {
-                &DisplayItem::PushStackingContext(ref context_item) => {
-                    self.hit_test_stacking_context(&context_item.stacking_context,
-                                                   item.scroll_node_id(),
-                                                   traversal,
-                                                   point,
-                                                   offset_lookup,
-                                                   result);
-                }
-                &DisplayItem::PopStackingContext(_) => return,
-                &DisplayItem::DefineClipScrollNode(ref item) => {
-                    offset_lookup.add_clip_scroll_node(&item.node);
-                }
-                _ => {
-                    if let Some(meta) = item.hit_test(*point, offset_lookup) {
-                        result.push(meta);
-                    }
-                }
-            }
-        }
-    }
-
-    fn hit_test_stacking_context<'a>(&self,
-                                     stacking_context: &StackingContext,
-                                     clip_id: ClipId,
-                                     traversal: &mut DisplayListTraversal<'a>,
-                                     point: &Point2D<Au>,
-                                     offset_lookup: &mut ScrollOffsetLookup,
-                                     result: &mut Vec<DisplayItemMetadata>) {
-        debug_assert!(stacking_context.context_type == StackingContextType::Real);
-
-        let mut point = *point - stacking_context.bounds.origin.to_vector();
-        if stacking_context.scroll_policy == ScrollPolicy::Fixed {
-            let old_offset = offset_lookup.calculated_total_offsets.get(&clip_id).cloned();
-            offset_lookup.calculated_total_offsets.insert(clip_id, Vector2D::zero());
-
-            self.hit_test_contents(traversal, &point, offset_lookup, result);
-
-            match old_offset {
-                Some(offset) => offset_lookup.calculated_total_offsets.insert(clip_id, offset),
-                None => offset_lookup.calculated_total_offsets.remove(&clip_id),
-            };
-        } else if let Some(transform) = stacking_context.transform {
-            if let Some(ref mut sublookup) =
-                offset_lookup.new_for_reference_frame(clip_id, &transform, &mut point) {
-                self.hit_test_contents(traversal, &point, sublookup, result);
-            }
-        } else {
-            self.hit_test_contents(traversal, &point, offset_lookup, result);
-        }
-    }
-
     pub fn print(&self) {
         let mut print_tree = PrintTree::new("Display List".to_owned());
         self.print_with_tree(&mut print_tree);
     }
 
     pub fn print_with_tree(&self, print_tree: &mut PrintTree) {
         print_tree.new_level("Items".to_owned());
         for item in &self.list {
@@ -1278,70 +1205,16 @@ impl DisplayItem {
 
     pub fn debug_with_level(&self, level: u32) {
         let mut indent = String::new();
         for _ in 0..level {
             indent.push_str("| ")
         }
         println!("{}+ {:?}", indent, self);
     }
-
-    fn hit_test(&self,
-                point: Point2D<Au>,
-                offset_lookup: &mut ScrollOffsetLookup)
-                -> Option<DisplayItemMetadata> {
-        // TODO(pcwalton): Use a precise algorithm here. This will allow us to properly hit
-        // test elements with `border-radius`, for example.
-        let base_item = self.base();
-
-        let scroll_offset = offset_lookup.full_offset_for_clip_scroll_node(&self.scroll_node_id());
-        let point = Point2D::new(point.x - Au::from_f32_px(scroll_offset.x),
-                                 point.y - Au::from_f32_px(scroll_offset.y));
-
-        if !base_item.local_clip.clip_rect().contains(&point.to_pointf()) {
-            // Clipped out.
-            return None;
-        }
-        if !self.bounds().contains(&point) {
-            // Can't possibly hit.
-            return None;
-        }
-        if base_item.metadata.pointing.is_none() {
-            // `pointer-events` is `none`. Ignore this item.
-            return None;
-        }
-
-        match *self {
-            DisplayItem::Border(ref border) => {
-                // If the point is inside the border, it didn't hit the border!
-                let interior_rect =
-                    Rect::new(
-                        Point2D::new(border.base.bounds.origin.x +
-                                     border.border_widths.left,
-                                     border.base.bounds.origin.y +
-                                     border.border_widths.top),
-                        Size2D::new(border.base.bounds.size.width -
-                                    (border.border_widths.left +
-                                     border.border_widths.right),
-                                    border.base.bounds.size.height -
-                                    (border.border_widths.top +
-                                     border.border_widths.bottom)));
-                if interior_rect.contains(&point) {
-                    return None;
-                }
-            }
-            DisplayItem::BoxShadow(_) => {
-                // Box shadows can never be hit.
-                return None;
-            }
-            _ => {}
-        }
-
-        Some(base_item.metadata)
-    }
 }
 
 impl fmt::Debug for DisplayItem {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         if let DisplayItem::PushStackingContext(ref item) = *self {
             return write!(f, "PushStackingContext({:?})", item.stacking_context);
         }
 
--- a/servo/components/layout/query.rs
+++ b/servo/components/layout/query.rs
@@ -5,42 +5,40 @@
 //! Utilities for querying the layout, as needed by the layout thread.
 
 use app_units::Au;
 use construct::ConstructionResult;
 use context::LayoutContext;
 use euclid::{Point2D, Vector2D, Rect, Size2D};
 use flow::{self, Flow};
 use fragment::{Fragment, FragmentBorderBoxIterator, SpecificFragmentInfo};
-use gfx::display_list::{DisplayItemMetadata, DisplayList, OpaqueNode, ScrollOffsetMap};
+use gfx::display_list::{DisplayList, OpaqueNode, ScrollOffsetMap};
 use inline::LAST_FRAGMENT_OF_ELEMENT;
 use ipc_channel::ipc::IpcSender;
 use msg::constellation_msg::PipelineId;
 use opaque_node::OpaqueNodeMethods;
-use script_layout_interface::rpc::{ContentBoxResponse, ContentBoxesResponse};
-use script_layout_interface::rpc::{HitTestResponse, LayoutRPC};
+use script_layout_interface::rpc::{ContentBoxResponse, ContentBoxesResponse, LayoutRPC};
 use script_layout_interface::rpc::{MarginStyleResponse, NodeGeometryResponse};
-use script_layout_interface::rpc::{NodeOverflowResponse, OffsetParentResponse};
-use script_layout_interface::rpc::{NodeScrollRootIdResponse, ResolvedStyleResponse, TextIndexResponse};
+use script_layout_interface::rpc::{NodeOverflowResponse, NodeScrollRootIdResponse};
+use script_layout_interface::rpc::{OffsetParentResponse, ResolvedStyleResponse, TextIndexResponse};
 use script_layout_interface::wrapper_traits::{LayoutNode, ThreadSafeLayoutElement, ThreadSafeLayoutNode};
 use script_traits::LayoutMsg as ConstellationMsg;
 use script_traits::UntrustedNodeAddress;
 use sequential;
 use std::cmp::{min, max};
 use std::ops::Deref;
 use std::sync::{Arc, Mutex};
 use style::computed_values;
 use style::context::{StyleContext, ThreadLocalStyleContext};
 use style::dom::TElement;
 use style::logical_geometry::{WritingMode, BlockFlowDirection, InlineBaseDirection};
 use style::properties::{style_structs, PropertyId, PropertyDeclarationId, LonghandId};
 use style::properties::longhands::{display, position};
 use style::selector_parser::PseudoElement;
 use style_traits::ToCss;
-use style_traits::cursor::Cursor;
 use webrender_api::ClipId;
 use wrapper::LayoutNodeLayoutData;
 
 /// Mutable data belonging to the LayoutThread.
 ///
 /// This needs to be protected by a mutex so we can do fast RPCs.
 pub struct LayoutThreadData {
     /// The channel on which messages can be sent to the constellation.
@@ -53,19 +51,16 @@ pub struct LayoutThreadData {
     pub content_box_response: Option<Rect<Au>>,
 
     /// A queued response for the content boxes of a node.
     pub content_boxes_response: Vec<Rect<Au>>,
 
     /// A queued response for the client {top, left, width, height} of a node in pixels.
     pub client_rect_response: Rect<i32>,
 
-    /// A queued response for the node at a given point
-    pub hit_test_response: (Option<DisplayItemMetadata>, bool),
-
     /// A queued response for the scroll root id for a given node.
     pub scroll_root_id_response: Option<ClipId>,
 
     /// A pair of overflow property in x and y
     pub overflow_response: NodeOverflowResponse,
 
     /// A queued response for the scroll {top, left, width, height} of a node in pixels.
     pub scroll_area_response: Rect<i32>,
@@ -114,34 +109,16 @@ impl LayoutRPC for LayoutRPCImpl {
 
     /// Requests the dimensions of all the content boxes, as in the `getClientRects()` call.
     fn content_boxes(&self) -> ContentBoxesResponse {
         let &LayoutRPCImpl(ref rw_data) = self;
         let rw_data = rw_data.lock().unwrap();
         ContentBoxesResponse(rw_data.content_boxes_response.clone())
     }
 
-    /// Requests the node containing the point of interest.
-    fn hit_test(&self) -> HitTestResponse {
-        let &LayoutRPCImpl(ref rw_data) = self;
-        let rw_data = rw_data.lock().unwrap();
-        let &(ref result, update_cursor) = &rw_data.hit_test_response;
-        if update_cursor {
-            // Compute the new cursor.
-            let cursor = match *result {
-                None => Cursor::Default,
-                Some(dim) => dim.pointing.unwrap(),
-            };
-            rw_data.constellation_chan.send(ConstellationMsg::SetCursor(cursor)).unwrap();
-        }
-        HitTestResponse {
-            node_address: result.map(|dim| dim.node.to_untrusted_node_address()),
-        }
-    }
-
     fn nodes_from_point_response(&self) -> Vec<UntrustedNodeAddress> {
         let &LayoutRPCImpl(ref rw_data) = self;
         let rw_data = rw_data.lock().unwrap();
         rw_data.nodes_from_point_response.clone()
     }
 
     fn node_geometry(&self) -> NodeGeometryResponse {
         let &LayoutRPCImpl(ref rw_data) = self;
--- a/servo/components/layout/webrender_helpers.rs
+++ b/servo/components/layout/webrender_helpers.rs
@@ -14,33 +14,22 @@ use gfx::display_list::{ClippingRegion, 
 use gfx::display_list::StackingContextType;
 use msg::constellation_msg::PipelineId;
 use style::computed_values::{image_rendering, mix_blend_mode, transform_style};
 use style::values::computed::{BorderStyle, Filter};
 use style::values::generics::effects::Filter as GenericFilter;
 use webrender_api::{self, ClipAndScrollInfo, ComplexClipRegion, DisplayListBuilder};
 use webrender_api::{ExtendMode, LayoutTransform};
 
-fn prim_info(local_rect: Rect<Au>,
-             local_clip: Option<webrender_api::LocalClip>) -> webrender_api::LayoutPrimitiveInfo {
-    match local_clip {
-        Some(local_clip) => {
-            webrender_api::LayoutPrimitiveInfo::with_clip(local_rect.to_rectf(), local_clip)
-        }
-        None => {
-            webrender_api::LayoutPrimitiveInfo::new(local_rect.to_rectf())
-        }
-    }
-}
-
 pub trait WebRenderDisplayListConverter {
     fn convert_to_webrender(&self, pipeline_id: PipelineId) -> DisplayListBuilder;
 }
 
 trait WebRenderDisplayItemConverter {
+    fn prim_info(&self) -> webrender_api::LayoutPrimitiveInfo;
     fn convert_to_webrender(&self,
                             builder: &mut DisplayListBuilder,
                             current_clip_and_scroll_info: &mut ClipAndScrollInfo);
 }
 
 trait ToBorderStyle {
     fn to_border_style(&self) -> webrender_api::BorderStyle;
 }
@@ -244,33 +233,43 @@ impl WebRenderDisplayListConverter for D
         for item in traversal {
             item.convert_to_webrender(&mut builder, &mut current_clip_and_scroll_info);
         }
         builder
     }
 }
 
 impl WebRenderDisplayItemConverter for DisplayItem {
+    fn prim_info(&self) -> webrender_api::LayoutPrimitiveInfo {
+        let tag = match self.base().metadata.pointing {
+            Some(cursor) => Some((self.base().metadata.node.0 as u64, cursor as u8)),
+            None => None,
+        };
+        webrender_api::LayoutPrimitiveInfo {
+            rect: self.base().bounds.to_rectf(),
+            local_clip: self.base().local_clip,
+            // TODO(gw): Make use of the WR backface visibility functionality.
+            is_backface_visible: true,
+            tag: tag,
+        }
+    }
+
     fn convert_to_webrender(&self,
                             builder: &mut DisplayListBuilder,
                             current_clip_and_scroll_info: &mut ClipAndScrollInfo) {
         let clip_and_scroll_info = self.base().clip_and_scroll_info;
         if clip_and_scroll_info != *current_clip_and_scroll_info {
             builder.pop_clip_id();
             builder.push_clip_and_scroll_info(clip_and_scroll_info);
             *current_clip_and_scroll_info = clip_and_scroll_info;
         }
 
         match *self {
             DisplayItem::SolidColor(ref item) => {
-                let color = item.color;
-                if color.a > 0.0 {
-                    builder.push_rect(&prim_info(item.base.bounds, Some(item.base.local_clip)),
-                                      color);
-                }
+                builder.push_rect(&self.prim_info(), item.color);
             }
             DisplayItem::Text(ref item) => {
                 let mut origin = item.baseline_origin.clone();
                 let mut glyphs = vec!();
 
                 for slice in item.text_run.natural_word_slices_in_visual_order(&item.range) {
                     for glyph in slice.glyphs.iter_glyphs_for_byte_range(&slice.range) {
                         let glyph_advance = if glyph.char_is_space() {
@@ -289,37 +288,36 @@ impl WebRenderDisplayItemConverter for D
                             };
                             glyphs.push(glyph);
                         }
                         origin.x = origin.x + glyph_advance;
                     };
                 }
 
                 if glyphs.len() > 0 {
-                    builder.push_text(&prim_info(item.base.bounds, Some(item.base.local_clip)),
+                    builder.push_text(&self.prim_info(),
                                       &glyphs,
                                       item.text_run.font_key,
                                       item.text_color,
                                       None);
                 }
             }
             DisplayItem::Image(ref item) => {
                 if let Some(id) = item.webrender_image.key {
                     if item.stretch_size.width > Au(0) &&
                        item.stretch_size.height > Au(0) {
-                        builder.push_image(&prim_info(item.base.bounds, Some(item.base.local_clip)),
+                        builder.push_image(&self.prim_info(),
                                            item.stretch_size.to_sizef(),
                                            item.tile_spacing.to_sizef(),
                                            item.image_rendering.to_image_rendering(),
                                            id);
                     }
                 }
             }
             DisplayItem::Border(ref item) => {
-                let rect = item.base.bounds;
                 let widths = item.border_widths.to_border_widths();
 
                 let details = match item.details {
                     BorderDetails::Normal(ref border) => {
                         let left = webrender_api::BorderSide {
                             color: border.color.left,
                             style: border.style.left.to_border_style(),
                         };
@@ -390,32 +388,32 @@ impl WebRenderDisplayItemConverter for D
                                gradient.gradient.radius.to_sizef(),
                                gradient.gradient.stops.clone(),
                                extend_mode),
                            outset: gradient.outset,
                        })
                     }
                 };
 
-                builder.push_border(&prim_info(rect, Some(item.base.local_clip)), widths, details);
+                builder.push_border(&self.prim_info(), widths, details);
             }
             DisplayItem::Gradient(ref item) => {
                 let rect = item.base.bounds;
                 let start_point = item.gradient.start_point.to_pointf();
                 let end_point = item.gradient.end_point.to_pointf();
                 let extend_mode = if item.gradient.repeating {
                     ExtendMode::Repeat
                 } else {
                     ExtendMode::Clamp
                 };
                 let gradient = builder.create_gradient(start_point,
                                                        end_point,
                                                        item.gradient.stops.clone(),
                                                        extend_mode);
-                builder.push_gradient(&prim_info(rect, Some(item.base.local_clip)),
+                builder.push_gradient(&self.prim_info(),
                                       gradient,
                                       rect.size.to_sizef(),
                                       webrender_api::LayoutSize::zero());
             }
             DisplayItem::RadialGradient(ref item) => {
                 let rect = item.base.bounds;
                 let center = item.gradient.center.to_pointf();
                 let radius = item.gradient.radius.to_sizef();
@@ -423,79 +421,77 @@ impl WebRenderDisplayItemConverter for D
                     ExtendMode::Repeat
                 } else {
                     ExtendMode::Clamp
                 };
                 let gradient = builder.create_radial_gradient(center,
                                                               radius,
                                                               item.gradient.stops.clone(),
                                                               extend_mode);
-                builder.push_radial_gradient(&prim_info(rect, Some(item.base.local_clip)),
+                builder.push_radial_gradient(&self.prim_info(),
                                              gradient,
                                              rect.size.to_sizef(),
                                              webrender_api::LayoutSize::zero());
             }
             DisplayItem::Line(ref item) => {
                 let box_bounds = item.base.bounds.to_rectf();
-                builder.push_line(&prim_info(item.base.bounds, Some(item.base.local_clip)),
+                builder.push_line(&self.prim_info(),
                                   box_bounds.origin.y + box_bounds.size.height,
                                   box_bounds.origin.x,
                                   box_bounds.origin.x + box_bounds.size.width,
                                   webrender_api::LineOrientation::Horizontal,
                                   box_bounds.size.height,
                                   item.color,
                                   item.style);
             }
             DisplayItem::BoxShadow(ref item) => {
-                let rect = item.base.bounds;
                 let box_bounds = item.box_bounds.to_rectf();
-                builder.push_box_shadow(&prim_info(rect, Some(item.base.local_clip)),
+                builder.push_box_shadow(&self.prim_info(),
                                         box_bounds,
                                         item.offset.to_vectorf(),
                                         item.color,
                                         item.blur_radius.to_f32_px(),
                                         item.spread_radius.to_f32_px(),
                                         item.border_radius.to_f32_px(),
                                         item.clip_mode.to_clip_mode());
             }
             DisplayItem::PushTextShadow(ref item) => {
-                let rect = item.base.bounds;
-                builder.push_shadow(&prim_info(rect, Some(item.base.local_clip)),
+                builder.push_shadow(&self.prim_info(),
                                     webrender_api::Shadow {
                                         blur_radius: item.blur_radius.to_f32_px(),
                                         offset: item.offset.to_vectorf(),
                                         color: item.color,
                                     });
             }
             DisplayItem::PopAllTextShadows(_) => {
                 builder.pop_all_shadows();
             }
             DisplayItem::Iframe(ref item) => {
-                let rect = item.base.bounds;
-                let pipeline_id = item.iframe.to_webrender();
-                builder.push_iframe(&prim_info(rect, Some(item.base.local_clip)), pipeline_id);
+                builder.push_iframe(&self.prim_info(), item.iframe.to_webrender());
             }
             DisplayItem::PushStackingContext(ref item) => {
                 let stacking_context = &item.stacking_context;
                 debug_assert!(stacking_context.context_type == StackingContextType::Real);
 
                 let transform = stacking_context.transform.map(|transform| {
                     LayoutTransform::from_untyped(&transform).into()
                 });
                 let perspective = stacking_context.perspective.map(|perspective| {
                     LayoutTransform::from_untyped(&perspective)
                 });
 
-                builder.push_stacking_context(&prim_info(stacking_context.bounds, None),
-                                              stacking_context.scroll_policy,
-                                              transform,
-                                              stacking_context.transform_style,
-                                              perspective,
-                                              stacking_context.mix_blend_mode,
-                                              stacking_context.filters.to_filter_ops());
+                builder.push_stacking_context(
+                    &webrender_api::LayoutPrimitiveInfo::new(stacking_context.bounds.to_rectf()),
+                    stacking_context.scroll_policy,
+                    transform,
+                    stacking_context.transform_style,
+                    perspective,
+                    stacking_context.mix_blend_mode,
+                    stacking_context.filters.to_filter_ops()
+                );
             }
             DisplayItem::PopStackingContext(_) => builder.pop_stacking_context(),
             DisplayItem::DefineClipScrollNode(ref item) => {
                 builder.push_clip_id(item.node.parent_id);
 
                 let our_id = item.node.id;
                 let item_rect = item.node.clip.main.to_rectf();
                 let webrender_id = match item.node.node_type {
--- a/servo/components/layout_thread/Cargo.toml
+++ b/servo/components/layout_thread/Cargo.toml
@@ -20,16 +20,17 @@ fnv = "1.0"
 gfx = {path = "../gfx"}
 gfx_traits = {path = "../gfx_traits"}
 heapsize = "0.4"
 html5ever = "0.20.0"
 ipc-channel = "0.9"
 layout = {path = "../layout"}
 layout_traits = {path = "../layout_traits"}
 lazy_static = "0.2"
+libc = "0.2"
 log = "0.3.5"
 metrics = {path = "../metrics"}
 msg = {path = "../msg"}
 net_traits = {path = "../net_traits"}
 nonzero = {path = "../nonzero"}
 parking_lot = "0.4"
 profile_traits = {path = "../profile_traits"}
 range = {path = "../range"}
--- a/servo/components/layout_thread/lib.rs
+++ b/servo/components/layout_thread/lib.rs
@@ -17,16 +17,17 @@ extern crate heapsize;
 #[macro_use]
 extern crate html5ever;
 extern crate ipc_channel;
 #[macro_use]
 extern crate layout;
 extern crate layout_traits;
 #[macro_use]
 extern crate lazy_static;
+extern crate libc;
 #[macro_use]
 extern crate log;
 extern crate metrics;
 extern crate msg;
 extern crate net_traits;
 extern crate nonzero;
 extern crate parking_lot;
 #[macro_use]
@@ -68,37 +69,37 @@ use layout::context::LayoutContext;
 use layout::context::RegisteredPainter;
 use layout::context::RegisteredPainters;
 use layout::context::heap_size_of_persistent_local_context;
 use layout::display_list_builder::ToGfxColor;
 use layout::flow::{self, Flow, ImmutableFlowUtils, MutableOwnedFlowUtils};
 use layout::flow_ref::FlowRef;
 use layout::incremental::{LayoutDamageComputation, REFLOW_ENTIRE_DOCUMENT, RelayoutMode};
 use layout::layout_debug;
-use layout::opaque_node::OpaqueNodeMethods;
 use layout::parallel;
 use layout::query::{LayoutRPCImpl, LayoutThreadData, process_content_box_request, process_content_boxes_request};
 use layout::query::{process_margin_style_query, process_node_overflow_request, process_resolved_style_request};
 use layout::query::{process_node_geometry_request, process_node_scroll_area_request};
 use layout::query::{process_node_scroll_root_id_request, process_offset_parent_query};
 use layout::sequential;
 use layout::traversal::{ComputeStackingRelativePositions, PreorderFlowTraversal, RecalcStyleAndConstructFlows};
 use layout::webrender_helpers::WebRenderDisplayListConverter;
 use layout::wrapper::LayoutNodeLayoutData;
 use layout_traits::LayoutThreadFactory;
+use libc::c_void;
 use metrics::{PaintTimeMetrics, ProfilerMetadataFactory};
 use msg::constellation_msg::PipelineId;
 use msg::constellation_msg::TopLevelBrowsingContextId;
 use net_traits::image_cache::{ImageCache, UsePlaceholder};
 use parking_lot::RwLock;
 use profile_traits::mem::{self, Report, ReportKind, ReportsChan};
 use profile_traits::time::{self, TimerMetadata, profile};
 use profile_traits::time::{TimerMetadataFrameType, TimerMetadataReflowType};
-use script_layout_interface::message::{Msg, NewLayoutThreadInfo, Reflow, ReflowGoal};
-use script_layout_interface::message::{ScriptReflow, ReflowComplete};
+use script_layout_interface::message::{Msg, NewLayoutThreadInfo, NodesFromPointQueryType, Reflow};
+use script_layout_interface::message::{ReflowComplete, ReflowGoal, ScriptReflow};
 use script_layout_interface::rpc::{LayoutRPC, MarginStyleResponse, NodeOverflowResponse, OffsetParentResponse};
 use script_layout_interface::rpc::TextIndexResponse;
 use script_layout_interface::wrapper_traits::LayoutNode;
 use script_traits::{ConstellationControlMsg, LayoutControlMsg, LayoutMsg as ConstellationMsg};
 use script_traits::{ScrollState, UntrustedNodeAddress};
 use script_traits::DrawAPaintImageResult;
 use script_traits::Painter;
 use selectors::Element;
@@ -115,20 +116,18 @@ use std::collections::HashMap;
 use std::mem as std_mem;
 use std::ops::{Deref, DerefMut};
 use std::process;
 use std::sync::{Arc, Mutex, MutexGuard};
 use std::sync::atomic::{AtomicUsize, Ordering};
 use std::sync::mpsc::{Receiver, Sender, channel};
 use std::thread;
 use style::animation::Animation;
-use style::context::{QuirksMode, SharedStyleContext};
-use style::context::{StyleSystemOptions, ThreadLocalStyleContextCreationInfo};
-use style::context::RegisteredSpeculativePainter;
-use style::context::RegisteredSpeculativePainters;
+use style::context::{QuirksMode, RegisteredSpeculativePainter, RegisteredSpeculativePainters};
+use style::context::{SharedStyleContext, StyleSystemOptions, ThreadLocalStyleContextCreationInfo};
 use style::dom::{ShowSubtree, ShowSubtreeDataAndPrimaryValues, TElement, TNode};
 use style::driver;
 use style::error_reporting::{NullReporter, RustLogReporter};
 use style::invalidation::element::restyle_hints::RestyleHint;
 use style::logical_geometry::LogicalPoint;
 use style::media_queries::{Device, MediaList, MediaType};
 use style::properties::PropertyId;
 use style::selector_parser::SnapshotMap;
@@ -514,17 +513,16 @@ impl LayoutThread {
             stylist: Stylist::new(device, QuirksMode::NoQuirks),
             rw_data: Arc::new(Mutex::new(
                 LayoutThreadData {
                     constellation_chan: constellation_chan,
                     display_list: None,
                     content_box_response: None,
                     content_boxes_response: Vec::new(),
                     client_rect_response: Rect::zero(),
-                    hit_test_response: (None, false),
                     scroll_root_id_response: None,
                     scroll_area_response: Rect::zero(),
                     overflow_response: NodeOverflowResponse(None),
                     resolved_style_response: String::new(),
                     offset_parent_response: OffsetParentResponse::empty(),
                     margin_style_response: MarginStyleResponse::empty(),
                     scroll_offsets: HashMap::new(),
                     text_index_response: TextIndexResponse(None),
@@ -700,16 +698,24 @@ impl LayoutThread {
             },
             Msg::TickAnimations => self.tick_all_animations(possibly_locked_rw_data),
             Msg::SetScrollStates(new_scroll_states) => {
                 self.set_scroll_states(new_scroll_states, possibly_locked_rw_data);
             }
             Msg::UpdateScrollStateFromScript(state) => {
                 let mut rw_data = possibly_locked_rw_data.lock();
                 rw_data.scroll_offsets.insert(state.scroll_root_id, state.scroll_offset);
+
+                let point = Point2D::new(-state.scroll_offset.x, -state.scroll_offset.y);
+                self.webrender_api.scroll_node_with_id(
+                    self.webrender_document,
+                    webrender_api::LayoutPoint::from_untyped(&point),
+                    state.scroll_root_id,
+                    webrender_api::ScrollClamping::ToContentBounds
+                );
             }
             Msg::ReapStyleAndLayoutData(dead_data) => {
                 unsafe {
                     drop_style_and_layout_data(dead_data)
                 }
             }
             Msg::CollectReports(reports_chan) => {
                 self.collect_reports(reports_chan, possibly_locked_rw_data);
@@ -1073,20 +1079,17 @@ impl LayoutThread {
                 debug!("layout: No root node: bailing");
                 match data.reflow_goal {
                     ReflowGoal::ContentBoxQuery(_) => {
                         rw_data.content_box_response = None;
                     },
                     ReflowGoal::ContentBoxesQuery(_) => {
                         rw_data.content_boxes_response = Vec::new();
                     },
-                    ReflowGoal::HitTestQuery(..) => {
-                        rw_data.hit_test_response = (None, false);
-                    },
-                    ReflowGoal::NodesFromPoint(..) => {
+                    ReflowGoal::NodesFromPointQuery(..) => {
                         rw_data.nodes_from_point_response = Vec::new();
                     },
                     ReflowGoal::NodeGeometryQuery(_) => {
                         rw_data.client_rect_response = Rect::zero();
                     },
                     ReflowGoal::NodeScrollGeometryQuery(_) => {
                         rw_data.scroll_area_response = Rect::zero();
                     },
@@ -1350,25 +1353,16 @@ impl LayoutThread {
             ReflowGoal::ContentBoxQuery(node) => {
                 let node = unsafe { ServoLayoutNode::new(&node) };
                 rw_data.content_box_response = process_content_box_request(node, root_flow);
             },
             ReflowGoal::ContentBoxesQuery(node) => {
                 let node = unsafe { ServoLayoutNode::new(&node) };
                 rw_data.content_boxes_response = process_content_boxes_request(node, root_flow);
             },
-            ReflowGoal::HitTestQuery(client_point, update_cursor) => {
-                let point = Point2D::new(Au::from_f32_px(client_point.x),
-                                         Au::from_f32_px(client_point.y));
-                let result = rw_data.display_list
-                                    .as_ref()
-                                    .expect("Tried to hit test with no display list")
-                                    .hit_test(&point, &rw_data.scroll_offsets);
-                rw_data.hit_test_response = (result.last().cloned(), update_cursor);
-            },
             ReflowGoal::TextIndexQuery(node, mouse_x, mouse_y) => {
                 let node = unsafe { ServoLayoutNode::new(&node) };
                 let opaque_node = node.opaque();
                 let client_point = Point2D::new(Au::from_px(mouse_x),
                                                 Au::from_px(mouse_y));
                 rw_data.text_index_response =
                     TextIndexResponse(rw_data.display_list
                                       .as_ref()
@@ -1406,35 +1400,38 @@ impl LayoutThread {
             ReflowGoal::OffsetParentQuery(node) => {
                 let node = unsafe { ServoLayoutNode::new(&node) };
                 rw_data.offset_parent_response = process_offset_parent_query(node, root_flow);
             },
             ReflowGoal::MarginStyleQuery(node) => {
                 let node = unsafe { ServoLayoutNode::new(&node) };
                 rw_data.margin_style_response = process_margin_style_query(node);
             },
-            ReflowGoal::NodesFromPoint(client_point) => {
-                let client_point = Point2D::new(Au::from_f32_px(client_point.x),
-                                                Au::from_f32_px(client_point.y));
-                let nodes_from_point_list = {
-                    let result = match rw_data.display_list {
-                        None => panic!("Tried to hit test without a DisplayList"),
-                        Some(ref display_list) => {
-                            display_list.hit_test(&client_point, &rw_data.scroll_offsets)
-                        }
-                    };
-
-                    result
+            ReflowGoal::NodesFromPointQuery(client_point, ref reflow_goal) => {
+                let mut flags = match reflow_goal {
+                    &NodesFromPointQueryType::Topmost => webrender_api::HitTestFlags::empty(),
+                    &NodesFromPointQueryType::All => webrender_api::HitTestFlags::FIND_ALL,
                 };
 
-                rw_data.nodes_from_point_response = nodes_from_point_list.iter()
-                   .rev()
-                   .map(|metadata| metadata.node.to_untrusted_node_address())
+                // The point we get is not relative to the entire WebRender scene, but to this
+                // particular pipeline, so we need to tell WebRender about that.
+                flags.insert(webrender_api::HitTestFlags::POINT_RELATIVE_TO_PIPELINE_VIEWPORT);
+
+                let client_point = webrender_api::WorldPoint::from_untyped(&client_point);
+                let results = self.webrender_api.hit_test(
+                    self.webrender_document,
+                    Some(self.id.to_webrender()),
+                    client_point,
+                    flags
+                );
+
+                rw_data.nodes_from_point_response = results.items.iter()
+                   .map(|item| UntrustedNodeAddress(item.tag.0 as *const c_void))
                    .collect()
-            }
+            },
 
             ReflowGoal::Full | ReflowGoal::TickAnimations => {}
         }
     }
 
     fn set_scroll_states<'a, 'b>(&mut self,
                                  new_scroll_states: Vec<ScrollState>,
                                  possibly_locked_rw_data: &mut RwData<'a, 'b>) {
--- a/servo/components/msg/constellation_msg.rs
+++ b/servo/components/msg/constellation_msg.rs
@@ -241,16 +241,27 @@ impl PipelineId {
     }
 
     pub fn to_webrender(&self) -> webrender_api::PipelineId {
         let PipelineNamespaceId(namespace_id) = self.namespace_id;
         let PipelineIndex(index) = self.index;
         webrender_api::PipelineId(namespace_id, index.get())
     }
 
+    #[allow(unsafe_code)]
+    pub fn from_webrender(pipeline: webrender_api::PipelineId) -> PipelineId {
+        let webrender_api::PipelineId(namespace_id, index) = pipeline;
+        unsafe {
+            PipelineId {
+                namespace_id: PipelineNamespaceId(namespace_id),
+                index: PipelineIndex(NonZero::new_unchecked(index)),
+            }
+        }
+    }
+
     pub fn root_scroll_node(&self) -> webrender_api::ClipId {
         webrender_api::ClipId::root_scroll_node(self.to_webrender())
     }
 
     pub fn root_clip_and_scroll_info(&self) -> webrender_api::ClipAndScrollInfo {
         webrender_api::ClipAndScrollInfo::simple(self.root_scroll_node())
     }
 }
--- a/servo/components/script/dom/document.rs
+++ b/servo/components/script/dom/document.rs
@@ -5,20 +5,18 @@
 use cookie_rs;
 use devtools_traits::ScriptToDevtoolsControlMsg;
 use document_loader::{DocumentLoader, LoadType};
 use dom::activation::{ActivationSource, synthetic_click_activation};
 use dom::attr::Attr;
 use dom::beforeunloadevent::BeforeUnloadEvent;
 use dom::bindings::callback::ExceptionHandling;
 use dom::bindings::cell::DomRefCell;
-use dom::bindings::codegen::Bindings::DOMRectBinding::DOMRectMethods;
 use dom::bindings::codegen::Bindings::DocumentBinding;
 use dom::bindings::codegen::Bindings::DocumentBinding::{DocumentMethods, DocumentReadyState, ElementCreationOptions};
-use dom::bindings::codegen::Bindings::ElementBinding::ElementMethods;
 use dom::bindings::codegen::Bindings::HTMLIFrameElementBinding::HTMLIFrameElementBinding::HTMLIFrameElementMethods;
 use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
 use dom::bindings::codegen::Bindings::NodeFilterBinding::NodeFilter;
 use dom::bindings::codegen::Bindings::PerformanceBinding::PerformanceMethods;
 use dom::bindings::codegen::Bindings::TouchBinding::TouchMethods;
 use dom::bindings::codegen::Bindings::WindowBinding::{FrameRequestCallback, ScrollBehavior, WindowMethods};
 use dom::bindings::codegen::UnionTypes::NodeOrString;
 use dom::bindings::error::{Error, ErrorResult, Fallible};
@@ -89,40 +87,38 @@ use dom::treewalker::TreeWalker;
 use dom::uievent::UIEvent;
 use dom::virtualmethods::vtable_for;
 use dom::webglcontextevent::WebGLContextEvent;
 use dom::window::{ReflowReason, Window};
 use dom::windowproxy::WindowProxy;
 use dom_struct::dom_struct;
 use encoding::EncodingRef;
 use encoding::all::UTF_8;
-use euclid::{Point2D, Vector2D};
+use euclid::Point2D;
 use html5ever::{LocalName, Namespace, QualName};
 use hyper::header::{Header, SetCookie};
 use hyper_serde::Serde;
 use ipc_channel::ipc::{self, IpcSender};
 use js::jsapi::{JSContext, JSRuntime};
 use js::jsapi::JS_GetRuntime;
 use msg::constellation_msg::{ALT, CONTROL, SHIFT, SUPER};
 use msg::constellation_msg::{BrowsingContextId, Key, KeyModifiers, KeyState, TopLevelBrowsingContextId};
 use net_traits::{FetchResponseMsg, IpcSend, ReferrerPolicy};
 use net_traits::CookieSource::NonHTTP;
 use net_traits::CoreResourceMsg::{GetCookiesForUrl, SetCookiesForUrl};
 use net_traits::pub_domains::is_pub_domain;
 use net_traits::request::RequestInit;
 use net_traits::response::HttpsState;
 use num_traits::ToPrimitive;
-use script_layout_interface::message::{Msg, ReflowGoal};
+use script_layout_interface::message::{Msg, NodesFromPointQueryType, ReflowGoal};
 use script_runtime::{CommonScriptMsg, ScriptThreadEventCategory};
 use script_thread::{MainThreadScriptMsg, ScriptThread};
-use script_traits::{AnimationState, CompositorEvent, DocumentActivity};
-use script_traits::{MouseButton, MouseEventType, MozBrowserEvent};
-use script_traits::{MsDuration, ScriptMsg, TouchpadPressurePhase};
-use script_traits::{TouchEventType, TouchId};
-use script_traits::UntrustedNodeAddress;
+use script_traits::{AnimationState, DocumentActivity, MouseButton, MouseEventType};
+use script_traits::{MozBrowserEvent, MsDuration, ScriptMsg, TouchEventType, TouchId};
+use script_traits::{TouchpadPressurePhase, UntrustedNodeAddress};
 use servo_arc::Arc;
 use servo_atoms::Atom;
 use servo_config::prefs::PREFS;
 use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl};
 use std::ascii::AsciiExt;
 use std::borrow::ToOwned;
 use std::cell::{Cell, Ref, RefMut};
 use std::collections::{HashMap, HashSet, VecDeque};
@@ -829,63 +825,42 @@ impl Document {
     pub fn dirty_all_nodes(&self) {
         let root = self.upcast::<Node>();
         for node in root.traverse_preorder() {
             node.dirty(NodeDamage::OtherNodeDamage)
         }
     }
 
     #[allow(unsafe_code)]
-    pub fn handle_mouse_event(&self,
-                              js_runtime: *mut JSRuntime,
-                              button: MouseButton,
-                              client_point: Point2D<f32>,
-                              mouse_event_type: MouseEventType) {
+    pub fn handle_mouse_event(
+        &self,
+        js_runtime: *mut JSRuntime,
+        _button: MouseButton,
+        client_point: Point2D<f32>,
+        mouse_event_type: MouseEventType,
+        node_address: Option<UntrustedNodeAddress>
+    ) {
         let mouse_event_type_string = match mouse_event_type {
             MouseEventType::Click => "click".to_owned(),
             MouseEventType::MouseUp => "mouseup".to_owned(),
             MouseEventType::MouseDown => "mousedown".to_owned(),
         };
         debug!("{}: at {:?}", mouse_event_type_string, client_point);
 
-        let node = match self.window.hit_test_query(client_point, false) {
-            Some(node_address) => {
-                debug!("node address is {:?}", node_address);
-                unsafe {
-                    node::from_untrusted_node_address(js_runtime, node_address)
-                }
-            },
+        let el = node_address.and_then(|address| {
+            let node = unsafe { node::from_untrusted_node_address(js_runtime, address) };
+            node.inclusive_ancestors()
+                .filter_map(DomRoot::downcast::<Element>)
+                .next()
+        });
+        let el = match el {
+            Some(el) => el,
             None => return,
         };
 
-        let el = match node.downcast::<Element>() {
-            Some(el) => DomRoot::from_ref(el),
-            None => {
-                let parent = node.GetParentNode();
-                match parent.and_then(DomRoot::downcast::<Element>) {
-                    Some(parent) => parent,
-                    None => return,
-                }
-            },
-        };
-
-        // If the target is an iframe, forward the event to the child document.
-        if let Some(iframe) = el.downcast::<HTMLIFrameElement>() {
-            if let Some(pipeline_id) = iframe.pipeline_id() {
-                let rect = iframe.upcast::<Element>().GetBoundingClientRect();
-                let child_origin = Vector2D::new(rect.X() as f32, rect.Y() as f32);
-                let child_point = client_point - child_origin;
-
-                let event = CompositorEvent::MouseButtonEvent(mouse_event_type, button, child_point);
-                let event = ScriptMsg::ForwardEvent(pipeline_id, event);
-                self.send_to_constellation(event);
-            }
-            return;
-        }
-
         let node = el.upcast::<Node>();
         debug!("{} on {:?}", mouse_event_type_string, node.debug_str());
         // Prevent click event if form control element is disabled.
         if let MouseEventType::Click = mouse_event_type {
             if el.click_event_filter_by_disabled_state() {
                 return;
             }
 
@@ -992,55 +967,34 @@ impl Document {
             }
         }
 
         // Update last_click_info with the time and position of the click.
         *self.last_click_info.borrow_mut() = Some((now, click_pos));
     }
 
     #[allow(unsafe_code)]
-    pub fn handle_touchpad_pressure_event(&self,
-                                          js_runtime: *mut JSRuntime,
-                                          client_point: Point2D<f32>,
-                                          pressure: f32,
-                                          phase_now: TouchpadPressurePhase) {
-        let node = match self.window.hit_test_query(client_point, false) {
-            Some(node_address) => unsafe {
-                node::from_untrusted_node_address(js_runtime, node_address)
-            },
-            None => return
+    pub fn handle_touchpad_pressure_event(
+        &self,
+        js_runtime: *mut JSRuntime,
+        pressure: f32,
+        phase_now: TouchpadPressurePhase,
+        node_address: Option<UntrustedNodeAddress>
+    ) {
+        let el = node_address.and_then(|address| {
+            let node = unsafe { node::from_untrusted_node_address(js_runtime, address) };
+            node.inclusive_ancestors()
+                .filter_map(DomRoot::downcast::<Element>)
+                .next()
+        });
+        let el = match el {
+            Some(el) => el,
+            None => return,
         };
 
-        let el = match node.downcast::<Element>() {
-            Some(el) => DomRoot::from_ref(el),
-            None => {
-                let parent = node.GetParentNode();
-                match parent.and_then(DomRoot::downcast::<Element>) {
-                    Some(parent) => parent,
-                    None => return
-                }
-            },
-        };
-
-        // If the target is an iframe, forward the event to the child document.
-        if let Some(iframe) = el.downcast::<HTMLIFrameElement>() {
-            if let Some(pipeline_id) = iframe.pipeline_id() {
-                let rect = iframe.upcast::<Element>().GetBoundingClientRect();
-                let child_origin = Vector2D::new(rect.X() as f32, rect.Y() as f32);
-                let child_point = client_point - child_origin;
-
-                let event = CompositorEvent::TouchpadPressureEvent(child_point,
-                                                                   pressure,
-                                                                   phase_now);
-                let event = ScriptMsg::ForwardEvent(pipeline_id, event);
-                self.send_to_constellation(event);
-            }
-            return;
-        }
-
         let phase_before = self.touchpad_pressure_phase.get();
         self.touchpad_pressure_phase.set(phase_now);
 
         if phase_before == TouchpadPressurePhase::BeforeClick &&
            phase_now == TouchpadPressurePhase::BeforeClick {
             return;
         }
 
@@ -1096,55 +1050,48 @@ impl Document {
                                           false,
                                           0i16,
                                           None);
         let event = mouse_event.upcast::<Event>();
         event.fire(target);
     }
 
     #[allow(unsafe_code)]
-    pub fn handle_mouse_move_event(&self,
-                                   js_runtime: *mut JSRuntime,
-                                   client_point: Option<Point2D<f32>>,
-                                   prev_mouse_over_target: &MutNullableDom<Element>) {
+    pub fn handle_mouse_move_event(
+        &self,
+        js_runtime: *mut JSRuntime,
+        client_point: Option<Point2D<f32>>,
+        prev_mouse_over_target: &MutNullableDom<Element>,
+        node_address: Option<UntrustedNodeAddress>
+    ) {
         let client_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(client_point) => client_point,
         };
 
-        let maybe_new_target = self.window.hit_test_query(client_point, true).and_then(|address| {
+        let maybe_new_target = node_address.and_then(|address| {
             let node = unsafe { node::from_untrusted_node_address(js_runtime, address) };
             node.inclusive_ancestors()
                 .filter_map(DomRoot::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) = new_target.downcast::<HTMLIFrameElement>() {
-                if let Some(pipeline_id) = iframe.pipeline_id() {
-                    let rect = iframe.upcast::<Element>().GetBoundingClientRect();
-                    let child_origin = Vector2D::new(rect.X() as f32, rect.Y() as f32);
-                    let child_point = client_point - child_origin;
-
-                    let event = CompositorEvent::MouseMoveEvent(Some(child_point));
-                    let event = ScriptMsg::ForwardEvent(pipeline_id, event);
-                    self.send_to_constellation(event);
-                }
-                return;
-            }
-
-            self.fire_mouse_event(client_point, new_target.upcast(), "mousemove".to_owned());
-        }
+        // Send mousemove event to topmost target, unless it's an iframe, in which case the
+        // compositor should have also sent an event to the inner document.
+        let new_target = match maybe_new_target {
+            Some(ref target) => target,
+            None => return,
+        };
+
+        self.fire_mouse_event(client_point, new_target.upcast(), "mousemove".to_owned());
 
         // 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()) {
@@ -1192,61 +1139,43 @@ impl Document {
 
         // Store the current mouse over target for next frame.
         prev_mouse_over_target.set(maybe_new_target.r());
 
         self.window.reflow(ReflowGoal::Full, ReflowReason::MouseEvent);
     }
 
     #[allow(unsafe_code)]
-    pub fn handle_touch_event(&self,
-                              js_runtime: *mut JSRuntime,
-                              event_type: TouchEventType,
-                              touch_id: TouchId,
-                              point: Point2D<f32>)
-                              -> TouchEventResult {
+    pub fn handle_touch_event(
+        &self,
+        js_runtime: *mut JSRuntime,
+        event_type: TouchEventType,
+        touch_id: TouchId,
+        point: Point2D<f32>,
+        node_address: Option<UntrustedNodeAddress>
+    ) -> TouchEventResult {
         let TouchId(identifier) = touch_id;
 
         let event_name = match event_type {
             TouchEventType::Down => "touchstart",
             TouchEventType::Move => "touchmove",
             TouchEventType::Up => "touchend",
             TouchEventType::Cancel => "touchcancel",
         };
 
-        let node = match self.window.hit_test_query(point, false) {
-            Some(node_address) => unsafe {
-                node::from_untrusted_node_address(js_runtime, node_address)
-            },
-            None => return TouchEventResult::Processed(false),
+        let el = node_address.and_then(|address| {
+            let node = unsafe { node::from_untrusted_node_address(js_runtime, address) };
+            node.inclusive_ancestors()
+                .filter_map(DomRoot::downcast::<Element>)
+                .next()
+        });
+        let el = match el {
+            Some(el) => el,
+            None => return TouchEventResult::Forwarded,
         };
-        let el = match node.downcast::<Element>() {
-            Some(el) => DomRoot::from_ref(el),
-            None => {
-                let parent = node.GetParentNode();
-                match parent.and_then(DomRoot::downcast::<Element>) {
-                    Some(parent) => parent,
-                    None => return TouchEventResult::Processed(false),
-                }
-            },
-        };
-
-        // If the target is an iframe, forward the event to the child document.
-        if let Some(iframe) = el.downcast::<HTMLIFrameElement>() {
-            if let Some(pipeline_id) = iframe.pipeline_id() {
-                let rect = iframe.upcast::<Element>().GetBoundingClientRect();
-                let child_origin = Vector2D::new(rect.X() as f32, rect.Y() as f32);
-                let child_point = point - child_origin;
-
-                let event = CompositorEvent::TouchEvent(event_type, touch_id, child_point);
-                let event = ScriptMsg::ForwardEvent(pipeline_id, event);
-                self.send_to_constellation(event);
-            }
-            return TouchEventResult::Forwarded;
-        }
 
         let target = DomRoot::upcast::<EventTarget>(el);
         let window = &*self.window;
 
         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);
@@ -2000,18 +1929,22 @@ impl Document {
         event.fire(target);
     }
 
     /// <https://html.spec.whatwg.org/multipage/#cookie-averse-document-object>
     pub fn is_cookie_averse(&self) -> bool {
         !self.has_browsing_context || !url_has_network_scheme(&self.url())
     }
 
-    pub fn nodes_from_point(&self, client_point: &Point2D<f32>) -> Vec<UntrustedNodeAddress> {
-        if !self.window.reflow(ReflowGoal::NodesFromPoint(*client_point), ReflowReason::Query) {
+    pub fn nodes_from_point(&self,
+                            client_point: &Point2D<f32>,
+                            reflow_goal: NodesFromPointQueryType)
+                            -> Vec<UntrustedNodeAddress> {
+        if !self.window.reflow(ReflowGoal::NodesFromPointQuery(*client_point, reflow_goal),
+                               ReflowReason::Query) {
             return vec!();
         };
 
         self.window.layout().nodes_from_point_response()
     }
 
     /// <https://html.spec.whatwg.org/multipage/#look-up-a-custom-element-definition>
     pub fn lookup_custom_element_definition(&self,
@@ -3657,22 +3590,21 @@ impl DocumentMethods for Document {
         if self.browsing_context().is_none() {
             return None;
         }
 
         if x < 0.0 || y < 0.0 || x > viewport.width || y > viewport.height {
             return None;
         }
 
-        match self.window.hit_test_query(*point, false) {
-            Some(untrusted_node_address) => {
+        match self.nodes_from_point(point, NodesFromPointQueryType::Topmost).first() {
+            Some(address) => {
                 let js_runtime = unsafe { JS_GetRuntime(window.get_cx()) };
-
                 let node = unsafe {
-                    node::from_untrusted_node_address(js_runtime, untrusted_node_address)
+                    node::from_untrusted_node_address(js_runtime, *address)
                 };
                 let parent_node = node.GetParentNode().unwrap();
                 let element_ref = node.downcast::<Element>().unwrap_or_else(|| {
                     parent_node.downcast::<Element>().unwrap()
                 });
 
                 Some(DomRoot::from_ref(element_ref))
             },
@@ -3696,17 +3628,18 @@ impl DocumentMethods for Document {
         // Step 2
         if x < 0.0 || y < 0.0 || x > viewport.width || y > viewport.height {
             return vec!();
         }
 
         let js_runtime = unsafe { JS_GetRuntime(window.get_cx()) };
 
         // Step 1 and Step 3
-        let mut elements: Vec<DomRoot<Element>> = self.nodes_from_point(point).iter()
+        let nodes = self.nodes_from_point(point, NodesFromPointQueryType::All);
+        let mut elements: Vec<DomRoot<Element>> = nodes.iter()
             .flat_map(|&untrusted_node_address| {
                 let node = unsafe {
                     node::from_untrusted_node_address(js_runtime, untrusted_node_address)
                 };
                 DomRoot::downcast::<Element>(node)
         }).collect();
 
         // Step 4
--- a/servo/components/script/dom/window.rs
+++ b/servo/components/script/dom/window.rs
@@ -1144,38 +1144,25 @@ impl Window {
                               None);
     }
 
     /// <https://drafts.csswg.org/cssom-view/#perform-a-scroll>
     pub fn perform_a_scroll(&self,
                             x: f32,
                             y: f32,
                             scroll_root_id: ClipId,
-                            behavior: ScrollBehavior,
-                            element: Option<&Element>) {
-        //TODO Step 1
-        let point = Point2D::new(x, y);
-        let smooth = match behavior {
-            ScrollBehavior::Auto => {
-                element.map_or(false, |_element| {
-                    // TODO check computed scroll-behaviour CSS property
-                    true
-                })
-            }
-            ScrollBehavior::Instant => false,
-            ScrollBehavior::Smooth => true
-        };
-
+                            _behavior: ScrollBehavior,
+                            _element: Option<&Element>) {
+        // TODO Step 1
+        // TODO(mrobinson, #18709): Add smooth scrolling support to WebRender so that we can
+        // properly process ScrollBehavior here.
         self.layout_chan.send(Msg::UpdateScrollStateFromScript(ScrollState {
             scroll_root_id: scroll_root_id,
             scroll_offset: Vector2D::new(-x, -y),
         })).unwrap();
-
-        let message = ScriptMsg::ScrollFragmentPoint(scroll_root_id, point, smooth);
-        self.send_to_constellation(message);
     }
 
     pub fn update_viewport_for_scroll(&self, x: f32, y: f32) {
         let size = self.current_viewport.get().size;
         let new_viewport = Rect::new(Point2D::new(Au::from_f32_px(x), Au::from_f32_px(y)), size);
         self.current_viewport.set(new_viewport)
     }
 
@@ -1401,28 +1388,16 @@ impl Window {
 
     pub fn client_rect_query(&self, node_geometry_request: TrustedNodeAddress) -> Rect<i32> {
         if !self.reflow(ReflowGoal::NodeGeometryQuery(node_geometry_request), ReflowReason::Query) {
             return Rect::zero();
         }
         self.layout_rpc.node_geometry().client_rect
     }
 
-    pub fn hit_test_query(&self,
-                          client_point: Point2D<f32>,
-                          update_cursor: bool)
-                          -> Option<UntrustedNodeAddress> {
-        if !self.reflow(ReflowGoal::HitTestQuery(client_point, update_cursor),
-                        ReflowReason::Query) {
-            return None
-        }
-
-        self.layout_rpc.hit_test().node_address
-    }
-
     pub fn scroll_area_query(&self, node: TrustedNodeAddress) -> Rect<i32> {
         if !self.reflow(ReflowGoal::NodeScrollGeometryQuery(node), ReflowReason::Query) {
             return Rect::zero();
         }
         self.layout_rpc.node_scroll_area().client_rect
     }
 
     pub fn overflow_query(&self,
@@ -1905,18 +1880,17 @@ fn should_move_clip_rect(clip_rect: Rect
 }
 
 fn debug_reflow_events(id: PipelineId, reflow_goal: &ReflowGoal, reason: &ReflowReason) {
     let mut debug_msg = format!("**** pipeline={}", id);
     debug_msg.push_str(match *reflow_goal {
         ReflowGoal::Full => "\tFull",
         ReflowGoal::ContentBoxQuery(_n) => "\tContentBoxQuery",
         ReflowGoal::ContentBoxesQuery(_n) => "\tContentBoxesQuery",
-        ReflowGoal::HitTestQuery(..) => "\tHitTestQuery",
-        ReflowGoal::NodesFromPoint(..) => "\tNodesFromPoint",
+        ReflowGoal::NodesFromPointQuery(..) => "\tNodesFromPointQuery",
         ReflowGoal::NodeGeometryQuery(_n) => "\tNodeGeometryQuery",
         ReflowGoal::NodeOverflowQuery(_n) => "\tNodeOverFlowQuery",
         ReflowGoal::NodeScrollGeometryQuery(_n) => "\tNodeScrollGeometryQuery",
         ReflowGoal::NodeScrollRootIdQuery(_n) => "\tNodeScrollRootIdQuery",
         ReflowGoal::ResolvedStyleQuery(_, _, _) => "\tResolvedStyleQuery",
         ReflowGoal::OffsetParentQuery(_n) => "\tOffsetParentQuery",
         ReflowGoal::MarginStyleQuery(_n) => "\tMarginStyleQuery",
         ReflowGoal::TextIndexQuery(..) => "\tTextIndexQuery",
--- a/servo/components/script/script_thread.rs
+++ b/servo/components/script/script_thread.rs
@@ -1005,17 +1005,17 @@ impl ScriptThread {
                     // step 7.8
                     if !animation_ticks.contains(&pipeline_id) {
                         animation_ticks.insert(pipeline_id);
                         sequential.push(event);
                     }
                 }
                 FromConstellation(ConstellationControlMsg::SendEvent(
                         _,
-                        MouseMoveEvent(_))) => {
+                        MouseMoveEvent(..))) => {
                     match mouse_move_event_index {
                         None => {
                             mouse_move_event_index = Some(sequential.len());
                             sequential.push(event);
                         }
                         Some(index) => {
                             sequential[index] = event
                         }
@@ -2182,31 +2182,32 @@ impl ScriptThread {
     ///
     /// TODO: Actually perform DOM event dispatch.
     fn handle_event(&self, pipeline_id: PipelineId, event: CompositorEvent) {
         match event {
             ResizeEvent(new_size, size_type) => {
                 self.handle_resize_event(pipeline_id, new_size, size_type);
             }
 
-            MouseButtonEvent(event_type, button, point) => {
-                self.handle_mouse_event(pipeline_id, event_type, button, point);
+            MouseButtonEvent(event_type, button, point, node_address) => {
+                self.handle_mouse_event(pipeline_id, event_type, button, point, node_address);
             }
 
-            MouseMoveEvent(point) => {
+            MouseMoveEvent(point, node_address) => {
                 let document = match { self.documents.borrow().find_document(pipeline_id) } {
                     Some(document) => document,
                     None => return warn!("Message sent to closed pipeline {}.", pipeline_id),
                 };
 
                 // 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);
+                                                 &self.topmost_mouse_over_target,
+                                                 node_address);
 
                 // Short-circuit if nothing changed
                 if self.topmost_mouse_over_target.get() == prev_mouse_over_target {
                     return;
                 }
 
                 let mut state_already_changed = false;
 
@@ -2239,18 +2240,24 @@ impl ScriptThread {
                                                .filter_map(DomRoot::downcast::<HTMLAnchorElement>)
                                                .next() {
                             let event = ScriptMsg::NodeStatus(None);
                             self.script_sender.send((pipeline_id, event)).unwrap();
                         }
                     }
                 }
             }
-            TouchEvent(event_type, identifier, point) => {
-                let touch_result = self.handle_touch_event(pipeline_id, event_type, identifier, point);
+            TouchEvent(event_type, identifier, point, node_address) => {
+                let touch_result = self.handle_touch_event(
+                    pipeline_id,
+                    event_type,
+                    identifier,
+                    point,
+                    node_address
+                );
                 match (event_type, touch_result) {
                     (TouchEventType::Down, TouchEventResult::Processed(handled)) => {
                         let result = if handled {
                             // TODO: Wait to see if preventDefault is called on the first touchmove event.
                             EventResult::DefaultAllowed
                         } else {
                             EventResult::DefaultPrevented
                         };
@@ -2258,60 +2265,82 @@ impl ScriptThread {
                         self.script_sender.send((pipeline_id, message)).unwrap();
                     }
                     _ => {
                         // TODO: Calling preventDefault on a touchup event should prevent clicks.
                     }
                 }
             }
 
-            TouchpadPressureEvent(point, pressure, phase) => {
+            TouchpadPressureEvent(_point, pressure, phase, node_address) => {
                 let doc = match { self.documents.borrow().find_document(pipeline_id) } {
                     Some(doc) => doc,
                     None => return warn!("Message sent to closed pipeline {}.", pipeline_id),
                 };
-                doc.handle_touchpad_pressure_event(self.js_runtime.rt(), point, pressure, phase);
+                doc.handle_touchpad_pressure_event(
+                    self.js_runtime.rt(),
+                    pressure,
+                    phase,
+                    node_address
+                );
             }
 
             KeyEvent(ch, key, state, modifiers) => {
                 let document = match { self.documents.borrow().find_document(pipeline_id) } {
                     Some(document) => document,
                     None => return warn!("Message sent to closed pipeline {}.", pipeline_id),
                 };
                 document.dispatch_key_event(ch, key, state, modifiers);
             }
         }
     }
 
-    fn handle_mouse_event(&self,
-                          pipeline_id: PipelineId,
-                          mouse_event_type: MouseEventType,
-                          button: MouseButton,
-                          point: Point2D<f32>) {
+    fn handle_mouse_event(
+        &self,
+        pipeline_id: PipelineId,
+        mouse_event_type: MouseEventType,
+        button: MouseButton,
+        point: Point2D<f32>,
+        node_address: Option<UntrustedNodeAddress>
+    ) {
         let document = match { self.documents.borrow().find_document(pipeline_id) } {
             Some(document) => document,
             None => return warn!("Message sent to closed pipeline {}.", pipeline_id),
         };
-        document.handle_mouse_event(self.js_runtime.rt(), button, point, mouse_event_type);
+        document.handle_mouse_event(
+            self.js_runtime.rt(),
+            button,
+            point,
+            mouse_event_type,
+            node_address
+        );
     }
 
-    fn handle_touch_event(&self,
-                          pipeline_id: PipelineId,
-                          event_type: TouchEventType,
-                          identifier: TouchId,
-                          point: Point2D<f32>)
-                          -> TouchEventResult {
+    fn handle_touch_event(
+        &self,
+        pipeline_id: PipelineId,
+        event_type: TouchEventType,
+        identifier: TouchId,
+        point: Point2D<f32>,
+        node_address: Option<UntrustedNodeAddress>
+    ) -> TouchEventResult {
         let document = match { self.documents.borrow().find_document(pipeline_id) } {
             Some(document) => document,
             None => {
                 warn!("Message sent to closed pipeline {}.", pipeline_id);
                 return TouchEventResult::Processed(true);
             },
         };
-        document.handle_touch_event(self.js_runtime.rt(), event_type, identifier, point)
+        document.handle_touch_event(
+            self.js_runtime.rt(),
+            event_type,
+            identifier,
+            point,
+            node_address
+        )
     }
 
     /// <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).
     fn handle_navigate(&self, parent_pipeline_id: PipelineId,
                               browsing_context_id: Option<BrowsingContextId>,
                               mut load_data: LoadData,
--- a/servo/components/script_layout_interface/message.rs
+++ b/servo/components/script_layout_interface/message.rs
@@ -95,63 +95,67 @@ pub enum Msg {
 
     /// Tells layout that script has added some paint worklet modules.
     RegisterPaint(Atom, Vec<Atom>, Box<Painter>),
 
     /// Send to layout the precise time when the navigation started.
     SetNavigationStart(f64),
 }
 
+#[derive(Debug, PartialEq)]
+pub enum NodesFromPointQueryType {
+    All,
+    Topmost,
+}
 
 /// Any query to perform with this reflow.
 #[derive(Debug, PartialEq)]
 pub enum ReflowGoal {
     Full,
     TickAnimations,
     ContentBoxQuery(TrustedNodeAddress),
     ContentBoxesQuery(TrustedNodeAddress),
     NodeOverflowQuery(TrustedNodeAddress),
-    HitTestQuery(Point2D<f32>, bool),
     NodeScrollRootIdQuery(TrustedNodeAddress),
     NodeGeometryQuery(TrustedNodeAddress),
     NodeScrollGeometryQuery(TrustedNodeAddress),
     ResolvedStyleQuery(TrustedNodeAddress, Option<PseudoElement>, PropertyId),
     OffsetParentQuery(TrustedNodeAddress),
     MarginStyleQuery(TrustedNodeAddress),
     TextIndexQuery(TrustedNodeAddress, i32, i32),
-    NodesFromPoint(Point2D<f32>),
+    NodesFromPointQuery(Point2D<f32>, NodesFromPointQueryType),
 }
 
 impl ReflowGoal {
     /// Returns true if the given ReflowQuery needs a full, up-to-date display list to
     /// be present or false if it only needs stacking-relative positions.
     pub fn needs_display_list(&self) -> bool {
         match *self {
-            ReflowGoal::NodesFromPoint(..) | ReflowGoal::HitTestQuery(..) |
-            ReflowGoal::TextIndexQuery(..) | ReflowGoal::TickAnimations |
-            ReflowGoal::Full => true,
+            ReflowGoal::NodesFromPointQuery(..) | ReflowGoal::TextIndexQuery(..) |
+            ReflowGoal::TickAnimations | ReflowGoal::Full => true,
             ReflowGoal::ContentBoxQuery(_) | ReflowGoal::ContentBoxesQuery(_) |
             ReflowGoal::NodeGeometryQuery(_) | ReflowGoal::NodeScrollGeometryQuery(_) |
             ReflowGoal::NodeOverflowQuery(_) | ReflowGoal::NodeScrollRootIdQuery(_) |
             ReflowGoal::ResolvedStyleQuery(..) | ReflowGoal::OffsetParentQuery(_) |
             ReflowGoal::MarginStyleQuery(_)  => false,
         }
     }
 
     /// Returns true if the given ReflowQuery needs its display list send to WebRender or
     /// false if a layout_thread display list is sufficient.
     pub fn needs_display(&self) -> bool {
         match *self {
             ReflowGoal::MarginStyleQuery(_)  | ReflowGoal::TextIndexQuery(..) |
-            ReflowGoal::HitTestQuery(..) | ReflowGoal::ContentBoxQuery(_) |
-            ReflowGoal::ContentBoxesQuery(_) | ReflowGoal::NodeGeometryQuery(_) |
-            ReflowGoal::NodeScrollGeometryQuery(_) | ReflowGoal::NodeOverflowQuery(_) |
-            ReflowGoal::NodeScrollRootIdQuery(_) | ReflowGoal::ResolvedStyleQuery(..) |
-            ReflowGoal::OffsetParentQuery(_) | ReflowGoal::NodesFromPoint(..) => false,
-            ReflowGoal::Full | ReflowGoal::TickAnimations => true,
+            ReflowGoal::ContentBoxQuery(_) | ReflowGoal::ContentBoxesQuery(_) |
+            ReflowGoal::NodeGeometryQuery(_) | ReflowGoal::NodeScrollGeometryQuery(_) |
+            ReflowGoal::NodeOverflowQuery(_) | ReflowGoal::NodeScrollRootIdQuery(_) |
+            ReflowGoal::ResolvedStyleQuery(..) |
+            ReflowGoal::OffsetParentQuery(_) => false,
+            ReflowGoal::NodesFromPointQuery(..) | ReflowGoal::Full |
+            ReflowGoal::TickAnimations => true,
         }
     }
 }
 
 /// Information needed for a reflow.
 pub struct Reflow {
     ///  A clipping rectangle for the page, an enlarged rectangle containing the viewport.
     pub page_clip_rect: Rect<Au>,
--- a/servo/components/script_layout_interface/rpc.rs
+++ b/servo/components/script_layout_interface/rpc.rs
@@ -24,45 +24,39 @@ pub trait LayoutRPC {
     /// Requests the geometry of this node. Used by APIs such as `clientTop`.
     fn node_geometry(&self) -> NodeGeometryResponse;
     /// Requests the overflow-x and overflow-y of this node. Used by `scrollTop` etc.
     fn node_overflow(&self) -> NodeOverflowResponse;
     /// Requests the scroll geometry of this node. Used by APIs such as `scrollTop`.
     fn node_scroll_area(&self) -> NodeGeometryResponse;
     /// Requests the scroll root id of this node. Used by APIs such as `scrollTop`
     fn node_scroll_root_id(&self) -> NodeScrollRootIdResponse;
-    /// Requests the node containing the point of interest
-    fn hit_test(&self) -> HitTestResponse;
     /// Query layout for the resolved value of a given CSS property
     fn resolved_style(&self) -> ResolvedStyleResponse;
     fn offset_parent(&self) -> OffsetParentResponse;
     /// Query layout for the resolve values of the margin properties for an element.
     fn margin_style(&self) -> MarginStyleResponse;
+    fn text_index(&self) -> TextIndexResponse;
     /// Requests the list of nodes from the given point.
     fn nodes_from_point_response(&self) -> Vec<UntrustedNodeAddress>;
 
-    fn text_index(&self) -> TextIndexResponse;
 }
 
 pub struct ContentBoxResponse(pub Option<Rect<Au>>);
 
 pub struct ContentBoxesResponse(pub Vec<Rect<Au>>);
 
 pub struct NodeGeometryResponse {
     pub client_rect: Rect<i32>,
 }
 
 pub struct NodeOverflowResponse(pub Option<Point2D<overflow_x::computed_value::T>>);
 
 pub struct NodeScrollRootIdResponse(pub ClipId);
 
-pub struct HitTestResponse {
-    pub node_address: Option<UntrustedNodeAddress>,
-}
-
 pub struct ResolvedStyleResponse(pub String);
 
 #[derive(Clone)]
 pub struct OffsetParentResponse {
     pub node_address: Option<UntrustedNodeAddress>,
     pub rect: Rect<Au>,
 }
 
--- a/servo/components/script_traits/lib.rs
+++ b/servo/components/script_traits/lib.rs
@@ -63,16 +63,17 @@ use servo_atoms::Atom;
 use servo_url::ImmutableOrigin;
 use servo_url::ServoUrl;
 use std::collections::HashMap;
 use std::fmt;
 use std::sync::Arc;
 use std::sync::mpsc::{Receiver, Sender, RecvTimeoutError};
 use style_traits::CSSPixel;
 use style_traits::SpeculativePainter;
+use style_traits::cursor::Cursor;
 use webdriver_msg::{LoadStatus, WebDriverScriptCommand};
 use webrender_api::{ClipId, DevicePixel, DocumentId, ImageKey};
 use webvr_traits::{WebVREvent, WebVRMsg};
 
 pub use script_msg::{LayoutMsg, ScriptMsg, EventResult, LogEntry};
 pub use script_msg::{ServiceWorkerMsg, ScopeThings, SWManagerMsg, SWManagerSenders, DOMMessage};
 
 /// The address of a node. Layout sends these back. They must be validated via
@@ -430,23 +431,23 @@ pub enum MouseEventType {
 }
 
 /// Events from the compositor that the script thread needs to know about
 #[derive(Deserialize, Serialize)]
 pub enum CompositorEvent {
     /// The window was resized.
     ResizeEvent(WindowSizeData, WindowSizeType),
     /// A mouse button state changed.
-    MouseButtonEvent(MouseEventType, MouseButton, Point2D<f32>),
+    MouseButtonEvent(MouseEventType, MouseButton, Point2D<f32>, Option<UntrustedNodeAddress>),
     /// The mouse was moved over a point (or was moved out of the recognizable region).
-    MouseMoveEvent(Option<Point2D<f32>>),
+    MouseMoveEvent(Option<Point2D<f32>>, Option<UntrustedNodeAddress>),
     /// A touch event was generated with a touch ID and location.
-    TouchEvent(TouchEventType, TouchId, Point2D<f32>),
+    TouchEvent(TouchEventType, TouchId, Point2D<f32>, Option<UntrustedNodeAddress>),
     /// Touchpad pressure event
-    TouchpadPressureEvent(Point2D<f32>, f32, TouchpadPressurePhase),
+    TouchpadPressureEvent(Point2D<f32>, f32, TouchpadPressurePhase, Option<UntrustedNodeAddress>),
     /// A key was pressed.
     KeyEvent(Option<char>, Key, KeyState, KeyModifiers),
 }
 
 /// Touchpad pressure phase for `TouchpadPressureEvent`.
 #[derive(Clone, Copy, Deserialize, HeapSizeOf, PartialEq, Serialize)]
 pub enum TouchpadPressurePhase {
     /// Pressure before a regular click.
@@ -794,16 +795,20 @@ pub enum ConstellationMsg {
     /// Dispatch WebVR events to the subscribed script threads.
     WebVREvents(Vec<PipelineId>, Vec<WebVREvent>),
     /// Create a new top level browsing context.
     NewBrowser(ServoUrl, IpcSender<TopLevelBrowsingContextId>),
     /// Close a top level browsing context.
     CloseBrowser(TopLevelBrowsingContextId),
     /// Make browser visible.
     SelectBrowser(TopLevelBrowsingContextId),
+    /// Forward an event to the script task of the given pipeline.
+    ForwardEvent(PipelineId, CompositorEvent),
+    /// Requesting a change to the onscreen cursor.
+    SetCursor(Cursor),
 }
 
 /// Resources required by workerglobalscopes
 #[derive(Clone, Deserialize, Serialize)]
 pub struct WorkerGlobalScopeInit {
     /// Chan to a resource thread
     pub resource_threads: ResourceThreads,
     /// Chan to the memory profiler
--- a/servo/components/script_traits/script_msg.rs
+++ b/servo/components/script_traits/script_msg.rs
@@ -22,17 +22,16 @@ use msg::constellation_msg::{Key, KeyMod
 use net_traits::CoreResourceMsg;
 use net_traits::request::RequestInit;
 use net_traits::storage_thread::StorageType;
 use servo_url::ImmutableOrigin;
 use servo_url::ServoUrl;
 use style_traits::CSSPixel;
 use style_traits::cursor::Cursor;
 use style_traits::viewport::ViewportConstraints;
-use webrender_api::ClipId;
 
 /// Messages from the layout to the constellation.
 #[derive(Deserialize, Serialize)]
 pub enum LayoutMsg {
     /// Indicates whether this pipeline is currently running animations.
     ChangeRunningAnimationsState(PipelineId, AnimationState),
     /// Inform the constellation of the size of the iframe's viewport.
     IFrameSizes(Vec<(BrowsingContextId, TypedSize2D<f32, CSSPixel>)>),
@@ -129,18 +128,16 @@ pub enum ScriptMsg {
     /// Mark a new document as active
     ActivateDocument,
     /// Set the document state for a pipeline (used by screenshot / reftests)
     SetDocumentState(DocumentState),
     /// Update the pipeline Url, which can change after redirections.
     SetFinalUrl(ServoUrl),
     /// Check if an alert dialog box should be presented
     Alert(String, IpcSender<bool>),
-    /// Scroll a page in a window
-    ScrollFragmentPoint(ClipId, Point2D<f32>, bool),
     /// Set title of current page
     /// <https://html.spec.whatwg.org/multipage/#document.title>
     SetTitle(Option<String>),
     /// Send a key event
     SendKeyEvent(Option<char>, Key, KeyState, KeyModifiers),
     /// Get Window Informations size and position
     GetClientWindow(IpcSender<(Size2D<u32>, Point2D<i32>)>),
     /// Move the window to a point
--- a/servo/components/style_traits/cursor.rs
+++ b/servo/components/style_traits/cursor.rs
@@ -30,16 +30,25 @@ macro_rules! define_cursor {
             /// Given a CSS keyword, get the corresponding cursor enum.
             pub fn from_css_keyword(keyword: &str) -> Result<Cursor, ()> {
                 match_ignore_ascii_case! { &keyword,
                     $( $c_css => Ok(Cursor::$c_variant), )+
                     $( #[cfg(feature = "gecko")] $g_css => Ok(Cursor::$g_variant), )+
                     _ => Err(())
                 }
             }
+
+            /// From the C u8 value, get the corresponding Cursor enum.
+            pub fn from_u8(value: u8) -> Result<Cursor, ()> {
+                match value {
+                    $( $c_value => Ok(Cursor::$c_variant), )+
+                    $( #[cfg(feature = "gecko")] $g_value => Ok(Cursor::$g_variant), )+
+                    _ => Err(())
+                }
+            }
         }
 
         impl ToCss for Cursor {
             fn to_css<W>(&self, dest: &mut W) -> ::std::fmt::Result where W: ::std::fmt::Write {
                 match *self {
                     $( Cursor::$c_variant => dest.write_str($c_css), )+
                     $( #[cfg(feature = "gecko")] Cursor::$g_variant => dest.write_str($g_css), )+
                 }