servo: Merge #4028 - Add glutin port (supported on Linux only currently) (from glennw:glutin); r=larsbergstrom
authorGlenn Watson <gw@intuitionlibrary.com>
Tue, 18 Nov 2014 15:48:29 -0700
changeset 381944 96fa8e3ec2f20afb5e37a26697c006d9e8ad7045
parent 381943 def1004022606f1393887abc7f6ae86a9b2dadbb
child 381945 ae961a2f746cbbf035bae3a32b87797e15170f8a
push id7198
push userjlorenzo@mozilla.com
push dateTue, 18 Apr 2017 12:07:49 +0000
treeherdermozilla-beta@d57aa49c3948 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerslarsbergstrom
servo: Merge #4028 - Add glutin port (supported on Linux only currently) (from glennw:glutin); r=larsbergstrom Default build uses glfw, but glutin can be enabled via: ./mach cargo build --no-default-features --features=glutin Remaining work: * Mac * Android * hi-dpi * nested event loop This PR also enables true headless (without X) rendering on Linux by specifying the rendering API as Mesa. Source-Repo: https://github.com/servo/servo Source-Revision: f5c6146de0b3bfda97edff6662033f4a981df3f6
servo/Cargo.lock
servo/Cargo.toml
servo/components/util/opts.rs
servo/ports/android/glut_app/Cargo.lock
servo/ports/cef/Cargo.lock
servo/ports/cef/core.rs
servo/ports/glfw/Cargo.toml
servo/ports/glfw/window.rs
servo/ports/glutin/Cargo.toml
servo/ports/glutin/lib.rs
servo/ports/glutin/window.rs
servo/src/main.rs
--- a/servo/Cargo.lock
+++ b/servo/Cargo.lock
@@ -1,15 +1,16 @@
 [root]
 name = "servo"
 version = "0.0.1"
 dependencies = [
  "compositing 0.0.1",
  "gfx 0.0.1",
  "glfw_app 0.0.1",
+ "glutin_app 0.0.1",
  "green 0.0.1 (git+https://github.com/servo/green-rs?ref=servo)",
  "layout 0.0.1",
  "msg 0.0.1",
  "net 0.0.1",
  "rustuv 0.0.1 (git+https://github.com/servo/green-rs?ref=servo)",
  "script 0.0.1",
  "url 0.1.0 (git+https://github.com/servo/rust-url)",
  "util 0.0.1",
@@ -20,16 +21,24 @@ name = "alert"
 version = "0.1.0"
 source = "git+https://github.com/servo/rust-alert#9bd18fc406683bae448bc6f5d546872970af252e"
 dependencies = [
  "cocoa 0.1.1 (git+https://github.com/servo/rust-cocoa)",
  "core_foundation 0.1.0 (git+https://github.com/servo/rust-core-foundation)",
 ]
 
 [[package]]
+name = "android_glue"
+version = "0.0.1"
+source = "git+https://github.com/tomaka/android-rs-glue#fe9acb5bd465da1df4561e2bd4ebcc6d305134a4"
+dependencies = [
+ "compile_msg 0.1.1 (git+https://github.com/huonw/compile_msg)",
+]
+
+[[package]]
 name = "azure"
 version = "0.1.0"
 source = "git+https://github.com/servo/rust-azure#64853170e435d9320f3fd828b9e7d16bc4c260a3"
 dependencies = [
  "core_foundation 0.1.0 (git+https://github.com/servo/rust-core-foundation)",
  "core_graphics 0.1.0 (git+https://github.com/servo/rust-core-graphics)",
  "core_text 0.1.0 (git+https://github.com/servo/rust-core-text)",
  "egl 0.1.0 (git+https://github.com/servo/rust-egl)",
@@ -55,19 +64,29 @@ version = "0.0.1"
 source = "git+https://github.com/servo/rust-cgl#7b7090729f65e2287c3d80651df02e547911b119"
 dependencies = [
  "gleam 0.0.1 (git+https://github.com/servo/gleam)",
 ]
 
 [[package]]
 name = "cocoa"
 version = "0.1.1"
+source = "git+https://github.com/DavidPartouche/rust-cocoa#0a951a6cdd5f1a175b929754c134f9443b105642"
+
+[[package]]
+name = "cocoa"
+version = "0.1.1"
 source = "git+https://github.com/servo/rust-cocoa#f926323d306401df33f528c9aeca8e582cad063b"
 
 [[package]]
+name = "compile_msg"
+version = "0.1.1"
+source = "git+https://github.com/huonw/compile_msg#f526abe54b49642bc1e969e6c2af1411798b6776"
+
+[[package]]
 name = "compositing"
 version = "0.0.1"
 dependencies = [
  "alert 0.1.0 (git+https://github.com/servo/rust-alert)",
  "azure 0.1.0 (git+https://github.com/servo/rust-azure)",
  "core_graphics 0.1.0 (git+https://github.com/servo/rust-core-graphics)",
  "core_text 0.1.0 (git+https://github.com/servo/rust-core-text)",
  "devtools 0.0.1",
@@ -274,23 +293,52 @@ source = "git+https://github.com/servo/g
 [[package]]
 name = "glfw_app"
 version = "0.0.1"
 dependencies = [
  "alert 0.1.0 (git+https://github.com/servo/rust-alert)",
  "cgl 0.0.1 (git+https://github.com/servo/rust-cgl)",
  "compositing 0.0.1",
  "geom 0.1.0 (git+https://github.com/servo/rust-geom)",
+ "gleam 0.0.1 (git+https://github.com/servo/gleam)",
  "glfw 0.0.1 (git+https://github.com/servo/glfw-rs?ref=servo)",
  "layers 0.1.0 (git+https://github.com/servo/rust-layers)",
  "msg 0.0.1",
  "util 0.0.1",
 ]
 
 [[package]]
+name = "glutin"
+version = "0.0.2"
+source = "git+https://github.com/tomaka/glutin#0dc5086efb29e84c48cd4dcc1da48ee9755842e3"
+dependencies = [
+ "android_glue 0.0.1 (git+https://github.com/tomaka/android-rs-glue)",
+ "cocoa 0.1.1 (git+https://github.com/DavidPartouche/rust-cocoa)",
+ "compile_msg 0.1.1 (git+https://github.com/huonw/compile_msg)",
+ "core_graphics 0.1.0 (git+https://github.com/servo/rust-core-graphics)",
+ "gl_common 0.0.1 (git+https://github.com/bjz/gl-rs.git)",
+ "gl_generator 0.0.1 (git+https://github.com/bjz/gl-rs.git)",
+]
+
+[[package]]
+name = "glutin_app"
+version = "0.0.1"
+dependencies = [
+ "alert 0.1.0 (git+https://github.com/servo/rust-alert)",
+ "cgl 0.0.1 (git+https://github.com/servo/rust-cgl)",
+ "compositing 0.0.1",
+ "geom 0.1.0 (git+https://github.com/servo/rust-geom)",
+ "gleam 0.0.1 (git+https://github.com/servo/gleam)",
+ "glutin 0.0.2 (git+https://github.com/tomaka/glutin)",
+ "layers 0.1.0 (git+https://github.com/servo/rust-layers)",
+ "msg 0.0.1",
+ "util 0.0.1",
+]
+
+[[package]]
 name = "glx"
 version = "0.0.1"
 source = "git+https://github.com/servo/rust-glx#7126ffa09fcfcc9f85f1406f3b5db729f5fdb7c3"
 dependencies = [
  "gl_generator 0.0.1 (git+https://github.com/bjz/gl-rs.git)",
 ]
 
 [[package]]
@@ -353,17 +401,17 @@ dependencies = [
 [[package]]
 name = "khronos_api"
 version = "0.0.1"
 source = "git+https://github.com/bjz/gl-rs.git#79cd3b3f9f19aa0e39f6af572fc8673a6d9760bc"
 
 [[package]]
 name = "layers"
 version = "0.1.0"
-source = "git+https://github.com/servo/rust-layers#84f6dd677aa681b03cfcbf89000d2c7b12684bd3"
+source = "git+https://github.com/servo/rust-layers#0f6edd58b3b572f2aac97567b752c15db5fa1982"
 dependencies = [
  "cgl 0.0.1 (git+https://github.com/servo/rust-cgl)",
  "core_foundation 0.1.0 (git+https://github.com/servo/rust-core-foundation)",
  "egl 0.1.0 (git+https://github.com/servo/rust-egl)",
  "geom 0.1.0 (git+https://github.com/servo/rust-geom)",
  "gleam 0.0.1 (git+https://github.com/servo/gleam)",
  "glx 0.0.1 (git+https://github.com/servo/rust-glx)",
  "io_surface 0.1.0 (git+https://github.com/servo/rust-io-surface)",
--- a/servo/Cargo.toml
+++ b/servo/Cargo.toml
@@ -21,16 +21,17 @@ harness = false
 
 [[test]]
 name = "contenttest"
 path = "tests/contenttest.rs"
 harness = false
 
 [features]
 default = ["glfw_app"]
+glutin = ["glutin_app"]
 
 [dependencies.compositing]
 path = "components/compositing"
 
 [dependencies.net]
 path = "components/net"
 
 [dependencies.msg]
@@ -47,16 +48,20 @@ path = "components/layout"
 
 [dependencies.gfx]
 path = "components/gfx"
 
 [dependencies.glfw_app]
 path = "ports/glfw"
 optional = true
 
+[dependencies.glutin_app]
+path = "ports/glutin"
+optional = true
+
 [dependencies.url]
 git = "https://github.com/servo/rust-url"
 
 [dependencies.green]
 git = "https://github.com/servo/green-rs"
 branch = "servo"
 
 [dependencies.rustuv]
--- a/servo/components/util/opts.rs
+++ b/servo/components/util/opts.rs
@@ -14,16 +14,22 @@ use getopts;
 use std::collections::HashSet;
 use std::cmp;
 use std::io;
 use std::mem;
 use std::os;
 use std::ptr;
 use std::rt;
 
+#[deriving(Clone)]
+pub enum RenderApi {
+    OpenGL,
+    Mesa,
+}
+
 /// Global flags for Servo, currently set on the command line.
 #[deriving(Clone)]
 pub struct Opts {
     /// The initial URLs to load.
     pub urls: Vec<String>,
 
     /// How many threads to use for CPU rendering (`-t`).
     ///
@@ -103,16 +109,18 @@ pub struct Opts {
     /// An optional string allowing the user agent to be set for testing.
     pub user_agent: Option<String>,
 
     /// Dumps the flow tree after a layout.
     pub dump_flow_tree: bool,
 
     /// Whether to show an error when display list geometry escapes flow overflow regions.
     pub validate_display_list_geometry: bool,
+
+    pub render_api: RenderApi,
 }
 
 fn print_usage(app: &str, opts: &[getopts::OptGroup]) {
     let message = format!("Usage: {} [ options ... ] [URL]\n\twhere options include", app);
     println!("{}", getopts::usage(message.as_slice(), opts));
 }
 
 pub fn print_debug_usage(app: &str)  {
@@ -170,16 +178,17 @@ fn default_opts() -> Opts {
         enable_text_antialiasing: false,
         trace_layout: false,
         devtools_port: None,
         initial_window_size: TypedSize2D(800, 600),
         user_agent: None,
         dump_flow_tree: false,
         validate_display_list_geometry: false,
         profile_tasks: false,
+        render_api: OpenGL,
     }
 }
 
 pub fn from_cmdline_args(args: &[String]) -> bool {
     let app_name = args[0].to_string();
     let args = args.tail();
 
     let opts = vec!(
@@ -196,17 +205,18 @@ pub fn from_cmdline_args(args: &[String]
         getopts::optopt("y", "layout-threads", "Number of threads to use for layout", "1"),
         getopts::optflag("i", "nonincremental-layout", "Enable to turn off incremental layout."),
         getopts::optflag("z", "headless", "Headless mode"),
         getopts::optflag("f", "hard-fail", "Exit on task failure instead of displaying about:failure"),
         getopts::optflagopt("", "devtools", "Start remote devtools server on port", "6000"),
         getopts::optopt("", "resolution", "Set window resolution.", "800x600"),
         getopts::optopt("u", "user-agent", "Set custom user agent string", "NCSA Mosaic/1.0 (X11;SunOS 4.1.4 sun4m)"),
         getopts::optopt("Z", "debug", "A comma-separated string of debug options. Pass help to show available options.", ""),
-        getopts::optflag("h", "help", "Print this message")
+        getopts::optflag("h", "help", "Print this message"),
+        getopts::optopt("r", "render-api", "Set the rendering API to use", "gl|mesa"),
     );
 
     let opt_match = match getopts::getopts(args, opts.as_slice()) {
         Ok(m) => m,
         Err(f) => {
             args_fail(format!("{}", f).as_slice());
             return false;
         }
@@ -286,16 +296,25 @@ pub fn from_cmdline_args(args: &[String]
             let res: Vec<uint> = res_string.as_slice().split('x').map(|r| from_str(r).unwrap()).collect();
             TypedSize2D(res[0], res[1])
         }
         None => {
             TypedSize2D(800, 600)
         }
     };
 
+    let render_api = match opt_match.opt_str("r").unwrap_or("gl".to_string()).as_slice() {
+        "mesa" => Mesa,
+        "gl" => OpenGL,
+        _ => {
+            args_fail("Unknown render api specified");
+            return false;
+        }
+    };
+
     let opts = Opts {
         urls: urls,
         n_render_threads: n_render_threads,
         gpu_painting: gpu_painting,
         tile_size: tile_size,
         device_pixels_per_px: device_pixels_per_px,
         time_profiler_period: time_profiler_period,
         memory_profiler_period: memory_profiler_period,
@@ -312,16 +331,17 @@ pub fn from_cmdline_args(args: &[String]
         devtools_port: devtools_port,
         initial_window_size: initial_window_size,
         user_agent: opt_match.opt_str("u"),
         show_debug_borders: debug_options.contains(&"show-compositor-borders"),
         show_debug_fragment_borders: debug_options.contains(&"show-fragment-borders"),
         enable_text_antialiasing: !debug_options.contains(&"disable-text-aa"),
         dump_flow_tree: debug_options.contains(&"dump-flow-tree"),
         validate_display_list_geometry: debug_options.contains(&"validate-display-list-geometry"),
+        render_api: render_api,
     };
 
     set_opts(opts);
     true
 }
 
 static mut EXPERIMENTAL_ENABLED: bool = false;
 
--- a/servo/ports/android/glut_app/Cargo.lock
+++ b/servo/ports/android/glut_app/Cargo.lock
@@ -220,16 +220,17 @@ dependencies = [
  "freetype 0.1.0 (git+https://github.com/servo/rust-freetype)",
  "geom 0.1.0 (git+https://github.com/servo/rust-geom)",
  "harfbuzz 0.1.0 (git+https://github.com/servo/rust-harfbuzz)",
  "layers 0.1.0 (git+https://github.com/servo/rust-layers)",
  "msg 0.0.1",
  "net 0.0.1",
  "plugins 0.0.1",
  "png 0.1.0 (git+https://github.com/servo/rust-png)",
+ "script_traits 0.0.1",
  "stb_image 0.1.0 (git+https://github.com/servo/rust-stb-image)",
  "style 0.0.1",
  "url 0.1.0 (git+https://github.com/servo/rust-url)",
  "util 0.0.1",
 ]
 
 [[package]]
 name = "gl_common"
@@ -330,17 +331,17 @@ dependencies = [
 [[package]]
 name = "khronos_api"
 version = "0.0.1"
 source = "git+https://github.com/bjz/gl-rs.git#79cd3b3f9f19aa0e39f6af572fc8673a6d9760bc"
 
 [[package]]
 name = "layers"
 version = "0.1.0"
-source = "git+https://github.com/servo/rust-layers#84f6dd677aa681b03cfcbf89000d2c7b12684bd3"
+source = "git+https://github.com/servo/rust-layers#0f6edd58b3b572f2aac97567b752c15db5fa1982"
 dependencies = [
  "cgl 0.0.1 (git+https://github.com/servo/rust-cgl)",
  "core_foundation 0.1.0 (git+https://github.com/servo/rust-core-foundation)",
  "egl 0.1.0 (git+https://github.com/servo/rust-egl)",
  "geom 0.1.0 (git+https://github.com/servo/rust-geom)",
  "gleam 0.0.1 (git+https://github.com/servo/gleam)",
  "glx 0.0.1 (git+https://github.com/servo/rust-glx)",
  "io_surface 0.1.0 (git+https://github.com/servo/rust-io-surface)",
--- a/servo/ports/cef/Cargo.lock
+++ b/servo/ports/cef/Cargo.lock
@@ -230,16 +230,17 @@ dependencies = [
  "freetype 0.1.0 (git+https://github.com/servo/rust-freetype)",
  "geom 0.1.0 (git+https://github.com/servo/rust-geom)",
  "harfbuzz 0.1.0 (git+https://github.com/servo/rust-harfbuzz)",
  "layers 0.1.0 (git+https://github.com/servo/rust-layers)",
  "msg 0.0.1",
  "net 0.0.1",
  "plugins 0.0.1",
  "png 0.1.0 (git+https://github.com/servo/rust-png)",
+ "script_traits 0.0.1",
  "stb_image 0.1.0 (git+https://github.com/servo/rust-stb-image)",
  "style 0.0.1",
  "url 0.1.0 (git+https://github.com/servo/rust-url)",
  "util 0.0.1",
 ]
 
 [[package]]
 name = "gl_common"
@@ -281,16 +282,17 @@ source = "git+https://github.com/servo/g
 [[package]]
 name = "glfw_app"
 version = "0.0.1"
 dependencies = [
  "alert 0.1.0 (git+https://github.com/servo/rust-alert)",
  "cgl 0.0.1 (git+https://github.com/servo/rust-cgl)",
  "compositing 0.0.1",
  "geom 0.1.0 (git+https://github.com/servo/rust-geom)",
+ "gleam 0.0.1 (git+https://github.com/servo/gleam)",
  "glfw 0.0.1 (git+https://github.com/servo/glfw-rs?ref=servo)",
  "layers 0.1.0 (git+https://github.com/servo/rust-layers)",
  "msg 0.0.1",
  "util 0.0.1",
 ]
 
 [[package]]
 name = "glx"
@@ -360,17 +362,17 @@ dependencies = [
 [[package]]
 name = "khronos_api"
 version = "0.0.1"
 source = "git+https://github.com/bjz/gl-rs.git#79cd3b3f9f19aa0e39f6af572fc8673a6d9760bc"
 
 [[package]]
 name = "layers"
 version = "0.1.0"
-source = "git+https://github.com/servo/rust-layers#84f6dd677aa681b03cfcbf89000d2c7b12684bd3"
+source = "git+https://github.com/servo/rust-layers#0f6edd58b3b572f2aac97567b752c15db5fa1982"
 dependencies = [
  "cgl 0.0.1 (git+https://github.com/servo/rust-cgl)",
  "core_foundation 0.1.0 (git+https://github.com/servo/rust-core-foundation)",
  "egl 0.1.0 (git+https://github.com/servo/rust-egl)",
  "geom 0.1.0 (git+https://github.com/servo/rust-geom)",
  "gleam 0.0.1 (git+https://github.com/servo/gleam)",
  "glx 0.0.1 (git+https://github.com/servo/rust-glx)",
  "io_surface 0.1.0 (git+https://github.com/servo/rust-io-surface)",
--- a/servo/ports/cef/core.rs
+++ b/servo/ports/cef/core.rs
@@ -5,16 +5,17 @@
 
 use command_line::command_line_init;
 use geom::size::TypedSize2D;
 use glfw_app;
 use libc::{c_int, c_void};
 use native;
 use servo::Browser;
 use servo_util::opts;
+use servo_util::opts::OpenGL;
 use types::{cef_app_t, cef_main_args_t, cef_settings_t};
 
 #[no_mangle]
 pub extern "C" fn cef_initialize(args: *const cef_main_args_t,
                                  _settings: *mut cef_settings_t,
                                  application: *mut cef_app_t,
                                  _windows_sandbox_info: *const c_void)
                                  -> c_int {
@@ -63,16 +64,17 @@ pub extern "C" fn cef_run_message_loop()
         enable_text_antialiasing: true,
         trace_layout: false,
         devtools_port: None,
         initial_window_size: TypedSize2D(800, 600),
         profile_tasks: false,
         user_agent: None,
         dump_flow_tree: false,
         validate_display_list_geometry: false,
+        render_api: OpenGL,
     });
     native::start(0, 0 as *const *const u8, proc() {
         let window = glfw_app::create_window();
         let mut browser = Browser::new(Some(window.clone()));
         while browser.handle_event(window.wait_events()) {}
         browser.shutdown()
     });
 }
--- a/servo/ports/glfw/Cargo.toml
+++ b/servo/ports/glfw/Cargo.toml
@@ -26,8 +26,11 @@ git = "https://github.com/servo/rust-lay
 [dependencies.msg]
 path = "../../components/msg"
 
 [dependencies.util]
 path = "../../components/util"
 
 [dependencies.cgl]
 git = "https://github.com/servo/rust-cgl"
+
+[dependencies.gleam]
+git = "https://github.com/servo/gleam"
--- a/servo/ports/glfw/window.rs
+++ b/servo/ports/glfw/window.rs
@@ -296,31 +296,31 @@ impl Window {
         let now = time::get_time();
         if now.sec == self.last_title_set_time.get().sec {
             return
         }
         self.last_title_set_time.set(now);
 
         match self.ready_state.get() {
             Blank => {
-                self.glfw_window.set_title("blank — Servo")
+                self.glfw_window.set_title("blank — Servo [GLFW]")
             }
             Loading => {
-                self.glfw_window.set_title("Loading — Servo")
+                self.glfw_window.set_title("Loading — Servo [GLFW]")
             }
             PerformingLayout => {
-                self.glfw_window.set_title("Performing Layout — Servo")
+                self.glfw_window.set_title("Performing Layout — Servo [GLFW]")
             }
             FinishedLoading => {
                 match self.render_state.get() {
                     RenderingRenderState => {
-                        self.glfw_window.set_title("Rendering — Servo")
+                        self.glfw_window.set_title("Rendering — Servo [GLFW]")
                     }
                     IdleRenderState => {
-                        self.glfw_window.set_title("Servo")
+                        self.glfw_window.set_title("Servo [GLFW]")
                     }
                 }
             }
         }
     }
 
     /// Helper function to handle keyboard events.
     fn handle_key(&self, key: glfw::Key, mods: glfw::Modifiers) {
new file mode 100644
--- /dev/null
+++ b/servo/ports/glutin/Cargo.toml
@@ -0,0 +1,36 @@
+[package]
+name = "glutin_app"
+version = "0.0.1"
+authors = ["The Servo Project Developers"]
+
+[lib]
+name = "glutin_app"
+path = "lib.rs"
+
+[dependencies.alert]
+git = "https://github.com/servo/rust-alert"
+
+[dependencies.compositing]
+path = "../../components/compositing"
+
+[dependencies.geom]
+git = "https://github.com/servo/rust-geom"
+
+[dependencies.layers]
+git = "https://github.com/servo/rust-layers"
+
+[dependencies.msg]
+path = "../../components/msg"
+
+[dependencies.util]
+path = "../../components/util"
+
+[dependencies.glutin]
+git = "https://github.com/tomaka/glutin"
+features = ["window", "headless"]
+
+[dependencies.gleam]
+git = "https://github.com/servo/gleam"
+
+[dependencies.cgl]
+git = "https://github.com/servo/rust-cgl"
new file mode 100644
--- /dev/null
+++ b/servo/ports/glutin/lib.rs
@@ -0,0 +1,46 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! A simple application that uses GLFW to open a window for Servo to display in.
+
+#![license = "MPL"]
+#![feature(macro_rules)]
+#![deny(unused_imports, unused_variable)]
+
+extern crate alert;
+#[cfg(target_os="macos")]
+extern crate cgl;
+extern crate compositing;
+extern crate geom;
+extern crate gleam;
+extern crate glutin;
+extern crate layers;
+extern crate libc;
+extern crate msg;
+extern crate time;
+extern crate util;
+extern crate egl;
+
+use compositing::windowing::WindowEvent;
+use geom::scale_factor::ScaleFactor;
+use std::rc::Rc;
+use window::Window;
+use util::opts;
+
+pub mod window;
+
+pub trait NestedEventLoopListener {
+    fn handle_event_from_nested_event_loop(&mut self, event: WindowEvent) -> bool;
+}
+
+pub fn create_window() -> Rc<Window> {
+    // Read command-line options.
+    let opts = opts::get();
+    let foreground = opts.output_file.is_none();
+    let scale_factor = opts.device_pixels_per_px.unwrap_or(ScaleFactor(1.0));
+    let size = opts.initial_window_size.as_f32() * scale_factor;
+
+    // Open a window.
+    Window::new(foreground, size.as_uint(), opts.render_api)
+}
new file mode 100644
--- /dev/null
+++ b/servo/ports/glutin/window.rs
@@ -0,0 +1,479 @@
+/* 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 GLFW.
+
+use alert::{Alert, AlertMethods};
+use compositing::compositor_task::{mod, CompositorProxy, CompositorReceiver};
+use compositing::windowing::{WindowEvent, WindowMethods};
+use compositing::windowing::{IdleWindowEvent, ResizeWindowEvent, LoadUrlWindowEvent};
+use compositing::windowing::{MouseWindowEventClass,  MouseWindowMoveEventClass, ScrollWindowEvent};
+use compositing::windowing::{ZoomWindowEvent, PinchZoomWindowEvent, NavigationWindowEvent};
+use compositing::windowing::{FinishedWindowEvent, QuitWindowEvent, MouseWindowClickEvent};
+use compositing::windowing::{MouseWindowMouseDownEvent, MouseWindowMouseUpEvent};
+use compositing::windowing::{Forward, Back};
+use geom::point::{Point2D, TypedPoint2D};
+use geom::scale_factor::ScaleFactor;
+use geom::size::TypedSize2D;
+use gleam::gl;
+use layers::geometry::DevicePixel;
+use layers::platform::surface::NativeGraphicsMetadata;
+use msg::compositor_msg::{IdleRenderState, RenderState, RenderingRenderState};
+use msg::compositor_msg::{FinishedLoading, Blank, Loading, PerformingLayout, ReadyState};
+use std::cell::{Cell, RefCell};
+use std::rc::Rc;
+use time::{mod, Timespec};
+use util::geometry::ScreenPx;
+use util::opts::{RenderApi, Mesa, OpenGL};
+use glutin;
+use NestedEventLoopListener;
+
+#[cfg(target_os="linux")]
+use std::ptr;
+
+struct HeadlessContext {
+    // Although currently unused, this context needs to be stored.
+    // Otherwise, its drop() is called, deleting the mesa context
+    // before it can be used.
+    _context: glutin::HeadlessContext,
+    size: TypedSize2D<DevicePixel, uint>,
+}
+
+enum WindowHandle {
+    Windowed(glutin::Window),
+    Headless(HeadlessContext),
+}
+
+bitflags!(
+    #[deriving(Show)]
+    flags KeyModifiers: 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,
+    }
+)
+
+/// The type of a window.
+pub struct Window {
+    glutin: WindowHandle,
+
+    mouse_down_button: Cell<Option<glutin::MouseButton>>,
+    mouse_down_point: Cell<Point2D<int>>,
+    event_queue: RefCell<Vec<WindowEvent>>,
+
+    mouse_pos: Cell<Point2D<int>>,
+    ready_state: Cell<ReadyState>,
+    render_state: Cell<RenderState>,
+    key_modifiers: Cell<KeyModifiers>,
+
+    last_title_set_time: Cell<Timespec>,
+}
+
+impl Window {
+    /// Creates a new window.
+    pub fn new(is_foreground: bool, size: TypedSize2D<DevicePixel, uint>, render_api: RenderApi)
+               -> Rc<Window> {
+
+        // Create the glutin window.
+        let window_size = size.to_untyped();
+
+        let glutin = match render_api {
+            OpenGL => {
+                let glutin_window = glutin::WindowBuilder::new()
+                                    .with_title("Servo [glutin]".to_string())
+                                    .with_dimensions(window_size.width, window_size.height)
+                                    .with_gl_version((3, 0))
+                                    .with_visibility(is_foreground)
+                                    .build()
+                                    .unwrap();
+                unsafe { glutin_window.make_current() };
+
+                gl::load_with(|s| glutin_window.get_proc_address(s));
+
+                Windowed(glutin_window)
+            }
+            Mesa => {
+                let headless_builder = glutin::HeadlessRendererBuilder::new(window_size.width,
+                                                                            window_size.height);
+                let headless_context = headless_builder.build().unwrap();
+                unsafe { headless_context.make_current() };
+
+                gl::load_with(|s| headless_context.get_proc_address(s));
+
+                Headless(HeadlessContext {
+                    _context: headless_context,
+                    size: size,
+                })
+            }
+        };
+
+        let window = Window {
+            glutin: glutin,
+            event_queue: RefCell::new(vec!()),
+            mouse_down_button: Cell::new(None),
+            mouse_down_point: Cell::new(Point2D(0, 0)),
+
+            mouse_pos: Cell::new(Point2D(0, 0)),
+            ready_state: Cell::new(Blank),
+            render_state: Cell::new(IdleRenderState),
+            key_modifiers: Cell::new(KeyModifiers::empty()),
+
+            last_title_set_time: Cell::new(Timespec::new(0, 0)),
+        };
+
+        Rc::new(window)
+    }
+}
+
+impl WindowMethods for Window {
+    /// Returns the size of the window in hardware pixels.
+    fn framebuffer_size(&self) -> TypedSize2D<DevicePixel, uint> {
+        let (width, height) = match self.glutin {
+            Windowed(ref window) => window.get_inner_size(),
+            Headless(ref context) => Some((context.size.to_untyped().width,
+                                           context.size.to_untyped().height)),
+        }.unwrap();
+        TypedSize2D(width as uint, height as uint)
+    }
+
+    /// Returns the size of the window in density-independent "px" units.
+    fn size(&self) -> TypedSize2D<ScreenPx, f32> {
+        // TODO: Handle hidpi
+        let (width, height) = match self.glutin {
+            Windowed(ref window) => window.get_inner_size(),
+            Headless(ref context) => Some((context.size.to_untyped().width,
+                                           context.size.to_untyped().height)),
+        }.unwrap();
+        TypedSize2D(width as f32, height as f32)
+    }
+
+    /// Presents the window to the screen (perhaps by page flipping).
+    fn present(&self) {
+        match self.glutin {
+            Windowed(ref window) => window.swap_buffers(),
+            Headless(_) => {},
+        }
+    }
+
+    fn create_compositor_channel(_: &Option<Rc<Window>>)
+                                 -> (Box<CompositorProxy+Send>, Box<CompositorReceiver>) {
+        let (sender, receiver) = channel();
+        (box GlutinCompositorProxy {
+             sender: sender,
+         } as Box<CompositorProxy+Send>,
+         box receiver as Box<CompositorReceiver>)
+    }
+
+    /// Sets the ready state.
+    fn set_ready_state(&self, ready_state: ReadyState) {
+        self.ready_state.set(ready_state);
+        self.update_window_title()
+    }
+
+    /// Sets the render state.
+    fn set_render_state(&self, render_state: RenderState) {
+        if self.ready_state.get() == FinishedLoading &&
+            self.render_state.get() == RenderingRenderState &&
+            render_state == IdleRenderState {
+            // page loaded
+            self.event_queue.borrow_mut().push(FinishedWindowEvent);
+        }
+
+        self.render_state.set(render_state);
+        self.update_window_title()
+    }
+
+    fn hidpi_factor(&self) -> ScaleFactor<ScreenPx, DevicePixel, f32> {
+        // TODO - handle hidpi
+        ScaleFactor(1.0)
+    }
+
+    #[cfg(target_os="linux")]
+    fn native_metadata(&self) -> NativeGraphicsMetadata {
+        match self.glutin {
+            Windowed(ref window) => {
+                NativeGraphicsMetadata {
+                    display: unsafe { window.platform_display() }
+                }
+            }
+            Headless(_) => {
+                NativeGraphicsMetadata {
+                    display: ptr::null_mut()
+                }
+            },
+        }
+    }
+
+    #[cfg(target_os="macos")]
+    fn native_metadata(&self) -> NativeGraphicsMetadata {
+        use cgl::{CGLGetCurrentContext, CGLGetPixelFormat};
+        unsafe {
+            NativeGraphicsMetadata {
+                pixel_format: CGLGetPixelFormat(CGLGetCurrentContext()),
+            }
+        }
+    }
+}
+
+impl Window {
+    /// Helper function to set the window title in accordance with the ready state.
+    fn update_window_title(&self) {
+        match self.glutin {
+            Windowed(ref window) => {
+                let now = time::get_time();
+                if now.sec == self.last_title_set_time.get().sec {
+                    return
+                }
+                self.last_title_set_time.set(now);
+
+                match self.ready_state.get() {
+                    Blank => {
+                        window.set_title("blank - Servo [glutin]")
+                    }
+                    Loading => {
+                        window.set_title("Loading - Servo [glutin]")
+                    }
+                    PerformingLayout => {
+                        window.set_title("Performing Layout - Servo [glutin]")
+                    }
+                    FinishedLoading => {
+                        match self.render_state.get() {
+                            RenderingRenderState => {
+                                window.set_title("Rendering - Servo [glutin]")
+                            }
+                            IdleRenderState => {
+                                window.set_title("Servo [glutin]")
+                            }
+                        }
+                    }
+                }
+            }
+            Headless(_) => {},
+        }
+    }
+}
+
+impl Window {
+    fn handle_window_event(&self, event: glutin::Event) -> bool {
+        match event {
+            glutin::KeyboardInput(element_state, _scan_code, virtual_key_code) => {
+                if virtual_key_code.is_some() {
+                    let virtual_key_code = virtual_key_code.unwrap();
+
+                    match (element_state, virtual_key_code) {
+                        (_, glutin::LControl) => self.toggle_modifier(LEFT_CONTROL),
+                        (_, glutin::RControl) => self.toggle_modifier(RIGHT_CONTROL),
+                        (_, glutin::LShift) => self.toggle_modifier(LEFT_SHIFT),
+                        (_, glutin::RShift) => self.toggle_modifier(RIGHT_SHIFT),
+                        (_, glutin::LAlt) => self.toggle_modifier(LEFT_ALT),
+                        (_, glutin::RAlt) => self.toggle_modifier(RIGHT_ALT),
+                        (glutin::Pressed, key_code) => return self.handle_key(key_code),
+                        (_, _) => {}
+                    }
+                }
+            }
+            glutin::Resized(width, height) => {
+                self.event_queue.borrow_mut().push(ResizeWindowEvent(TypedSize2D(width, height)));
+            }
+            glutin::MouseInput(element_state, mouse_button) => {
+                if mouse_button == glutin::LeftMouseButton ||
+                                    mouse_button == glutin::RightMouseButton {
+                        let mouse_pos = self.mouse_pos.get();
+                        self.handle_mouse(mouse_button, element_state, mouse_pos.x, mouse_pos.y);
+                   }
+            }
+            glutin::MouseMoved((x, y)) => {
+                self.mouse_pos.set(Point2D(x, y));
+                self.event_queue.borrow_mut().push(
+                    MouseWindowMoveEventClass(TypedPoint2D(x as f32, y as f32)));
+            }
+            glutin::MouseWheel(delta) => {
+                if self.ctrl_pressed() {
+                    // Ctrl-Scrollwheel simulates a "pinch zoom" gesture.
+                    if delta < 0 {
+                        self.event_queue.borrow_mut().push(PinchZoomWindowEvent(1.0/1.1));
+                    } else if delta > 0 {
+                        self.event_queue.borrow_mut().push(PinchZoomWindowEvent(1.1));
+                    }
+                } else {
+                    let dx = 0.0;
+                    let dy = (delta as f32) * 30.0;
+                    self.scroll_window(dx, dy);
+                }
+            },
+            _ => {}
+        }
+
+        false
+    }
+
+    #[inline]
+    fn ctrl_pressed(&self) -> bool {
+        self.key_modifiers.get().intersects(LEFT_CONTROL | RIGHT_CONTROL)
+    }
+
+    #[inline]
+    fn shift_pressed(&self) -> bool {
+        self.key_modifiers.get().intersects(LEFT_SHIFT | RIGHT_SHIFT)
+    }
+
+    fn toggle_modifier(&self, modifier: KeyModifiers) {
+        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, dx: f32, dy: f32) {
+        let mouse_pos = self.mouse_pos.get();
+        let event = ScrollWindowEvent(TypedPoint2D(dx as f32, dy as f32),
+                                      TypedPoint2D(mouse_pos.x as i32, mouse_pos.y as i32));
+        self.event_queue.borrow_mut().push(event);
+    }
+
+    /// Helper function to handle keyboard events.
+    fn handle_key(&self, key: glutin::VirtualKeyCode) -> bool {
+        match key {
+            glutin::Escape => return true,
+            glutin::L if self.ctrl_pressed() => {
+                self.load_url(); // Ctrl+L
+            }
+            glutin::Equals if self.ctrl_pressed() => { // Ctrl-+
+                self.event_queue.borrow_mut().push(ZoomWindowEvent(1.1));
+            }
+            glutin::Minus if self.ctrl_pressed() => { // Ctrl--
+                self.event_queue.borrow_mut().push(ZoomWindowEvent(1.0/1.1));
+            }
+            glutin::Back if self.shift_pressed() => { // Shift-Backspace
+                self.event_queue.borrow_mut().push(NavigationWindowEvent(Forward));
+            }
+            glutin::Back => { // Backspace
+                self.event_queue.borrow_mut().push(NavigationWindowEvent(Back));
+            }
+            glutin::PageDown => {
+                self.scroll_window(0.0, -self.framebuffer_size().as_f32().to_untyped().height);
+            }
+            glutin::PageUp => {
+                self.scroll_window(0.0, self.framebuffer_size().as_f32().to_untyped().height);
+            }
+            _ => {}
+        }
+
+        false
+    }
+
+    /// Helper function to handle a click
+    fn handle_mouse(&self, button: glutin::MouseButton, action: glutin::ElementState, x: int, y: int) {
+        // FIXME(tkuehn): max pixel dist should be based on pixel density
+        let max_pixel_dist = 10f64;
+        let event = match action {
+            glutin::Pressed => {
+                self.mouse_down_point.set(Point2D(x, y));
+                self.mouse_down_button.set(Some(button));
+                MouseWindowMouseDownEvent(0, TypedPoint2D(x as f32, y as f32))
+            }
+            glutin::Released => {
+                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(0,
+                                                                    TypedPoint2D(x as f32,
+                                                                                 y as f32));
+                            self.event_queue.borrow_mut().push(MouseWindowEventClass(click_event));
+                        }
+                    }
+                    Some(_) => (),
+                }
+                MouseWindowMouseUpEvent(0, TypedPoint2D(x as f32, y as f32))
+            }
+        };
+        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) {
+        let mut alert: Alert = AlertMethods::new("Navigate to:");
+        alert.add_prompt();
+        alert.run();
+        let value = alert.prompt_value();
+        if "" == value.as_slice() {    // To avoid crashing on Linux.
+            self.event_queue.borrow_mut().push(LoadUrlWindowEvent("http://purple.com/".to_string()))
+        } else {
+            self.event_queue.borrow_mut().push(LoadUrlWindowEvent(value.clone()))
+        }
+    }
+
+    pub unsafe fn set_nested_event_loop_listener(
+            &self,
+            _listener: *mut NestedEventLoopListener + 'static) {
+        // TODO: Support this with glutin
+        //self.glfw_window.set_refresh_polling(false);
+        //glfw::ffi::glfwSetWindowRefreshCallback(self.glfw_window.ptr, Some(on_refresh));
+        //glfw::ffi::glfwSetFramebufferSizeCallback(self.glfw_window.ptr, Some(on_framebuffer_size));
+        //g_nested_event_loop_listener = Some(listener)
+    }
+
+    pub unsafe fn remove_nested_event_loop_listener(&self) {
+        // TODO: Support this with glutin
+        //glfw::ffi::glfwSetWindowRefreshCallback(self.glfw_window.ptr, None);
+        //glfw::ffi::glfwSetFramebufferSizeCallback(self.glfw_window.ptr, None);
+        //self.glfw_window.set_refresh_polling(true);
+        //g_nested_event_loop_listener = None
+    }
+
+    pub fn wait_events(&self) -> WindowEvent {
+        {
+            let mut event_queue = self.event_queue.borrow_mut();
+            if !event_queue.is_empty() {
+                return event_queue.remove(0).unwrap();
+            }
+        }
+
+        match self.glutin {
+            Windowed(ref window) => {
+                let mut close_event = false;
+                for event in window.poll_events() {
+                    close_event = self.handle_window_event(event);
+                    if close_event {
+                        break;
+                    }
+                }
+
+                if close_event || window.is_closed() {
+                    QuitWindowEvent
+                } else {
+                    self.event_queue.borrow_mut().remove(0).unwrap_or(IdleWindowEvent)
+                }
+            }
+            Headless(_) => {
+                self.event_queue.borrow_mut().remove(0).unwrap_or(IdleWindowEvent)
+            }
+        }
+    }
+}
+
+struct GlutinCompositorProxy {
+    sender: Sender<compositor_task::Msg>,
+}
+
+impl CompositorProxy for GlutinCompositorProxy {
+    fn send(&mut self, msg: compositor_task::Msg) {
+        // Send a message and kick the OS event loop awake.
+        self.sender.send(msg);
+        // TODO: Support this with glutin
+        //glfw::Glfw::post_empty_event()
+    }
+    fn clone_compositor_proxy(&self) -> Box<CompositorProxy+Send> {
+        box GlutinCompositorProxy {
+            sender: self.sender.clone(),
+        } as Box<CompositorProxy+Send>
+    }
+}
--- a/servo/src/main.rs
+++ b/servo/src/main.rs
@@ -8,18 +8,21 @@
 #![deny(unused_imports)]
 #![deny(unused_variables)]
 
 extern crate servo;
 extern crate native;
 extern crate time;
 extern crate "util" as servo_util;
 
-#[cfg(not(any(test,target_os="android")))]
-extern crate glfw_app;
+#[cfg(all(feature = "glutin",not(any(test,target_os="android"))))]
+extern crate "glutin_app" as app;
+#[cfg(all(feature = "glfw_app",not(any(test,target_os="android"))))]
+extern crate "glfw_app" as app;
+
 #[cfg(not(any(test,target_os="android")))]
 extern crate compositing;
 
 #[cfg(not(any(test,target_os="android")))]
 use servo_util::opts;
 
 #[cfg(not(any(test,target_os="android")))]
 use servo_util::rtinstrument;
@@ -29,29 +32,29 @@ use servo::Browser;
 #[cfg(not(any(test,target_os="android")))]
 use compositing::windowing::{IdleWindowEvent, ResizeWindowEvent, WindowEvent};
 
 #[cfg(not(any(test,target_os="android")))]
 use std::os;
 
 #[cfg(not(any(test,target_os="android")))]
 struct BrowserWrapper {
-    browser: Browser<glfw_app::window::Window>,
+    browser: Browser<app::window::Window>,
 }
 
 #[cfg(not(any(test,target_os="android")))]
 #[start]
 #[allow(dead_code)]
 fn start(argc: int, argv: *const *const u8) -> int {
     native::start(argc, argv, proc() {
         if opts::from_cmdline_args(os::args().as_slice()) {
             let window = if opts::get().headless {
                 None
             } else {
-                Some(glfw_app::create_window())
+                Some(app::create_window())
             };
 
             let mut browser = BrowserWrapper {
                 browser: Browser::new(window.clone()),
             };
 
             match window {
                 None => {}
@@ -90,17 +93,17 @@ fn start(argc: int, argv: *const *const 
             browser.shutdown();
 
             rtinstrument::teardown();
         }
     })
 }
 
 #[cfg(not(any(test,target_os="android")))]
-impl glfw_app::NestedEventLoopListener for BrowserWrapper {
+impl app::NestedEventLoopListener for BrowserWrapper {
     fn handle_event_from_nested_event_loop(&mut self, event: WindowEvent) -> bool {
         let is_resize = match event {
             ResizeWindowEvent(..) => true,
             _ => false,
         };
         if !self.browser.handle_event(event) {
             return false
         }