servo: Merge #10224 - webgl: Add attribute validations and other nits (from emilio:shader-type-validations); r=jdm
authorEmilio Cobos Álvarez <ecoal95@gmail.com>
Tue, 12 Apr 2016 04:49:19 +0500
changeset 476650 f0a2cc1f08dec0c3f6e623894d796bb5cba9d545
parent 476649 cc4bbd467e8dcf96cf48ac297e29a14fd3ea2b45
child 476651 617f797316375095e9cfc79b7acb4722993a6e9c
push id44079
push userbmo:gps@mozilla.com
push dateSat, 04 Feb 2017 00:14:49 +0000
reviewersjdm
servo: Merge #10224 - webgl: Add attribute validations and other nits (from emilio:shader-type-validations); r=jdm Fixes https://github.com/servo/servo/issues/9958 Depends on a bunch of prs, and needs a test. r? @jdm Source-Repo: https://github.com/servo/servo Source-Revision: f0014bd9cd5ac5db3e5a2f9fa8eafeb992df309a
servo/components/canvas/webgl_paint_thread.rs
servo/components/compositing/constellation.rs
servo/components/script/dom/bindings/trace.rs
servo/components/script/dom/webglrenderingcontext.rs
servo/components/script/dom/webglshader.rs
servo/components/script_traits/script_msg.rs
servo/tests/html/test_webgl_triangle.html
--- a/servo/components/canvas/webgl_paint_thread.rs
+++ b/servo/components/canvas/webgl_paint_thread.rs
@@ -1,17 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use canvas_traits::{CanvasCommonMsg, CanvasMsg, CanvasPixelData, CanvasData, FromLayoutMsg};
 use euclid::size::Size2D;
 use gleam::gl;
 use ipc_channel::ipc::{self, IpcSender, IpcSharedMemory};
