Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
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 }