servo: Merge #13382 - Add OSMesa headless mode, run WPT against Webrender (from glennw:headless); r=larsbergstrom
authorGlenn Watson <github@intuitionlibrary.com>
Tue, 27 Sep 2016 21:46:13 -0500
changeset 339763 96d5cceb016c415bf1685b066e29b6fa2c795527
parent 339762 bd478fc9bb0ee7573e39cd9a48fe7d512981dbf1
child 339764 ac89fa39d735bd0ddec9a3d1fd5694225503456e
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)
reviewerslarsbergstrom
servo: Merge #13382 - Add OSMesa headless mode, run WPT against Webrender (from glennw:headless); r=larsbergstrom Add OSMesa headless mode, run WPT against Webrender. Source-Repo: https://github.com/servo/servo Source-Revision: c7e1a575a5225ccfb00a38fb50e052d986c0a78d
servo/.travis.yml
servo/components/canvas/webgl_paint_thread.rs
servo/components/script/dom/webidls/Element.webidl
servo/components/script/dom/webidls/Window.webidl
servo/components/script/lib.rs
servo/components/servo/Cargo.lock
servo/components/servo/lib.rs
servo/components/util/opts.rs
servo/ports/cef/Cargo.lock
servo/ports/glutin/Cargo.toml
servo/ports/glutin/lib.rs
servo/ports/glutin/window.rs
servo/python/servo/command_base.py
servo/python/servo/post_build_commands.py
servo/python/servo/testing_commands.py
--- a/servo/.travis.yml
+++ b/servo/.travis.yml
@@ -35,16 +35,17 @@ matrix:
             - libgles2-mesa-dev
             - python-virtualenv
             - xorg-dev
             - ccache
             - libdbus-glib-1-dev
             - libavformat-dev
             - libavcodec-dev
             - libavutil-dev
+            - libedit-dev
 
 branches:
   only:
     - master
 
 notifications:
   webhooks:
     - https://buildtimetrend.herokuapp.com/travis
--- a/servo/components/canvas/webgl_paint_thread.rs
+++ b/servo/components/canvas/webgl_paint_thread.rs
@@ -2,38 +2,110 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use canvas_traits::{CanvasCommonMsg, CanvasData, CanvasMsg, CanvasPixelData};
 use canvas_traits::{FromLayoutMsg, byte_swap};
 use euclid::size::Size2D;
 use gleam::gl;
 use ipc_channel::ipc::{self, IpcSender, IpcSharedMemory};
-use offscreen_gl_context::{ColorAttachmentType, GLContext, GLLimits, GLContextAttributes, NativeGLContext};
+use offscreen_gl_context::{ColorAttachmentType, GLContext, GLLimits};
+use offscreen_gl_context::{GLContextAttributes, NativeGLContext, OSMesaContext};
 use std::borrow::ToOwned;
 use std::sync::mpsc::channel;
+use util::opts;
 use util::thread::spawn_named;
 use webrender_traits;
 
