toolkit/devtools/server/actors/webgl.js

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this
     3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     4 "use strict";
     6 const {Cc, Ci, Cu, Cr} = require("chrome");
     7 const events = require("sdk/event/core");
     8 const protocol = require("devtools/server/protocol");
     9 const { ContentObserver } = require("devtools/content-observer");
    11 const { on, once, off, emit } = events;
    12 const { method, Arg, Option, RetVal } = protocol;
    14 const WEBGL_CONTEXT_NAMES = ["webgl", "experimental-webgl", "moz-webgl"];
    16 // These traits are bit masks. Make sure they're powers of 2.
    17 const PROGRAM_DEFAULT_TRAITS = 0;
    18 const PROGRAM_BLACKBOX_TRAIT = 1;
    19 const PROGRAM_HIGHLIGHT_TRAIT = 2;
    21 exports.register = function(handle) {
    22   handle.addTabActor(WebGLActor, "webglActor");
    23 }
    25 exports.unregister = function(handle) {
    26   handle.removeTabActor(WebGLActor);
    27 }
    29 /**
    30  * A WebGL Shader contributing to building a WebGL Program.
    31  * You can either retrieve, or compile the source of a shader, which will
    32  * automatically inflict the necessary changes to the WebGL state.
    33  */
    34 let ShaderActor = protocol.ActorClass({
    35   typeName: "gl-shader",
    37   /**
    38    * Create the shader actor.
    39    *
    40    * @param DebuggerServerConnection conn
    41    *        The server connection.
    42    * @param WebGLProgram program
    43    *        The WebGL program being linked.
    44    * @param WebGLShader shader
    45    *        The cooresponding vertex or fragment shader.
    46    * @param WebGLProxy proxy
    47    *        The proxy methods for the WebGL context owning this shader.
    48    */
    49   initialize: function(conn, program, shader, proxy) {
    50     protocol.Actor.prototype.initialize.call(this, conn);
    51     this.program = program;
    52     this.shader = shader;
    53     this.text = proxy.getShaderSource(shader);
    54     this.linkedProxy = proxy;
    55   },
    57   /**
    58    * Gets the source code for this shader.
    59    */
    60   getText: method(function() {
    61     return this.text;
    62   }, {
    63     response: { text: RetVal("string") }
    64   }),
    66   /**
    67    * Sets and compiles new source code for this shader.
    68    */
    69   compile: method(function(text) {
    70     // Get the shader and corresponding program to change via the WebGL proxy.
    71     let { linkedProxy: proxy, shader, program } = this;
    73     // Get the new shader source to inject.
    74     let oldText = this.text;
    75     let newText = text;
    77     // Overwrite the shader's source.
    78     let error = proxy.compileShader(program, shader, this.text = newText);
    80     // If something went wrong, revert to the previous shader.
    81     if (error.compile || error.link) {
    82       proxy.compileShader(program, shader, this.text = oldText);
    83       return error;
    84     }
    85     return undefined;
    86   }, {
    87     request: { text: Arg(0, "string") },
    88     response: { error: RetVal("nullable:json") }
    89   })
    90 });
    92 /**
    93  * The corresponding Front object for the ShaderActor.
    94  */
    95 let ShaderFront = protocol.FrontClass(ShaderActor, {
    96   initialize: function(client, form) {
    97     protocol.Front.prototype.initialize.call(this, client, form);
    98   }
    99 });
   101 /**
   102  * A WebGL program is composed (at the moment, analogue to OpenGL ES 2.0)
   103  * of two shaders: a vertex shader and a fragment shader.
   104  */
   105 let ProgramActor = protocol.ActorClass({
   106   typeName: "gl-program",
   108   /**
   109    * Create the program actor.
   110    *
   111    * @param DebuggerServerConnection conn
   112    *        The server connection.
   113    * @param WebGLProgram program
   114    *        The WebGL program being linked.
   115    * @param WebGLShader[] shaders
   116    *        The WebGL program's cooresponding vertex and fragment shaders.
   117    * @param WebGLCache cache
   118    *        The state storage for the WebGL context owning this program.
   119    * @param WebGLProxy proxy
   120    *        The proxy methods for the WebGL context owning this program.
   121    */
   122   initialize: function(conn, [program, shaders, cache, proxy]) {
   123     protocol.Actor.prototype.initialize.call(this, conn);
   124     this._shaderActorsCache = { vertex: null, fragment: null };
   125     this.program = program;
   126     this.shaders = shaders;
   127     this.linkedCache = cache;
   128     this.linkedProxy = proxy;
   129   },
   131   get ownerWindow() this.linkedCache.ownerWindow,
   132   get ownerContext() this.linkedCache.ownerContext,
   134   /**
   135    * Gets the vertex shader linked to this program. This method guarantees
   136    * a single actor instance per shader.
   137    */
   138   getVertexShader: method(function() {
   139     return this._getShaderActor("vertex");
   140   }, {
   141     response: { shader: RetVal("gl-shader") }
   142   }),
   144   /**
   145    * Gets the fragment shader linked to this program. This method guarantees
   146    * a single actor instance per shader.
   147    */
   148   getFragmentShader: method(function() {
   149     return this._getShaderActor("fragment");
   150   }, {
   151     response: { shader: RetVal("gl-shader") }
   152   }),
   154   /**
   155    * Highlights any geometry rendered using this program.
   156    */
   157   highlight: method(function(tint) {
   158     this.linkedProxy.highlightTint = tint;
   159     this.linkedCache.setProgramTrait(this.program, PROGRAM_HIGHLIGHT_TRAIT);
   160   }, {
   161     request: { tint: Arg(0, "array:number") },
   162     oneway: true
   163   }),
   165   /**
   166    * Allows geometry to be rendered normally using this program.
   167    */
   168   unhighlight: method(function() {
   169     this.linkedCache.unsetProgramTrait(this.program, PROGRAM_HIGHLIGHT_TRAIT);
   170   }, {
   171     oneway: true
   172   }),
   174   /**
   175    * Prevents any geometry from being rendered using this program.
   176    */
   177   blackbox: method(function() {
   178     this.linkedCache.setProgramTrait(this.program, PROGRAM_BLACKBOX_TRAIT);
   179   }, {
   180     oneway: true
   181   }),
   183   /**
   184    * Allows geometry to be rendered using this program.
   185    */
   186   unblackbox: method(function() {
   187     this.linkedCache.unsetProgramTrait(this.program, PROGRAM_BLACKBOX_TRAIT);
   188   }, {
   189     oneway: true
   190   }),
   192   /**
   193    * Returns a cached ShaderActor instance based on the required shader type.
   194    *
   195    * @param string type
   196    *        Either "vertex" or "fragment".
   197    * @return ShaderActor
   198    *         The respective shader actor instance.
   199    */
   200   _getShaderActor: function(type) {
   201     if (this._shaderActorsCache[type]) {
   202       return this._shaderActorsCache[type];
   203     }
   204     let proxy = this.linkedProxy;
   205     let shader = proxy.getShaderOfType(this.shaders, type);
   206     let shaderActor = new ShaderActor(this.conn, this.program, shader, proxy);
   207     return this._shaderActorsCache[type] = shaderActor;
   208   }
   209 });
   211 /**
   212  * The corresponding Front object for the ProgramActor.
   213  */
   214 let ProgramFront = protocol.FrontClass(ProgramActor, {
   215   initialize: function(client, form) {
   216     protocol.Front.prototype.initialize.call(this, client, form);
   217   }
   218 });
   220 /**
   221  * The WebGL Actor handles simple interaction with a WebGL context via a few
   222  * high-level methods. After instantiating this actor, you'll need to set it
   223  * up by calling setup().
   224  */
   225 let WebGLActor = exports.WebGLActor = protocol.ActorClass({
   226   typeName: "webgl",
   227   initialize: function(conn, tabActor) {
   228     protocol.Actor.prototype.initialize.call(this, conn);
   229     this.tabActor = tabActor;
   230     this._onGlobalCreated = this._onGlobalCreated.bind(this);
   231     this._onGlobalDestroyed = this._onGlobalDestroyed.bind(this);
   232     this._onProgramLinked = this._onProgramLinked.bind(this);
   233   },
   234   destroy: function(conn) {
   235     protocol.Actor.prototype.destroy.call(this, conn);
   236     this.finalize();
   237   },
   239   /**
   240    * Starts waiting for the current tab actor's document global to be
   241    * created, in order to instrument the Canvas context and become
   242    * aware of everything the content does WebGL-wise.
   243    *
   244    * See ContentObserver and WebGLInstrumenter for more details.
   245    */
   246   setup: method(function({ reload }) {
   247     if (this._initialized) {
   248       return;
   249     }
   250     this._initialized = true;
   252     this._programActorsCache = [];
   253     this._contentObserver = new ContentObserver(this.tabActor);
   254     this._webglObserver = new WebGLObserver();
   256     on(this._contentObserver, "global-created", this._onGlobalCreated);
   257     on(this._contentObserver, "global-destroyed", this._onGlobalDestroyed);
   258     on(this._webglObserver, "program-linked", this._onProgramLinked);
   260     if (reload) {
   261       this.tabActor.window.location.reload();
   262     }
   263   }, {
   264     request: { reload: Option(0, "boolean") },
   265     oneway: true
   266   }),
   268   /**
   269    * Stops listening for document global changes and puts this actor
   270    * to hibernation. This method is called automatically just before the
   271    * actor is destroyed.
   272    */
   273   finalize: method(function() {
   274     if (!this._initialized) {
   275       return;
   276     }
   277     this._initialized = false;
   279     this._contentObserver.stopListening();
   280     off(this._contentObserver, "global-created", this._onGlobalCreated);
   281     off(this._contentObserver, "global-destroyed", this._onGlobalDestroyed);
   282     off(this._webglObserver, "program-linked", this._onProgramLinked);
   284     this._programActorsCache = null;
   285     this._contentObserver = null;
   286     this._webglObserver = null;
   287   }, {
   288    oneway: true
   289   }),
   291   /**
   292    * Gets an array of cached program actors for the current tab actor's window.
   293    * This is useful for dealing with bfcache, when no new programs are linked.
   294    */
   295   getPrograms: method(function() {
   296     let id = ContentObserver.GetInnerWindowID(this.tabActor.window);
   297     return this._programActorsCache.filter(e => e.ownerWindow == id);
   298   }, {
   299     response: { programs: RetVal("array:gl-program") }
   300   }),
   302   /**
   303    * Events emitted by this actor. The "program-linked" event is fired
   304    * every time a WebGL program was linked with its respective two shaders.
   305    */
   306   events: {
   307     "program-linked": {
   308       type: "programLinked",
   309       program: Arg(0, "gl-program")
   310     }
   311   },
   313   /**
   314    * Invoked whenever the current tab actor's document global is created.
   315    */
   316   _onGlobalCreated: function(window) {
   317     WebGLInstrumenter.handle(window, this._webglObserver);
   318   },
   320   /**
   321    * Invoked whenever the current tab actor's inner window is destroyed.
   322    */
   323   _onGlobalDestroyed: function(id) {
   324     removeFromArray(this._programActorsCache, e => e.ownerWindow == id);
   325     this._webglObserver.unregisterContextsForWindow(id);
   326   },
   328   /**
   329    * Invoked whenever an observed WebGL context links a program.
   330    */
   331   _onProgramLinked: function(...args) {
   332     let programActor = new ProgramActor(this.conn, args);
   333     this._programActorsCache.push(programActor);
   334     events.emit(this, "program-linked", programActor);
   335   }
   336 });
   338 /**
   339  * The corresponding Front object for the WebGLActor.
   340  */
   341 let WebGLFront = exports.WebGLFront = protocol.FrontClass(WebGLActor, {
   342   initialize: function(client, { webglActor }) {
   343     protocol.Front.prototype.initialize.call(this, client, { actor: webglActor });
   344     client.addActorPool(this);
   345     this.manage(this);
   346   }
   347 });
   349 /**
   350  * Instruments a HTMLCanvasElement with the appropriate inspection methods.
   351  */
   352 let WebGLInstrumenter = {
   353   /**
   354    * Overrides the getContext method in the HTMLCanvasElement prototype.
   355    *
   356    * @param nsIDOMWindow window
   357    *        The window to perform the instrumentation in.
   358    * @param WebGLObserver observer
   359    *        The observer watching function calls in the context.
   360    */
   361   handle: function(window, observer) {
   362     let self = this;
   364     let id = ContentObserver.GetInnerWindowID(window);
   365     let canvasElem = XPCNativeWrapper.unwrap(window.HTMLCanvasElement);
   366     let canvasPrototype = canvasElem.prototype;
   367     let originalGetContext = canvasPrototype.getContext;
   369     /**
   370      * Returns a drawing context on the canvas, or null if the context ID is
   371      * not supported. This override creates an observer for the targeted context
   372      * type and instruments specific functions in the targeted context instance.
   373      */
   374     canvasPrototype.getContext = function(name, options) {
   375       // Make sure a context was able to be created.
   376       let context = originalGetContext.call(this, name, options);
   377       if (!context) {
   378         return context;
   379       }
   380       // Make sure a WebGL (not a 2D) context will be instrumented.
   381       if (WEBGL_CONTEXT_NAMES.indexOf(name) == -1) {
   382         return context;
   383       }
   384       // Repeated calls to 'getContext' return the same instance, no need to
   385       // instrument everything again.
   386       if (observer.for(context)) {
   387         return context;
   388       }
   390       // Create a separate state storage for this context.
   391       observer.registerContextForWindow(id, context);
   393       // Link our observer to the new WebGL context methods.
   394       for (let { timing, callback, functions } of self._methods) {
   395         for (let func of functions) {
   396           self._instrument(observer, context, func, callback, timing);
   397         }
   398       }
   400       // Return the decorated context back to the content consumer, which
   401       // will continue using it normally.
   402       return context;
   403     };
   404   },
   406   /**
   407    * Overrides a specific method in a HTMLCanvasElement context.
   408    *
   409    * @param WebGLObserver observer
   410    *        The observer watching function calls in the context.
   411    * @param WebGLRenderingContext context
   412    *        The targeted WebGL context instance.
   413    * @param string funcName
   414    *        The function to override.
   415    * @param array callbackName [optional]
   416    *        The two callback function names in the observer, corresponding to
   417    *        the "before" and "after" invocation times. If unspecified, they will
   418    *        default to the name of the function to override.
   419    * @param number timing [optional]
   420    *        When to issue the callback in relation to the actual context
   421    *        function call. Availalble values are -1 for "before" (default)
   422    *        1 for "after" and 0 for "before and after".
   423    */
   424   _instrument: function(observer, context, funcName, callbackName = [], timing = -1) {
   425     let { cache, proxy } = observer.for(context);
   426     let originalFunc = context[funcName];
   427     let beforeFuncName = callbackName[0] || funcName;
   428     let afterFuncName = callbackName[1] || callbackName[0] || funcName;
   430     context[funcName] = function(...glArgs) {
   431       if (timing <= 0 && !observer.suppressHandlers) {
   432         let glBreak = observer[beforeFuncName](glArgs, cache, proxy);
   433         if (glBreak) return undefined;
   434       }
   436       let glResult = originalFunc.apply(this, glArgs);
   438       if (timing >= 0 && !observer.suppressHandlers) {
   439         let glBreak = observer[afterFuncName](glArgs, glResult, cache, proxy);
   440         if (glBreak) return undefined;
   441       }
   443       return glResult;
   444     };
   445   },
   447   /**
   448    * Override mappings for WebGL methods.
   449    */
   450   _methods: [{
   451     timing: 1, // after
   452     functions: [
   453       "linkProgram", "getAttribLocation", "getUniformLocation"
   454     ]
   455   }, {
   456     timing: -1, // before
   457     callback: [
   458       "toggleVertexAttribArray"
   459     ],
   460     functions: [
   461       "enableVertexAttribArray", "disableVertexAttribArray"
   462     ]
   463   }, {
   464     timing: -1, // before
   465     callback: [
   466       "attribute_"
   467     ],
   468     functions: [
   469       "vertexAttrib1f", "vertexAttrib2f", "vertexAttrib3f", "vertexAttrib4f",
   470       "vertexAttrib1fv", "vertexAttrib2fv", "vertexAttrib3fv", "vertexAttrib4fv",
   471       "vertexAttribPointer"
   472     ]
   473   }, {
   474     timing: -1, // before
   475     callback: [
   476       "uniform_"
   477     ],
   478     functions: [
   479       "uniform1i", "uniform2i", "uniform3i", "uniform4i",
   480       "uniform1f", "uniform2f", "uniform3f", "uniform4f",
   481       "uniform1iv", "uniform2iv", "uniform3iv", "uniform4iv",
   482       "uniform1fv", "uniform2fv", "uniform3fv", "uniform4fv",
   483       "uniformMatrix2fv", "uniformMatrix3fv", "uniformMatrix4fv"
   484     ]
   485   }, {
   486     timing: -1, // before
   487     functions: [
   488       "useProgram", "enable", "disable", "blendColor",
   489       "blendEquation", "blendEquationSeparate",
   490       "blendFunc", "blendFuncSeparate"
   491     ]
   492   }, {
   493     timing: 0, // before and after
   494     callback: [
   495       "beforeDraw_", "afterDraw_"
   496     ],
   497     functions: [
   498       "drawArrays", "drawElements"
   499     ]
   500   }]
   501   // TODO: It'd be a good idea to handle other functions as well:
   502   //   - getActiveUniform
   503   //   - getUniform
   504   //   - getActiveAttrib
   505   //   - getVertexAttrib
   506 };
   508 /**
   509  * An observer that captures a WebGL context's method calls.
   510  */
   511 function WebGLObserver() {
   512   this._contexts = new Map();
   513 }
   515 WebGLObserver.prototype = {
   516   _contexts: null,
   518   /**
   519    * Creates a WebGLCache and a WebGLProxy for the specified window and context.
   520    *
   521    * @param number id
   522    *        The id of the window containing the WebGL context.
   523    * @param WebGLRenderingContext context
   524    *        The WebGL context used in the cache and proxy instances.
   525    */
   526   registerContextForWindow: function(id, context) {
   527     let cache = new WebGLCache(id, context);
   528     let proxy = new WebGLProxy(id, context, cache, this);
   529     cache.refreshState(proxy);
   531     this._contexts.set(context, {
   532       ownerWindow: id,
   533       cache: cache,
   534       proxy: proxy
   535     });
   536   },
   538   /**
   539    * Removes all WebGLCache and WebGLProxy instances for a particular window.
   540    *
   541    * @param number id
   542    *        The id of the window containing the WebGL context.
   543    */
   544   unregisterContextsForWindow: function(id) {
   545     removeFromMap(this._contexts, e => e.ownerWindow == id);
   546   },
   548   /**
   549    * Gets the WebGLCache and WebGLProxy instances for a particular context.
   550    *
   551    * @param WebGLRenderingContext context
   552    *        The WebGL context used in the cache and proxy instances.
   553    * @return object
   554    *         An object containing the corresponding { cache, proxy } instances.
   555    */
   556   for: function(context) {
   557     return this._contexts.get(context);
   558   },
   560   /**
   561    * Set this flag to true to stop observing any context function calls.
   562    */
   563   suppressHandlers: false,
   565   /**
   566    * Called immediately *after* 'linkProgram' is requested in the context.
   567    *
   568    * @param array glArgs
   569    *        Overridable arguments with which the function is called.
   570    * @param void glResult
   571    *        The returned value of the original function call.
   572    * @param WebGLCache cache
   573    *        The state storage for the WebGL context initiating this call.
   574    * @param WebGLProxy proxy
   575    *        The proxy methods for the WebGL context initiating this call.
   576    */
   577   linkProgram: function(glArgs, glResult, cache, proxy) {
   578     let program = glArgs[0];
   579     let shaders = proxy.getAttachedShaders(program);
   580     cache.addProgram(program, PROGRAM_DEFAULT_TRAITS);
   581     emit(this, "program-linked", program, shaders, cache, proxy);
   582   },
   584   /**
   585    * Called immediately *after* 'getAttribLocation' is requested in the context.
   586    *
   587    * @param array glArgs
   588    *        Overridable arguments with which the function is called.
   589    * @param GLint glResult
   590    *        The returned value of the original function call.
   591    * @param WebGLCache cache
   592    *        The state storage for the WebGL context initiating this call.
   593    */
   594   getAttribLocation: function(glArgs, glResult, cache) {
   595     // Make sure the attribute's value is legal before caching.
   596     if (glResult < 0) {
   597       return;
   598     }
   599     let [program, name] = glArgs;
   600     cache.addAttribute(program, name, glResult);
   601   },
   603   /**
   604    * Called immediately *after* 'getUniformLocation' is requested in the context.
   605    *
   606    * @param array glArgs
   607    *        Overridable arguments with which the function is called.
   608    * @param WebGLUniformLocation glResult
   609    *        The returned value of the original function call.
   610    * @param WebGLCache cache
   611    *        The state storage for the WebGL context initiating this call.
   612    */
   613   getUniformLocation: function(glArgs, glResult, cache) {
   614     // Make sure the uniform's value is legal before caching.
   615     if (!glResult) {
   616       return;
   617     }
   618     let [program, name] = glArgs;
   619     cache.addUniform(program, name, glResult);
   620   },
   622   /**
   623    * Called immediately *before* 'enableVertexAttribArray' or
   624    * 'disableVertexAttribArray'is requested in the context.
   625    *
   626    * @param array glArgs
   627    *        Overridable arguments with which the function is called.
   628    * @param WebGLCache cache
   629    *        The state storage for the WebGL context initiating this call.
   630    */
   631   toggleVertexAttribArray: function(glArgs, cache) {
   632     glArgs[0] = cache.getCurrentAttributeLocation(glArgs[0]);
   633     return glArgs[0] < 0; // Return true to break original function call.
   634   },
   636   /**
   637    * Called immediately *before* 'attribute_' is requested in the context.
   638    *
   639    * @param array glArgs
   640    *        Overridable arguments with which the function is called.
   641    * @param WebGLCache cache
   642    *        The state storage for the WebGL context initiating this call.
   643    */
   644   attribute_: function(glArgs, cache) {
   645     glArgs[0] = cache.getCurrentAttributeLocation(glArgs[0]);
   646     return glArgs[0] < 0; // Return true to break original function call.
   647   },
   649   /**
   650    * Called immediately *before* 'uniform_' is requested in the context.
   651    *
   652    * @param array glArgs
   653    *        Overridable arguments with which the function is called.
   654    * @param WebGLCache cache
   655    *        The state storage for the WebGL context initiating this call.
   656    */
   657   uniform_: function(glArgs, cache) {
   658     glArgs[0] = cache.getCurrentUniformLocation(glArgs[0]);
   659     return !glArgs[0]; // Return true to break original function call.
   660   },
   662   /**
   663    * Called immediately *before* 'useProgram' is requested in the context.
   664    *
   665    * @param array glArgs
   666    *        Overridable arguments with which the function is called.
   667    * @param WebGLCache cache
   668    *        The state storage for the WebGL context initiating this call.
   669    */
   670   useProgram: function(glArgs, cache) {
   671     // Manually keeping a cache and not using gl.getParameter(CURRENT_PROGRAM)
   672     // because gl.get* functions are slow as potatoes.
   673     cache.currentProgram = glArgs[0];
   674   },
   676   /**
   677    * Called immediately *before* 'enable' is requested in the context.
   678    *
   679    * @param array glArgs
   680    *        Overridable arguments with which the function is called.
   681    * @param WebGLCache cache
   682    *        The state storage for the WebGL context initiating this call.
   683    */
   684   enable: function(glArgs, cache) {
   685     cache.currentState[glArgs[0]] = true;
   686   },
   688   /**
   689    * Called immediately *before* 'disable' is requested in the context.
   690    *
   691    * @param array glArgs
   692    *        Overridable arguments with which the function is called.
   693    * @param WebGLCache cache
   694    *        The state storage for the WebGL context initiating this call.
   695    */
   696   disable: function(glArgs, cache) {
   697     cache.currentState[glArgs[0]] = false;
   698   },
   700   /**
   701    * Called immediately *before* 'blendColor' is requested in the context.
   702    *
   703    * @param array glArgs
   704    *        Overridable arguments with which the function is called.
   705    * @param WebGLCache cache
   706    *        The state storage for the WebGL context initiating this call.
   707    */
   708   blendColor: function(glArgs, cache) {
   709     let blendColor = cache.currentState.blendColor;
   710     blendColor[0] = glArgs[0];
   711     blendColor[1] = glArgs[1];
   712     blendColor[2] = glArgs[2];
   713     blendColor[3] = glArgs[3];
   714   },
   716   /**
   717    * Called immediately *before* 'blendEquation' is requested in the context.
   718    *
   719    * @param array glArgs
   720    *        Overridable arguments with which the function is called.
   721    * @param WebGLCache cache
   722    *        The state storage for the WebGL context initiating this call.
   723    */
   724   blendEquation: function(glArgs, cache) {
   725     let state = cache.currentState;
   726     state.blendEquationRgb = state.blendEquationAlpha = glArgs[0];
   727   },
   729   /**
   730    * Called immediately *before* 'blendEquationSeparate' is requested in the context.
   731    *
   732    * @param array glArgs
   733    *        Overridable arguments with which the function is called.
   734    * @param WebGLCache cache
   735    *        The state storage for the WebGL context initiating this call.
   736    */
   737   blendEquationSeparate: function(glArgs, cache) {
   738     let state = cache.currentState;
   739     state.blendEquationRgb = glArgs[0];
   740     state.blendEquationAlpha = glArgs[1];
   741   },
   743   /**
   744    * Called immediately *before* 'blendFunc' is requested in the context.
   745    *
   746    * @param array glArgs
   747    *        Overridable arguments with which the function is called.
   748    * @param WebGLCache cache
   749    *        The state storage for the WebGL context initiating this call.
   750    */
   751   blendFunc: function(glArgs, cache) {
   752     let state = cache.currentState;
   753     state.blendSrcRgb = state.blendSrcAlpha = glArgs[0];
   754     state.blendDstRgb = state.blendDstAlpha = glArgs[1];
   755   },
   757   /**
   758    * Called immediately *before* 'blendFuncSeparate' is requested in the context.
   759    *
   760    * @param array glArgs
   761    *        Overridable arguments with which the function is called.
   762    * @param WebGLCache cache
   763    *        The state storage for the WebGL context initiating this call.
   764    */
   765   blendFuncSeparate: function(glArgs, cache) {
   766     let state = cache.currentState;
   767     state.blendSrcRgb = glArgs[0];
   768     state.blendDstRgb = glArgs[1];
   769     state.blendSrcAlpha = glArgs[2];
   770     state.blendDstAlpha = glArgs[3];
   771   },
   773   /**
   774    * Called immediately *before* 'drawArrays' or 'drawElements' is requested
   775    * in the context.
   776    *
   777    * @param array glArgs
   778    *        Overridable arguments with which the function is called.
   779    * @param WebGLCache cache
   780    *        The state storage for the WebGL context initiating this call.
   781    * @param WebGLProxy proxy
   782    *        The proxy methods for the WebGL context initiating this call.
   783    */
   784   beforeDraw_: function(glArgs, cache, proxy) {
   785     let traits = cache.currentProgramTraits;
   787     // Handle program blackboxing.
   788     if (traits & PROGRAM_BLACKBOX_TRAIT) {
   789       return true; // Return true to break original function call.
   790     }
   791     // Handle program highlighting.
   792     if (traits & PROGRAM_HIGHLIGHT_TRAIT) {
   793       proxy.enableHighlighting();
   794     }
   796     return false;
   797   },
   799   /**
   800    * Called immediately *after* 'drawArrays' or 'drawElements' is requested
   801    * in the context.
   802    *
   803    * @param array glArgs
   804    *        Overridable arguments with which the function is called.
   805    * @param void glResult
   806    *        The returned value of the original function call.
   807    * @param WebGLCache cache
   808    *        The state storage for the WebGL context initiating this call.
   809    * @param WebGLProxy proxy
   810    *        The proxy methods for the WebGL context initiating this call.
   811    */
   812   afterDraw_: function(glArgs, glResult, cache, proxy) {
   813     let traits = cache.currentProgramTraits;
   815     // Handle program highlighting.
   816     if (traits & PROGRAM_HIGHLIGHT_TRAIT) {
   817       proxy.disableHighlighting();
   818     }
   819   }
   820 };
   822 /**
   823  * A mechanism for storing a single WebGL context's state, programs, shaders,
   824  * attributes or uniforms.
   825  *
   826  * @param number id
   827  *        The id of the window containing the WebGL context.
   828  * @param WebGLRenderingContext context
   829  *        The WebGL context for which the state is stored.
   830  */
   831 function WebGLCache(id, context) {
   832   this._id = id;
   833   this._gl = context;
   834   this._programs = new Map();
   835   this.currentState = {};
   836 }
   838 WebGLCache.prototype = {
   839   _id: 0,
   840   _gl: null,
   841   _programs: null,
   842   _currentProgramInfo: null,
   843   _currentAttributesMap: null,
   844   _currentUniformsMap: null,
   846   get ownerWindow() this._id,
   847   get ownerContext() this._gl,
   849   /**
   850    * A collection of flags or properties representing the context's state.
   851    * Implemented as an object hash and not a Map instance because keys are
   852    * always either strings or numbers.
   853    */
   854   currentState: null,
   856   /**
   857    * Populates the current state with values retrieved from the context.
   858    *
   859    * @param WebGLProxy proxy
   860    *        The proxy methods for the WebGL context owning the state.
   861    */
   862   refreshState: function(proxy) {
   863     let gl = this._gl;
   864     let s = this.currentState;
   866     // Populate only with the necessary parameters. Not all default WebGL
   867     // state values are required.
   868     s[gl.BLEND] = proxy.isEnabled("BLEND");
   869     s.blendColor = proxy.getParameter("BLEND_COLOR");
   870     s.blendEquationRgb = proxy.getParameter("BLEND_EQUATION_RGB");
   871     s.blendEquationAlpha = proxy.getParameter("BLEND_EQUATION_ALPHA");
   872     s.blendSrcRgb = proxy.getParameter("BLEND_SRC_RGB");
   873     s.blendSrcAlpha = proxy.getParameter("BLEND_SRC_ALPHA");
   874     s.blendDstRgb = proxy.getParameter("BLEND_DST_RGB");
   875     s.blendDstAlpha = proxy.getParameter("BLEND_DST_ALPHA");
   876   },
   878   /**
   879    * Adds a program to the cache.
   880    *
   881    * @param WebGLProgram program
   882    *        The shader for which the traits are to be cached.
   883    * @param number traits
   884    *        A default properties mask set for the program.
   885    */
   886   addProgram: function(program, traits) {
   887     this._programs.set(program, {
   888       traits: traits,
   889       attributes: [], // keys are GLints (numbers)
   890       uniforms: new Map() // keys are WebGLUniformLocations (objects)
   891     });
   892   },
   894   /**
   895    * Adds a specific trait to a program. The effect of such properties is
   896    * determined by the consumer of this cache.
   897    *
   898    * @param WebGLProgram program
   899    *        The program to add the trait to.
   900    * @param number trait
   901    *        The property added to the program.
   902    */
   903   setProgramTrait: function(program, trait) {
   904     this._programs.get(program).traits |= trait;
   905   },
   907   /**
   908    * Removes a specific trait from a program.
   909    *
   910    * @param WebGLProgram program
   911    *        The program to remove the trait from.
   912    * @param number trait
   913    *        The property removed from the program.
   914    */
   915   unsetProgramTrait: function(program, trait) {
   916     this._programs.get(program).traits &= ~trait;
   917   },
   919   /**
   920    * Sets the currently used program in the context.
   921    * @param WebGLProgram program
   922    */
   923   set currentProgram(program) {
   924     let programInfo = this._programs.get(program);
   925     if (programInfo == null) {
   926       return;
   927     }
   928     this._currentProgramInfo = programInfo;
   929     this._currentAttributesMap = programInfo.attributes;
   930     this._currentUniformsMap = programInfo.uniforms;
   931   },
   933   /**
   934    * Gets the traits for the currently used program.
   935    * @return number
   936    */
   937   get currentProgramTraits() {
   938     return this._currentProgramInfo.traits;
   939   },
   941   /**
   942    * Adds an attribute to the cache.
   943    *
   944    * @param WebGLProgram program
   945    *        The program for which the attribute is bound.
   946    * @param string name
   947    *        The attribute name.
   948    * @param GLint value
   949    *        The attribute value.
   950    */
   951   addAttribute: function(program, name, value) {
   952     this._programs.get(program).attributes[value] = {
   953       name: name,
   954       value: value
   955     };
   956   },
   958   /**
   959    * Adds a uniform to the cache.
   960    *
   961    * @param WebGLProgram program
   962    *        The program for which the uniform is bound.
   963    * @param string name
   964    *        The uniform name.
   965    * @param WebGLUniformLocation value
   966    *        The uniform value.
   967    */
   968   addUniform: function(program, name, value) {
   969     this._programs.get(program).uniforms.set(new XPCNativeWrapper(value), {
   970       name: name,
   971       value: value
   972     });
   973   },
   975   /**
   976    * Updates the attribute locations for a specific program.
   977    * This is necessary, for example, when the shader is relinked and all the
   978    * attribute locations become obsolete.
   979    *
   980    * @param WebGLProgram program
   981    *        The program for which the attributes need updating.
   982    */
   983   updateAttributesForProgram: function(program) {
   984     let attributes = this._programs.get(program).attributes;
   985     for (let attribute of attributes) {
   986       attribute.value = this._gl.getAttribLocation(program, attribute.name);
   987     }
   988   },
   990   /**
   991    * Updates the uniform locations for a specific program.
   992    * This is necessary, for example, when the shader is relinked and all the
   993    * uniform locations become obsolete.
   994    *
   995    * @param WebGLProgram program
   996    *        The program for which the uniforms need updating.
   997    */
   998   updateUniformsForProgram: function(program) {
   999     let uniforms = this._programs.get(program).uniforms;
  1000     for (let [, uniform] of uniforms) {
  1001       uniform.value = this._gl.getUniformLocation(program, uniform.name);
  1003   },
  1005   /**
  1006    * Gets the actual attribute location in a specific program.
  1007    * When relinked, all the attribute locations become obsolete and are updated
  1008    * in the cache. This method returns the (current) real attribute location.
  1010    * @param GLint initialValue
  1011    *        The initial attribute value.
  1012    * @return GLint
  1013    *         The current attribute value, or the initial value if it's already
  1014    *         up to date with its corresponding program.
  1015    */
  1016   getCurrentAttributeLocation: function(initialValue) {
  1017     let attributes = this._currentAttributesMap;
  1018     let currentInfo = attributes ? attributes[initialValue] : null;
  1019     return currentInfo ? currentInfo.value : initialValue;
  1020   },
  1022   /**
  1023    * Gets the actual uniform location in a specific program.
  1024    * When relinked, all the uniform locations become obsolete and are updated
  1025    * in the cache. This method returns the (current) real uniform location.
  1027    * @param WebGLUniformLocation initialValue
  1028    *        The initial uniform value.
  1029    * @return WebGLUniformLocation
  1030    *         The current uniform value, or the initial value if it's already
  1031    *         up to date with its corresponding program.
  1032    */
  1033   getCurrentUniformLocation: function(initialValue) {
  1034     let uniforms = this._currentUniformsMap;
  1035     let currentInfo = uniforms ? uniforms.get(initialValue) : null;
  1036     return currentInfo ? currentInfo.value : initialValue;
  1038 };
  1040 /**
  1041  * A mechanism for injecting or qureying state into/from a single WebGL context.
  1043  * Any interaction with a WebGL context should go through this proxy.
  1044  * Otherwise, the corresponding observer would register the calls as coming
  1045  * from content, which is usually not desirable. Infinite call stacks are bad.
  1047  * @param number id
  1048  *        The id of the window containing the WebGL context.
  1049  * @param WebGLRenderingContext context
  1050  *        The WebGL context used for the proxy methods.
  1051  * @param WebGLCache cache
  1052  *        The state storage for the corresponding context.
  1053  * @param WebGLObserver observer
  1054  *        The observer watching function calls in the corresponding context.
  1055  */
  1056 function WebGLProxy(id, context, cache, observer) {
  1057   this._id = id;
  1058   this._gl = context;
  1059   this._cache = cache;
  1060   this._observer = observer;
  1062   let exports = [
  1063     "isEnabled",
  1064     "getParameter",
  1065     "getAttachedShaders",
  1066     "getShaderSource",
  1067     "getShaderOfType",
  1068     "compileShader",
  1069     "enableHighlighting",
  1070     "disableHighlighting"
  1071   ];
  1072   exports.forEach(e => this[e] = (...args) => this._call(e, args));
  1075 WebGLProxy.prototype = {
  1076   _id: 0,
  1077   _gl: null,
  1078   _cache: null,
  1079   _observer: null,
  1081   get ownerWindow() this._id,
  1082   get ownerContext() this._gl,
  1084   /**
  1085    * Test whether a WebGL capability is enabled.
  1087    * @param string name
  1088    *        The WebGL capability name, for example "BLEND".
  1089    * @return boolean
  1090    *         True if enabled, false otherwise.
  1091    */
  1092   _isEnabled: function(name) {
  1093     return this._gl.isEnabled(this._gl[name]);
  1094   },
  1096   /**
  1097    * Returns the value for the specified WebGL parameter name.
  1099    * @param string name
  1100    *        The WebGL parameter name, for example "BLEND_COLOR".
  1101    * @return any
  1102    *         The corresponding parameter's value.
  1103    */
  1104   _getParameter: function(name) {
  1105     return this._gl.getParameter(this._gl[name]);
  1106   },
  1108   /**
  1109    * Returns the renderbuffer property value for the specified WebGL parameter.
  1110    * If no renderbuffer binding is available, null is returned.
  1112    * @param string name
  1113    *        The WebGL parameter name, for example "BLEND_COLOR".
  1114    * @return any
  1115    *         The corresponding parameter's value.
  1116    */
  1117   _getRenderbufferParameter: function(name) {
  1118     if (!this._getParameter("RENDERBUFFER_BINDING")) {
  1119       return null;
  1121     let gl = this._gl;
  1122     return gl.getRenderbufferParameter(gl.RENDERBUFFER, gl[name]);
  1123   },
  1125   /**
  1126    * Returns the framebuffer property value for the specified WebGL parameter.
  1127    * If no framebuffer binding is available, null is returned.
  1129    * @param string type
  1130    *        The framebuffer object attachment point, for example "COLOR_ATTACHMENT0".
  1131    * @param string name
  1132    *        The WebGL parameter name, for example "FRAMEBUFFER_ATTACHMENT_OBJECT_NAME".
  1133    *        If unspecified, defaults to "FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE".
  1134    * @return any
  1135    *         The corresponding parameter's value.
  1136    */
  1137   _getFramebufferAttachmentParameter: function(type, name = "FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE") {
  1138     if (!this._getParameter("FRAMEBUFFER_BINDING")) {
  1139       return null;
  1141     let gl = this._gl;
  1142     return gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl[type], gl[name]);
  1143   },
  1145   /**
  1146    * Returns the shader objects attached to a program object.
  1148    * @param WebGLProgram program
  1149    *        The program for which to retrieve the attached shaders.
  1150    * @return array
  1151    *         The attached vertex and fragment shaders.
  1152    */
  1153   _getAttachedShaders: function(program) {
  1154     return this._gl.getAttachedShaders(program);
  1155   },
  1157   /**
  1158    * Returns the source code string from a shader object.
  1160    * @param WebGLShader shader
  1161    *        The shader for which to retrieve the source code.
  1162    * @return string
  1163    *         The shader's source code.
  1164    */
  1165   _getShaderSource: function(shader) {
  1166     return this._gl.getShaderSource(shader);
  1167   },
  1169   /**
  1170    * Finds a shader of the specified type in a list.
  1172    * @param WebGLShader[] shaders
  1173    *        The shaders for which to check the type.
  1174    * @param string type
  1175    *        Either "vertex" or "fragment".
  1176    * @return WebGLShader | null
  1177    *         The shader of the specified type, or null if nothing is found.
  1178    */
  1179   _getShaderOfType: function(shaders, type) {
  1180     let gl = this._gl;
  1181     let shaderTypeEnum = {
  1182       vertex: gl.VERTEX_SHADER,
  1183       fragment: gl.FRAGMENT_SHADER
  1184     }[type];
  1186     for (let shader of shaders) {
  1187       if (gl.getShaderParameter(shader, gl.SHADER_TYPE) == shaderTypeEnum) {
  1188         return shader;
  1191     return null;
  1192   },
  1194   /**
  1195    * Changes a shader's source code and relinks the respective program.
  1197    * @param WebGLProgram program
  1198    *        The program who's linked shader is to be modified.
  1199    * @param WebGLShader shader
  1200    *        The shader to be modified.
  1201    * @param string text
  1202    *        The new shader source code.
  1203    * @return object
  1204    *         An object containing the compilation and linking status.
  1205    */
  1206   _compileShader: function(program, shader, text) {
  1207     let gl = this._gl;
  1208     gl.shaderSource(shader, text);
  1209     gl.compileShader(shader);
  1210     gl.linkProgram(program);
  1212     let error = { compile: "", link: "" };
  1214     if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
  1215       error.compile = gl.getShaderInfoLog(shader);
  1217     if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
  1218       error.link = gl.getShaderInfoLog(shader);
  1221     this._cache.updateAttributesForProgram(program);
  1222     this._cache.updateUniformsForProgram(program);
  1224     return error;
  1225   },
  1227   /**
  1228    * Enables color blending based on the geometry highlight tint.
  1229    */
  1230   _enableHighlighting: function() {
  1231     let gl = this._gl;
  1233     // Avoid changing the blending params when "rendering to texture".
  1235     // Check drawing to a custom framebuffer bound to the default renderbuffer.
  1236     let hasFramebuffer = this._getParameter("FRAMEBUFFER_BINDING");
  1237     let hasRenderbuffer = this._getParameter("RENDERBUFFER_BINDING");
  1238     if (hasFramebuffer && !hasRenderbuffer) {
  1239       return;
  1242     // Check drawing to a depth or stencil component of the framebuffer.
  1243     let writesDepth = this._getFramebufferAttachmentParameter("DEPTH_ATTACHMENT");
  1244     let writesStencil = this._getFramebufferAttachmentParameter("STENCIL_ATTACHMENT");
  1245     if (writesDepth || writesStencil) {
  1246       return;
  1249     // Non-premultiplied alpha blending based on a predefined constant color.
  1250     // Simply using gl.colorMask won't work, because we want non-tinted colors
  1251     // to be drawn as black, not ignored.
  1252     gl.enable(gl.BLEND);
  1253     gl.blendColor.apply(gl, this.highlightTint);
  1254     gl.blendEquation(gl.FUNC_ADD);
  1255     gl.blendFunc(gl.CONSTANT_COLOR, gl.ONE_MINUS_SRC_ALPHA, gl.CONSTANT_COLOR, gl.ZERO);
  1256     this.wasHighlighting = true;
  1257   },
  1259   /**
  1260    * Disables color blending based on the geometry highlight tint, by
  1261    * reverting the corresponding params back to their original values.
  1262    */
  1263   _disableHighlighting: function() {
  1264     let gl = this._gl;
  1265     let s = this._cache.currentState;
  1267     gl[s[gl.BLEND] ? "enable" : "disable"](gl.BLEND);
  1268     gl.blendColor.apply(gl, s.blendColor);
  1269     gl.blendEquationSeparate(s.blendEquationRgb, s.blendEquationAlpha);
  1270     gl.blendFuncSeparate(s.blendSrcRgb, s.blendDstRgb, s.blendSrcAlpha, s.blendDstAlpha);
  1271   },
  1273   /**
  1274    * The color tint used for highlighting geometry.
  1275    * @see _enableHighlighting and _disableHighlighting.
  1276    */
  1277   highlightTint: [0, 0, 0, 0],
  1279   /**
  1280    * Executes a function in this object.
  1282    * This method makes sure that any handlers in the context observer are
  1283    * suppressed, hence stopping observing any context function calls.
  1285    * @param string funcName
  1286    *        The function to call.
  1287    * @param array args
  1288    *        An array of arguments.
  1289    * @return any
  1290    *         The called function result.
  1291    */
  1292   _call: function(funcName, args) {
  1293     let prevState = this._observer.suppressHandlers;
  1295     this._observer.suppressHandlers = true;
  1296     let result = this["_" + funcName].apply(this, args);
  1297     this._observer.suppressHandlers = prevState;
  1299     return result;
  1301 };
  1303 // Utility functions.
  1305 function removeFromMap(map, predicate) {
  1306   for (let [key, value] of map) {
  1307     if (predicate(value)) {
  1308       map.delete(key);
  1311 };
  1313 function removeFromArray(array, predicate) {
  1314   for (let value of array) {
  1315     if (predicate(value)) {
  1316       array.splice(array.indexOf(value), 1);

mercurial