servo: Merge #20228 - Ports refactoring (from paulrouget:ports_refactor); r=jdm
authorPaul Rouget <me@paulrouget.com>
Thu, 22 Mar 2018 08:10:45 -0400
changeset 409561 2c6f444e76cee053ee27c3010d1c55d594e428e4
parent 409560 a348763a5ee1eead73ff2f2701cb72f35b4a7782
child 409562 37e07672384db6648f4477b98c3646728a0c9d9a
push id101247
push usernerli@mozilla.com
push dateThu, 22 Mar 2018 23:00:51 +0000
treeherdermozilla-inbound@02e384bdf97d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjdm
milestone61.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 #20228 - Ports refactoring (from paulrouget:ports_refactor); r=jdm Depends on https://github.com/servo/servo/pull/20071 and https://github.com/servo/servo/pull/19895 This PR does 3 things: 1) merge all the embedder coordinates callbacks into one 2) hand the embedder messages directly to the embedder 3) split the embedder code in 2 files: window.rs and browser.rs This is in preparation for tabs support in `ports/`. We want to separate the windowing logic (glutin stuff) and the browsing logic (tabs management, browsers state, etc). It's also easier to bypass the callbacks and directly handle events. --- <!-- 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 - [ ] These changes do not require tests because _____ <!-- 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: 2de89377db63ec03ae3b1256486c4c32b33f5fce
servo/Cargo.lock
servo/components/compositing/compositor.rs
servo/components/compositing/compositor_thread.rs
servo/components/compositing/windowing.rs
servo/components/constellation/constellation.rs
servo/components/script_traits/script_msg.rs
servo/components/servo/lib.rs
servo/ports/servo/Cargo.toml
servo/ports/servo/browser.rs
servo/ports/servo/glutin_app/keyutils.rs
servo/ports/servo/glutin_app/mod.rs
servo/ports/servo/glutin_app/window.rs
servo/ports/servo/main.rs
servo/resources/prefs.json
--- a/servo/Cargo.lock
+++ b/servo/Cargo.lock
@@ -2693,24 +2693,22 @@ dependencies = [
  "compositing 0.0.1",
  "euclid 0.17.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "gdi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "gleam 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)",
  "glutin 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "libservo 0.0.1",
  "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
  "msg 0.0.1",
- "net_traits 0.0.1",
  "osmesa-src 17.3.1-devel (git+https://github.com/servo/osmesa-src)",
  "osmesa-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "script_traits 0.0.1",
  "servo-egl 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo_config 0.0.1",
  "servo_geometry 0.0.1",
- "servo_url 0.0.1",
  "sig 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "style_traits 0.0.1",
  "tinyfiledialogs 3.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "user32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "webrender_api 0.57.0 (git+https://github.com/servo/webrender)",
  "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "winit 0.11.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "winres 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
--- a/servo/components/compositing/compositor.rs
+++ b/servo/components/compositing/compositor.rs
@@ -29,19 +29,19 @@ use std::rc::Rc;
 use std::sync::mpsc::Sender;
 use std::time::{Duration, Instant};
 use style_traits::{CSSPixel, DevicePixel, PinchZoomFactor};
 use style_traits::cursor::CursorKind;
 use style_traits::viewport::ViewportConstraints;
 use time::{now, precise_time_ns, precise_time_s};
 use touch::{TouchHandler, TouchAction};
 use webrender;
-use webrender_api::{self, DeviceIntPoint, DevicePoint, DeviceUintRect, DeviceUintSize, HitTestFlags, HitTestResult};
+use webrender_api::{self, DeviceIntPoint, DevicePoint, HitTestFlags, HitTestResult};
 use webrender_api::{LayoutVector2D, ScrollEventPhase, ScrollLocation};
-use windowing::{self, MouseWindowEvent, WebRenderDebugOption, WindowMethods};
+use windowing::{self, EmbedderCoordinates, MouseWindowEvent, WebRenderDebugOption, WindowMethods};
 
 #[derive(Debug, PartialEq)]
 enum UnableToComposite {
     WindowUnprepared,
     NotReadyToPaintImage(NotReadyToPaint),
 }
 
 #[derive(Debug, PartialEq)]
@@ -105,35 +105,26 @@ pub struct IOCompositor<Window: WindowMe
     root_pipeline: Option<CompositionPipeline>,
 
     /// Tracks details about each active pipeline that the compositor knows about.
     pipeline_details: HashMap<PipelineId, PipelineDetails>,
 
     /// The scene scale, to allow for zooming and high-resolution painting.
     scale: TypedScale<f32, LayerPixel, DevicePixel>,
 
-    /// The size of the rendering area.
-    frame_size: DeviceUintSize,
-
-    /// The position and size of the window within the rendering area.
-    window_rect: DeviceUintRect,
-
     /// "Mobile-style" zoom that does not reflow the page.
     viewport_zoom: PinchZoomFactor,
 
     /// Viewport zoom constraints provided by @viewport.
     min_viewport_zoom: Option<PinchZoomFactor>,
     max_viewport_zoom: Option<PinchZoomFactor>,
 
     /// "Desktop-style" zoom that resizes the viewport to fit the window.
     page_zoom: TypedScale<f32, CSSPixel, DeviceIndependentPixel>,
 
-    /// The device pixel ratio for this window.
-    scale_factor: TypedScale<f32, DeviceIndependentPixel, DevicePixel>,
-
     /// The type of composition to perform
     composite_target: CompositeTarget,
 
     /// Tracks whether we should composite this frame.
     composition_request: CompositionRequest,
 
     /// Tracks whether we are in the process of shutting down, or have shut down and should close
     /// the compositor.
@@ -188,16 +179,19 @@ pub struct IOCompositor<Window: WindowMe
     gl: Rc<gl::Gl>,
 
     /// Map of the pending paint metrics per layout thread.
     /// The layout thread for each specific pipeline expects the compositor to
     /// paint frames with specific given IDs (epoch). Once the compositor paints
     /// these frames, it records the paint time for each of them and sends the
     /// metric to the corresponding layout thread.
     pending_paint_metrics: HashMap<PipelineId, Epoch>,
+
+    /// The coordinates of the native window, its view and the screen.
+    embedder_coordinates: EmbedderCoordinates,
 }
 
 #[derive(Clone, Copy)]
 struct ScrollZoomEvent {
     /// Change the pinch zoom level by this factor
     magnification: f32,
     /// Scroll by this offset, or to Start or End
     scroll_location: ScrollLocation,
@@ -345,34 +339,29 @@ impl webrender_api::RenderNotifier for R
             self.wake_up();
         }
     }
 }
 
 impl<Window: WindowMethods> IOCompositor<Window> {
     fn new(window: Rc<Window>, state: InitialCompositorState)
            -> IOCompositor<Window> {
-        let frame_size = window.framebuffer_size();
-        let window_rect = window.window_rect();
-        let scale_factor = window.hidpi_factor();
         let composite_target = match opts::get().output_file {
             Some(_) => CompositeTarget::PngFile,
             None => CompositeTarget::Window
         };
 
         IOCompositor {
             gl: window.gl(),
+            embedder_coordinates: window.get_coordinates(),
             window: window,
             port: state.receiver,
             root_pipeline: None,
             pipeline_details: HashMap::new(),
-            frame_size: frame_size,
-            window_rect: window_rect,
             scale: TypedScale::new(1.0),
-            scale_factor: scale_factor,
             composition_request: CompositionRequest::NoCompositingNecessary,
             touch_handler: TouchHandler::new(),
             pending_scroll_zoom_events: Vec::new(),
             waiting_for_results_of_scroll: false,
             composite_target: composite_target,
             shutdown_state: ShutdownState::NotShuttingDown,
             page_zoom: TypedScale::new(1.0),
             viewport_zoom: PinchZoomFactor::new(1.0),
@@ -543,16 +532,34 @@ impl<Window: WindowMethods> IOCompositor
                     self.composite_if_necessary(CompositingReason::Headless);
                 }
             },
 
             (Msg::PendingPaintMetric(pipeline_id, epoch), _) => {
                 self.pending_paint_metrics.insert(pipeline_id, epoch);
             }
 
+            (Msg::GetClientWindow(req), ShutdownState::NotShuttingDown) => {
+                if let Err(e) = req.send(self.embedder_coordinates.window) {
+                    warn!("Sending response to get client window failed ({}).", e);
+                }
+            }
+
+            (Msg::GetScreenSize(req), ShutdownState::NotShuttingDown) => {
+                if let Err(e) = req.send(self.embedder_coordinates.screen) {
+                    warn!("Sending response to get screen size failed ({}).", e);
+                }
+            }
+
+            (Msg::GetScreenAvailSize(req), ShutdownState::NotShuttingDown) => {
+                if let Err(e) = req.send(self.embedder_coordinates.screen_avail) {
+                    warn!("Sending response to get screen avail size failed ({}).", e);
+                }
+            }
+
             // When we are shutting_down, we need to avoid performing operations
             // such as Paint that may crash because we have begun tearing down
             // the rest of our resources.
             (_, ShutdownState::ShuttingDown) => {}
         }
 
         true
     }
@@ -629,24 +636,24 @@ impl<Window: WindowMethods> IOCompositor
         }
     }
 
     fn remove_pipeline_root_layer(&mut self, pipeline_id: PipelineId) {
         self.pipeline_details.remove(&pipeline_id);
     }
 
     fn send_window_size(&self, size_type: WindowSizeType) {
-        let dppx = self.page_zoom * self.hidpi_factor();
+        let dppx = self.page_zoom * self.embedder_coordinates.hidpi_factor;
 
         self.webrender_api.set_window_parameters(self.webrender_document,
-                                                 self.frame_size,
-                                                 self.window_rect,
-                                                 self.hidpi_factor().get());
+                                                 self.embedder_coordinates.framebuffer,
+                                                 self.embedder_coordinates.viewport,
+                                                 self.embedder_coordinates.hidpi_factor.get());
 
