toolkit/devtools/server/actors/canvas.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 {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
     9 const protocol = require("devtools/server/protocol");
    10 const {CallWatcherActor, CallWatcherFront} = require("devtools/server/actors/call-watcher");
    11 const DevToolsUtils = require("devtools/toolkit/DevToolsUtils.js");
    13 const {on, once, off, emit} = events;
    14 const {method, custom, Arg, Option, RetVal} = protocol;
    16 const CANVAS_CONTEXTS = [
    17   "CanvasRenderingContext2D",
    18   "WebGLRenderingContext"
    19 ];
    21 const ANIMATION_GENERATORS = [
    22   "requestAnimationFrame",
    23   "mozRequestAnimationFrame"
    24 ];
    26 const DRAW_CALLS = [
    27   // 2D canvas
    28   "fill",
    29   "stroke",
    30   "clearRect",
    31   "fillRect",
    32   "strokeRect",
    33   "fillText",
    34   "strokeText",
    35   "drawImage",
    37   // WebGL
    38   "clear",
    39   "drawArrays",
    40   "drawElements",
    41   "finish",
    42   "flush"
    43 ];
    45 const INTERESTING_CALLS = [
    46   // 2D canvas
    47   "save",
    48   "restore",
    50   // WebGL
    51   "useProgram"
    52 ];
    54 exports.register = function(handle) {
    55   handle.addTabActor(CanvasActor, "canvasActor");
    56 };
    58 exports.unregister = function(handle) {
    59   handle.removeTabActor(CanvasActor);
    60 };
    62 /**
    63  * Type representing an Uint32Array buffer, serialized fast(er).
    64  *
    65  * XXX: It would be nice if on local connections (only), we could just *give*
    66  * the buffer directly to the front, instead of going through all this
    67  * serialization redundancy.
    68  */
    69 protocol.types.addType("uint32-array", {
    70   write: (v) => "[" + Array.join(v, ",") + "]",
    71   read: (v) => new Uint32Array(JSON.parse(v))
    72 });
    74 /**
    75  * Type describing a thumbnail or screenshot in a recorded animation frame.
    76  */
    77 protocol.types.addDictType("snapshot-image", {
    78   index: "number",
    79   width: "number",
    80   height: "number",
    81   flipped: "boolean",
    82   pixels: "uint32-array"
    83 });
    85 /**
    86  * Type describing an overview of a recorded animation frame.
    87  */
    88 protocol.types.addDictType("snapshot-overview", {
    89   calls: "array:function-call",
    90   thumbnails: "array:snapshot-image",
    91   screenshot: "snapshot-image"
    92 });
    94 /**
    95  * This actor represents a recorded animation frame snapshot, along with
    96  * all the corresponding canvas' context methods invoked in that frame,
    97  * thumbnails for each draw call and a screenshot of the end result.
    98  */
    99 let FrameSnapshotActor = protocol.ActorClass({
   100   typeName: "frame-snapshot",
   102   /**
   103    * Creates the frame snapshot call actor.
   104    *
   105    * @param DebuggerServerConnection conn
   106    *        The server connection.
   107    * @param HTMLCanvasElement canvas
   108    *        A reference to the content canvas.
   109    * @param array calls
   110    *        An array of "function-call" actor instances.
   111    * @param object screenshot
   112    *        A single "snapshot-image" type instance.
   113    */
   114   initialize: function(conn, { canvas, calls, screenshot }) {
   115     protocol.Actor.prototype.initialize.call(this, conn);
   116     this._contentCanvas = canvas;
   117     this._functionCalls = calls;
   118     this._lastDrawCallScreenshot = screenshot;
   119   },
   121   /**
   122    * Gets as much data about this snapshot without computing anything costly.
   123    */
   124   getOverview: method(function() {
   125     return {
   126       calls: this._functionCalls,
   127       thumbnails: this._functionCalls.map(e => e._thumbnail).filter(e => !!e),
   128       screenshot: this._lastDrawCallScreenshot
   129     };
   130   }, {
   131     response: { overview: RetVal("snapshot-overview") }
   132   }),
   134   /**
   135    * Gets a screenshot of the canvas's contents after the specified
   136    * function was called.
   137    */
   138   generateScreenshotFor: method(function(functionCall) {
   139     let caller = functionCall.details.caller;
   140     let global = functionCall.meta.global;
   142     let canvas = this._contentCanvas;
   143     let calls = this._functionCalls;
   144     let index = calls.indexOf(functionCall);
   146     // To get a screenshot, replay all the steps necessary to render the frame,
   147     // by invoking the context calls up to and including the specified one.
   148     // This will be done in a custom framebuffer in case of a WebGL context.
   149     let { replayContext, lastDrawCallIndex } = ContextUtils.replayAnimationFrame({
   150       contextType: global,
   151       canvas: canvas,
   152       calls: calls,
   153       first: 0,
   154       last: index
   155     });
   157     // To keep things fast, generate an image that's relatively small.
   158     let dimensions = Math.min(CanvasFront.SCREENSHOT_HEIGHT_MAX, canvas.height);
   159     let screenshot;
   161     // Depending on the canvas' context, generating a screenshot is done
   162     // in different ways. In case of the WebGL context, we also need to reset
   163     // the framebuffer binding to the default value.
   164     if (global == CallWatcherFront.CANVAS_WEBGL_CONTEXT) {
   165       screenshot = ContextUtils.getPixelsForWebGL(replayContext);
   166       replayContext.bindFramebuffer(replayContext.FRAMEBUFFER, null);
   167       screenshot.flipped = true;
   168     }
   169     // In case of 2D contexts, no additional special treatment is necessary.
   170     else if (global == CallWatcherFront.CANVAS_2D_CONTEXT) {
   171       screenshot = ContextUtils.getPixelsFor2D(replayContext);
   172       screenshot.flipped = false;
   173     }
   175     screenshot.index = lastDrawCallIndex;
   176     return screenshot;
   177   }, {
   178     request: { call: Arg(0, "function-call") },
   179     response: { screenshot: RetVal("snapshot-image") }
   180   })
   181 });
   183 /**
   184  * The corresponding Front object for the FrameSnapshotActor.
   185  */
   186 let FrameSnapshotFront = protocol.FrontClass(FrameSnapshotActor, {
   187   initialize: function(client, form) {
   188     protocol.Front.prototype.initialize.call(this, client, form);
   189     this._lastDrawCallScreenshot = null;
   190     this._cachedScreenshots = new WeakMap();
   191   },
   193   /**
   194    * This implementation caches the last draw call screenshot to optimize
   195    * frontend requests to `generateScreenshotFor`.
   196    */
   197   getOverview: custom(function() {
   198     return this._getOverview().then(data => {
   199       this._lastDrawCallScreenshot = data.screenshot;
   200       return data;
   201     });
   202   }, {
   203     impl: "_getOverview"
   204   }),
   206   /**
   207    * This implementation saves a roundtrip to the backend if the screenshot
   208    * was already generated and retrieved once.
   209    */
   210   generateScreenshotFor: custom(function(functionCall) {
   211     if (CanvasFront.ANIMATION_GENERATORS.has(functionCall.name)) {
   212       return promise.resolve(this._lastDrawCallScreenshot);
   213     }
   214     let cachedScreenshot = this._cachedScreenshots.get(functionCall);
   215     if (cachedScreenshot) {
   216       return cachedScreenshot;
   217     }
   218     let screenshot = this._generateScreenshotFor(functionCall);
   219     this._cachedScreenshots.set(functionCall, screenshot);
   220     return screenshot;
   221   }, {
   222     impl: "_generateScreenshotFor"
   223   })
   224 });
   226 /**
   227  * This Canvas Actor handles simple instrumentation of all the methods
   228  * of a 2D or WebGL context, to provide information regarding all the calls
   229  * made when drawing frame inside an animation loop.
   230  */
   231 let CanvasActor = exports.CanvasActor = protocol.ActorClass({
   232   typeName: "canvas",
   233   initialize: function(conn, tabActor) {
   234     protocol.Actor.prototype.initialize.call(this, conn);
   235     this.tabActor = tabActor;
   236     this._onContentFunctionCall = this._onContentFunctionCall.bind(this);
   237   },
   238   destroy: function(conn) {
   239     protocol.Actor.prototype.destroy.call(this, conn);
   240     this.finalize();
   241   },
   243   /**
   244    * Starts listening for function calls.
   245    */
   246   setup: method(function({ reload }) {
   247     if (this._initialized) {
   248       return;
   249     }
   250     this._initialized = true;
   252     this._callWatcher = new CallWatcherActor(this.conn, this.tabActor);
   253     this._callWatcher.onCall = this._onContentFunctionCall;
   254     this._callWatcher.setup({
   255       tracedGlobals: CANVAS_CONTEXTS,
   256       tracedFunctions: ANIMATION_GENERATORS,
   257       performReload: reload
   258     });
   259   }, {
   260     request: { reload: Option(0, "boolean") },
   261     oneway: true
   262   }),
   264   /**
   265    * Stops listening for function calls.
   266    */
   267   finalize: method(function() {
   268     if (!this._initialized) {
   269       return;
   270     }
   271     this._initialized = false;
   273     this._callWatcher.finalize();
   274     this._callWatcher = null;
   275   }, {
   276     oneway: true
   277   }),
   279   /**
   280    * Returns whether this actor has been set up.
   281    */
   282   isInitialized: method(function() {
   283     return !!this._initialized;
   284   }, {
   285     response: { initialized: RetVal("boolean") }
   286   }),
   288   /**
   289    * Records a snapshot of all the calls made during the next animation frame.
   290    * The animation should be implemented via the de-facto requestAnimationFrame
   291    * utility, not inside a `setInterval` or recursive `setTimeout`.
   292    *
   293    * XXX: Currently only supporting requestAnimationFrame. When this isn't used,
   294    * it'd be a good idea to display a huge red flashing banner telling people to
   295    * STOP USING `setInterval` OR `setTimeout` FOR ANIMATION. Bug 978948.
   296    */
   297   recordAnimationFrame: method(function() {
   298     if (this._callWatcher.isRecording()) {
   299       return this._currentAnimationFrameSnapshot.promise;
   300     }
   302     this._callWatcher.eraseRecording();
   303     this._callWatcher.resumeRecording();
   305     let deferred = this._currentAnimationFrameSnapshot = promise.defer();
   306     return deferred.promise;
   307   }, {
   308     response: { snapshot: RetVal("frame-snapshot") }
   309   }),
   311   /**
   312    * Invoked whenever an instrumented function is called, be it on a
   313    * 2d or WebGL context, or an animation generator like requestAnimationFrame.
   314    */
   315   _onContentFunctionCall: function(functionCall) {
   316     let { window, name, args } = functionCall.details;
   318     // The function call arguments are required to replay animation frames,
   319     // in order to generate screenshots. However, simply storing references to
   320     // every kind of object is a bad idea, since their properties may change.
   321     // Consider transformation matrices for example, which are typically
   322     // Float32Arrays whose values can easily change across context calls.
   323     // They need to be cloned.
   324     inplaceShallowCloneArrays(args, window);
   326     if (CanvasFront.ANIMATION_GENERATORS.has(name)) {
   327       this._handleAnimationFrame(functionCall);
   328       return;
   329     }
   330     if (CanvasFront.DRAW_CALLS.has(name) && this._animationStarted) {
   331       this._handleDrawCall(functionCall);
   332       return;
   333     }
   334   },
   336   /**
   337    * Handle animations generated using requestAnimationFrame.
   338    */
   339   _handleAnimationFrame: function(functionCall) {
   340     if (!this._animationStarted) {
   341       this._handleAnimationFrameBegin();
   342     } else {
   343       this._handleAnimationFrameEnd(functionCall);
   344     }
   345   },
   347   /**
   348    * Called whenever an animation frame rendering begins.
   349    */
   350   _handleAnimationFrameBegin: function() {
   351     this._callWatcher.eraseRecording();
   352     this._animationStarted = true;
   353   },
   355   /**
   356    * Called whenever an animation frame rendering ends.
   357    */
   358   _handleAnimationFrameEnd: function() {
   359     // Get a hold of all the function calls made during this animation frame.
   360     // Since only one snapshot can be recorded at a time, erase all the
   361     // previously recorded calls.
   362     let functionCalls = this._callWatcher.pauseRecording();
   363     this._callWatcher.eraseRecording();
   365     // Since the animation frame finished, get a hold of the (already retrieved)
   366     // canvas pixels to conveniently create a screenshot of the final rendering.
   367     let index = this._lastDrawCallIndex;
   368     let width = this._lastContentCanvasWidth;
   369     let height = this._lastContentCanvasHeight;
   370     let flipped = this._lastThumbnailFlipped;
   371     let pixels = ContextUtils.getPixelStorage()["32bit"];
   372     let lastDrawCallScreenshot = {
   373       index: index,
   374       width: width,
   375       height: height,
   376       flipped: flipped,
   377       pixels: pixels.subarray(0, width * height)
   378     };
   380     // Wrap the function calls and screenshot in a FrameSnapshotActor instance,
   381     // which will resolve the promise returned by `recordAnimationFrame`.
   382     let frameSnapshot = new FrameSnapshotActor(this.conn, {
   383       canvas: this._lastDrawCallCanvas,
   384       calls: functionCalls,
   385       screenshot: lastDrawCallScreenshot
   386     });
   388     this._currentAnimationFrameSnapshot.resolve(frameSnapshot);
   389     this._currentAnimationFrameSnapshot = null;
   390     this._animationStarted = false;
   391   },
   393   /**
   394    * Invoked whenever a draw call is detected in the animation frame which is
   395    * currently being recorded.
   396    */
   397   _handleDrawCall: function(functionCall) {
   398     let functionCalls = this._callWatcher.pauseRecording();
   399     let caller = functionCall.details.caller;
   400     let global = functionCall.meta.global;
   402     let contentCanvas = this._lastDrawCallCanvas = caller.canvas;
   403     let index = this._lastDrawCallIndex = functionCalls.indexOf(functionCall);
   404     let w = this._lastContentCanvasWidth = contentCanvas.width;
   405     let h = this._lastContentCanvasHeight = contentCanvas.height;
   407     // To keep things fast, generate images of small and fixed dimensions.
   408     let dimensions = CanvasFront.THUMBNAIL_HEIGHT;
   409     let thumbnail;
   411     // Create a thumbnail on every draw call on the canvas context, to augment
   412     // the respective function call actor with this additional data.
   413     if (global == CallWatcherFront.CANVAS_WEBGL_CONTEXT) {
   414       // Check if drawing to a custom framebuffer (when rendering to texture).
   415       // Don't create a thumbnail in this particular case.
   416       let framebufferBinding = caller.getParameter(caller.FRAMEBUFFER_BINDING);
   417       if (framebufferBinding == null) {
   418         thumbnail = ContextUtils.getPixelsForWebGL(caller, 0, 0, w, h, dimensions);
   419         thumbnail.flipped = this._lastThumbnailFlipped = true;
   420         thumbnail.index = index;
   421       }
   422     } else if (global == CallWatcherFront.CANVAS_2D_CONTEXT) {
   423       thumbnail = ContextUtils.getPixelsFor2D(caller, 0, 0, w, h, dimensions);
   424       thumbnail.flipped = this._lastThumbnailFlipped = false;
   425       thumbnail.index = index;
   426     }
   428     functionCall._thumbnail = thumbnail;
   429     this._callWatcher.resumeRecording();
   430   }
   431 });
   433 /**
   434  * A collection of methods for manipulating canvas contexts.
   435  */
   436 let ContextUtils = {
   437   /**
   438    * WebGL contexts are sensitive to how they're queried. Use this function
   439    * to make sure the right context is always retrieved, if available.
   440    *
   441    * @param HTMLCanvasElement canvas
   442    *        The canvas element for which to get a WebGL context.
   443    * @param WebGLRenderingContext gl
   444    *        The queried WebGL context, or null if unavailable.
   445    */
   446   getWebGLContext: function(canvas) {
   447     return canvas.getContext("webgl") ||
   448            canvas.getContext("experimental-webgl");
   449   },
   451   /**
   452    * Gets a hold of the rendered pixels in the most efficient way possible for
   453    * a canvas with a WebGL context.
   454    *
   455    * @param WebGLRenderingContext gl
   456    *        The WebGL context to get a screenshot from.
   457    * @param number srcX [optional]
   458    *        The first left pixel that is read from the framebuffer.
   459    * @param number srcY [optional]
   460    *        The first top pixel that is read from the framebuffer.
   461    * @param number srcWidth [optional]
   462    *        The number of pixels to read on the X axis.
   463    * @param number srcHeight [optional]
   464    *        The number of pixels to read on the Y axis.
   465    * @param number dstHeight [optional]
   466    *        The desired generated screenshot height.
   467    * @return object
   468    *         An objet containing the screenshot's width, height and pixel data.
   469    */
   470   getPixelsForWebGL: function(gl,
   471     srcX = 0, srcY = 0,
   472     srcWidth = gl.canvas.width,
   473     srcHeight = gl.canvas.height,
   474     dstHeight = srcHeight)
   475   {
   476     let contentPixels = ContextUtils.getPixelStorage(srcWidth, srcHeight);
   477     let { "8bit": charView, "32bit": intView } = contentPixels;
   478     gl.readPixels(srcX, srcY, srcWidth, srcHeight, gl.RGBA, gl.UNSIGNED_BYTE, charView);
   479     return this.resizePixels(intView, srcWidth, srcHeight, dstHeight);
   480   },
   482   /**
   483    * Gets a hold of the rendered pixels in the most efficient way possible for
   484    * a canvas with a 2D context.
   485    *
   486    * @param CanvasRenderingContext2D ctx
   487    *        The 2D context to get a screenshot from.
   488    * @param number srcX [optional]
   489    *        The first left pixel that is read from the canvas.
   490    * @param number srcY [optional]
   491    *        The first top pixel that is read from the canvas.
   492    * @param number srcWidth [optional]
   493    *        The number of pixels to read on the X axis.
   494    * @param number srcHeight [optional]
   495    *        The number of pixels to read on the Y axis.
   496    * @param number dstHeight [optional]
   497    *        The desired generated screenshot height.
   498    * @return object
   499    *         An objet containing the screenshot's width, height and pixel data.
   500    */
   501   getPixelsFor2D: function(ctx,
   502     srcX = 0, srcY = 0,
   503     srcWidth = ctx.canvas.width,
   504     srcHeight = ctx.canvas.height,
   505     dstHeight = srcHeight)
   506   {
   507     let { data } = ctx.getImageData(srcX, srcY, srcWidth, srcHeight);
   508     let { "32bit": intView } = ContextUtils.usePixelStorage(data.buffer);
   509     return this.resizePixels(intView, srcWidth, srcHeight, dstHeight);
   510   },
   512   /**
   513    * Resizes the provided pixels to fit inside a rectangle with the specified
   514    * height and the same aspect ratio as the source.
   515    *
   516    * @param Uint32Array srcPixels
   517    *        The source pixel data, assuming 32bit/pixel and 4 color components.
   518    * @param number srcWidth
   519    *        The source pixel data width.
   520    * @param number srcHeight
   521    *        The source pixel data height.
   522    * @param number dstHeight [optional]
   523    *        The desired resized pixel data height.
   524    * @return object
   525    *         An objet containing the resized pixels width, height and data.
   526    */
   527   resizePixels: function(srcPixels, srcWidth, srcHeight, dstHeight) {
   528     let screenshotRatio = dstHeight / srcHeight;
   529     let dstWidth = Math.floor(srcWidth * screenshotRatio);
   531     // Use a plain array instead of a Uint32Array to make serializing faster.
   532     let dstPixels = new Array(dstWidth * dstHeight);
   534     // If the resized image ends up being completely transparent, returning
   535     // an empty array will skip some redundant serialization cycles.
   536     let isTransparent = true;
   538     for (let dstX = 0; dstX < dstWidth; dstX++) {
   539       for (let dstY = 0; dstY < dstHeight; dstY++) {
   540         let srcX = Math.floor(dstX / screenshotRatio);
   541         let srcY = Math.floor(dstY / screenshotRatio);
   542         let cPos = srcX + srcWidth * srcY;
   543         let dPos = dstX + dstWidth * dstY;
   544         let color = dstPixels[dPos] = srcPixels[cPos];
   545         if (color) {
   546           isTransparent = false;
   547         }
   548       }
   549     }
   551     return {
   552       width: dstWidth,
   553       height: dstHeight,
   554       pixels: isTransparent ? [] : dstPixels
   555     };
   556   },
   558   /**
   559    * Invokes a series of canvas context calls, to "replay" an animation frame
   560    * and generate a screenshot.
   561    *
   562    * In case of a WebGL context, an offscreen framebuffer is created for
   563    * the respective canvas, and the rendering will be performed into it.
   564    * This is necessary because some state (like shaders, textures etc.) can't
   565    * be shared between two different WebGL contexts.
   566    * Hopefully, once SharedResources are a thing this won't be necessary:
   567    * http://www.khronos.org/webgl/wiki/SharedResouces
   568    *
   569    * In case of a 2D context, a new canvas is created, since there's no
   570    * intrinsic state that can't be easily duplicated.
   571    *
   572    * @param number contexType
   573    *        The type of context to use. See the CallWatcherFront scope types.
   574    * @param HTMLCanvasElement canvas
   575    *        The canvas element which is the source of all context calls.
   576    * @param array calls
   577    *        An array of function call actors.
   578    * @param number first
   579    *        The first function call to start from.
   580    * @param number last
   581    *        The last (inclusive) function call to end at.
   582    * @return object
   583    *         The context on which the specified calls were invoked and the
   584    *         last registered draw call's index.
   585    */
   586   replayAnimationFrame: function({ contextType, canvas, calls, first, last }) {
   587     let w = canvas.width;
   588     let h = canvas.height;
   590     let replayCanvas;
   591     let replayContext;
   592     let customFramebuffer;
   593     let lastDrawCallIndex = -1;
   595     // In case of WebGL contexts, rendering will be done offscreen, in a
   596     // custom framebuffer, but on the provided canvas context.
   597     if (contextType == CallWatcherFront.CANVAS_WEBGL_CONTEXT) {
   598       replayCanvas = canvas;
   599       replayContext = this.getWebGLContext(replayCanvas);
   600       customFramebuffer = this.createBoundFramebuffer(replayContext, w, h);
   601     }
   602     // In case of 2D contexts, draw everything on a separate canvas context.
   603     else if (contextType == CallWatcherFront.CANVAS_2D_CONTEXT) {
   604       let contentDocument = canvas.ownerDocument;
   605       replayCanvas = contentDocument.createElement("canvas");
   606       replayCanvas.width = w;
   607       replayCanvas.height = h;
   608       replayContext = replayCanvas.getContext("2d");
   609       replayContext.clearRect(0, 0, w, h);
   610     }
   612     // Replay all the context calls up to and including the specified one.
   613     for (let i = first; i <= last; i++) {
   614       let { type, name, args } = calls[i].details;
   616       // Prevent WebGL context calls that try to reset the framebuffer binding
   617       // to the default value, since we want to perform the rendering offscreen.
   618       if (name == "bindFramebuffer" && args[1] == null) {
   619         replayContext.bindFramebuffer(replayContext.FRAMEBUFFER, customFramebuffer);
   620       } else {
   621         if (type == CallWatcherFront.METHOD_FUNCTION) {
   622           replayContext[name].apply(replayContext, args);
   623         } else if (type == CallWatcherFront.SETTER_FUNCTION) {
   624           replayContext[name] = args;
   625         } else {
   626           // Ignore getter calls.
   627         }
   628         if (CanvasFront.DRAW_CALLS.has(name)) {
   629           lastDrawCallIndex = i;
   630         }
   631       }
   632     }
   634     return {
   635       replayContext: replayContext,
   636       lastDrawCallIndex: lastDrawCallIndex
   637     };
   638   },
   640   /**
   641    * Gets an object containing a buffer large enough to hold width * height
   642    * pixels, assuming 32bit/pixel and 4 color components.
   643    *
   644    * This method avoids allocating memory and tries to reuse a common buffer
   645    * as much as possible.
   646    *
   647    * @param number w
   648    *        The desired pixel array storage width.
   649    * @param number h
   650    *        The desired pixel array storage height.
   651    * @return object
   652    *         The requested pixel array buffer.
   653    */
   654   getPixelStorage: function(w = 0, h = 0) {
   655     let storage = this._currentPixelStorage;
   656     if (storage && storage["32bit"].length >= w * h) {
   657       return storage;
   658     }
   659     return this.usePixelStorage(new ArrayBuffer(w * h * 4));
   660   },
   662   /**
   663    * Creates and saves the array buffer views used by `getPixelStorage`.
   664    *
   665    * @param ArrayBuffer buffer
   666    *        The raw buffer used as storage for various array buffer views.
   667    */
   668   usePixelStorage: function(buffer) {
   669     let array8bit = new Uint8Array(buffer);
   670     let array32bit = new Uint32Array(buffer);
   671     return this._currentPixelStorage = {
   672       "8bit": array8bit,
   673       "32bit": array32bit
   674     };
   675   },
   677   /**
   678    * Creates a framebuffer of the specified dimensions for a WebGL context,
   679    * assuming a RGBA color buffer, a depth buffer and no stencil buffer.
   680    *
   681    * @param WebGLRenderingContext gl
   682    *        The WebGL context to create and bind a framebuffer for.
   683    * @param number width
   684    *        The desired width of the renderbuffers.
   685    * @param number height
   686    *        The desired height of the renderbuffers.
   687    * @return WebGLFramebuffer
   688    *         The generated framebuffer object.
   689    */
   690   createBoundFramebuffer: function(gl, width, height) {
   691     let framebuffer = gl.createFramebuffer();
   692     gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
   694     // Use a texture as the color rendebuffer attachment, since consumenrs of
   695     // this function will most likely want to read the rendered pixels back.
   696     let colorBuffer = gl.createTexture();
   697     gl.bindTexture(gl.TEXTURE_2D, colorBuffer);
   698     gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
   699     gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
   700     gl.generateMipmap(gl.TEXTURE_2D);
   701     gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
   703     let depthBuffer = gl.createRenderbuffer();
   704     gl.bindRenderbuffer(gl.RENDERBUFFER, depthBuffer);
   705     gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, width, height);
   707     gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, colorBuffer, 0);
   708     gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthBuffer);
   710     gl.bindTexture(gl.TEXTURE_2D, null);
   711     gl.bindRenderbuffer(gl.RENDERBUFFER, null);
   713     return framebuffer;
   714   }
   715 };
   717 /**
   718  * The corresponding Front object for the CanvasActor.
   719  */
   720 let CanvasFront = exports.CanvasFront = protocol.FrontClass(CanvasActor, {
   721   initialize: function(client, { canvasActor }) {
   722     protocol.Front.prototype.initialize.call(this, client, { actor: canvasActor });
   723     client.addActorPool(this);
   724     this.manage(this);
   725   }
   726 });
   728 /**
   729  * Constants.
   730  */
   731 CanvasFront.CANVAS_CONTEXTS = new Set(CANVAS_CONTEXTS);
   732 CanvasFront.ANIMATION_GENERATORS = new Set(ANIMATION_GENERATORS);
   733 CanvasFront.DRAW_CALLS = new Set(DRAW_CALLS);
   734 CanvasFront.INTERESTING_CALLS = new Set(INTERESTING_CALLS);
   735 CanvasFront.THUMBNAIL_HEIGHT = 50; // px
   736 CanvasFront.SCREENSHOT_HEIGHT_MAX = 256; // px
   737 CanvasFront.INVALID_SNAPSHOT_IMAGE = {
   738   index: -1,
   739   width: 0,
   740   height: 0,
   741   pixels: []
   742 };
   744 /**
   745  * Goes through all the arguments and creates a one-level shallow copy
   746  * of all arrays and array buffers.
   747  */
   748 function inplaceShallowCloneArrays(functionArguments, contentWindow) {
   749   let { Object, Array, ArrayBuffer } = contentWindow;
   751   functionArguments.forEach((arg, index, store) => {
   752     if (arg instanceof Array) {
   753       store[index] = arg.slice();
   754     }
   755     if (arg instanceof Object && arg.buffer instanceof ArrayBuffer) {
   756       store[index] = new arg.constructor(arg);
   757     }
   758   });
   759 }

mercurial