toolkit/devtools/server/actors/webgl.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/toolkit/devtools/server/actors/webgl.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,1319 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +"use strict";
     1.8 +
     1.9 +const {Cc, Ci, Cu, Cr} = require("chrome");
    1.10 +const events = require("sdk/event/core");
    1.11 +const protocol = require("devtools/server/protocol");
    1.12 +const { ContentObserver } = require("devtools/content-observer");
    1.13 +
    1.14 +const { on, once, off, emit } = events;
    1.15 +const { method, Arg, Option, RetVal } = protocol;
    1.16 +
    1.17 +const WEBGL_CONTEXT_NAMES = ["webgl", "experimental-webgl", "moz-webgl"];
    1.18 +
    1.19 +// These traits are bit masks. Make sure they're powers of 2.
    1.20 +const PROGRAM_DEFAULT_TRAITS = 0;
    1.21 +const PROGRAM_BLACKBOX_TRAIT = 1;
    1.22 +const PROGRAM_HIGHLIGHT_TRAIT = 2;
    1.23 +
    1.24 +exports.register = function(handle) {
    1.25 +  handle.addTabActor(WebGLActor, "webglActor");
    1.26 +}
    1.27 +
    1.28 +exports.unregister = function(handle) {
    1.29 +  handle.removeTabActor(WebGLActor);
    1.30 +}
    1.31 +
    1.32 +/**
    1.33 + * A WebGL Shader contributing to building a WebGL Program.
    1.34 + * You can either retrieve, or compile the source of a shader, which will
    1.35 + * automatically inflict the necessary changes to the WebGL state.
    1.36 + */
    1.37 +let ShaderActor = protocol.ActorClass({
    1.38 +  typeName: "gl-shader",
    1.39 +
    1.40 +  /**
    1.41 +   * Create the shader actor.
    1.42 +   *
    1.43 +   * @param DebuggerServerConnection conn
    1.44 +   *        The server connection.
    1.45 +   * @param WebGLProgram program
    1.46 +   *        The WebGL program being linked.
    1.47 +   * @param WebGLShader shader
    1.48 +   *        The cooresponding vertex or fragment shader.
    1.49 +   * @param WebGLProxy proxy
    1.50 +   *        The proxy methods for the WebGL context owning this shader.
    1.51 +   */
    1.52 +  initialize: function(conn, program, shader, proxy) {
    1.53 +    protocol.Actor.prototype.initialize.call(this, conn);
    1.54 +    this.program = program;
    1.55 +    this.shader = shader;
    1.56 +    this.text = proxy.getShaderSource(shader);
    1.57 +    this.linkedProxy = proxy;
    1.58 +  },
    1.59 +
    1.60 +  /**
    1.61 +   * Gets the source code for this shader.
    1.62 +   */
    1.63 +  getText: method(function() {
    1.64 +    return this.text;
    1.65 +  }, {
    1.66 +    response: { text: RetVal("string") }
    1.67 +  }),
    1.68 +
    1.69 +  /**
    1.70 +   * Sets and compiles new source code for this shader.
    1.71 +   */
    1.72 +  compile: method(function(text) {
    1.73 +    // Get the shader and corresponding program to change via the WebGL proxy.
    1.74 +    let { linkedProxy: proxy, shader, program } = this;
    1.75 +
    1.76 +    // Get the new shader source to inject.
    1.77 +    let oldText = this.text;
    1.78 +    let newText = text;
    1.79 +
    1.80 +    // Overwrite the shader's source.
    1.81 +    let error = proxy.compileShader(program, shader, this.text = newText);
    1.82 +
    1.83 +    // If something went wrong, revert to the previous shader.
    1.84 +    if (error.compile || error.link) {
    1.85 +      proxy.compileShader(program, shader, this.text = oldText);
    1.86 +      return error;
    1.87 +    }
    1.88 +    return undefined;
    1.89 +  }, {
    1.90 +    request: { text: Arg(0, "string") },
    1.91 +    response: { error: RetVal("nullable:json") }
    1.92 +  })
    1.93 +});
    1.94 +
    1.95 +/**
    1.96 + * The corresponding Front object for the ShaderActor.
    1.97 + */
    1.98 +let ShaderFront = protocol.FrontClass(ShaderActor, {
    1.99 +  initialize: function(client, form) {
   1.100 +    protocol.Front.prototype.initialize.call(this, client, form);
   1.101 +  }
   1.102 +});
   1.103 +
   1.104 +/**
   1.105 + * A WebGL program is composed (at the moment, analogue to OpenGL ES 2.0)
   1.106 + * of two shaders: a vertex shader and a fragment shader.
   1.107 + */
   1.108 +let ProgramActor = protocol.ActorClass({
   1.109 +  typeName: "gl-program",
   1.110 +
   1.111 +  /**
   1.112 +   * Create the program actor.
   1.113 +   *
   1.114 +   * @param DebuggerServerConnection conn
   1.115 +   *        The server connection.
   1.116 +   * @param WebGLProgram program
   1.117 +   *        The WebGL program being linked.
   1.118 +   * @param WebGLShader[] shaders
   1.119 +   *        The WebGL program's cooresponding vertex and fragment shaders.
   1.120 +   * @param WebGLCache cache
   1.121 +   *        The state storage for the WebGL context owning this program.
   1.122 +   * @param WebGLProxy proxy
   1.123 +   *        The proxy methods for the WebGL context owning this program.
   1.124 +   */
   1.125 +  initialize: function(conn, [program, shaders, cache, proxy]) {
   1.126 +    protocol.Actor.prototype.initialize.call(this, conn);
   1.127 +    this._shaderActorsCache = { vertex: null, fragment: null };
   1.128 +    this.program = program;
   1.129 +    this.shaders = shaders;
   1.130 +    this.linkedCache = cache;
   1.131 +    this.linkedProxy = proxy;
   1.132 +  },
   1.133 +
   1.134 +  get ownerWindow() this.linkedCache.ownerWindow,
   1.135 +  get ownerContext() this.linkedCache.ownerContext,
   1.136 +
   1.137 +  /**
   1.138 +   * Gets the vertex shader linked to this program. This method guarantees
   1.139 +   * a single actor instance per shader.
   1.140 +   */
   1.141 +  getVertexShader: method(function() {
   1.142 +    return this._getShaderActor("vertex");
   1.143 +  }, {
   1.144 +    response: { shader: RetVal("gl-shader") }
   1.145 +  }),
   1.146 +
   1.147 +  /**
   1.148 +   * Gets the fragment shader linked to this program. This method guarantees
   1.149 +   * a single actor instance per shader.
   1.150 +   */
   1.151 +  getFragmentShader: method(function() {
   1.152 +    return this._getShaderActor("fragment");
   1.153 +  }, {
   1.154 +    response: { shader: RetVal("gl-shader") }
   1.155 +  }),
   1.156 +
   1.157 +  /**
   1.158 +   * Highlights any geometry rendered using this program.
   1.159 +   */
   1.160 +  highlight: method(function(tint) {
   1.161 +    this.linkedProxy.highlightTint = tint;
   1.162 +    this.linkedCache.setProgramTrait(this.program, PROGRAM_HIGHLIGHT_TRAIT);
   1.163 +  }, {
   1.164 +    request: { tint: Arg(0, "array:number") },
   1.165 +    oneway: true
   1.166 +  }),
   1.167 +
   1.168 +  /**
   1.169 +   * Allows geometry to be rendered normally using this program.
   1.170 +   */
   1.171 +  unhighlight: method(function() {
   1.172 +    this.linkedCache.unsetProgramTrait(this.program, PROGRAM_HIGHLIGHT_TRAIT);
   1.173 +  }, {
   1.174 +    oneway: true
   1.175 +  }),
   1.176 +
   1.177 +  /**
   1.178 +   * Prevents any geometry from being rendered using this program.
   1.179 +   */
   1.180 +  blackbox: method(function() {
   1.181 +    this.linkedCache.setProgramTrait(this.program, PROGRAM_BLACKBOX_TRAIT);
   1.182 +  }, {
   1.183 +    oneway: true
   1.184 +  }),
   1.185 +
   1.186 +  /**
   1.187 +   * Allows geometry to be rendered using this program.
   1.188 +   */
   1.189 +  unblackbox: method(function() {
   1.190 +    this.linkedCache.unsetProgramTrait(this.program, PROGRAM_BLACKBOX_TRAIT);
   1.191 +  }, {
   1.192 +    oneway: true
   1.193 +  }),
   1.194 +
   1.195 +  /**
   1.196 +   * Returns a cached ShaderActor instance based on the required shader type.
   1.197 +   *
   1.198 +   * @param string type
   1.199 +   *        Either "vertex" or "fragment".
   1.200 +   * @return ShaderActor
   1.201 +   *         The respective shader actor instance.
   1.202 +   */
   1.203 +  _getShaderActor: function(type) {
   1.204 +    if (this._shaderActorsCache[type]) {
   1.205 +      return this._shaderActorsCache[type];
   1.206 +    }
   1.207 +    let proxy = this.linkedProxy;
   1.208 +    let shader = proxy.getShaderOfType(this.shaders, type);
   1.209 +    let shaderActor = new ShaderActor(this.conn, this.program, shader, proxy);
   1.210 +    return this._shaderActorsCache[type] = shaderActor;
   1.211 +  }
   1.212 +});
   1.213 +
   1.214 +/**
   1.215 + * The corresponding Front object for the ProgramActor.
   1.216 + */
   1.217 +let ProgramFront = protocol.FrontClass(ProgramActor, {
   1.218 +  initialize: function(client, form) {
   1.219 +    protocol.Front.prototype.initialize.call(this, client, form);
   1.220 +  }
   1.221 +});
   1.222 +
   1.223 +/**
   1.224 + * The WebGL Actor handles simple interaction with a WebGL context via a few
   1.225 + * high-level methods. After instantiating this actor, you'll need to set it
   1.226 + * up by calling setup().
   1.227 + */
   1.228 +let WebGLActor = exports.WebGLActor = protocol.ActorClass({
   1.229 +  typeName: "webgl",
   1.230 +  initialize: function(conn, tabActor) {
   1.231 +    protocol.Actor.prototype.initialize.call(this, conn);
   1.232 +    this.tabActor = tabActor;
   1.233 +    this._onGlobalCreated = this._onGlobalCreated.bind(this);
   1.234 +    this._onGlobalDestroyed = this._onGlobalDestroyed.bind(this);
   1.235 +    this._onProgramLinked = this._onProgramLinked.bind(this);
   1.236 +  },
   1.237 +  destroy: function(conn) {
   1.238 +    protocol.Actor.prototype.destroy.call(this, conn);
   1.239 +    this.finalize();
   1.240 +  },
   1.241 +
   1.242 +  /**
   1.243 +   * Starts waiting for the current tab actor's document global to be
   1.244 +   * created, in order to instrument the Canvas context and become
   1.245 +   * aware of everything the content does WebGL-wise.
   1.246 +   *
   1.247 +   * See ContentObserver and WebGLInstrumenter for more details.
   1.248 +   */
   1.249 +  setup: method(function({ reload }) {
   1.250 +    if (this._initialized) {
   1.251 +      return;
   1.252 +    }
   1.253 +    this._initialized = true;
   1.254 +
   1.255 +    this._programActorsCache = [];
   1.256 +    this._contentObserver = new ContentObserver(this.tabActor);
   1.257 +    this._webglObserver = new WebGLObserver();
   1.258 +
   1.259 +    on(this._contentObserver, "global-created", this._onGlobalCreated);
   1.260 +    on(this._contentObserver, "global-destroyed", this._onGlobalDestroyed);
   1.261 +    on(this._webglObserver, "program-linked", this._onProgramLinked);
   1.262 +
   1.263 +    if (reload) {
   1.264 +      this.tabActor.window.location.reload();
   1.265 +    }
   1.266 +  }, {
   1.267 +    request: { reload: Option(0, "boolean") },
   1.268 +    oneway: true
   1.269 +  }),
   1.270 +
   1.271 +  /**
   1.272 +   * Stops listening for document global changes and puts this actor
   1.273 +   * to hibernation. This method is called automatically just before the
   1.274 +   * actor is destroyed.
   1.275 +   */
   1.276 +  finalize: method(function() {
   1.277 +    if (!this._initialized) {
   1.278 +      return;
   1.279 +    }
   1.280 +    this._initialized = false;
   1.281 +
   1.282 +    this._contentObserver.stopListening();
   1.283 +    off(this._contentObserver, "global-created", this._onGlobalCreated);
   1.284 +    off(this._contentObserver, "global-destroyed", this._onGlobalDestroyed);
   1.285 +    off(this._webglObserver, "program-linked", this._onProgramLinked);
   1.286 +
   1.287 +    this._programActorsCache = null;
   1.288 +    this._contentObserver = null;
   1.289 +    this._webglObserver = null;
   1.290 +  }, {
   1.291 +   oneway: true
   1.292 +  }),
   1.293 +
   1.294 +  /**
   1.295 +   * Gets an array of cached program actors for the current tab actor's window.
   1.296 +   * This is useful for dealing with bfcache, when no new programs are linked.
   1.297 +   */
   1.298 +  getPrograms: method(function() {
   1.299 +    let id = ContentObserver.GetInnerWindowID(this.tabActor.window);
   1.300 +    return this._programActorsCache.filter(e => e.ownerWindow == id);
   1.301 +  }, {
   1.302 +    response: { programs: RetVal("array:gl-program") }
   1.303 +  }),
   1.304 +
   1.305 +  /**
   1.306 +   * Events emitted by this actor. The "program-linked" event is fired
   1.307 +   * every time a WebGL program was linked with its respective two shaders.
   1.308 +   */
   1.309 +  events: {
   1.310 +    "program-linked": {
   1.311 +      type: "programLinked",
   1.312 +      program: Arg(0, "gl-program")
   1.313 +    }
   1.314 +  },
   1.315 +
   1.316 +  /**
   1.317 +   * Invoked whenever the current tab actor's document global is created.
   1.318 +   */
   1.319 +  _onGlobalCreated: function(window) {
   1.320 +    WebGLInstrumenter.handle(window, this._webglObserver);
   1.321 +  },
   1.322 +
   1.323 +  /**
   1.324 +   * Invoked whenever the current tab actor's inner window is destroyed.
   1.325 +   */
   1.326 +  _onGlobalDestroyed: function(id) {
   1.327 +    removeFromArray(this._programActorsCache, e => e.ownerWindow == id);
   1.328 +    this._webglObserver.unregisterContextsForWindow(id);
   1.329 +  },
   1.330 +
   1.331 +  /**
   1.332 +   * Invoked whenever an observed WebGL context links a program.
   1.333 +   */
   1.334 +  _onProgramLinked: function(...args) {
   1.335 +    let programActor = new ProgramActor(this.conn, args);
   1.336 +    this._programActorsCache.push(programActor);
   1.337 +    events.emit(this, "program-linked", programActor);
   1.338 +  }
   1.339 +});
   1.340 +
   1.341 +/**
   1.342 + * The corresponding Front object for the WebGLActor.
   1.343 + */
   1.344 +let WebGLFront = exports.WebGLFront = protocol.FrontClass(WebGLActor, {
   1.345 +  initialize: function(client, { webglActor }) {
   1.346 +    protocol.Front.prototype.initialize.call(this, client, { actor: webglActor });
   1.347 +    client.addActorPool(this);
   1.348 +    this.manage(this);
   1.349 +  }
   1.350 +});
   1.351 +
   1.352 +/**
   1.353 + * Instruments a HTMLCanvasElement with the appropriate inspection methods.
   1.354 + */
   1.355 +let WebGLInstrumenter = {
   1.356 +  /**
   1.357 +   * Overrides the getContext method in the HTMLCanvasElement prototype.
   1.358 +   *
   1.359 +   * @param nsIDOMWindow window
   1.360 +   *        The window to perform the instrumentation in.
   1.361 +   * @param WebGLObserver observer
   1.362 +   *        The observer watching function calls in the context.
   1.363 +   */
   1.364 +  handle: function(window, observer) {
   1.365 +    let self = this;
   1.366 +
   1.367 +    let id = ContentObserver.GetInnerWindowID(window);
   1.368 +    let canvasElem = XPCNativeWrapper.unwrap(window.HTMLCanvasElement);
   1.369 +    let canvasPrototype = canvasElem.prototype;
   1.370 +    let originalGetContext = canvasPrototype.getContext;
   1.371 +
   1.372 +    /**
   1.373 +     * Returns a drawing context on the canvas, or null if the context ID is
   1.374 +     * not supported. This override creates an observer for the targeted context
   1.375 +     * type and instruments specific functions in the targeted context instance.
   1.376 +     */
   1.377 +    canvasPrototype.getContext = function(name, options) {
   1.378 +      // Make sure a context was able to be created.
   1.379 +      let context = originalGetContext.call(this, name, options);
   1.380 +      if (!context) {
   1.381 +        return context;
   1.382 +      }
   1.383 +      // Make sure a WebGL (not a 2D) context will be instrumented.
   1.384 +      if (WEBGL_CONTEXT_NAMES.indexOf(name) == -1) {
   1.385 +        return context;
   1.386 +      }
   1.387 +      // Repeated calls to 'getContext' return the same instance, no need to
   1.388 +      // instrument everything again.
   1.389 +      if (observer.for(context)) {
   1.390 +        return context;
   1.391 +      }
   1.392 +
   1.393 +      // Create a separate state storage for this context.
   1.394 +      observer.registerContextForWindow(id, context);
   1.395 +
   1.396 +      // Link our observer to the new WebGL context methods.
   1.397 +      for (let { timing, callback, functions } of self._methods) {
   1.398 +        for (let func of functions) {
   1.399 +          self._instrument(observer, context, func, callback, timing);
   1.400 +        }
   1.401 +      }
   1.402 +
   1.403 +      // Return the decorated context back to the content consumer, which
   1.404 +      // will continue using it normally.
   1.405 +      return context;
   1.406 +    };
   1.407 +  },
   1.408 +
   1.409 +  /**
   1.410 +   * Overrides a specific method in a HTMLCanvasElement context.
   1.411 +   *
   1.412 +   * @param WebGLObserver observer
   1.413 +   *        The observer watching function calls in the context.
   1.414 +   * @param WebGLRenderingContext context
   1.415 +   *        The targeted WebGL context instance.
   1.416 +   * @param string funcName
   1.417 +   *        The function to override.
   1.418 +   * @param array callbackName [optional]
   1.419 +   *        The two callback function names in the observer, corresponding to
   1.420 +   *        the "before" and "after" invocation times. If unspecified, they will
   1.421 +   *        default to the name of the function to override.
   1.422 +   * @param number timing [optional]
   1.423 +   *        When to issue the callback in relation to the actual context
   1.424 +   *        function call. Availalble values are -1 for "before" (default)
   1.425 +   *        1 for "after" and 0 for "before and after".
   1.426 +   */
   1.427 +  _instrument: function(observer, context, funcName, callbackName = [], timing = -1) {
   1.428 +    let { cache, proxy } = observer.for(context);
   1.429 +    let originalFunc = context[funcName];
   1.430 +    let beforeFuncName = callbackName[0] || funcName;
   1.431 +    let afterFuncName = callbackName[1] || callbackName[0] || funcName;
   1.432 +
   1.433 +    context[funcName] = function(...glArgs) {
   1.434 +      if (timing <= 0 && !observer.suppressHandlers) {
   1.435 +        let glBreak = observer[beforeFuncName](glArgs, cache, proxy);
   1.436 +        if (glBreak) return undefined;
   1.437 +      }
   1.438 +
   1.439 +      let glResult = originalFunc.apply(this, glArgs);
   1.440 +
   1.441 +      if (timing >= 0 && !observer.suppressHandlers) {
   1.442 +        let glBreak = observer[afterFuncName](glArgs, glResult, cache, proxy);
   1.443 +        if (glBreak) return undefined;
   1.444 +      }
   1.445 +
   1.446 +      return glResult;
   1.447 +    };
   1.448 +  },
   1.449 +
   1.450 +  /**
   1.451 +   * Override mappings for WebGL methods.
   1.452 +   */
   1.453 +  _methods: [{
   1.454 +    timing: 1, // after
   1.455 +    functions: [
   1.456 +      "linkProgram", "getAttribLocation", "getUniformLocation"
   1.457 +    ]
   1.458 +  }, {
   1.459 +    timing: -1, // before
   1.460 +    callback: [
   1.461 +      "toggleVertexAttribArray"
   1.462 +    ],
   1.463 +    functions: [
   1.464 +      "enableVertexAttribArray", "disableVertexAttribArray"
   1.465 +    ]
   1.466 +  }, {
   1.467 +    timing: -1, // before
   1.468 +    callback: [
   1.469 +      "attribute_"
   1.470 +    ],
   1.471 +    functions: [
   1.472 +      "vertexAttrib1f", "vertexAttrib2f", "vertexAttrib3f", "vertexAttrib4f",
   1.473 +      "vertexAttrib1fv", "vertexAttrib2fv", "vertexAttrib3fv", "vertexAttrib4fv",
   1.474 +      "vertexAttribPointer"
   1.475 +    ]
   1.476 +  }, {
   1.477 +    timing: -1, // before
   1.478 +    callback: [
   1.479 +      "uniform_"
   1.480 +    ],
   1.481 +    functions: [
   1.482 +      "uniform1i", "uniform2i", "uniform3i", "uniform4i",
   1.483 +      "uniform1f", "uniform2f", "uniform3f", "uniform4f",
   1.484 +      "uniform1iv", "uniform2iv", "uniform3iv", "uniform4iv",
   1.485 +      "uniform1fv", "uniform2fv", "uniform3fv", "uniform4fv",
   1.486 +      "uniformMatrix2fv", "uniformMatrix3fv", "uniformMatrix4fv"
   1.487 +    ]
   1.488 +  }, {
   1.489 +    timing: -1, // before
   1.490 +    functions: [
   1.491 +      "useProgram", "enable", "disable", "blendColor",
   1.492 +      "blendEquation", "blendEquationSeparate",
   1.493 +      "blendFunc", "blendFuncSeparate"
   1.494 +    ]
   1.495 +  }, {
   1.496 +    timing: 0, // before and after
   1.497 +    callback: [
   1.498 +      "beforeDraw_", "afterDraw_"
   1.499 +    ],
   1.500 +    functions: [
   1.501 +      "drawArrays", "drawElements"
   1.502 +    ]
   1.503 +  }]
   1.504 +  // TODO: It'd be a good idea to handle other functions as well:
   1.505 +  //   - getActiveUniform
   1.506 +  //   - getUniform
   1.507 +  //   - getActiveAttrib
   1.508 +  //   - getVertexAttrib
   1.509 +};
   1.510 +
   1.511 +/**
   1.512 + * An observer that captures a WebGL context's method calls.
   1.513 + */
   1.514 +function WebGLObserver() {
   1.515 +  this._contexts = new Map();
   1.516 +}
   1.517 +
   1.518 +WebGLObserver.prototype = {
   1.519 +  _contexts: null,
   1.520 +
   1.521 +  /**
   1.522 +   * Creates a WebGLCache and a WebGLProxy for the specified window and context.
   1.523 +   *
   1.524 +   * @param number id
   1.525 +   *        The id of the window containing the WebGL context.
   1.526 +   * @param WebGLRenderingContext context
   1.527 +   *        The WebGL context used in the cache and proxy instances.
   1.528 +   */
   1.529 +  registerContextForWindow: function(id, context) {
   1.530 +    let cache = new WebGLCache(id, context);
   1.531 +    let proxy = new WebGLProxy(id, context, cache, this);
   1.532 +    cache.refreshState(proxy);
   1.533 +
   1.534 +    this._contexts.set(context, {
   1.535 +      ownerWindow: id,
   1.536 +      cache: cache,
   1.537 +      proxy: proxy
   1.538 +    });
   1.539 +  },
   1.540 +
   1.541 +  /**
   1.542 +   * Removes all WebGLCache and WebGLProxy instances for a particular window.
   1.543 +   *
   1.544 +   * @param number id
   1.545 +   *        The id of the window containing the WebGL context.
   1.546 +   */
   1.547 +  unregisterContextsForWindow: function(id) {
   1.548 +    removeFromMap(this._contexts, e => e.ownerWindow == id);
   1.549 +  },
   1.550 +
   1.551 +  /**
   1.552 +   * Gets the WebGLCache and WebGLProxy instances for a particular context.
   1.553 +   *
   1.554 +   * @param WebGLRenderingContext context
   1.555 +   *        The WebGL context used in the cache and proxy instances.
   1.556 +   * @return object
   1.557 +   *         An object containing the corresponding { cache, proxy } instances.
   1.558 +   */
   1.559 +  for: function(context) {
   1.560 +    return this._contexts.get(context);
   1.561 +  },
   1.562 +
   1.563 +  /**
   1.564 +   * Set this flag to true to stop observing any context function calls.
   1.565 +   */
   1.566 +  suppressHandlers: false,
   1.567 +
   1.568 +  /**
   1.569 +   * Called immediately *after* 'linkProgram' is requested in the context.
   1.570 +   *
   1.571 +   * @param array glArgs
   1.572 +   *        Overridable arguments with which the function is called.
   1.573 +   * @param void glResult
   1.574 +   *        The returned value of the original function call.
   1.575 +   * @param WebGLCache cache
   1.576 +   *        The state storage for the WebGL context initiating this call.
   1.577 +   * @param WebGLProxy proxy
   1.578 +   *        The proxy methods for the WebGL context initiating this call.
   1.579 +   */
   1.580 +  linkProgram: function(glArgs, glResult, cache, proxy) {
   1.581 +    let program = glArgs[0];
   1.582 +    let shaders = proxy.getAttachedShaders(program);
   1.583 +    cache.addProgram(program, PROGRAM_DEFAULT_TRAITS);
   1.584 +    emit(this, "program-linked", program, shaders, cache, proxy);
   1.585 +  },
   1.586 +
   1.587 +  /**
   1.588 +   * Called immediately *after* 'getAttribLocation' is requested in the context.
   1.589 +   *
   1.590 +   * @param array glArgs
   1.591 +   *        Overridable arguments with which the function is called.
   1.592 +   * @param GLint glResult
   1.593 +   *        The returned value of the original function call.
   1.594 +   * @param WebGLCache cache
   1.595 +   *        The state storage for the WebGL context initiating this call.
   1.596 +   */
   1.597 +  getAttribLocation: function(glArgs, glResult, cache) {
   1.598 +    // Make sure the attribute's value is legal before caching.
   1.599 +    if (glResult < 0) {
   1.600 +      return;
   1.601 +    }
   1.602 +    let [program, name] = glArgs;
   1.603 +    cache.addAttribute(program, name, glResult);
   1.604 +  },
   1.605 +
   1.606 +  /**
   1.607 +   * Called immediately *after* 'getUniformLocation' is requested in the context.
   1.608 +   *
   1.609 +   * @param array glArgs
   1.610 +   *        Overridable arguments with which the function is called.
   1.611 +   * @param WebGLUniformLocation glResult
   1.612 +   *        The returned value of the original function call.
   1.613 +   * @param WebGLCache cache
   1.614 +   *        The state storage for the WebGL context initiating this call.
   1.615 +   */
   1.616 +  getUniformLocation: function(glArgs, glResult, cache) {
   1.617 +    // Make sure the uniform's value is legal before caching.
   1.618 +    if (!glResult) {
   1.619 +      return;
   1.620 +    }
   1.621 +    let [program, name] = glArgs;
   1.622 +    cache.addUniform(program, name, glResult);
   1.623 +  },
   1.624 +
   1.625 +  /**
   1.626 +   * Called immediately *before* 'enableVertexAttribArray' or
   1.627 +   * 'disableVertexAttribArray'is requested in the context.
   1.628 +   *
   1.629 +   * @param array glArgs
   1.630 +   *        Overridable arguments with which the function is called.
   1.631 +   * @param WebGLCache cache
   1.632 +   *        The state storage for the WebGL context initiating this call.
   1.633 +   */
   1.634 +  toggleVertexAttribArray: function(glArgs, cache) {
   1.635 +    glArgs[0] = cache.getCurrentAttributeLocation(glArgs[0]);
   1.636 +    return glArgs[0] < 0; // Return true to break original function call.
   1.637 +  },
   1.638 +
   1.639 +  /**
   1.640 +   * Called immediately *before* 'attribute_' is requested in the context.
   1.641 +   *
   1.642 +   * @param array glArgs
   1.643 +   *        Overridable arguments with which the function is called.
   1.644 +   * @param WebGLCache cache
   1.645 +   *        The state storage for the WebGL context initiating this call.
   1.646 +   */
   1.647 +  attribute_: function(glArgs, cache) {
   1.648 +    glArgs[0] = cache.getCurrentAttributeLocation(glArgs[0]);
   1.649 +    return glArgs[0] < 0; // Return true to break original function call.
   1.650 +  },
   1.651 +
   1.652 +  /**
   1.653 +   * Called immediately *before* 'uniform_' is requested in the context.
   1.654 +   *
   1.655 +   * @param array glArgs
   1.656 +   *        Overridable arguments with which the function is called.
   1.657 +   * @param WebGLCache cache
   1.658 +   *        The state storage for the WebGL context initiating this call.
   1.659 +   */
   1.660 +  uniform_: function(glArgs, cache) {
   1.661 +    glArgs[0] = cache.getCurrentUniformLocation(glArgs[0]);
   1.662 +    return !glArgs[0]; // Return true to break original function call.
   1.663 +  },
   1.664 +
   1.665 +  /**
   1.666 +   * Called immediately *before* 'useProgram' is requested in the context.
   1.667 +   *
   1.668 +   * @param array glArgs
   1.669 +   *        Overridable arguments with which the function is called.
   1.670 +   * @param WebGLCache cache
   1.671 +   *        The state storage for the WebGL context initiating this call.
   1.672 +   */
   1.673 +  useProgram: function(glArgs, cache) {
   1.674 +    // Manually keeping a cache and not using gl.getParameter(CURRENT_PROGRAM)
   1.675 +    // because gl.get* functions are slow as potatoes.
   1.676 +    cache.currentProgram = glArgs[0];
   1.677 +  },
   1.678 +
   1.679 +  /**
   1.680 +   * Called immediately *before* 'enable' is requested in the context.
   1.681 +   *
   1.682 +   * @param array glArgs
   1.683 +   *        Overridable arguments with which the function is called.
   1.684 +   * @param WebGLCache cache
   1.685 +   *        The state storage for the WebGL context initiating this call.
   1.686 +   */
   1.687 +  enable: function(glArgs, cache) {
   1.688 +    cache.currentState[glArgs[0]] = true;
   1.689 +  },
   1.690 +
   1.691 +  /**
   1.692 +   * Called immediately *before* 'disable' is requested in the context.
   1.693 +   *
   1.694 +   * @param array glArgs
   1.695 +   *        Overridable arguments with which the function is called.
   1.696 +   * @param WebGLCache cache
   1.697 +   *        The state storage for the WebGL context initiating this call.
   1.698 +   */
   1.699 +  disable: function(glArgs, cache) {
   1.700 +    cache.currentState[glArgs[0]] = false;
   1.701 +  },
   1.702 +
   1.703 +  /**
   1.704 +   * Called immediately *before* 'blendColor' is requested in the context.
   1.705 +   *
   1.706 +   * @param array glArgs
   1.707 +   *        Overridable arguments with which the function is called.
   1.708 +   * @param WebGLCache cache
   1.709 +   *        The state storage for the WebGL context initiating this call.
   1.710 +   */
   1.711 +  blendColor: function(glArgs, cache) {
   1.712 +    let blendColor = cache.currentState.blendColor;
   1.713 +    blendColor[0] = glArgs[0];
   1.714 +    blendColor[1] = glArgs[1];
   1.715 +    blendColor[2] = glArgs[2];
   1.716 +    blendColor[3] = glArgs[3];
   1.717 +  },
   1.718 +
   1.719 +  /**
   1.720 +   * Called immediately *before* 'blendEquation' is requested in the context.
   1.721 +   *
   1.722 +   * @param array glArgs
   1.723 +   *        Overridable arguments with which the function is called.
   1.724 +   * @param WebGLCache cache
   1.725 +   *        The state storage for the WebGL context initiating this call.
   1.726 +   */
   1.727 +  blendEquation: function(glArgs, cache) {
   1.728 +    let state = cache.currentState;
   1.729 +    state.blendEquationRgb = state.blendEquationAlpha = glArgs[0];
   1.730 +  },
   1.731 +
   1.732 +  /**
   1.733 +   * Called immediately *before* 'blendEquationSeparate' is requested in the context.
   1.734 +   *
   1.735 +   * @param array glArgs
   1.736 +   *        Overridable arguments with which the function is called.
   1.737 +   * @param WebGLCache cache
   1.738 +   *        The state storage for the WebGL context initiating this call.
   1.739 +   */
   1.740 +  blendEquationSeparate: function(glArgs, cache) {
   1.741 +    let state = cache.currentState;
   1.742 +    state.blendEquationRgb = glArgs[0];
   1.743 +    state.blendEquationAlpha = glArgs[1];
   1.744 +  },
   1.745 +
   1.746 +  /**
   1.747 +   * Called immediately *before* 'blendFunc' is requested in the context.
   1.748 +   *
   1.749 +   * @param array glArgs
   1.750 +   *        Overridable arguments with which the function is called.
   1.751 +   * @param WebGLCache cache
   1.752 +   *        The state storage for the WebGL context initiating this call.
   1.753 +   */
   1.754 +  blendFunc: function(glArgs, cache) {
   1.755 +    let state = cache.currentState;
   1.756 +    state.blendSrcRgb = state.blendSrcAlpha = glArgs[0];
   1.757 +    state.blendDstRgb = state.blendDstAlpha = glArgs[1];
   1.758 +  },
   1.759 +
   1.760 +  /**
   1.761 +   * Called immediately *before* 'blendFuncSeparate' is requested in the context.
   1.762 +   *
   1.763 +   * @param array glArgs
   1.764 +   *        Overridable arguments with which the function is called.
   1.765 +   * @param WebGLCache cache
   1.766 +   *        The state storage for the WebGL context initiating this call.
   1.767 +   */
   1.768 +  blendFuncSeparate: function(glArgs, cache) {
   1.769 +    let state = cache.currentState;
   1.770 +    state.blendSrcRgb = glArgs[0];
   1.771 +    state.blendDstRgb = glArgs[1];
   1.772 +    state.blendSrcAlpha = glArgs[2];
   1.773 +    state.blendDstAlpha = glArgs[3];
   1.774 +  },
   1.775 +
   1.776 +  /**
   1.777 +   * Called immediately *before* 'drawArrays' or 'drawElements' is requested
   1.778 +   * in the context.
   1.779 +   *
   1.780 +   * @param array glArgs
   1.781 +   *        Overridable arguments with which the function is called.
   1.782 +   * @param WebGLCache cache
   1.783 +   *        The state storage for the WebGL context initiating this call.
   1.784 +   * @param WebGLProxy proxy
   1.785 +   *        The proxy methods for the WebGL context initiating this call.
   1.786 +   */
   1.787 +  beforeDraw_: function(glArgs, cache, proxy) {
   1.788 +    let traits = cache.currentProgramTraits;
   1.789 +
   1.790 +    // Handle program blackboxing.
   1.791 +    if (traits & PROGRAM_BLACKBOX_TRAIT) {
   1.792 +      return true; // Return true to break original function call.
   1.793 +    }
   1.794 +    // Handle program highlighting.
   1.795 +    if (traits & PROGRAM_HIGHLIGHT_TRAIT) {
   1.796 +      proxy.enableHighlighting();
   1.797 +    }
   1.798 +
   1.799 +    return false;
   1.800 +  },
   1.801 +
   1.802 +  /**
   1.803 +   * Called immediately *after* 'drawArrays' or 'drawElements' is requested
   1.804 +   * in the context.
   1.805 +   *
   1.806 +   * @param array glArgs
   1.807 +   *        Overridable arguments with which the function is called.
   1.808 +   * @param void glResult
   1.809 +   *        The returned value of the original function call.
   1.810 +   * @param WebGLCache cache
   1.811 +   *        The state storage for the WebGL context initiating this call.
   1.812 +   * @param WebGLProxy proxy
   1.813 +   *        The proxy methods for the WebGL context initiating this call.
   1.814 +   */
   1.815 +  afterDraw_: function(glArgs, glResult, cache, proxy) {
   1.816 +    let traits = cache.currentProgramTraits;
   1.817 +
   1.818 +    // Handle program highlighting.
   1.819 +    if (traits & PROGRAM_HIGHLIGHT_TRAIT) {
   1.820 +      proxy.disableHighlighting();
   1.821 +    }
   1.822 +  }
   1.823 +};
   1.824 +
   1.825 +/**
   1.826 + * A mechanism for storing a single WebGL context's state, programs, shaders,
   1.827 + * attributes or uniforms.
   1.828 + *
   1.829 + * @param number id
   1.830 + *        The id of the window containing the WebGL context.
   1.831 + * @param WebGLRenderingContext context
   1.832 + *        The WebGL context for which the state is stored.
   1.833 + */
   1.834 +function WebGLCache(id, context) {
   1.835 +  this._id = id;
   1.836 +  this._gl = context;
   1.837 +  this._programs = new Map();
   1.838 +  this.currentState = {};
   1.839 +}
   1.840 +
   1.841 +WebGLCache.prototype = {
   1.842 +  _id: 0,
   1.843 +  _gl: null,
   1.844 +  _programs: null,
   1.845 +  _currentProgramInfo: null,
   1.846 +  _currentAttributesMap: null,
   1.847 +  _currentUniformsMap: null,
   1.848 +
   1.849 +  get ownerWindow() this._id,
   1.850 +  get ownerContext() this._gl,
   1.851 +
   1.852 +  /**
   1.853 +   * A collection of flags or properties representing the context's state.
   1.854 +   * Implemented as an object hash and not a Map instance because keys are
   1.855 +   * always either strings or numbers.
   1.856 +   */
   1.857 +  currentState: null,
   1.858 +
   1.859 +  /**
   1.860 +   * Populates the current state with values retrieved from the context.
   1.861 +   *
   1.862 +   * @param WebGLProxy proxy
   1.863 +   *        The proxy methods for the WebGL context owning the state.
   1.864 +   */
   1.865 +  refreshState: function(proxy) {
   1.866 +    let gl = this._gl;
   1.867 +    let s = this.currentState;
   1.868 +
   1.869 +    // Populate only with the necessary parameters. Not all default WebGL
   1.870 +    // state values are required.
   1.871 +    s[gl.BLEND] = proxy.isEnabled("BLEND");
   1.872 +    s.blendColor = proxy.getParameter("BLEND_COLOR");
   1.873 +    s.blendEquationRgb = proxy.getParameter("BLEND_EQUATION_RGB");
   1.874 +    s.blendEquationAlpha = proxy.getParameter("BLEND_EQUATION_ALPHA");
   1.875 +    s.blendSrcRgb = proxy.getParameter("BLEND_SRC_RGB");
   1.876 +    s.blendSrcAlpha = proxy.getParameter("BLEND_SRC_ALPHA");
   1.877 +    s.blendDstRgb = proxy.getParameter("BLEND_DST_RGB");
   1.878 +    s.blendDstAlpha = proxy.getParameter("BLEND_DST_ALPHA");
   1.879 +  },
   1.880 +
   1.881 +  /**
   1.882 +   * Adds a program to the cache.
   1.883 +   *
   1.884 +   * @param WebGLProgram program
   1.885 +   *        The shader for which the traits are to be cached.
   1.886 +   * @param number traits
   1.887 +   *        A default properties mask set for the program.
   1.888 +   */
   1.889 +  addProgram: function(program, traits) {
   1.890 +    this._programs.set(program, {
   1.891 +      traits: traits,
   1.892 +      attributes: [], // keys are GLints (numbers)
   1.893 +      uniforms: new Map() // keys are WebGLUniformLocations (objects)
   1.894 +    });
   1.895 +  },
   1.896 +
   1.897 +  /**
   1.898 +   * Adds a specific trait to a program. The effect of such properties is
   1.899 +   * determined by the consumer of this cache.
   1.900 +   *
   1.901 +   * @param WebGLProgram program
   1.902 +   *        The program to add the trait to.
   1.903 +   * @param number trait
   1.904 +   *        The property added to the program.
   1.905 +   */
   1.906 +  setProgramTrait: function(program, trait) {
   1.907 +    this._programs.get(program).traits |= trait;
   1.908 +  },
   1.909 +
   1.910 +  /**
   1.911 +   * Removes a specific trait from a program.
   1.912 +   *
   1.913 +   * @param WebGLProgram program
   1.914 +   *        The program to remove the trait from.
   1.915 +   * @param number trait
   1.916 +   *        The property removed from the program.
   1.917 +   */
   1.918 +  unsetProgramTrait: function(program, trait) {
   1.919 +    this._programs.get(program).traits &= ~trait;
   1.920 +  },
   1.921 +
   1.922 +  /**
   1.923 +   * Sets the currently used program in the context.
   1.924 +   * @param WebGLProgram program
   1.925 +   */
   1.926 +  set currentProgram(program) {
   1.927 +    let programInfo = this._programs.get(program);
   1.928 +    if (programInfo == null) {
   1.929 +      return;
   1.930 +    }
   1.931 +    this._currentProgramInfo = programInfo;
   1.932 +    this._currentAttributesMap = programInfo.attributes;
   1.933 +    this._currentUniformsMap = programInfo.uniforms;
   1.934 +  },
   1.935 +
   1.936 +  /**
   1.937 +   * Gets the traits for the currently used program.
   1.938 +   * @return number
   1.939 +   */
   1.940 +  get currentProgramTraits() {
   1.941 +    return this._currentProgramInfo.traits;
   1.942 +  },
   1.943 +
   1.944 +  /**
   1.945 +   * Adds an attribute to the cache.
   1.946 +   *
   1.947 +   * @param WebGLProgram program
   1.948 +   *        The program for which the attribute is bound.
   1.949 +   * @param string name
   1.950 +   *        The attribute name.
   1.951 +   * @param GLint value
   1.952 +   *        The attribute value.
   1.953 +   */
   1.954 +  addAttribute: function(program, name, value) {
   1.955 +    this._programs.get(program).attributes[value] = {
   1.956 +      name: name,
   1.957 +      value: value
   1.958 +    };
   1.959 +  },
   1.960 +
   1.961 +  /**
   1.962 +   * Adds a uniform to the cache.
   1.963 +   *
   1.964 +   * @param WebGLProgram program
   1.965 +   *        The program for which the uniform is bound.
   1.966 +   * @param string name
   1.967 +   *        The uniform name.
   1.968 +   * @param WebGLUniformLocation value
   1.969 +   *        The uniform value.
   1.970 +   */
   1.971 +  addUniform: function(program, name, value) {
   1.972 +    this._programs.get(program).uniforms.set(new XPCNativeWrapper(value), {
   1.973 +      name: name,
   1.974 +      value: value
   1.975 +    });
   1.976 +  },
   1.977 +
   1.978 +  /**
   1.979 +   * Updates the attribute locations for a specific program.
   1.980 +   * This is necessary, for example, when the shader is relinked and all the
   1.981 +   * attribute locations become obsolete.
   1.982 +   *
   1.983 +   * @param WebGLProgram program
   1.984 +   *        The program for which the attributes need updating.
   1.985 +   */
   1.986 +  updateAttributesForProgram: function(program) {
   1.987 +    let attributes = this._programs.get(program).attributes;
   1.988 +    for (let attribute of attributes) {
   1.989 +      attribute.value = this._gl.getAttribLocation(program, attribute.name);
   1.990 +    }
   1.991 +  },
   1.992 +
   1.993 +  /**
   1.994 +   * Updates the uniform locations for a specific program.
   1.995 +   * This is necessary, for example, when the shader is relinked and all the
   1.996 +   * uniform locations become obsolete.
   1.997 +   *
   1.998 +   * @param WebGLProgram program
   1.999 +   *        The program for which the uniforms need updating.
  1.1000 +   */
  1.1001 +  updateUniformsForProgram: function(program) {
  1.1002 +    let uniforms = this._programs.get(program).uniforms;
  1.1003 +    for (let [, uniform] of uniforms) {
  1.1004 +      uniform.value = this._gl.getUniformLocation(program, uniform.name);
  1.1005 +    }
  1.1006 +  },
  1.1007 +
  1.1008 +  /**
  1.1009 +   * Gets the actual attribute location in a specific program.
  1.1010 +   * When relinked, all the attribute locations become obsolete and are updated
  1.1011 +   * in the cache. This method returns the (current) real attribute location.
  1.1012 +   *
  1.1013 +   * @param GLint initialValue
  1.1014 +   *        The initial attribute value.
  1.1015 +   * @return GLint
  1.1016 +   *         The current attribute value, or the initial value if it's already
  1.1017 +   *         up to date with its corresponding program.
  1.1018 +   */
  1.1019 +  getCurrentAttributeLocation: function(initialValue) {
  1.1020 +    let attributes = this._currentAttributesMap;
  1.1021 +    let currentInfo = attributes ? attributes[initialValue] : null;
  1.1022 +    return currentInfo ? currentInfo.value : initialValue;
  1.1023 +  },
  1.1024 +
  1.1025 +  /**
  1.1026 +   * Gets the actual uniform location in a specific program.
  1.1027 +   * When relinked, all the uniform locations become obsolete and are updated
  1.1028 +   * in the cache. This method returns the (current) real uniform location.
  1.1029 +   *
  1.1030 +   * @param WebGLUniformLocation initialValue
  1.1031 +   *        The initial uniform value.
  1.1032 +   * @return WebGLUniformLocation
  1.1033 +   *         The current uniform value, or the initial value if it's already
  1.1034 +   *         up to date with its corresponding program.
  1.1035 +   */
  1.1036 +  getCurrentUniformLocation: function(initialValue) {
  1.1037 +    let uniforms = this._currentUniformsMap;
  1.1038 +    let currentInfo = uniforms ? uniforms.get(initialValue) : null;
  1.1039 +    return currentInfo ? currentInfo.value : initialValue;
  1.1040 +  }
  1.1041 +};
  1.1042 +
  1.1043 +/**
  1.1044 + * A mechanism for injecting or qureying state into/from a single WebGL context.
  1.1045 + *
  1.1046 + * Any interaction with a WebGL context should go through this proxy.
  1.1047 + * Otherwise, the corresponding observer would register the calls as coming
  1.1048 + * from content, which is usually not desirable. Infinite call stacks are bad.
  1.1049 + *
  1.1050 + * @param number id
  1.1051 + *        The id of the window containing the WebGL context.
  1.1052 + * @param WebGLRenderingContext context
  1.1053 + *        The WebGL context used for the proxy methods.
  1.1054 + * @param WebGLCache cache
  1.1055 + *        The state storage for the corresponding context.
  1.1056 + * @param WebGLObserver observer
  1.1057 + *        The observer watching function calls in the corresponding context.
  1.1058 + */
  1.1059 +function WebGLProxy(id, context, cache, observer) {
  1.1060 +  this._id = id;
  1.1061 +  this._gl = context;
  1.1062 +  this._cache = cache;
  1.1063 +  this._observer = observer;
  1.1064 +
  1.1065 +  let exports = [
  1.1066 +    "isEnabled",
  1.1067 +    "getParameter",
  1.1068 +    "getAttachedShaders",
  1.1069 +    "getShaderSource",
  1.1070 +    "getShaderOfType",
  1.1071 +    "compileShader",
  1.1072 +    "enableHighlighting",
  1.1073 +    "disableHighlighting"
  1.1074 +  ];
  1.1075 +  exports.forEach(e => this[e] = (...args) => this._call(e, args));
  1.1076 +}
  1.1077 +
  1.1078 +WebGLProxy.prototype = {
  1.1079 +  _id: 0,
  1.1080 +  _gl: null,
  1.1081 +  _cache: null,
  1.1082 +  _observer: null,
  1.1083 +
  1.1084 +  get ownerWindow() this._id,
  1.1085 +  get ownerContext() this._gl,
  1.1086 +
  1.1087 +  /**
  1.1088 +   * Test whether a WebGL capability is enabled.
  1.1089 +   *
  1.1090 +   * @param string name
  1.1091 +   *        The WebGL capability name, for example "BLEND".
  1.1092 +   * @return boolean
  1.1093 +   *         True if enabled, false otherwise.
  1.1094 +   */
  1.1095 +  _isEnabled: function(name) {
  1.1096 +    return this._gl.isEnabled(this._gl[name]);
  1.1097 +  },
  1.1098 +
  1.1099 +  /**
  1.1100 +   * Returns the value for the specified WebGL parameter name.
  1.1101 +   *
  1.1102 +   * @param string name
  1.1103 +   *        The WebGL parameter name, for example "BLEND_COLOR".
  1.1104 +   * @return any
  1.1105 +   *         The corresponding parameter's value.
  1.1106 +   */
  1.1107 +  _getParameter: function(name) {
  1.1108 +    return this._gl.getParameter(this._gl[name]);
  1.1109 +  },
  1.1110 +
  1.1111 +  /**
  1.1112 +   * Returns the renderbuffer property value for the specified WebGL parameter.
  1.1113 +   * If no renderbuffer binding is available, null is returned.
  1.1114 +   *
  1.1115 +   * @param string name
  1.1116 +   *        The WebGL parameter name, for example "BLEND_COLOR".
  1.1117 +   * @return any
  1.1118 +   *         The corresponding parameter's value.
  1.1119 +   */
  1.1120 +  _getRenderbufferParameter: function(name) {
  1.1121 +    if (!this._getParameter("RENDERBUFFER_BINDING")) {
  1.1122 +      return null;
  1.1123 +    }
  1.1124 +    let gl = this._gl;
  1.1125 +    return gl.getRenderbufferParameter(gl.RENDERBUFFER, gl[name]);
  1.1126 +  },
  1.1127 +
  1.1128 +  /**
  1.1129 +   * Returns the framebuffer property value for the specified WebGL parameter.
  1.1130 +   * If no framebuffer binding is available, null is returned.
  1.1131 +   *
  1.1132 +   * @param string type
  1.1133 +   *        The framebuffer object attachment point, for example "COLOR_ATTACHMENT0".
  1.1134 +   * @param string name
  1.1135 +   *        The WebGL parameter name, for example "FRAMEBUFFER_ATTACHMENT_OBJECT_NAME".
  1.1136 +   *        If unspecified, defaults to "FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE".
  1.1137 +   * @return any
  1.1138 +   *         The corresponding parameter's value.
  1.1139 +   */
  1.1140 +  _getFramebufferAttachmentParameter: function(type, name = "FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE") {
  1.1141 +    if (!this._getParameter("FRAMEBUFFER_BINDING")) {
  1.1142 +      return null;
  1.1143 +    }
  1.1144 +    let gl = this._gl;
  1.1145 +    return gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl[type], gl[name]);
  1.1146 +  },
  1.1147 +
  1.1148 +  /**
  1.1149 +   * Returns the shader objects attached to a program object.
  1.1150 +   *
  1.1151 +   * @param WebGLProgram program
  1.1152 +   *        The program for which to retrieve the attached shaders.
  1.1153 +   * @return array
  1.1154 +   *         The attached vertex and fragment shaders.
  1.1155 +   */
  1.1156 +  _getAttachedShaders: function(program) {
  1.1157 +    return this._gl.getAttachedShaders(program);
  1.1158 +  },
  1.1159 +
  1.1160 +  /**
  1.1161 +   * Returns the source code string from a shader object.
  1.1162 +   *
  1.1163 +   * @param WebGLShader shader
  1.1164 +   *        The shader for which to retrieve the source code.
  1.1165 +   * @return string
  1.1166 +   *         The shader's source code.
  1.1167 +   */
  1.1168 +  _getShaderSource: function(shader) {
  1.1169 +    return this._gl.getShaderSource(shader);
  1.1170 +  },
  1.1171 +
  1.1172 +  /**
  1.1173 +   * Finds a shader of the specified type in a list.
  1.1174 +   *
  1.1175 +   * @param WebGLShader[] shaders
  1.1176 +   *        The shaders for which to check the type.
  1.1177 +   * @param string type
  1.1178 +   *        Either "vertex" or "fragment".
  1.1179 +   * @return WebGLShader | null
  1.1180 +   *         The shader of the specified type, or null if nothing is found.
  1.1181 +   */
  1.1182 +  _getShaderOfType: function(shaders, type) {
  1.1183 +    let gl = this._gl;
  1.1184 +    let shaderTypeEnum = {
  1.1185 +      vertex: gl.VERTEX_SHADER,
  1.1186 +      fragment: gl.FRAGMENT_SHADER
  1.1187 +    }[type];
  1.1188 +
  1.1189 +    for (let shader of shaders) {
  1.1190 +      if (gl.getShaderParameter(shader, gl.SHADER_TYPE) == shaderTypeEnum) {
  1.1191 +        return shader;
  1.1192 +      }
  1.1193 +    }
  1.1194 +    return null;
  1.1195 +  },
  1.1196 +
  1.1197 +  /**
  1.1198 +   * Changes a shader's source code and relinks the respective program.
  1.1199 +   *
  1.1200 +   * @param WebGLProgram program
  1.1201 +   *        The program who's linked shader is to be modified.
  1.1202 +   * @param WebGLShader shader
  1.1203 +   *        The shader to be modified.
  1.1204 +   * @param string text
  1.1205 +   *        The new shader source code.
  1.1206 +   * @return object
  1.1207 +   *         An object containing the compilation and linking status.
  1.1208 +   */
  1.1209 +  _compileShader: function(program, shader, text) {
  1.1210 +    let gl = this._gl;
  1.1211 +    gl.shaderSource(shader, text);
  1.1212 +    gl.compileShader(shader);
  1.1213 +    gl.linkProgram(program);
  1.1214 +
  1.1215 +    let error = { compile: "", link: "" };
  1.1216 +
  1.1217 +    if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
  1.1218 +      error.compile = gl.getShaderInfoLog(shader);
  1.1219 +    }
  1.1220 +    if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
  1.1221 +      error.link = gl.getShaderInfoLog(shader);
  1.1222 +    }
  1.1223 +
  1.1224 +    this._cache.updateAttributesForProgram(program);
  1.1225 +    this._cache.updateUniformsForProgram(program);
  1.1226 +
  1.1227 +    return error;
  1.1228 +  },
  1.1229 +
  1.1230 +  /**
  1.1231 +   * Enables color blending based on the geometry highlight tint.
  1.1232 +   */
  1.1233 +  _enableHighlighting: function() {
  1.1234 +    let gl = this._gl;
  1.1235 +
  1.1236 +    // Avoid changing the blending params when "rendering to texture".
  1.1237 +
  1.1238 +    // Check drawing to a custom framebuffer bound to the default renderbuffer.
  1.1239 +    let hasFramebuffer = this._getParameter("FRAMEBUFFER_BINDING");
  1.1240 +    let hasRenderbuffer = this._getParameter("RENDERBUFFER_BINDING");
  1.1241 +    if (hasFramebuffer && !hasRenderbuffer) {
  1.1242 +      return;
  1.1243 +    }
  1.1244 +
  1.1245 +    // Check drawing to a depth or stencil component of the framebuffer.
  1.1246 +    let writesDepth = this._getFramebufferAttachmentParameter("DEPTH_ATTACHMENT");
  1.1247 +    let writesStencil = this._getFramebufferAttachmentParameter("STENCIL_ATTACHMENT");
  1.1248 +    if (writesDepth || writesStencil) {
  1.1249 +      return;
  1.1250 +    }
  1.1251 +
  1.1252 +    // Non-premultiplied alpha blending based on a predefined constant color.
  1.1253 +    // Simply using gl.colorMask won't work, because we want non-tinted colors
  1.1254 +    // to be drawn as black, not ignored.
  1.1255 +    gl.enable(gl.BLEND);
  1.1256 +    gl.blendColor.apply(gl, this.highlightTint);
  1.1257 +    gl.blendEquation(gl.FUNC_ADD);
  1.1258 +    gl.blendFunc(gl.CONSTANT_COLOR, gl.ONE_MINUS_SRC_ALPHA, gl.CONSTANT_COLOR, gl.ZERO);
  1.1259 +    this.wasHighlighting = true;
  1.1260 +  },
  1.1261 +
  1.1262 +  /**
  1.1263 +   * Disables color blending based on the geometry highlight tint, by
  1.1264 +   * reverting the corresponding params back to their original values.
  1.1265 +   */
  1.1266 +  _disableHighlighting: function() {
  1.1267 +    let gl = this._gl;
  1.1268 +    let s = this._cache.currentState;
  1.1269 +
  1.1270 +    gl[s[gl.BLEND] ? "enable" : "disable"](gl.BLEND);
  1.1271 +    gl.blendColor.apply(gl, s.blendColor);
  1.1272 +    gl.blendEquationSeparate(s.blendEquationRgb, s.blendEquationAlpha);
  1.1273 +    gl.blendFuncSeparate(s.blendSrcRgb, s.blendDstRgb, s.blendSrcAlpha, s.blendDstAlpha);
  1.1274 +  },
  1.1275 +
  1.1276 +  /**
  1.1277 +   * The color tint used for highlighting geometry.
  1.1278 +   * @see _enableHighlighting and _disableHighlighting.
  1.1279 +   */
  1.1280 +  highlightTint: [0, 0, 0, 0],
  1.1281 +
  1.1282 +  /**
  1.1283 +   * Executes a function in this object.
  1.1284 +   *
  1.1285 +   * This method makes sure that any handlers in the context observer are
  1.1286 +   * suppressed, hence stopping observing any context function calls.
  1.1287 +   *
  1.1288 +   * @param string funcName
  1.1289 +   *        The function to call.
  1.1290 +   * @param array args
  1.1291 +   *        An array of arguments.
  1.1292 +   * @return any
  1.1293 +   *         The called function result.
  1.1294 +   */
  1.1295 +  _call: function(funcName, args) {
  1.1296 +    let prevState = this._observer.suppressHandlers;
  1.1297 +
  1.1298 +    this._observer.suppressHandlers = true;
  1.1299 +    let result = this["_" + funcName].apply(this, args);
  1.1300 +    this._observer.suppressHandlers = prevState;
  1.1301 +
  1.1302 +    return result;
  1.1303 +  }
  1.1304 +};
  1.1305 +
  1.1306 +// Utility functions.
  1.1307 +
  1.1308 +function removeFromMap(map, predicate) {
  1.1309 +  for (let [key, value] of map) {
  1.1310 +    if (predicate(value)) {
  1.1311 +      map.delete(key);
  1.1312 +    }
  1.1313 +  }
  1.1314 +};
  1.1315 +
  1.1316 +function removeFromArray(array, predicate) {
  1.1317 +  for (let value of array) {
  1.1318 +    if (predicate(value)) {
  1.1319 +      array.splice(array.indexOf(value), 1);
  1.1320 +    }
  1.1321 +  }
  1.1322 +}

mercurial