-use offscreen_gl_context::{ColorAttachmentType, GLContext, GLContextAttributes, NativeGLContext};
+use offscreen_gl_context::{ColorAttachmentType, GLContext, GLLimits, GLContextAttributes, NativeGLContext};
 use std::borrow::ToOwned;
 use std::sync::mpsc::channel;
 use util::thread::spawn_named;
 use util::vec::byte_swap;
 use webrender_traits;
 
 enum WebGLPaintTaskData {
     WebRender(webrender_traits::RenderApi, webrender_traits::WebGLContextId),
@@ -21,30 +21,34 @@ enum WebGLPaintTaskData {
 pub struct WebGLPaintThread {
     size: Size2D<i32>,
     data: WebGLPaintTaskData,
 }
 
 impl WebGLPaintThread {
     fn new(size: Size2D<i32>,
            attrs: GLContextAttributes,
-           webrender_api_sender: Option<webrender_traits::RenderApiSender>) -> Result<WebGLPaintThread, String> {
-        let data = if let Some(sender) = webrender_api_sender {
+           webrender_api_sender: Option<webrender_traits::RenderApiSender>)
+        -> Result<(WebGLPaintThread, GLLimits), String> {
+        let (data, limits) = if let Some(sender) = webrender_api_sender {
             let webrender_api = sender.create_api();
-            let (id, _) = try!(webrender_api.request_webgl_context(&size, attrs));
-            WebGLPaintTaskData::WebRender(webrender_api, id)
+            let (id, limits) = try!(webrender_api.request_webgl_context(&size, attrs));
+            (WebGLPaintTaskData::WebRender(webrender_api, id), limits)
         } else {
             let context = try!(GLContext::<NativeGLContext>::new(size, attrs, ColorAttachmentType::Texture, None));
-            WebGLPaintTaskData::Servo(context)
+            let limits = context.borrow_limits().clone();
+            (WebGLPaintTaskData::Servo(context), limits)
         };
 
-        Ok(WebGLPaintThread {
+        let painter_object = WebGLPaintThread {
             size: size,
             data: data,
-        })
+        };
+
+        Ok((painter_object, limits))
     }
 
     pub 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);
             }
@@ -54,23 +58,23 @@ impl WebGLPaintThread {
         }
     }
 
     /// Creates a new `WebGLPaintThread` and returns the out-of-process sender and the in-process
     /// sender for it.
     pub fn start(size: Size2D<i32>,
                  attrs: GLContextAttributes,
                  webrender_api_sender: Option<webrender_traits::RenderApiSender>)
-                 -> Result<IpcSender<CanvasMsg>, String> {
+                 -> Result<(IpcSender<CanvasMsg>, GLLimits), String> {
         let (sender, receiver) = ipc::channel::<CanvasMsg>().unwrap();
         let (result_chan, result_port) = channel();
         spawn_named("WebGLThread".to_owned(), move || {
             let mut painter = match WebGLPaintThread::new(size, attrs, webrender_api_sender) {
-                Ok(thread) => {
-                    result_chan.send(Ok(())).unwrap();
+                Ok((thread, limits)) => {
+                    result_chan.send(Ok(limits)).unwrap();
                     thread
                 },
                 Err(e) => {
                     result_chan.send(Err(e)).unwrap();
                     return
                 }
             };
             painter.init();
@@ -90,18 +94,17 @@ impl WebGLPaintThread {
                                 painter.send_data(chan),
                         }
                     }
                     CanvasMsg::Canvas2d(_) => panic!("Wrong message sent to WebGLThread"),
                 }
             }
         });
 
-        try!(result_port.recv().unwrap());
-        Ok(sender)
+        result_port.recv().unwrap().map(|limits| (sender, limits))
     }
 
     fn send_data(&mut self, chan: IpcSender<CanvasData>) {
         match self.data {
             WebGLPaintTaskData::Servo(_) => {
                 let width = self.size.width as usize;
                 let height = self.size.height as usize;
 
--- a/servo/components/compositing/constellation.rs
+++ b/servo/components/compositing/constellation.rs
@@ -34,17 +34,17 @@ use msg::constellation_msg::{FrameId, Pi
 use msg::constellation_msg::{Key, KeyModifiers, KeyState, LoadData};
 use msg::constellation_msg::{PipelineNamespace, PipelineNamespaceId, NavigationDirection};
 use msg::constellation_msg::{SubpageId, WindowSizeData};
 use msg::constellation_msg::{self, ConstellationChan, Failure};
 use msg::webdriver_msg;
 use net_traits::image_cache_thread::ImageCacheThread;
 use net_traits::storage_thread::{StorageThread, StorageThreadMsg};
 use net_traits::{self, ResourceThread};
-use offscreen_gl_context::GLContextAttributes;
+use offscreen_gl_context::{GLContextAttributes, GLLimits};
 use pipeline::{CompositionPipeline, InitialPipelineState, Pipeline, UnprivilegedPipelineContent};
 use profile_traits::mem;
 use profile_traits::time;
 use rand::{random, Rng, SeedableRng, StdRng};
 #[cfg(not(target_os = "windows"))]
 use sandboxing;
 use script_traits::{AnimationState, CompositorEvent, ConstellationControlMsg};
 use script_traits::{DocumentState, LayoutControlMsg};
@@ -1344,21 +1344,21 @@ impl<LTF: LayoutThreadFactory, STF: Scri
         response_sender.send(sender)
             .unwrap_or_else(|e| debug!("Create canvas paint thread response failed ({})", e))
     }
 
     fn handle_create_webgl_paint_thread_msg(
             &mut self,
             size: &Size2D<i32>,
             attributes: GLContextAttributes,
-            response_sender: IpcSender<Result<IpcSender<CanvasMsg>, String>>) {
+            response_sender: IpcSender<Result<(IpcSender<CanvasMsg>, GLLimits), String>>) {
         let webrender_api = self.webrender_api_sender.clone();
-        let sender = WebGLPaintThread::start(*size, attributes, webrender_api);
+        let response = WebGLPaintThread::start(*size, attributes, webrender_api);
 
-        response_sender.send(sender)
+        response_sender.send(response)
             .unwrap_or_else(|e| debug!("Create WebGL paint thread response failed ({})", e))
     }
 
     fn handle_webdriver_msg(&mut self, msg: WebDriverCommandMsg) {
         // Find the script channel for the given parent pipeline,
         // and pass the event to that script thread.
         match msg {
             WebDriverCommandMsg::LoadUrl(pipeline_id, load_data, reply) => {
--- a/servo/components/script/dom/bindings/trace.rs
+++ b/servo/components/script/dom/bindings/trace.rs
@@ -57,16 +57,17 @@ use layout_interface::{LayoutChan, Layou
 use libc;
 use msg::constellation_msg::ConstellationChan;
 use msg::constellation_msg::{PipelineId, SubpageId, WindowSizeData};
 use net_traits::Metadata;
 use net_traits::image::base::{Image, ImageMetadata};
 use net_traits::image_cache_thread::{ImageCacheChan, ImageCacheThread};
 use net_traits::response::HttpsState;
 use net_traits::storage_thread::StorageType;
+use offscreen_gl_context::GLLimits;
 use profile_traits::mem::ProfilerChan as MemProfilerChan;
 use profile_traits::time::ProfilerChan as TimeProfilerChan;
 use script_runtime::ScriptChan;
 use script_traits::{LayoutMsg, ScriptMsg, TimerEventId, TimerSource, TouchpadPressurePhase, UntrustedNodeAddress};
 use serde::{Deserialize, Serialize};
 use smallvec::SmallVec;
 use std::boxed::FnBox;
 use std::cell::{Cell, UnsafeCell};
@@ -303,17 +304,17 @@ no_jsmanaged_fields!(UntrustedNodeAddres
 no_jsmanaged_fields!(LengthOrPercentageOrAuto);
 no_jsmanaged_fields!(RGBA);
 no_jsmanaged_fields!(EuclidLength<Unit, T>);
 no_jsmanaged_fields!(Matrix2D<T>);
 no_jsmanaged_fields!(StorageType);
 no_jsmanaged_fields!(CanvasGradientStop, LinearGradientStyle, RadialGradientStyle);
 no_jsmanaged_fields!(LineCapStyle, LineJoinStyle, CompositionOrBlending);
 no_jsmanaged_fields!(RepetitionStyle);
-no_jsmanaged_fields!(WebGLError);
+no_jsmanaged_fields!(WebGLError, GLLimits);
 no_jsmanaged_fields!(TimeProfilerChan);
 no_jsmanaged_fields!(MemProfilerChan);
 no_jsmanaged_fields!(PseudoElement);
 no_jsmanaged_fields!(Length);
 no_jsmanaged_fields!(ElementState);
 no_jsmanaged_fields!(DOMString);
 no_jsmanaged_fields!(Mime);
 no_jsmanaged_fields!(AttrIdentifier);
--- a/servo/components/script/dom/webglrenderingcontext.rs
+++ b/servo/components/script/dom/webglrenderingcontext.rs
@@ -26,17 +26,17 @@ use dom::webglshader::WebGLShader;
 use dom::webgltexture::{TexParameterValue, WebGLTexture};
 use dom::webgluniformlocation::WebGLUniformLocation;
 use euclid::size::Size2D;
 use ipc_channel::ipc::{self, IpcSender};
 use js::jsapi::{JSContext, JSObject, RootedValue};
 use js::jsval::{BooleanValue, DoubleValue, Int32Value, JSVal, NullValue, UndefinedValue};
 use net_traits::image::base::PixelFormat;
 use net_traits::image_cache_thread::ImageResponse;
-use offscreen_gl_context::GLContextAttributes;
+use offscreen_gl_context::{GLContextAttributes, GLLimits};
 use script_traits::ScriptMsg as ConstellationMsg;
 use std::cell::Cell;
 use util::str::DOMString;
 use util::vec::byte_swap;
 use webrender_traits::WebGLError::*;
 use webrender_traits::{WebGLCommand, WebGLError, WebGLFramebufferBindingRequest, WebGLParameter};
 
 pub const MAX_UNIFORM_AND_ATTRIBUTE_LEN: usize = 256;
@@ -66,16 +66,18 @@ bitflags! {
     }
 }
 
 #[dom_struct]
 pub struct WebGLRenderingContext {
     reflector_: Reflector,
     #[ignore_heap_size_of = "Defined in ipc-channel"]
     ipc_renderer: IpcSender<CanvasMsg>,
+    #[ignore_heap_size_of = "Defined in offscreen_gl_context"]
+    limits: GLLimits,
     canvas: JS<HTMLCanvasElement>,
     #[ignore_heap_size_of = "Defined in webrender_traits"]
     last_error: Cell<Option<WebGLError>>,
     texture_unpacking_settings: Cell<TextureUnpacking>,
     bound_texture_2d: MutNullableHeap<JS<WebGLTexture>>,
     bound_texture_cube_map: MutNullableHeap<JS<WebGLTexture>>,
     bound_buffer_array: MutNullableHeap<JS<WebGLBuffer>>,
     bound_buffer_element_array: MutNullableHeap<JS<WebGLBuffer>>,
@@ -90,20 +92,21 @@ impl WebGLRenderingContext {
                      -> Result<WebGLRenderingContext, String> {
         let (sender, receiver) = ipc::channel().unwrap();
         let constellation_chan = global.constellation_chan();
         constellation_chan.0
                           .send(ConstellationMsg::CreateWebGLPaintThread(size, attrs, sender))
                           .unwrap();
         let result = receiver.recv().unwrap();
 
-        result.map(|ipc_renderer| {
+        result.map(|(ipc_renderer, context_limits)| {
             WebGLRenderingContext {
                 reflector_: Reflector::new(),
                 ipc_renderer: ipc_renderer,
+                limits: context_limits,
                 canvas: JS::from_ref(canvas),
                 last_error: Cell::new(None),
                 texture_unpacking_settings: Cell::new(CONVERT_COLORSPACE),
                 bound_texture_2d: MutNullableHeap::new(None),
                 bound_texture_cube_map: MutNullableHeap::new(None),
                 bound_buffer_array: MutNullableHeap::new(None),
                 bound_buffer_element_array: MutNullableHeap::new(None),
                 current_program: MutNullableHeap::new(None),
@@ -134,41 +137,48 @@ impl WebGLRenderingContext {
         self.ipc_renderer.send(CanvasMsg::Common(CanvasCommonMsg::Recreate(size))).unwrap();
     }
 
     pub fn ipc_renderer(&self) -> IpcSender<CanvasMsg> {
         self.ipc_renderer.clone()
     }
 
     pub fn webgl_error(&self, err: WebGLError) {
+        // TODO(emilio): Add useful debug messages to this
+        warn!("WebGL error: {:?}, previous error was {:?}", err, self.last_error.get());
+
         // If an error has been detected no further errors must be
         // recorded until `getError` has been called
         if self.last_error.get().is_none() {
             self.last_error.set(Some(err));
         }
     }
 
     fn tex_parameter(&self, target: u32, name: u32, value: TexParameterValue) {
         let texture = match target {
             constants::TEXTURE_2D => self.bound_texture_2d.get(),
             constants::TEXTURE_CUBE_MAP => self.bound_texture_cube_map.get(),
             _ => return self.webgl_error(InvalidEnum),
         };
         if let Some(texture) = texture {
             handle_potential_webgl_error!(self, texture.tex_parameter(target, name, value));
         } else {
-            return self.webgl_error(InvalidOperation);
+            self.webgl_error(InvalidOperation)
         }
     }
 
     fn mark_as_dirty(&self) {
         self.canvas.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
     }
 
     fn vertex_attrib(&self, indx: u32, x: f32, y: f32, z: f32, w: f32) {
+        if indx > self.limits.max_vertex_attribs {
+            return self.webgl_error(InvalidValue);
+        }
+
         self.ipc_renderer
             .send(CanvasMsg::WebGL(WebGLCommand::VertexAttrib(indx, x, y, z, w)))
             .unwrap();
     }
 }
 
 impl Drop for WebGLRenderingContext {
     fn drop(&mut self) {
@@ -675,16 +685,23 @@ impl WebGLRenderingContextMethods for We
 
     // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9
     fn CreateProgram(&self) -> Option<Root<WebGLProgram>> {
         WebGLProgram::maybe_new(self.global().r(), self.ipc_renderer.clone())
     }
 
     // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9
     fn CreateShader(&self, shader_type: u32) -> Option<Root<WebGLShader>> {
+        match shader_type {
+            constants::VERTEX_SHADER | constants::FRAGMENT_SHADER => {},
+            _ => {
+                self.webgl_error(InvalidEnum);
+                return None;
+            }
+        }
         WebGLShader::maybe_new(self.global().r(), self.ipc_renderer.clone(), shader_type)
     }
 
     // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.5
     fn DeleteBuffer(&self, buffer: Option<&WebGLBuffer>) {
         if let Some(buffer) = buffer {
             buffer.delete()
         }
@@ -732,23 +749,23 @@ impl WebGLRenderingContextMethods for We
             constants::LINE_LOOP | constants::LINES |
             constants::TRIANGLE_STRIP | constants::TRIANGLE_FAN |
             constants::TRIANGLES => {
                 if self.current_program.get().is_none() {
                     return self.webgl_error(InvalidOperation);
                 }
 
                 if first < 0 || count < 0 {
-                    self.webgl_error(InvalidValue);
-                } else {
-                    self.ipc_renderer
-                        .send(CanvasMsg::WebGL(WebGLCommand::DrawArrays(mode, first, count)))
-                        .unwrap();
-                    self.mark_as_dirty();
+                    return self.webgl_error(InvalidValue);
                 }
+
+                self.ipc_renderer
+                    .send(CanvasMsg::WebGL(WebGLCommand::DrawArrays(mode, first, count)))
+                    .unwrap();
+                self.mark_as_dirty();
             },
             _ => self.webgl_error(InvalidEnum),
         }
     }
 
     // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.11
     fn DrawElements(&self, mode: u32, count: i32, type_: u32, offset: i64) {
         let type_size = match type_ {
@@ -785,16 +802,20 @@ impl WebGLRenderingContextMethods for We
                 self.mark_as_dirty();
             },
             _ => self.webgl_error(InvalidEnum),
         }
     }
 
     // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10
     fn EnableVertexAttribArray(&self, attrib_id: u32) {
+        if attrib_id > self.limits.max_vertex_attribs {
+            return self.webgl_error(InvalidValue);
+        }
+
         self.ipc_renderer
             .send(CanvasMsg::WebGL(WebGLCommand::EnableVertexAttribArray(attrib_id)))
             .unwrap()
     }
 
     // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10
     fn GetActiveUniform(&self, program: Option<&WebGLProgram>, index: u32) -> Option<Root<WebGLActiveInfo>> {
         program.and_then(|p| match p.get_active_uniform(index) {
@@ -1183,17 +1204,16 @@ impl WebGLRenderingContextMethods for We
         }
     }
 
     // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10
     fn VertexAttrib1f(&self, indx: u32, x: f32) {
         self.vertex_attrib(indx, x, 0f32, 0f32, 1f32)
     }
 
-    #[allow(unsafe_code)]
     // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10
     fn VertexAttrib1fv(&self, _cx: *mut JSContext, indx: u32, data: *mut JSObject) {
         if let Some(data_vec) = array_buffer_view_to_vec_checked::<f32>(data) {
             if data_vec.len() < 4 {
                 return self.webgl_error(InvalidOperation);
             }
             self.vertex_attrib(indx, data_vec[0], 0f32, 0f32, 1f32)
         } else {
@@ -1201,17 +1221,16 @@ impl WebGLRenderingContextMethods for We
         }
     }
 
     // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10
     fn VertexAttrib2f(&self, indx: u32, x: f32, y: f32) {
         self.vertex_attrib(indx, x, y, 0f32, 1f32)
     }
 
-    #[allow(unsafe_code)]
     // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10
     fn VertexAttrib2fv(&self, _cx: *mut JSContext, indx: u32, data: *mut JSObject) {
         if let Some(data_vec) = array_buffer_view_to_vec_checked::<f32>(data) {
             if data_vec.len() < 2 {
                 return self.webgl_error(InvalidOperation);
             }
             self.vertex_attrib(indx, data_vec[0], data_vec[1], 0f32, 1f32)
         } else {
@@ -1252,16 +1271,20 @@ impl WebGLRenderingContextMethods for We
         } else {
             self.webgl_error(InvalidValue);
         }
     }
 
     // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10
     fn VertexAttribPointer(&self, attrib_id: u32, size: i32, data_type: u32,
                            normalized: bool, stride: i32, offset: i64) {
+        if attrib_id > self.limits.max_vertex_attribs {
+            return self.webgl_error(InvalidValue);
+        }
+
         if let constants::FLOAT = data_type {
            let msg = CanvasMsg::WebGL(
                WebGLCommand::VertexAttribPointer2f(attrib_id, size, normalized, stride, offset as u32));
             self.ipc_renderer.send(msg).unwrap()
         } else {
             panic!("VertexAttribPointer: Data Type not supported")
         }
     }
--- a/servo/components/script/dom/webglshader.rs
+++ b/servo/components/script/dom/webglshader.rs
@@ -96,16 +96,17 @@ impl WebGLShader {
         }
 
         if let Some(ref source) = *self.source.borrow() {
             let validator = ShaderValidator::for_webgl(self.gl_type,
                                                        SHADER_OUTPUT_FORMAT,
                                                        &BuiltInResources::default()).unwrap();
             match validator.compile_and_translate(&[source]) {
                 Ok(translated_source) => {
+                    debug!("Shader translated: {}", translated_source);
                     // NOTE: At this point we should be pretty sure that the compilation in the paint thread
                     // will succeed.
                     // It could be interesting to retrieve the info log from the paint thread though
                     let msg = WebGLCommand::CompileShader(self.id, translated_source);
                     self.renderer.send(CanvasMsg::WebGL(msg)).unwrap();
                     self.compilation_status.set(ShaderCompilationStatus::Succeeded);
                 },
                 Err(error) => {
--- a/servo/components/script_traits/script_msg.rs
+++ b/servo/components/script_traits/script_msg.rs
@@ -9,17 +9,17 @@ use MouseButton;
 use MouseEventType;
 use MozBrowserEvent;
 use canvas_traits::CanvasMsg;
 use euclid::point::Point2D;
 use euclid::size::Size2D;
 use ipc_channel::ipc::IpcSender;
 use msg::constellation_msg::{Failure, NavigationDirection, PipelineId};
 use msg::constellation_msg::{LoadData, SubpageId};
-use offscreen_gl_context::GLContextAttributes;
+use offscreen_gl_context::{GLContextAttributes, GLLimits};
 use style_traits::cursor::Cursor;
 use style_traits::viewport::ViewportConstraints;
 use url::Url;
 
 /// Messages from the layout to the constellation.
 #[derive(Deserialize, Serialize)]
 pub enum LayoutMsg {
     /// Indicates whether this pipeline is currently running animations.
@@ -38,18 +38,18 @@ pub enum ScriptMsg {
     /// Indicates whether this pipeline is currently running animations.
     ChangeRunningAnimationsState(PipelineId, AnimationState),
     /// Requests that a new 2D canvas thread be created. (This is done in the constellation because
     /// 2D canvases may use the GPU and we don't want to give untrusted content access to the GPU.)
     CreateCanvasPaintThread(Size2D<i32>, IpcSender<IpcSender<CanvasMsg>>),
     /// Requests that a new WebGL thread be created. (This is done in the constellation because
     /// WebGL uses the GPU and we don't want to give untrusted content access to the GPU.)
     CreateWebGLPaintThread(Size2D<i32>,
-                         GLContextAttributes,
-                         IpcSender<Result<IpcSender<CanvasMsg>, String>>),
+                           GLContextAttributes,
+                           IpcSender<Result<(IpcSender<CanvasMsg>, GLLimits), String>>),
     /// Dispatched after the DOM load event has fired on a document
     /// Causes a `load` event to be dispatched to any enclosing frame context element
     /// for the given pipeline.
     DOMLoad(PipelineId),
     /// Script thread failure.
     Failure(Failure),
     /// Notifies the constellation that this frame has received focus.
     Focus(PipelineId),
--- a/servo/tests/html/test_webgl_triangle.html
+++ b/servo/tests/html/test_webgl_triangle.html
@@ -4,33 +4,35 @@
   <meta charset="utf-8" />
   <title>WebGL Triangle Test</title>
 </head>
 <body>
 <div style="text-align: center">
    <canvas id="canvas" width="512" height="512"></canvas>
 </div>
 <script id="vertexshader" type="x-shader">
-   attribute vec2 aVertexPosition;
-   attribute vec4 aColour;
-   varying vec4 aVertexColor;
+precision mediump float;
 
-   void main() {
-      aVertexColor = aColour;
-      gl_Position = vec4(aVertexPosition, 0.0, 1.0);
-   }
+attribute vec2 aVertexPosition;
+attribute vec4 aColour;
+varying vec4 aVertexColor;
+
+void main() {
+  aVertexColor = aColour;
+  gl_Position = vec4(aVertexPosition, 0.0, 1.0);
+}
 </script>
 <script id="fragmentshader" type="x-shader">
-   precision mediump float;
+precision mediump float;
 
-   varying vec4 aVertexColor;
+varying vec4 aVertexColor;
 
-   void main() {
-      gl_FragColor = aVertexColor;
-   }
+void main() {
+  gl_FragColor = aVertexColor;
+}
 </script>
 <script type="text/javascript">
 
    var canvas;
    function initWebGL()
    {
       canvas = document.getElementById("canvas");
       var gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
@@ -39,16 +41,19 @@
    }
 
    var gl = initWebGL();
 
    // Setup Shaders:
    var v = document.getElementById("vertexshader").firstChild.nodeValue;
    var f = document.getElementById("fragmentshader").firstChild.nodeValue;
 
+   console.log("vertex source: ", v);
+   console.log("fragment source: ", f);
+
    var vs = gl.createShader(gl.VERTEX_SHADER);
    gl.shaderSource(vs, v);
    gl.compileShader(vs);
 
    if (!gl.getShaderParameter(vs, gl.COMPILE_STATUS)) {
       alert(gl.getShaderInfoLog(vs));
    }
 
@@ -91,16 +96,17 @@
    // Setup Geometry
    gl.useProgram(program);
 
    program.aVertexPosition = gl.getAttribLocation(program, "aVertexPosition");
    gl.enableVertexAttribArray(program.aVertexPosition);
    gl.vertexAttribPointer(program.aVertexPosition, itemSize, gl.FLOAT, false, 0, 0);
 
    program.aColour = gl.getAttribLocation(program, "aColour");
+   console.log("aColour: " + program.aColour);
    gl.enableVertexAttribArray(program.aColour);
    gl.vertexAttribPointer(program.aColour, 4, gl.FLOAT, false, 0, 24);
 
    // Draw
    gl.drawArrays(gl.TRIANGLES, 0, 3);
 </script>
 </body>
 </html>