-        let initial_viewport = self.window_rect.size.to_f32() / dppx;
+        let initial_viewport = self.embedder_coordinates.viewport.size.to_f32() / dppx;
 
         let data = WindowSizeData {
             device_pixel_ratio: dppx,
             initial_viewport: initial_viewport,
         };
 
         let top_level_browsing_context_id = self.root_pipeline.as_ref().map(|pipeline| {
             pipeline.top_level_browsing_context_id
@@ -657,34 +664,29 @@ impl<Window: WindowMethods> IOCompositor
         if let Err(e) = self.constellation_chan.send(msg) {
             warn!("Sending window resize to constellation failed ({}).", e);
         }
     }
 
     pub fn on_resize_window_event(&mut self) {
         debug!("compositor resize requested");
 
+        let old_coords = self.embedder_coordinates;
+        self.embedder_coordinates = self.window.get_coordinates();
+
         // 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;
+        if self.embedder_coordinates.hidpi_factor != old_coords.hidpi_factor {
             self.update_zoom_transform();
         }
 
-        let new_window_rect = self.window.window_rect();
-        let new_frame_size = self.window.framebuffer_size();
-
-        if self.window_rect == new_window_rect &&
-           self.frame_size == new_frame_size {
+        if self.embedder_coordinates.viewport == old_coords.viewport &&
+           self.embedder_coordinates.framebuffer == old_coords.framebuffer {
             return;
         }
 
-        self.frame_size = new_frame_size;
-        self.window_rect = new_window_rect;
-
         self.send_window_size(WindowSizeType::Resize);
     }
 
     pub fn on_mouse_window_event_class(&mut self, mouse_window_event: MouseWindowEvent) {
         if opts::get().convert_mouse_to_touch {
             match mouse_window_event {
                 MouseWindowEvent::Click(_, _) => {}
                 MouseWindowEvent::MouseDown(_, p) => self.on_touch_down(TouchId(0), p),
@@ -1091,17 +1093,17 @@ impl<Window: WindowMethods> IOCompositor
         }
     }
 
     fn hidpi_factor(&self) -> TypedScale<f32, DeviceIndependentPixel, DevicePixel> {
         match opts::get().device_pixels_per_px {
             Some(device_pixels_per_px) => TypedScale::new(device_pixels_per_px),
             None => match opts::get().output_file {
                 Some(_) => TypedScale::new(1.0),
-                None => self.scale_factor
+                None => self.embedder_coordinates.hidpi_factor,
             }
         }
     }
 
     fn device_pixels_per_page_px(&self) -> TypedScale<f32, CSSPixel, DevicePixel> {
         self.page_zoom * self.hidpi_factor()
     }
 
@@ -1251,17 +1253,18 @@ impl<Window: WindowMethods> IOCompositor
     /// Composite either to the screen or to a png image or both.
     /// Returns Ok if composition was performed or Err if it was not possible to composite
     /// for some reason. If CompositeTarget is Window or Png no image data is returned;
     /// in the latter case the image is written directly to a file. If CompositeTarget
     /// is WindowAndPng Ok(Some(png::Image)) is returned.
     fn composite_specific_target(&mut self,
                                  target: CompositeTarget)
                                  -> Result<Option<Image>, UnableToComposite> {
-        let (width, height) = (self.frame_size.width_typed(), self.frame_size.height_typed());
+        let width = self.embedder_coordinates.framebuffer.width_typed();
+        let height = self.embedder_coordinates.framebuffer.height_typed();
         if !self.window.prepare_for_composite(width, height) {
             return Err(UnableToComposite::WindowUnprepared)
         }
 
         self.webrender.update();
 
         let wait_for_stable_image = match target {
             CompositeTarget::WindowAndPng | CompositeTarget::PngFile => true,
@@ -1286,17 +1289,17 @@ impl<Window: WindowMethods> IOCompositor
             _ => initialize_png(&*self.gl, width, height)
         };
 
         profile(ProfilerCategory::Compositing, None, self.time_profiler_chan.clone(), || {
             debug!("compositor: compositing");
 
             // Paint the scene.
             // TODO(gw): Take notice of any errors the renderer returns!
-            self.webrender.render(self.frame_size).ok();
+            self.webrender.render(self.embedder_coordinates.framebuffer).ok();
         });
 
         // If there are pending paint metrics, we check if any of the painted epochs is
         // one of the ones that the paint metrics recorder is expecting . In that case,
         // we get the current time, inform the layout thread about it and remove the
         // pending metric from the list.
         if !self.pending_paint_metrics.is_empty() {
             let paint_time = precise_time_ns();
--- a/servo/components/compositing/compositor_thread.rs
+++ b/servo/components/compositing/compositor_thread.rs
@@ -116,23 +116,16 @@ pub enum EmbedderMsg {
     /// A status message to be displayed by the browser chrome.
     Status(TopLevelBrowsingContextId, Option<String>),
     /// Alerts the embedder that the current page has changed its title.
     ChangePageTitle(TopLevelBrowsingContextId, Option<String>),
     /// Move the window to a point
     MoveTo(TopLevelBrowsingContextId, DeviceIntPoint),
     /// Resize the window to size
     ResizeTo(TopLevelBrowsingContextId, DeviceUintSize),
-    /// Get Window Informations size and position
-    GetClientWindow(TopLevelBrowsingContextId,
-                    IpcSender<(DeviceUintSize, DeviceIntPoint)>),
-    /// Get screen size (pixel)
-    GetScreenSize(TopLevelBrowsingContextId, IpcSender<(DeviceUintSize)>),
-    /// Get screen available size (pixel)
-    GetScreenAvailSize(TopLevelBrowsingContextId, IpcSender<(DeviceUintSize)>),
     /// Wether or not to follow a link
     AllowNavigation(TopLevelBrowsingContextId, ServoUrl, IpcSender<bool>),
     /// Sends an unconsumed key event back to the embedder.
     KeyEvent(Option<TopLevelBrowsingContextId>, Option<char>, Key, KeyState, KeyModifiers),
     /// Changes the cursor.
     SetCursor(CursorKind),
     /// A favicon was detected
     NewFavicon(TopLevelBrowsingContextId, ServoUrl),
@@ -143,16 +136,18 @@ pub enum EmbedderMsg {
     /// Enter or exit fullscreen
     SetFullscreenState(TopLevelBrowsingContextId, bool),
     /// The load of a page has begun
     LoadStart(TopLevelBrowsingContextId),
     /// The load of a page has completed
     LoadComplete(TopLevelBrowsingContextId),
     /// A pipeline panicked. First string is the reason, second one is the backtrace.
     Panic(TopLevelBrowsingContextId, String, Option<String>),
+    /// Servo has shut down
+    Shutdown,
 }
 
 /// Messages from the painting thread and the constellation thread to the compositor thread.
 pub enum Msg {
     /// Requests that the compositor shut down.
     Exit,
 
     /// Informs the compositor that the constellation has completed shutdown.
@@ -191,16 +186,22 @@ pub enum Msg {
     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),
 
+    /// Get Window Informations size and position.
+    GetClientWindow(IpcSender<(DeviceUintSize, DeviceIntPoint)>),
+    /// Get screen size.
+    GetScreenSize(IpcSender<DeviceUintSize>),
+    /// Get screen available size.
+    GetScreenAvailSize(IpcSender<DeviceUintSize>),
 }
 
 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::ChangeRunningAnimationsState(..) => write!(f, "ChangeRunningAnimationsState"),
@@ -211,40 +212,41 @@ impl Debug for Msg {
             Msg::ViewportConstrained(..) => write!(f, "ViewportConstrained"),
             Msg::IsReadyToSaveImageReply(..) => write!(f, "IsReadyToSaveImageReply"),
             Msg::PipelineVisibilityChanged(..) => write!(f, "PipelineVisibilityChanged"),
             Msg::PipelineExited(..) => write!(f, "PipelineExited"),
             Msg::NewScrollFrameReady(..) => write!(f, "NewScrollFrameReady"),
             Msg::Dispatch(..) => write!(f, "Dispatch"),
             Msg::PendingPaintMetric(..) => write!(f, "PendingPaintMetric"),
             Msg::LoadComplete(..) => write!(f, "LoadComplete"),
+            Msg::GetClientWindow(..) => write!(f, "GetClientWindow"),
+            Msg::GetScreenSize(..) => write!(f, "GetScreenSize"),
+            Msg::GetScreenAvailSize(..) => write!(f, "GetScreenAvailSize"),
         }
     }
 }
 
 impl Debug for EmbedderMsg {
     fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
         match *self {
             EmbedderMsg::Status(..) => write!(f, "Status"),
             EmbedderMsg::ChangePageTitle(..) => write!(f, "ChangePageTitle"),
             EmbedderMsg::MoveTo(..) => write!(f, "MoveTo"),
             EmbedderMsg::ResizeTo(..) => write!(f, "ResizeTo"),
-            EmbedderMsg::GetClientWindow(..) => write!(f, "GetClientWindow"),
-            EmbedderMsg::GetScreenSize(..) => write!(f, "GetScreenSize"),
-            EmbedderMsg::GetScreenAvailSize(..) => write!(f, "GetScreenAvailSize"),
             EmbedderMsg::AllowNavigation(..) => write!(f, "AllowNavigation"),
             EmbedderMsg::KeyEvent(..) => write!(f, "KeyEvent"),
             EmbedderMsg::SetCursor(..) => write!(f, "SetCursor"),
             EmbedderMsg::NewFavicon(..) => write!(f, "NewFavicon"),
             EmbedderMsg::HeadParsed(..) => write!(f, "HeadParsed"),
             EmbedderMsg::HistoryChanged(..) => write!(f, "HistoryChanged"),
             EmbedderMsg::SetFullscreenState(..) => write!(f, "SetFullscreenState"),
             EmbedderMsg::LoadStart(..) => write!(f, "LoadStart"),
             EmbedderMsg::LoadComplete(..) => write!(f, "LoadComplete"),
             EmbedderMsg::Panic(..) => write!(f, "Panic"),
+            EmbedderMsg::Shutdown => write!(f, "Shutdown"),
         }
     }
 }
 
 /// Data used to construct a compositor.
 pub struct InitialCompositorState {
     /// A channel to the compositor.
     pub sender: CompositorProxy,
--- a/servo/components/compositing/windowing.rs
+++ b/servo/components/compositing/windowing.rs
@@ -4,24 +4,22 @@
 
 //! Abstract windowing methods. The concrete implementations of these can be found in `platform/`.
 
 use compositor_thread::EventLoopWaker;
 use euclid::TypedScale;
 use gleam::gl;
 use ipc_channel::ipc::IpcSender;
 use msg::constellation_msg::{Key, KeyModifiers, KeyState, TopLevelBrowsingContextId, TraversalDirection};
-use net_traits::net_error_list::NetError;
-use script_traits::{LoadData, MouseButton, TouchEventType, TouchId};
+use script_traits::{MouseButton, TouchEventType, TouchId};
 use servo_geometry::{DeviceIndependentPixel, DeviceUintLength};
 use servo_url::ServoUrl;
 use std::fmt::{Debug, Error, Formatter};
 use std::rc::Rc;
 use style_traits::DevicePixel;
-use style_traits::cursor::CursorKind;
 use webrender_api::{DeviceIntPoint, DevicePoint, DeviceUintSize, DeviceUintRect, ScrollLocation};
 
 #[derive(Clone)]
 pub enum MouseWindowEvent {
     Click(MouseButton, DevicePoint),
     MouseDown(MouseButton, DevicePoint),
     MouseUp(MouseButton, DevicePoint),
 }
@@ -116,80 +114,44 @@ impl Debug for WindowEvent {
 
 #[derive(Clone, Copy, Debug, PartialEq)]
 pub enum AnimationState {
     Idle,
     Animating,
 }
 
 pub trait WindowMethods {
-    /// Returns the rendering area size in hardware pixels.
-    fn framebuffer_size(&self) -> DeviceUintSize;
-    /// Returns the position and size of the window within the rendering area.
-    fn window_rect(&self) -> DeviceUintRect;
     /// Presents the window to the screen (perhaps by page flipping).
     fn present(&self);
-
-    /// Return the size of the window with head and borders and position of the window values
-    fn client_window(&self, ctx: TopLevelBrowsingContextId) -> (DeviceUintSize, DeviceIntPoint);
-    /// Return the size of the screen.
-    fn screen_size(&self, ctx: TopLevelBrowsingContextId) -> DeviceUintSize;
-    /// Return the available size of the screen.
-    fn screen_avail_size(&self, ctx: TopLevelBrowsingContextId) -> DeviceUintSize;
-    /// Set the size inside of borders and head
-    fn set_inner_size(&self, ctx: TopLevelBrowsingContextId, size: DeviceUintSize);
-    /// Set the window position
-    fn set_position(&self, ctx: TopLevelBrowsingContextId, point: DeviceIntPoint);
-    /// Set fullscreen state
-    fn set_fullscreen_state(&self, ctx: TopLevelBrowsingContextId, state: bool);
-
-    /// Sets the page title for the current page.
-    fn set_page_title(&self, ctx: TopLevelBrowsingContextId, title: Option<String>);
-    /// Called when the browser chrome should display a status message.
-    fn status(&self, ctx: TopLevelBrowsingContextId, Option<String>);
-    /// Called when the browser has started loading a frame.
-    fn load_start(&self, ctx: TopLevelBrowsingContextId);
-    /// Called when the browser is done loading a frame.
-    fn load_end(&self, ctx: TopLevelBrowsingContextId);
-    /// Called when the browser encounters an error while loading a URL
-    fn load_error(&self, ctx: TopLevelBrowsingContextId, code: NetError, url: String);
-    /// Wether or not to follow a link
-    fn allow_navigation(&self, ctx: TopLevelBrowsingContextId, url: ServoUrl, IpcSender<bool>);
-    /// Called when the <head> tag has finished parsing
-    fn head_parsed(&self, ctx: TopLevelBrowsingContextId);
-    /// Called when the history state has changed.
-    fn history_changed(&self, ctx: TopLevelBrowsingContextId, Vec<LoadData>, usize);
-
-    /// Returns the scale factor of the system (device pixels / device independent pixels).
-    fn hidpi_factor(&self) -> TypedScale<f32, DeviceIndependentPixel, DevicePixel>;
-
-    /// Returns a thread-safe object to wake up the window's event loop.
-    fn create_event_loop_waker(&self) -> Box<EventLoopWaker>;
-
     /// Requests that the window system prepare a composite. Typically this will involve making
     /// some type of platform-specific graphics context current. Returns true if the composite may
     /// proceed and false if it should not.
     fn prepare_for_composite(&self, width: DeviceUintLength, height: DeviceUintLength) -> bool;
-
-    /// Sets the cursor to be used in the window.
-    fn set_cursor(&self, cursor: CursorKind);
-
-    /// Process a key event.
-    fn handle_key(&self, ctx: Option<TopLevelBrowsingContextId>, ch: Option<char>, key: Key, mods: KeyModifiers);
-
+    /// Return the GL function pointer trait.
+    fn gl(&self) -> Rc<gl::Gl>;
+    /// Returns a thread-safe object to wake up the window's event loop.
+    fn create_event_loop_waker(&self) -> Box<EventLoopWaker>;
+    /// Get the coordinates of the native window, the screen and the framebuffer.
+    fn get_coordinates(&self) -> EmbedderCoordinates;
     /// Does this window support a clipboard
     fn supports_clipboard(&self) -> bool;
-
-    /// Add a favicon
-    fn set_favicon(&self, ctx: TopLevelBrowsingContextId, url: ServoUrl);
-
-    /// Return the GL function pointer trait.
-    fn gl(&self) -> Rc<gl::Gl>;
-
     /// Set whether the application is currently animating.
     /// Typically, when animations are active, the window
     /// will want to avoid blocking on UI events, and just
     /// run the event loop at the vsync interval.
-    fn set_animation_state(&self, _state: AnimationState) {}
+    fn set_animation_state(&self, _state: AnimationState);
+}
 
-    /// Called when a pipeline panics.
-    fn handle_panic(&self, browser_id: TopLevelBrowsingContextId, reason: String, backtrace: Option<String>);
+#[derive(Clone, Copy, Debug)]
+pub struct EmbedderCoordinates {
+    /// The pixel density of the display.
+    pub hidpi_factor: TypedScale<f32, DeviceIndependentPixel, DevicePixel>,
+    /// Size of the screen.
+    pub screen: DeviceUintSize,
+    /// Size of the available screen space (screen without toolbars and docks).
+    pub screen_avail: DeviceUintSize,
+    /// Size of the native window.
+    pub window: (DeviceUintSize, DeviceIntPoint),
+    /// Size of the GL buffer in the window.
+    pub framebuffer: DeviceUintSize,
+    /// Coordinates of the document within the framebuffer.
+    pub viewport: DeviceUintRect,
 }
--- a/servo/components/constellation/constellation.rs
+++ b/servo/components/constellation/constellation.rs
@@ -1224,34 +1224,33 @@ 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::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));
             }
 
             FromScriptMsg::ResizeTo(size) => {
                 self.embedder_proxy.send(EmbedderMsg::ResizeTo(source_top_ctx_id, size));
             }
 
-            FromScriptMsg::GetScreenSize(send) => {
-                self.embedder_proxy.send(EmbedderMsg::GetScreenSize(source_top_ctx_id, send));
+            FromScriptMsg::GetClientWindow(send) => {
+                self.compositor_proxy.send(ToCompositorMsg::GetClientWindow(send));
             }
-
+            FromScriptMsg::GetScreenSize(send) => {
+                self.compositor_proxy.send(ToCompositorMsg::GetScreenSize(send));
+            }
             FromScriptMsg::GetScreenAvailSize(send) => {
-                self.embedder_proxy.send(EmbedderMsg::GetScreenAvailSize(source_top_ctx_id, send));
+                self.compositor_proxy.send(ToCompositorMsg::GetScreenAvailSize(send));
             }
 
             FromScriptMsg::Exit => {
                 self.compositor_proxy.send(ToCompositorMsg::Exit);
             }
             FromScriptMsg::LogEntry(thread_name, entry) => {
                 self.handle_log_entry(Some(source_top_ctx_id), thread_name, entry);
             }
--- a/servo/components/script_traits/script_msg.rs
+++ b/servo/components/script_traits/script_msg.rs
@@ -131,18 +131,16 @@ pub enum ScriptMsg {
     SetFinalUrl(ServoUrl),
     /// Check if an alert dialog box should be presented
     Alert(String, IpcSender<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<(DeviceUintSize, DeviceIntPoint)>),
     /// Move the window to a point
     MoveTo(DeviceIntPoint),
     /// Resize the window to size
     ResizeTo(DeviceUintSize),
     /// Script has handled a touch event, and either prevented or allowed default actions.
     TouchEventProcessed(EventResult),
     /// A log entry, with the top-level browsing context id and thread name
     LogEntry(Option<String>, LogEntry),
@@ -150,16 +148,18 @@ pub enum ScriptMsg {
     PipelineExited,
     /// Send messages from postMessage calls from serviceworker
     /// to constellation for storing in service worker manager
     ForwardDOMMessage(DOMMessage, ServoUrl),
     /// Store the data required to activate a service worker for the given scope
     RegisterServiceWorker(ScopeThings, ServoUrl),
     /// Enter or exit fullscreen
     SetFullscreenState(bool),
+    /// Get Window Informations size and position
+    GetClientWindow(IpcSender<(DeviceUintSize, DeviceIntPoint)>),
     /// Get the screen size (pixel)
     GetScreenSize(IpcSender<(DeviceUintSize)>),
     /// Get the available screen size (pixel)
     GetScreenAvailSize(IpcSender<(DeviceUintSize)>),
     /// Requests that the compositor shut down.
     Exit,
 }
 
--- a/servo/components/servo/lib.rs
+++ b/servo/components/servo/lib.rs
@@ -69,30 +69,28 @@ fn webdriver(_port: u16, _constellation:
 
 use bluetooth::BluetoothThreadFactory;
 use bluetooth_traits::BluetoothRequest;
 use canvas::gl_context::GLContextFactory;
 use canvas::webgl_thread::WebGLThreads;
 use compositing::{IOCompositor, ShutdownState, RenderNotifier};
 use compositing::compositor_thread::{self, CompositorProxy, CompositorReceiver, InitialCompositorState};
 use compositing::compositor_thread::{EmbedderMsg, EmbedderProxy, EmbedderReceiver};
-use compositing::windowing::WindowEvent;
-use compositing::windowing::WindowMethods;
+use compositing::windowing::{WindowEvent, WindowMethods};
 use constellation::{Constellation, InitialConstellationState, UnprivilegedPipelineContent};
 use constellation::{FromCompositorLogger, FromScriptLogger};
 #[cfg(all(not(target_os = "windows"), not(target_os = "ios")))]
 use constellation::content_process_sandbox_profile;
 use env_logger::Logger as EnvLogger;
 use euclid::Length;
 #[cfg(all(not(target_os = "windows"), not(target_os = "ios")))]
 use gaol::sandbox::{ChildSandbox, ChildSandboxMethods};
 use gfx::font_cache_thread::FontCacheThread;
 use ipc_channel::ipc::{self, IpcSender};
 use log::{Log, LogMetadata, LogRecord};
-use msg::constellation_msg::KeyState;
 use net::resource_thread::new_resource_threads;
 use net_traits::IpcSend;
 use profile::mem as profile_mem;
 use profile::time as profile_time;
 use profile_traits::mem;
 use profile_traits::time;
 use script_traits::{ConstellationMsg, SWManagerSenders, ScriptToConstellationChan};
 use servo_config::opts;
@@ -104,33 +102,34 @@ use std::path::PathBuf;
 use std::rc::Rc;
 use std::sync::mpsc::{Sender, channel};
 use webrender::RendererKind;
 use webvr::{WebVRThread, WebVRCompositorHandler};
 
 pub use gleam::gl;
 pub use servo_config as config;
 pub use servo_url as url;
-pub use msg::constellation_msg::TopLevelBrowsingContextId as BrowserId;
+pub use msg::constellation_msg::{KeyState, TopLevelBrowsingContextId as BrowserId};
 
 /// The in-process interface to Servo.
 ///
 /// It does everything necessary to render the web, primarily
 /// orchestrating the interaction between JavaScript, CSS layout,
 /// rendering, and the client window.
 ///
 /// Clients create a `Servo` instance for a given reference-counted type
 /// implementing `WindowMethods`, which is the bridge to whatever
 /// application Servo is embedded in. Clients then create an event
 /// loop to pump messages between the embedding application and
 /// various browser components.
 pub struct Servo<Window: WindowMethods + 'static> {
     compositor: IOCompositor<Window>,
     constellation_chan: Sender<ConstellationMsg>,
-    embedder_receiver: EmbedderReceiver
+    embedder_receiver: EmbedderReceiver,
+    embedder_events: Vec<EmbedderMsg>,
 }
 
 impl<Window> Servo<Window> where Window: WindowMethods + 'static {
     pub fn new(window: Rc<Window>) -> Servo<Window> {
         // Global configuration options, parsed from the command line.
         let opts = opts::get();
 
         // Make sure the gl context is made current.
@@ -153,27 +152,19 @@ impl<Window> Servo<Window> where Window:
         });
         let devtools_chan = opts.devtools_port.map(|port| {
             devtools::start_server(port)
         });
 
         let mut resource_path = resources_dir_path().unwrap();
         resource_path.push("shaders");
 
+        let coordinates = window.get_coordinates();
+
         let (mut webrender, webrender_api_sender) = {
-            // TODO(gw): Duplicates device_pixels_per_screen_px from compositor. Tidy up!
-            let scale_factor = window.hidpi_factor().get();
-            let device_pixel_ratio = match opts.device_pixels_per_px {
-                Some(device_pixels_per_px) => device_pixels_per_px,
-                None => match opts.output_file {
-                    Some(_) => 1.0,
-                    None => scale_factor,
-                }
-            };
-
             let renderer_kind = if opts::get().should_use_osmesa() {
                 RendererKind::OSMesa
             } else {
                 RendererKind::Native
             };
 
             let recorder = if opts.webrender_record {
                 let record_path = PathBuf::from("wr-record.bin");
@@ -184,33 +175,33 @@ impl<Window> Servo<Window> where Window:
             };
 
             let mut debug_flags = webrender::DebugFlags::empty();
             debug_flags.set(webrender::DebugFlags::PROFILER_DBG, opts.webrender_stats);
 
             let render_notifier = Box::new(RenderNotifier::new(compositor_proxy.clone()));
 
             webrender::Renderer::new(window.gl(), render_notifier, webrender::RendererOptions {
-                device_pixel_ratio: device_pixel_ratio,
+                device_pixel_ratio: coordinates.hidpi_factor.get(),
                 resource_override_path: Some(resource_path),
                 enable_aa: opts.enable_text_antialiasing,
                 debug_flags: debug_flags,
                 debug: opts.webrender_debug,
                 recorder: recorder,
                 precache_shaders: opts.precache_shaders,
                 enable_scrollbars: opts.output_file.is_none(),
                 renderer_kind: renderer_kind,
                 enable_subpixel_aa: opts.enable_subpixel_text_antialiasing,
                 ..Default::default()
             }).expect("Unable to initialize webrender!")
         };
 
         let webrender_api = webrender_api_sender.create_api();
         let wr_document_layer = 0; //TODO
-        let webrender_document = webrender_api.add_document(window.framebuffer_size(), wr_document_layer);
+        let webrender_document = webrender_api.add_document(coordinates.framebuffer, wr_document_layer);
 
         // Important that this call is done in a single-threaded fashion, we
         // can't defer it after `create_constellation` has started.
         script::init();
 
         // Create the constellation, which maintains the engine
         // pipelines, including the script and layout threads, as well
         // as the navigation context.
@@ -249,16 +240,17 @@ impl<Window> Servo<Window> where Window:
             webrender_document,
             webrender_api,
         });
 
         Servo {
             compositor: compositor,
             constellation_chan: constellation_chan,
             embedder_receiver: embedder_receiver,
+            embedder_events: Vec::new(),
         }
     }
 
     fn handle_window_event(&mut self, event: WindowEvent) {
         match event {
             WindowEvent::Idle => {
             }
 
@@ -365,128 +357,47 @@ impl<Window> Servo<Window> where Window:
         while let Some(msg) = self.embedder_receiver.try_recv_embedder_msg() {
             match (msg, self.compositor.shutdown_state) {
                 (_, ShutdownState::FinishedShuttingDown) => {
                     error!("embedder shouldn't be handling messages after compositor has shut down");
                 },
 
                 (_, ShutdownState::ShuttingDown) => {},
 
-                (EmbedderMsg::Status(top_level_browsing_context, message), ShutdownState::NotShuttingDown) => {
-                    self.compositor.window.status(top_level_browsing_context, message);
-                },
-
-                (EmbedderMsg::ChangePageTitle(top_level_browsing_context, title), ShutdownState::NotShuttingDown) => {
-                    self.compositor.window.set_page_title(top_level_browsing_context, title);
-                },
-
-                (EmbedderMsg::MoveTo(top_level_browsing_context, point),
-                 ShutdownState::NotShuttingDown) => {
-                    self.compositor.window.set_position(top_level_browsing_context, point);
-                },
-
-                (EmbedderMsg::ResizeTo(top_level_browsing_context, size),
-                 ShutdownState::NotShuttingDown) => {
-                    self.compositor.window.set_inner_size(top_level_browsing_context, size);
-                },
-
-                (EmbedderMsg::GetClientWindow(top_level_browsing_context, send),
+                (EmbedderMsg::KeyEvent(top_level_browsing_context, ch, key, state, modified),
                  ShutdownState::NotShuttingDown) => {
-                    let rect = self.compositor.window.client_window(top_level_browsing_context);
-                    if let Err(e) = send.send(rect) {
-                        warn!("Sending response to get client window failed ({}).", e);
-                    }
-                },
-
-                (EmbedderMsg::GetScreenSize(top_level_browsing_context, send),
-                 ShutdownState::NotShuttingDown) => {
-                    let rect = self.compositor.window.screen_size(top_level_browsing_context);
-                    if let Err(e) = send.send(rect) {
-                        warn!("Sending response to get screen size failed ({}).", e);
-                    }
-                },
-
-                (EmbedderMsg::GetScreenAvailSize(top_level_browsing_context, send),
-                 ShutdownState::NotShuttingDown) => {
-                    let rect = self.compositor.window.screen_avail_size(top_level_browsing_context);
-                    if let Err(e) = send.send(rect) {
-                        warn!("Sending response to get screen available size failed ({}).", e);
+                    if state == KeyState::Pressed {
+                        let msg = EmbedderMsg::KeyEvent(top_level_browsing_context, ch, key, state, modified);
+                        self.embedder_events.push(msg);
                     }
                 },
 
-                (EmbedderMsg::AllowNavigation(top_level_browsing_context,
-                                              url,
-                                              response_chan),
-                 ShutdownState::NotShuttingDown) => {
-                    self.compositor.window.allow_navigation(top_level_browsing_context, url, response_chan);
-                },
-
-                (EmbedderMsg::KeyEvent(top_level_browsing_context,
-                                       ch,
-                                       key,
-                                       state,
-                                       modified),
-                 ShutdownState::NotShuttingDown) => {
-                    if state == KeyState::Pressed {
-                        self.compositor.window.handle_key(top_level_browsing_context, ch, key, modified);
-                    }
-                },
-
-                (EmbedderMsg::SetCursor(cursor), ShutdownState::NotShuttingDown) => {
-                    self.compositor.window.set_cursor(cursor)
-                },
-
-                (EmbedderMsg::NewFavicon(top_level_browsing_context, url), ShutdownState::NotShuttingDown) => {
-                    self.compositor.window.set_favicon(top_level_browsing_context, url);
+                (msg, ShutdownState::NotShuttingDown) => {
+                    self.embedder_events.push(msg);
                 },
-
-                (EmbedderMsg::HeadParsed(top_level_browsing_context, ), ShutdownState::NotShuttingDown) => {
-                    self.compositor.window.head_parsed(top_level_browsing_context, );
-                },
-
-                (EmbedderMsg::HistoryChanged(top_level_browsing_context, entries, current),
-                 ShutdownState::NotShuttingDown) => {
-                    self.compositor.window.history_changed(top_level_browsing_context, entries, current);
-                },
-
-                (EmbedderMsg::SetFullscreenState(top_level_browsing_context, state),
-                 ShutdownState::NotShuttingDown) => {
-                    self.compositor.window.set_fullscreen_state(top_level_browsing_context, state);
-                },
-
-                (EmbedderMsg::LoadStart(top_level_browsing_context), ShutdownState::NotShuttingDown) => {
-                    self.compositor.window.load_start(top_level_browsing_context);
-                },
-
-                (EmbedderMsg::LoadComplete(top_level_browsing_context), ShutdownState::NotShuttingDown) => {
-                    // Inform the embedder that the load has finished.
-                    //
-                    // TODO(pcwalton): Specify which frame's load completed.
-                    self.compositor.window.load_end(top_level_browsing_context);
-                },
-                (EmbedderMsg::Panic(top_level_browsing_context, reason, backtrace),
-                 ShutdownState::NotShuttingDown) => {
-                    self.compositor.window.handle_panic(top_level_browsing_context, reason, backtrace);
-                },
-
             }
         }
     }
 
-    pub fn handle_events(&mut self, events: Vec<WindowEvent>) -> bool {
+    pub fn get_events(&mut self) -> Vec<EmbedderMsg> {
+        ::std::mem::replace(&mut self.embedder_events, Vec::new())
+    }
+
+    pub fn handle_events(&mut self, events: Vec<WindowEvent>) {
         if self.compositor.receive_messages() {
             self.receive_messages();
         }
         for event in events {
             self.handle_window_event(event);
         }
         if self.compositor.shutdown_state != ShutdownState::FinishedShuttingDown {
             self.compositor.perform_updates();
+        } else {
+            self.embedder_events.push(EmbedderMsg::Shutdown);
         }
-        self.compositor.shutdown_state != ShutdownState::FinishedShuttingDown
     }
 
     pub fn repaint_synchronously(&mut self) {
         self.compositor.repaint_synchronously()
     }
 
     pub fn pinch_zoom_level(&self) -> f32 {
         self.compositor.pinch_zoom_level()
--- a/servo/ports/servo/Cargo.toml
+++ b/servo/ports/servo/Cargo.toml
@@ -38,21 +38,19 @@ backtrace = "0.3"
 bitflags = "1.0"
 compositing = {path = "../../components/compositing"}
 euclid = "0.17"
 gleam = "0.4"
 glutin = "0.13"
 libservo = {path = "../../components/servo"}
 log = "0.3.5"
 msg = {path = "../../components/msg"}
-net_traits = {path = "../../components/net_traits"}
 script_traits = {path = "../../components/script_traits"}
 servo_geometry = {path = "../../components/geometry"}
 servo_config = {path = "../../components/config"}
-servo_url = {path = "../../components/url"}
 style_traits = {path = "../../components/style_traits"}
 tinyfiledialogs = "3.0"
 webrender_api = {git = "https://github.com/servo/webrender", features = ["ipc"]}
 winit = "0.11.2"
 
 [target.'cfg(not(target_os = "android"))'.dependencies]
 sig = "0.1"
 
new file mode 100644
--- /dev/null
+++ b/servo/ports/servo/browser.rs
@@ -0,0 +1,313 @@
+/* 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 compositing::compositor_thread::EmbedderMsg;
+use compositing::windowing::{WebRenderDebugOption, WindowEvent};
+use euclid::{TypedPoint2D, TypedVector2D};
+use glutin_app::keyutils::{CMD_OR_CONTROL, CMD_OR_ALT};
+use glutin_app::window::{Window, LINE_HEIGHT};
+use msg::constellation_msg::{Key, TopLevelBrowsingContextId as BrowserId};
+use msg::constellation_msg::{KeyModifiers, KeyState, TraversalDirection};
+use script_traits::TouchEventType;
+use servo::net_traits::pub_domains::is_reg_domain;
+use servo::servo_url::ServoUrl;
+use servo_config::prefs::PREFS;
+use std::mem;
+use std::rc::Rc;
+use tinyfiledialogs;
+use webrender_api::ScrollLocation;
+
+pub struct Browser {
+    current_url: Option<ServoUrl>,
+    /// id of the top level browsing context. It is unique as tabs
+    /// are not supported yet. None until created.
+    browser_id: Option<BrowserId>,
+
+    title: Option<String>,
+    status: Option<String>,
+    favicon: Option<ServoUrl>,
+    loading_state: Option<LoadingState>,
+    window: Rc<Window>,
+    event_queue: Vec<WindowEvent>,
+    shutdown_requested: bool,
+}
+
+enum LoadingState {
+    Connecting,
+    Loading,
+    Loaded,
+}
+
+impl Browser {
+    pub fn new(window: Rc<Window>) -> Browser {
+        Browser {
+            title: None,
+            current_url: None,
+            browser_id: None,
+            status: None,
+            favicon: None,
+            loading_state: None,
+            window: window,
+            event_queue: Vec::new(),
+            shutdown_requested: false,
+        }
+    }
+
+    pub fn get_events(&mut self) -> Vec<WindowEvent> {
+        mem::replace(&mut self.event_queue, Vec::new())
+    }
+
+    pub fn set_browser_id(&mut self, browser_id: BrowserId) {
+        self.browser_id = Some(browser_id);
+    }
+
+    pub fn handle_window_events(&mut self, events: Vec<WindowEvent>) {
+        for event in events {
+            match event {
+                WindowEvent::KeyEvent(ch, key, state, mods) => {
+                    self.handle_key_from_window(ch, key, state, mods);
+                },
+                event => {
+                    self.event_queue.push(event);
+                }
+            }
+        }
+    }
+
+    pub fn shutdown_requested(&self) -> bool {
+        self.shutdown_requested
+    }
+
+    /// Handle key events before sending them to Servo.
+    fn handle_key_from_window(&mut self, ch: Option<char>, key: Key, state: KeyState, mods: KeyModifiers) {
+        match (mods, ch, key) {
+            (CMD_OR_CONTROL, Some('r'), _) => {
+                if let Some(id) = self.browser_id {
+                    self.event_queue.push(WindowEvent::Reload(id));
+                }
+            }
+            (CMD_OR_CONTROL, Some('l'), _) => {
+                if let Some(id) = self.browser_id {
+                    let url: String = if let Some(ref current_url) = self.current_url {
+                        current_url.to_string()
+                    } else {
+                        String::from("")
+                    };
+                    let title = "URL or search query";
+                    if let Some(input) = tinyfiledialogs::input_box(title, title, &url) {
+                        if let Some(url) = sanitize_url(&input) {
+                            self.event_queue.push(WindowEvent::LoadUrl(id, url));
+                        }
+                    }
+                }
+            }
+            (CMD_OR_CONTROL, Some('q'), _) => {
+                self.event_queue.push(WindowEvent::Quit);
+            }
+            (_, Some('3'), _) => if mods ^ KeyModifiers::CONTROL == KeyModifiers::SHIFT {
+                self.event_queue.push(WindowEvent::CaptureWebRender);
+            }
+            (KeyModifiers::CONTROL, None, Key::F10) => {
+                let event = WindowEvent::ToggleWebRenderDebug(WebRenderDebugOption::RenderTargetDebug);
+                self.event_queue.push(event);
+            }
+            (KeyModifiers::CONTROL, None, Key::F11) => {
+                let event = WindowEvent::ToggleWebRenderDebug(WebRenderDebugOption::TextureCacheDebug);
+                self.event_queue.push(event);
+            }
+            (KeyModifiers::CONTROL, None, Key::F12) => {
+                let event = WindowEvent::ToggleWebRenderDebug(WebRenderDebugOption::Profiler);
+                self.event_queue.push(event);
+            }
+            (CMD_OR_ALT, None, Key::Right) | (KeyModifiers::NONE, None, Key::NavigateForward) => {
+                if let Some(id) = self.browser_id {
+                    let event = WindowEvent::Navigation(id, TraversalDirection::Forward(1));
+                    self.event_queue.push(event);
+                }
+            }
+            (CMD_OR_ALT, None, Key::Left) | (KeyModifiers::NONE, None, Key::NavigateBackward) => {
+                if let Some(id) = self.browser_id {
+                    let event = WindowEvent::Navigation(id, TraversalDirection::Back(1));
+                    self.event_queue.push(event);
+                }
+            }
+            (KeyModifiers::NONE, None, Key::Escape) => {
+                self.event_queue.push(WindowEvent::Quit);
+            }
+            _ => {
+                let event = self.platform_handle_key(key, mods);
+                self.event_queue.push(event.unwrap_or(WindowEvent::KeyEvent(ch, key, state, mods)));
+            }
+        }
+
+    }
+
+    #[cfg(not(target_os = "win"))]
+    fn platform_handle_key(&self, key: Key, mods: KeyModifiers) -> Option<WindowEvent> {
+        match (mods, key, self.browser_id) {
+            (CMD_OR_CONTROL, Key::LeftBracket, Some(id)) => {
+                Some(WindowEvent::Navigation(id, TraversalDirection::Back(1)))
+            }
+            (CMD_OR_CONTROL, Key::RightBracket, Some(id)) => {
+                Some(WindowEvent::Navigation(id, TraversalDirection::Forward(1)))
+            }
+            _ => None
+        }
+    }
+
+    #[cfg(target_os = "win")]
+    fn platform_handle_key(&self, key: Key, mods: KeyModifiers) -> Option<WindowEvent> {
+        None
+    }
+
+    /// Handle key events after they have been handled by Servo.
+    fn handle_key_from_servo(&mut self, _: Option<BrowserId>, ch: Option<char>,
+                             key: Key, _: KeyState, mods: KeyModifiers) {
+        match (mods, ch, key) {
+            (_, Some('+'), _) => {
+                if mods & !KeyModifiers::SHIFT == CMD_OR_CONTROL {
+                    self.event_queue.push(WindowEvent::Zoom(1.1));
+                } else if mods & !KeyModifiers::SHIFT == CMD_OR_CONTROL | KeyModifiers::ALT {
+                    self.event_queue.push(WindowEvent::PinchZoom(1.1));
+                }
+            }
+            (CMD_OR_CONTROL, Some('-'), _) => {
+                self.event_queue.push(WindowEvent::Zoom(1.0 / 1.1));
+            }
+            (_, Some('-'), _) if mods == CMD_OR_CONTROL | KeyModifiers::ALT => {
+                self.event_queue.push(WindowEvent::PinchZoom(1.0 / 1.1));
+            }
+            (CMD_OR_CONTROL, Some('0'), _) => {
+                self.event_queue.push(WindowEvent::ResetZoom);
+            }
+
+            (KeyModifiers::NONE, None, Key::PageDown) => {
+               let scroll_location = ScrollLocation::Delta(TypedVector2D::new(0.0,
+                                   -self.window.page_height() + 2.0 * LINE_HEIGHT));
+                self.scroll_window_from_key(scroll_location, TouchEventType::Move);
+            }
+            (KeyModifiers::NONE, None, Key::PageUp) => {
+                let scroll_location = ScrollLocation::Delta(TypedVector2D::new(0.0,
+                                   self.window.page_height() - 2.0 * LINE_HEIGHT));
+                self.scroll_window_from_key(scroll_location, TouchEventType::Move);
+            }
+
+            (KeyModifiers::NONE, None, Key::Home) => {
+                self.scroll_window_from_key(ScrollLocation::Start, TouchEventType::Move);
+            }
+
+            (KeyModifiers::NONE, None, Key::End) => {
+                self.scroll_window_from_key(ScrollLocation::End, TouchEventType::Move);
+            }
+
+            (KeyModifiers::NONE, None, Key::Up) => {
+                self.scroll_window_from_key(ScrollLocation::Delta(TypedVector2D::new(0.0, 3.0 * LINE_HEIGHT)),
+                                            TouchEventType::Move);
+            }
+            (KeyModifiers::NONE, None, Key::Down) => {
+                self.scroll_window_from_key(ScrollLocation::Delta(TypedVector2D::new(0.0, -3.0 * LINE_HEIGHT)),
+                                            TouchEventType::Move);
+            }
+            (KeyModifiers::NONE, None, Key::Left) => {
+                self.scroll_window_from_key(ScrollLocation::Delta(TypedVector2D::new(LINE_HEIGHT, 0.0)),
+                                            TouchEventType::Move);
+            }
+            (KeyModifiers::NONE, None, Key::Right) => {
+                self.scroll_window_from_key(ScrollLocation::Delta(TypedVector2D::new(-LINE_HEIGHT, 0.0)),
+                                            TouchEventType::Move);
+            }
+
+            _ => {
+            }
+        }
+    }
+
+    fn scroll_window_from_key(&mut self, scroll_location: ScrollLocation, phase: TouchEventType) {
+        let event = WindowEvent::Scroll(scroll_location, TypedPoint2D::zero(), phase);
+        self.event_queue.push(event);
+    }
+
+    pub fn handle_servo_events(&mut self, events: Vec<EmbedderMsg>) {
+        for event in events {
+            match event {
+                EmbedderMsg::Status(_browser_id, status) => {
+                    self.status = status;
+                },
+                EmbedderMsg::ChangePageTitle(_browser_id, title) => {
+                    self.title = title;
+
+                    let fallback_title: String = if let Some(ref current_url) = self.current_url {
+                        current_url.to_string()
+                    } else {
+                        String::from("Untitled")
+                    };
+                    let title = match self.title {
+                        Some(ref title) if title.len() > 0 => &**title,
+                        _ => &fallback_title,
+                    };
+                    let title = format!("{} - Servo", title);
+                    self.window.set_title(&title);
+                }
+                EmbedderMsg::MoveTo(_browser_id, point) => {
+                    self.window.set_position(point);
+                }
+                EmbedderMsg::ResizeTo(_browser_id, size) => {
+                    self.window.set_inner_size(size);
+                }
+                EmbedderMsg::AllowNavigation(_browser_id, _url, response_chan) => {
+                    if let Err(e) = response_chan.send(true) {
+                        warn!("Failed to send allow_navigation() response: {}", e);
+                    };
+                }
+                EmbedderMsg::KeyEvent(browser_id, ch, key, state, modified) => {
+                    self.handle_key_from_servo(browser_id, ch, key, state, modified);
+                }
+                EmbedderMsg::SetCursor(cursor) => {
+                    self.window.set_cursor(cursor);
+                }
+                EmbedderMsg::NewFavicon(_browser_id, url) => {
+                    self.favicon = Some(url);
+                }
+                EmbedderMsg::HeadParsed(_browser_id, ) => {
+                    self.loading_state = Some(LoadingState::Loading);
+                }
+                EmbedderMsg::HistoryChanged(_browser_id, entries, current) => {
+                    self.current_url = Some(entries[current].url.clone());
+                }
+                EmbedderMsg::SetFullscreenState(_browser_id, state) => {
+                    self.window.set_fullscreen(state);
+                }
+                EmbedderMsg::LoadStart(_browser_id) => {
+                    self.loading_state = Some(LoadingState::Connecting);
+                }
+                EmbedderMsg::LoadComplete(_browser_id) => {
+                    self.loading_state = Some(LoadingState::Loaded);
+                }
+                EmbedderMsg::Shutdown => {
+                    self.shutdown_requested = true;
+                },
+                EmbedderMsg::Panic(_browser_id, _reason, _backtrace) => {
+                }
+            }
+        }
+    }
+
+}
+
+fn sanitize_url(request: &str) -> Option<ServoUrl> {
+    let request = request.trim();
+    ServoUrl::parse(&request).ok()
+        .or_else(|| {
+            if request.contains('/') || is_reg_domain(request) {
+                ServoUrl::parse(&format!("http://{}", request)).ok()
+            } else {
+                None
+            }
+        }).or_else(|| {
+            PREFS.get("shell.searchpage").as_string().and_then(|s: &str| {
+                let url = s.replace("%s", request);
+                ServoUrl::parse(&url).ok()
+            })
+        })
+}
new file mode 100644
--- /dev/null
+++ b/servo/ports/servo/glutin_app/keyutils.rs
@@ -0,0 +1,352 @@
+/* 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 msg::constellation_msg::{self, Key, KeyModifiers};
+use winit::{self, VirtualKeyCode};
+
+bitflags! {
+    pub struct GlutinKeyModifiers: u8 {
+        const LEFT_CONTROL = 1;
+        const RIGHT_CONTROL = 2;
+        const LEFT_SHIFT = 4;
+        const RIGHT_SHIFT = 8;
+        const LEFT_ALT = 16;
+        const RIGHT_ALT = 32;
+        const LEFT_SUPER = 64;
+        const RIGHT_SUPER = 128;
+    }
+}
+
+// Some shortcuts use Cmd on Mac and Control on other systems.
+#[cfg(target_os = "macos")]
+pub const CMD_OR_CONTROL: KeyModifiers = KeyModifiers::SUPER;
+#[cfg(not(target_os = "macos"))]
+pub const CMD_OR_CONTROL: KeyModifiers = KeyModifiers::CONTROL;
+
+// Some shortcuts use Cmd on Mac and Alt on other systems.
+#[cfg(target_os = "macos")]
+pub const CMD_OR_ALT: KeyModifiers = KeyModifiers::SUPER;
+#[cfg(not(target_os = "macos"))]
+pub const CMD_OR_ALT: KeyModifiers = KeyModifiers::ALT;
+
+pub fn char_to_script_key(c: char) -> Option<constellation_msg::Key> {
+    match c {
+        ' ' => Some(Key::Space),
+        '"' => Some(Key::Apostrophe),
+        '\'' => Some(Key::Apostrophe),
+        '<' => Some(Key::Comma),
+        ',' => Some(Key::Comma),
+        '_' => Some(Key::Minus),
+        '-' => Some(Key::Minus),
+        '>' => Some(Key::Period),
+        '.' => Some(Key::Period),
+        '?' => Some(Key::Slash),
+        '/' => Some(Key::Slash),
+        '~' => Some(Key::GraveAccent),
+        '`' => Some(Key::GraveAccent),
+        ')' => Some(Key::Num0),
+        '0' => Some(Key::Num0),
+        '!' => Some(Key::Num1),
+        '1' => Some(Key::Num1),
+        '@' => Some(Key::Num2),
+        '2' => Some(Key::Num2),
+        '#' => Some(Key::Num3),
+        '3' => Some(Key::Num3),
+        '$' => Some(Key::Num4),
+        '4' => Some(Key::Num4),
+        '%' => Some(Key::Num5),
+        '5' => Some(Key::Num5),
+        '^' => Some(Key::Num6),
+        '6' => Some(Key::Num6),
+        '&' => Some(Key::Num7),
+        '7' => Some(Key::Num7),
+        '*' => Some(Key::Num8),
+        '8' => Some(Key::Num8),
+        '(' => Some(Key::Num9),
+        '9' => Some(Key::Num9),
+        ':' => Some(Key::Semicolon),
+        ';' => Some(Key::Semicolon),
+        '+' => Some(Key::Equal),
+        '=' => Some(Key::Equal),
+        'A' => Some(Key::A),
+        'a' => Some(Key::A),
+        'B' => Some(Key::B),
+        'b' => Some(Key::B),
+        'C' => Some(Key::C),
+        'c' => Some(Key::C),
+        'D' => Some(Key::D),
+        'd' => Some(Key::D),
+        'E' => Some(Key::E),
+        'e' => Some(Key::E),
+        'F' => Some(Key::F),
+        'f' => Some(Key::F),
+        'G' => Some(Key::G),
+        'g' => Some(Key::G),
+        'H' => Some(Key::H),
+        'h' => Some(Key::H),
+        'I' => Some(Key::I),
+        'i' => Some(Key::I),
+        'J' => Some(Key::J),
+        'j' => Some(Key::J),
+        'K' => Some(Key::K),
+        'k' => Some(Key::K),
+        'L' => Some(Key::L),
+        'l' => Some(Key::L),
+        'M' => Some(Key::M),
+        'm' => Some(Key::M),
+        'N' => Some(Key::N),
+        'n' => Some(Key::N),
+        'O' => Some(Key::O),
+        'o' => Some(Key::O),
+        'P' => Some(Key::P),
+        'p' => Some(Key::P),
+        'Q' => Some(Key::Q),
+        'q' => Some(Key::Q),
+        'R' => Some(Key::R),
+        'r' => Some(Key::R),
+        'S' => Some(Key::S),
+        's' => Some(Key::S),
+        'T' => Some(Key::T),
+        't' => Some(Key::T),
+        'U' => Some(Key::U),
+        'u' => Some(Key::U),
+        'V' => Some(Key::V),
+        'v' => Some(Key::V),
+        'W' => Some(Key::W),
+        'w' => Some(Key::W),
+        'X' => Some(Key::X),
+        'x' => Some(Key::X),
+        'Y' => Some(Key::Y),
+        'y' => Some(Key::Y),
+        'Z' => Some(Key::Z),
+        'z' => Some(Key::Z),
+        '{' => Some(Key::LeftBracket),
+        '[' => Some(Key::LeftBracket),
+        '|' => Some(Key::Backslash),
+        '\\' => Some(Key::Backslash),
+        '}' => Some(Key::RightBracket),
+        ']' => Some(Key::RightBracket),
+        _ => None
+    }
+}
+
+pub fn glutin_key_to_script_key(key: winit::VirtualKeyCode) -> Result<constellation_msg::Key, ()> {
+    // TODO(negge): add more key mappings
+    match key {
+        VirtualKeyCode::A => Ok(Key::A),
+        VirtualKeyCode::B => Ok(Key::B),
+        VirtualKeyCode::C => Ok(Key::C),
+        VirtualKeyCode::D => Ok(Key::D),
+        VirtualKeyCode::E => Ok(Key::E),
+        VirtualKeyCode::F => Ok(Key::F),
+        VirtualKeyCode::G => Ok(Key::G),
+        VirtualKeyCode::H => Ok(Key::H),
+        VirtualKeyCode::I => Ok(Key::I),
+        VirtualKeyCode::J => Ok(Key::J),
+        VirtualKeyCode::K => Ok(Key::K),
+        VirtualKeyCode::L => Ok(Key::L),
+        VirtualKeyCode::M => Ok(Key::M),
+        VirtualKeyCode::N => Ok(Key::N),
+        VirtualKeyCode::O => Ok(Key::O),
+        VirtualKeyCode::P => Ok(Key::P),
+        VirtualKeyCode::Q => Ok(Key::Q),
+        VirtualKeyCode::R => Ok(Key::R),
+        VirtualKeyCode::S => Ok(Key::S),
+        VirtualKeyCode::T => Ok(Key::T),
+        VirtualKeyCode::U => Ok(Key::U),
+        VirtualKeyCode::V => Ok(Key::V),
+        VirtualKeyCode::W => Ok(Key::W),
+        VirtualKeyCode::X => Ok(Key::X),
+        VirtualKeyCode::Y => Ok(Key::Y),
+        VirtualKeyCode::Z => Ok(Key::Z),
+
+        VirtualKeyCode::Numpad0 => Ok(Key::Kp0),
+        VirtualKeyCode::Numpad1 => Ok(Key::Kp1),
+        VirtualKeyCode::Numpad2 => Ok(Key::Kp2),
+        VirtualKeyCode::Numpad3 => Ok(Key::Kp3),
+        VirtualKeyCode::Numpad4 => Ok(Key::Kp4),
+        VirtualKeyCode::Numpad5 => Ok(Key::Kp5),
+        VirtualKeyCode::Numpad6 => Ok(Key::Kp6),
+        VirtualKeyCode::Numpad7 => Ok(Key::Kp7),
+        VirtualKeyCode::Numpad8 => Ok(Key::Kp8),
+        VirtualKeyCode::Numpad9 => Ok(Key::Kp9),
+
+        VirtualKeyCode::Key0 => Ok(Key::Num0),
+        VirtualKeyCode::Key1 => Ok(Key::Num1),
+        VirtualKeyCode::Key2 => Ok(Key::Num2),
+        VirtualKeyCode::Key3 => Ok(Key::Num3),
+        VirtualKeyCode::Key4 => Ok(Key::Num4),
+        VirtualKeyCode::Key5 => Ok(Key::Num5),
+        VirtualKeyCode::Key6 => Ok(Key::Num6),
+        VirtualKeyCode::Key7 => Ok(Key::Num7),
+        VirtualKeyCode::Key8 => Ok(Key::Num8),
+        VirtualKeyCode::Key9 => Ok(Key::Num9),
+
+        VirtualKeyCode::Return => Ok(Key::Enter),
+        VirtualKeyCode::Space => Ok(Key::Space),
+        VirtualKeyCode::Escape => Ok(Key::Escape),
+        VirtualKeyCode::Equals => Ok(Key::Equal),
+        VirtualKeyCode::Minus => Ok(Key::Minus),
+        VirtualKeyCode::Back => Ok(Key::Backspace),
+        VirtualKeyCode::PageDown => Ok(Key::PageDown),
+        VirtualKeyCode::PageUp => Ok(Key::PageUp),
+
+        VirtualKeyCode::Insert => Ok(Key::Insert),
+        VirtualKeyCode::Home => Ok(Key::Home),
+        VirtualKeyCode::Delete => Ok(Key::Delete),
+        VirtualKeyCode::End => Ok(Key::End),
+
+        VirtualKeyCode::Left => Ok(Key::Left),
+        VirtualKeyCode::Up => Ok(Key::Up),
+        VirtualKeyCode::Right => Ok(Key::Right),
+        VirtualKeyCode::Down => Ok(Key::Down),
+
+        VirtualKeyCode::LShift => Ok(Key::LeftShift),
+        VirtualKeyCode::LControl => Ok(Key::LeftControl),
+        VirtualKeyCode::LAlt => Ok(Key::LeftAlt),
+        VirtualKeyCode::LWin => Ok(Key::LeftSuper),
+        VirtualKeyCode::RShift => Ok(Key::RightShift),
+        VirtualKeyCode::RControl => Ok(Key::RightControl),
+        VirtualKeyCode::RAlt => Ok(Key::RightAlt),
+        VirtualKeyCode::RWin => Ok(Key::RightSuper),
+
+        VirtualKeyCode::Apostrophe => Ok(Key::Apostrophe),
+        VirtualKeyCode::Backslash => Ok(Key::Backslash),
+        VirtualKeyCode::Comma => Ok(Key::Comma),
+        VirtualKeyCode::Grave => Ok(Key::GraveAccent),
+        VirtualKeyCode::LBracket => Ok(Key::LeftBracket),
+        VirtualKeyCode::Period => Ok(Key::Period),
+        VirtualKeyCode::RBracket => Ok(Key::RightBracket),
+        VirtualKeyCode::Semicolon => Ok(Key::Semicolon),
+        VirtualKeyCode::Slash => Ok(Key::Slash),
+        VirtualKeyCode::Tab => Ok(Key::Tab),
+        VirtualKeyCode::Subtract => Ok(Key::Minus),
+
+        VirtualKeyCode::F1 => Ok(Key::F1),
+        VirtualKeyCode::F2 => Ok(Key::F2),
+        VirtualKeyCode::F3 => Ok(Key::F3),
+        VirtualKeyCode::F4 => Ok(Key::F4),
+        VirtualKeyCode::F5 => Ok(Key::F5),
+        VirtualKeyCode::F6 => Ok(Key::F6),
+        VirtualKeyCode::F7 => Ok(Key::F7),
+        VirtualKeyCode::F8 => Ok(Key::F8),
+        VirtualKeyCode::F9 => Ok(Key::F9),
+        VirtualKeyCode::F10 => Ok(Key::F10),
+        VirtualKeyCode::F11 => Ok(Key::F11),
+        VirtualKeyCode::F12 => Ok(Key::F12),
+
+        VirtualKeyCode::NavigateBackward => Ok(Key::NavigateBackward),
+        VirtualKeyCode::NavigateForward => Ok(Key::NavigateForward),
+        _ => Err(()),
+    }
+}
+
+pub fn glutin_mods_to_script_mods(modifiers: GlutinKeyModifiers) -> constellation_msg::KeyModifiers {
+    let mut result = constellation_msg::KeyModifiers::empty();
+    if modifiers.intersects(GlutinKeyModifiers::LEFT_SHIFT | GlutinKeyModifiers::RIGHT_SHIFT) {
+        result.insert(KeyModifiers::SHIFT);
+    }
+    if modifiers.intersects(GlutinKeyModifiers::LEFT_CONTROL | GlutinKeyModifiers::RIGHT_CONTROL) {
+        result.insert(KeyModifiers::CONTROL);
+    }
+    if modifiers.intersects(GlutinKeyModifiers::LEFT_ALT | GlutinKeyModifiers::RIGHT_ALT) {
+        result.insert(KeyModifiers::ALT);
+    }
+    if modifiers.intersects(GlutinKeyModifiers::LEFT_SUPER | GlutinKeyModifiers::RIGHT_SUPER) {
+        result.insert(KeyModifiers::SUPER);
+    }
+    result
+}
+
+pub fn is_printable(key_code: VirtualKeyCode) -> bool {
+    use winit::VirtualKeyCode::*;
+    match key_code {
+        Escape |
+        F1 |
+        F2 |
+        F3 |
+        F4 |
+        F5 |
+        F6 |
+        F7 |
+        F8 |
+        F9 |
+        F10 |
+        F11 |
+        F12 |
+        F13 |
+        F14 |
+        F15 |
+        Snapshot |
+        Scroll |
+        Pause |
+        Insert |
+        Home |
+        Delete |
+        End |
+        PageDown |
+        PageUp |
+        Left |
+        Up |
+        Right |
+        Down |
+        Back |
+        LAlt |
+        LControl |
+        LMenu |
+        LShift |
+        LWin |
+        Mail |
+        MediaSelect |
+        MediaStop |
+        Mute |
+        MyComputer |
+        NavigateForward |
+        NavigateBackward |
+        NextTrack |
+        NoConvert |
+        PlayPause |
+        Power |
+        PrevTrack |
+        RAlt |
+        RControl |
+        RMenu |
+        RShift |
+        RWin |
+        Sleep |
+        Stop |
+        VolumeDown |
+        VolumeUp |
+        Wake |
+        WebBack |
+        WebFavorites |
+        WebForward |
+        WebHome |
+        WebRefresh |
+        WebSearch |
+        WebStop => false,
+        _ => true,
+    }
+}
+
+/// Detect if given char is default ignorable in unicode
+/// http://www.unicode.org/L2/L2002/02368-default-ignorable.pdf
+pub fn is_identifier_ignorable(ch: &char) -> bool {
+    match *ch {
+        '\u{0000}'...'\u{0008}' | '\u{000E}'...'\u{001F}' |
+            '\u{007F}'...'\u{0084}' | '\u{0086}'...'\u{009F}' |
+            '\u{06DD}' | '\u{070F}' |
+            '\u{180B}'...'\u{180D}' | '\u{180E}' |
+            '\u{200C}'...'\u{200F}' |
+            '\u{202A}'...'\u{202E}' | '\u{2060}'...'\u{2063}' |
+            '\u{2064}'...'\u{2069}' | '\u{206A}'...'\u{206F}' |
+            '\u{FE00}'...'\u{FE0F}' | '\u{FEFF}' |
+            '\u{FFF0}'...'\u{FFF8}' | '\u{FFF9}'...'\u{FFFB}' |
+            '\u{1D173}'...'\u{1D17A}' | '\u{E0000}' |
+            '\u{E0001}' |
+            '\u{E0002}'...'\u{E001F}' | '\u{E0020}'...'\u{E007F}' |
+            '\u{E0080}'...'\u{E0FFF}' => true,
+        _ => false
+    }
+}
--- a/servo/ports/servo/glutin_app/mod.rs
+++ b/servo/ports/servo/glutin_app/mod.rs
@@ -1,14 +1,15 @@
 /* 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/. */
 
 //! A simple application that uses glutin to open a window for Servo to display in.
 
+pub mod keyutils;
 pub mod window;
 
 use servo_config::opts;
 use std::rc::Rc;
 
 pub fn create_window() -> Rc<window::Window> {
     // Read command-line options.
     let opts = opts::get();
--- a/servo/ports/servo/glutin_app/window.rs
+++ b/servo/ports/servo/glutin_app/window.rs
@@ -1,86 +1,54 @@
 /* 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/. */
 
 //! A windowing implementation using glutin.
 
 use compositing::compositor_thread::EventLoopWaker;
 use compositing::windowing::{AnimationState, MouseWindowEvent, WindowEvent};
-use compositing::windowing::{WebRenderDebugOption, WindowMethods};
+use compositing::windowing::{EmbedderCoordinates, WindowMethods};
 use euclid::{Length, TypedPoint2D, TypedVector2D, TypedScale, TypedSize2D};
 #[cfg(target_os = "windows")]
 use gdi32;
 use gleam::gl;
-use glutin;
-use glutin::{Api, GlContext, GlRequest};
-use msg::constellation_msg::{self, Key, TopLevelBrowsingContextId as BrowserId};
-use msg::constellation_msg::{KeyModifiers, KeyState, TraversalDirection};
-use net_traits::net_error_list::NetError;
-use net_traits::pub_domains::is_reg_domain;
+use glutin::{self, Api, GlContext, GlRequest};
+use msg::constellation_msg::{Key, KeyState};
 #[cfg(any(target_os = "linux", target_os = "macos"))]
 use osmesa_sys;
-use script_traits::{LoadData, TouchEventType};
-use servo::ipc_channel::ipc::IpcSender;
+use script_traits::TouchEventType;
 use servo_config::opts;
-use servo_config::prefs::PREFS;
 use servo_geometry::DeviceIndependentPixel;
-use servo_url::ServoUrl;
 use std::cell::{Cell, RefCell};
 #[cfg(any(target_os = "linux", target_os = "macos"))]
 use std::ffi::CString;
 use std::mem;
 use std::os::raw::c_void;
 use std::ptr;
 use std::rc::Rc;
 use std::sync::Arc;
 use std::thread;
 use std::time;
 use style_traits::DevicePixel;
 use style_traits::cursor::CursorKind;
-use tinyfiledialogs;
+use super::keyutils::{self, GlutinKeyModifiers};
 #[cfg(target_os = "windows")]
 use user32;
 use webrender_api::{DeviceIntPoint, DeviceUintRect, DeviceUintSize, ScrollLocation};
 #[cfg(target_os = "windows")]
 use winapi;
 use winit;
 use winit::{ElementState, Event, MouseButton, MouseScrollDelta, TouchPhase, VirtualKeyCode};
 #[cfg(target_os = "macos")]
 use winit::os::macos::{ActivationPolicy, WindowBuilderExt};
 
 
-bitflags! {
-    struct GlutinKeyModifiers: u8 {
-        const LEFT_CONTROL = 1;
-        const RIGHT_CONTROL = 2;
-        const LEFT_SHIFT = 4;
-        const RIGHT_SHIFT = 8;
-        const LEFT_ALT = 16;
-        const RIGHT_ALT = 32;
-        const LEFT_SUPER = 64;
-        const RIGHT_SUPER = 128;
-    }
-}
-
-// Some shortcuts use Cmd on Mac and Control on other systems.
-#[cfg(target_os = "macos")]
-const CMD_OR_CONTROL: KeyModifiers = KeyModifiers::SUPER;
-#[cfg(not(target_os = "macos"))]
-const CMD_OR_CONTROL: KeyModifiers = KeyModifiers::CONTROL;
-
-// Some shortcuts use Cmd on Mac and Alt on other systems.
-#[cfg(target_os = "macos")]
-const CMD_OR_ALT: KeyModifiers = KeyModifiers::SUPER;
-#[cfg(not(target_os = "macos"))]
-const CMD_OR_ALT: KeyModifiers = KeyModifiers::ALT;
-
 // This should vary by zoom level and maybe actual text size (focused or under cursor)
-const LINE_HEIGHT: f32 = 38.0;
+pub const LINE_HEIGHT: f32 = 38.0;
 
 const MULTISAMPLES: u16 = 16;
 
 #[cfg(target_os = "macos")]
 fn builder_with_platform_options(mut builder: winit::WindowBuilder) -> winit::WindowBuilder {
     if opts::get().headless || opts::get().output_file.is_some() {
         // Prevent the window from showing in Dock.app, stealing focus,
         // or appearing at all when running in headless mode or generating an
@@ -174,35 +142,24 @@ enum WindowKind {
     Headless(HeadlessContext),
 }
 
 /// The type of a window.
 pub struct Window {
     kind: WindowKind,
     screen_size: TypedSize2D<u32, DeviceIndependentPixel>,
     inner_size: Cell<TypedSize2D<u32, DeviceIndependentPixel>>,
-
     mouse_down_button: Cell<Option<winit::MouseButton>>,
     mouse_down_point: Cell<TypedPoint2D<i32, DevicePixel>>,
     event_queue: RefCell<Vec<WindowEvent>>,
-
-    /// id of the top level browsing context. It is unique as tabs
-    /// are not supported yet. None until created.
-    browser_id: Cell<Option<BrowserId>>,
-
     mouse_pos: Cell<TypedPoint2D<i32, DevicePixel>>,
     key_modifiers: Cell<GlutinKeyModifiers>,
-    current_url: RefCell<Option<ServoUrl>>,
-
-    last_pressed_key: Cell<Option<constellation_msg::Key>>,
-
+    last_pressed_key: Cell<Option<Key>>,
     animation_state: Cell<AnimationState>,
-
     fullscreen: Cell<bool>,
-
     gl: Rc<gl::Gl>,
     suspended: Cell<bool>,
 }
 
 #[cfg(not(target_os = "windows"))]
 fn window_creation_scale_factor() -> TypedScale<f32, DeviceIndependentPixel, DevicePixel> {
     TypedScale::new(1.0)
 }
@@ -211,20 +168,16 @@ fn window_creation_scale_factor() -> Typ
 fn window_creation_scale_factor() -> TypedScale<f32, DeviceIndependentPixel, DevicePixel> {
         let hdc = unsafe { user32::GetDC(::std::ptr::null_mut()) };
         let ppi = unsafe { gdi32::GetDeviceCaps(hdc, winapi::wingdi::LOGPIXELSY) };
         TypedScale::new(ppi as f32 / 96.0)
 }
 
 
 impl Window {
-    pub fn set_browser_id(&self, browser_id: BrowserId) {
-        self.browser_id.set(Some(browser_id));
-    }
-
     pub fn new(is_foreground: bool,
                window_size: TypedSize2D<u32, DeviceIndependentPixel>) -> Rc<Window> {
         let win_size: DeviceUintSize = (window_size.to_f32() * window_creation_scale_factor()).to_u32();
         let width = win_size.to_untyped().width;
         let height = win_size.to_untyped().height;
 
         // If there's no chrome, start off with the window invisible. It will be set to visible in
         // `load_end()`. This avoids an ugly flash of unstyled content (especially important since
@@ -311,79 +264,156 @@ impl Window {
         gl.finish();
 
         let window = Window {
             kind: window_kind,
             event_queue: RefCell::new(vec!()),
             mouse_down_button: Cell::new(None),
             mouse_down_point: Cell::new(TypedPoint2D::new(0, 0)),
 
-            browser_id: Cell::new(None),
-
             mouse_pos: Cell::new(TypedPoint2D::new(0, 0)),
             key_modifiers: Cell::new(GlutinKeyModifiers::empty()),
-            current_url: RefCell::new(None),
 
             last_pressed_key: Cell::new(None),
             gl: gl.clone(),
             animation_state: Cell::new(AnimationState::Idle),
             fullscreen: Cell::new(false),
             inner_size: Cell::new(inner_size),
             screen_size,
             suspended: Cell::new(false),
         };
 
         window.present();
 
         Rc::new(window)
     }
 
+    pub fn get_events(&self) -> Vec<WindowEvent> {
+        mem::replace(&mut *self.event_queue.borrow_mut(), Vec::new())
+    }
+
+    pub fn page_height(&self) -> f32 {
+        let dpr = self.hidpi_factor();
+        match self.kind {
+            WindowKind::Window(ref window, _) => {
+                let (_, height) = window.get_inner_size().expect("Failed to get window inner size.");
+                height as f32 * dpr.get()
+            },
+            WindowKind::Headless(ref context) => {
+                context.height as f32 * dpr.get()
+            }
+        }
+    }
+
+    pub fn set_title(&self, title: &str) {
+        if let WindowKind::Window(ref window, _) = self.kind {
+            window.set_title(title);
+        }
+    }
+
+    pub fn set_inner_size(&self, size: DeviceUintSize) {
+        if let WindowKind::Window(ref window, _) = self.kind {
+            let size = size.to_f32() / self.hidpi_factor();
+            window.set_inner_size(size.width as u32, size.height as u32)
+        }
+    }
+
+    pub fn set_position(&self, point: DeviceIntPoint) {
+        if let WindowKind::Window(ref window, _) = self.kind {
+            let point = point.to_f32() / self.hidpi_factor();
+            window.set_position(point.x as i32, point.y as i32)
+        }
+    }
+
+    pub fn set_fullscreen(&self, state: bool) {
+        match self.kind {
+            WindowKind::Window(ref window, ..) => {
+                if self.fullscreen.get() != state {
+                    window.set_fullscreen(None);
+                }
+            },
+            WindowKind::Headless(..) => {}
+        }
+        self.fullscreen.set(state);
+    }
+
+    fn is_animating(&self) -> bool {
+        self.animation_state.get() == AnimationState::Animating && !self.suspended.get()
+    }
+
+    pub fn run<T>(&self, mut servo_callback: T) where T: FnMut() -> bool {
+        match self.kind {
+            WindowKind::Window(_, ref events_loop) => {
+                let mut stop = false;
+                loop {
+                    if self.is_animating() {
+                        // We block on compositing (servo_callback ends up calling swap_buffers)
+                        events_loop.borrow_mut().poll_events(|e| {
+                            self.glutin_event_to_servo_event(e);
+                        });
+                        stop = servo_callback();
+                    } else {
+                        // We block on glutin's event loop (window events)
+                        events_loop.borrow_mut().run_forever(|e| {
+                            self.glutin_event_to_servo_event(e);
+                            if !self.event_queue.borrow().is_empty() {
+                                if !self.suspended.get() {
+                                    stop = servo_callback();
+                                }
+                            }
+                            if stop || self.is_animating() {
+                                winit::ControlFlow::Break
+                            } else {
+                                winit::ControlFlow::Continue
+                            }
+                        });
+                    }
+                    if stop {
+                        break;
+                    }
+                }
+            }
+            WindowKind::Headless(..) => {
+                loop {
+                    // Sleep the main thread to avoid using 100% CPU
+                    // This can be done better, see comments in #18777
+                    if self.event_queue.borrow().is_empty() {
+                        thread::sleep(time::Duration::from_millis(5));
+                    }
+                    let stop = servo_callback();
+                    if stop {
+                        break;
+                    }
+                }
+            }
+        }
+    }
+
     #[cfg(not(any(target_arch = "arm", target_arch = "aarch64")))]
     fn gl_version() -> GlRequest {
         return GlRequest::Specific(Api::OpenGl, (3, 2));
     }
 
     #[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
     fn gl_version() -> GlRequest {
         GlRequest::Specific(Api::OpenGlEs, (3, 0))
     }
 
-    /// Detect if given char is default ignorable in unicode
-    /// http://www.unicode.org/L2/L2002/02368-default-ignorable.pdf
-    fn is_identifier_ignorable(&self, ch: &char) -> bool {
-        match *ch {
-            '\u{0000}'...'\u{0008}' | '\u{000E}'...'\u{001F}' |
-            '\u{007F}'...'\u{0084}' | '\u{0086}'...'\u{009F}' |
-            '\u{06DD}' | '\u{070F}' |
-            '\u{180B}'...'\u{180D}' | '\u{180E}' |
-            '\u{200C}'...'\u{200F}' |
-            '\u{202A}'...'\u{202E}' | '\u{2060}'...'\u{2063}' |
-            '\u{2064}'...'\u{2069}' | '\u{206A}'...'\u{206F}' |
-            '\u{FE00}'...'\u{FE0F}' | '\u{FEFF}' |
-            '\u{FFF0}'...'\u{FFF8}' | '\u{FFF9}'...'\u{FFFB}' |
-            '\u{1D173}'...'\u{1D17A}' | '\u{E0000}' |
-            '\u{E0001}' |
-            '\u{E0002}'...'\u{E001F}' | '\u{E0020}'...'\u{E007F}' |
-            '\u{E0080}'...'\u{E0FFF}' => true,
-            _ => false
-        }
-    }
-
     fn handle_received_character(&self, ch: char) {
-        let modifiers = Window::glutin_mods_to_script_mods(self.key_modifiers.get());
-        if self.is_identifier_ignorable(&ch) {
+        let modifiers = keyutils::glutin_mods_to_script_mods(self.key_modifiers.get());
+        if keyutils::is_identifier_ignorable(&ch) {
             return
         }
         if let Some(last_pressed_key) = self.last_pressed_key.get() {
             let event = WindowEvent::KeyEvent(Some(ch), last_pressed_key, KeyState::Pressed, modifiers);
             self.event_queue.borrow_mut().push(event);
         } else {
             // Only send the character if we can print it (by ignoring characters like backspace)
             if !ch.is_control() {
-                match Window::char_to_script_key(ch) {
+                match keyutils::char_to_script_key(ch) {
                     Some(key) => {
                         let event = WindowEvent::KeyEvent(Some(ch),
                                                           key,
                                                           KeyState::Pressed,
                                                           modifiers);
                         self.event_queue.borrow_mut().push(event);
                     }
                     None => {}
@@ -405,32 +435,32 @@ impl Window {
             VirtualKeyCode::RWin => self.toggle_modifier(GlutinKeyModifiers::RIGHT_SUPER),
             _ => {}
         }
     }
 
     fn handle_keyboard_input(&self, element_state: ElementState, virtual_key_code: VirtualKeyCode) {
         self.toggle_keyboard_modifiers(virtual_key_code);
 
-        if let Ok(key) = Window::glutin_key_to_script_key(virtual_key_code) {
+        if let Ok(key) = keyutils::glutin_key_to_script_key(virtual_key_code) {
             let state = match element_state {
                 ElementState::Pressed => KeyState::Pressed,
                 ElementState::Released => KeyState::Released,
             };
             if element_state == ElementState::Pressed {
-                if is_printable(virtual_key_code) {
+                if keyutils::is_printable(virtual_key_code) {
                     self.last_pressed_key.set(Some(key));
                 }
             }
-            let modifiers = Window::glutin_mods_to_script_mods(self.key_modifiers.get());
+            let modifiers = keyutils::glutin_mods_to_script_mods(self.key_modifiers.get());
             self.event_queue.borrow_mut().push(WindowEvent::KeyEvent(None, key, state, modifiers));
         }
     }
 
-    fn handle_window_event(&self, event: winit::Event) {
+    fn glutin_event_to_servo_event(&self, event: winit::Event) {
         match event {
             Event::WindowEvent {
                 event: winit::WindowEvent::ReceivedCharacter(ch),
                 ..
             } => self.handle_received_character(ch),
             Event::WindowEvent {
                 event: winit::WindowEvent::KeyboardInput {
                     input: winit::KeyboardInput {
@@ -457,23 +487,32 @@ impl Window {
                 self.mouse_pos.set(TypedPoint2D::new(x as i32, y as i32));
                 self.event_queue.borrow_mut().push(
                     WindowEvent::MouseWindowMoveEventClass(TypedPoint2D::new(x as f32, y as f32)));
             }
             Event::WindowEvent {
                 event: winit::WindowEvent::MouseWheel { delta, phase, .. },
                 ..
             } => {
-                let (dx, dy) = match delta {
+                let (mut dx, mut dy) = match delta {
                     MouseScrollDelta::LineDelta(dx, dy) => (dx, dy * LINE_HEIGHT),
                     MouseScrollDelta::PixelDelta(dx, dy) => (dx, dy),
                 };
+                // Scroll events snap to the major axis of movement, with vertical
+                // preferred over horizontal.
+                if dy.abs() >= dx.abs() {
+                    dx = 0.0;
+                } else {
+                    dy = 0.0;
+                }
+
                 let scroll_location = ScrollLocation::Delta(TypedVector2D::new(dx, dy));
                 let phase = glutin_phase_to_touch_event_type(phase);
-                self.scroll_window(scroll_location, phase);
+                let event = WindowEvent::Scroll(scroll_location, self.mouse_pos.get(), phase);
+                self.event_queue.borrow_mut().push(event);
             },
             Event::WindowEvent {
                 event: winit::WindowEvent::Touch(touch),
                 ..
             } => {
                 use script_traits::TouchId;
 
                 let phase = glutin_phase_to_touch_event_type(touch.phase);
@@ -522,32 +561,16 @@ impl Window {
     }
 
     fn toggle_modifier(&self, modifier: GlutinKeyModifiers) {
         let mut modifiers = self.key_modifiers.get();
         modifiers.toggle(modifier);
         self.key_modifiers.set(modifiers);
     }
 
-    /// Helper function to send a scroll event.
-    fn scroll_window(&self, mut scroll_location: ScrollLocation, phase: TouchEventType) {
-        // Scroll events snap to the major axis of movement, with vertical
-        // preferred over horizontal.
-        if let ScrollLocation::Delta(ref mut delta) = scroll_location {
-            if delta.y.abs() >= delta.x.abs() {
-                delta.x = 0.0;
-            } else {
-                delta.y = 0.0;
-            }
-        }
-
-        let event = WindowEvent::Scroll(scroll_location, self.mouse_pos.get(), phase);
-        self.event_queue.borrow_mut().push(event);
-    }
-
     /// Helper function to handle a click
     fn handle_mouse(&self, button: winit::MouseButton,
                     action: winit::ElementState,
                     coords: TypedPoint2D<i32, DevicePixel>) {
         use script_traits::MouseButton;
 
         let max_pixel_dist = 10.0 * self.hidpi_factor().get();
         let event = match action {
@@ -573,400 +596,139 @@ impl Window {
                     },
                     Some(_) => mouse_up_event,
                 }
             }
         };
         self.event_queue.borrow_mut().push(WindowEvent::MouseWindowEventClass(event));
     }
 
-    pub fn get_events(&self) -> Vec<WindowEvent> {
-        mem::replace(&mut *self.event_queue.borrow_mut(), Vec::new())
-    }
-
-    fn is_animating(&self) -> bool {
-        self.animation_state.get() == AnimationState::Animating && !self.suspended.get()
+    fn hidpi_factor(&self) -> TypedScale<f32, DeviceIndependentPixel, DevicePixel> {
+        match opts::get().device_pixels_per_px {
+            Some(device_pixels_per_px) => TypedScale::new(device_pixels_per_px),
+            None => match opts::get().output_file {
+                Some(_) => TypedScale::new(1.0),
+                None => self.platform_hidpi_factor()
+            }
+        }
     }
 
-    pub fn run<T>(&self, mut servo_callback: T) where T: FnMut() -> bool {
+    #[cfg(not(target_os = "windows"))]
+    fn platform_hidpi_factor(&self) -> TypedScale<f32, DeviceIndependentPixel, DevicePixel> {
         match self.kind {
-            WindowKind::Window(_, ref events_loop) => {
-                let mut stop = false;
-                loop {
-                    if self.is_animating() {
-                        // We block on compositing (servo_callback ends up calling swap_buffers)
-                        events_loop.borrow_mut().poll_events(|e| {
-                            self.handle_window_event(e);
-                        });
-                        stop = servo_callback();
-                    } else {
-                        // We block on glutin's event loop (window events)
-                        events_loop.borrow_mut().run_forever(|e| {
-                            self.handle_window_event(e);
-                            if !self.event_queue.borrow().is_empty() {
-                                if !self.suspended.get() {
-                                    stop = servo_callback();
-                                }
-                            }
-                            if stop || self.is_animating() {
-                                winit::ControlFlow::Break
-                            } else {
-                                winit::ControlFlow::Continue
-                            }
-                        });
-                    }
-                    if stop {
-                        break;
-                    }
-                }
+            WindowKind::Window(ref window, ..) => {
+                TypedScale::new(window.hidpi_factor())
             }
             WindowKind::Headless(..) => {
-                loop {
-                    // Sleep the main thread to avoid using 100% CPU
-                    // This can be done better, see comments in #18777
-                    if self.event_queue.borrow().is_empty() {
-                        thread::sleep(time::Duration::from_millis(5));
-                    }
-                    let stop = servo_callback();
-                    if stop {
-                        break;
-                    }
-                }
+                TypedScale::new(1.0)
             }
         }
     }
 
-    fn char_to_script_key(c: char) -> Option<constellation_msg::Key> {
-        match c {
-            ' ' => Some(Key::Space),
-            '"' => Some(Key::Apostrophe),
-            '\'' => Some(Key::Apostrophe),
-            '<' => Some(Key::Comma),
-            ',' => Some(Key::Comma),
-            '_' => Some(Key::Minus),
-            '-' => Some(Key::Minus),
-            '>' => Some(Key::Period),
-            '.' => Some(Key::Period),
-            '?' => Some(Key::Slash),
-            '/' => Some(Key::Slash),
-            '~' => Some(Key::GraveAccent),
-            '`' => Some(Key::GraveAccent),
-            ')' => Some(Key::Num0),
-            '0' => Some(Key::Num0),
-            '!' => Some(Key::Num1),
-            '1' => Some(Key::Num1),
-            '@' => Some(Key::Num2),
-            '2' => Some(Key::Num2),
-            '#' => Some(Key::Num3),
-            '3' => Some(Key::Num3),
-            '$' => Some(Key::Num4),
-            '4' => Some(Key::Num4),
-            '%' => Some(Key::Num5),
-            '5' => Some(Key::Num5),
-            '^' => Some(Key::Num6),
-            '6' => Some(Key::Num6),
-            '&' => Some(Key::Num7),
-            '7' => Some(Key::Num7),
-            '*' => Some(Key::Num8),
-            '8' => Some(Key::Num8),
-            '(' => Some(Key::Num9),
-            '9' => Some(Key::Num9),
-            ':' => Some(Key::Semicolon),
-            ';' => Some(Key::Semicolon),
-            '+' => Some(Key::Equal),
-            '=' => Some(Key::Equal),
-            'A' => Some(Key::A),
-            'a' => Some(Key::A),
-            'B' => Some(Key::B),
-            'b' => Some(Key::B),
-            'C' => Some(Key::C),
-            'c' => Some(Key::C),
-            'D' => Some(Key::D),
-            'd' => Some(Key::D),
-            'E' => Some(Key::E),
-            'e' => Some(Key::E),
-            'F' => Some(Key::F),
-            'f' => Some(Key::F),
-            'G' => Some(Key::G),
-            'g' => Some(Key::G),
-            'H' => Some(Key::H),
-            'h' => Some(Key::H),
-            'I' => Some(Key::I),
-            'i' => Some(Key::I),
-            'J' => Some(Key::J),
-            'j' => Some(Key::J),
-            'K' => Some(Key::K),
-            'k' => Some(Key::K),
-            'L' => Some(Key::L),
-            'l' => Some(Key::L),
-            'M' => Some(Key::M),
-            'm' => Some(Key::M),
-            'N' => Some(Key::N),
-            'n' => Some(Key::N),
-            'O' => Some(Key::O),
-            'o' => Some(Key::O),
-            'P' => Some(Key::P),
-            'p' => Some(Key::P),
-            'Q' => Some(Key::Q),
-            'q' => Some(Key::Q),
-            'R' => Some(Key::R),
-            'r' => Some(Key::R),
-            'S' => Some(Key::S),
-            's' => Some(Key::S),
-            'T' => Some(Key::T),
-            't' => Some(Key::T),
-            'U' => Some(Key::U),
-            'u' => Some(Key::U),
-            'V' => Some(Key::V),
-            'v' => Some(Key::V),
-            'W' => Some(Key::W),
-            'w' => Some(Key::W),
-            'X' => Some(Key::X),
-            'x' => Some(Key::X),
-            'Y' => Some(Key::Y),
-            'y' => Some(Key::Y),
-            'Z' => Some(Key::Z),
-            'z' => Some(Key::Z),
-            '{' => Some(Key::LeftBracket),
-            '[' => Some(Key::LeftBracket),
-            '|' => Some(Key::Backslash),
-            '\\' => Some(Key::Backslash),
-            '}' => Some(Key::RightBracket),
-            ']' => Some(Key::RightBracket),
-            _ => None
-        }
+    #[cfg(target_os = "windows")]
+    fn platform_hidpi_factor(&self) -> TypedScale<f32, DeviceIndependentPixel, DevicePixel> {
+        let hdc = unsafe { user32::GetDC(::std::ptr::null_mut()) };
+        let ppi = unsafe { gdi32::GetDeviceCaps(hdc, winapi::wingdi::LOGPIXELSY) };
+        TypedScale::new(ppi as f32 / 96.0)
     }
 
-    fn glutin_key_to_script_key(key: winit::VirtualKeyCode) -> Result<constellation_msg::Key, ()> {
-        // TODO(negge): add more key mappings
-        match key {
-            VirtualKeyCode::A => Ok(Key::A),
-            VirtualKeyCode::B => Ok(Key::B),
-            VirtualKeyCode::C => Ok(Key::C),
-            VirtualKeyCode::D => Ok(Key::D),
-            VirtualKeyCode::E => Ok(Key::E),
-            VirtualKeyCode::F => Ok(Key::F),
-            VirtualKeyCode::G => Ok(Key::G),
-            VirtualKeyCode::H => Ok(Key::H),
-            VirtualKeyCode::I => Ok(Key::I),
-            VirtualKeyCode::J => Ok(Key::J),
-            VirtualKeyCode::K => Ok(Key::K),
-            VirtualKeyCode::L => Ok(Key::L),
-            VirtualKeyCode::M => Ok(Key::M),
-            VirtualKeyCode::N => Ok(Key::N),
-            VirtualKeyCode::O => Ok(Key::O),
-            VirtualKeyCode::P => Ok(Key::P),
-            VirtualKeyCode::Q => Ok(Key::Q),
-            VirtualKeyCode::R => Ok(Key::R),
-            VirtualKeyCode::S => Ok(Key::S),
-            VirtualKeyCode::T => Ok(Key::T),
-            VirtualKeyCode::U => Ok(Key::U),
-            VirtualKeyCode::V => Ok(Key::V),
-            VirtualKeyCode::W => Ok(Key::W),
-            VirtualKeyCode::X => Ok(Key::X),
-            VirtualKeyCode::Y => Ok(Key::Y),
-            VirtualKeyCode::Z => Ok(Key::Z),
-
-            VirtualKeyCode::Numpad0 => Ok(Key::Kp0),
-            VirtualKeyCode::Numpad1 => Ok(Key::Kp1),
-            VirtualKeyCode::Numpad2 => Ok(Key::Kp2),
-            VirtualKeyCode::Numpad3 => Ok(Key::Kp3),
-            VirtualKeyCode::Numpad4 => Ok(Key::Kp4),
-            VirtualKeyCode::Numpad5 => Ok(Key::Kp5),
-            VirtualKeyCode::Numpad6 => Ok(Key::Kp6),
-            VirtualKeyCode::Numpad7 => Ok(Key::Kp7),
-            VirtualKeyCode::Numpad8 => Ok(Key::Kp8),
-            VirtualKeyCode::Numpad9 => Ok(Key::Kp9),
-
-            VirtualKeyCode::Key0 => Ok(Key::Num0),
-            VirtualKeyCode::Key1 => Ok(Key::Num1),
-            VirtualKeyCode::Key2 => Ok(Key::Num2),
-            VirtualKeyCode::Key3 => Ok(Key::Num3),
-            VirtualKeyCode::Key4 => Ok(Key::Num4),
-            VirtualKeyCode::Key5 => Ok(Key::Num5),
-            VirtualKeyCode::Key6 => Ok(Key::Num6),
-            VirtualKeyCode::Key7 => Ok(Key::Num7),
-            VirtualKeyCode::Key8 => Ok(Key::Num8),
-            VirtualKeyCode::Key9 => Ok(Key::Num9),
-
-            VirtualKeyCode::Return => Ok(Key::Enter),
-            VirtualKeyCode::Space => Ok(Key::Space),
-            VirtualKeyCode::Escape => Ok(Key::Escape),
-            VirtualKeyCode::Equals => Ok(Key::Equal),
-            VirtualKeyCode::Minus => Ok(Key::Minus),
-            VirtualKeyCode::Back => Ok(Key::Backspace),
-            VirtualKeyCode::PageDown => Ok(Key::PageDown),
-            VirtualKeyCode::PageUp => Ok(Key::PageUp),
-
-            VirtualKeyCode::Insert => Ok(Key::Insert),
-            VirtualKeyCode::Home => Ok(Key::Home),
-            VirtualKeyCode::Delete => Ok(Key::Delete),
-            VirtualKeyCode::End => Ok(Key::End),
-
-            VirtualKeyCode::Left => Ok(Key::Left),
-            VirtualKeyCode::Up => Ok(Key::Up),
-            VirtualKeyCode::Right => Ok(Key::Right),
-            VirtualKeyCode::Down => Ok(Key::Down),
+    /// Has no effect on Android.
+    pub fn set_cursor(&self, cursor: CursorKind) {
+        match self.kind {
+            WindowKind::Window(ref window, ..) => {
+                use winit::MouseCursor;
 
-            VirtualKeyCode::LShift => Ok(Key::LeftShift),
-            VirtualKeyCode::LControl => Ok(Key::LeftControl),
-            VirtualKeyCode::LAlt => Ok(Key::LeftAlt),
-            VirtualKeyCode::LWin => Ok(Key::LeftSuper),
-            VirtualKeyCode::RShift => Ok(Key::RightShift),
-            VirtualKeyCode::RControl => Ok(Key::RightControl),
-            VirtualKeyCode::RAlt => Ok(Key::RightAlt),
-            VirtualKeyCode::RWin => Ok(Key::RightSuper),
-
-            VirtualKeyCode::Apostrophe => Ok(Key::Apostrophe),
-            VirtualKeyCode::Backslash => Ok(Key::Backslash),
-            VirtualKeyCode::Comma => Ok(Key::Comma),
-            VirtualKeyCode::Grave => Ok(Key::GraveAccent),
-            VirtualKeyCode::LBracket => Ok(Key::LeftBracket),
-            VirtualKeyCode::Period => Ok(Key::Period),
-            VirtualKeyCode::RBracket => Ok(Key::RightBracket),
-            VirtualKeyCode::Semicolon => Ok(Key::Semicolon),
-            VirtualKeyCode::Slash => Ok(Key::Slash),
-            VirtualKeyCode::Tab => Ok(Key::Tab),
-            VirtualKeyCode::Subtract => Ok(Key::Minus),
-
-            VirtualKeyCode::F1 => Ok(Key::F1),
-            VirtualKeyCode::F2 => Ok(Key::F2),
-            VirtualKeyCode::F3 => Ok(Key::F3),
-            VirtualKeyCode::F4 => Ok(Key::F4),
-            VirtualKeyCode::F5 => Ok(Key::F5),
-            VirtualKeyCode::F6 => Ok(Key::F6),
-            VirtualKeyCode::F7 => Ok(Key::F7),
-            VirtualKeyCode::F8 => Ok(Key::F8),
-            VirtualKeyCode::F9 => Ok(Key::F9),
-            VirtualKeyCode::F10 => Ok(Key::F10),
-            VirtualKeyCode::F11 => Ok(Key::F11),
-            VirtualKeyCode::F12 => Ok(Key::F12),
-
-            VirtualKeyCode::NavigateBackward => Ok(Key::NavigateBackward),
-            VirtualKeyCode::NavigateForward => Ok(Key::NavigateForward),
-            _ => Err(()),
+                let glutin_cursor = match cursor {
+                    CursorKind::Auto => MouseCursor::Default,
+                    CursorKind::None => MouseCursor::NoneCursor,
+                    CursorKind::Default => MouseCursor::Default,
+                    CursorKind::Pointer => MouseCursor::Hand,
+                    CursorKind::ContextMenu => MouseCursor::ContextMenu,
+                    CursorKind::Help => MouseCursor::Help,
+                    CursorKind::Progress => MouseCursor::Progress,
+                    CursorKind::Wait => MouseCursor::Wait,
+                    CursorKind::Cell => MouseCursor::Cell,
+                    CursorKind::Crosshair => MouseCursor::Crosshair,
+                    CursorKind::Text => MouseCursor::Text,
+                    CursorKind::VerticalText => MouseCursor::VerticalText,
+                    CursorKind::Alias => MouseCursor::Alias,
+                    CursorKind::Copy => MouseCursor::Copy,
+                    CursorKind::Move => MouseCursor::Move,
+                    CursorKind::NoDrop => MouseCursor::NoDrop,
+                    CursorKind::NotAllowed => MouseCursor::NotAllowed,
+                    CursorKind::Grab => MouseCursor::Grab,
+                    CursorKind::Grabbing => MouseCursor::Grabbing,
+                    CursorKind::EResize => MouseCursor::EResize,
+                    CursorKind::NResize => MouseCursor::NResize,
+                    CursorKind::NeResize => MouseCursor::NeResize,
+                    CursorKind::NwResize => MouseCursor::NwResize,
+                    CursorKind::SResize => MouseCursor::SResize,
+                    CursorKind::SeResize => MouseCursor::SeResize,
+                    CursorKind::SwResize => MouseCursor::SwResize,
+                    CursorKind::WResize => MouseCursor::WResize,
+                    CursorKind::EwResize => MouseCursor::EwResize,
+                    CursorKind::NsResize => MouseCursor::NsResize,
+                    CursorKind::NeswResize => MouseCursor::NeswResize,
+                    CursorKind::NwseResize => MouseCursor::NwseResize,
+                    CursorKind::ColResize => MouseCursor::ColResize,
+                    CursorKind::RowResize => MouseCursor::RowResize,
+                    CursorKind::AllScroll => MouseCursor::AllScroll,
+                    CursorKind::ZoomIn => MouseCursor::ZoomIn,
+                    CursorKind::ZoomOut => MouseCursor::ZoomOut,
+                };
+                window.set_cursor(glutin_cursor);
+            }
+            WindowKind::Headless(..) => {}
         }
     }
-
-    fn glutin_mods_to_script_mods(modifiers: GlutinKeyModifiers) -> constellation_msg::KeyModifiers {
-        let mut result = constellation_msg::KeyModifiers::empty();
-        if modifiers.intersects(GlutinKeyModifiers::LEFT_SHIFT | GlutinKeyModifiers::RIGHT_SHIFT) {
-            result.insert(KeyModifiers::SHIFT);
-        }
-        if modifiers.intersects(GlutinKeyModifiers::LEFT_CONTROL | GlutinKeyModifiers::RIGHT_CONTROL) {
-            result.insert(KeyModifiers::CONTROL);
-        }
-        if modifiers.intersects(GlutinKeyModifiers::LEFT_ALT | GlutinKeyModifiers::RIGHT_ALT) {
-            result.insert(KeyModifiers::ALT);
-        }
-        if modifiers.intersects(GlutinKeyModifiers::LEFT_SUPER | GlutinKeyModifiers::RIGHT_SUPER) {
-            result.insert(KeyModifiers::SUPER);
-        }
-        result
-    }
-
-    #[cfg(not(target_os = "win"))]
-    fn platform_handle_key(&self, key: Key, mods: constellation_msg::KeyModifiers, browser_id: BrowserId) {
-        match (mods, key) {
-            (CMD_OR_CONTROL, Key::LeftBracket) => {
-                let event = WindowEvent::Navigation(browser_id, TraversalDirection::Back(1));
-                self.event_queue.borrow_mut().push(event);
-            }
-            (CMD_OR_CONTROL, Key::RightBracket) => {
-                let event = WindowEvent::Navigation(browser_id, TraversalDirection::Forward(1));
-                self.event_queue.borrow_mut().push(event);
-            }
-            _ => {}
-        }
-    }
-
-    #[cfg(target_os = "win")]
-    fn platform_handle_key(&self, key: Key, mods: constellation_msg::KeyModifiers, browser_id: BrowserId) {
-    }
 }
 
 impl WindowMethods for Window {
     fn gl(&self) -> Rc<gl::Gl> {
         self.gl.clone()
     }
 
-    fn framebuffer_size(&self) -> DeviceUintSize {
-        (self.inner_size.get().to_f32() * self.hidpi_factor()).to_u32()
-    }
-
-    fn window_rect(&self) -> DeviceUintRect {
-        let size = self.framebuffer_size();
-        let origin = TypedPoint2D::zero();
-        DeviceUintRect::new(origin, size)
-    }
-
-    fn client_window(&self, _: BrowserId) -> (DeviceUintSize, DeviceIntPoint) {
-        let (size, point) = match self.kind {
-            WindowKind::Window(ref window, ..) => {
+    fn get_coordinates(&self) -> EmbedderCoordinates {
+        let dpr = self.hidpi_factor();
+        match self.kind {
+            WindowKind::Window(ref window, _) => {
                 // TODO(ajeffrey): can this fail?
                 let (width, height) = window.get_outer_size().expect("Failed to get window outer size.");
-                let size = TypedSize2D::new(width as f32, height as f32);
-                // TODO(ajeffrey): can this fail?
-                let (x, y) = window.get_position().expect("Failed to get window position.");
-                let origin = TypedPoint2D::new(x as f32, y as f32);
-                (size, origin)
-            }
-            WindowKind::Headless(ref context) => {
-                let size = TypedSize2D::new(context.width as f32, context.height as f32);
-                let origin = TypedPoint2D::zero();
-                (size, origin)
-            }
-        };
-        let dpr = self.hidpi_factor();
-        ((size * dpr).to_u32(), (point * dpr).to_i32())
-    }
+                let (x, y) = window.get_position().unwrap_or((0, 0));
+                let win_size = (TypedSize2D::new(width as f32, height as f32) * dpr).to_u32();
+                let win_origin = (TypedPoint2D::new(x as f32, y as f32) * dpr).to_i32();
+                let screen = (self.screen_size.to_f32() * dpr).to_u32();
 
-    fn screen_size(&self, _: BrowserId) -> DeviceUintSize {
-        (self.screen_size.to_f32() * self.hidpi_factor()).to_u32()
-    }
-
-    fn screen_avail_size(&self, browser_id: BrowserId) -> DeviceUintSize {
-        // FIXME: Glutin doesn't have API for available size. Fallback to screen size
-        self.screen_size(browser_id)
-    }
+                let (width, height) = window.get_inner_size().expect("Failed to get window inner size.");
+                let inner_size = (TypedSize2D::new(width as f32, height as f32) * dpr).to_u32();
 
-    fn set_animation_state(&self, state: AnimationState) {
-        self.animation_state.set(state);
-    }
-
-    fn set_inner_size(&self, _: BrowserId, size: DeviceUintSize) {
-        match self.kind {
-            WindowKind::Window(ref window, ..) => {
-                let size = size.to_f32() / self.hidpi_factor();
-                window.set_inner_size(size.width as u32, size.height as u32)
-            }
-            WindowKind::Headless(..) => {}
-        }
-    }
+                let viewport = DeviceUintRect::new(TypedPoint2D::zero(), inner_size);
 
-    fn set_position(&self, _: BrowserId, point: DeviceIntPoint) {
-        match self.kind {
-            WindowKind::Window(ref window, ..) => {
-                let point = point.to_f32() / self.hidpi_factor();
-                window.set_position(point.x as i32, point.y as i32)
-            }
-            WindowKind::Headless(..) => {}
-        }
-    }
-
-    fn set_fullscreen_state(&self, _: BrowserId, state: bool) {
-        match self.kind {
-            WindowKind::Window(ref window, ..) => {
-                if self.fullscreen.get() != state {
-                    window.set_fullscreen(None);
+                EmbedderCoordinates {
+                    viewport: viewport,
+                    framebuffer: inner_size,
+                    window: (win_size, win_origin),
+                    screen: screen,
+                    // FIXME: Glutin doesn't have API for available size. Fallback to screen size
+                    screen_avail: screen,
+                    hidpi_factor: dpr,
                 }
             },
-            WindowKind::Headless(..) => {}
+            WindowKind::Headless(ref context) => {
+                let size = (TypedSize2D::new(context.width, context.height).to_f32() * dpr).to_u32();
+                EmbedderCoordinates {
+                    viewport: DeviceUintRect::new(TypedPoint2D::zero(), size),
+                    framebuffer: size,
+                    window: (size, TypedPoint2D::zero()),
+                    screen: size,
+                    screen_avail: size,
+                    hidpi_factor: dpr,
+                }
+            }
         }
-        self.fullscreen.set(state);
     }
 
     fn present(&self) {
         match self.kind {
             WindowKind::Window(ref window, ..) => {
                 if let Err(err) = window.swap_buffers() {
                     warn!("Failed to swap window buffers ({}).", err);
                 }
@@ -1006,378 +768,29 @@ impl WindowMethods for Window {
                     proxy: self.proxy.clone(),
                 })
             }
         }
 
         Box::new(GlutinEventLoopWaker::new(&self))
     }
 
-    #[cfg(not(target_os = "windows"))]
-    fn hidpi_factor(&self) -> TypedScale<f32, DeviceIndependentPixel, DevicePixel> {
-        match self.kind {
-            WindowKind::Window(ref window, ..) => {
-                TypedScale::new(window.hidpi_factor())
-            }
-            WindowKind::Headless(..) => {
-                TypedScale::new(1.0)
-            }
-        }
-    }
-
-    #[cfg(target_os = "windows")]
-    fn hidpi_factor(&self) -> TypedScale<f32, DeviceIndependentPixel, DevicePixel> {
-        let hdc = unsafe { user32::GetDC(::std::ptr::null_mut()) };
-        let ppi = unsafe { gdi32::GetDeviceCaps(hdc, winapi::wingdi::LOGPIXELSY) };
-        TypedScale::new(ppi as f32 / 96.0)
-    }
-
-    fn set_page_title(&self, _: BrowserId, title: Option<String>) {
-        match self.kind {
-            WindowKind::Window(ref window, ..) => {
-                let fallback_title: String = if let Some(ref current_url) = *self.current_url.borrow() {
-                    current_url.to_string()
-                } else {
-                    String::from("Untitled")
-                };
-
-                let title = match title {
-                    Some(ref title) if title.len() > 0 => &**title,
-                    _ => &fallback_title,
-                };
-                let title = format!("{} - Servo", title);
-                window.set_title(&title);
-            }
-            WindowKind::Headless(..) => {}
-        }
-    }
-
-    fn status(&self, _: BrowserId, _: Option<String>) {
-    }
-
-    fn load_start(&self, _: BrowserId) {
-    }
-
-    fn load_end(&self, _: BrowserId) {
-        if opts::get().no_native_titlebar {
-            match self.kind {
-                WindowKind::Window(ref window, ..) => {
-                    window.show();
-                }
-                WindowKind::Headless(..) => {}
-            }
-        }
-    }
-
-    fn history_changed(&self, _: BrowserId, history: Vec<LoadData>, current: usize) {
-        *self.current_url.borrow_mut() = Some(history[current].url.clone());
-    }
-
-    fn load_error(&self, _: BrowserId, _: NetError, _: String) {
-    }
-
-    fn head_parsed(&self, _: BrowserId) {
-    }
-
-    /// Has no effect on Android.
-    fn set_cursor(&self, cursor: CursorKind) {
-        match self.kind {
-            WindowKind::Window(ref window, ..) => {
-                use winit::MouseCursor;
-
-                let glutin_cursor = match cursor {
-                    CursorKind::Auto => MouseCursor::Default,
-                    CursorKind::None => MouseCursor::NoneCursor,
-                    CursorKind::Default => MouseCursor::Default,
-                    CursorKind::Pointer => MouseCursor::Hand,
-                    CursorKind::ContextMenu => MouseCursor::ContextMenu,
-                    CursorKind::Help => MouseCursor::Help,
-                    CursorKind::Progress => MouseCursor::Progress,
-                    CursorKind::Wait => MouseCursor::Wait,
-                    CursorKind::Cell => MouseCursor::Cell,
-                    CursorKind::Crosshair => MouseCursor::Crosshair,
-                    CursorKind::Text => MouseCursor::Text,
-                    CursorKind::VerticalText => MouseCursor::VerticalText,
-                    CursorKind::Alias => MouseCursor::Alias,
-                    CursorKind::Copy => MouseCursor::Copy,
-                    CursorKind::Move => MouseCursor::Move,
-                    CursorKind::NoDrop => MouseCursor::NoDrop,
-                    CursorKind::NotAllowed => MouseCursor::NotAllowed,
-                    CursorKind::Grab => MouseCursor::Grab,
-                    CursorKind::Grabbing => MouseCursor::Grabbing,
-                    CursorKind::EResize => MouseCursor::EResize,
-                    CursorKind::NResize => MouseCursor::NResize,
-                    CursorKind::NeResize => MouseCursor::NeResize,
-                    CursorKind::NwResize => MouseCursor::NwResize,
-                    CursorKind::SResize => MouseCursor::SResize,
-                    CursorKind::SeResize => MouseCursor::SeResize,
-                    CursorKind::SwResize => MouseCursor::SwResize,
-                    CursorKind::WResize => MouseCursor::WResize,
-                    CursorKind::EwResize => MouseCursor::EwResize,
-                    CursorKind::NsResize => MouseCursor::NsResize,
-                    CursorKind::NeswResize => MouseCursor::NeswResize,
-                    CursorKind::NwseResize => MouseCursor::NwseResize,
-                    CursorKind::ColResize => MouseCursor::ColResize,
-                    CursorKind::RowResize => MouseCursor::RowResize,
-                    CursorKind::AllScroll => MouseCursor::AllScroll,
-                    CursorKind::ZoomIn => MouseCursor::ZoomIn,
-                    CursorKind::ZoomOut => MouseCursor::ZoomOut,
-                };
-                window.set_cursor(glutin_cursor);
-            }
-            WindowKind::Headless(..) => {}
-        }
-    }
-
-    fn set_favicon(&self, _: BrowserId, _: ServoUrl) {
+    fn set_animation_state(&self, state: AnimationState) {
+        self.animation_state.set(state);
     }
 
     fn prepare_for_composite(&self, _width: Length<u32, DevicePixel>, _height: Length<u32, DevicePixel>) -> bool {
         true
     }
 
-    /// Helper function to handle keyboard events.
-    fn handle_key(&self, _: Option<BrowserId>, ch: Option<char>, key: Key, mods: constellation_msg::KeyModifiers) {
-        let browser_id = match self.browser_id.get() {
-            Some(id) => id,
-            None => { unreachable!("Can't get keys without a browser"); }
-        };
-        match (mods, ch, key) {
-            (_, Some('+'), _) => {
-                if mods & !KeyModifiers::SHIFT == CMD_OR_CONTROL {
-                    self.event_queue.borrow_mut().push(WindowEvent::Zoom(1.1));
-                } else if mods & !KeyModifiers::SHIFT == CMD_OR_CONTROL | KeyModifiers::ALT {
-                    self.event_queue.borrow_mut().push(WindowEvent::PinchZoom(1.1));
-                }
-            }
-            (CMD_OR_CONTROL, Some('-'), _) => {
-                self.event_queue.borrow_mut().push(WindowEvent::Zoom(1.0 / 1.1));
-            }
-            (_, Some('-'), _) if mods == CMD_OR_CONTROL | KeyModifiers::ALT => {
-                self.event_queue.borrow_mut().push(WindowEvent::PinchZoom(1.0 / 1.1));
-            }
-            (CMD_OR_CONTROL, Some('0'), _) => {
-                self.event_queue.borrow_mut().push(WindowEvent::ResetZoom);
-            }
-
-            (KeyModifiers::NONE, None, Key::NavigateForward) => {
-                let event = WindowEvent::Navigation(browser_id, TraversalDirection::Forward(1));
-                self.event_queue.borrow_mut().push(event);
-            }
-            (KeyModifiers::NONE, None, Key::NavigateBackward) => {
-                let event = WindowEvent::Navigation(browser_id, TraversalDirection::Back(1));
-                self.event_queue.borrow_mut().push(event);
-            }
-
-            (KeyModifiers::NONE, None, Key::Escape) => {
-                if let Some(true) = PREFS.get("shell.builtin-key-shortcuts.enabled").as_boolean() {
-                    self.event_queue.borrow_mut().push(WindowEvent::Quit);
-                }
-            }
-
-            (CMD_OR_ALT, None, Key::Right) => {
-                let event = WindowEvent::Navigation(browser_id, TraversalDirection::Forward(1));
-                self.event_queue.borrow_mut().push(event);
-            }
-            (CMD_OR_ALT, None, Key::Left) => {
-                let event = WindowEvent::Navigation(browser_id, TraversalDirection::Back(1));
-                self.event_queue.borrow_mut().push(event);
-            }
-
-            (KeyModifiers::NONE, None, Key::PageDown) => {
-               let scroll_location = ScrollLocation::Delta(TypedVector2D::new(0.0,
-                                   -self.framebuffer_size()
-                                        .to_f32()
-                                        .to_untyped()
-                                        .height + 2.0 * LINE_HEIGHT));
-                self.scroll_window(scroll_location,
-                                   TouchEventType::Move);
-            }
-            (KeyModifiers::NONE, None, Key::PageUp) => {
-                let scroll_location = ScrollLocation::Delta(TypedVector2D::new(0.0,
-                                   self.framebuffer_size()
-                                       .to_f32()
-                                       .to_untyped()
-                                       .height - 2.0 * LINE_HEIGHT));
-                self.scroll_window(scroll_location,
-                                   TouchEventType::Move);
-            }
-
-            (KeyModifiers::NONE, None, Key::Home) => {
-                self.scroll_window(ScrollLocation::Start, TouchEventType::Move);
-            }
-
-            (KeyModifiers::NONE, None, Key::End) => {
-                self.scroll_window(ScrollLocation::End, TouchEventType::Move);
-            }
-
-            (KeyModifiers::NONE, None, Key::Up) => {
-                self.scroll_window(ScrollLocation::Delta(TypedVector2D::new(0.0, 3.0 * LINE_HEIGHT)),
-                                   TouchEventType::Move);
-            }
-            (KeyModifiers::NONE, None, Key::Down) => {
-                self.scroll_window(ScrollLocation::Delta(TypedVector2D::new(0.0, -3.0 * LINE_HEIGHT)),
-                                   TouchEventType::Move);
-            }
-            (KeyModifiers::NONE, None, Key::Left) => {
-                self.scroll_window(ScrollLocation::Delta(TypedVector2D::new(LINE_HEIGHT, 0.0)), TouchEventType::Move);
-            }
-            (KeyModifiers::NONE, None, Key::Right) => {
-                self.scroll_window(ScrollLocation::Delta(TypedVector2D::new(-LINE_HEIGHT, 0.0)), TouchEventType::Move);
-            }
-            (CMD_OR_CONTROL, Some('r'), _) => {
-                if let Some(true) = PREFS.get("shell.builtin-key-shortcuts.enabled").as_boolean() {
-                    self.event_queue.borrow_mut().push(WindowEvent::Reload(browser_id));
-                }
-            }
-            (CMD_OR_CONTROL, Some('l'), _) => {
-                if let Some(true) = PREFS.get("shell.builtin-key-shortcuts.enabled").as_boolean() {
-                    let url: String = if let Some(ref url) = *self.current_url.borrow() {
-                        url.to_string()
-                    } else {
-                        String::from("")
-                    };
-                    let title = "URL or search query";
-                    if let Some(input) = tinyfiledialogs::input_box(title, title, &url) {
-                        if let Some(url) = sanitize_url(&input) {
-                            self.event_queue.borrow_mut().push(WindowEvent::LoadUrl(browser_id, url));
-                        }
-                    }
-                }
-            }
-            (CMD_OR_CONTROL, Some('q'), _) => {
-                if let Some(true) = PREFS.get("shell.builtin-key-shortcuts.enabled").as_boolean() {
-                    self.event_queue.borrow_mut().push(WindowEvent::Quit);
-                }
-            }
-            (_, Some('3'), _) => if mods ^ KeyModifiers::CONTROL == KeyModifiers::SHIFT {
-                self.event_queue.borrow_mut().push(WindowEvent::CaptureWebRender);
-            }
-            (KeyModifiers::CONTROL, None, Key::F10) => {
-                let event = WindowEvent::ToggleWebRenderDebug(WebRenderDebugOption::RenderTargetDebug);
-                self.event_queue.borrow_mut().push(event);
-            }
-            (KeyModifiers::CONTROL, None, Key::F11) => {
-                let event = WindowEvent::ToggleWebRenderDebug(WebRenderDebugOption::TextureCacheDebug);
-                self.event_queue.borrow_mut().push(event);
-            }
-            (KeyModifiers::CONTROL, None, Key::F12) => {
-                let event = WindowEvent::ToggleWebRenderDebug(WebRenderDebugOption::Profiler);
-                self.event_queue.borrow_mut().push(event);
-            }
-
-            _ => {
-                self.platform_handle_key(key, mods, browser_id);
-            }
-        }
-    }
-
-    fn allow_navigation(&self, _: BrowserId, _: ServoUrl, response_chan: IpcSender<bool>) {
-        if let Err(e) = response_chan.send(true) {
-            warn!("Failed to send allow_navigation() response: {}", e);
-        };
-    }
-
     fn supports_clipboard(&self) -> bool {
         true
     }
-
-    fn handle_panic(&self, _: BrowserId, _reason: String, _backtrace: Option<String>) {
-        // Nothing to do here yet. The crash has already been reported on the console.
-    }
 }
 
 fn glutin_phase_to_touch_event_type(phase: TouchPhase) -> TouchEventType {
     match phase {
         TouchPhase::Started => TouchEventType::Down,
         TouchPhase::Moved => TouchEventType::Move,
         TouchPhase::Ended => TouchEventType::Up,
         TouchPhase::Cancelled => TouchEventType::Cancel,
     }
 }
-
-fn is_printable(key_code: VirtualKeyCode) -> bool {
-    use winit::VirtualKeyCode::*;
-    match key_code {
-        Escape |
-        F1 |
-        F2 |
-        F3 |
-        F4 |
-        F5 |
-        F6 |
-        F7 |
-        F8 |
-        F9 |
-        F10 |
-        F11 |
-        F12 |
-        F13 |
-        F14 |
-        F15 |
-        Snapshot |
-        Scroll |
-        Pause |
-        Insert |
-        Home |
-        Delete |
-        End |
-        PageDown |
-        PageUp |
-        Left |
-        Up |
-        Right |
-        Down |
-        Back |
-        LAlt |
-        LControl |
-        LMenu |
-        LShift |
-        LWin |
-        Mail |
-        MediaSelect |
-        MediaStop |
-        Mute |
-        MyComputer |
-        NavigateForward |
-        NavigateBackward |
-        NextTrack |
-        NoConvert |
-        PlayPause |
-        Power |
-        PrevTrack |
-        RAlt |
-        RControl |
-        RMenu |
-        RShift |
-        RWin |
-        Sleep |
-        Stop |
-        VolumeDown |
-        VolumeUp |
-        Wake |
-        WebBack |
-        WebFavorites |
-        WebForward |
-        WebHome |
-        WebRefresh |
-        WebSearch |
-        WebStop => false,
-        _ => true,
-    }
-}
-
-fn sanitize_url(request: &str) -> Option<ServoUrl> {
-    let request = request.trim();
-    ServoUrl::parse(&request).ok()
-        .or_else(|| {
-            if request.contains('/') || is_reg_domain(request) {
-                ServoUrl::parse(&format!("http://{}", request)).ok()
-            } else {
-                None
-            }
-        }).or_else(|| {
-            PREFS.get("shell.searchpage").as_string().and_then(|s: &str| {
-                let url = s.replace("%s", request);
-                ServoUrl::parse(&url).ok()
-            })
-        })
-}
--- a/servo/ports/servo/main.rs
+++ b/servo/ports/servo/main.rs
@@ -24,23 +24,21 @@ extern crate backtrace;
 extern crate compositing;
 extern crate euclid;
 #[cfg(target_os = "windows")] extern crate gdi32;
 extern crate gleam;
 extern crate glutin;
 // The window backed by glutin
 #[macro_use] extern crate log;
 extern crate msg;
-extern crate net_traits;
 #[cfg(any(target_os = "linux", target_os = "macos"))] extern crate osmesa_sys;
 extern crate script_traits;
 extern crate servo;
 extern crate servo_config;
 extern crate servo_geometry;
-extern crate servo_url;
 #[cfg(all(feature = "unstable", not(target_os = "android")))]
 #[macro_use]
 extern crate sig;
 extern crate style_traits;
 extern crate tinyfiledialogs;
 extern crate webrender_api;
 extern crate winit;
 #[cfg(target_os = "windows")] extern crate winapi;
@@ -58,16 +56,18 @@ use servo::config::servo_version;
 use servo::ipc_channel::ipc;
 use servo::servo_config::prefs::PREFS;
 use servo::servo_url::ServoUrl;
 use std::env;
 use std::panic;
 use std::process;
 use std::thread;
 
+mod browser;
+
 pub mod platform {
     #[cfg(target_os = "macos")]
     pub use platform::macos::deinit;
 
     #[cfg(target_os = "macos")]
     pub mod macos;
 
     #[cfg(not(target_os = "macos"))]
@@ -158,47 +158,67 @@ fn main() {
 
     if opts::get().is_printing_version {
         println!("{}", servo_version());
         process::exit(0);
     }
 
     let window = glutin_app::create_window();
 
+    let mut browser = browser::Browser::new(window.clone());
+
     // If the url is not provided, we fallback to the homepage in PREFS,
     // or a blank page in case the homepage is not set either.
     let cwd = env::current_dir().unwrap();
     let cmdline_url = opts::get().url.clone();
     let pref_url = PREFS.get("shell.homepage").as_string()
         .and_then(|str| parse_url_or_filename(&cwd, str).ok());
     let blank_url = ServoUrl::parse("about:blank").ok();
 
     let target_url = cmdline_url.or(pref_url).or(blank_url).unwrap();
 
     let mut servo = Servo::new(window.clone());
 
     let (sender, receiver) = ipc::channel().unwrap();
     servo.handle_events(vec![WindowEvent::NewBrowser(target_url, sender)]);
     let browser_id = receiver.recv().unwrap();
-    window.set_browser_id(browser_id);
+    browser.set_browser_id(browser_id);
     servo.handle_events(vec![WindowEvent::SelectBrowser(browser_id)]);
 
     servo.setup_logging();
 
     window.run(|| {
-        let events = window.get_events();
-        let need_resize = events.iter().any(|e| match *e {
+        let win_events = window.get_events();
+
+        // FIXME: this could be handled by Servo. We don't need
+        // a repaint_synchronously function exposed.
+        let need_resize = win_events.iter().any(|e| match *e {
             WindowEvent::Resize => true,
-            _ => false
+            _ => false,
         });
-        let stop = !servo.handle_events(events);
+
+        browser.handle_window_events(win_events);
+
+        let mut servo_events = servo.get_events();
+        loop {
+            browser.handle_servo_events(servo_events);
+            servo.handle_events(browser.get_events());
+            if browser.shutdown_requested() {
+                return true;
+            }
+            servo_events = servo.get_events();
+            if servo_events.is_empty() {
+                break;
+            }
+        }
+
         if need_resize {
             servo.repaint_synchronously();
         }
-        stop
+        false
     });
 
     servo.deinit();
 
     platform::deinit()
 }
 
 #[cfg(target_os = "android")]
--- a/servo/resources/prefs.json
+++ b/servo/resources/prefs.json
@@ -58,16 +58,15 @@
   "js.werror.enabled": false,
   "layout.animations.test.enabled": false,
   "layout.columns.enabled": false,
   "layout.viewport.enabled": false,
   "layout.writing-mode.enabled": false,
   "network.http-cache.disabled": false,
   "network.mime.sniff": false,
   "session-history.max-length": 20,
-  "shell.builtin-key-shortcuts.enabled": true,
   "shell.homepage": "https://servo.org",
   "shell.keep_screen_on.enabled": false,
   "shell.native-orientation": "both",
   "shell.native-titlebar.enabled": true,
   "shell.searchpage": "https://duckduckgo.com/html/?q=%s",
   "webgl.testing.context_creation_error": false
 }