+enum GLContextWrapper {
+    Native(GLContext<NativeGLContext>),
+    OSMesa(GLContext<OSMesaContext>),
+}
+
+impl GLContextWrapper {
+    fn new(size: Size2D<i32>,
+           attributes: GLContextAttributes) -> Result<GLContextWrapper, &'static str> {
+        if opts::get().should_use_osmesa() {
+            let ctx = GLContext::<OSMesaContext>::new(size,
+                                                      attributes,
+                                                      ColorAttachmentType::Texture,
+                                                      None);
+            ctx.map(GLContextWrapper::OSMesa)
+        } else {
+            let ctx = GLContext::<NativeGLContext>::new(size,
+                                                        attributes,
+                                                        ColorAttachmentType::Texture,
+                                                        None);
+            ctx.map(GLContextWrapper::Native)
+        }
+    }
+
+    pub fn get_limits(&self) -> GLLimits {
+        match *self {
+            GLContextWrapper::Native(ref ctx) => {
+                ctx.borrow_limits().clone()
+            }
+            GLContextWrapper::OSMesa(ref ctx) => {
+                ctx.borrow_limits().clone()
+            }
+        }
+    }
+
+    fn resize(&mut self, size: Size2D<i32>) -> Result<Size2D<i32>, &'static str> {
+        match *self {
+            GLContextWrapper::Native(ref mut ctx) => {
+                try!(ctx.resize(size));
+                Ok(ctx.borrow_draw_buffer().unwrap().size())
+            }
+            GLContextWrapper::OSMesa(ref mut ctx) => {
+                try!(ctx.resize(size));
+                Ok(ctx.borrow_draw_buffer().unwrap().size())
+            }
+        }
+    }
+
+    pub fn make_current(&self) {
+        match *self {
+            GLContextWrapper::Native(ref ctx) => {
+                ctx.make_current().unwrap();
+            }
+            GLContextWrapper::OSMesa(ref ctx) => {
+                ctx.make_current().unwrap();
+            }
+        }
+    }
+
+    pub fn apply_command(&self, cmd: webrender_traits::WebGLCommand) {
+        match *self {
+            GLContextWrapper::Native(ref ctx) => {
+                cmd.apply(ctx);
+            }
+            GLContextWrapper::OSMesa(ref ctx) => {
+                cmd.apply(ctx);
+            }
+        }
+    }
+}
+
 enum WebGLPaintTaskData {
     WebRender(webrender_traits::RenderApi, webrender_traits::WebGLContextId),
-    Readback(GLContext<NativeGLContext>, (Option<(webrender_traits::RenderApi, webrender_traits::ImageKey)>)),
+    Readback(GLContextWrapper, (Option<(webrender_traits::RenderApi, webrender_traits::ImageKey)>)),
 }
 
 pub struct WebGLPaintThread {
     size: Size2D<i32>,
     data: WebGLPaintTaskData,
 }
 
 fn create_readback_painter(size: Size2D<i32>,
                            attrs: GLContextAttributes,
                            webrender_api: Option<webrender_traits::RenderApi>)
     -> Result<(WebGLPaintThread, GLLimits), String> {
-    let context = try!(GLContext::<NativeGLContext>::new(size, attrs, ColorAttachmentType::Texture, None));
-    let limits = context.borrow_limits().clone();
+    let context = try!(GLContextWrapper::new(size, attrs));
+    let limits = context.get_limits();
     let webrender_api_and_image_key = webrender_api.map(|wr| {
         let key = wr.alloc_image();
         (wr, key)
     });
     let painter = WebGLPaintThread {
         size: size,
         data: WebGLPaintTaskData::Readback(context, webrender_api_and_image_key)
     };
@@ -68,17 +140,17 @@ impl WebGLPaintThread {
 
     fn handle_webgl_message(&self, message: webrender_traits::WebGLCommand) {
         debug!("WebGL message: {:?}", message);
         match self.data {
             WebGLPaintTaskData::WebRender(ref api, id) => {
                 api.send_webgl_command(id, message);
             }
             WebGLPaintTaskData::Readback(ref ctx, _) => {
-                message.apply(ctx);
+                ctx.apply_command(message);
             }
         }
     }
 
     /// Creates a new `WebGLPaintThread` and returns an `IpcSender` to
     /// communicate with it.
     pub fn start(size: Size2D<i32>,
                  attrs: GLContextAttributes,
@@ -169,34 +241,33 @@ impl WebGLPaintThread {
     }
 
     #[allow(unsafe_code)]
     fn recreate(&mut self, size: Size2D<i32>) -> Result<(), &'static str> {
         match self.data {
             WebGLPaintTaskData::Readback(ref mut context, _) => {
                 if size.width > self.size.width ||
                    size.height > self.size.height {
-                    try!(context.resize(size));
-                    self.size = context.borrow_draw_buffer().unwrap().size();
+                    self.size = try!(context.resize(size));
                 } else {
                     self.size = size;
                     unsafe { gl::Scissor(0, 0, size.width, size.height); }
                 }
             }
             WebGLPaintTaskData::WebRender(_, _) => {
                 // TODO
             }
         }
 
         Ok(())
     }
 
     fn init(&mut self) {
         if let WebGLPaintTaskData::Readback(ref context, _) = self.data {
-            context.make_current().unwrap();
+            context.make_current();
         }
     }
 }
 
 impl Drop for WebGLPaintThread {
     fn drop(&mut self) {
         if let WebGLPaintTaskData::Readback(_, Some((ref mut wr, image_key))) = self.data {
             wr.delete_image(image_key);
--- a/servo/components/script/dom/webidls/Element.webidl
+++ b/servo/components/script/dom/webidls/Element.webidl
@@ -80,32 +80,24 @@ interface Element : Node {
   void insertAdjacentHTML(DOMString position, DOMString html);
 };
 
 // http://dev.w3.org/csswg/cssom-view/#extensions-to-the-element-interface
 partial interface Element {
   DOMRectList getClientRects();
   DOMRect getBoundingClientRect();
 
-  [Func="::script_can_initiate_scroll"]
   void scroll(optional ScrollToOptions options);
-  [Func="::script_can_initiate_scroll"]
   void scroll(unrestricted double x, unrestricted double y);
 
-  [Func="::script_can_initiate_scroll"]
   void scrollTo(optional ScrollToOptions options);
-  [Func="::script_can_initiate_scroll"]
   void scrollTo(unrestricted double x, unrestricted double y);
-  [Func="::script_can_initiate_scroll"]
   void scrollBy(optional ScrollToOptions options);
-  [Func="::script_can_initiate_scroll"]
   void scrollBy(unrestricted double x, unrestricted double y);
-  [Func="::script_can_initiate_scroll"]
   attribute unrestricted double scrollTop;
-  [Func="::script_can_initiate_scroll"]
   attribute unrestricted double scrollLeft;
   readonly attribute long scrollWidth;
   readonly attribute long scrollHeight;
 
   readonly attribute long clientTop;
   readonly attribute long clientLeft;
   readonly attribute long clientWidth;
   readonly attribute long clientHeight;
--- a/servo/components/script/dom/webidls/Window.webidl
+++ b/servo/components/script/dom/webidls/Window.webidl
@@ -133,27 +133,21 @@ partial interface Window {
   readonly attribute long innerWidth;
   readonly attribute long innerHeight;
 
   // viewport scrolling
   readonly attribute long scrollX;
   readonly attribute long pageXOffset;
   readonly attribute long scrollY;
   readonly attribute long pageYOffset;
-  [Func="::script_can_initiate_scroll"]
   void scroll(optional ScrollToOptions options);
-  [Func="::script_can_initiate_scroll"]
   void scroll(unrestricted double x, unrestricted double y);
-  [Func="::script_can_initiate_scroll"]
   void scrollTo(optional ScrollToOptions options);
-  [Func="::script_can_initiate_scroll"]
   void scrollTo(unrestricted double x, unrestricted double y);
-  [Func="::script_can_initiate_scroll"]
   void scrollBy(optional ScrollToOptions options);
-  [Func="::script_can_initiate_scroll"]
   void scrollBy(unrestricted double x, unrestricted double y);
 
   // client
   readonly attribute long screenX;
   readonly attribute long screenY;
   readonly attribute long outerWidth;
   readonly attribute long outerHeight;
   readonly attribute double devicePixelRatio;
--- a/servo/components/script/lib.rs
+++ b/servo/components/script/lib.rs
@@ -109,20 +109,18 @@ mod serviceworker_manager;
 mod task_source;
 pub mod textinput;
 mod timers;
 mod unpremultiplytable;
 mod webdriver_handlers;
 
 use dom::bindings::codegen::RegisterBindings;
 use dom::bindings::proxyhandler;
-use js::jsapi::{Handle, JSContext, JSObject};
 use script_traits::SWManagerSenders;
 use serviceworker_manager::ServiceWorkerManager;
-use util::opts;
 
 #[cfg(target_os = "linux")]
 #[allow(unsafe_code)]
 fn perform_platform_specific_initialization() {
     use std::mem;
     // 4096 is default max on many linux systems
     const MAX_FILE_LIMIT: libc::rlim_t = 4096;
 
@@ -170,18 +168,8 @@ pub fn init(sw_senders: SWManagerSenders
     ServiceWorkerManager::spawn_manager(sw_senders);
 
     // Create the global vtables used by the (generated) DOM
     // bindings to implement JS proxies.
     RegisterBindings::RegisterProxyHandlers();
 
     perform_platform_specific_initialization();
 }
-
-/// FIXME(pcwalton): Currently WebRender cannot handle DOM-initiated scrolls. Remove this when it
-/// can. See PR #11680 for details.
-///
-/// This function is only marked `unsafe` because the `[Func=foo]` WebIDL attribute requires it. It
-/// shouldn't actually do anything unsafe.
-#[allow(unsafe_code)]
-pub unsafe fn script_can_initiate_scroll(_: *mut JSContext, _: Handle<*mut JSObject>) -> bool {
-    !opts::get().use_webrender
-}
--- a/servo/components/servo/Cargo.lock
+++ b/servo/components/servo/Cargo.lock
@@ -230,17 +230,17 @@ dependencies = [
  "azure 0.8.0 (git+https://github.com/servo/rust-azure)",
  "canvas_traits 0.0.1",
  "euclid 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "gfx_traits 0.0.1",
  "gleam 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)",
  "ipc-channel 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "num-traits 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)",
- "offscreen_gl_context 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "offscreen_gl_context 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "plugins 0.0.1",
  "util 0.0.1",
  "webrender_traits 0.5.1 (git+https://github.com/servo/webrender)",
 ]
 
 [[package]]
 name = "canvas_traits"
 version = "0.0.1"
@@ -364,17 +364,17 @@ dependencies = [
  "gfx 0.0.1",
  "gfx_traits 0.0.1",
  "ipc-channel 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "layers 0.5.3 (git+https://github.com/servo/rust-layers)",
  "layout_traits 0.0.1",
  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "msg 0.0.1",
  "net_traits 0.0.1",
- "offscreen_gl_context 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "offscreen_gl_context 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "plugins 0.0.1",
  "profile_traits 0.0.1",
  "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
  "script_traits 0.0.1",
  "serde 0.8.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_macros 0.8.9 (registry+https://github.com/rust-lang/crates.io-index)",
  "style_traits 0.0.1",
  "url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -897,16 +897,18 @@ dependencies = [
  "compositing 0.0.1",
  "euclid 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "gdi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "gleam 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)",
  "layers 0.5.3 (git+https://github.com/servo/rust-layers)",
  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "msg 0.0.1",
  "net_traits 0.0.1",
+ "osmesa-src 12.0.1 (git+https://github.com/servo/osmesa-src)",
+ "osmesa-sys 0.1.2 (git+https://github.com/daggerbot/osmesa-rs)",
  "script_traits 0.0.1",
  "servo-egl 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo-glutin 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "style_traits 0.0.1",
  "url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "user32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "util 0.0.1",
  "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1612,25 +1614,26 @@ dependencies = [
 
 [[package]]
 name = "odds"
 version = "0.2.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "offscreen_gl_context"
-version = "0.4.2"
+version = "0.4.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "cgl 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-foundation 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "gl_generator 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "gleam 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "osmesa-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 0.8.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "x11 2.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "ogg"
 version = "0.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1700,18 +1703,31 @@ name = "ordered-float"
 version = "0.2.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "num-traits 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)",
  "unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
+name = "osmesa-src"
+version = "12.0.1"
+source = "git+https://github.com/servo/osmesa-src#aaccb7b7acdbcd54708db6530cea4177642cf64c"
+
+[[package]]
 name = "osmesa-sys"
-version = "0.1.1"
+version = "0.1.2"
+source = "git+https://github.com/daggerbot/osmesa-rs#7ef7ebc612302794e7ed3bd068c93b70950218a1"
+dependencies = [
+ "shared_library 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "osmesa-sys"
+version = "0.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "shared_library 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "phf"
 version = "0.7.16"
@@ -1966,17 +1982,17 @@ dependencies = [
  "js 0.1.3 (git+https://github.com/servo/rust-mozjs)",
  "libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "mime 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "mime_guess 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "msg 0.0.1",
  "net_traits 0.0.1",
  "num-traits 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)",
- "offscreen_gl_context 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "offscreen_gl_context 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "open 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "phf 0.7.16 (registry+https://github.com/rust-lang/crates.io-index)",
  "phf_macros 0.7.16 (registry+https://github.com/rust-lang/crates.io-index)",
  "plugins 0.0.1",
  "profile_traits 0.0.1",
  "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
  "range 0.0.1",
  "ref_slice 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2050,17 +2066,17 @@ dependencies = [
  "heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "heapsize_plugin 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "hyper_serde 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "ipc-channel 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "layers 0.5.3 (git+https://github.com/servo/rust-layers)",
  "libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
  "msg 0.0.1",
  "net_traits 0.0.1",
- "offscreen_gl_context 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "offscreen_gl_context 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "plugins 0.0.1",
  "profile_traits 0.0.1",
  "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 0.8.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_macros 0.8.9 (registry+https://github.com/rust-lang/crates.io-index)",
  "style_traits 0.0.1",
  "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)",
  "url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2175,17 +2191,17 @@ dependencies = [
  "dwmapi-sys 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "gdi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "gl_generator 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "image 0.10.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
  "objc 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "osmesa-sys 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "osmesa-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "shared_library 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "shell32-sys 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "user32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "wayland-client 0.5.12 (registry+https://github.com/rust-lang/crates.io-index)",
  "wayland-kbd 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "wayland-window 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "x11-dl 2.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2640,51 +2656,51 @@ dependencies = [
  "util 0.0.1",
  "uuid 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "webdriver 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "webrender"
 version = "0.5.1"
-source = "git+https://github.com/servo/webrender#58b9e983a5e74ac1670fcf67f9c2ba68740ab2cc"
+source = "git+https://github.com/servo/webrender#cf945d15c71c757c6694b40a38fd7cfef1a2f827"
 dependencies = [
  "app_units 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "bincode 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "bit-set 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-graphics 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-text 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "fnv 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "freetype 0.1.0 (git+https://github.com/servo/rust-freetype)",
  "gleam 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)",
  "ipc-channel 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "num-traits 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)",
- "offscreen_gl_context 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "offscreen_gl_context 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "rayon 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)",
  "webrender_traits 0.5.1 (git+https://github.com/servo/webrender)",
 ]
 
 [[package]]
 name = "webrender_traits"
 version = "0.5.1"
-source = "git+https://github.com/servo/webrender#58b9e983a5e74ac1670fcf67f9c2ba68740ab2cc"
+source = "git+https://github.com/servo/webrender#cf945d15c71c757c6694b40a38fd7cfef1a2f827"
 dependencies = [
  "app_units 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-graphics 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "gleam 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)",
  "heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "ipc-channel 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "offscreen_gl_context 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "offscreen_gl_context 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 0.8.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_macros 0.8.9 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "websocket"
 version = "0.17.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2897,26 +2913,28 @@ dependencies = [
 "checksum num-bigint 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)" = "88b14378471f7c2adc5262f05b4701ef53e8da376453a8d8fee48e51db745e49"
 "checksum num-integer 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "fb24d9bfb3f222010df27995441ded1e954f8f69cd35021f6bef02ca9552fb92"
 "checksum num-iter 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "287a1c9969a847055e1122ec0ea7a5c5d6f72aad97934e131c83d5c08ab4e45c"
 "checksum num-rational 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)" = "54ff603b8334a72fbb27fe66948aac0abaaa40231b3cecd189e76162f6f38aaf"
 "checksum num-traits 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)" = "8359ea48994f253fa958b5b90b013728b06f54872e5a58bce39540fcdd0f2527"
 "checksum num_cpus 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "cee7e88156f3f9e19bdd598f8d6c9db7bf4078f99f8381f43a55b09648d1a6e3"
 "checksum objc 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7c9311aa5acd7bee14476afa0f0557f564e9d0d61218a8b833d9b1f871fa5fba"
 "checksum odds 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)" = "f3701cfdec1676e319ad37ff96c31de39df8c92006032976153366f52693bf40"
-"checksum offscreen_gl_context 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0860b03349dd951ade7f4fdb1f6724a76a3750b094a760d2aeee1af1d4706400"
+"checksum offscreen_gl_context 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ffcda42bebbbbee2b403bc46d8ea85e0dcf25b5242592bde6eae7788d67cd655"
 "checksum ogg 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "426d8dc59cdd206be1925461087350385c0a02f291d87625829c6d08e72b457b"
 "checksum ogg_metadata 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e755cc735fa6faa709cb23048433d9201d6caa85fa96215386ccdd5e9b40ad01"
 "checksum open 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c228597177bc4a6876e278f7c7948ac033bfcb4d163ccdd5a009557c8fe5fa1e"
 "checksum openssl 0.7.14 (registry+https://github.com/rust-lang/crates.io-index)" = "c4117b6244aac42ed0150a6019b4d953d28247c5dd6ae6f46ae469b5f2318733"
 "checksum openssl-sys 0.7.17 (registry+https://github.com/rust-lang/crates.io-index)" = "89c47ee94c352eea9ddaf8e364be7f978a3bb6d66d73176572484238dd5a5c3f"
 "checksum openssl-sys-extras 0.7.14 (registry+https://github.com/rust-lang/crates.io-index)" = "11c5e1dba7d3d03d80f045bf0d60111dc69213b67651e7c889527a3badabb9fa"
 "checksum openssl-verify 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ed86cce894f6b0ed4572e21eb34026f1dc8869cb9ee3869029131bc8c3feb2d"
 "checksum ordered-float 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cc511538298611a79d5a4ddfbb75315b866d942ed26a00bdc3590795c68b7279"
-"checksum osmesa-sys 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b0040392971435cdab6bb52f0d4dc2fbb959c90b4263bec33af6ef092f8f828d"
+"checksum osmesa-src 12.0.1 (git+https://github.com/servo/osmesa-src)" = "<none>"
+"checksum osmesa-sys 0.1.2 (git+https://github.com/daggerbot/osmesa-rs)" = "<none>"
+"checksum osmesa-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "88cfece6e95d2e717e0872a7f53a8684712ad13822a7979bc760b9c77ec0013b"
 "checksum phf 0.7.16 (registry+https://github.com/rust-lang/crates.io-index)" = "52c875926de24c01b5b69153eaa258b57920a39b44bbce8ef1f2052a6c5a6a1a"
 "checksum phf_codegen 0.7.16 (registry+https://github.com/rust-lang/crates.io-index)" = "0a8912c2cc0ea2e8ae70388ec0af9a445e7ab37b3c9cc61f059c2b34db8ed50b"
 "checksum phf_generator 0.7.16 (registry+https://github.com/rust-lang/crates.io-index)" = "04b5ea825e28cb6efd89d9133b129b2003b45a221aeda025509b125b00ecb7c4"
 "checksum phf_macros 0.7.16 (registry+https://github.com/rust-lang/crates.io-index)" = "132ea70eed654520d98d0e54e262292a94bd5f150671d98b9c8d0782fafce37e"
 "checksum phf_shared 0.7.16 (registry+https://github.com/rust-lang/crates.io-index)" = "2c43b5dbe94d31f1f4ed45c50bb06d70e72fd53f15422b0a915b5c237e130dd6"
 "checksum pkg-config 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8cee804ecc7eaf201a4a207241472cc870e825206f6c031e3ee2a72fa425f2fa"
 "checksum pnacl-build-helper 1.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "61c9231d31aea845007443d62fcbb58bb6949ab9c18081ee1e09920e0cf1118b"
 "checksum png 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "06208e2ee243e3118a55dda9318f821f206d8563fb8d4df258767f8e62bb0997"
--- a/servo/components/servo/lib.rs
+++ b/servo/components/servo/lib.rs
@@ -142,27 +142,34 @@ impl<Window> Browser<Window> where Windo
                 let device_pixel_ratio = match opts.device_pixels_per_px {
                     Some(device_pixels_per_px) => device_pixels_per_px,
                     None => match opts.output_file {
                         Some(_) => 1.0,
                         None => scale_factor,
                     }
                 };
 
+                let renderer_kind = if opts::get().should_use_osmesa() {
+                    webrender_traits::RendererKind::OSMesa
+                } else {
+                    webrender_traits::RendererKind::Native
+                };
+
                 let (webrender, webrender_sender) =
                     webrender::Renderer::new(webrender::RendererOptions {
                         device_pixel_ratio: device_pixel_ratio,
                         resource_path: resource_path,
                         enable_aa: opts.enable_text_antialiasing,
                         enable_msaa: opts.use_msaa,
                         enable_profiler: opts.webrender_stats,
                         debug: opts.webrender_debug,
                         enable_recording: false,
                         precache_shaders: opts.precache_shaders,
                         enable_scrollbars: opts.output_file.is_none(),
+                        renderer_kind: renderer_kind,
                     });
                 (Some(webrender), Some(webrender_sender))
             } else {
                 (None, None)
             }
         } else {
             (None, None)
         };
--- a/servo/components/util/opts.rs
+++ b/servo/components/util/opts.rs
@@ -786,18 +786,17 @@ pub fn from_cmdline_args(args: &[String]
         (contents, url)
     }).collect();
 
     let do_not_use_native_titlebar =
         opt_match.opt_present("b") ||
         !PREFS.get("shell.native-titlebar.enabled").as_boolean().unwrap();
 
     let use_webrender =
-        (PREFS.get("gfx.webrender.enabled").as_boolean().unwrap() || opt_match.opt_present("w")) &&
-        !opt_match.opt_present("z");
+        PREFS.get("gfx.webrender.enabled").as_boolean().unwrap() || opt_match.opt_present("w");
 
     let render_api = match opt_match.opt_str("G") {
         Some(ref ga) if ga == "gl" => RenderApi::GL,
         Some(ref ga) if ga == "es2" => RenderApi::ES2,
         None => DEFAULT_RENDER_API,
         _ => args_fail(&format!("error: graphics option must be gl or es2:")),
     };
 
@@ -939,8 +938,14 @@ pub fn parse_url_or_filename(cwd: &Path,
     match Url::parse(input) {
         Ok(url) => Ok(url),
         Err(url::ParseError::RelativeUrlWithoutBase) => {
             Url::from_file_path(&*cwd.join(input))
         }
         Err(_) => Err(()),
     }
 }
+
+impl Opts {
+    pub fn should_use_osmesa(&self) -> bool {
+        self.headless
+    }
+}
--- a/servo/ports/cef/Cargo.lock
+++ b/servo/ports/cef/Cargo.lock
@@ -203,17 +203,17 @@ dependencies = [
  "azure 0.8.0 (git+https://github.com/servo/rust-azure)",
  "canvas_traits 0.0.1",
  "euclid 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "gfx_traits 0.0.1",
  "gleam 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)",
  "ipc-channel 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "num-traits 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)",
- "offscreen_gl_context 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "offscreen_gl_context 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "plugins 0.0.1",
  "util 0.0.1",
  "webrender_traits 0.5.1 (git+https://github.com/servo/webrender)",
 ]
 
 [[package]]
 name = "canvas_traits"
 version = "0.0.1"
@@ -321,17 +321,17 @@ dependencies = [
  "gfx 0.0.1",
  "gfx_traits 0.0.1",
  "ipc-channel 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "layers 0.5.3 (git+https://github.com/servo/rust-layers)",
  "layout_traits 0.0.1",
  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "msg 0.0.1",
  "net_traits 0.0.1",
- "offscreen_gl_context 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "offscreen_gl_context 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "plugins 0.0.1",
  "profile_traits 0.0.1",
  "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
  "script_traits 0.0.1",
  "serde 0.8.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_macros 0.8.9 (registry+https://github.com/rust-lang/crates.io-index)",
  "style_traits 0.0.1",
  "url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -804,16 +804,18 @@ dependencies = [
  "compositing 0.0.1",
  "euclid 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "gdi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "gleam 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)",
  "layers 0.5.3 (git+https://github.com/servo/rust-layers)",
  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "msg 0.0.1",
  "net_traits 0.0.1",
+ "osmesa-src 12.0.1 (git+https://github.com/servo/osmesa-src)",
+ "osmesa-sys 0.1.2 (git+https://github.com/daggerbot/osmesa-rs)",
  "script_traits 0.0.1",
  "servo-egl 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo-glutin 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "style_traits 0.0.1",
  "url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "user32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "util 0.0.1",
  "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1483,25 +1485,26 @@ dependencies = [
 
 [[package]]
 name = "odds"
 version = "0.2.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "offscreen_gl_context"
-version = "0.4.2"
+version = "0.4.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "cgl 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-foundation 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "gl_generator 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "gleam 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "osmesa-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 0.8.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "x11 2.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "ogg"
 version = "0.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1571,18 +1574,31 @@ name = "ordered-float"
 version = "0.2.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "num-traits 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)",
  "unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
+name = "osmesa-src"
+version = "12.0.1"
+source = "git+https://github.com/servo/osmesa-src#aaccb7b7acdbcd54708db6530cea4177642cf64c"
+
+[[package]]
 name = "osmesa-sys"
-version = "0.1.1"
+version = "0.1.2"
+source = "git+https://github.com/daggerbot/osmesa-rs#7ef7ebc612302794e7ed3bd068c93b70950218a1"
+dependencies = [
+ "shared_library 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "osmesa-sys"
+version = "0.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "shared_library 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "phf"
 version = "0.7.16"
@@ -1817,17 +1833,17 @@ dependencies = [
  "js 0.1.3 (git+https://github.com/servo/rust-mozjs)",
  "libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "mime 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "mime_guess 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "msg 0.0.1",
  "net_traits 0.0.1",
  "num-traits 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)",
- "offscreen_gl_context 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "offscreen_gl_context 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "open 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "phf 0.7.16 (registry+https://github.com/rust-lang/crates.io-index)",
  "phf_macros 0.7.16 (registry+https://github.com/rust-lang/crates.io-index)",
  "plugins 0.0.1",
  "profile_traits 0.0.1",
  "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
  "range 0.0.1",
  "ref_slice 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1891,17 +1907,17 @@ dependencies = [
  "heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "heapsize_plugin 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "hyper_serde 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "ipc-channel 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "layers 0.5.3 (git+https://github.com/servo/rust-layers)",
  "libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
  "msg 0.0.1",
  "net_traits 0.0.1",
- "offscreen_gl_context 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "offscreen_gl_context 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "plugins 0.0.1",
  "profile_traits 0.0.1",
  "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 0.8.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_macros 0.8.9 (registry+https://github.com/rust-lang/crates.io-index)",
  "style_traits 0.0.1",
  "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)",
  "url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2058,17 +2074,17 @@ dependencies = [
  "dwmapi-sys 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "gdi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "gl_generator 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "image 0.10.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
  "objc 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "osmesa-sys 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "osmesa-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "shared_library 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "shell32-sys 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "user32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "wayland-client 0.5.12 (registry+https://github.com/rust-lang/crates.io-index)",
  "wayland-kbd 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "wayland-window 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "x11-dl 2.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2500,51 +2516,51 @@ dependencies = [
  "util 0.0.1",
  "uuid 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "webdriver 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "webrender"
 version = "0.5.1"
-source = "git+https://github.com/servo/webrender#58b9e983a5e74ac1670fcf67f9c2ba68740ab2cc"
+source = "git+https://github.com/servo/webrender#cf945d15c71c757c6694b40a38fd7cfef1a2f827"
 dependencies = [
  "app_units 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "bincode 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "bit-set 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-graphics 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-text 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "fnv 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "freetype 0.1.0 (git+https://github.com/servo/rust-freetype)",
  "gleam 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)",
  "ipc-channel 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "num-traits 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)",
- "offscreen_gl_context 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "offscreen_gl_context 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "rayon 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)",
  "webrender_traits 0.5.1 (git+https://github.com/servo/webrender)",
 ]
 
 [[package]]
 name = "webrender_traits"
 version = "0.5.1"
-source = "git+https://github.com/servo/webrender#58b9e983a5e74ac1670fcf67f9c2ba68740ab2cc"
+source = "git+https://github.com/servo/webrender#cf945d15c71c757c6694b40a38fd7cfef1a2f827"
 dependencies = [
  "app_units 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-graphics 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "gleam 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)",
  "heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "ipc-channel 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "offscreen_gl_context 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "offscreen_gl_context 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 0.8.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_macros 0.8.9 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "websocket"
 version = "0.17.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2751,26 +2767,28 @@ dependencies = [
 "checksum num-bigint 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)" = "88b14378471f7c2adc5262f05b4701ef53e8da376453a8d8fee48e51db745e49"
 "checksum num-integer 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "fb24d9bfb3f222010df27995441ded1e954f8f69cd35021f6bef02ca9552fb92"
 "checksum num-iter 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "287a1c9969a847055e1122ec0ea7a5c5d6f72aad97934e131c83d5c08ab4e45c"
 "checksum num-rational 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)" = "54ff603b8334a72fbb27fe66948aac0abaaa40231b3cecd189e76162f6f38aaf"
 "checksum num-traits 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)" = "8359ea48994f253fa958b5b90b013728b06f54872e5a58bce39540fcdd0f2527"
 "checksum num_cpus 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "cee7e88156f3f9e19bdd598f8d6c9db7bf4078f99f8381f43a55b09648d1a6e3"
 "checksum objc 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7c9311aa5acd7bee14476afa0f0557f564e9d0d61218a8b833d9b1f871fa5fba"
 "checksum odds 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)" = "f3701cfdec1676e319ad37ff96c31de39df8c92006032976153366f52693bf40"
-"checksum offscreen_gl_context 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0860b03349dd951ade7f4fdb1f6724a76a3750b094a760d2aeee1af1d4706400"
+"checksum offscreen_gl_context 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ffcda42bebbbbee2b403bc46d8ea85e0dcf25b5242592bde6eae7788d67cd655"
 "checksum ogg 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "426d8dc59cdd206be1925461087350385c0a02f291d87625829c6d08e72b457b"
 "checksum ogg_metadata 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e755cc735fa6faa709cb23048433d9201d6caa85fa96215386ccdd5e9b40ad01"
 "checksum open 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c228597177bc4a6876e278f7c7948ac033bfcb4d163ccdd5a009557c8fe5fa1e"
 "checksum openssl 0.7.14 (registry+https://github.com/rust-lang/crates.io-index)" = "c4117b6244aac42ed0150a6019b4d953d28247c5dd6ae6f46ae469b5f2318733"
 "checksum openssl-sys 0.7.17 (registry+https://github.com/rust-lang/crates.io-index)" = "89c47ee94c352eea9ddaf8e364be7f978a3bb6d66d73176572484238dd5a5c3f"
 "checksum openssl-sys-extras 0.7.14 (registry+https://github.com/rust-lang/crates.io-index)" = "11c5e1dba7d3d03d80f045bf0d60111dc69213b67651e7c889527a3badabb9fa"
 "checksum openssl-verify 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ed86cce894f6b0ed4572e21eb34026f1dc8869cb9ee3869029131bc8c3feb2d"
 "checksum ordered-float 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cc511538298611a79d5a4ddfbb75315b866d942ed26a00bdc3590795c68b7279"
-"checksum osmesa-sys 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b0040392971435cdab6bb52f0d4dc2fbb959c90b4263bec33af6ef092f8f828d"
+"checksum osmesa-src 12.0.1 (git+https://github.com/servo/osmesa-src)" = "<none>"
+"checksum osmesa-sys 0.1.2 (git+https://github.com/daggerbot/osmesa-rs)" = "<none>"
+"checksum osmesa-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "88cfece6e95d2e717e0872a7f53a8684712ad13822a7979bc760b9c77ec0013b"
 "checksum phf 0.7.16 (registry+https://github.com/rust-lang/crates.io-index)" = "52c875926de24c01b5b69153eaa258b57920a39b44bbce8ef1f2052a6c5a6a1a"
 "checksum phf_codegen 0.7.16 (registry+https://github.com/rust-lang/crates.io-index)" = "0a8912c2cc0ea2e8ae70388ec0af9a445e7ab37b3c9cc61f059c2b34db8ed50b"
 "checksum phf_generator 0.7.16 (registry+https://github.com/rust-lang/crates.io-index)" = "04b5ea825e28cb6efd89d9133b129b2003b45a221aeda025509b125b00ecb7c4"
 "checksum phf_macros 0.7.16 (registry+https://github.com/rust-lang/crates.io-index)" = "132ea70eed654520d98d0e54e262292a94bd5f150671d98b9c8d0782fafce37e"
 "checksum phf_shared 0.7.16 (registry+https://github.com/rust-lang/crates.io-index)" = "2c43b5dbe94d31f1f4ed45c50bb06d70e72fd53f15422b0a915b5c237e130dd6"
 "checksum pkg-config 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8cee804ecc7eaf201a4a207241472cc870e825206f6c031e3ee2a72fa425f2fa"
 "checksum pnacl-build-helper 1.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "61c9231d31aea845007443d62fcbb58bb6949ab9c18081ee1e09920e0cf1118b"
 "checksum png 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "06208e2ee243e3118a55dda9318f821f206d8563fb8d4df258767f8e62bb0997"
--- a/servo/ports/glutin/Cargo.toml
+++ b/servo/ports/glutin/Cargo.toml
@@ -18,18 +18,27 @@ log = "0.3.5"
 msg = {path = "../../components/msg"}
 net_traits = {path = "../../components/net_traits"}
 script_traits = {path = "../../components/script_traits"}
 servo-glutin = "0.6"
 style_traits = {path = "../../components/style_traits"}
 url = {version = "1.2", features = ["heap_size"]}
 util = {path = "../../components/util"}
 
+[target.'cfg(any(target_os = "linux", target_os = "macos"))'.dependencies]
+osmesa-sys = {git = "https://github.com/daggerbot/osmesa-rs"}
+
 [target.'cfg(target_os = "linux")'.dependencies]
 x11 = "2.0.0"
 
 [target.'cfg(target_os = "android")'.dependencies]
 servo-egl = "0.2"
 
 [target.'cfg(target_os = "windows")'.dependencies]
 winapi = "0.2"
 user32-sys = "0.2"
 gdi32-sys = "0.2"
+
+[target.'cfg(target_os = "macos")'.dependencies]
+osmesa-src = {git = "https://github.com/servo/osmesa-src"}
+
+[target.x86_64-unknown-linux-gnu.dependencies]
+osmesa-src = {git = "https://github.com/servo/osmesa-src"}
--- a/servo/ports/glutin/lib.rs
+++ b/servo/ports/glutin/lib.rs
@@ -12,16 +12,17 @@ extern crate compositing;
 #[cfg(target_os = "android")] extern crate egl;
 extern crate euclid;
 extern crate gleam;
 extern crate glutin;
 extern crate layers;
 #[macro_use] extern crate log;
 extern crate msg;
 extern crate net_traits;
+#[cfg(any(target_os = "linux", target_os = "macos"))] extern crate osmesa_sys;
 extern crate script_traits;
 extern crate style_traits;
 extern crate url;
 extern crate util;
 #[cfg(target_os = "linux")] extern crate x11;
 #[cfg(target_os = "windows")] extern crate winapi;
 #[cfg(target_os = "windows")] extern crate user32;
 #[cfg(target_os = "windows")] extern crate gdi32;
--- a/servo/ports/glutin/window.rs
+++ b/servo/ports/glutin/window.rs
@@ -19,20 +19,27 @@ use glutin::{Api, ElementState, Event, G
 use glutin::{ScanCode, TouchPhase};
 #[cfg(target_os = "macos")]
 use glutin::os::macos::{ActivationPolicy, WindowBuilderExt};
 use layers::geometry::DevicePixel;
 use layers::platform::surface::NativeDisplay;
 use msg::constellation_msg::{self, Key};
 use msg::constellation_msg::{ALT, CONTROL, KeyState, NONE, SHIFT, SUPER};
 use net_traits::net_error_list::NetError;
+#[cfg(any(target_os = "linux", target_os = "macos"))]
+use osmesa_sys;
 use script_traits::{TouchEventType, TouchpadPressurePhase};
 use std::cell::{Cell, RefCell};
+#[cfg(any(target_os = "linux", target_os = "macos"))]
+use std::ffi::CString;
+#[cfg(any(target_os = "linux", target_os = "macos"))]
+use std::mem;
 #[cfg(not(target_os = "android"))]
 use std::os::raw::c_void;
+use std::ptr;
 use std::rc::Rc;
 use std::sync::mpsc::{Sender, channel};
 use style_traits::cursor::Cursor;
 use url::Url;
 #[cfg(target_os = "windows")]
 use user32;
 use util::geometry::ScreenPx;
 use util::opts;
@@ -86,19 +93,98 @@ fn builder_with_platform_options(mut bui
     builder.with_app_name(String::from("Servo"))
 }
 
 #[cfg(not(target_os = "macos"))]
 fn builder_with_platform_options(builder: glutin::WindowBuilder) -> glutin::WindowBuilder {
     builder
 }
 
+#[cfg(any(target_os = "linux", target_os = "macos"))]
+struct HeadlessContext {
+    width: u32,
+    height: u32,
+    _context: osmesa_sys::OSMesaContext,
+    _buffer: Vec<u32>,
+}
+
+#[cfg(not(any(target_os = "linux", target_os = "macos")))]
+struct HeadlessContext {
+    width: u32,
+    height: u32,
+}
+
+impl HeadlessContext {
+    #[cfg(any(target_os = "linux", target_os = "macos"))]
+    fn new(width: u32, height: u32) -> HeadlessContext {
+        let mut attribs = Vec::new();
+
+        attribs.push(osmesa_sys::OSMESA_PROFILE);
+        attribs.push(osmesa_sys::OSMESA_CORE_PROFILE);
+        attribs.push(osmesa_sys::OSMESA_CONTEXT_MAJOR_VERSION);
+        attribs.push(3);
+        attribs.push(osmesa_sys::OSMESA_CONTEXT_MINOR_VERSION);
+        attribs.push(3);
+        attribs.push(0);
+
+        let context = unsafe {
+            osmesa_sys::OSMesaCreateContextAttribs(attribs.as_ptr(), ptr::null_mut())
+        };
+
+        assert!(!context.is_null());
+
+        let mut buffer = vec![0; (width * height) as usize];
+
+        unsafe {
+            let ret = osmesa_sys::OSMesaMakeCurrent(context,
+                                                    buffer.as_mut_ptr() as *mut _,
+                                                    gl::UNSIGNED_BYTE,
+                                                    width as i32,
+                                                    height as i32);
+            assert!(ret != 0);
+        };
+
+        HeadlessContext {
+            width: width,
+            height: height,
+            _context: context,
+            _buffer: buffer,
+        }
+    }
+
+    #[cfg(not(any(target_os = "linux", target_os = "macos")))]
+    fn new(width: u32, height: u32) -> HeadlessContext {
+        HeadlessContext {
+            width: width,
+            height: height,
+        }
+    }
+
+    #[cfg(any(target_os = "linux", target_os = "macos"))]
+    fn get_proc_address(s: &str) -> *const c_void {
+        let c_str = CString::new(s).expect("Unable to create CString");
+        unsafe {
+            mem::transmute(osmesa_sys::OSMesaGetProcAddress(c_str.as_ptr()))
+        }
+    }
+
+    #[cfg(not(any(target_os = "linux", target_os = "macos")))]
+    fn get_proc_address(_: &str) -> *const c_void {
+        ptr::null() as *const _
+    }
+}
+
+enum WindowKind {
+    Window(glutin::Window),
+    Headless(HeadlessContext),
+}
+
 /// The type of a window.
 pub struct Window {
-    window: glutin::Window,
+    kind: WindowKind,
 
     mouse_down_button: Cell<Option<glutin::MouseButton>>,
     mouse_down_point: Cell<Point2D<i32>>,
     event_queue: RefCell<Vec<WindowEvent>>,
 
     mouse_pos: Cell<Point2D<i32>>,
     key_modifiers: Cell<KeyModifiers>,
     current_url: RefCell<Option<Url>>,
@@ -134,53 +220,65 @@ impl Window {
         let height = win_size.to_untyped().height;
 
         // If there's no chrome, start off with the window invisible. It will be set to visible in
         // `load_end()`. This avoids an ugly flash of unstyled content (especially important since
         // unstyled content is white and chrome often has a transparent background). See issue
         // #9996.
         let visible = is_foreground && !opts::get().no_native_titlebar;
 
+        let window_kind = if opts::get().headless {
+            WindowKind::Headless(HeadlessContext::new(width, height))
+        } else {
+            let mut builder =
+                glutin::WindowBuilder::new().with_title("Servo".to_string())
+                                            .with_decorations(!opts::get().no_native_titlebar)
+                                            .with_transparency(opts::get().no_native_titlebar)
+                                            .with_dimensions(width, height)
+                                            .with_gl(Window::gl_version())
+                                            .with_visibility(visible)
+                                            .with_parent(parent)
+                                            .with_multitouch();
 
-        let mut builder =
-            glutin::WindowBuilder::new().with_title("Servo".to_string())
-                                        .with_decorations(!opts::get().no_native_titlebar)
-                                        .with_transparency(opts::get().no_native_titlebar)
-                                        .with_dimensions(width, height)
-                                        .with_gl(Window::gl_version())
-                                        .with_visibility(visible)
-                                        .with_parent(parent)
-                                        .with_multitouch();
+            if let Ok(mut icon_path) = resource_files::resources_dir_path() {
+                icon_path.push("servo.png");
+                builder = builder.with_icon(icon_path);
+            }
+
+            if opts::get().enable_vsync {
+                builder = builder.with_vsync();
+            }
+
+            if opts::get().use_msaa {
+                builder = builder.with_multisampling(MULTISAMPLES)
+            }
+
+            builder = builder_with_platform_options(builder);
 
+            let mut glutin_window = builder.build().expect("Failed to create window.");
 
-        if let Ok(mut icon_path) = resource_files::resources_dir_path() {
-            icon_path.push("servo.png");
-            builder = builder.with_icon(icon_path);
+            unsafe { glutin_window.make_current().expect("Failed to make context current!") }
+
+            glutin_window.set_window_resize_callback(Some(Window::nested_window_resize as fn(u32, u32)));
+
+            WindowKind::Window(glutin_window)
+        };
+
+        Window::load_gl_functions(&window_kind);
+
+        if opts::get().headless {
+            // Print some information about the headless renderer that
+            // can be useful in diagnosing CI failures on build machines.
+            println!("{}", gl::get_string(gl::VENDOR));
+            println!("{}", gl::get_string(gl::RENDERER));
+            println!("{}", gl::get_string(gl::VERSION));
         }
 
-        if opts::get().enable_vsync {
-            builder = builder.with_vsync();
-        }
-
-        if opts::get().use_msaa {
-            builder = builder.with_multisampling(MULTISAMPLES)
-        }
-
-        builder = builder_with_platform_options(builder);
-
-        let mut glutin_window = builder.build().expect("Failed to create window.");
-
-        unsafe { glutin_window.make_current().expect("Failed to make context current!") }
-
-        glutin_window.set_window_resize_callback(Some(Window::nested_window_resize as fn(u32, u32)));
-
-        Window::load_gl_functions(&glutin_window);
-
         let window = Window {
-            window: glutin_window,
+            kind: window_kind,
             event_queue: RefCell::new(vec!()),
             mouse_down_button: Cell::new(None),
             mouse_down_point: Cell::new(Point2D::new(0, 0)),
 
             mouse_pos: Cell::new(Point2D::new(0, 0)),
             key_modifiers: Cell::new(KeyModifiers::empty()),
             current_url: RefCell::new(None),
 
@@ -192,17 +290,24 @@ impl Window {
         gl::clear(gl::COLOR_BUFFER_BIT);
         gl::finish();
         window.present();
 
         Rc::new(window)
     }
 
     pub fn platform_window(&self) -> glutin::WindowID {
-        unsafe { glutin::WindowID::new(self.window.platform_window()) }
+        match self.kind {
+            WindowKind::Window(ref window) => {
+                unsafe { glutin::WindowID::new(window.platform_window()) }
+            }
+            WindowKind::Headless(..) => {
+                unreachable!();
+            }
+        }
     }
 
     fn nested_window_resize(width: u32, height: u32) {
         unsafe {
             match g_nested_event_loop_listener {
                 None => {}
                 Some(listener) => {
                     (*listener).handle_event_from_nested_event_loop(
@@ -228,22 +333,31 @@ impl Window {
     }
 
     #[cfg(target_os = "android")]
     fn gl_version() -> GlRequest {
         GlRequest::Specific(Api::OpenGlEs, (3, 0))
     }
 
     #[cfg(not(target_os = "android"))]
-    fn load_gl_functions(window: &glutin::Window) {
-        gl::load_with(|s| window.get_proc_address(s) as *const c_void);
+    fn load_gl_functions(window_kind: &WindowKind) {
+        match window_kind {
+            &WindowKind::Window(ref window) => {
+                gl::load_with(|s| window.get_proc_address(s) as *const c_void);
+            }
+            &WindowKind::Headless(..) => {
+                gl::load_with(|s| {
+                    HeadlessContext::get_proc_address(s)
+                });
+            }
+        }
     }
 
     #[cfg(target_os = "android")]
-    fn load_gl_functions(_: &glutin::Window) {
+    fn load_gl_functions(_: &WindowKind) {
     }
 
     fn handle_window_event(&self, event: glutin::Event) -> bool {
         match event {
             Event::ReceivedCharacter(ch) => {
                 if !ch.is_control() {
                     self.pending_key_event_char.set(Some(ch));
                 }
@@ -412,61 +526,75 @@ impl Window {
                 }
             }
         };
         self.event_queue.borrow_mut().push(WindowEvent::MouseWindowEventClass(event));
     }
 
     #[cfg(any(target_os = "macos", target_os = "windows"))]
     fn handle_next_event(&self) -> bool {
-        let event = match self.window.wait_events().next() {
-            None => {
-                warn!("Window event stream closed.");
-                return false;
-            },
-            Some(event) => event,
-        };
-        let mut close = self.handle_window_event(event);
-        if !close {
-            while let Some(event) = self.window.poll_events().next() {
-                if self.handle_window_event(event) {
-                    close = true;
-                    break
+        match self.kind {
+            WindowKind::Window(ref window) => {
+                let event = match window.wait_events().next() {
+                    None => {
+                        warn!("Window event stream closed.");
+                        return false;
+                    },
+                    Some(event) => event,
+                };
+                let mut close = self.handle_window_event(event);
+                if !close {
+                    while let Some(event) = window.poll_events().next() {
+                        if self.handle_window_event(event) {
+                            close = true;
+                            break
+                        }
+                    }
                 }
+                close
+            }
+            WindowKind::Headless(..) => {
+                false
             }
         }
-        close
     }
 
     #[cfg(any(target_os = "linux", target_os = "android"))]
     fn handle_next_event(&self) -> bool {
         use std::thread;
         use std::time::Duration;
 
         // WebRender can use the normal blocking event check and proper vsync,
         // because it doesn't call X11 functions from another thread, so doesn't
         // hit the same issues explained below.
         if opts::get().use_webrender {
-            let event = match self.window.wait_events().next() {
-                None => {
-                    warn!("Window event stream closed.");
-                    return false;
-                },
-                Some(event) => event,
-            };
-            let mut close = self.handle_window_event(event);
-            if !close {
-                while let Some(event) = self.window.poll_events().next() {
-                    if self.handle_window_event(event) {
-                        close = true;
-                        break
+            match self.kind {
+                WindowKind::Window(ref window) => {
+                    let event = match window.wait_events().next() {
+                        None => {
+                            warn!("Window event stream closed.");
+                            return false;
+                        },
+                        Some(event) => event,
+                    };
+                    let mut close = self.handle_window_event(event);
+                    if !close {
+                        while let Some(event) = window.poll_events().next() {
+                            if self.handle_window_event(event) {
+                                close = true;
+                                break
+                            }
+                        }
                     }
+                    close
+                }
+                WindowKind::Headless(..) => {
+                    false
                 }
             }
-            close
         } else {
             // TODO(gw): This is an awful hack to work around the
             // broken way we currently call X11 from multiple threads.
             //
             // On some (most?) X11 implementations, blocking here
             // with XPeekEvent results in the paint thread getting stuck
             // in XGetGeometry randomly. When this happens the result
             // is that until you trigger the XPeekEvent to return
@@ -475,42 +603,54 @@ impl Window {
             // results.
             //
             // For now, poll events and sleep for ~1 frame if there
             // are no events. This means we don't spin the CPU at
             // 100% usage, but is far from ideal!
             //
             // See https://github.com/servo/servo/issues/5780
             //
-            let first_event = self.window.poll_events().next();
+            match self.kind {
+                WindowKind::Window(ref window) => {
+                    let first_event = window.poll_events().next();
 
-            match first_event {
-                Some(event) => {
-                    self.handle_window_event(event)
+                    match first_event {
+                        Some(event) => {
+                            self.handle_window_event(event)
+                        }
+                        None => {
+                            thread::sleep(Duration::from_millis(16));
+                            false
+                        }
+                    }
                 }
-                None => {
-                    thread::sleep(Duration::from_millis(16));
+                WindowKind::Headless(..) => {
                     false
                 }
             }
         }
     }
 
     pub fn wait_events(&self) -> Vec<WindowEvent> {
         use std::mem;
 
         let mut events = mem::replace(&mut *self.event_queue.borrow_mut(), Vec::new());
         let mut close_event = false;
 
         // When writing to a file then exiting, use event
         // polling so that we don't block on a GUI event
         // such as mouse click.
         if opts::get().output_file.is_some() || opts::get().exit_after_load || opts::get().headless {
-            while let Some(event) = self.window.poll_events().next() {
-                close_event = self.handle_window_event(event) || close_event;
+            match self.kind {
+                WindowKind::Window(ref window) => {
+                    while let Some(event) = window.poll_events().next() {
+                        close_event = self.handle_window_event(event) || close_event;
+                    }
+                }
+                WindowKind::Headless(..) => {}
             }
         } else {
             close_event = self.handle_next_event();
         }
 
         if close_event {
             events.push(WindowEvent::Quit)
         }
@@ -666,54 +806,99 @@ impl Window {
 
 #[cfg(target_os = "android")]
 fn create_window_proxy(_: &Window) -> Option<glutin::WindowProxy> {
     None
 }
 
 #[cfg(not(target_os = "android"))]
 fn create_window_proxy(window: &Window) -> Option<glutin::WindowProxy> {
-    Some(window.window.create_window_proxy())
+    match window.kind {
+        WindowKind::Window(ref window) => {
+            Some(window.create_window_proxy())
+        }
+        WindowKind::Headless(..) => {
+            None
+        }
+    }
 }
 
 impl WindowMethods for Window {
     fn framebuffer_size(&self) -> TypedSize2D<u32, DevicePixel> {
-        let scale_factor = self.window.hidpi_factor() as u32;
-        // TODO(ajeffrey): can this fail?
-        let (width, height) = self.window.get_inner_size().expect("Failed to get window inner size.");
-        TypedSize2D::new(width * scale_factor, height * scale_factor)
+        match self.kind {
+            WindowKind::Window(ref window) => {
+                let scale_factor = window.hidpi_factor() as u32;
+                // TODO(ajeffrey): can this fail?
+                let (width, height) = window.get_inner_size().expect("Failed to get window inner size.");
+                TypedSize2D::new(width * scale_factor, height * scale_factor)
+            }
+            WindowKind::Headless(ref context) => {
+                TypedSize2D::new(context.width, context.height)
+            }
+        }
     }
 
     fn size(&self) -> TypedSize2D<f32, ScreenPx> {
-        // TODO(ajeffrey): can this fail?
-        let (width, height) = self.window.get_inner_size().expect("Failed to get window inner size.");
-        TypedSize2D::new(width as f32, height as f32)
+        match self.kind {
+            WindowKind::Window(ref window) => {
+                // TODO(ajeffrey): can this fail?
+                let (width, height) = window.get_inner_size().expect("Failed to get window inner size.");
+                TypedSize2D::new(width as f32, height as f32)
+            }
+            WindowKind::Headless(ref context) => {
+                TypedSize2D::new(context.width as f32, context.height as f32)
+            }
+        }
     }
 
     fn client_window(&self) -> (Size2D<u32>, Point2D<i32>) {
-        // TODO(ajeffrey): can this fail?
-        let (width, height) = self.window.get_outer_size().expect("Failed to get window outer size.");
-        let size = Size2D::new(width, height);
-        // TODO(ajeffrey): can this fail?
-        let (x, y) = self.window.get_position().expect("Failed to get window position.");
-        let origin = Point2D::new(x as i32, y as i32);
-        (size, origin)
+        match self.kind {
+            WindowKind::Window(ref window) => {
+                // TODO(ajeffrey): can this fail?
+                let (width, height) = window.get_outer_size().expect("Failed to get window outer size.");
+                let size = Size2D::new(width, height);
+                // TODO(ajeffrey): can this fail?
+                let (x, y) = window.get_position().expect("Failed to get window position.");
+                let origin = Point2D::new(x as i32, y as i32);
+                (size, origin)
+            }
+            WindowKind::Headless(ref context) => {
+                let size = TypedSize2D::new(context.width, context.height);
+                (size, Point2D::zero())
+            }
+        }
+
     }
 
     fn set_inner_size(&self, size: Size2D<u32>) {
-        self.window.set_inner_size(size.width as u32, size.height as u32)
+        match self.kind {
+            WindowKind::Window(ref window) => {
+                window.set_inner_size(size.width as u32, size.height as u32)
+            }
+            WindowKind::Headless(..) => {}
+        }
     }
 
     fn set_position(&self, point: Point2D<i32>) {
-        self.window.set_position(point.x, point.y)
+        match self.kind {
+            WindowKind::Window(ref window) => {
+                window.set_position(point.x, point.y)
+            }
+            WindowKind::Headless(..) => {}
+        }
     }
 
     fn present(&self) {
-        if let Err(err) = self.window.swap_buffers() {
-            warn!("Failed to swap window buffers ({}).", err);
+        match self.kind {
+            WindowKind::Window(ref window) => {
+                if let Err(err) = window.swap_buffers() {
+                    warn!("Failed to swap window buffers ({}).", err);
+                }
+            }
+            WindowKind::Headless(..) => {}
         }
     }
 
     fn create_compositor_channel(&self)
                                  -> (Box<CompositorProxy + Send>, Box<CompositorReceiver>) {
         let (sender, receiver) = channel();
 
         let window_proxy = create_window_proxy(self);
@@ -722,121 +907,150 @@ impl WindowMethods for Window {
              sender: sender,
              window_proxy: window_proxy,
          } as Box<CompositorProxy + Send>,
          box receiver as Box<CompositorReceiver>)
     }
 
     #[cfg(not(target_os = "windows"))]
     fn scale_factor(&self) -> ScaleFactor<f32, ScreenPx, DevicePixel> {
-        ScaleFactor::new(self.window.hidpi_factor())
+        match self.kind {
+            WindowKind::Window(ref window) => {
+                ScaleFactor::new(window.hidpi_factor())
+            }
+            WindowKind::Headless(..) => {
+                ScaleFactor::new(1.0)
+            }
+        }
     }
 
     #[cfg(target_os = "windows")]
     fn scale_factor(&self) -> ScaleFactor<f32, ScreenPx, DevicePixel> {
         let hdc = unsafe { user32::GetDC(::std::ptr::null_mut()) };
         let ppi = unsafe { gdi32::GetDeviceCaps(hdc, winapi::wingdi::LOGPIXELSY) };
         ScaleFactor::new(ppi as f32 / 96.0)
     }
 
     fn set_page_title(&self, title: Option<String>) {
-        let fallback_title: String = if let Some(ref current_url) = *self.current_url.borrow() {
-            current_url.to_string()
-        } else {
-            String::from("Untitled")
-        };
+        match self.kind {
+            WindowKind::Window(ref window) => {
+                let fallback_title: String = if let Some(ref current_url) = *self.current_url.borrow() {
+                    current_url.to_string()
+                } else {
+                    String::from("Untitled")
+                };
 
-        let title = match title {
-            Some(ref title) if title.len() > 0 => &**title,
-            _ => &fallback_title,
-        };
-        let title = format!("{} - Servo", title);
-        self.window.set_title(&title);
+                let title = match title {
+                    Some(ref title) if title.len() > 0 => &**title,
+                    _ => &fallback_title,
+                };
+                let title = format!("{} - Servo", title);
+                window.set_title(&title);
+            }
+            WindowKind::Headless(..) => {}
+        }
     }
 
     fn set_page_url(&self, url: Url) {
         *self.current_url.borrow_mut() = Some(url);
     }
 
     fn status(&self, _: Option<String>) {
     }
 
     fn load_start(&self, _: bool, _: bool) {
     }
 
     fn load_end(&self, _: bool, _: bool, root: bool) {
         if root && opts::get().no_native_titlebar {
-            self.window.show()
+            match self.kind {
+                WindowKind::Window(ref window) => {
+                    window.show();
+                }
+                WindowKind::Headless(..) => {}
+            }
         }
     }
 
     fn load_error(&self, _: NetError, _: String) {
     }
 
     fn head_parsed(&self) {
     }
 
     /// Has no effect on Android.
     fn set_cursor(&self, c: Cursor) {
-        use glutin::MouseCursor;
+        match self.kind {
+            WindowKind::Window(ref window) => {
+                use glutin::MouseCursor;
 
-        let glutin_cursor = match c {
-            Cursor::None => MouseCursor::NoneCursor,
-            Cursor::Default => MouseCursor::Default,
-            Cursor::Pointer => MouseCursor::Hand,
-            Cursor::ContextMenu => MouseCursor::ContextMenu,
-            Cursor::Help => MouseCursor::Help,
-            Cursor::Progress => MouseCursor::Progress,
-            Cursor::Wait => MouseCursor::Wait,
-            Cursor::Cell => MouseCursor::Cell,
-            Cursor::Crosshair => MouseCursor::Crosshair,
-            Cursor::Text => MouseCursor::Text,
-            Cursor::VerticalText => MouseCursor::VerticalText,
-            Cursor::Alias => MouseCursor::Alias,
-            Cursor::Copy => MouseCursor::Copy,
-            Cursor::Move => MouseCursor::Move,
-            Cursor::NoDrop => MouseCursor::NoDrop,
-            Cursor::NotAllowed => MouseCursor::NotAllowed,
-            Cursor::Grab => MouseCursor::Grab,
-            Cursor::Grabbing => MouseCursor::Grabbing,
-            Cursor::EResize => MouseCursor::EResize,
-            Cursor::NResize => MouseCursor::NResize,
-            Cursor::NeResize => MouseCursor::NeResize,
-            Cursor::NwResize => MouseCursor::NwResize,
-            Cursor::SResize => MouseCursor::SResize,
-            Cursor::SeResize => MouseCursor::SeResize,
-            Cursor::SwResize => MouseCursor::SwResize,
-            Cursor::WResize => MouseCursor::WResize,
-            Cursor::EwResize => MouseCursor::EwResize,
-            Cursor::NsResize => MouseCursor::NsResize,
-            Cursor::NeswResize => MouseCursor::NeswResize,
-            Cursor::NwseResize => MouseCursor::NwseResize,
-            Cursor::ColResize => MouseCursor::ColResize,
-            Cursor::RowResize => MouseCursor::RowResize,
-            Cursor::AllScroll => MouseCursor::AllScroll,
-            Cursor::ZoomIn => MouseCursor::ZoomIn,
-            Cursor::ZoomOut => MouseCursor::ZoomOut,
-        };
-        self.window.set_cursor(glutin_cursor);
+                let glutin_cursor = match c {
+                    Cursor::None => MouseCursor::NoneCursor,
+                    Cursor::Default => MouseCursor::Default,
+                    Cursor::Pointer => MouseCursor::Hand,
+                    Cursor::ContextMenu => MouseCursor::ContextMenu,
+                    Cursor::Help => MouseCursor::Help,
+                    Cursor::Progress => MouseCursor::Progress,
+                    Cursor::Wait => MouseCursor::Wait,
+                    Cursor::Cell => MouseCursor::Cell,
+                    Cursor::Crosshair => MouseCursor::Crosshair,
+                    Cursor::Text => MouseCursor::Text,
+                    Cursor::VerticalText => MouseCursor::VerticalText,
+                    Cursor::Alias => MouseCursor::Alias,
+                    Cursor::Copy => MouseCursor::Copy,
+                    Cursor::Move => MouseCursor::Move,
+                    Cursor::NoDrop => MouseCursor::NoDrop,
+                    Cursor::NotAllowed => MouseCursor::NotAllowed,
+                    Cursor::Grab => MouseCursor::Grab,
+                    Cursor::Grabbing => MouseCursor::Grabbing,
+                    Cursor::EResize => MouseCursor::EResize,
+                    Cursor::NResize => MouseCursor::NResize,
+                    Cursor::NeResize => MouseCursor::NeResize,
+                    Cursor::NwResize => MouseCursor::NwResize,
+                    Cursor::SResize => MouseCursor::SResize,
+                    Cursor::SeResize => MouseCursor::SeResize,
+                    Cursor::SwResize => MouseCursor::SwResize,
+                    Cursor::WResize => MouseCursor::WResize,
+                    Cursor::EwResize => MouseCursor::EwResize,
+                    Cursor::NsResize => MouseCursor::NsResize,
+                    Cursor::NeswResize => MouseCursor::NeswResize,
+                    Cursor::NwseResize => MouseCursor::NwseResize,
+                    Cursor::ColResize => MouseCursor::ColResize,
+                    Cursor::RowResize => MouseCursor::RowResize,
+                    Cursor::AllScroll => MouseCursor::AllScroll,
+                    Cursor::ZoomIn => MouseCursor::ZoomIn,
+                    Cursor::ZoomOut => MouseCursor::ZoomOut,
+                };
+                window.set_cursor(glutin_cursor);
+            }
+            WindowKind::Headless(..) => {}
+        }
     }
 
     fn set_favicon(&self, _: Url) {
     }
 
     fn prepare_for_composite(&self, _width: usize, _height: usize) -> bool {
         true
     }
 
     #[cfg(target_os = "linux")]
     fn native_display(&self) -> NativeDisplay {
         use x11::xlib;
         unsafe {
             match opts::get().render_api {
                 RenderApi::GL => {
-                    NativeDisplay::new(self.window.platform_display() as *mut xlib::Display)
+                    match self.kind {
+                        WindowKind::Window(ref window) => {
+                            NativeDisplay::new(window.platform_display() as *mut xlib::Display)
+                        }
+                        WindowKind::Headless(..) => {
+                            unreachable!()
+                        }
+                    }
                 },
                 RenderApi::ES2 => {
                     NativeDisplay::new_egl_display()
                 }
             }
         }
     }
 
--- a/servo/python/servo/command_base.py
+++ b/servo/python/servo/command_base.py
@@ -2,16 +2,17 @@
 # file at the top-level directory of this distribution.
 #
 # Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
 # http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
 # <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
 # option. This file may not be copied, modified, or distributed
 # except according to those terms.
 
+from glob import glob
 import gzip
 import itertools
 import locale
 import os
 from os import path
 import contextlib
 import subprocess
 from subprocess import PIPE
@@ -42,16 +43,27 @@ def setlocale(name):
     """Context manager for changing the current locale"""
     saved_locale = locale.setlocale(locale.LC_ALL)
     try:
         yield locale.setlocale(locale.LC_ALL, name)
     finally:
         locale.setlocale(locale.LC_ALL, saved_locale)
 
 
+def find_dep_path_newest(package, bin_path):
+    deps_path = path.join(path.split(bin_path)[0], "build")
+    with cd(deps_path):
+        candidates = glob(package + '-*')
+    candidates = (path.join(deps_path, c) for c in candidates)
+    candidate_times = sorted(((path.getmtime(c), c) for c in candidates), reverse=True)
+    if len(candidate_times) > 0:
+        return candidate_times[0][1]
+    return None
+
+
 def archive_deterministically(dir_to_archive, dest_archive, prepend_path=None):
     """Create a .tar.gz archive in a deterministic (reproducible) manner.
 
     See https://reproducible-builds.org/docs/archives/ for more details."""
 
     def reset(tarinfo):
         """Helper to reset owner/group and modification time for tar entries"""
         tarinfo.uid = tarinfo.gid = 0
--- a/servo/python/servo/post_build_commands.py
+++ b/servo/python/servo/post_build_commands.py
@@ -4,52 +4,39 @@
 # Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
 # http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
 # <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
 # option. This file may not be copied, modified, or distributed
 # except according to those terms.
 
 from __future__ import print_function, unicode_literals
 
-from glob import glob
 import os
 import os.path as path
 import subprocess
 from shutil import copytree, rmtree, copy2
 
 from mach.registrar import Registrar
 
 from mach.decorators import (
     CommandArgument,
     CommandProvider,
     Command,
 )
 
-from servo.command_base import CommandBase, cd, call, check_call, is_windows, is_macosx
+from servo.command_base import CommandBase, call, check_call, find_dep_path_newest, is_windows, is_macosx
 
 
 def read_file(filename, if_exists=False):
     if if_exists and not path.exists(filename):
         return None
     with open(filename) as f:
         return f.read()
 
 
-def find_dep_path_newest(package, bin_path):
-    deps_path = path.join(path.split(bin_path)[0], "build")
-    with cd(deps_path):
-        print(os.getcwd())
-        candidates = glob(package + '-*')
-    candidates = (path.join(deps_path, c) for c in candidates)
-    candidate_times = sorted(((path.getmtime(c), c) for c in candidates), reverse=True)
-    if len(candidate_times) > 0:
-        return candidate_times[0][1]
-    return None
-
-
 @CommandProvider
 class PostBuildCommands(CommandBase):
     @Command('run',
              description='Run Servo',
              category='post-build')
     @CommandArgument('--release', '-r', action='store_true',
                      help='Run the release build')
     @CommandArgument('--dev', '-d', action='store_true',
--- a/servo/python/servo/testing_commands.py
+++ b/servo/python/servo/testing_commands.py
@@ -20,17 +20,17 @@ from time import time
 
 from mach.registrar import Registrar
 from mach.decorators import (
     CommandArgument,
     CommandProvider,
     Command,
 )
 
-from servo.command_base import CommandBase, call, cd, check_call, host_triple
+from servo.command_base import BuildNotFound, CommandBase, call, cd, check_call, find_dep_path_newest, host_triple
 from wptrunner import wptcommandline
 from update import updatecommandline
 from servo_tidy import tidy
 from servo_tidy_tests import test_tidy
 
 SCRIPT_PATH = os.path.split(__file__)[0]
 PROJECT_TOPLEVEL_PATH = os.path.abspath(os.path.join(SCRIPT_PATH, "..", ".."))
 WEB_PLATFORM_TESTS_PATH = os.path.join("tests", "wpt", "web-platform-tests")
@@ -408,16 +408,42 @@ class MachCommands(CommandBase):
             if all_handled:
                 return correct_function(**kwargs)
             else:
                 # Dispatch each test to the correct suite via test()
                 Registrar.dispatch("test", context=self.context, params=requested_paths)
 
     # Helper for test_css and test_wpt:
     def wptrunner(self, run_file, **kwargs):
+        # On Linux and mac, find the OSMesa software rendering library and
+        # add it to the dynamic linker search path.
+        if sys.platform.startswith('linux'):
+            try:
+                args = [self.get_binary_path(True, False)]
+                osmesa_path = path.join(find_dep_path_newest('osmesa-src', args[0]), "out", "lib", "gallium")
+                os.environ["LD_LIBRARY_PATH"] = osmesa_path
+                os.environ["GALLIUM_DRIVER"] = "softpipe"
+            except BuildNotFound:
+                # This can occur when cross compiling (e.g. arm64), in which case
+                # we won't run the tests anyway so can safely ignore this step.
+                pass
+        if sys.platform.startswith('darwin'):
+            try:
+                args = [self.get_binary_path(True, False)]
+                osmesa_path = path.join(find_dep_path_newest('osmesa-src', args[0]),
+                                        "out", "src", "gallium", "targets", "osmesa", ".libs")
+                glapi_path = path.join(find_dep_path_newest('osmesa-src', args[0]),
+                                       "out", "src", "mapi", "shared-glapi", ".libs")
+                os.environ["DYLD_LIBRARY_PATH"] = osmesa_path + ":" + glapi_path
+                os.environ["GALLIUM_DRIVER"] = "softpipe"
+            except BuildNotFound:
+                # This can occur when cross compiling (e.g. arm64), in which case
+                # we won't run the tests anyway so can safely ignore this step.
+                pass
+
         os.environ["RUST_BACKTRACE"] = "1"
         kwargs["debug"] = not kwargs["release"]
         if kwargs.pop("chaos"):
             kwargs["debugger"] = "rr"
             kwargs["debugger_args"] = "record --chaos"
             kwargs["repeat_until_unexpected"] = True
             # TODO: Delete rr traces from green test runs?
         prefs = kwargs.pop("prefs")