servo: Merge #2603 - Use some typed units in compositor and windowing (from mbrubeck:units); r=pcwalton
authorMatt Brubeck <mbrubeck@limpet.net>
Fri, 06 Jun 2014 16:04:34 -0400
changeset 334492 1877e9396a6e433840334fd23bd927ef3469e94f
parent 334491 ea3479a9b5d7708cfa2eb0a22fa284930650f0ad
child 334493 d55fe8e24bb61341cc1256869fa2b224c8a6a682
push id31307
push usergszorc@mozilla.com
push dateSat, 04 Feb 2017 00:59:06 +0000
treeherdermozilla-central@94079d43835f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspcwalton
servo: Merge #2603 - Use some typed units in compositor and windowing (from mbrubeck:units); r=pcwalton This is a rebased, squashed, and updated version of #2444. Source-Repo: https://github.com/servo/servo Source-Revision: 6c382243c4b3de9f0eec9cd71c757897ffd1b2e0
servo/src/components/main/compositing/compositor.rs
servo/src/components/main/compositing/compositor_layer.rs
servo/src/components/main/platform/common/glfw_windowing.rs
servo/src/components/main/platform/common/glut_windowing.rs
servo/src/components/main/windowing.rs
servo/src/components/util/geometry.rs
servo/src/components/util/opts.rs
--- a/servo/src/components/main/compositing/compositor.rs
+++ b/servo/src/components/main/compositing/compositor.rs
@@ -11,31 +11,33 @@ use windowing::{FinishedWindowEvent, Idl
 use windowing::{MouseWindowEvent, MouseWindowEventClass, MouseWindowMouseDownEvent};
 use windowing::{MouseWindowMouseUpEvent, MouseWindowMoveEventClass, NavigationWindowEvent};
 use windowing::{QuitWindowEvent, RefreshWindowEvent, ResizeWindowEvent, ScrollWindowEvent};
 use windowing::{WindowEvent, WindowMethods, WindowNavigateMsg, ZoomWindowEvent};
 
 use azure::azure_hl::{SourceSurfaceMethods, Color};
 use azure::azure_hl;
 use geom::matrix::identity;
-use geom::point::Point2D;
+use geom::point::{Point2D, TypedPoint2D};
 use geom::rect::Rect;
-use geom::size::Size2D;
+use geom::size::{Size2D, TypedSize2D};
+use geom::scale_factor::ScaleFactor;
 use layers::layers::{ContainerLayer, ContainerLayerKind};
 use layers::platform::surface::NativeCompositingGraphicsContext;
 use layers::rendergl;
 use layers::rendergl::RenderContext;
 use layers::scene::Scene;
 use opengles::gl2;
 use png;
 use servo_msg::compositor_msg::{Blank, Epoch, FinishedLoading, IdleRenderState, LayerBufferSet};
 use servo_msg::compositor_msg::{LayerId, ReadyState, RenderState, ScrollPolicy, Scrollable};
 use servo_msg::constellation_msg::{ConstellationChan, ExitMsg, LoadUrlMsg, NavigateMsg};
 use servo_msg::constellation_msg::{PipelineId, ResizedWindowMsg};
 use servo_msg::constellation_msg;
+use servo_util::geometry::{DevicePixel, PagePx, ScreenPx};
 use servo_util::opts::Opts;
 use servo_util::time::{profile, ProfilerChan};
 use servo_util::{time, url};
 use std::io::timer::sleep;
 use std::path::Path;
 use std::rc::Rc;
 use time::precise_time_s;
 
@@ -55,17 +57,20 @@ pub struct IOCompositor {
 
     /// The root pipeline.
     root_pipeline: Option<CompositionPipeline>,
 
     /// The canvas to paint a page.
     scene: Scene,
 
     /// The application window size.
-    window_size: Size2D<uint>,
+    window_size: TypedSize2D<DevicePixel, uint>,
+
+    /// The device pixel ratio for this window.
+    hidpi_factor: ScaleFactor<ScreenPx, DevicePixel, f32>,
 
     /// The platform-specific graphics context.
     graphics_context: NativeCompositingGraphicsContext,
 
     /// Tracks whether the renderer has finished its first rendering
     composite_ready: bool,
 
     /// Tracks whether we are in the process of shutting down.
@@ -73,17 +78,17 @@ pub struct IOCompositor {
 
     /// Tracks whether we should close compositor.
     done: bool,
 
     /// Tracks whether we need to re-composite a page.
     recomposite: bool,
 
     /// Keeps track of the current zoom factor.
-    world_zoom: f32,
+    world_zoom: ScaleFactor<PagePx, ScreenPx, f32>,
 
     /// Tracks whether the zoom action has happend recently.
     zoom_action: bool,
 
     /// The time of the last zoom action has started.
     zoom_time: f64,
 
     /// Current display/reflow status of the page
@@ -119,42 +124,34 @@ impl IOCompositor {
         let window: Rc<Window> = WindowMethods::new(app, opts.output_file.is_none());
 
         // Create an initial layer tree.
         //
         // TODO: There should be no initial layer tree until the renderer creates one from the display
         // list. This is only here because we don't have that logic in the renderer yet.
         let root_layer = Rc::new(ContainerLayer());
         let window_size = window.size();
-
-        let hidpi_factor = match opts.device_pixels_per_px {
-            Some(dppx) => dppx,
-            None => match opts.output_file {
-                Some(_) => 1.0,
-                None => window.hidpi_factor(),
-            }
-        };
-
-        root_layer.common.borrow_mut().set_transform(identity().scale(hidpi_factor, hidpi_factor, 1f32));
+        let hidpi_factor = window.hidpi_factor();
 
         IOCompositor {
             window: window,
             port: port,
             opts: opts,
             context: rendergl::init_render_context(),
             root_layer: root_layer.clone(),
             root_pipeline: None,
-            scene: Scene(ContainerLayerKind(root_layer), window_size, identity()),
-            window_size: Size2D(window_size.width as uint, window_size.height as uint),
+            scene: Scene(ContainerLayerKind(root_layer), window_size.to_untyped(), identity()),
+            window_size: window_size.as_uint(),
+            hidpi_factor: hidpi_factor,
             graphics_context: CompositorTask::create_graphics_context(),
             composite_ready: false,
             shutting_down: false,
             done: false,
             recomposite: false,
-            world_zoom: hidpi_factor,
+            world_zoom: ScaleFactor(1.0),
             zoom_action: false,
             zoom_time: 0f64,
             ready_state: Blank,
             load_complete: false,
             compositor_layer: None,
             constellation_chan: constellation_chan,
             profiler_chan: profiler_chan,
             fragment_point: None
@@ -166,26 +163,27 @@ impl IOCompositor {
                   port: Receiver<Msg>,
                   constellation_chan: ConstellationChan,
                   profiler_chan: ProfilerChan) {
         let mut compositor = IOCompositor::new(app,
                                                opts,
                                                port,
                                                constellation_chan,
                                                profiler_chan);
+        compositor.update_zoom_transform();
 
         // Starts the compositor, which listens for messages on the specified port.
         compositor.run();
     }
 
     fn run (&mut self) {
         // Tell the constellation about the initial window size.
         {
             let ConstellationChan(ref chan) = self.constellation_chan;
-            chan.send(ResizedWindowMsg(self.window_size));
+            chan.send(ResizedWindowMsg(self.window_size.to_untyped()));
         }
 
         // Enter the main event loop.
         while !self.done {
             // Check for new messages coming from the rendering task.
             self.handle_message();
 
             if self.done {
@@ -336,22 +334,20 @@ impl IOCompositor {
                frame_tree: SendableFrameTree,
                response_chan: Sender<()>,
                new_constellation_chan: ConstellationChan) {
         response_chan.send(());
 
         self.root_pipeline = Some(frame_tree.pipeline.clone());
 
         // Initialize the new constellation channel by sending it the root window size.
-        let window_size = self.window.size();
-        let window_size = Size2D(window_size.width as uint,
-                                 window_size.height as uint);
+        let window_size = self.window.size().as_uint();
         {
             let ConstellationChan(ref chan) = new_constellation_chan;
-            chan.send(ResizedWindowMsg(window_size));
+            chan.send(ResizedWindowMsg(window_size.to_untyped()));
         }
 
         self.constellation_chan = new_constellation_chan;
     }
 
     fn create_root_compositor_layer_if_necessary(&mut self,
                                                  id: PipelineId,
                                                  layer_id: LayerId,
@@ -419,27 +415,29 @@ impl IOCompositor {
                                                                 scroll_policy))
             }
             None => fail!("Compositor: Received new layer without initialized pipeline"),
         };
 
         self.ask_for_tiles();
     }
 
+    /// The size of the content area in CSS px at the current zoom level
+    fn page_window(&self) -> TypedSize2D<PagePx, f32> {
+        self.window_size.as_f32() / self.device_pixels_per_page_px()
+    }
+
     fn set_layer_page_size(&mut self,
                            pipeline_id: PipelineId,
                            layer_id: LayerId,
                            new_size: Size2D<f32>,
                            epoch: Epoch) {
+        let page_window = self.page_window();
         let (ask, move): (bool, bool) = match self.compositor_layer {
             Some(ref mut layer) => {
-                let window_size = &self.window_size;
-                let world_zoom = self.world_zoom;
-                let page_window = Size2D(window_size.width as f32 / world_zoom,
-                                         window_size.height as f32 / world_zoom);
                 layer.resize(pipeline_id, layer_id, new_size, page_window, epoch);
                 let move = self.fragment_point.take().map_or(false, |point| {
                     layer.move(pipeline_id, layer_id, point, page_window)
                 });
 
                 (true, move)
             }
             None => (false, false)
@@ -498,23 +496,19 @@ impl IOCompositor {
         // TODO: Recycle the old buffers; send them back to the renderer to reuse if
         // it wishes.
     }
 
     fn scroll_fragment_to_point(&mut self,
                                 pipeline_id: PipelineId,
                                 layer_id: LayerId,
                                 point: Point2D<f32>) {
-        let world_zoom = self.world_zoom;
-        let page_window = Size2D(self.window_size.width as f32 / world_zoom,
-                                 self.window_size.height as f32 / world_zoom);
-
+        let page_window = self.page_window();
         let (ask, move): (bool, bool) = match self.compositor_layer {
             Some(ref mut layer) if layer.pipeline.id == pipeline_id && !layer.hidden => {
-
                 (true, layer.move(pipeline_id, layer_id, point, page_window))
             }
             Some(_) | None => {
                 self.fragment_point = Some(point);
 
                 (false, false)
             }
         };
@@ -576,95 +570,120 @@ impl IOCompositor {
                 let ConstellationChan(ref chan) = self.constellation_chan;
                 chan.send(ExitMsg);
                 self.shutting_down = true;
             }
         }
     }
 
     fn on_resize_window_event(&mut self, width: uint, height: uint) {
-        let new_size = Size2D(width, height);
+        let new_size: TypedSize2D<DevicePixel, uint> = TypedSize2D(width, height);
         if self.window_size != new_size {
             debug!("osmain: window resized to {:u}x{:u}", width, height);
             self.window_size = new_size;
             let ConstellationChan(ref chan) = self.constellation_chan;
-            chan.send(ResizedWindowMsg(new_size))
+            chan.send(ResizedWindowMsg(new_size.to_untyped()))
         } else {
             debug!("osmain: dropping window resize since size is still {:u}x{:u}", width, height);
         }
+        // A size change could also mean a resolution change.
+        let new_hidpi_factor = self.window.hidpi_factor();
+        if self.hidpi_factor != new_hidpi_factor {
+            self.hidpi_factor = new_hidpi_factor;
+            self.update_zoom_transform();
+        }
     }
 
     fn on_load_url_window_event(&mut self, url_string: String) {
         debug!("osmain: loading URL `{:s}`", url_string);
         self.load_complete = false;
         let root_pipeline_id = match self.compositor_layer {
             Some(ref layer) => layer.pipeline.id.clone(),
             None => fail!("Compositor: Received LoadUrlWindowEvent without initialized compositor layers"),
         };
 
         let msg = LoadUrlMsg(root_pipeline_id, url::parse_url(url_string.as_slice(), None));
         let ConstellationChan(ref chan) = self.constellation_chan;
         chan.send(msg);
     }
 
     fn on_mouse_window_event_class(&self, mouse_window_event: MouseWindowEvent) {
-        let world_zoom = self.world_zoom;
+        let scale = self.device_pixels_per_page_px();
         let point = match mouse_window_event {
-            MouseWindowClickEvent(_, p) => Point2D(p.x / world_zoom, p.y / world_zoom),
-            MouseWindowMouseDownEvent(_, p) => Point2D(p.x / world_zoom, p.y / world_zoom),
-            MouseWindowMouseUpEvent(_, p) => Point2D(p.x / world_zoom, p.y / world_zoom),
+            MouseWindowClickEvent(_, p) => p / scale,
+            MouseWindowMouseDownEvent(_, p) => p / scale,
+            MouseWindowMouseUpEvent(_, p) => p / scale,
         };
         for layer in self.compositor_layer.iter() {
             layer.send_mouse_event(mouse_window_event, point);
         }
     }
 
-    fn on_mouse_window_move_event_class(&self, cursor: Point2D<f32>) {
+    fn on_mouse_window_move_event_class(&self, cursor: TypedPoint2D<DevicePixel, f32>) {
+        let scale = self.device_pixels_per_page_px();
         for layer in self.compositor_layer.iter() {
-            layer.send_mouse_move_event(cursor);
+            layer.send_mouse_move_event(cursor / scale);
         }
     }
 
-    fn on_scroll_window_event(&mut self, delta: Point2D<f32>, cursor: Point2D<i32>) {
-        let world_zoom = self.world_zoom;
+    fn on_scroll_window_event(&mut self,
+                              delta: TypedPoint2D<DevicePixel, f32>,
+                              cursor: TypedPoint2D<DevicePixel, i32>) {
+        let scale = self.device_pixels_per_page_px();
         // TODO: modify delta to snap scroll to pixels.
-        let page_delta = Point2D(delta.x as f32 / world_zoom, delta.y as f32 / world_zoom);
-        let page_cursor: Point2D<f32> = Point2D(cursor.x as f32 / world_zoom,
-                                                cursor.y as f32 / world_zoom);
-        let page_window = Size2D(self.window_size.width as f32 / world_zoom,
-                                 self.window_size.height as f32 / world_zoom);
+        let page_delta = delta / scale;
+        let page_cursor = cursor.as_f32() / scale;
+        let page_window = self.page_window();
         let mut scroll = false;
         for layer in self.compositor_layer.mut_iter() {
             scroll = layer.handle_scroll_event(page_delta, page_cursor, page_window) || scroll;
         }
         self.recomposite_if(scroll);
         self.ask_for_tiles();
     }
 
+    fn device_pixels_per_screen_px(&self) -> ScaleFactor<ScreenPx, DevicePixel, f32> {
+        match self.opts.device_pixels_per_px {
+            Some(device_pixels_per_px) => device_pixels_per_px,
+            None => match self.opts.output_file {
+                Some(_) => ScaleFactor(1.0),
+                None => self.hidpi_factor
+            }
+        }
+    }
+
+    fn device_pixels_per_page_px(&self) -> ScaleFactor<PagePx, DevicePixel, f32> {
+        self.world_zoom * self.device_pixels_per_screen_px()
+    }
+
+    fn update_zoom_transform(&mut self) {
+        let scale = self.device_pixels_per_page_px();
+        self.root_layer.common.borrow_mut().set_transform(identity().scale(scale.get(), scale.get(), 1f32));
+    }
+
     fn on_zoom_window_event(&mut self, magnification: f32) {
         self.zoom_action = true;
         self.zoom_time = precise_time_s();
         let old_world_zoom = self.world_zoom;
-        let window_size = &self.window_size;
+        let window_size = self.window_size.as_f32();
 
         // Determine zoom amount
-        self.world_zoom = (self.world_zoom * magnification).max(1.0);
+        self.world_zoom = ScaleFactor((self.world_zoom.get() * magnification).max(1.0));
         let world_zoom = self.world_zoom;
 
-        {
-            self.root_layer.common.borrow_mut().set_transform(identity().scale(world_zoom, world_zoom, 1f32));
-        }
+        self.update_zoom_transform();
 
         // Scroll as needed
-        let page_delta = Point2D(window_size.width as f32 * (1.0 / world_zoom - 1.0 / old_world_zoom) * 0.5,
-                                 window_size.height as f32 * (1.0 / world_zoom - 1.0 / old_world_zoom) * 0.5);
+        let page_delta = TypedPoint2D(
+            window_size.width.get() * (world_zoom.inv() - old_world_zoom.inv()).get() * 0.5,
+            window_size.height.get() * (world_zoom.inv() - old_world_zoom.inv()).get() * 0.5);
         // TODO: modify delta to snap scroll to pixels.
-        let page_cursor = Point2D(-1f32, -1f32); // Make sure this hits the base layer
-        let page_window = Size2D(window_size.width as f32 / world_zoom,
-                                 window_size.height as f32 / world_zoom);
+        let page_cursor = TypedPoint2D(-1f32, -1f32); // Make sure this hits the base layer
+        let page_window = self.page_window();
+
         for layer in self.compositor_layer.mut_iter() {
             layer.handle_scroll_event(page_delta, page_cursor, page_window);
         }
 
         self.recomposite = true;
     }
 
     fn on_navigation_window_event(&self, direction: WindowNavigateMsg) {
@@ -673,38 +692,37 @@ impl IOCompositor {
             windowing::Back => constellation_msg::Back,
         };
         let ConstellationChan(ref chan) = self.constellation_chan;
         chan.send(NavigateMsg(direction))
     }
 
     /// Get BufferRequests from each layer.
     fn ask_for_tiles(&mut self) {
-        let world_zoom = self.world_zoom;
-        let window_size_page = Size2D(self.window_size.width as f32 / world_zoom,
-                                      self.window_size.height as f32 / world_zoom);
+        let scale = self.device_pixels_per_page_px();
+        let page_window = self.page_window();
         for layer in self.compositor_layer.mut_iter() {
             if !layer.hidden {
-                let rect = Rect(Point2D(0f32, 0f32), window_size_page);
+                let rect = Rect(Point2D(0f32, 0f32), page_window.to_untyped());
                 let recomposite = layer.get_buffer_request(&self.graphics_context,
                                                            rect,
-                                                           world_zoom) ||
+                                                           scale.get()) ||
                                   self.recomposite;
                 self.recomposite = recomposite;
             } else {
                 debug!("Compositor: root layer is hidden!");
             }
         }
     }
 
     fn composite(&mut self) {
         profile(time::CompositingCategory, self.profiler_chan.clone(), || {
             debug!("compositor: compositing");
             // Adjust the layer dimensions as necessary to correspond to the size of the window.
-            self.scene.size = self.window.size();
+            self.scene.size = self.window.size().to_untyped();
             // Render the scene.
             match self.compositor_layer {
                 Some(ref mut layer) => {
                     self.scene.background_color.r = layer.unrendered_color.r;
                     self.scene.background_color.g = layer.unrendered_color.g;
                     self.scene.background_color.b = layer.unrendered_color.b;
                     self.scene.background_color.a = layer.unrendered_color.a;
                 }
@@ -712,17 +730,17 @@ impl IOCompositor {
             }
             rendergl::render_scene(self.context, &self.scene);
         });
 
         // Render to PNG. We must read from the back buffer (ie, before
         // self.window.present()) as OpenGL ES 2 does not have glReadBuffer().
         if self.load_complete && self.ready_state == FinishedLoading
             && self.opts.output_file.is_some() {
-            let (width, height) = (self.window_size.width as uint, self.window_size.height as uint);
+            let (width, height) = (self.window_size.width.get(), self.window_size.height.get());
             let path = from_str::<Path>(self.opts.output_file.get_ref().as_slice()).unwrap();
             let mut pixels = gl2::read_pixels(0, 0,
                                               width as gl2::GLsizei,
                                               height as gl2::GLsizei,
                                               gl2::RGB, gl2::UNSIGNED_BYTE);
             // flip image vertically (texture is upside down)
             let orig_pixels = pixels.clone();
             let stride = width * 3;
--- a/servo/src/components/main/compositing/compositor_layer.rs
+++ b/servo/src/components/main/compositing/compositor_layer.rs
@@ -3,31 +3,33 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use compositing::quadtree::{Quadtree, Normal, Hidden};
 use pipeline::CompositionPipeline;
 use windowing::{MouseWindowEvent, MouseWindowClickEvent, MouseWindowMouseDownEvent};
 use windowing::{MouseWindowMouseUpEvent};
 
 use azure::azure_hl::Color;
+use geom::length::Length;
 use geom::matrix::identity;
-use geom::point::Point2D;
-use geom::rect::Rect;
-use geom::size::Size2D;
+use geom::point::{Point2D, TypedPoint2D};
+use geom::rect::{Rect, TypedRect};
+use geom::size::{Size2D, TypedSize2D};
 use gfx::render_task::{ReRenderMsg, UnusedBufferMsg};
 use gfx;
 use layers::layers::{ContainerLayerKind, ContainerLayer, Flip, NoFlip, TextureLayer};
 use layers::layers::TextureLayerKind;
 use layers::platform::surface::{NativeCompositingGraphicsContext, NativeSurfaceMethods};
 use layers::texturegl::{Texture, TextureTarget};
 use script::dom::event::{ClickEvent, MouseDownEvent, MouseMoveEvent, MouseUpEvent};
 use script::script_task::{ScriptChan, SendEventMsg};
 use servo_msg::compositor_msg::{Epoch, FixedPosition, LayerBuffer, LayerBufferSet, LayerId};
 use servo_msg::compositor_msg::{ScrollPolicy, Tile};
 use servo_msg::constellation_msg::PipelineId;
+use servo_util::geometry::PagePx;
 use std::rc::Rc;
 
 #[cfg(target_os="macos")]
 #[cfg(target_os="android")]
 use layers::layers::VerticalFlip;
 #[cfg(not(target_os="macos"))]
 use layers::texturegl::TextureTarget2D;
 #[cfg(target_os="macos")]
@@ -54,17 +56,17 @@ pub struct CompositorLayer {
 
     /// The size of the underlying page in page coordinates. This is an option
     /// because we may not know the size of the page until layout is finished completely.
     /// if we have no size yet, the layer is hidden until a size message is recieved.
     pub page_size: Option<Size2D<f32>>,
 
     /// The offset of the page due to scrolling. (0,0) is when the window sees the
     /// top left corner of the page.
-    pub scroll_offset: Point2D<f32>,
+    pub scroll_offset: TypedPoint2D<PagePx, f32>,
 
     /// This layer's children. These could be iframes or any element which
     /// differs in scroll behavior from its parent. Each is associated with a
     /// ContainerLayer which determines its position relative to its parent and
     /// clipping rect. Children are stored in the order in which they are drawn.
     pub children: Vec<CompositorLayerChild>,
 
     /// This layer's quadtree. This is where all buffers are stored for this layer.
@@ -164,17 +166,17 @@ impl CompositorLayer {
            wants_scroll_events: WantsScrollEventsFlag,
            scroll_policy: ScrollPolicy)
            -> CompositorLayer {
         CompositorLayer {
             pipeline: pipeline,
             id: layer_id,
             bounds: bounds,
             page_size: page_size,
-            scroll_offset: Point2D(0f32, 0f32),
+            scroll_offset: TypedPoint2D(0f32, 0f32),
             children: vec!(),
             quadtree: match page_size {
                 None => NoTree(tile_size, Some(MAX_TILE_MEMORY_PER_LAYER)),
                 Some(page_size) => {
                     Tree(Quadtree::new(Size2D(page_size.width as uint, page_size.height as uint),
                                        tile_size,
                                        Some(MAX_TILE_MEMORY_PER_LAYER)))
                 }
@@ -197,17 +199,17 @@ impl CompositorLayer {
                     tile_size: uint,
                     cpu_painting: bool)
                     -> CompositorLayer {
         CompositorLayer {
             pipeline: pipeline,
             id: LayerId::null(),
             bounds: Rect(Point2D(0f32, 0f32), page_size),
             page_size: Some(page_size),
-            scroll_offset: Point2D(0f32, 0f32),
+            scroll_offset: TypedPoint2D(0f32, 0f32),
             children: vec!(),
             quadtree: NoTree(tile_size, Some(MAX_TILE_MEMORY_PER_LAYER)),
             root_layer: Rc::new(ContainerLayer()),
             hidden: false,
             epoch: Epoch(0),
             wants_scroll_events: WantsScrollEvents,
             scroll_policy: FixedPosition,
             cpu_painting: cpu_painting,
@@ -280,19 +282,19 @@ impl CompositorLayer {
         true
     }
 
     /// Move the layer's descendants that don't want scroll events and scroll by a relative
     /// specified amount in page coordinates. This also takes in a cursor position to see if the
     /// mouse is over child layers first. If a layer successfully scrolled, returns true; otherwise
     /// returns false, so a parent layer can scroll instead.
     pub fn handle_scroll_event(&mut self,
-                               delta: Point2D<f32>,
-                               cursor: Point2D<f32>,
-                               window_size: Size2D<f32>)
+                               delta: TypedPoint2D<PagePx, f32>,
+                               cursor: TypedPoint2D<PagePx, f32>,
+                               window_size: TypedSize2D<PagePx, f32>)
                                -> bool {
         // If this layer is hidden, neither it nor its children will scroll.
         if self.hidden {
             return false
         }
 
         // If this layer doesn't want scroll events, neither it nor its children can handle scroll
         // events.
@@ -303,16 +305,17 @@ impl CompositorLayer {
         // Allow children to scroll.
         let cursor = cursor - self.scroll_offset;
         for child in self.children.mut_iter() {
             match *child.container.scissor.borrow() {
                 None => {
                     error!("CompositorLayer: unable to perform cursor hit test for layer");
                 }
                 Some(rect) => {
+                    let rect: TypedRect<PagePx, f32> = Rect::from_untyped(&rect);
                     if cursor.x >= rect.origin.x && cursor.x < rect.origin.x + rect.size.width
                         && cursor.y >= rect.origin.y && cursor.y < rect.origin.y + rect.size.height
                         && child.child.handle_scroll_event(delta,
                                                            cursor - rect.origin,
                                                            rect.size) {
                         return true
                     }
                 }
@@ -324,22 +327,27 @@ impl CompositorLayer {
         let old_origin = self.scroll_offset;
         self.scroll_offset = self.scroll_offset + delta;
 
         // bounds checking
         let page_size = match self.page_size {
             Some(size) => size,
             None => fail!("CompositorLayer: tried to scroll with no page size set"),
         };
+
+        let window_size = window_size.to_untyped();
+        let scroll_offset = self.scroll_offset.to_untyped();
+
         let min_x = (window_size.width - page_size.width).min(0.0);
-        self.scroll_offset.x = self.scroll_offset.x.clamp(&min_x, &0.0);
+        self.scroll_offset.x = Length(scroll_offset.x.clamp(&min_x, &0.0));
+
         let min_y = (window_size.height - page_size.height).min(0.0);
-        self.scroll_offset.y = self.scroll_offset.y.clamp(&min_y, &0.0);
+        self.scroll_offset.y = Length(scroll_offset.y.clamp(&min_y, &0.0));
 
-        if old_origin - self.scroll_offset == Point2D(0f32, 0f32) {
+        if old_origin - self.scroll_offset == TypedPoint2D(0f32, 0f32) {
             return false
         }
 
         self.scroll(self.scroll_offset)
     }
 
     #[allow(dead_code)]
     fn dump_layer_tree(&self, layer: Rc<ContainerLayer>, indent: String) {
@@ -353,69 +361,70 @@ impl CompositorLayer {
                     println!("{}  (texture layer)", indent);
                 }
             }
         }
     }
 
     /// Actually scrolls the descendants of a layer that scroll. This is called by
     /// `handle_scroll_event` above when it determines that a layer wants to scroll.
-    fn scroll(&mut self, scroll_offset: Point2D<f32>) -> bool {
+    fn scroll(&mut self, scroll_offset: TypedPoint2D<PagePx, f32>) -> bool {
         let mut result = false;
 
         // Only scroll this layer if it's not fixed-positioned.
         if self.scroll_policy != FixedPosition {
             // Scroll this layer!
             self.scroll_offset = scroll_offset;
 
             self.root_layer.common.borrow_mut().set_transform(
-                identity().translate(self.scroll_offset.x, self.scroll_offset.y, 0.0));
+                identity().translate(self.scroll_offset.x.get(), self.scroll_offset.y.get(), 0.0));
 
             result = true
         }
 
         for kid_holder in self.children.mut_iter() {
             result = kid_holder.child.scroll(scroll_offset) || result;
         }
 
         result
     }
 
     // Takes in a MouseWindowEvent, determines if it should be passed to children, and
     // sends the event off to the appropriate pipeline. NB: the cursor position is in
     // page coordinates.
-    pub fn send_mouse_event(&self, event: MouseWindowEvent, cursor: Point2D<f32>) {
+    pub fn send_mouse_event(&self, event: MouseWindowEvent, cursor: TypedPoint2D<PagePx, f32>) {
         let cursor = cursor - self.scroll_offset;
         for child in self.children.iter().filter(|&x| !x.child.hidden) {
             match *child.container.scissor.borrow() {
                 None => {
                     error!("CompositorLayer: unable to perform cursor hit test for layer");
                 }
                 Some(rect) => {
+                    let rect: TypedRect<PagePx, f32> = Rect::from_untyped(&rect);
                     if cursor.x >= rect.origin.x && cursor.x < rect.origin.x + rect.size.width
                         && cursor.y >= rect.origin.y && cursor.y < rect.origin.y + rect.size.height {
                         child.child.send_mouse_event(event, cursor - rect.origin);
                         return;
                     }
                 }
             }
         }
 
         // This mouse event is mine!
         let message = match event {
-            MouseWindowClickEvent(button, _) => ClickEvent(button, cursor),
-            MouseWindowMouseDownEvent(button, _) => MouseDownEvent(button, cursor),
-            MouseWindowMouseUpEvent(button, _) => MouseUpEvent(button, cursor),
+            MouseWindowClickEvent(button, _) => ClickEvent(button, cursor.to_untyped()),
+            MouseWindowMouseDownEvent(button, _) => MouseDownEvent(button, cursor.to_untyped()),
+            MouseWindowMouseUpEvent(button, _) => MouseUpEvent(button, cursor.to_untyped()),
         };
         let ScriptChan(ref chan) = self.pipeline.script_chan;
         let _ = chan.send_opt(SendEventMsg(self.pipeline.id.clone(), message));
     }
 
-    pub fn send_mouse_move_event(&self, cursor: Point2D<f32>) {
-        let message = MouseMoveEvent(cursor);
+    pub fn send_mouse_move_event(&self, cursor: TypedPoint2D<PagePx, f32>) {
+        let message = MouseMoveEvent(cursor.to_untyped());
         let ScriptChan(ref chan) = self.pipeline.script_chan;
         let _ = chan.send_opt(SendEventMsg(self.pipeline.id.clone(), message));
     }
 
     // Given the current window size, determine which tiles need to be (re-)rendered and sends them
     // off the the appropriate renderer. Returns true if and only if the scene should be repainted.
     pub fn get_buffer_request(&mut self,
                               graphics_context: &NativeCompositingGraphicsContext,
@@ -448,18 +457,19 @@ impl CompositorLayer {
         if redisplay {
             self.build_layer_tree(graphics_context);
         }
 
         let transform = |x: &mut CompositorLayerChild| -> bool {
             match *x.container.scissor.borrow() {
                 Some(scissor) => {
                     let mut new_rect = window_rect;
-                    new_rect.origin.x = new_rect.origin.x - x.child.scroll_offset.x;
-                    new_rect.origin.y = new_rect.origin.y - x.child.scroll_offset.y;
+                    let offset = x.child.scroll_offset.to_untyped();
+                    new_rect.origin.x = new_rect.origin.x - offset.x;
+                    new_rect.origin.y = new_rect.origin.y - offset.y;
                     match new_rect.intersection(&scissor) {
                         Some(new_rect) => {
                             // Child layers act as if they are rendered at (0,0), so we
                             // subtract the layer's (x,y) coords in its containing page
                             // to make the child_rect appear in coordinates local to it.
                             let child_rect = Rect(new_rect.origin.sub(&scissor.origin),
                                                   new_rect.size);
                             x.child.get_buffer_request(graphics_context, child_rect, scale)
@@ -530,17 +540,17 @@ impl CompositorLayer {
 
     // Set the layer's page size. This signals that the renderer is ready for BufferRequests.
     // If the layer is hidden and has a defined clipping rect, unhide it.
     // This method returns false if the specified layer is not found.
     pub fn resize(&mut self,
                   pipeline_id: PipelineId,
                   layer_id: LayerId,
                   new_size: Size2D<f32>,
-                  window_size: Size2D<f32>,
+                  window_size: TypedSize2D<PagePx, f32>,
                   epoch: Epoch)
                   -> bool {
         debug!("compositor_layer: starting resize()");
         if self.pipeline.id != pipeline_id || self.id != layer_id {
             return self.resize_helper(pipeline_id, layer_id, new_size, epoch)
         }
 
         debug!("compositor_layer: layer found for resize()");
@@ -557,55 +567,58 @@ impl CompositorLayer {
                 self.quadtree = Tree(Quadtree::new(Size2D(new_size.width as uint,
                                                           new_size.height as uint),
                                                    tile_size,
                                                    max_mem))
             }
         }
         // Call scroll for bounds checking if the page shrunk. Use (-1, -1) as the cursor position
         // to make sure the scroll isn't propagated downwards.
-        self.handle_scroll_event(Point2D(0f32, 0f32), Point2D(-1f32, -1f32), window_size);
+        self.handle_scroll_event(TypedPoint2D(0f32, 0f32), TypedPoint2D(-1f32, -1f32), window_size);
         self.hidden = false;
         self.set_occlusions();
         true
     }
 
     pub fn move(&mut self,
                 pipeline_id: PipelineId,
                 layer_id: LayerId,
                 origin: Point2D<f32>,
-                window_size: Size2D<f32>)
+                window_size: TypedSize2D<PagePx, f32>)
                 -> bool {
         // Search children for the right layer to move.
         if self.pipeline.id != pipeline_id || self.id != layer_id {
             return self.children.mut_iter().any(|kid_holder| {
                 kid_holder.child.move(pipeline_id, layer_id, origin, window_size)
             })
         }
 
         if self.wants_scroll_events != WantsScrollEvents {
             return false
         }
 
         // Scroll this layer!
         let old_origin = self.scroll_offset;
-        self.scroll_offset = Point2D(0f32, 0f32) - origin;
+        self.scroll_offset = Point2D::from_untyped(&(origin * -1.0));
 
         // bounds checking
         let page_size = match self.page_size {
             Some(size) => size,
             None => fail!("CompositorLayer: tried to scroll with no page size set"),
         };
+        let window_size = window_size.to_untyped();
+        let scroll_offset = self.scroll_offset.to_untyped();
+
         let min_x = (window_size.width - page_size.width).min(0.0);
-        self.scroll_offset.x = self.scroll_offset.x.clamp(&min_x, &0.0);
+        self.scroll_offset.x = Length(scroll_offset.x.clamp(&min_x, &0.0));
         let min_y = (window_size.height - page_size.height).min(0.0);
-        self.scroll_offset.y = self.scroll_offset.y.clamp(&min_y, &0.0);
+        self.scroll_offset.y = Length(scroll_offset.y.clamp(&min_y, &0.0));
 
         // check to see if we scrolled
-        if old_origin - self.scroll_offset == Point2D(0f32, 0f32) {
+        if old_origin - self.scroll_offset == TypedPoint2D(0f32, 0f32) {
             return false;
         }
 
         self.scroll(self.scroll_offset)
     }
 
     // Returns whether the layer should be vertically flipped.
     #[cfg(target_os="macos")]
@@ -664,19 +677,20 @@ impl CompositorLayer {
                                                             tile_size,
                                                             max_mem))
                     }
                 }
                 match *child_node.container.scissor.borrow() {
                     Some(scissor) => {
                         // Call scroll for bounds checking if the page shrunk. Use (-1, -1) as the
                         // cursor position to make sure the scroll isn't propagated downwards.
-                        child.handle_scroll_event(Point2D(0f32, 0f32),
-                                                  Point2D(-1f32, -1f32),
-                                                  scissor.size);
+                        let size: TypedSize2D<PagePx, f32> = Size2D::from_untyped(&scissor.size);
+                        child.handle_scroll_event(TypedPoint2D(0f32, 0f32),
+                                                  TypedPoint2D(-1f32, -1f32),
+                                                  size);
                         child.hidden = false;
                     }
                     None => {} // Nothing to do
                 }
                 true
             }
             None => false,
         };
--- a/servo/src/components/main/platform/common/glfw_windowing.rs
+++ b/servo/src/components/main/platform/common/glfw_windowing.rs
@@ -14,20 +14,22 @@ use windowing::{Forward, Back};
 use alert::{Alert, AlertMethods};
 use libc::{exit, c_int};
 use time;
 use time::Timespec;
 use std::cell::{Cell, RefCell};
 use std::comm::Receiver;
 use std::rc::Rc;
 
-use geom::point::Point2D;
-use geom::size::Size2D;
+use geom::point::{Point2D, TypedPoint2D};
+use geom::scale_factor::ScaleFactor;
+use geom::size::TypedSize2D;
 use servo_msg::compositor_msg::{IdleRenderState, RenderState, RenderingRenderState};
 use servo_msg::compositor_msg::{FinishedLoading, Blank, Loading, PerformingLayout, ReadyState};
+use servo_util::geometry::{ScreenPx, DevicePixel};
 
 use glfw;
 use glfw::Context;
 
 /// A structure responsible for setting up and tearing down the entire windowing system.
 pub struct Application {
     pub glfw: glfw::Glfw,
 }
@@ -139,19 +141,19 @@ impl WindowMethods<Application> for Wind
         window.glfw_window.set_scroll_polling(true);
 
         let wrapped_window = Rc::new(window);
 
         wrapped_window
     }
 
     /// Returns the size of the window.
-    fn size(&self) -> Size2D<f32> {
+    fn size(&self) -> TypedSize2D<DevicePixel, f32> {
         let (width, height) = self.glfw_window.get_framebuffer_size();
-        Size2D(width as f32, height as f32)
+        TypedSize2D(width as f32, height as f32)
     }
 
     /// Presents the window to the screen (perhaps by page flipping).
     fn present(&self) {
         self.glfw_window.swap_buffers();
     }
 
     fn recv(&self) -> WindowEvent {
@@ -188,20 +190,20 @@ impl WindowMethods<Application> for Wind
             // page loaded
             self.event_queue.borrow_mut().push(FinishedWindowEvent);
         }
 
         self.render_state.set(render_state);
         self.update_window_title()
     }
 
-    fn hidpi_factor(&self) -> f32 {
+    fn hidpi_factor(&self) -> ScaleFactor<ScreenPx, DevicePixel, f32> {
         let (backing_size, _) = self.glfw_window.get_framebuffer_size();
         let (window_size, _) = self.glfw_window.get_size();
-        (backing_size as f32) / (window_size as f32)
+        ScaleFactor((backing_size as f32) / (window_size as f32))
     }
 }
 
 impl Window {
     fn handle_window_event(&self, window: &glfw::Window, event: glfw::WindowEvent) {
         match event {
             glfw::KeyEvent(key, _, action, mods) => {
                 if action == glfw::Press {
@@ -222,31 +224,33 @@ impl Window {
                 let hidpi = (backing_size as f32) / (window_size as f32);
                 let x = x as f32 * hidpi;
                 let y = y as f32 * hidpi;
                 if button == glfw::MouseButtonLeft || button == glfw::MouseButtonRight {
                     self.handle_mouse(button, action, x as i32, y as i32);
                 }
             },
             glfw::CursorPosEvent(xpos, ypos) => {
-                self.event_queue.borrow_mut().push(MouseWindowMoveEventClass(Point2D(xpos as f32, ypos as f32)));
+                self.event_queue.borrow_mut().push(
+                    MouseWindowMoveEventClass(TypedPoint2D(xpos as f32, ypos as f32)));
             },
             glfw::ScrollEvent(xpos, ypos) => {
                 let dx = (xpos as f32) * 30.0;
                 let dy = (ypos as f32) * 30.0;
 
                 let (x, y) = window.get_cursor_pos();
                 //handle hidpi displays, since GLFW returns non-hi-def coordinates.
                 let (backing_size, _) = window.get_framebuffer_size();
                 let (window_size, _) = window.get_size();
                 let hidpi = (backing_size as f32) / (window_size as f32);
                 let x = x as f32 * hidpi;
                 let y = y as f32 * hidpi;
 
-                self.event_queue.borrow_mut().push(ScrollWindowEvent(Point2D(dx, dy), Point2D(x as i32, y as i32)));
+                self.event_queue.borrow_mut().push(ScrollWindowEvent(TypedPoint2D(dx, dy),
+                                                                     TypedPoint2D(x as i32, y as i32)));
             },
             _ => {}
         }
     }
 
     /// Helper function to set the window title in accordance with the ready state.
     fn update_window_title(&self) {
         let now = time::get_time();
@@ -302,34 +306,34 @@ impl Window {
     /// Helper function to handle a click
     fn handle_mouse(&self, button: glfw::MouseButton, action: glfw::Action, x: c_int, y: c_int) {
         // FIXME(tkuehn): max pixel dist should be based on pixel density
         let max_pixel_dist = 10f64;
         let event = match action {
             glfw::Press => {
                 self.mouse_down_point.set(Point2D(x, y));
                 self.mouse_down_button.set(Some(button));
-                MouseWindowMouseDownEvent(button as uint, Point2D(x as f32, y as f32))
+                MouseWindowMouseDownEvent(button as uint, TypedPoint2D(x as f32, y as f32))
             }
             glfw::Release => {
                 match self.mouse_down_button.get() {
                     None => (),
                     Some(but) if button == but => {
                         let pixel_dist = self.mouse_down_point.get() - Point2D(x, y);
                         let pixel_dist = ((pixel_dist.x * pixel_dist.x +
                                            pixel_dist.y * pixel_dist.y) as f64).sqrt();
                         if pixel_dist < max_pixel_dist {
                             let click_event = MouseWindowClickEvent(button as uint,
-                                                                    Point2D(x as f32, y as f32));
+                                                                    TypedPoint2D(x as f32, y as f32));
                             self.event_queue.borrow_mut().push(MouseWindowEventClass(click_event));
                         }
                     }
                     Some(_) => (),
                 }
-                MouseWindowMouseUpEvent(button as uint, Point2D(x as f32, y as f32))
+                MouseWindowMouseUpEvent(button as uint, TypedPoint2D(x as f32, y as f32))
             }
             _ => fail!("I cannot recognize the type of mouse action that occured. :-(")
         };
         self.event_queue.borrow_mut().push(MouseWindowEventClass(event));
     }
 
     /// Helper function to pop up an alert box prompting the user to load a URL.
     fn load_url(&self) {
--- a/servo/src/components/main/platform/common/glut_windowing.rs
+++ b/servo/src/components/main/platform/common/glut_windowing.rs
@@ -9,20 +9,22 @@ use windowing::{IdleWindowEvent, ResizeW
 use windowing::{ScrollWindowEvent, ZoomWindowEvent, NavigationWindowEvent, FinishedWindowEvent};
 use windowing::{MouseWindowClickEvent, MouseWindowMouseDownEvent, MouseWindowMouseUpEvent};
 use windowing::{Forward, Back};
 
 use alert::{Alert, AlertMethods};
 use libc::{c_int, c_uchar};
 use std::cell::{Cell, RefCell};
 use std::rc::Rc;
-use geom::point::Point2D;
-use geom::size::Size2D;
+use geom::point::{Point2D, TypedPoint2D};
+use geom::scale_factor::ScaleFactor;
+use geom::size::TypedSize2D;
 use servo_msg::compositor_msg::{IdleRenderState, RenderState, RenderingRenderState};
 use servo_msg::compositor_msg::{FinishedLoading, Blank, ReadyState};
+use servo_util::geometry::{ScreenPx, DevicePixel};
 
 use glut::glut::{ACTIVE_SHIFT, DOUBLE, WindowHeight};
 use glut::glut::WindowWidth;
 use glut::glut;
 
 // static THROBBER: [char, ..8] = [ '⣾', '⣽', '⣻', '⢿', '⡿', '⣟', '⣯', '⣷' ];
 
 /// A structure responsible for setting up and tearing down the entire windowing system.
@@ -113,39 +115,43 @@ impl WindowMethods<Application> for Wind
             fn call(&self, button: c_int, state: c_int, x: c_int, y: c_int) {
                 if button < 3 {
                     let tmp = local_window();
                     tmp.handle_mouse(button, state, x, y);
                 } else {
                     match button {
                         3 => {
                             let tmp = local_window();
-                            tmp.event_queue.borrow_mut().push(ScrollWindowEvent(Point2D(0.0, 5.0 as f32), Point2D(0.0 as i32, 5.0 as i32)));
+                            tmp.event_queue.borrow_mut().push(ScrollWindowEvent(
+                                    TypedPoint2D(0.0, 5.0 as f32),
+                                    TypedPoint2D(0.0 as i32, 5.0 as i32)));
                         },
                         4 => {
                             let tmp = local_window();
-                            tmp.event_queue.borrow_mut().push(ScrollWindowEvent(Point2D(0.0, -5.0 as f32), Point2D(0.0 as i32, -5.0 as i32)));
+                            tmp.event_queue.borrow_mut().push(ScrollWindowEvent(
+                                    TypedPoint2D(0.0, -5.0 as f32),
+                                    TypedPoint2D(0.0 as i32, -5.0 as i32)));
                         },
                         _ => {}
                     }
                 }
             }
         }
         glut::mouse_func(box MouseCallbackState);
 
         let wrapped_window = Rc::new(window);
 
         install_local_window(wrapped_window.clone());
 
         wrapped_window
     }
 
     /// Returns the size of the window.
-    fn size(&self) -> Size2D<f32> {
-        Size2D(glut::get(WindowWidth) as f32, glut::get(WindowHeight) as f32)
+    fn size(&self) -> TypedSize2D<DevicePixel, f32> {
+        TypedSize2D(glut::get(WindowWidth) as f32, glut::get(WindowHeight) as f32)
     }
 
     /// Presents the window to the screen (perhaps by page flipping).
     fn present(&self) {
         glut::swap_buffers();
     }
 
     fn recv(&self) -> WindowEvent {
@@ -174,19 +180,19 @@ impl WindowMethods<Application> for Wind
             self.event_queue.borrow_mut().push(FinishedWindowEvent);
         }
 
         self.render_state.set(render_state);
         //FIXME: set_window_title causes crash with Android version of freeGLUT. Temporarily blocked.
         //self.update_window_title()
     }
 
-    fn hidpi_factor(&self) -> f32 {
+    fn hidpi_factor(&self) -> ScaleFactor<ScreenPx, DevicePixel, f32> {
         //FIXME: Do nothing in GLUT now.
-    1f32
+        ScaleFactor(1.0)
     }
 }
 
 impl Window {
     /// Helper function to set the window title in accordance with the ready state.
     // fn update_window_title(&self) {
     //     let throbber = THROBBER[self.throbber_frame];
     //     match self.ready_state {
@@ -213,18 +219,20 @@ impl Window {
     /// Helper function to handle keyboard events.
     fn handle_key(&self, key: u8) {
         debug!("got key: {}", key);
         let modifiers = glut::get_modifiers();
         match key {
             42 => self.load_url(),
             43 => self.event_queue.borrow_mut().push(ZoomWindowEvent(1.1)),
             45 => self.event_queue.borrow_mut().push(ZoomWindowEvent(0.909090909)),
-            56 => self.event_queue.borrow_mut().push(ScrollWindowEvent(Point2D(0.0, 5.0 as f32), Point2D(0.0 as i32, 5.0 as i32))),
-            50 => self.event_queue.borrow_mut().push(ScrollWindowEvent(Point2D(0.0, -5.0 as f32), Point2D(0.0 as i32, -5.0 as i32))),
+            56 => self.event_queue.borrow_mut().push(ScrollWindowEvent(TypedPoint2D(0.0, 5.0 as f32),
+                                                                       TypedPoint2D(0.0 as i32, 5.0 as i32))),
+            50 => self.event_queue.borrow_mut().push(ScrollWindowEvent(TypedPoint2D(0.0, -5.0 as f32),
+                                                                       TypedPoint2D(0.0 as i32, -5.0 as i32))),
             127 => {
                 if (modifiers & ACTIVE_SHIFT) != 0 {
                     self.event_queue.borrow_mut().push(NavigationWindowEvent(Forward));
                 }
                 else {
                     self.event_queue.borrow_mut().push(NavigationWindowEvent(Back));
                 }
             }
@@ -235,30 +243,30 @@ impl Window {
     /// Helper function to handle a click
     fn handle_mouse(&self, button: c_int, state: c_int, x: c_int, y: c_int) {
         // FIXME(tkuehn): max pixel dist should be based on pixel density
         let max_pixel_dist = 10f32;
         let event = match state {
             glut::MOUSE_DOWN => {
                 self.mouse_down_point.set(Point2D(x, y));
                 self.mouse_down_button.set(button);
-                MouseWindowMouseDownEvent(button as uint, Point2D(x as f32, y as f32))
+                MouseWindowMouseDownEvent(button as uint, TypedPoint2D(x as f32, y as f32))
             }
             glut::MOUSE_UP => {
                 if self.mouse_down_button.get() == button {
                     let pixel_dist = self.mouse_down_point.get() - Point2D(x, y);
                     let pixel_dist = ((pixel_dist.x * pixel_dist.x +
                                        pixel_dist.y * pixel_dist.y) as f32).sqrt();
                     if pixel_dist < max_pixel_dist {
                         let click_event = MouseWindowClickEvent(button as uint,
-                                                           Point2D(x as f32, y as f32));
+                                                           TypedPoint2D(x as f32, y as f32));
                         self.event_queue.borrow_mut().push(MouseWindowEventClass(click_event));
                     }
                 }
-                MouseWindowMouseUpEvent(button as uint, Point2D(x as f32, y as f32))
+                MouseWindowMouseUpEvent(button as uint, TypedPoint2D(x as f32, y as f32))
             }
             _ => fail!("I cannot recognize the type of mouse action that occured. :-(")
         };
         self.event_queue.borrow_mut().push(MouseWindowEventClass(event));
     }
 
     /// Helper function to pop up an alert box prompting the user to load a URL.
     fn load_url(&self) {
--- a/servo/src/components/main/windowing.rs
+++ b/servo/src/components/main/windowing.rs
@@ -1,23 +1,25 @@
 /* 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/. */
 
 //! Abstract windowing methods. The concrete implementations of these can be found in `platform/`.
 
-use geom::point::Point2D;
-use geom::size::Size2D;
+use geom::point::TypedPoint2D;
+use geom::scale_factor::ScaleFactor;
+use geom::size::TypedSize2D;
 use servo_msg::compositor_msg::{ReadyState, RenderState};
+use servo_util::geometry::{ScreenPx, DevicePixel};
 use std::rc::Rc;
 
 pub enum MouseWindowEvent {
-    MouseWindowClickEvent(uint, Point2D<f32>),
-    MouseWindowMouseDownEvent(uint, Point2D<f32>),
-    MouseWindowMouseUpEvent(uint, Point2D<f32>),
+    MouseWindowClickEvent(uint, TypedPoint2D<DevicePixel, f32>),
+    MouseWindowMouseDownEvent(uint, TypedPoint2D<DevicePixel, f32>),
+    MouseWindowMouseUpEvent(uint, TypedPoint2D<DevicePixel, f32>),
 }
 
 pub enum WindowNavigateMsg {
     Forward,
     Back,
 }
 
 /// Events that the windowing system sends to Servo.
@@ -31,19 +33,19 @@ pub enum WindowEvent {
     RefreshWindowEvent,
     /// Sent when the window is resized.
     ResizeWindowEvent(uint, uint),
     /// Sent when a new URL is to be loaded.
     LoadUrlWindowEvent(String),
     /// Sent when a mouse hit test is to be performed.
     MouseWindowEventClass(MouseWindowEvent),
     /// Sent when a mouse move.
-    MouseWindowMoveEventClass(Point2D<f32>),
+    MouseWindowMoveEventClass(TypedPoint2D<DevicePixel, f32>),
     /// Sent when the user scrolls. Includes the current cursor position.
-    ScrollWindowEvent(Point2D<f32>, Point2D<i32>),
+    ScrollWindowEvent(TypedPoint2D<DevicePixel, f32>, TypedPoint2D<DevicePixel, i32>),
     /// Sent when the user zooms.
     ZoomWindowEvent(f32),
     /// Sent when the user uses chrome navigation (i.e. backspace or shift-backspace).
     NavigationWindowEvent(WindowNavigateMsg),
     /// Sent when rendering is finished.
     FinishedWindowEvent,
     /// Sent when the user quits the application
     QuitWindowEvent,
@@ -53,24 +55,24 @@ pub enum WindowEvent {
 pub trait ApplicationMethods {
     fn new() -> Self;
 }
 
 pub trait WindowMethods<A> {
     /// Creates a new window.
     fn new(app: &A, is_foreground: bool) -> Rc<Self>;
     /// Returns the size of the window.
-    fn size(&self) -> Size2D<f32>;
+    fn size(&self) -> TypedSize2D<DevicePixel, f32>;
     /// Presents the window to the screen (perhaps by page flipping).
     fn present(&self);
 
     /// Spins the event loop and returns the next event.
     fn recv(&self) -> WindowEvent;
 
     /// Sets the ready state of the current page.
     fn set_ready_state(&self, ready_state: ReadyState);
     /// Sets the render state of the current page.
     fn set_render_state(&self, render_state: RenderState);
 
     /// Returns the hidpi factor of the monitor.
-    fn hidpi_factor(&self) -> f32;
+    fn hidpi_factor(&self) -> ScaleFactor<ScreenPx, DevicePixel, f32>;
 }
 
--- a/servo/src/components/util/geometry.rs
+++ b/servo/src/components/util/geometry.rs
@@ -5,19 +5,51 @@
 use geom::point::Point2D;
 use geom::rect::Rect;
 use geom::size::Size2D;
 
 use std::default::Default;
 use std::num::{NumCast, One, Zero};
 use std::fmt;
 
+// Units for use with geom::length and geom::scale_factor.
+
+/// One hardware pixel.
+///
+/// This unit corresponds to the smallest addressable element of the display hardware.
+pub enum DevicePixel {}
+
+/// A normalized "pixel" at the default resolution for the display.
+///
+/// Like the CSS "px" unit, the exact physical size of this unit may vary between devices, but it
+/// should approximate a device-independent reference length.  This unit corresponds to Android's
+/// "density-independent pixel" (dip), Mac OS X's "point", and Windows "device-independent pixel."
+///
+/// The relationship between DevicePixel and ScreenPx is defined by the OS.  On most low-dpi
+/// screens, one ScreenPx is equal to one DevicePixel.  But on high-density screens it can be
+/// some larger number.  For example, by default on Apple "retina" displays, one ScreenPx equals
+/// two DevicePixels.  On Android "MDPI" displays, one ScreenPx equals 1.5 device pixels.
+///
+/// The ratio between ScreenPx and DevicePixel for a given display be found by calling
+/// `servo::windowing::WindowMethods::hidpi_factor`.
+pub enum ScreenPx {}
+
+/// One CSS "px" in the root coordinate system for the content document.
+///
+///
+/// PagePx is equal to ScreenPx multiplied by a "zoom" factor controlled by the user.  At the
+/// default zoom level of 100%, one PagePx is equal to one ScreenPx.  However, if the document
+/// is zoomed in or out then this scale may be larger or smaller.
+pub enum PagePx {}
+
 // An Au is an "App Unit" and represents 1/60th of a CSS pixel.  It was
 // originally proposed in 2002 as a standard unit of measure in Gecko.
 // See https://bugzilla.mozilla.org/show_bug.cgi?id=177805 for more info.
+//
+// FIXME: Implement Au using Length and ScaleFactor instead of a custom type.
 #[deriving(Clone, Eq, Ord, Zero)]
 pub struct Au(pub i32);
 
 impl Default for Au {
     #[inline]
     fn default() -> Au {
         Au(0)
     }
--- a/servo/src/components/util/opts.rs
+++ b/servo/src/components/util/opts.rs
@@ -1,17 +1,20 @@
 /* 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/. */
 
 //! Configuration options for a single run of the servo application. Created
 //! from command line arguments.
 
+use geometry::{DevicePixel, ScreenPx};
+
 use azure::azure_hl::{BackendType, CairoBackend, CoreGraphicsBackend};
 use azure::azure_hl::{CoreGraphicsAcceleratedBackend, Direct2DBackend, SkiaBackend};
+use geom::scale_factor::ScaleFactor;
 use getopts;
 use std::cmp;
 use std::io;
 use std::os;
 use std::rt;
 
 /// Global flags for Servo, currently set on the command line.
 #[deriving(Clone)]
@@ -31,17 +34,17 @@ pub struct Opts {
     /// compositing is always done on the GPU.
     pub cpu_painting: bool,
 
     /// The maximum size of each tile in pixels (`-s`).
     pub tile_size: uint,
 
     /// The ratio of device pixels per px at the default scale. If unspecified, will use the
     /// platform default setting.
-    pub device_pixels_per_px: Option<f32>,
+    pub device_pixels_per_px: Option<ScaleFactor<ScreenPx, DevicePixel, f32>>,
 
     /// `None` to disable the profiler or `Some` with an interval in seconds to enable it and cause
     /// it to produce output on that interval (`-p`).
     pub profiler_period: Option<f64>,
 
     /// The number of threads to use for layout (`-y`). Defaults to 1, which results in a recursive
     /// sequential algorithm.
     pub layout_threads: uint,
@@ -131,17 +134,17 @@ pub fn from_cmdline_args(args: &[String]
     };
 
     let tile_size: uint = match opt_match.opt_str("s") {
         Some(tile_size_str) => from_str(tile_size_str.as_slice()).unwrap(),
         None => 512,
     };
 
     let device_pixels_per_px = opt_match.opt_str("device-pixel-ratio").map(|dppx_str|
-        from_str(dppx_str.as_slice()).unwrap()
+        ScaleFactor(from_str(dppx_str.as_slice()).unwrap())
     );
 
     let n_render_threads: uint = match opt_match.opt_str("t") {
         Some(n_render_threads_str) => from_str(n_render_threads_str.as_slice()).unwrap(),
         None => 1,      // FIXME: Number of cores.
     };
 
     // if only flag is present, default to 5 second period