Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / core / inspector / InjectedScriptCanvasModuleSource.js
1 /*
2  * Copyright (C) 2013 Google Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  *     * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *     * Redistributions in binary form must reproduce the above
11  * copyright notice, this list of conditions and the following disclaimer
12  * in the documentation and/or other materials provided with the
13  * distribution.
14  *     * Neither the name of Google Inc. nor the names of its
15  * contributors may be used to endorse or promote products derived from
16  * this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30
31 /**
32  * FIXME: ES5 strict mode check is suppressed due to multiple uses of arguments.callee.
33  * @fileoverview
34  * @suppress {es5Strict}
35  */
36
37 /**
38  * @param {InjectedScriptHostClass} InjectedScriptHost
39  * @param {Window} inspectedWindow
40  * @param {number} injectedScriptId
41  * @param {!InjectedScript} injectedScript
42  */
43 (function (InjectedScriptHost, inspectedWindow, injectedScriptId, injectedScript) {
44
45 var TypeUtils = {
46     /**
47      * http://www.khronos.org/registry/typedarray/specs/latest/#7
48      * @const
49      * @type {!Array.<function(new:ArrayBufferView, (!ArrayBuffer|!ArrayBufferView), number=, number=)>}
50      */
51     _typedArrayClasses: (function(typeNames) {
52         var result = [];
53         for (var i = 0, n = typeNames.length; i < n; ++i) {
54             if (inspectedWindow[typeNames[i]])
55                 result.push(inspectedWindow[typeNames[i]]);
56         }
57         return result;
58     })(["Int8Array", "Uint8Array", "Uint8ClampedArray", "Int16Array", "Uint16Array", "Int32Array", "Uint32Array", "Float32Array", "Float64Array"]),
59
60     /**
61      * @const
62      * @type {!Array.<string>}
63      */
64     _supportedPropertyPrefixes: ["webkit"],
65
66     /**
67      * @param {*} array
68      * @return {function(new:ArrayBufferView, (!ArrayBuffer|!ArrayBufferView), number=, number=)|null}
69      */
70     typedArrayClass: function(array)
71     {
72         var classes = TypeUtils._typedArrayClasses;
73         for (var i = 0, n = classes.length; i < n; ++i) {
74             if (array instanceof classes[i])
75                 return classes[i];
76         }
77         return null;
78     },
79
80     /**
81      * @param {*} obj
82      * @return {*}
83      */
84     clone: function(obj)
85     {
86         if (!obj)
87             return obj;
88
89         var type = typeof obj;
90         if (type !== "object" && type !== "function")
91             return obj;
92
93         // Handle Array and ArrayBuffer instances.
94         if (typeof obj.slice === "function") {
95             console.assert(obj instanceof Array || obj instanceof ArrayBuffer);
96             return obj.slice(0);
97         }
98
99         var typedArrayClass = TypeUtils.typedArrayClass(obj);
100         if (typedArrayClass)
101             return new typedArrayClass(/** @type {!ArrayBufferView} */ (obj));
102
103         if (obj instanceof HTMLImageElement) {
104             var img = /** @type {!HTMLImageElement} */ (obj);
105             // Special case for Images with Blob URIs: cloneNode will fail if the Blob URI has already been revoked.
106             // FIXME: Maybe this is a bug in WebKit core?
107             if (/^blob:/.test(img.src))
108                 return TypeUtils.cloneIntoCanvas(img);
109             return img.cloneNode(true);
110         }
111
112         if (obj instanceof HTMLCanvasElement)
113             return TypeUtils.cloneIntoCanvas(obj);
114
115         if (obj instanceof HTMLVideoElement)
116             return TypeUtils.cloneIntoCanvas(obj, obj.videoWidth, obj.videoHeight);
117
118         if (obj instanceof ImageData) {
119             var context = TypeUtils._dummyCanvas2dContext();
120             // FIXME: suppress type checks due to outdated builtin externs for createImageData.
121             var result = (/** @type {?} */ (context)).createImageData(obj);
122             for (var i = 0, n = obj.data.length; i < n; ++i)
123               result.data[i] = obj.data[i];
124             return result;
125         }
126
127         // Try to convert to a primitive value via valueOf().
128         if (typeof obj.valueOf === "function") {
129             var value = obj.valueOf();
130             var valueType = typeof value;
131             if (valueType !== "object" && valueType !== "function")
132                 return value;
133         }
134
135         console.error("ASSERT_NOT_REACHED: failed to clone object: ", obj);
136         return obj;
137     },
138
139     /**
140      * @param {!HTMLImageElement|!HTMLCanvasElement|!HTMLVideoElement} obj
141      * @param {number=} width
142      * @param {number=} height
143      * @return {!HTMLCanvasElement}
144      */
145     cloneIntoCanvas: function(obj, width, height)
146     {
147         var canvas = /** @type {!HTMLCanvasElement} */ (inspectedWindow.document.createElement("canvas"));
148         canvas.width = width || +obj.width;
149         canvas.height = height || +obj.height;
150         var context = /** @type {!CanvasRenderingContext2D} */ (Resource.wrappedObject(canvas.getContext("2d")));
151         context.drawImage(obj, 0, 0);
152         return canvas;
153     },
154
155     /**
156      * @param {?Object=} obj
157      * @return {?Object}
158      */
159     cloneObject: function(obj)
160     {
161         if (!obj)
162             return null;
163         var result = {};
164         for (var key in obj)
165             result[key] = obj[key];
166         return result;
167     },
168
169     /**
170      * @param {!Array.<string>} names
171      * @return {!Object.<string, boolean>}
172      */
173     createPrefixedPropertyNamesSet: function(names)
174     {
175         var result = Object.create(null);
176         for (var i = 0, name; name = names[i]; ++i) {
177             result[name] = true;
178             var suffix = name.substr(0, 1).toUpperCase() + name.substr(1);
179             for (var j = 0, prefix; prefix = TypeUtils._supportedPropertyPrefixes[j]; ++j)
180                 result[prefix + suffix] = true;
181         }
182         return result;
183     },
184
185     /**
186      * @return {number}
187      */
188     now: function()
189     {
190         try {
191             return inspectedWindow.performance.now();
192         } catch(e) {
193             try {
194                 return Date.now();
195             } catch(ex) {
196             }
197         }
198         return 0;
199     },
200
201     /**
202      * @param {string} property
203      * @param {!Object} obj
204      * @return {boolean}
205      */
206     isEnumPropertyName: function(property, obj)
207     {
208         return (/^[A-Z][A-Z0-9_]+$/.test(property) && typeof obj[property] === "number");
209     },
210
211     /**
212      * @return {!CanvasRenderingContext2D}
213      */
214     _dummyCanvas2dContext: function()
215     {
216         var context = TypeUtils._dummyCanvas2dContextInstance;
217         if (!context) {
218             var canvas = /** @type {!HTMLCanvasElement} */ (inspectedWindow.document.createElement("canvas"));
219             context = /** @type {!CanvasRenderingContext2D} */ (Resource.wrappedObject(canvas.getContext("2d")));
220             TypeUtils._dummyCanvas2dContextInstance = context;
221         }
222         return context;
223     }
224 }
225
226 /** @typedef {{name:string, valueIsEnum:(boolean|undefined), value:*, values:(!Array.<!TypeUtils.InternalResourceStateDescriptor>|undefined), isArray:(boolean|undefined)}} */
227 TypeUtils.InternalResourceStateDescriptor;
228
229 /**
230  * @interface
231  */
232 function StackTrace()
233 {
234 }
235
236 StackTrace.prototype = {
237     /**
238      * @param {number} index
239      * @return {{sourceURL: string, lineNumber: number, columnNumber: number}|undefined}
240      */
241     callFrame: function(index)
242     {
243     }
244 }
245
246 /**
247  * @param {number=} stackTraceLimit
248  * @param {?Function=} topMostFunctionToIgnore
249  * @return {?StackTrace}
250  */
251 StackTrace.create = function(stackTraceLimit, topMostFunctionToIgnore)
252 {
253     if (typeof Error.captureStackTrace === "function")
254         return new StackTraceV8(stackTraceLimit, topMostFunctionToIgnore || arguments.callee);
255     // FIXME: Support JSC, and maybe other browsers.
256     return null;
257 }
258
259 /**
260  * @constructor
261  * @implements {StackTrace}
262  * @param {number=} stackTraceLimit
263  * @param {?Function=} topMostFunctionToIgnore
264  * @see http://code.google.com/p/v8/wiki/JavaScriptStackTraceApi
265  */
266 function StackTraceV8(stackTraceLimit, topMostFunctionToIgnore)
267 {
268     var oldPrepareStackTrace = Error.prepareStackTrace;
269     var oldStackTraceLimit = Error.stackTraceLimit;
270     if (typeof stackTraceLimit === "number")
271         Error.stackTraceLimit = stackTraceLimit;
272
273     /**
274      * @param {!Object} error
275      * @param {!Array.<!CallSite>} structuredStackTrace
276      * @return {!Array.<{sourceURL: string, lineNumber: number, columnNumber: number}>}
277      */
278     Error.prepareStackTrace = function(error, structuredStackTrace)
279     {
280         return structuredStackTrace.map(function(callSite) {
281             return {
282                 sourceURL: callSite.getFileName(),
283                 lineNumber: callSite.getLineNumber(),
284                 columnNumber: callSite.getColumnNumber()
285             };
286         });
287     }
288
289     var holder = /** @type {{stack: !Array.<{sourceURL: string, lineNumber: number, columnNumber: number}>}} */ ({});
290     Error.captureStackTrace(holder, topMostFunctionToIgnore || arguments.callee);
291     this._stackTrace = holder.stack;
292
293     Error.stackTraceLimit = oldStackTraceLimit;
294     Error.prepareStackTrace = oldPrepareStackTrace;
295 }
296
297 StackTraceV8.prototype = {
298     /**
299      * @override
300      * @param {number} index
301      * @return {{sourceURL: string, lineNumber: number, columnNumber: number}|undefined}
302      */
303     callFrame: function(index)
304     {
305         return this._stackTrace[index];
306     }
307 }
308
309 /**
310  * @constructor
311  * @template T
312  */
313 function Cache()
314 {
315     this.reset();
316 }
317
318 Cache.prototype = {
319     /**
320      * @return {number}
321      */
322     size: function()
323     {
324         return this._size;
325     },
326
327     reset: function()
328     {
329         /** @type {!Object.<number, !T>} */
330         this._items = Object.create(null);
331         /** @type {number} */
332         this._size = 0;
333     },
334
335     /**
336      * @param {number} key
337      * @return {boolean}
338      */
339     has: function(key)
340     {
341         return key in this._items;
342     },
343
344     /**
345      * @param {number} key
346      * @return {T|undefined}
347      */
348     get: function(key)
349     {
350         return this._items[key];
351     },
352
353     /**
354      * @param {number} key
355      * @param {!T} item
356      */
357     put: function(key, item)
358     {
359         if (!this.has(key))
360             ++this._size;
361         this._items[key] = item;
362     }
363 }
364
365 /**
366  * @constructor
367  * @param {?Resource|!Object} thisObject
368  * @param {string} functionName
369  * @param {!Array|!Arguments} args
370  * @param {!Resource|*=} result
371  * @param {?StackTrace=} stackTrace
372  */
373 function Call(thisObject, functionName, args, result, stackTrace)
374 {
375     this._thisObject = thisObject;
376     this._functionName = functionName;
377     this._args = Array.prototype.slice.call(args, 0);
378     this._result = result;
379     this._stackTrace = stackTrace || null;
380
381     if (!this._functionName)
382         console.assert(this._args.length === 2 && typeof this._args[0] === "string");
383 }
384
385 Call.prototype = {
386     /**
387      * @return {?Resource}
388      */
389     resource: function()
390     {
391         return Resource.forObject(this._thisObject);
392     },
393
394     /**
395      * @return {string}
396      */
397     functionName: function()
398     {
399         return this._functionName;
400     },
401
402     /**
403      * @return {boolean}
404      */
405     isPropertySetter: function()
406     {
407         return !this._functionName;
408     },
409
410     /**
411      * @return {!Array}
412      */
413     args: function()
414     {
415         return this._args;
416     },
417
418     /**
419      * @return {*}
420      */
421     result: function()
422     {
423         return this._result;
424     },
425
426     /**
427      * @return {?StackTrace}
428      */
429     stackTrace: function()
430     {
431         return this._stackTrace;
432     },
433
434     /**
435      * @param {?StackTrace} stackTrace
436      */
437     setStackTrace: function(stackTrace)
438     {
439         this._stackTrace = stackTrace;
440     },
441
442     /**
443      * @param {*} result
444      */
445     setResult: function(result)
446     {
447         this._result = result;
448     },
449
450     /**
451      * @param {string} name
452      * @param {?Object} attachment
453      */
454     setAttachment: function(name, attachment)
455     {
456         if (attachment) {
457             /** @type {?Object.<string, !Object>|undefined} */
458             this._attachments = this._attachments || Object.create(null);
459             this._attachments[name] = attachment;
460         } else if (this._attachments) {
461             delete this._attachments[name];
462         }
463     },
464
465     /**
466      * @param {string} name
467      * @return {?Object}
468      */
469     attachment: function(name)
470     {
471         return this._attachments ? (this._attachments[name] || null) : null;
472     },
473
474     freeze: function()
475     {
476         if (this._freezed)
477             return;
478         this._freezed = true;
479         for (var i = 0, n = this._args.length; i < n; ++i) {
480             // FIXME: freeze the Resources also!
481             if (!Resource.forObject(this._args[i]))
482                 this._args[i] = TypeUtils.clone(this._args[i]);
483         }
484     },
485
486     /**
487      * @param {!Cache.<!ReplayableResource>} cache
488      * @return {!ReplayableCall}
489      */
490     toReplayable: function(cache)
491     {
492         this.freeze();
493         var thisObject = /** @type {!ReplayableResource} */ (Resource.toReplayable(this._thisObject, cache));
494         var result = Resource.toReplayable(this._result, cache);
495         var args = this._args.map(function(obj) {
496             return Resource.toReplayable(obj, cache);
497         });
498         var attachments = TypeUtils.cloneObject(this._attachments);
499         return new ReplayableCall(thisObject, this._functionName, args, result, this._stackTrace, attachments);
500     },
501
502     /**
503      * @param {!ReplayableCall} replayableCall
504      * @param {!Cache.<!Resource>} cache
505      * @return {!Call}
506      */
507     replay: function(replayableCall, cache)
508     {
509         var replayableResult = replayableCall.result();
510         if (replayableResult instanceof ReplayableResource && !cache.has(replayableResult.id())) {
511             var resource = replayableResult.replay(cache);
512             console.assert(resource.calls().length > 0, "Expected create* call for the Resource");
513             return resource.calls()[0];
514         }
515
516         var replayObject = ReplayableResource.replay(replayableCall.replayableResource(), cache);
517         var replayArgs = replayableCall.args().map(function(obj) {
518             return ReplayableResource.replay(obj, cache);
519         });
520         var replayResult = undefined;
521
522         if (replayableCall.isPropertySetter())
523             replayObject[replayArgs[0]] = replayArgs[1];
524         else {
525             var replayFunction = replayObject[replayableCall.functionName()];
526             console.assert(typeof replayFunction === "function", "Expected a function to replay");
527             replayResult = replayFunction.apply(replayObject, replayArgs);
528
529             if (replayableResult instanceof ReplayableResource) {
530                 var resource = replayableResult.replay(cache);
531                 if (!resource.wrappedObject())
532                     resource.setWrappedObject(replayResult);
533             }
534         }
535
536         this._thisObject = replayObject;
537         this._functionName = replayableCall.functionName();
538         this._args = replayArgs;
539         this._result = replayResult;
540         this._stackTrace = replayableCall.stackTrace();
541         this._freezed = true;
542         var attachments = replayableCall.attachments();
543         this._attachments = attachments ? TypeUtils.cloneObject(attachments) : null;
544         return this;
545     }
546 }
547
548 /**
549  * @constructor
550  * @param {!ReplayableResource} thisObject
551  * @param {string} functionName
552  * @param {!Array.<!ReplayableResource|*>} args
553  * @param {!ReplayableResource|*} result
554  * @param {?StackTrace} stackTrace
555  * @param {?Object.<string, !Object>} attachments
556  */
557 function ReplayableCall(thisObject, functionName, args, result, stackTrace, attachments)
558 {
559     this._thisObject = thisObject;
560     this._functionName = functionName;
561     this._args = args;
562     this._result = result;
563     this._stackTrace = stackTrace;
564     if (attachments)
565         this._attachments = attachments;
566 }
567
568 ReplayableCall.prototype = {
569     /**
570      * @return {!ReplayableResource}
571      */
572     replayableResource: function()
573     {
574         return this._thisObject;
575     },
576
577     /**
578      * @return {string}
579      */
580     functionName: function()
581     {
582         return this._functionName;
583     },
584
585     /**
586      * @return {boolean}
587      */
588     isPropertySetter: function()
589     {
590         return !this._functionName;
591     },
592
593     /**
594      * @return {string}
595      */
596     propertyName: function()
597     {
598         console.assert(this.isPropertySetter());
599         return /** @type {string} */ (this._args[0]);
600     },
601
602     /**
603      * @return {*}
604      */
605     propertyValue: function()
606     {
607         console.assert(this.isPropertySetter());
608         return this._args[1];
609     },
610
611     /**
612      * @return {!Array.<!ReplayableResource|*>}
613      */
614     args: function()
615     {
616         return this._args;
617     },
618
619     /**
620      * @return {!ReplayableResource|*}
621      */
622     result: function()
623     {
624         return this._result;
625     },
626
627     /**
628      * @return {?StackTrace}
629      */
630     stackTrace: function()
631     {
632         return this._stackTrace;
633     },
634
635     /**
636      * @return {?Object.<string, !Object>}
637      */
638     attachments: function()
639     {
640         return this._attachments;
641     },
642
643     /**
644      * @param {string} name
645      * @return {!Object}
646      */
647     attachment: function(name)
648     {
649         return this._attachments && this._attachments[name];
650     },
651
652     /**
653      * @param {!Cache.<!Resource>} cache
654      * @return {!Call}
655      */
656     replay: function(cache)
657     {
658         var call = /** @type {!Call} */ (Object.create(Call.prototype));
659         return call.replay(this, cache);
660     }
661 }
662
663 /**
664  * @constructor
665  * @param {!Object} wrappedObject
666  * @param {string} name
667  */
668 function Resource(wrappedObject, name)
669 {
670     /** @type {number} */
671     this._id = ++Resource._uniqueId;
672     /** @type {string} */
673     this._name = name || "Resource";
674     /** @type {number} */
675     this._kindId = Resource._uniqueKindIds[this._name] = (Resource._uniqueKindIds[this._name] || 0) + 1;
676     /** @type {?ResourceTrackingManager} */
677     this._resourceManager = null;
678     /** @type {!Array.<!Call>} */
679     this._calls = [];
680     /**
681      * This is to prevent GC from collecting associated resources.
682      * Otherwise, for example in WebGL, subsequent calls to gl.getParameter()
683      * may return a recently created instance that is no longer bound to a
684      * Resource object (thus, no history to replay it later).
685      *
686      * @type {!Object.<string, !Resource>}
687      */
688     this._boundResources = Object.create(null);
689     this.setWrappedObject(wrappedObject);
690 }
691
692 /**
693  * @type {number}
694  */
695 Resource._uniqueId = 0;
696
697 /**
698  * @type {!Object.<string, number>}
699  */
700 Resource._uniqueKindIds = {};
701
702 /**
703  * @param {*} obj
704  * @return {?Resource}
705  */
706 Resource.forObject = function(obj)
707 {
708     if (!obj)
709         return null;
710     if (obj instanceof Resource)
711         return obj;
712     if (typeof obj === "object")
713         return obj["__resourceObject"];
714     return null;
715 }
716
717 /**
718  * @param {!Resource|*} obj
719  * @return {*}
720  */
721 Resource.wrappedObject = function(obj)
722 {
723     var resource = Resource.forObject(obj);
724     return resource ? resource.wrappedObject() : obj;
725 }
726
727 /**
728  * @param {!Resource|*} obj
729  * @param {!Cache.<!ReplayableResource>} cache
730  * @return {!ReplayableResource|*}
731  */
732 Resource.toReplayable = function(obj, cache)
733 {
734     var resource = Resource.forObject(obj);
735     return resource ? resource.toReplayable(cache) : obj;
736 }
737
738 Resource.prototype = {
739     /**
740      * @return {number}
741      */
742     id: function()
743     {
744         return this._id;
745     },
746
747     /**
748      * @return {string}
749      */
750     name: function()
751     {
752         return this._name;
753     },
754
755     /**
756      * @return {string}
757      */
758     description: function()
759     {
760         return this._name + "@" + this._kindId;
761     },
762
763     /**
764      * @return {!Object}
765      */
766     wrappedObject: function()
767     {
768         return this._wrappedObject;
769     },
770
771     /**
772      * @param {!Object} value
773      */
774     setWrappedObject: function(value)
775     {
776         console.assert(value, "wrappedObject should not be NULL");
777         console.assert(!(value instanceof Resource), "Binding a Resource object to another Resource object?");
778         this._wrappedObject = value;
779         this._bindObjectToResource(value);
780     },
781
782     /**
783      * @return {!Object}
784      */
785     proxyObject: function()
786     {
787         if (!this._proxyObject)
788             this._proxyObject = this._wrapObject();
789         return this._proxyObject;
790     },
791
792     /**
793      * @return {?ResourceTrackingManager}
794      */
795     manager: function()
796     {
797         return this._resourceManager;
798     },
799
800     /**
801      * @param {!ResourceTrackingManager} value
802      */
803     setManager: function(value)
804     {
805         this._resourceManager = value;
806     },
807
808     /**
809      * @return {!Array.<!Call>}
810      */
811     calls: function()
812     {
813         return this._calls;
814     },
815
816     /**
817      * @return {?ContextResource}
818      */
819     contextResource: function()
820     {
821         if (this instanceof ContextResource)
822             return /** @type {!ContextResource} */ (this);
823
824         if (this._calculatingContextResource)
825             return null;
826
827         this._calculatingContextResource = true;
828         var result = null;
829         for (var i = 0, n = this._calls.length; i < n; ++i) {
830             result = this._calls[i].resource().contextResource();
831             if (result)
832                 break;
833         }
834         delete this._calculatingContextResource;
835         console.assert(result, "Failed to find context resource for " + this._name + "@" + this._kindId);
836         return result;
837     },
838
839     /**
840      * @return {!Array.<!TypeUtils.InternalResourceStateDescriptor>}
841      */
842     currentState: function()
843     {
844         var result = [];
845         var proxyObject = this.proxyObject();
846         if (!proxyObject)
847             return result;
848         var statePropertyNames = this._proxyStatePropertyNames || [];
849         for (var i = 0, n = statePropertyNames.length; i < n; ++i) {
850             var pname = statePropertyNames[i];
851             result.push({ name: pname, value: proxyObject[pname] });
852         }
853         result.push({ name: "context", value: this.contextResource() });
854         return result;
855     },
856
857     /**
858      * @return {string}
859      */
860     toDataURL: function()
861     {
862         return "";
863     },
864
865     /**
866      * @param {!Cache.<!ReplayableResource>} cache
867      * @return {!ReplayableResource}
868      */
869     toReplayable: function(cache)
870     {
871         var result = cache.get(this._id);
872         if (result)
873             return result;
874         var data = {
875             id: this._id,
876             name: this._name,
877             kindId: this._kindId
878         };
879         result = new ReplayableResource(this, data);
880         cache.put(this._id, result); // Put into the cache early to avoid loops.
881         data.calls = this._calls.map(function(call) {
882             return call.toReplayable(cache);
883         });
884         this._populateReplayableData(data, cache);
885         var contextResource = this.contextResource();
886         if (contextResource !== this)
887             data.contextResource = Resource.toReplayable(contextResource, cache);
888         return result;
889     },
890
891     /**
892      * @param {!Object} data
893      * @param {!Cache.<!ReplayableResource>} cache
894      */
895     _populateReplayableData: function(data, cache)
896     {
897         // Do nothing. Should be overridden by subclasses.
898     },
899
900     /**
901      * @param {!Object} data
902      * @param {!Cache.<!Resource>} cache
903      * @return {!Resource}
904      */
905     replay: function(data, cache)
906     {
907         var resource = cache.get(data.id);
908         if (resource)
909             return resource;
910         this._id = data.id;
911         this._name = data.name;
912         this._kindId = data.kindId;
913         this._resourceManager = null;
914         this._calls = [];
915         this._boundResources = Object.create(null);
916         this._wrappedObject = null;
917         cache.put(data.id, this); // Put into the cache early to avoid loops.
918         this._doReplayCalls(data, cache);
919         console.assert(this._wrappedObject, "Resource should be reconstructed!");
920         return this;
921     },
922
923     /**
924      * @param {!Object} data
925      * @param {!Cache.<!Resource>} cache
926      */
927     _doReplayCalls: function(data, cache)
928     {
929         for (var i = 0, n = data.calls.length; i < n; ++i)
930             this._calls.push(data.calls[i].replay(cache));
931     },
932
933     /**
934      * @param {!Call} call
935      */
936     pushCall: function(call)
937     {
938         call.freeze();
939         this._calls.push(call);
940     },
941
942     /**
943      * @param {!Call} call
944      */
945     onCallReplayed: function(call)
946     {
947         // Ignore by default.
948     },
949
950     /**
951      * @param {!Object} object
952      */
953     _bindObjectToResource: function(object)
954     {
955         Object.defineProperty(object, "__resourceObject", {
956             value: this,
957             writable: false,
958             enumerable: false,
959             configurable: true
960         });
961     },
962
963     /**
964      * @param {string} key
965      * @param {*} obj
966      */
967     _registerBoundResource: function(key, obj)
968     {
969         var resource = Resource.forObject(obj);
970         if (resource)
971             this._boundResources[key] = resource;
972         else
973             delete this._boundResources[key];
974     },
975
976     /**
977      * @return {?Object}
978      */
979     _wrapObject: function()
980     {
981         var wrappedObject = this.wrappedObject();
982         if (!wrappedObject)
983             return null;
984         var proxy = Object.create(wrappedObject.__proto__); // In order to emulate "instanceof".
985
986         var customWrapFunctions = this._customWrapFunctions();
987         /** @type {!Array.<string>} */
988         this._proxyStatePropertyNames = [];
989
990         /**
991          * @param {string} property
992          * @this {Resource}
993          */
994         function processProperty(property)
995         {
996             if (typeof wrappedObject[property] === "function") {
997                 var customWrapFunction = customWrapFunctions[property];
998                 if (customWrapFunction)
999                     proxy[property] = this._wrapCustomFunction(this, wrappedObject, wrappedObject[property], property, customWrapFunction);
1000                 else
1001                     proxy[property] = this._wrapFunction(this, wrappedObject, wrappedObject[property], property);
1002             } else if (TypeUtils.isEnumPropertyName(property, wrappedObject)) {
1003                 // Fast access to enums and constants.
1004                 proxy[property] = wrappedObject[property];
1005             } else {
1006                 this._proxyStatePropertyNames.push(property);
1007                 Object.defineProperty(proxy, property, {
1008                     get: function()
1009                     {
1010                         var obj = wrappedObject[property];
1011                         var resource = Resource.forObject(obj);
1012                         return resource ? resource : obj;
1013                     },
1014                     set: this._wrapPropertySetter(this, wrappedObject, property),
1015                     enumerable: true
1016                 });
1017             }
1018         }
1019
1020         var isEmpty = true;
1021         for (var property in wrappedObject) {
1022             isEmpty = false;
1023             processProperty.call(this, property);
1024         }
1025         if (isEmpty)
1026             return wrappedObject; // Nothing to proxy.
1027
1028         this._bindObjectToResource(proxy);
1029         return proxy;
1030     },
1031
1032     /**
1033      * @param {!Resource} resource
1034      * @param {!Object} originalObject
1035      * @param {!Function} originalFunction
1036      * @param {string} functionName
1037      * @param {!Function} customWrapFunction
1038      * @return {!Function}
1039      */
1040     _wrapCustomFunction: function(resource, originalObject, originalFunction, functionName, customWrapFunction)
1041     {
1042         return function()
1043         {
1044             var manager = resource.manager();
1045             var isCapturing = manager && manager.capturing();
1046             if (isCapturing)
1047                 manager.captureArguments(resource, arguments);
1048             var wrapFunction = new Resource.WrapFunction(originalObject, originalFunction, functionName, arguments);
1049             customWrapFunction.apply(wrapFunction, arguments);
1050             if (isCapturing) {
1051                 var call = wrapFunction.call();
1052                 call.setStackTrace(StackTrace.create(1, arguments.callee));
1053                 manager.captureCall(call);
1054             }
1055             return wrapFunction.result();
1056         };
1057     },
1058
1059     /**
1060      * @param {!Resource} resource
1061      * @param {!Object} originalObject
1062      * @param {!Function} originalFunction
1063      * @param {string} functionName
1064      * @return {!Function}
1065      */
1066     _wrapFunction: function(resource, originalObject, originalFunction, functionName)
1067     {
1068         return function()
1069         {
1070             var manager = resource.manager();
1071             if (!manager || !manager.capturing())
1072                 return originalFunction.apply(originalObject, arguments);
1073             manager.captureArguments(resource, arguments);
1074             var result = originalFunction.apply(originalObject, arguments);
1075             var stackTrace = StackTrace.create(1, arguments.callee);
1076             var call = new Call(resource, functionName, arguments, result, stackTrace);
1077             manager.captureCall(call);
1078             return result;
1079         };
1080     },
1081
1082     /**
1083      * @param {!Resource} resource
1084      * @param {!Object} originalObject
1085      * @param {string} propertyName
1086      * @return {function(*)}
1087      */
1088     _wrapPropertySetter: function(resource, originalObject, propertyName)
1089     {
1090         return function(value)
1091         {
1092             resource._registerBoundResource(propertyName, value);
1093             var manager = resource.manager();
1094             if (!manager || !manager.capturing()) {
1095                 originalObject[propertyName] = Resource.wrappedObject(value);
1096                 return;
1097             }
1098             var args = [propertyName, value];
1099             manager.captureArguments(resource, args);
1100             originalObject[propertyName] = Resource.wrappedObject(value);
1101             var stackTrace = StackTrace.create(1, arguments.callee);
1102             var call = new Call(resource, "", args, undefined, stackTrace);
1103             manager.captureCall(call);
1104         };
1105     },
1106
1107     /**
1108      * @return {!Object.<string, !Function>}
1109      */
1110     _customWrapFunctions: function()
1111     {
1112         return Object.create(null); // May be overridden by subclasses.
1113     }
1114 }
1115
1116 /**
1117  * @constructor
1118  * @param {!Object} originalObject
1119  * @param {!Function} originalFunction
1120  * @param {string} functionName
1121  * @param {!Array|!Arguments} args
1122  */
1123 Resource.WrapFunction = function(originalObject, originalFunction, functionName, args)
1124 {
1125     this._originalObject = originalObject;
1126     this._originalFunction = originalFunction;
1127     this._functionName = functionName;
1128     this._args = args;
1129     this._resource = Resource.forObject(originalObject);
1130     console.assert(this._resource, "Expected a wrapped call on a Resource object.");
1131 }
1132
1133 Resource.WrapFunction.prototype = {
1134     /**
1135      * @return {*}
1136      */
1137     result: function()
1138     {
1139         if (!this._executed) {
1140             this._executed = true;
1141             this._result = this._originalFunction.apply(this._originalObject, this._args);
1142         }
1143         return this._result;
1144     },
1145
1146     /**
1147      * @return {!Call}
1148      */
1149     call: function()
1150     {
1151         if (!this._call)
1152             this._call = new Call(this._resource, this._functionName, this._args, this.result());
1153         return this._call;
1154     },
1155
1156     /**
1157      * @param {*} result
1158      */
1159     overrideResult: function(result)
1160     {
1161         var call = this.call();
1162         call.setResult(result);
1163         this._result = result;
1164     }
1165 }
1166
1167 /**
1168  * @param {function(new:Resource, !Object, string)} resourceConstructor
1169  * @param {string} resourceName
1170  * @return {function(this:Resource.WrapFunction)}
1171  */
1172 Resource.WrapFunction.resourceFactoryMethod = function(resourceConstructor, resourceName)
1173 {
1174     /** @this {Resource.WrapFunction} */
1175     return function()
1176     {
1177         var wrappedObject = /** @type {?Object} */ (this.result());
1178         if (!wrappedObject)
1179             return;
1180         var resource = new resourceConstructor(wrappedObject, resourceName);
1181         var manager = this._resource.manager();
1182         if (manager)
1183             manager.registerResource(resource);
1184         this.overrideResult(resource.proxyObject());
1185         resource.pushCall(this.call());
1186     }
1187 }
1188
1189 /**
1190  * @constructor
1191  * @param {!Resource} originalResource
1192  * @param {!Object} data
1193  */
1194 function ReplayableResource(originalResource, data)
1195 {
1196     this._proto = originalResource.__proto__;
1197     this._data = data;
1198 }
1199
1200 ReplayableResource.prototype = {
1201     /**
1202      * @return {number}
1203      */
1204     id: function()
1205     {
1206         return this._data.id;
1207     },
1208
1209     /**
1210      * @return {string}
1211      */
1212     name: function()
1213     {
1214         return this._data.name;
1215     },
1216
1217     /**
1218      * @return {string}
1219      */
1220     description: function()
1221     {
1222         return this._data.name + "@" + this._data.kindId;
1223     },
1224
1225     /**
1226      * @return {!ReplayableResource}
1227      */
1228     contextResource: function()
1229     {
1230         return this._data.contextResource || this;
1231     },
1232
1233     /**
1234      * @param {!Cache.<!Resource>} cache
1235      * @return {!Resource}
1236      */
1237     replay: function(cache)
1238     {
1239         var result = /** @type {!Resource} */ (Object.create(this._proto));
1240         result = result.replay(this._data, cache)
1241         console.assert(result.__proto__ === this._proto, "Wrong type of a replay result");
1242         return result;
1243     }
1244 }
1245
1246 /**
1247  * @param {!ReplayableResource|*} obj
1248  * @param {!Cache.<!Resource>} cache
1249  * @return {*}
1250  */
1251 ReplayableResource.replay = function(obj, cache)
1252 {
1253     return (obj instanceof ReplayableResource) ? obj.replay(cache).wrappedObject() : obj;
1254 }
1255
1256 /**
1257  * @constructor
1258  * @extends {Resource}
1259  * @param {!Object} wrappedObject
1260  * @param {string} name
1261  */
1262 function ContextResource(wrappedObject, name)
1263 {
1264     Resource.call(this, wrappedObject, name);
1265 }
1266
1267 ContextResource.prototype = {
1268     __proto__: Resource.prototype
1269 }
1270
1271 /**
1272  * @constructor
1273  * @extends {Resource}
1274  * @param {!Object} wrappedObject
1275  * @param {string} name
1276  */
1277 function LogEverythingResource(wrappedObject, name)
1278 {
1279     Resource.call(this, wrappedObject, name);
1280 }
1281
1282 LogEverythingResource.prototype = {
1283     /**
1284      * @override
1285      * @return {!Object.<string, !Function>}
1286      */
1287     _customWrapFunctions: function()
1288     {
1289         var wrapFunctions = Object.create(null);
1290         var wrappedObject = this.wrappedObject();
1291         if (wrappedObject) {
1292             for (var property in wrappedObject) {
1293                 /** @this {Resource.WrapFunction} */
1294                 wrapFunctions[property] = function()
1295                 {
1296                     this._resource.pushCall(this.call());
1297                 }
1298             }
1299         }
1300         return wrapFunctions;
1301     },
1302
1303     __proto__: Resource.prototype
1304 }
1305
1306 ////////////////////////////////////////////////////////////////////////////////
1307 // WebGL
1308 ////////////////////////////////////////////////////////////////////////////////
1309
1310 /**
1311  * @constructor
1312  * @extends {Resource}
1313  * @param {!Object} wrappedObject
1314  * @param {string} name
1315  */
1316 function WebGLBoundResource(wrappedObject, name)
1317 {
1318     Resource.call(this, wrappedObject, name);
1319     /** @type {!Object.<string, *>} */
1320     this._state = {};
1321 }
1322
1323 WebGLBoundResource.prototype = {
1324     /**
1325      * @override
1326      * @param {!Object} data
1327      * @param {!Cache.<!ReplayableResource>} cache
1328      */
1329     _populateReplayableData: function(data, cache)
1330     {
1331         var state = this._state;
1332         data.state = {};
1333         Object.keys(state).forEach(function(parameter) {
1334             data.state[parameter] = Resource.toReplayable(state[parameter], cache);
1335         });
1336     },
1337
1338     /**
1339      * @override
1340      * @param {!Object} data
1341      * @param {!Cache.<!Resource>} cache
1342      */
1343     _doReplayCalls: function(data, cache)
1344     {
1345         var gl = this._replayContextResource(data, cache).wrappedObject();
1346
1347         /** @type {!Object.<string, !Array.<string>>} */
1348         var bindingsData = {
1349             TEXTURE_2D: ["bindTexture", "TEXTURE_BINDING_2D"],
1350             TEXTURE_CUBE_MAP: ["bindTexture", "TEXTURE_BINDING_CUBE_MAP"],
1351             ARRAY_BUFFER: ["bindBuffer", "ARRAY_BUFFER_BINDING"],
1352             ELEMENT_ARRAY_BUFFER: ["bindBuffer", "ELEMENT_ARRAY_BUFFER_BINDING"],
1353             FRAMEBUFFER: ["bindFramebuffer", "FRAMEBUFFER_BINDING"],
1354             RENDERBUFFER: ["bindRenderbuffer", "RENDERBUFFER_BINDING"]
1355         };
1356         var originalBindings = {};
1357         Object.keys(bindingsData).forEach(function(bindingTarget) {
1358             var bindingParameter = bindingsData[bindingTarget][1];
1359             originalBindings[bindingTarget] = gl.getParameter(gl[bindingParameter]);
1360         });
1361
1362         var state = {};
1363         Object.keys(data.state).forEach(function(parameter) {
1364             state[parameter] = ReplayableResource.replay(data.state[parameter], cache);
1365         });
1366         this._state = state;
1367         Resource.prototype._doReplayCalls.call(this, data, cache);
1368
1369         Object.keys(bindingsData).forEach(function(bindingTarget) {
1370             var bindMethodName = bindingsData[bindingTarget][0];
1371             gl[bindMethodName].call(gl, gl[bindingTarget], originalBindings[bindingTarget]);
1372         });
1373     },
1374
1375     /**
1376      * @param {!Object} data
1377      * @param {!Cache.<!Resource>} cache
1378      * @return {?WebGLRenderingContextResource}
1379      */
1380     _replayContextResource: function(data, cache)
1381     {
1382         var calls = /** @type {!Array.<!ReplayableCall>} */ (data.calls);
1383         for (var i = 0, n = calls.length; i < n; ++i) {
1384             var resource = ReplayableResource.replay(calls[i].replayableResource(), cache);
1385             var contextResource = WebGLRenderingContextResource.forObject(resource);
1386             if (contextResource)
1387                 return contextResource;
1388         }
1389         return null;
1390     },
1391
1392     /**
1393      * @param {number} target
1394      * @param {string} bindMethodName
1395      */
1396     pushBinding: function(target, bindMethodName)
1397     {
1398         if (this._state.bindTarget !== target) {
1399             this._state.bindTarget = target;
1400             this.pushCall(new Call(WebGLRenderingContextResource.forObject(this), bindMethodName, [target, this]));
1401         }
1402     },
1403
1404     __proto__: Resource.prototype
1405 }
1406
1407 /**
1408  * @constructor
1409  * @extends {WebGLBoundResource}
1410  * @param {!Object} wrappedObject
1411  * @param {string} name
1412  */
1413 function WebGLTextureResource(wrappedObject, name)
1414 {
1415     WebGLBoundResource.call(this, wrappedObject, name);
1416 }
1417
1418 WebGLTextureResource.prototype = {
1419     /**
1420      * @override (overrides @return type)
1421      * @return {!WebGLTexture}
1422      */
1423     wrappedObject: function()
1424     {
1425         return this._wrappedObject;
1426     },
1427
1428     /**
1429      * @override
1430      * @return {!Array.<!TypeUtils.InternalResourceStateDescriptor>}
1431      */
1432     currentState: function()
1433     {
1434         var result = [];
1435         var glResource = WebGLRenderingContextResource.forObject(this);
1436         var gl = glResource.wrappedObject();
1437         var texture = this.wrappedObject();
1438         if (!gl || !texture)
1439             return result;
1440         result.push({ name: "isTexture", value: gl.isTexture(texture) });
1441         result.push({ name: "context", value: this.contextResource() });
1442
1443         var target = this._state.bindTarget;
1444         if (typeof target !== "number")
1445             return result;
1446
1447         var bindingParameter;
1448         switch (target) {
1449         case gl.TEXTURE_2D:
1450             bindingParameter = gl.TEXTURE_BINDING_2D;
1451             break;
1452         case gl.TEXTURE_CUBE_MAP:
1453             bindingParameter = gl.TEXTURE_BINDING_CUBE_MAP;
1454             break;
1455         default:
1456             console.error("ASSERT_NOT_REACHED: unknown texture target " + target);
1457             return result;
1458         }
1459         result.push({ name: "target", value: target, valueIsEnum: true });
1460
1461         var oldTexture = /** @type {!WebGLTexture} */ (gl.getParameter(bindingParameter));
1462         if (oldTexture !== texture)
1463             gl.bindTexture(target, texture);
1464
1465         var textureParameters = [
1466             "TEXTURE_MAG_FILTER",
1467             "TEXTURE_MIN_FILTER",
1468             "TEXTURE_WRAP_S",
1469             "TEXTURE_WRAP_T",
1470             "TEXTURE_MAX_ANISOTROPY_EXT" // EXT_texture_filter_anisotropic extension
1471         ];
1472         glResource.queryStateValues(gl.getTexParameter, target, textureParameters, result);
1473
1474         if (oldTexture !== texture)
1475             gl.bindTexture(target, oldTexture);
1476         return result;
1477     },
1478
1479     /**
1480      * @override
1481      * @param {!Object} data
1482      * @param {!Cache.<!Resource>} cache
1483      */
1484     _doReplayCalls: function(data, cache)
1485     {
1486         var gl = this._replayContextResource(data, cache).wrappedObject();
1487
1488         var state = {};
1489         WebGLRenderingContextResource.PixelStoreParameters.forEach(function(parameter) {
1490             state[parameter] = gl.getParameter(gl[parameter]);
1491         });
1492
1493         WebGLBoundResource.prototype._doReplayCalls.call(this, data, cache);
1494
1495         WebGLRenderingContextResource.PixelStoreParameters.forEach(function(parameter) {
1496             gl.pixelStorei(gl[parameter], state[parameter]);
1497         });
1498     },
1499
1500     /**
1501      * @override
1502      * @param {!Call} call
1503      */
1504     pushCall: function(call)
1505     {
1506         var gl = WebGLRenderingContextResource.forObject(call.resource()).wrappedObject();
1507         WebGLRenderingContextResource.PixelStoreParameters.forEach(function(parameter) {
1508             var value = gl.getParameter(gl[parameter]);
1509             if (this._state[parameter] !== value) {
1510                 this._state[parameter] = value;
1511                 var pixelStoreCall = new Call(gl, "pixelStorei", [gl[parameter], value]);
1512                 WebGLBoundResource.prototype.pushCall.call(this, pixelStoreCall);
1513             }
1514         }, this);
1515
1516         // FIXME: remove any older calls that no longer contribute to the resource state.
1517         // FIXME: optimize memory usage: maybe it's more efficient to store one texImage2D call instead of many texSubImage2D.
1518         WebGLBoundResource.prototype.pushCall.call(this, call);
1519     },
1520
1521     /**
1522      * Handles: texParameteri, texParameterf
1523      * @param {!Call} call
1524      */
1525     pushCall_texParameter: function(call)
1526     {
1527         var args = call.args();
1528         var pname = args[1];
1529         var param = args[2];
1530         if (this._state[pname] !== param) {
1531             this._state[pname] = param;
1532             WebGLBoundResource.prototype.pushCall.call(this, call);
1533         }
1534     },
1535
1536     /**
1537      * Handles: copyTexImage2D, copyTexSubImage2D
1538      * copyTexImage2D and copyTexSubImage2D define a texture image with pixels from the current framebuffer.
1539      * @param {!Call} call
1540      */
1541     pushCall_copyTexImage2D: function(call)
1542     {
1543         var glResource = WebGLRenderingContextResource.forObject(call.resource());
1544         var gl = glResource.wrappedObject();
1545         var framebufferResource = /** @type {!WebGLFramebufferResource} */ (glResource.currentBinding(gl.FRAMEBUFFER));
1546         if (framebufferResource)
1547             this.pushCall(new Call(glResource, "bindFramebuffer", [gl.FRAMEBUFFER, framebufferResource]));
1548         else {
1549             // FIXME: Implement this case.
1550             console.error("ASSERT_NOT_REACHED: Could not properly process a gl." + call.functionName() + " call while the DRAWING BUFFER is bound.");
1551         }
1552         this.pushCall(call);
1553     },
1554
1555     __proto__: WebGLBoundResource.prototype
1556 }
1557
1558 /**
1559  * @constructor
1560  * @extends {Resource}
1561  * @param {!Object} wrappedObject
1562  * @param {string} name
1563  */
1564 function WebGLProgramResource(wrappedObject, name)
1565 {
1566     Resource.call(this, wrappedObject, name);
1567 }
1568
1569 WebGLProgramResource.prototype = {
1570     /**
1571      * @override (overrides @return type)
1572      * @return {!WebGLProgram}
1573      */
1574     wrappedObject: function()
1575     {
1576         return this._wrappedObject;
1577     },
1578
1579     /**
1580      * @override
1581      * @return {!Array.<!TypeUtils.InternalResourceStateDescriptor>}
1582      */
1583     currentState: function()
1584     {
1585         /**
1586          * @param {!Object} obj
1587          * @param {!Array.<!TypeUtils.InternalResourceStateDescriptor>} output
1588          */
1589         function convertToStateDescriptors(obj, output)
1590         {
1591             for (var pname in obj)
1592                 output.push({ name: pname, value: obj[pname], valueIsEnum: (pname === "type") });
1593         }
1594
1595         var result = [];
1596         var program = this.wrappedObject();
1597         if (!program)
1598             return result;
1599         var glResource = WebGLRenderingContextResource.forObject(this);
1600         var gl = glResource.wrappedObject();
1601         var programParameters = ["DELETE_STATUS", "LINK_STATUS", "VALIDATE_STATUS"];
1602         glResource.queryStateValues(gl.getProgramParameter, program, programParameters, result);
1603         result.push({ name: "getProgramInfoLog", value: gl.getProgramInfoLog(program) });
1604         result.push({ name: "isProgram", value: gl.isProgram(program) });
1605         result.push({ name: "context", value: this.contextResource() });
1606
1607         // ATTACHED_SHADERS
1608         var callFormatter = CallFormatter.forResource(this);
1609         var shaders = gl.getAttachedShaders(program) || [];
1610         var shaderDescriptors = [];
1611         for (var i = 0, n = shaders.length; i < n; ++i) {
1612             var shaderResource = Resource.forObject(shaders[i]);
1613             var pname = callFormatter.enumNameForValue(shaderResource.type());
1614             shaderDescriptors.push({ name: pname, value: shaderResource });
1615         }
1616         result.push({ name: "ATTACHED_SHADERS", values: shaderDescriptors, isArray: true });
1617
1618         // ACTIVE_UNIFORMS
1619         var uniformDescriptors = [];
1620         var uniforms = this._activeUniforms(true);
1621         for (var i = 0, n = uniforms.length; i < n; ++i) {
1622             var pname = "" + i;
1623             var values = [];
1624             convertToStateDescriptors(uniforms[i], values);
1625             uniformDescriptors.push({ name: pname, values: values });
1626         }
1627         result.push({ name: "ACTIVE_UNIFORMS", values: uniformDescriptors, isArray: true });
1628
1629         // ACTIVE_ATTRIBUTES
1630         var attributesCount = /** @type {number} */ (gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES));
1631         var attributeDescriptors = [];
1632         for (var i = 0; i < attributesCount; ++i) {
1633             var activeInfo = gl.getActiveAttrib(program, i);
1634             if (!activeInfo)
1635                 continue;
1636             var pname = "" + i;
1637             var values = [];
1638             convertToStateDescriptors(activeInfo, values);
1639             attributeDescriptors.push({ name: pname, values: values });
1640         }
1641         result.push({ name: "ACTIVE_ATTRIBUTES", values: attributeDescriptors, isArray: true });
1642
1643         return result;
1644     },
1645
1646     /**
1647      * @param {boolean=} includeAllInfo
1648      * @return {!Array.<{name:string, type:number, value:*, size:(number|undefined)}>}
1649      */
1650     _activeUniforms: function(includeAllInfo)
1651     {
1652         var uniforms = [];
1653         var program = this.wrappedObject();
1654         if (!program)
1655             return uniforms;
1656
1657         var gl = WebGLRenderingContextResource.forObject(this).wrappedObject();
1658         var uniformsCount = /** @type {number} */ (gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS));
1659         for (var i = 0; i < uniformsCount; ++i) {
1660             var activeInfo = gl.getActiveUniform(program, i);
1661             if (!activeInfo)
1662                 continue;
1663             var uniformLocation = gl.getUniformLocation(program, activeInfo.name);
1664             if (!uniformLocation)
1665                 continue;
1666             var value = gl.getUniform(program, uniformLocation);
1667             var item = Object.create(null);
1668             item.name = activeInfo.name;
1669             item.type = activeInfo.type;
1670             item.value = value;
1671             if (includeAllInfo)
1672                 item.size = activeInfo.size;
1673             uniforms.push(item);
1674         }
1675         return uniforms;
1676     },
1677
1678     /**
1679      * @override
1680      * @param {!Object} data
1681      * @param {!Cache.<!ReplayableResource>} cache
1682      */
1683     _populateReplayableData: function(data, cache)
1684     {
1685         var glResource = WebGLRenderingContextResource.forObject(this);
1686         var originalErrors = glResource.getAllErrors();
1687         data.uniforms = this._activeUniforms();
1688         glResource.restoreErrors(originalErrors);
1689     },
1690
1691     /**
1692      * @override
1693      * @param {!Object} data
1694      * @param {!Cache.<!Resource>} cache
1695      */
1696     _doReplayCalls: function(data, cache)
1697     {
1698         Resource.prototype._doReplayCalls.call(this, data, cache);
1699         var gl = WebGLRenderingContextResource.forObject(this).wrappedObject();
1700         var program = this.wrappedObject();
1701
1702         var originalProgram = /** @type {!WebGLProgram} */ (gl.getParameter(gl.CURRENT_PROGRAM));
1703         var currentProgram = originalProgram;
1704
1705         data.uniforms.forEach(function(uniform) {
1706             var uniformLocation = gl.getUniformLocation(program, uniform.name);
1707             if (!uniformLocation)
1708                 return;
1709             if (currentProgram !== program) {
1710                 currentProgram = program;
1711                 gl.useProgram(program);
1712             }
1713             var methodName = this._uniformMethodNameByType(gl, uniform.type);
1714             if (methodName.indexOf("Matrix") === -1)
1715                 gl[methodName].call(gl, uniformLocation, uniform.value);
1716             else
1717                 gl[methodName].call(gl, uniformLocation, false, uniform.value);
1718         }.bind(this));
1719
1720         if (currentProgram !== originalProgram)
1721             gl.useProgram(originalProgram);
1722     },
1723
1724     /**
1725      * @param {!WebGLRenderingContext} gl
1726      * @param {number} type
1727      * @return {string}
1728      */
1729     _uniformMethodNameByType: function(gl, type)
1730     {
1731         var uniformMethodNames = WebGLProgramResource._uniformMethodNames;
1732         if (!uniformMethodNames) {
1733             uniformMethodNames = {};
1734             uniformMethodNames[gl.FLOAT] = "uniform1f";
1735             uniformMethodNames[gl.FLOAT_VEC2] = "uniform2fv";
1736             uniformMethodNames[gl.FLOAT_VEC3] = "uniform3fv";
1737             uniformMethodNames[gl.FLOAT_VEC4] = "uniform4fv";
1738             uniformMethodNames[gl.INT] = "uniform1i";
1739             uniformMethodNames[gl.BOOL] = "uniform1i";
1740             uniformMethodNames[gl.SAMPLER_2D] = "uniform1i";
1741             uniformMethodNames[gl.SAMPLER_CUBE] = "uniform1i";
1742             uniformMethodNames[gl.INT_VEC2] = "uniform2iv";
1743             uniformMethodNames[gl.BOOL_VEC2] = "uniform2iv";
1744             uniformMethodNames[gl.INT_VEC3] = "uniform3iv";
1745             uniformMethodNames[gl.BOOL_VEC3] = "uniform3iv";
1746             uniformMethodNames[gl.INT_VEC4] = "uniform4iv";
1747             uniformMethodNames[gl.BOOL_VEC4] = "uniform4iv";
1748             uniformMethodNames[gl.FLOAT_MAT2] = "uniformMatrix2fv";
1749             uniformMethodNames[gl.FLOAT_MAT3] = "uniformMatrix3fv";
1750             uniformMethodNames[gl.FLOAT_MAT4] = "uniformMatrix4fv";
1751             WebGLProgramResource._uniformMethodNames = uniformMethodNames;
1752         }
1753         console.assert(uniformMethodNames[type], "Unknown uniform type " + type);
1754         return uniformMethodNames[type];
1755     },
1756
1757     /**
1758      * @override
1759      * @param {!Call} call
1760      */
1761     pushCall: function(call)
1762     {
1763         // FIXME: remove any older calls that no longer contribute to the resource state.
1764         // FIXME: handle multiple attachShader && detachShader.
1765         Resource.prototype.pushCall.call(this, call);
1766     },
1767
1768     __proto__: Resource.prototype
1769 }
1770
1771 /**
1772  * @constructor
1773  * @extends {Resource}
1774  * @param {!Object} wrappedObject
1775  * @param {string} name
1776  */
1777 function WebGLShaderResource(wrappedObject, name)
1778 {
1779     Resource.call(this, wrappedObject, name);
1780 }
1781
1782 WebGLShaderResource.prototype = {
1783     /**
1784      * @override (overrides @return type)
1785      * @return {!WebGLShader}
1786      */
1787     wrappedObject: function()
1788     {
1789         return this._wrappedObject;
1790     },
1791
1792     /**
1793      * @return {number}
1794      */
1795     type: function()
1796     {
1797         var call = this._calls[0];
1798         if (call && call.functionName() === "createShader")
1799             return call.args()[0];
1800         console.error("ASSERT_NOT_REACHED: Failed to restore shader type from the log.", call);
1801         return 0;
1802     },
1803
1804     /**
1805      * @override
1806      * @return {!Array.<!TypeUtils.InternalResourceStateDescriptor>}
1807      */
1808     currentState: function()
1809     {
1810         var result = [];
1811         var shader = this.wrappedObject();
1812         if (!shader)
1813             return result;
1814         var glResource = WebGLRenderingContextResource.forObject(this);
1815         var gl = glResource.wrappedObject();
1816         var shaderParameters = ["SHADER_TYPE", "DELETE_STATUS", "COMPILE_STATUS"];
1817         glResource.queryStateValues(gl.getShaderParameter, shader, shaderParameters, result);
1818         result.push({ name: "getShaderInfoLog", value: gl.getShaderInfoLog(shader) });
1819         result.push({ name: "getShaderSource", value: gl.getShaderSource(shader) });
1820         result.push({ name: "isShader", value: gl.isShader(shader) });
1821         result.push({ name: "context", value: this.contextResource() });
1822
1823         // getShaderPrecisionFormat
1824         var shaderType = this.type();
1825         var precisionValues = [];
1826         var precisionParameters = ["LOW_FLOAT", "MEDIUM_FLOAT", "HIGH_FLOAT", "LOW_INT", "MEDIUM_INT", "HIGH_INT"];
1827         for (var i = 0, pname; pname = precisionParameters[i]; ++i)
1828             precisionValues.push({ name: pname, value: gl.getShaderPrecisionFormat(shaderType, gl[pname]) });
1829         result.push({ name: "getShaderPrecisionFormat", values: precisionValues });
1830
1831         return result;
1832     },
1833
1834     /**
1835      * @override
1836      * @param {!Call} call
1837      */
1838     pushCall: function(call)
1839     {
1840         // FIXME: remove any older calls that no longer contribute to the resource state.
1841         // FIXME: handle multiple shaderSource calls.
1842         Resource.prototype.pushCall.call(this, call);
1843     },
1844
1845     __proto__: Resource.prototype
1846 }
1847
1848 /**
1849  * @constructor
1850  * @extends {WebGLBoundResource}
1851  * @param {!Object} wrappedObject
1852  * @param {string} name
1853  */
1854 function WebGLBufferResource(wrappedObject, name)
1855 {
1856     WebGLBoundResource.call(this, wrappedObject, name);
1857 }
1858
1859 WebGLBufferResource.prototype = {
1860     /**
1861      * @override (overrides @return type)
1862      * @return {!WebGLBuffer}
1863      */
1864     wrappedObject: function()
1865     {
1866         return this._wrappedObject;
1867     },
1868
1869     /**
1870      * @return {?ArrayBufferView}
1871      */
1872     cachedBufferData: function()
1873     {
1874         /**
1875          * Creates a view to a given buffer, does NOT copy the buffer.
1876          * @param {!ArrayBuffer|!ArrayBufferView} buffer
1877          * @return {!Uint8Array}
1878          */
1879         function createUint8ArrayBufferView(buffer)
1880         {
1881             return buffer instanceof ArrayBuffer ? new Uint8Array(buffer) : new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
1882         }
1883
1884         if (!this._cachedBufferData) {
1885             for (var i = this._calls.length - 1; i >= 0; --i) {
1886                 var call = this._calls[i];
1887                 if (call.functionName() === "bufferData") {
1888                     var sizeOrData = /** @type {number|!ArrayBuffer|!ArrayBufferView} */ (call.args()[1]);
1889                     if (typeof sizeOrData === "number")
1890                         this._cachedBufferData = new ArrayBuffer(sizeOrData);
1891                     else
1892                         this._cachedBufferData = sizeOrData;
1893                     this._lastBufferSubDataIndex = i + 1;
1894                     break;
1895                 }
1896             }
1897             if (!this._cachedBufferData)
1898                 return null;
1899         }
1900
1901         // Apply any "bufferSubData" calls that have not been applied yet.
1902         var bufferDataView;
1903         while (this._lastBufferSubDataIndex < this._calls.length) {
1904             var call = this._calls[this._lastBufferSubDataIndex++];
1905             if (call.functionName() !== "bufferSubData")
1906                 continue;
1907             var offset = /** @type {number} */ (call.args()[1]);
1908             var data = /** @type {!ArrayBuffer|!ArrayBufferView} */ (call.args()[2]);
1909             var view = createUint8ArrayBufferView(data);
1910             if (!bufferDataView)
1911                 bufferDataView = createUint8ArrayBufferView(this._cachedBufferData);
1912             bufferDataView.set(view, offset);
1913
1914             var isFullReplacement = (offset === 0 && bufferDataView.length === view.length);
1915             if (this._cachedBufferData instanceof ArrayBuffer) {
1916                 // The buffer data has no type yet. Try to guess from the "bufferSubData" call.
1917                 var typedArrayClass = TypeUtils.typedArrayClass(data);
1918                 if (typedArrayClass)
1919                     this._cachedBufferData = new typedArrayClass(this._cachedBufferData); // Does not copy the buffer.
1920             } else if (isFullReplacement) {
1921                 var typedArrayClass = TypeUtils.typedArrayClass(data);
1922                 if (typedArrayClass) {
1923                     var typedArrayData = /** @type {!ArrayBufferView} */ (data);
1924                     this._cachedBufferData = new typedArrayClass(this._cachedBufferData.buffer, this._cachedBufferData.byteOffset, typedArrayData.length); // Does not copy the buffer.
1925                 }
1926             }
1927         }
1928
1929         if (this._cachedBufferData instanceof ArrayBuffer) {
1930             // If we failed to guess the data type yet, use Uint8Array.
1931             return new Uint8Array(this._cachedBufferData);
1932         }
1933         return this._cachedBufferData;
1934     },
1935
1936     /**
1937      * @override
1938      * @return {!Array.<!TypeUtils.InternalResourceStateDescriptor>}
1939      */
1940     currentState: function()
1941     {
1942         var result = [];
1943         var glResource = WebGLRenderingContextResource.forObject(this);
1944         var gl = glResource.wrappedObject();
1945         var buffer = this.wrappedObject();
1946         if (!gl || !buffer)
1947             return result;
1948         result.push({ name: "isBuffer", value: gl.isBuffer(buffer) });
1949         result.push({ name: "context", value: this.contextResource() });
1950
1951         var target = this._state.bindTarget;
1952         if (typeof target !== "number")
1953             return result;
1954
1955         var bindingParameter;
1956         switch (target) {
1957         case gl.ARRAY_BUFFER:
1958             bindingParameter = gl.ARRAY_BUFFER_BINDING;
1959             break;
1960         case gl.ELEMENT_ARRAY_BUFFER:
1961             bindingParameter = gl.ELEMENT_ARRAY_BUFFER_BINDING;
1962             break;
1963         default:
1964             console.error("ASSERT_NOT_REACHED: unknown buffer target " + target);
1965             return result;
1966         }
1967         result.push({ name: "target", value: target, valueIsEnum: true });
1968
1969         var oldBuffer = /** @type {!WebGLBuffer} */ (gl.getParameter(bindingParameter));
1970         if (oldBuffer !== buffer)
1971             gl.bindBuffer(target, buffer);
1972
1973         var bufferParameters = ["BUFFER_SIZE", "BUFFER_USAGE"];
1974         glResource.queryStateValues(gl.getBufferParameter, target, bufferParameters, result);
1975
1976         if (oldBuffer !== buffer)
1977             gl.bindBuffer(target, oldBuffer);
1978
1979         try {
1980             var data = this.cachedBufferData();
1981             if (data)
1982                 result.push({ name: "bufferData", value: data });
1983         } catch (e) {
1984             console.error("Exception while restoring bufferData", e);
1985         }
1986
1987         return result;
1988     },
1989
1990     /**
1991      * @param {!Call} call
1992      */
1993     pushCall_bufferData: function(call)
1994     {
1995         // FIXME: remove any older calls that no longer contribute to the resource state.
1996         delete this._cachedBufferData;
1997         delete this._lastBufferSubDataIndex;
1998         WebGLBoundResource.prototype.pushCall.call(this, call);
1999     },
2000
2001     /**
2002      * @param {!Call} call
2003      */
2004     pushCall_bufferSubData: function(call)
2005     {
2006         // FIXME: Optimize memory for bufferSubData.
2007         WebGLBoundResource.prototype.pushCall.call(this, call);
2008     },
2009
2010     __proto__: WebGLBoundResource.prototype
2011 }
2012
2013 /**
2014  * @constructor
2015  * @extends {WebGLBoundResource}
2016  * @param {!Object} wrappedObject
2017  * @param {string} name
2018  */
2019 function WebGLFramebufferResource(wrappedObject, name)
2020 {
2021     WebGLBoundResource.call(this, wrappedObject, name);
2022 }
2023
2024 WebGLFramebufferResource.prototype = {
2025     /**
2026      * @override (overrides @return type)
2027      * @return {!WebGLFramebuffer}
2028      */
2029     wrappedObject: function()
2030     {
2031         return this._wrappedObject;
2032     },
2033
2034     /**
2035      * @override
2036      * @return {!Array.<!TypeUtils.InternalResourceStateDescriptor>}
2037      */
2038     currentState: function()
2039     {
2040         var result = [];
2041         var framebuffer = this.wrappedObject();
2042         if (!framebuffer)
2043             return result;
2044         var gl = WebGLRenderingContextResource.forObject(this).wrappedObject();
2045
2046         var oldFramebuffer = /** @type {!WebGLFramebuffer} */ (gl.getParameter(gl.FRAMEBUFFER_BINDING));
2047         if (oldFramebuffer !== framebuffer)
2048             gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
2049
2050         var attachmentParameters = ["COLOR_ATTACHMENT0", "DEPTH_ATTACHMENT", "STENCIL_ATTACHMENT"];
2051         var framebufferParameters = ["FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE", "FRAMEBUFFER_ATTACHMENT_OBJECT_NAME", "FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL", "FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE"];
2052         for (var i = 0, attachment; attachment = attachmentParameters[i]; ++i) {
2053             var values = [];
2054             for (var j = 0, pname; pname = framebufferParameters[j]; ++j) {
2055                 var value = gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl[attachment], gl[pname]);
2056                 value = Resource.forObject(value) || value;
2057                 values.push({ name: pname, value: value, valueIsEnum: WebGLRenderingContextResource.GetResultIsEnum[pname] });
2058             }
2059             result.push({ name: attachment, values: values });
2060         }
2061         result.push({ name: "isFramebuffer", value: gl.isFramebuffer(framebuffer) });
2062         result.push({ name: "context", value: this.contextResource() });
2063
2064         if (oldFramebuffer !== framebuffer)
2065             gl.bindFramebuffer(gl.FRAMEBUFFER, oldFramebuffer);
2066         return result;
2067     },
2068
2069     /**
2070      * @override
2071      * @param {!Call} call
2072      */
2073     pushCall: function(call)
2074     {
2075         // FIXME: remove any older calls that no longer contribute to the resource state.
2076         WebGLBoundResource.prototype.pushCall.call(this, call);
2077     },
2078
2079     __proto__: WebGLBoundResource.prototype
2080 }
2081
2082 /**
2083  * @constructor
2084  * @extends {WebGLBoundResource}
2085  * @param {!Object} wrappedObject
2086  * @param {string} name
2087  */
2088 function WebGLRenderbufferResource(wrappedObject, name)
2089 {
2090     WebGLBoundResource.call(this, wrappedObject, name);
2091 }
2092
2093 WebGLRenderbufferResource.prototype = {
2094     /**
2095      * @override (overrides @return type)
2096      * @return {!WebGLRenderbuffer}
2097      */
2098     wrappedObject: function()
2099     {
2100         return this._wrappedObject;
2101     },
2102
2103     /**
2104      * @override
2105      * @return {!Array.<!TypeUtils.InternalResourceStateDescriptor>}
2106      */
2107     currentState: function()
2108     {
2109         var result = [];
2110         var renderbuffer = this.wrappedObject();
2111         if (!renderbuffer)
2112             return result;
2113         var glResource = WebGLRenderingContextResource.forObject(this);
2114         var gl = glResource.wrappedObject();
2115
2116         var oldRenderbuffer = /** @type {!WebGLRenderbuffer} */ (gl.getParameter(gl.RENDERBUFFER_BINDING));
2117         if (oldRenderbuffer !== renderbuffer)
2118             gl.bindRenderbuffer(gl.RENDERBUFFER, renderbuffer);
2119
2120         var renderbufferParameters = ["RENDERBUFFER_WIDTH", "RENDERBUFFER_HEIGHT", "RENDERBUFFER_INTERNAL_FORMAT", "RENDERBUFFER_RED_SIZE", "RENDERBUFFER_GREEN_SIZE", "RENDERBUFFER_BLUE_SIZE", "RENDERBUFFER_ALPHA_SIZE", "RENDERBUFFER_DEPTH_SIZE", "RENDERBUFFER_STENCIL_SIZE"];
2121         glResource.queryStateValues(gl.getRenderbufferParameter, gl.RENDERBUFFER, renderbufferParameters, result);
2122         result.push({ name: "isRenderbuffer", value: gl.isRenderbuffer(renderbuffer) });
2123         result.push({ name: "context", value: this.contextResource() });
2124
2125         if (oldRenderbuffer !== renderbuffer)
2126             gl.bindRenderbuffer(gl.RENDERBUFFER, oldRenderbuffer);
2127         return result;
2128     },
2129
2130     /**
2131      * @override
2132      * @param {!Call} call
2133      */
2134     pushCall: function(call)
2135     {
2136         // FIXME: remove any older calls that no longer contribute to the resource state.
2137         WebGLBoundResource.prototype.pushCall.call(this, call);
2138     },
2139
2140     __proto__: WebGLBoundResource.prototype
2141 }
2142
2143 /**
2144  * @constructor
2145  * @extends {Resource}
2146  * @param {!Object} wrappedObject
2147  * @param {string} name
2148  */
2149 function WebGLUniformLocationResource(wrappedObject, name)
2150 {
2151     Resource.call(this, wrappedObject, name);
2152 }
2153
2154 WebGLUniformLocationResource.prototype = {
2155     /**
2156      * @override (overrides @return type)
2157      * @return {!WebGLUniformLocation}
2158      */
2159     wrappedObject: function()
2160     {
2161         return this._wrappedObject;
2162     },
2163
2164     /**
2165      * @return {?WebGLProgramResource}
2166      */
2167     program: function()
2168     {
2169         var call = this._calls[0];
2170         if (call && call.functionName() === "getUniformLocation")
2171             return /** @type {!WebGLProgramResource} */ (Resource.forObject(call.args()[0]));
2172         console.error("ASSERT_NOT_REACHED: Failed to restore WebGLUniformLocation from the log.", call);
2173         return null;
2174     },
2175
2176     /**
2177      * @return {string}
2178      */
2179     name: function()
2180     {
2181         var call = this._calls[0];
2182         if (call && call.functionName() === "getUniformLocation")
2183             return call.args()[1];
2184         console.error("ASSERT_NOT_REACHED: Failed to restore WebGLUniformLocation from the log.", call);
2185         return "";
2186     },
2187
2188     /**
2189      * @override
2190      * @return {!Array.<!TypeUtils.InternalResourceStateDescriptor>}
2191      */
2192     currentState: function()
2193     {
2194         var result = [];
2195         var location = this.wrappedObject();
2196         if (!location)
2197             return result;
2198         var programResource = this.program();
2199         var program = programResource && programResource.wrappedObject();
2200         if (!program)
2201             return result;
2202         var gl = WebGLRenderingContextResource.forObject(this).wrappedObject();
2203         var uniformValue = gl.getUniform(program, location);
2204         var name = this.name();
2205         result.push({ name: "name", value: name });
2206         result.push({ name: "program", value: programResource });
2207         result.push({ name: "value", value: uniformValue });
2208         result.push({ name: "context", value: this.contextResource() });
2209
2210         if (typeof this._type !== "number") {
2211             var altName = name + "[0]";
2212             var uniformsCount = /** @type {number} */ (gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS));
2213             for (var i = 0; i < uniformsCount; ++i) {
2214                 var activeInfo = gl.getActiveUniform(program, i);
2215                 if (!activeInfo)
2216                     continue;
2217                 if (activeInfo.name === name || activeInfo.name === altName) {
2218                     this._type = activeInfo.type;
2219                     this._size = activeInfo.size;
2220                     if (activeInfo.name === name)
2221                         break;
2222                 }
2223             }
2224         }
2225         if (typeof this._type === "number")
2226             result.push({ name: "type", value: this._type, valueIsEnum: true });
2227         if (typeof this._size === "number")
2228             result.push({ name: "size", value: this._size });
2229
2230         return result;
2231     },
2232
2233     /**
2234      * @override
2235      * @param {!Object} data
2236      * @param {!Cache.<!ReplayableResource>} cache
2237      */
2238     _populateReplayableData: function(data, cache)
2239     {
2240         data.type = this._type;
2241         data.size = this._size;
2242     },
2243
2244     /**
2245      * @override
2246      * @param {!Object} data
2247      * @param {!Cache.<!Resource>} cache
2248      */
2249     _doReplayCalls: function(data, cache)
2250     {
2251         this._type = data.type;
2252         this._size = data.size;
2253         Resource.prototype._doReplayCalls.call(this, data, cache);
2254     },
2255
2256     __proto__: Resource.prototype
2257 }
2258
2259 /**
2260  * @constructor
2261  * @extends {ContextResource}
2262  * @param {!WebGLRenderingContext} glContext
2263  */
2264 function WebGLRenderingContextResource(glContext)
2265 {
2266     ContextResource.call(this, glContext, "WebGLRenderingContext");
2267     /** @type {?Object.<number, boolean>} */
2268     this._customErrors = null;
2269     /** @type {!Object.<string, string>} */
2270     this._extensions = {};
2271     /** @type {!Object.<string, number>} */
2272     this._extensionEnums = {};
2273 }
2274
2275 /**
2276  * @const
2277  * @type {!Array.<string>}
2278  */
2279 WebGLRenderingContextResource.GLCapabilities = [
2280     "BLEND",
2281     "CULL_FACE",
2282     "DEPTH_TEST",
2283     "DITHER",
2284     "POLYGON_OFFSET_FILL",
2285     "SAMPLE_ALPHA_TO_COVERAGE",
2286     "SAMPLE_COVERAGE",
2287     "SCISSOR_TEST",
2288     "STENCIL_TEST"
2289 ];
2290
2291 /**
2292  * @const
2293  * @type {!Array.<string>}
2294  */
2295 WebGLRenderingContextResource.PixelStoreParameters = [
2296     "PACK_ALIGNMENT",
2297     "UNPACK_ALIGNMENT",
2298     "UNPACK_COLORSPACE_CONVERSION_WEBGL",
2299     "UNPACK_FLIP_Y_WEBGL",
2300     "UNPACK_PREMULTIPLY_ALPHA_WEBGL"
2301 ];
2302
2303 /**
2304  * @const
2305  * @type {!Array.<string>}
2306  */
2307 WebGLRenderingContextResource.StateParameters = [
2308     "ACTIVE_TEXTURE",
2309     "ARRAY_BUFFER_BINDING",
2310     "BLEND_COLOR",
2311     "BLEND_DST_ALPHA",
2312     "BLEND_DST_RGB",
2313     "BLEND_EQUATION_ALPHA",
2314     "BLEND_EQUATION_RGB",
2315     "BLEND_SRC_ALPHA",
2316     "BLEND_SRC_RGB",
2317     "COLOR_CLEAR_VALUE",
2318     "COLOR_WRITEMASK",
2319     "CULL_FACE_MODE",
2320     "CURRENT_PROGRAM",
2321     "DEPTH_CLEAR_VALUE",
2322     "DEPTH_FUNC",
2323     "DEPTH_RANGE",
2324     "DEPTH_WRITEMASK",
2325     "ELEMENT_ARRAY_BUFFER_BINDING",
2326     "FRAGMENT_SHADER_DERIVATIVE_HINT_OES", // OES_standard_derivatives extension
2327     "FRAMEBUFFER_BINDING",
2328     "FRONT_FACE",
2329     "GENERATE_MIPMAP_HINT",
2330     "LINE_WIDTH",
2331     "PACK_ALIGNMENT",
2332     "POLYGON_OFFSET_FACTOR",
2333     "POLYGON_OFFSET_UNITS",
2334     "RENDERBUFFER_BINDING",
2335     "SAMPLE_COVERAGE_INVERT",
2336     "SAMPLE_COVERAGE_VALUE",
2337     "SCISSOR_BOX",
2338     "STENCIL_BACK_FAIL",
2339     "STENCIL_BACK_FUNC",
2340     "STENCIL_BACK_PASS_DEPTH_FAIL",
2341     "STENCIL_BACK_PASS_DEPTH_PASS",
2342     "STENCIL_BACK_REF",
2343     "STENCIL_BACK_VALUE_MASK",
2344     "STENCIL_BACK_WRITEMASK",
2345     "STENCIL_CLEAR_VALUE",
2346     "STENCIL_FAIL",
2347     "STENCIL_FUNC",
2348     "STENCIL_PASS_DEPTH_FAIL",
2349     "STENCIL_PASS_DEPTH_PASS",
2350     "STENCIL_REF",
2351     "STENCIL_VALUE_MASK",
2352     "STENCIL_WRITEMASK",
2353     "UNPACK_ALIGNMENT",
2354     "UNPACK_COLORSPACE_CONVERSION_WEBGL",
2355     "UNPACK_FLIP_Y_WEBGL",
2356     "UNPACK_PREMULTIPLY_ALPHA_WEBGL",
2357     "VERTEX_ARRAY_BINDING_OES", // OES_vertex_array_object extension
2358     "VIEWPORT"
2359 ];
2360
2361 /**
2362  * True for those enums that return also an enum via a getter API method (e.g. getParameter, getShaderParameter, etc.).
2363  * @const
2364  * @type {!Object.<string, boolean>}
2365  */
2366 WebGLRenderingContextResource.GetResultIsEnum = TypeUtils.createPrefixedPropertyNamesSet([
2367     // gl.getParameter()
2368     "ACTIVE_TEXTURE",
2369     "BLEND_DST_ALPHA",
2370     "BLEND_DST_RGB",
2371     "BLEND_EQUATION_ALPHA",
2372     "BLEND_EQUATION_RGB",
2373     "BLEND_SRC_ALPHA",
2374     "BLEND_SRC_RGB",
2375     "CULL_FACE_MODE",
2376     "DEPTH_FUNC",
2377     "FRONT_FACE",
2378     "GENERATE_MIPMAP_HINT",
2379     "FRAGMENT_SHADER_DERIVATIVE_HINT_OES",
2380     "STENCIL_BACK_FAIL",
2381     "STENCIL_BACK_FUNC",
2382     "STENCIL_BACK_PASS_DEPTH_FAIL",
2383     "STENCIL_BACK_PASS_DEPTH_PASS",
2384     "STENCIL_FAIL",
2385     "STENCIL_FUNC",
2386     "STENCIL_PASS_DEPTH_FAIL",
2387     "STENCIL_PASS_DEPTH_PASS",
2388     "UNPACK_COLORSPACE_CONVERSION_WEBGL",
2389     // gl.getBufferParameter()
2390     "BUFFER_USAGE",
2391     // gl.getFramebufferAttachmentParameter()
2392     "FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE",
2393     // gl.getRenderbufferParameter()
2394     "RENDERBUFFER_INTERNAL_FORMAT",
2395     // gl.getTexParameter()
2396     "TEXTURE_MAG_FILTER",
2397     "TEXTURE_MIN_FILTER",
2398     "TEXTURE_WRAP_S",
2399     "TEXTURE_WRAP_T",
2400     // gl.getShaderParameter()
2401     "SHADER_TYPE",
2402     // gl.getVertexAttrib()
2403     "VERTEX_ATTRIB_ARRAY_TYPE"
2404 ]);
2405
2406 /**
2407  * @const
2408  * @type {!Object.<string, boolean>}
2409  */
2410 WebGLRenderingContextResource.DrawingMethods = TypeUtils.createPrefixedPropertyNamesSet([
2411     "clear",
2412     "drawArrays",
2413     "drawElements"
2414 ]);
2415
2416 /**
2417  * @param {*} obj
2418  * @return {?WebGLRenderingContextResource}
2419  */
2420 WebGLRenderingContextResource.forObject = function(obj)
2421 {
2422     var resource = Resource.forObject(obj);
2423     if (!resource)
2424         return null;
2425     resource = resource.contextResource();
2426     return (resource instanceof WebGLRenderingContextResource) ? resource : null;
2427 }
2428
2429 WebGLRenderingContextResource.prototype = {
2430     /**
2431      * @override (overrides @return type)
2432      * @return {!WebGLRenderingContext}
2433      */
2434     wrappedObject: function()
2435     {
2436         return this._wrappedObject;
2437     },
2438
2439     /**
2440      * @override
2441      * @return {string}
2442      */
2443     toDataURL: function()
2444     {
2445         return this.wrappedObject().canvas.toDataURL();
2446     },
2447
2448     /**
2449      * @return {!Array.<number>}
2450      */
2451     getAllErrors: function()
2452     {
2453         var errors = [];
2454         var gl = this.wrappedObject();
2455         if (gl) {
2456             while (true) {
2457                 var error = gl.getError();
2458                 if (error === gl.NO_ERROR)
2459                     break;
2460                 this.clearError(error);
2461                 errors.push(error);
2462             }
2463         }
2464         if (this._customErrors) {
2465             for (var key in this._customErrors) {
2466                 var error = Number(key);
2467                 errors.push(error);
2468             }
2469             delete this._customErrors;
2470         }
2471         return errors;
2472     },
2473
2474     /**
2475      * @param {!Array.<number>} errors
2476      */
2477     restoreErrors: function(errors)
2478     {
2479         var gl = this.wrappedObject();
2480         if (gl) {
2481             var wasError = false;
2482             while (gl.getError() !== gl.NO_ERROR)
2483                 wasError = true;
2484             console.assert(!wasError, "Error(s) while capturing current WebGL state.");
2485         }
2486         if (!errors.length)
2487             delete this._customErrors;
2488         else {
2489             this._customErrors = {};
2490             for (var i = 0, n = errors.length; i < n; ++i)
2491                 this._customErrors[errors[i]] = true;
2492         }
2493     },
2494
2495     /**
2496      * @param {number} error
2497      */
2498     clearError: function(error)
2499     {
2500         if (this._customErrors)
2501             delete this._customErrors[error];
2502     },
2503
2504     /**
2505      * @return {number}
2506      */
2507     nextError: function()
2508     {
2509         if (this._customErrors) {
2510             for (var key in this._customErrors) {
2511                 var error = Number(key);
2512                 delete this._customErrors[error];
2513                 return error;
2514             }
2515         }
2516         delete this._customErrors;
2517         var gl = this.wrappedObject();
2518         return gl ? gl.NO_ERROR : 0;
2519     },
2520
2521     /**
2522      * @param {string} name
2523      * @param {?Object} obj
2524      */
2525     registerWebGLExtension: function(name, obj)
2526     {
2527         // FIXME: Wrap OES_vertex_array_object extension.
2528         var lowerName = name.toLowerCase();
2529         if (obj && !this._extensions[lowerName]) {
2530             this._extensions[lowerName] = name;
2531             for (var property in obj) {
2532                 if (TypeUtils.isEnumPropertyName(property, obj))
2533                     this._extensionEnums[property] = /** @type {number} */ (obj[property]);
2534             }
2535         }
2536     },
2537
2538     /**
2539      * @param {string} name
2540      * @return {number|undefined}
2541      */
2542     _enumValueForName: function(name)
2543     {
2544         if (typeof this._extensionEnums[name] === "number")
2545             return this._extensionEnums[name];
2546         var gl = this.wrappedObject();
2547         return (typeof gl[name] === "number" ? gl[name] : undefined);
2548     },
2549
2550     /**
2551      * @param {function(this:WebGLRenderingContext, T, number):*} func
2552      * @param {T} targetOrWebGLObject
2553      * @param {!Array.<string>} pnames
2554      * @param {!Array.<!TypeUtils.InternalResourceStateDescriptor>} output
2555      * @template T
2556      */
2557     queryStateValues: function(func, targetOrWebGLObject, pnames, output)
2558     {
2559         var gl = this.wrappedObject();
2560         for (var i = 0, pname; pname = pnames[i]; ++i) {
2561             var enumValue = this._enumValueForName(pname);
2562             if (typeof enumValue !== "number")
2563                 continue;
2564             var value = func.call(gl, targetOrWebGLObject, enumValue);
2565             value = Resource.forObject(value) || value;
2566             output.push({ name: pname, value: value, valueIsEnum: WebGLRenderingContextResource.GetResultIsEnum[pname] });
2567         }
2568     },
2569
2570     /**
2571      * @override
2572      * @return {!Array.<!TypeUtils.InternalResourceStateDescriptor>}
2573      */
2574     currentState: function()
2575     {
2576         /**
2577          * @param {!Object} obj
2578          * @param {!Array.<!TypeUtils.InternalResourceStateDescriptor>} output
2579          */
2580         function convertToStateDescriptors(obj, output)
2581         {
2582             for (var pname in obj)
2583                 output.push({ name: pname, value: obj[pname], valueIsEnum: WebGLRenderingContextResource.GetResultIsEnum[pname] });
2584         }
2585
2586         var gl = this.wrappedObject();
2587         var glState = this._internalCurrentState(null);
2588
2589         // VERTEX_ATTRIB_ARRAYS
2590         var vertexAttribStates = [];
2591         for (var i = 0, n = glState.VERTEX_ATTRIB_ARRAYS.length; i < n; ++i) {
2592             var pname = "" + i;
2593             var values = [];
2594             convertToStateDescriptors(glState.VERTEX_ATTRIB_ARRAYS[i], values);
2595             vertexAttribStates.push({ name: pname, values: values });
2596         }
2597         delete glState.VERTEX_ATTRIB_ARRAYS;
2598
2599         // TEXTURE_UNITS
2600         var textureUnits = [];
2601         for (var i = 0, n = glState.TEXTURE_UNITS.length; i < n; ++i) {
2602             var pname = "TEXTURE" + i;
2603             var values = [];
2604             convertToStateDescriptors(glState.TEXTURE_UNITS[i], values);
2605             textureUnits.push({ name: pname, values: values });
2606         }
2607         delete glState.TEXTURE_UNITS;
2608
2609         var result = [];
2610         convertToStateDescriptors(glState, result);
2611         result.push({ name: "VERTEX_ATTRIB_ARRAYS", values: vertexAttribStates, isArray: true });
2612         result.push({ name: "TEXTURE_UNITS", values: textureUnits, isArray: true });
2613
2614         var textureBindingParameters = ["TEXTURE_BINDING_2D", "TEXTURE_BINDING_CUBE_MAP"];
2615         for (var i = 0, pname; pname = textureBindingParameters[i]; ++i) {
2616             var value = gl.getParameter(gl[pname]);
2617             value = Resource.forObject(value) || value;
2618             result.push({ name: pname, value: value });
2619         }
2620
2621         // ENABLED_EXTENSIONS
2622         var enabledExtensions = [];
2623         for (var lowerName in this._extensions) {
2624             var pname = this._extensions[lowerName];
2625             var value = gl.getExtension(pname);
2626             value = Resource.forObject(value) || value;
2627             enabledExtensions.push({ name: pname, value: value });
2628         }
2629         result.push({ name: "ENABLED_EXTENSIONS", values: enabledExtensions, isArray: true });
2630
2631         return result;
2632     },
2633
2634     /**
2635      * @param {?Cache.<!ReplayableResource>} cache
2636      * @return {!Object.<string, *>}
2637      */
2638     _internalCurrentState: function(cache)
2639     {
2640         /**
2641          * @param {!Resource|*} obj
2642          * @return {!Resource|!ReplayableResource|*}
2643          */
2644         function maybeToReplayable(obj)
2645         {
2646             return cache ? Resource.toReplayable(obj, cache) : (Resource.forObject(obj) || obj);
2647         }
2648
2649         var gl = this.wrappedObject();
2650         var originalErrors = this.getAllErrors();
2651
2652         // Take a full GL state snapshot.
2653         var glState = Object.create(null);
2654         WebGLRenderingContextResource.GLCapabilities.forEach(function(parameter) {
2655             glState[parameter] = gl.isEnabled(gl[parameter]);
2656         });
2657         for (var i = 0, pname; pname = WebGLRenderingContextResource.StateParameters[i]; ++i) {
2658             var enumValue = this._enumValueForName(pname);
2659             if (typeof enumValue === "number")
2660                 glState[pname] = maybeToReplayable(gl.getParameter(enumValue));
2661         }
2662
2663         // VERTEX_ATTRIB_ARRAYS
2664         var maxVertexAttribs = /** @type {number} */ (gl.getParameter(gl.MAX_VERTEX_ATTRIBS));
2665         var vertexAttribParameters = [
2666             "VERTEX_ATTRIB_ARRAY_BUFFER_BINDING",
2667             "VERTEX_ATTRIB_ARRAY_ENABLED",
2668             "VERTEX_ATTRIB_ARRAY_SIZE",
2669             "VERTEX_ATTRIB_ARRAY_STRIDE",
2670             "VERTEX_ATTRIB_ARRAY_TYPE",
2671             "VERTEX_ATTRIB_ARRAY_NORMALIZED",
2672             "CURRENT_VERTEX_ATTRIB",
2673             "VERTEX_ATTRIB_ARRAY_DIVISOR_ANGLE" // ANGLE_instanced_arrays extension
2674         ];
2675         var vertexAttribStates = [];
2676         for (var index = 0; index < maxVertexAttribs; ++index) {
2677             var state = Object.create(null);
2678             for (var i = 0, pname; pname = vertexAttribParameters[i]; ++i) {
2679                 var enumValue = this._enumValueForName(pname);
2680                 if (typeof enumValue === "number")
2681                     state[pname] = maybeToReplayable(gl.getVertexAttrib(index, enumValue));
2682             }
2683             state.VERTEX_ATTRIB_ARRAY_POINTER = gl.getVertexAttribOffset(index, gl.VERTEX_ATTRIB_ARRAY_POINTER);
2684             vertexAttribStates.push(state);
2685         }
2686         glState.VERTEX_ATTRIB_ARRAYS = vertexAttribStates;
2687
2688         // TEXTURE_UNITS
2689         var savedActiveTexture = /** @type {number} */ (gl.getParameter(gl.ACTIVE_TEXTURE));
2690         var maxTextureImageUnits = /** @type {number} */ (gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS));
2691         var textureUnits = [];
2692         for (var i = 0; i < maxTextureImageUnits; ++i) {
2693             gl.activeTexture(gl.TEXTURE0 + i);
2694             var state = Object.create(null);
2695             state.TEXTURE_2D = maybeToReplayable(gl.getParameter(gl.TEXTURE_BINDING_2D));
2696             state.TEXTURE_CUBE_MAP = maybeToReplayable(gl.getParameter(gl.TEXTURE_BINDING_CUBE_MAP));
2697             textureUnits.push(state);
2698         }
2699         glState.TEXTURE_UNITS = textureUnits;
2700         gl.activeTexture(savedActiveTexture);
2701
2702         this.restoreErrors(originalErrors);
2703         return glState;
2704     },
2705
2706     /**
2707      * @override
2708      * @param {!Object} data
2709      * @param {!Cache.<!ReplayableResource>} cache
2710      */
2711     _populateReplayableData: function(data, cache)
2712     {
2713         var gl = this.wrappedObject();
2714         data.originalCanvas = gl.canvas;
2715         data.originalContextAttributes = gl.getContextAttributes();
2716         data.extensions = TypeUtils.cloneObject(this._extensions);
2717         data.extensionEnums = TypeUtils.cloneObject(this._extensionEnums);
2718         data.glState = this._internalCurrentState(cache);
2719     },
2720
2721     /**
2722      * @override
2723      * @param {!Object} data
2724      * @param {!Cache.<!Resource>} cache
2725      */
2726     _doReplayCalls: function(data, cache)
2727     {
2728         this._customErrors = null;
2729         this._extensions = TypeUtils.cloneObject(data.extensions) || {};
2730         this._extensionEnums = TypeUtils.cloneObject(data.extensionEnums) || {};
2731
2732         var canvas = data.originalCanvas.cloneNode(true);
2733         var replayContext = null;
2734         var contextIds = ["experimental-webgl", "webkit-3d", "3d"];
2735         for (var i = 0, contextId; contextId = contextIds[i]; ++i) {
2736             replayContext = canvas.getContext(contextId, data.originalContextAttributes);
2737             if (replayContext)
2738                 break;
2739         }
2740
2741         console.assert(replayContext, "Failed to create a WebGLRenderingContext for the replay.");
2742
2743         var gl = /** @type {!WebGLRenderingContext} */ (Resource.wrappedObject(replayContext));
2744         this.setWrappedObject(gl);
2745
2746         // Enable corresponding WebGL extensions.
2747         for (var name in this._extensions)
2748             gl.getExtension(name);
2749
2750         var glState = data.glState;
2751         gl.bindFramebuffer(gl.FRAMEBUFFER, /** @type {!WebGLFramebuffer} */ (ReplayableResource.replay(glState.FRAMEBUFFER_BINDING, cache)));
2752         gl.bindRenderbuffer(gl.RENDERBUFFER, /** @type {!WebGLRenderbuffer} */ (ReplayableResource.replay(glState.RENDERBUFFER_BINDING, cache)));
2753
2754         // Enable or disable server-side GL capabilities.
2755         WebGLRenderingContextResource.GLCapabilities.forEach(function(parameter) {
2756             console.assert(parameter in glState);
2757             if (glState[parameter])
2758                 gl.enable(gl[parameter]);
2759             else
2760                 gl.disable(gl[parameter]);
2761         });
2762
2763         gl.blendColor(glState.BLEND_COLOR[0], glState.BLEND_COLOR[1], glState.BLEND_COLOR[2], glState.BLEND_COLOR[3]);
2764         gl.blendEquationSeparate(glState.BLEND_EQUATION_RGB, glState.BLEND_EQUATION_ALPHA);
2765         gl.blendFuncSeparate(glState.BLEND_SRC_RGB, glState.BLEND_DST_RGB, glState.BLEND_SRC_ALPHA, glState.BLEND_DST_ALPHA);
2766         gl.clearColor(glState.COLOR_CLEAR_VALUE[0], glState.COLOR_CLEAR_VALUE[1], glState.COLOR_CLEAR_VALUE[2], glState.COLOR_CLEAR_VALUE[3]);
2767         gl.clearDepth(glState.DEPTH_CLEAR_VALUE);
2768         gl.clearStencil(glState.STENCIL_CLEAR_VALUE);
2769         gl.colorMask(glState.COLOR_WRITEMASK[0], glState.COLOR_WRITEMASK[1], glState.COLOR_WRITEMASK[2], glState.COLOR_WRITEMASK[3]);
2770         gl.cullFace(glState.CULL_FACE_MODE);
2771         gl.depthFunc(glState.DEPTH_FUNC);
2772         gl.depthMask(glState.DEPTH_WRITEMASK);
2773         gl.depthRange(glState.DEPTH_RANGE[0], glState.DEPTH_RANGE[1]);
2774         gl.frontFace(glState.FRONT_FACE);
2775         gl.hint(gl.GENERATE_MIPMAP_HINT, glState.GENERATE_MIPMAP_HINT);
2776         gl.lineWidth(glState.LINE_WIDTH);
2777
2778         var enumValue = this._enumValueForName("FRAGMENT_SHADER_DERIVATIVE_HINT_OES");
2779         if (typeof enumValue === "number")
2780             gl.hint(enumValue, glState.FRAGMENT_SHADER_DERIVATIVE_HINT_OES);
2781
2782         WebGLRenderingContextResource.PixelStoreParameters.forEach(function(parameter) {
2783             gl.pixelStorei(gl[parameter], glState[parameter]);
2784         });
2785
2786         gl.polygonOffset(glState.POLYGON_OFFSET_FACTOR, glState.POLYGON_OFFSET_UNITS);
2787         gl.sampleCoverage(glState.SAMPLE_COVERAGE_VALUE, glState.SAMPLE_COVERAGE_INVERT);
2788         gl.stencilFuncSeparate(gl.FRONT, glState.STENCIL_FUNC, glState.STENCIL_REF, glState.STENCIL_VALUE_MASK);
2789         gl.stencilFuncSeparate(gl.BACK, glState.STENCIL_BACK_FUNC, glState.STENCIL_BACK_REF, glState.STENCIL_BACK_VALUE_MASK);
2790         gl.stencilOpSeparate(gl.FRONT, glState.STENCIL_FAIL, glState.STENCIL_PASS_DEPTH_FAIL, glState.STENCIL_PASS_DEPTH_PASS);
2791         gl.stencilOpSeparate(gl.BACK, glState.STENCIL_BACK_FAIL, glState.STENCIL_BACK_PASS_DEPTH_FAIL, glState.STENCIL_BACK_PASS_DEPTH_PASS);
2792         gl.stencilMaskSeparate(gl.FRONT, glState.STENCIL_WRITEMASK);
2793         gl.stencilMaskSeparate(gl.BACK, glState.STENCIL_BACK_WRITEMASK);
2794
2795         gl.scissor(glState.SCISSOR_BOX[0], glState.SCISSOR_BOX[1], glState.SCISSOR_BOX[2], glState.SCISSOR_BOX[3]);
2796         gl.viewport(glState.VIEWPORT[0], glState.VIEWPORT[1], glState.VIEWPORT[2], glState.VIEWPORT[3]);
2797
2798         gl.useProgram(/** @type {!WebGLProgram} */ (ReplayableResource.replay(glState.CURRENT_PROGRAM, cache)));
2799
2800         // VERTEX_ATTRIB_ARRAYS
2801         var maxVertexAttribs = /** @type {number} */ (gl.getParameter(gl.MAX_VERTEX_ATTRIBS));
2802         for (var i = 0; i < maxVertexAttribs; ++i) {
2803             var state = glState.VERTEX_ATTRIB_ARRAYS[i] || {};
2804             if (state.VERTEX_ATTRIB_ARRAY_ENABLED)
2805                 gl.enableVertexAttribArray(i);
2806             else
2807                 gl.disableVertexAttribArray(i);
2808             if (state.CURRENT_VERTEX_ATTRIB)
2809                 gl.vertexAttrib4fv(i, state.CURRENT_VERTEX_ATTRIB);
2810             var buffer = /** @type {!WebGLBuffer} */ (ReplayableResource.replay(state.VERTEX_ATTRIB_ARRAY_BUFFER_BINDING, cache));
2811             if (buffer) {
2812                 gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
2813                 gl.vertexAttribPointer(i, state.VERTEX_ATTRIB_ARRAY_SIZE, state.VERTEX_ATTRIB_ARRAY_TYPE, state.VERTEX_ATTRIB_ARRAY_NORMALIZED, state.VERTEX_ATTRIB_ARRAY_STRIDE, state.VERTEX_ATTRIB_ARRAY_POINTER);
2814             }
2815         }
2816         gl.bindBuffer(gl.ARRAY_BUFFER, /** @type {!WebGLBuffer} */ (ReplayableResource.replay(glState.ARRAY_BUFFER_BINDING, cache)));
2817         gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, /** @type {!WebGLBuffer} */ (ReplayableResource.replay(glState.ELEMENT_ARRAY_BUFFER_BINDING, cache)));
2818
2819         // TEXTURE_UNITS
2820         var maxTextureImageUnits = /** @type {number} */ (gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS));
2821         for (var i = 0; i < maxTextureImageUnits; ++i) {
2822             gl.activeTexture(gl.TEXTURE0 + i);
2823             var state = glState.TEXTURE_UNITS[i] || {};
2824             gl.bindTexture(gl.TEXTURE_2D, /** @type {!WebGLTexture} */ (ReplayableResource.replay(state.TEXTURE_2D, cache)));
2825             gl.bindTexture(gl.TEXTURE_CUBE_MAP, /** @type {!WebGLTexture} */ (ReplayableResource.replay(state.TEXTURE_CUBE_MAP, cache)));
2826         }
2827         gl.activeTexture(glState.ACTIVE_TEXTURE);
2828
2829         ContextResource.prototype._doReplayCalls.call(this, data, cache);
2830     },
2831
2832     /**
2833      * @param {!Object|number} target
2834      * @return {?Resource}
2835      */
2836     currentBinding: function(target)
2837     {
2838         var resource = Resource.forObject(target);
2839         if (resource)
2840             return resource;
2841         var gl = this.wrappedObject();
2842         var bindingParameter;
2843         var bindMethodName;
2844         target = +target; // Explicitly convert to a number.
2845         var bindMethodTarget = target;
2846         switch (target) {
2847         case gl.ARRAY_BUFFER:
2848             bindingParameter = gl.ARRAY_BUFFER_BINDING;
2849             bindMethodName = "bindBuffer";
2850             break;
2851         case gl.ELEMENT_ARRAY_BUFFER:
2852             bindingParameter = gl.ELEMENT_ARRAY_BUFFER_BINDING;
2853             bindMethodName = "bindBuffer";
2854             break;
2855         case gl.TEXTURE_2D:
2856             bindingParameter = gl.TEXTURE_BINDING_2D;
2857             bindMethodName = "bindTexture";
2858             break;
2859         case gl.TEXTURE_CUBE_MAP:
2860         case gl.TEXTURE_CUBE_MAP_POSITIVE_X:
2861         case gl.TEXTURE_CUBE_MAP_NEGATIVE_X:
2862         case gl.TEXTURE_CUBE_MAP_POSITIVE_Y:
2863         case gl.TEXTURE_CUBE_MAP_NEGATIVE_Y:
2864         case gl.TEXTURE_CUBE_MAP_POSITIVE_Z:
2865         case gl.TEXTURE_CUBE_MAP_NEGATIVE_Z:
2866             bindingParameter = gl.TEXTURE_BINDING_CUBE_MAP;
2867             bindMethodTarget = gl.TEXTURE_CUBE_MAP;
2868             bindMethodName = "bindTexture";
2869             break;
2870         case gl.FRAMEBUFFER:
2871             bindingParameter = gl.FRAMEBUFFER_BINDING;
2872             bindMethodName = "bindFramebuffer";
2873             break;
2874         case gl.RENDERBUFFER:
2875             bindingParameter = gl.RENDERBUFFER_BINDING;
2876             bindMethodName = "bindRenderbuffer";
2877             break;
2878         default:
2879             console.error("ASSERT_NOT_REACHED: unknown binding target " + target);
2880             return null;
2881         }
2882         resource = Resource.forObject(gl.getParameter(bindingParameter));
2883         if (resource)
2884             resource.pushBinding(bindMethodTarget, bindMethodName);
2885         return resource;
2886     },
2887
2888     /**
2889      * @override
2890      * @param {!Call} call
2891      */
2892     onCallReplayed: function(call)
2893     {
2894         var functionName = call.functionName();
2895         var args = call.args();
2896         switch (functionName) {
2897         case "bindBuffer":
2898         case "bindFramebuffer":
2899         case "bindRenderbuffer":
2900         case "bindTexture":
2901             // Update BINDING state for Resources in the replay world.
2902             var resource = Resource.forObject(args[1]);
2903             if (resource)
2904                 resource.pushBinding(args[0], functionName);
2905             break;
2906         case "getExtension":
2907             this.registerWebGLExtension(args[0], /** @type {!Object} */ (call.result()));
2908             break;
2909         case "bufferData":
2910             var resource = /** @type {!WebGLBufferResource} */ (this.currentBinding(args[0]));
2911             if (resource)
2912                 resource.pushCall_bufferData(call);
2913             break;
2914         case "bufferSubData":
2915             var resource = /** @type {!WebGLBufferResource} */ (this.currentBinding(args[0]));
2916             if (resource)
2917                 resource.pushCall_bufferSubData(call);
2918             break;
2919         }
2920     },
2921
2922     /**
2923      * @override
2924      * @return {!Object.<string, !Function>}
2925      */
2926     _customWrapFunctions: function()
2927     {
2928         var wrapFunctions = WebGLRenderingContextResource._wrapFunctions;
2929         if (!wrapFunctions) {
2930             wrapFunctions = Object.create(null);
2931
2932             wrapFunctions["createBuffer"] = Resource.WrapFunction.resourceFactoryMethod(WebGLBufferResource, "WebGLBuffer");
2933             wrapFunctions["createShader"] = Resource.WrapFunction.resourceFactoryMethod(WebGLShaderResource, "WebGLShader");
2934             wrapFunctions["createProgram"] = Resource.WrapFunction.resourceFactoryMethod(WebGLProgramResource, "WebGLProgram");
2935             wrapFunctions["createTexture"] = Resource.WrapFunction.resourceFactoryMethod(WebGLTextureResource, "WebGLTexture");
2936             wrapFunctions["createFramebuffer"] = Resource.WrapFunction.resourceFactoryMethod(WebGLFramebufferResource, "WebGLFramebuffer");
2937             wrapFunctions["createRenderbuffer"] = Resource.WrapFunction.resourceFactoryMethod(WebGLRenderbufferResource, "WebGLRenderbuffer");
2938             wrapFunctions["getUniformLocation"] = Resource.WrapFunction.resourceFactoryMethod(WebGLUniformLocationResource, "WebGLUniformLocation");
2939
2940             stateModifyingWrapFunction("bindAttribLocation");
2941             stateModifyingWrapFunction("compileShader");
2942             stateModifyingWrapFunction("detachShader");
2943             stateModifyingWrapFunction("linkProgram");
2944             stateModifyingWrapFunction("shaderSource");
2945             stateModifyingWrapFunction("bufferData", WebGLBufferResource.prototype.pushCall_bufferData);
2946             stateModifyingWrapFunction("bufferSubData", WebGLBufferResource.prototype.pushCall_bufferSubData);
2947             stateModifyingWrapFunction("compressedTexImage2D");
2948             stateModifyingWrapFunction("compressedTexSubImage2D");
2949             stateModifyingWrapFunction("copyTexImage2D", WebGLTextureResource.prototype.pushCall_copyTexImage2D);
2950             stateModifyingWrapFunction("copyTexSubImage2D", WebGLTextureResource.prototype.pushCall_copyTexImage2D);
2951             stateModifyingWrapFunction("generateMipmap");
2952             stateModifyingWrapFunction("texImage2D");
2953             stateModifyingWrapFunction("texSubImage2D");
2954             stateModifyingWrapFunction("texParameterf", WebGLTextureResource.prototype.pushCall_texParameter);
2955             stateModifyingWrapFunction("texParameteri", WebGLTextureResource.prototype.pushCall_texParameter);
2956             stateModifyingWrapFunction("renderbufferStorage");
2957
2958             /** @this {Resource.WrapFunction} */
2959             wrapFunctions["getError"] = function()
2960             {
2961                 var gl = /** @type {!WebGLRenderingContext} */ (this._originalObject);
2962                 var error = this.result();
2963                 if (error !== gl.NO_ERROR)
2964                     this._resource.clearError(error);
2965                 else {
2966                     error = this._resource.nextError();
2967                     if (error !== gl.NO_ERROR)
2968                         this.overrideResult(error);
2969                 }
2970             }
2971
2972             /**
2973              * @param {string} name
2974              * @this {Resource.WrapFunction}
2975              */
2976             wrapFunctions["getExtension"] = function(name)
2977             {
2978                 this._resource.registerWebGLExtension(name, this.result());
2979             }
2980
2981             //
2982             // Register bound WebGL resources.
2983             //
2984
2985             /**
2986              * @param {!WebGLProgram} program
2987              * @param {!WebGLShader} shader
2988              * @this {Resource.WrapFunction}
2989              */
2990             wrapFunctions["attachShader"] = function(program, shader)
2991             {
2992                 var resource = this._resource.currentBinding(program);
2993                 if (resource) {
2994                     resource.pushCall(this.call());
2995                     var shaderResource = /** @type {!WebGLShaderResource} */ (Resource.forObject(shader));
2996                     if (shaderResource) {
2997                         var shaderType = shaderResource.type();
2998                         resource._registerBoundResource("__attachShader_" + shaderType, shaderResource);
2999                     }
3000                 }
3001             }
3002             /**
3003              * @param {number} target
3004              * @param {number} attachment
3005              * @param {number} objectTarget
3006              * @param {!WebGLRenderbuffer|!WebGLTexture} obj
3007              * @this {Resource.WrapFunction}
3008              */
3009             wrapFunctions["framebufferRenderbuffer"] = wrapFunctions["framebufferTexture2D"] = function(target, attachment, objectTarget, obj)
3010             {
3011                 var resource = this._resource.currentBinding(target);
3012                 if (resource) {
3013                     resource.pushCall(this.call());
3014                     resource._registerBoundResource("__framebufferAttachmentObjectName", obj);
3015                 }
3016             }
3017             /**
3018              * @param {number} target
3019              * @param {!Object} obj
3020              * @this {Resource.WrapFunction}
3021              */
3022             wrapFunctions["bindBuffer"] = wrapFunctions["bindFramebuffer"] = wrapFunctions["bindRenderbuffer"] = function(target, obj)
3023             {
3024                 this._resource.currentBinding(target); // To call WebGLBoundResource.prototype.pushBinding().
3025                 this._resource._registerBoundResource("__bindBuffer_" + target, obj);
3026             }
3027             /**
3028              * @param {number} target
3029              * @param {!WebGLTexture} obj
3030              * @this {Resource.WrapFunction}
3031              */
3032             wrapFunctions["bindTexture"] = function(target, obj)
3033             {
3034                 this._resource.currentBinding(target); // To call WebGLBoundResource.prototype.pushBinding().
3035                 var gl = /** @type {!WebGLRenderingContext} */ (this._originalObject);
3036                 var currentTextureBinding = /** @type {number} */ (gl.getParameter(gl.ACTIVE_TEXTURE));
3037                 this._resource._registerBoundResource("__bindTexture_" + target + "_" + currentTextureBinding, obj);
3038             }
3039             /**
3040              * @param {!WebGLProgram} program
3041              * @this {Resource.WrapFunction}
3042              */
3043             wrapFunctions["useProgram"] = function(program)
3044             {
3045                 this._resource._registerBoundResource("__useProgram", program);
3046             }
3047             /**
3048              * @param {number} index
3049              * @this {Resource.WrapFunction}
3050              */
3051             wrapFunctions["vertexAttribPointer"] = function(index)
3052             {
3053                 var gl = /** @type {!WebGLRenderingContext} */ (this._originalObject);
3054                 this._resource._registerBoundResource("__vertexAttribPointer_" + index, gl.getParameter(gl.ARRAY_BUFFER_BINDING));
3055             }
3056
3057             WebGLRenderingContextResource._wrapFunctions = wrapFunctions;
3058         }
3059
3060         /**
3061          * @param {string} methodName
3062          * @param {function(this:Resource, !Call)=} pushCallFunc
3063          */
3064         function stateModifyingWrapFunction(methodName, pushCallFunc)
3065         {
3066             if (pushCallFunc) {
3067                 /**
3068                  * @param {!Object|number} target
3069                  * @this {Resource.WrapFunction}
3070                  */
3071                 wrapFunctions[methodName] = function(target)
3072                 {
3073                     var resource = this._resource.currentBinding(target);
3074                     if (resource)
3075                         pushCallFunc.call(resource, this.call());
3076                 }
3077             } else {
3078                 /**
3079                  * @param {!Object|number} target
3080                  * @this {Resource.WrapFunction}
3081                  */
3082                 wrapFunctions[methodName] = function(target)
3083                 {
3084                     var resource = this._resource.currentBinding(target);
3085                     if (resource)
3086                         resource.pushCall(this.call());
3087                 }
3088             }
3089         }
3090
3091         return wrapFunctions;
3092     },
3093
3094     __proto__: ContextResource.prototype
3095 }
3096
3097 ////////////////////////////////////////////////////////////////////////////////
3098 // 2D Canvas
3099 ////////////////////////////////////////////////////////////////////////////////
3100
3101 /**
3102  * @constructor
3103  * @extends {ContextResource}
3104  * @param {!CanvasRenderingContext2D} context
3105  */
3106 function CanvasRenderingContext2DResource(context)
3107 {
3108     ContextResource.call(this, context, "CanvasRenderingContext2D");
3109 }
3110
3111 /**
3112  * @const
3113  * @type {!Array.<string>}
3114  */
3115 CanvasRenderingContext2DResource.AttributeProperties = [
3116     "strokeStyle",
3117     "fillStyle",
3118     "globalAlpha",
3119     "lineWidth",
3120     "lineCap",
3121     "lineJoin",
3122     "miterLimit",
3123     "shadowOffsetX",
3124     "shadowOffsetY",
3125     "shadowBlur",
3126     "shadowColor",
3127     "globalCompositeOperation",
3128     "font",
3129     "textAlign",
3130     "textBaseline",
3131     "lineDashOffset",
3132     "imageSmoothingEnabled",
3133     "webkitLineDash",
3134     "webkitLineDashOffset"
3135 ];
3136
3137 /**
3138  * @const
3139  * @type {!Array.<string>}
3140  */
3141 CanvasRenderingContext2DResource.PathMethods = [
3142     "beginPath",
3143     "moveTo",
3144     "closePath",
3145     "lineTo",
3146     "quadraticCurveTo",
3147     "bezierCurveTo",
3148     "arcTo",
3149     "arc",
3150     "rect"
3151 ];
3152
3153 /**
3154  * @const
3155  * @type {!Array.<string>}
3156  */
3157 CanvasRenderingContext2DResource.TransformationMatrixMethods = [
3158     "scale",
3159     "rotate",
3160     "translate",
3161     "transform",
3162     "setTransform"
3163 ];
3164
3165 /**
3166  * @const
3167  * @type {!Object.<string, boolean>}
3168  */
3169 CanvasRenderingContext2DResource.DrawingMethods = TypeUtils.createPrefixedPropertyNamesSet([
3170     "clearRect",
3171     "drawImage",
3172     "drawImageFromRect",
3173     "drawCustomFocusRing",
3174     "drawFocusIfNeeded",
3175     "fill",
3176     "fillRect",
3177     "fillText",
3178     "putImageData",
3179     "putImageDataHD",
3180     "stroke",
3181     "strokeRect",
3182     "strokeText"
3183 ]);
3184
3185 CanvasRenderingContext2DResource.prototype = {
3186     /**
3187      * @override (overrides @return type)
3188      * @return {!CanvasRenderingContext2D}
3189      */
3190     wrappedObject: function()
3191     {
3192         return this._wrappedObject;
3193     },
3194
3195     /**
3196      * @override
3197      * @return {string}
3198      */
3199     toDataURL: function()
3200     {
3201         return this.wrappedObject().canvas.toDataURL();
3202     },
3203
3204     /**
3205      * @override
3206      * @return {!Array.<!TypeUtils.InternalResourceStateDescriptor>}
3207      */
3208     currentState: function()
3209     {
3210         var result = [];
3211         var state = this._internalCurrentState(null);
3212         for (var pname in state)
3213             result.push({ name: pname, value: state[pname] });
3214         result.push({ name: "context", value: this.contextResource() });
3215         return result;
3216     },
3217
3218     /**
3219      * @param {?Cache.<!ReplayableResource>} cache
3220      * @return {!Object.<string, *>}
3221      */
3222     _internalCurrentState: function(cache)
3223     {
3224         /**
3225          * @param {!Resource|*} obj
3226          * @return {!Resource|!ReplayableResource|*}
3227          */
3228         function maybeToReplayable(obj)
3229         {
3230             return cache ? Resource.toReplayable(obj, cache) : (Resource.forObject(obj) || obj);
3231         }
3232
3233         var ctx = this.wrappedObject();
3234         var state = Object.create(null);
3235         CanvasRenderingContext2DResource.AttributeProperties.forEach(function(attribute) {
3236             if (attribute in ctx)
3237                 state[attribute] = maybeToReplayable(ctx[attribute]);
3238         });
3239         if (ctx.getLineDash)
3240             state.lineDash = ctx.getLineDash();
3241         return state;
3242     },
3243
3244     /**
3245      * @param {?Object.<string, *>} state
3246      * @param {!Cache.<!Resource>} cache
3247      */
3248     _applyAttributesState: function(state, cache)
3249     {
3250         if (!state)
3251             return;
3252         var ctx = this.wrappedObject();
3253         for (var attribute in state) {
3254             if (attribute === "lineDash") {
3255                 if (ctx.setLineDash)
3256                     ctx.setLineDash(/** @type {!Array.<number>} */ (state[attribute]));
3257             } else
3258                 ctx[attribute] = ReplayableResource.replay(state[attribute], cache);
3259         }
3260     },
3261
3262     /**
3263      * @override
3264      * @param {!Object} data
3265      * @param {!Cache.<!ReplayableResource>} cache
3266      */
3267     _populateReplayableData: function(data, cache)
3268     {
3269         var ctx = this.wrappedObject();
3270         // FIXME: Convert resources in the state (CanvasGradient, CanvasPattern) to Replayable.
3271         data.currentAttributes = this._internalCurrentState(null);
3272         data.originalCanvasCloned = TypeUtils.cloneIntoCanvas(/** @type {!HTMLCanvasElement} */ (ctx.canvas));
3273         if (ctx.getContextAttributes)
3274             data.originalContextAttributes = ctx.getContextAttributes();
3275     },
3276
3277     /**
3278      * @override
3279      * @param {!Object} data
3280      * @param {!Cache.<!Resource>} cache
3281      */
3282     _doReplayCalls: function(data, cache)
3283     {
3284         var canvas = TypeUtils.cloneIntoCanvas(data.originalCanvasCloned);
3285         var ctx = /** @type {!CanvasRenderingContext2D} */ (Resource.wrappedObject(canvas.getContext("2d", data.originalContextAttributes)));
3286         this.setWrappedObject(ctx);
3287
3288         for (var i = 0, n = data.calls.length; i < n; ++i) {
3289             var replayableCall = /** @type {!ReplayableCall} */ (data.calls[i]);
3290             if (replayableCall.functionName() === "save")
3291                 this._applyAttributesState(replayableCall.attachment("canvas2dAttributesState"), cache);
3292             this._calls.push(replayableCall.replay(cache));
3293         }
3294         this._applyAttributesState(data.currentAttributes, cache);
3295     },
3296
3297     /**
3298      * @param {!Call} call
3299      */
3300     pushCall_setTransform: function(call)
3301     {
3302         var saveCallIndex = this._lastIndexOfMatchingSaveCall();
3303         var index = this._lastIndexOfAnyCall(CanvasRenderingContext2DResource.PathMethods);
3304         index = Math.max(index, saveCallIndex);
3305         if (this._removeCallsFromLog(CanvasRenderingContext2DResource.TransformationMatrixMethods, index + 1))
3306             this._removeAllObsoleteCallsFromLog();
3307         this.pushCall(call);
3308     },
3309
3310     /**
3311      * @param {!Call} call
3312      */
3313     pushCall_beginPath: function(call)
3314     {
3315         var index = this._lastIndexOfAnyCall(["clip"]);
3316         if (this._removeCallsFromLog(CanvasRenderingContext2DResource.PathMethods, index + 1))
3317             this._removeAllObsoleteCallsFromLog();
3318         this.pushCall(call);
3319     },
3320
3321     /**
3322      * @param {!Call} call
3323      */
3324     pushCall_save: function(call)
3325     {
3326         // FIXME: Convert resources in the state (CanvasGradient, CanvasPattern) to Replayable.
3327         call.setAttachment("canvas2dAttributesState", this._internalCurrentState(null));
3328         this.pushCall(call);
3329     },
3330
3331     /**
3332      * @param {!Call} call
3333      */
3334     pushCall_restore: function(call)
3335     {
3336         var lastIndexOfSave = this._lastIndexOfMatchingSaveCall();
3337         if (lastIndexOfSave === -1)
3338             return;
3339         this._calls[lastIndexOfSave].setAttachment("canvas2dAttributesState", null); // No longer needed, free memory.
3340
3341         var modified = false;
3342         if (this._removeCallsFromLog(["clip"], lastIndexOfSave + 1))
3343             modified = true;
3344
3345         var lastIndexOfAnyPathMethod = this._lastIndexOfAnyCall(CanvasRenderingContext2DResource.PathMethods);
3346         var index = Math.max(lastIndexOfSave, lastIndexOfAnyPathMethod);
3347         if (this._removeCallsFromLog(CanvasRenderingContext2DResource.TransformationMatrixMethods, index + 1))
3348             modified = true;
3349
3350         if (modified)
3351             this._removeAllObsoleteCallsFromLog();
3352
3353         var lastCall = this._calls[this._calls.length - 1];
3354         if (lastCall && lastCall.functionName() === "save")
3355             this._calls.pop();
3356         else
3357             this.pushCall(call);
3358     },
3359
3360     /**
3361      * @param {number=} fromIndex
3362      * @return {number}
3363      */
3364     _lastIndexOfMatchingSaveCall: function(fromIndex)
3365     {
3366         if (typeof fromIndex !== "number")
3367             fromIndex = this._calls.length - 1;
3368         else
3369             fromIndex = Math.min(fromIndex, this._calls.length - 1);
3370         var stackDepth = 1;
3371         for (var i = fromIndex; i >= 0; --i) {
3372             var functionName = this._calls[i].functionName();
3373             if (functionName === "restore")
3374                 ++stackDepth;
3375             else if (functionName === "save") {
3376                 --stackDepth;
3377                 if (!stackDepth)
3378                     return i;
3379             }
3380         }
3381         return -1;
3382     },
3383
3384     /**
3385      * @param {!Array.<string>} functionNames
3386      * @param {number=} fromIndex
3387      * @return {number}
3388      */
3389     _lastIndexOfAnyCall: function(functionNames, fromIndex)
3390     {
3391         if (typeof fromIndex !== "number")
3392             fromIndex = this._calls.length - 1;
3393         else
3394             fromIndex = Math.min(fromIndex, this._calls.length - 1);
3395         for (var i = fromIndex; i >= 0; --i) {
3396             if (functionNames.indexOf(this._calls[i].functionName()) !== -1)
3397                 return i;
3398         }
3399         return -1;
3400     },
3401
3402     _removeAllObsoleteCallsFromLog: function()
3403     {
3404         // Remove all PATH methods between clip() and beginPath() calls.
3405         var lastIndexOfBeginPath = this._lastIndexOfAnyCall(["beginPath"]);
3406         while (lastIndexOfBeginPath !== -1) {
3407             var index = this._lastIndexOfAnyCall(["clip"], lastIndexOfBeginPath - 1);
3408             this._removeCallsFromLog(CanvasRenderingContext2DResource.PathMethods, index + 1, lastIndexOfBeginPath);
3409             lastIndexOfBeginPath = this._lastIndexOfAnyCall(["beginPath"], index - 1);
3410         }
3411
3412         // Remove all TRASFORMATION MATRIX methods before restore() or setTransform() but after any PATH or corresponding save() method.
3413         var lastRestore = this._lastIndexOfAnyCall(["restore", "setTransform"]);
3414         while (lastRestore !== -1) {
3415             var saveCallIndex = this._lastIndexOfMatchingSaveCall(lastRestore - 1);
3416             var index = this._lastIndexOfAnyCall(CanvasRenderingContext2DResource.PathMethods, lastRestore - 1);
3417             index = Math.max(index, saveCallIndex);
3418             this._removeCallsFromLog(CanvasRenderingContext2DResource.TransformationMatrixMethods, index + 1, lastRestore);
3419             lastRestore = this._lastIndexOfAnyCall(["restore", "setTransform"], index - 1);
3420         }
3421
3422         // Remove all save-restore consecutive pairs.
3423         var restoreCalls = 0;
3424         for (var i = this._calls.length - 1; i >= 0; --i) {
3425             var functionName = this._calls[i].functionName();
3426             if (functionName === "restore") {
3427                 ++restoreCalls;
3428                 continue;
3429             }
3430             if (functionName === "save" && restoreCalls > 0) {
3431                 var saveCallIndex = i;
3432                 for (var j = i - 1; j >= 0 && i - j < restoreCalls; --j) {
3433                     if (this._calls[j].functionName() === "save")
3434                         saveCallIndex = j;
3435                     else
3436                         break;
3437                 }
3438                 this._calls.splice(saveCallIndex, (i - saveCallIndex + 1) * 2);
3439                 i = saveCallIndex;
3440             }
3441             restoreCalls = 0;
3442         }
3443     },
3444
3445     /**
3446      * @param {!Array.<string>} functionNames
3447      * @param {number} fromIndex
3448      * @param {number=} toIndex
3449      * @return {boolean}
3450      */
3451     _removeCallsFromLog: function(functionNames, fromIndex, toIndex)
3452     {
3453         var oldLength = this._calls.length;
3454         if (typeof toIndex !== "number")
3455             toIndex = oldLength;
3456         else
3457             toIndex = Math.min(toIndex, oldLength);
3458         var newIndex = Math.min(fromIndex, oldLength);
3459         for (var i = newIndex; i < toIndex; ++i) {
3460             var call = this._calls[i];
3461             if (functionNames.indexOf(call.functionName()) === -1)
3462                 this._calls[newIndex++] = call;
3463         }
3464         if (newIndex >= toIndex)
3465             return false;
3466         this._calls.splice(newIndex, toIndex - newIndex);
3467         return true;
3468     },
3469
3470     /**
3471      * @override
3472      * @return {!Object.<string, !Function>}
3473      */
3474     _customWrapFunctions: function()
3475     {
3476         var wrapFunctions = CanvasRenderingContext2DResource._wrapFunctions;
3477         if (!wrapFunctions) {
3478             wrapFunctions = Object.create(null);
3479
3480             wrapFunctions["createLinearGradient"] = Resource.WrapFunction.resourceFactoryMethod(LogEverythingResource, "CanvasGradient");
3481             wrapFunctions["createRadialGradient"] = Resource.WrapFunction.resourceFactoryMethod(LogEverythingResource, "CanvasGradient");
3482             wrapFunctions["createPattern"] = Resource.WrapFunction.resourceFactoryMethod(LogEverythingResource, "CanvasPattern");
3483
3484             for (var i = 0, methodName; methodName = CanvasRenderingContext2DResource.TransformationMatrixMethods[i]; ++i)
3485                 stateModifyingWrapFunction(methodName, methodName === "setTransform" ? this.pushCall_setTransform : undefined);
3486             for (var i = 0, methodName; methodName = CanvasRenderingContext2DResource.PathMethods[i]; ++i)
3487                 stateModifyingWrapFunction(methodName, methodName === "beginPath" ? this.pushCall_beginPath : undefined);
3488
3489             stateModifyingWrapFunction("save", this.pushCall_save);
3490             stateModifyingWrapFunction("restore", this.pushCall_restore);
3491             stateModifyingWrapFunction("clip");
3492
3493             CanvasRenderingContext2DResource._wrapFunctions = wrapFunctions;
3494         }
3495
3496         /**
3497          * @param {string} methodName
3498          * @param {function(this:Resource, !Call)=} func
3499          */
3500         function stateModifyingWrapFunction(methodName, func)
3501         {
3502             if (func) {
3503                 /** @this {Resource.WrapFunction} */
3504                 wrapFunctions[methodName] = function()
3505                 {
3506                     func.call(this._resource, this.call());
3507                 }
3508             } else {
3509                 /** @this {Resource.WrapFunction} */
3510                 wrapFunctions[methodName] = function()
3511                 {
3512                     this._resource.pushCall(this.call());
3513                 }
3514             }
3515         }
3516
3517         return wrapFunctions;
3518     },
3519
3520     __proto__: ContextResource.prototype
3521 }
3522
3523 /**
3524  * @constructor
3525  * @param {!Object.<string, boolean>=} drawingMethodNames
3526  */
3527 function CallFormatter(drawingMethodNames)
3528 {
3529     this._drawingMethodNames = drawingMethodNames || Object.create(null);
3530 }
3531
3532 CallFormatter.prototype = {
3533     /**
3534      * @param {!ReplayableCall} replayableCall
3535      * @param {string=} objectGroup
3536      * @return {!Object}
3537      */
3538     formatCall: function(replayableCall, objectGroup)
3539     {
3540         var result = {};
3541         var functionName = replayableCall.functionName();
3542         if (functionName) {
3543             result.functionName = functionName;
3544             result.arguments = [];
3545             var args = replayableCall.args();
3546             for (var i = 0, n = args.length; i < n; ++i)
3547                 result.arguments.push(this.formatValue(args[i], objectGroup));
3548             if (replayableCall.result() !== undefined)
3549                 result.result = this.formatValue(replayableCall.result(), objectGroup);
3550             if (this._drawingMethodNames[functionName])
3551                 result.isDrawingCall = true;
3552         } else {
3553             result.property = replayableCall.propertyName();
3554             result.value = this.formatValue(replayableCall.propertyValue(), objectGroup);
3555         }
3556         return result;
3557     },
3558
3559     /**
3560      * @param {*} value
3561      * @param {string=} objectGroup
3562      * @return {!CanvasAgent.CallArgument}
3563      */
3564     formatValue: function(value, objectGroup)
3565     {
3566         if (value instanceof Resource || value instanceof ReplayableResource) {
3567             return {
3568                 description: value.description(),
3569                 resourceId: CallFormatter.makeStringResourceId(value.id())
3570             };
3571         }
3572
3573         var remoteObject = injectedScript.wrapObject(value, objectGroup || "", true, false);
3574         var description = remoteObject.description || ("" + value);
3575
3576         var result = {
3577             description: description,
3578             type: /** @type {!CanvasAgent.CallArgumentType} */ (remoteObject.type)
3579         };
3580         if (remoteObject.subtype)
3581             result.subtype = /** @type {!CanvasAgent.CallArgumentSubtype} */ (remoteObject.subtype);
3582         if (remoteObject.objectId) {
3583             if (objectGroup)
3584                 result.remoteObject = remoteObject;
3585             else
3586                 injectedScript.releaseObject(remoteObject.objectId);
3587         }
3588         return result;
3589     },
3590
3591     /**
3592      * @param {string} name
3593      * @return {?string}
3594      */
3595     enumValueForName: function(name)
3596     {
3597         return null;
3598     },
3599
3600     /**
3601      * @param {number} value
3602      * @param {!Array.<string>=} options
3603      * @return {?string}
3604      */
3605     enumNameForValue: function(value, options)
3606     {
3607         return null;
3608     },
3609
3610     /**
3611      * @param {!Array.<!TypeUtils.InternalResourceStateDescriptor>} descriptors
3612      * @param {string=} objectGroup
3613      * @return {!Array.<!CanvasAgent.ResourceStateDescriptor>}
3614      */
3615     formatResourceStateDescriptors: function(descriptors, objectGroup)
3616     {
3617         var result = [];
3618         for (var i = 0, n = descriptors.length; i < n; ++i) {
3619             var d = descriptors[i];
3620             var item;
3621             if (d.values)
3622                 item = { name: d.name, values: this.formatResourceStateDescriptors(d.values, objectGroup) };
3623             else {
3624                 item = { name: d.name, value: this.formatValue(d.value, objectGroup) };
3625                 if (d.valueIsEnum && typeof d.value === "number") {
3626                     var enumName = this.enumNameForValue(d.value);
3627                     if (enumName)
3628                         item.value.enumName = enumName;
3629                 }
3630             }
3631             var enumValue = this.enumValueForName(d.name);
3632             if (enumValue)
3633                 item.enumValueForName = enumValue;
3634             if (d.isArray)
3635                 item.isArray = true;
3636             result.push(item);
3637         }
3638         return result;
3639     }
3640 }
3641
3642 /**
3643  * @const
3644  * @type {!Object.<string, !CallFormatter>}
3645  */
3646 CallFormatter._formatters = {};
3647
3648 /**
3649  * @param {string} resourceName
3650  * @param {!CallFormatter} callFormatter
3651  */
3652 CallFormatter.register = function(resourceName, callFormatter)
3653 {
3654     CallFormatter._formatters[resourceName] = callFormatter;
3655 }
3656
3657 /**
3658  * @param {!Resource|!ReplayableResource} resource
3659  * @return {!CallFormatter}
3660  */
3661 CallFormatter.forResource = function(resource)
3662 {
3663     var formatter = CallFormatter._formatters[resource.name()];
3664     if (!formatter) {
3665         var contextResource = resource.contextResource();
3666         formatter = (contextResource && CallFormatter._formatters[contextResource.name()]) || new CallFormatter();
3667     }
3668     return formatter;
3669 }
3670
3671 /**
3672  * @param {number} resourceId
3673  * @return {!CanvasAgent.ResourceId}
3674  */
3675 CallFormatter.makeStringResourceId = function(resourceId)
3676 {
3677     return "{\"injectedScriptId\":" + injectedScriptId + ",\"resourceId\":" + resourceId + "}";
3678 }
3679
3680 /**
3681  * @constructor
3682  * @extends {CallFormatter}
3683  * @param {!Object.<string, boolean>} drawingMethodNames
3684  */
3685 function WebGLCallFormatter(drawingMethodNames)
3686 {
3687     CallFormatter.call(this, drawingMethodNames);
3688 }
3689
3690 /**
3691  * NOTE: The code below is generated from the IDL file by the script:
3692  * /devtools/scripts/check_injected_webgl_calls_info.py
3693  *
3694  * @type {!Array.<{aname: string, enum: (!Array.<number>|undefined), bitfield: (!Array.<number>|undefined), returnType: string, hints: (!Array.<string>|undefined)}>}
3695  */
3696 WebGLCallFormatter.EnumsInfo = [
3697     {"aname": "activeTexture", "enum": [0]},
3698     {"aname": "bindBuffer", "enum": [0]},
3699     {"aname": "bindFramebuffer", "enum": [0]},
3700     {"aname": "bindRenderbuffer", "enum": [0]},
3701     {"aname": "bindTexture", "enum": [0]},
3702     {"aname": "blendEquation", "enum": [0]},
3703     {"aname": "blendEquationSeparate", "enum": [0, 1]},
3704     {"aname": "blendFunc", "enum": [0, 1], "hints": ["ZERO", "ONE"]},
3705     {"aname": "blendFuncSeparate", "enum": [0, 1, 2, 3], "hints": ["ZERO", "ONE"]},
3706     {"aname": "bufferData", "enum": [0, 2]},
3707     {"aname": "bufferSubData", "enum": [0]},
3708     {"aname": "checkFramebufferStatus", "enum": [0], "returnType": "enum"},
3709     {"aname": "clear", "bitfield": [0]},
3710     {"aname": "compressedTexImage2D", "enum": [0, 2]},
3711     {"aname": "compressedTexSubImage2D", "enum": [0, 6]},
3712     {"aname": "copyTexImage2D", "enum": [0, 2]},
3713     {"aname": "copyTexSubImage2D", "enum": [0]},
3714     {"aname": "createShader", "enum": [0]},
3715     {"aname": "cullFace", "enum": [0]},
3716     {"aname": "depthFunc", "enum": [0]},
3717     {"aname": "disable", "enum": [0]},
3718     {"aname": "drawArrays", "enum": [0], "hints": ["POINTS", "LINES"]},
3719     {"aname": "drawElements", "enum": [0, 2], "hints": ["POINTS", "LINES"]},
3720     {"aname": "enable", "enum": [0]},
3721     {"aname": "framebufferRenderbuffer", "enum": [0, 1, 2]},
3722     {"aname": "framebufferTexture2D", "enum": [0, 1, 2]},
3723     {"aname": "frontFace", "enum": [0]},
3724     {"aname": "generateMipmap", "enum": [0]},
3725     {"aname": "getBufferParameter", "enum": [0, 1]},
3726     {"aname": "getError", "hints": ["NO_ERROR"], "returnType": "enum"},
3727     {"aname": "getFramebufferAttachmentParameter", "enum": [0, 1, 2]},
3728     {"aname": "getParameter", "enum": [0]},
3729     {"aname": "getProgramParameter", "enum": [1]},
3730     {"aname": "getRenderbufferParameter", "enum": [0, 1]},
3731     {"aname": "getShaderParameter", "enum": [1]},
3732     {"aname": "getShaderPrecisionFormat", "enum": [0, 1]},
3733     {"aname": "getTexParameter", "enum": [0, 1], "returnType": "enum"},
3734     {"aname": "getVertexAttrib", "enum": [1]},
3735     {"aname": "getVertexAttribOffset", "enum": [1]},
3736     {"aname": "hint", "enum": [0, 1]},
3737     {"aname": "isEnabled", "enum": [0]},
3738     {"aname": "pixelStorei", "enum": [0]},
3739     {"aname": "readPixels", "enum": [4, 5]},
3740     {"aname": "renderbufferStorage", "enum": [0, 1]},
3741     {"aname": "stencilFunc", "enum": [0]},
3742     {"aname": "stencilFuncSeparate", "enum": [0, 1]},
3743     {"aname": "stencilMaskSeparate", "enum": [0]},
3744     {"aname": "stencilOp", "enum": [0, 1, 2], "hints": ["ZERO", "ONE"]},
3745     {"aname": "stencilOpSeparate", "enum": [0, 1, 2, 3], "hints": ["ZERO", "ONE"]},
3746     {"aname": "texParameterf", "enum": [0, 1, 2]},
3747     {"aname": "texParameteri", "enum": [0, 1, 2]},
3748     {"aname": "texImage2D", "enum": [0, 2, 6, 7]},
3749     {"aname": "texImage2D", "enum": [0, 2, 3, 4]},
3750     {"aname": "texSubImage2D", "enum": [0, 6, 7]},
3751     {"aname": "texSubImage2D", "enum": [0, 4, 5]},
3752     {"aname": "vertexAttribPointer", "enum": [2]}
3753 ];
3754
3755 WebGLCallFormatter.prototype = {
3756     /**
3757      * @override
3758      * @param {!ReplayableCall} replayableCall
3759      * @param {string=} objectGroup
3760      * @return {!Object}
3761      */
3762     formatCall: function(replayableCall, objectGroup)
3763     {
3764         var result = CallFormatter.prototype.formatCall.call(this, replayableCall, objectGroup);
3765         if (!result.functionName)
3766             return result;
3767         var enumsInfo = this._findEnumsInfo(replayableCall);
3768         if (!enumsInfo)
3769             return result;
3770         var enumArgsIndexes = enumsInfo["enum"] || [];
3771         for (var i = 0, n = enumArgsIndexes.length; i < n; ++i) {
3772             var index = enumArgsIndexes[i];
3773             var callArgument = result.arguments[index];
3774             this._formatEnumValue(callArgument, enumsInfo["hints"]);
3775         }
3776         var bitfieldArgsIndexes = enumsInfo["bitfield"] || [];
3777         for (var i = 0, n = bitfieldArgsIndexes.length; i < n; ++i) {
3778             var index = bitfieldArgsIndexes[i];
3779             var callArgument = result.arguments[index];
3780             this._formatEnumBitmaskValue(callArgument, enumsInfo["hints"]);
3781         }
3782         if (enumsInfo.returnType === "enum")
3783             this._formatEnumValue(result.result, enumsInfo["hints"]);
3784         else if (enumsInfo.returnType === "bitfield")
3785             this._formatEnumBitmaskValue(result.result, enumsInfo["hints"]);
3786         return result;
3787     },
3788
3789     /**
3790      * @override
3791      * @param {string} name
3792      * @return {?string}
3793      */
3794     enumValueForName: function(name)
3795     {
3796         this._initialize();
3797         if (name in this._enumNameToValue)
3798             return "" + this._enumNameToValue[name];
3799         return null;
3800     },
3801
3802     /**
3803      * @override
3804      * @param {number} value
3805      * @param {!Array.<string>=} options
3806      * @return {?string}
3807      */
3808     enumNameForValue: function(value, options)
3809     {
3810         this._initialize();
3811         options = options || [];
3812         for (var i = 0, n = options.length; i < n; ++i) {
3813             if (this._enumNameToValue[options[i]] === value)
3814                 return options[i];
3815         }
3816         var names = this._enumValueToNames[value];
3817         if (!names || names.length !== 1)
3818             return null;
3819         return names[0];
3820     },
3821
3822     /**
3823      * @param {!ReplayableCall} replayableCall
3824      * @return {?Object}
3825      */
3826     _findEnumsInfo: function(replayableCall)
3827     {
3828         function findMaxArgumentIndex(enumsInfo)
3829         {
3830             var result = -1;
3831             var enumArgsIndexes = enumsInfo["enum"] || [];
3832             for (var i = 0, n = enumArgsIndexes.length; i < n; ++i)
3833                 result = Math.max(result, enumArgsIndexes[i]);
3834             var bitfieldArgsIndexes = enumsInfo["bitfield"] || [];
3835             for (var i = 0, n = bitfieldArgsIndexes.length; i < n; ++i)
3836                 result = Math.max(result, bitfieldArgsIndexes[i]);
3837             return result;
3838         }
3839
3840         var result = null;
3841         for (var i = 0, enumsInfo; enumsInfo = WebGLCallFormatter.EnumsInfo[i]; ++i) {
3842             if (enumsInfo["aname"] !== replayableCall.functionName())
3843                 continue;
3844             var argsCount = replayableCall.args().length;
3845             var maxArgumentIndex = findMaxArgumentIndex(enumsInfo);
3846             if (maxArgumentIndex >= argsCount)
3847                 continue;
3848             // To resolve ambiguity (see texImage2D, texSubImage2D) choose description with max argument indexes.
3849             if (!result || findMaxArgumentIndex(result) < maxArgumentIndex)
3850                 result = enumsInfo;
3851         }
3852         return result;
3853     },
3854
3855     /**
3856      * @param {?CanvasAgent.CallArgument|undefined} callArgument
3857      * @param {!Array.<string>=} options
3858      */
3859     _formatEnumValue: function(callArgument, options)
3860     {
3861         if (!callArgument || isNaN(callArgument.description))
3862             return;
3863         this._initialize();
3864         var value = +callArgument.description;
3865         var enumName = this.enumNameForValue(value, options);
3866         if (enumName)
3867             callArgument.enumName = enumName;
3868     },
3869
3870     /**
3871      * @param {?CanvasAgent.CallArgument|undefined} callArgument
3872      * @param {!Array.<string>=} options
3873      */
3874     _formatEnumBitmaskValue: function(callArgument, options)
3875     {
3876         if (!callArgument || isNaN(callArgument.description))
3877             return;
3878         this._initialize();
3879         var value = +callArgument.description;
3880         options = options || [];
3881         /** @type {!Array.<string>} */
3882         var result = [];
3883         for (var i = 0, n = options.length; i < n; ++i) {
3884             var bitValue = this._enumNameToValue[options[i]] || 0;
3885             if (value & bitValue) {
3886                 result.push(options[i]);
3887                 value &= ~bitValue;
3888             }
3889         }
3890         while (value) {
3891             var nextValue = value & (value - 1);
3892             var bitValue = value ^ nextValue;
3893             var names = this._enumValueToNames[bitValue];
3894             if (!names || names.length !== 1) {
3895                 console.warn("Ambiguous WebGL enum names for value " + bitValue + ": " + names);
3896                 return;
3897             }
3898             result.push(names[0]);
3899             value = nextValue;
3900         }
3901         result.sort();
3902         callArgument.enumName = result.join(" | ");
3903     },
3904
3905     _initialize: function()
3906     {
3907         if (this._enumNameToValue)
3908             return;
3909
3910         /** @type {!Object.<string, number>} */
3911         this._enumNameToValue = Object.create(null);
3912         /** @type {!Object.<number, !Array.<string>>} */
3913         this._enumValueToNames = Object.create(null);
3914
3915         /**
3916          * @param {?Object} obj
3917          * @this WebGLCallFormatter
3918          */
3919         function iterateWebGLEnums(obj)
3920         {
3921             if (!obj)
3922                 return;
3923             for (var property in obj) {
3924                 if (TypeUtils.isEnumPropertyName(property, obj)) {
3925                     var value = /** @type {number} */ (obj[property]);
3926                     this._enumNameToValue[property] = value;
3927                     var names = this._enumValueToNames[value];
3928                     if (names) {
3929                         if (names.indexOf(property) === -1)
3930                             names.push(property);
3931                     } else
3932                         this._enumValueToNames[value] = [property];
3933                 }
3934             }
3935         }
3936
3937         /**
3938          * @param {!Array.<string>} values
3939          * @return {string}
3940          */
3941         function commonSubstring(values)
3942         {
3943             var length = values.length;
3944             for (var i = 0; i < length; ++i) {
3945                 for (var j = 0; j < length; ++j) {
3946                     if (values[j].indexOf(values[i]) === -1)
3947                         break;
3948                 }
3949                 if (j === length)
3950                     return values[i];
3951             }
3952             return "";
3953         }
3954
3955         var gl = this._createUninstrumentedWebGLRenderingContext();
3956         iterateWebGLEnums.call(this, gl);
3957
3958         var extensions = gl.getSupportedExtensions() || [];
3959         for (var i = 0, n = extensions.length; i < n; ++i)
3960             iterateWebGLEnums.call(this, gl.getExtension(extensions[i]));
3961
3962         // Sort to get rid of ambiguity.
3963         for (var value in this._enumValueToNames) {
3964             var numericValue = Number(value);
3965             var names = this._enumValueToNames[numericValue];
3966             if (names.length > 1) {
3967                 // Choose one enum name if possible. For example:
3968                 //   [BLEND_EQUATION, BLEND_EQUATION_RGB] => BLEND_EQUATION
3969                 //   [COLOR_ATTACHMENT0, COLOR_ATTACHMENT0_WEBGL] => COLOR_ATTACHMENT0
3970                 var common = commonSubstring(names);
3971                 if (common)
3972                     this._enumValueToNames[numericValue] = [common];
3973                 else
3974                     this._enumValueToNames[numericValue] = names.sort();
3975             }
3976         }
3977     },
3978
3979     /**
3980      * @return {?WebGLRenderingContext}
3981      */
3982     _createUninstrumentedWebGLRenderingContext: function()
3983     {
3984         var canvas = /** @type {!HTMLCanvasElement} */ (inspectedWindow.document.createElement("canvas"));
3985         var contextIds = ["experimental-webgl", "webkit-3d", "3d"];
3986         for (var i = 0, contextId; contextId = contextIds[i]; ++i) {
3987             var context = canvas.getContext(contextId);
3988             if (context)
3989                 return /** @type {!WebGLRenderingContext} */ (Resource.wrappedObject(context));
3990         }
3991         return null;
3992     },
3993
3994     __proto__: CallFormatter.prototype
3995 }
3996
3997 CallFormatter.register("CanvasRenderingContext2D", new CallFormatter(CanvasRenderingContext2DResource.DrawingMethods));
3998 CallFormatter.register("WebGLRenderingContext", new WebGLCallFormatter(WebGLRenderingContextResource.DrawingMethods));
3999
4000 /**
4001  * @constructor
4002  */
4003 function TraceLog()
4004 {
4005     /** @type {!Array.<!ReplayableCall>} */
4006     this._replayableCalls = [];
4007     /** @type {!Cache.<!ReplayableResource>} */
4008     this._replayablesCache = new Cache();
4009     /** @type {!Object.<number, boolean>} */
4010     this._frameEndCallIndexes = {};
4011     /** @type {!Object.<number, boolean>} */
4012     this._resourcesCreatedInThisTraceLog = {};
4013 }
4014
4015 TraceLog.prototype = {
4016     /**
4017      * @return {number}
4018      */
4019     size: function()
4020     {
4021         return this._replayableCalls.length;
4022     },
4023
4024     /**
4025      * @return {!Array.<!ReplayableCall>}
4026      */
4027     replayableCalls: function()
4028     {
4029         return this._replayableCalls;
4030     },
4031
4032     /**
4033      * @param {number} id
4034      * @return {!ReplayableResource|undefined}
4035      */
4036     replayableResource: function(id)
4037     {
4038         return this._replayablesCache.get(id);
4039     },
4040
4041     /**
4042      * @param {number} resourceId
4043      * @return {boolean}
4044      */
4045     createdInThisTraceLog: function(resourceId)
4046     {
4047         return !!this._resourcesCreatedInThisTraceLog[resourceId];
4048     },
4049
4050     /**
4051      * @param {!Resource} resource
4052      */
4053     captureResource: function(resource)
4054     {
4055         resource.toReplayable(this._replayablesCache);
4056     },
4057
4058     /**
4059      * @param {!Call} call
4060      */
4061     addCall: function(call)
4062     {
4063         var resource = Resource.forObject(call.result());
4064         if (resource && !this._replayablesCache.has(resource.id()))
4065             this._resourcesCreatedInThisTraceLog[resource.id()] = true;
4066         this._replayableCalls.push(call.toReplayable(this._replayablesCache));
4067     },
4068
4069     addFrameEndMark: function()
4070     {
4071         var index = this._replayableCalls.length - 1;
4072         if (index >= 0)
4073             this._frameEndCallIndexes[index] = true;
4074     },
4075
4076     /**
4077      * @param {number} index
4078      * @return {boolean}
4079      */
4080     isFrameEndCallAt: function(index)
4081     {
4082         return !!this._frameEndCallIndexes[index];
4083     }
4084 }
4085
4086 /**
4087  * @constructor
4088  * @param {!TraceLog} traceLog
4089  */
4090 function TraceLogPlayer(traceLog)
4091 {
4092     /** @type {!TraceLog} */
4093     this._traceLog = traceLog;
4094     /** @type {number} */
4095     this._nextReplayStep = 0;
4096     /** @type {!Cache.<!Resource>} */
4097     this._replayWorldCache = new Cache();
4098 }
4099
4100 TraceLogPlayer.prototype = {
4101     /**
4102      * @return {!TraceLog}
4103      */
4104     traceLog: function()
4105     {
4106         return this._traceLog;
4107     },
4108
4109     /**
4110      * @param {number} id
4111      * @return {!Resource|undefined}
4112      */
4113     replayWorldResource: function(id)
4114     {
4115         return this._replayWorldCache.get(id);
4116     },
4117
4118     /**
4119      * @return {number}
4120      */
4121     nextReplayStep: function()
4122     {
4123         return this._nextReplayStep;
4124     },
4125
4126     reset: function()
4127     {
4128         this._nextReplayStep = 0;
4129         this._replayWorldCache.reset();
4130     },
4131
4132     /**
4133      * @param {number} stepNum
4134      * @return {{replayTime:number, lastCall:(!Call)}}
4135      */
4136     stepTo: function(stepNum)
4137     {
4138         stepNum = Math.min(stepNum, this._traceLog.size() - 1);
4139         console.assert(stepNum >= 0);
4140         if (this._nextReplayStep > stepNum)
4141             this.reset();
4142
4143         // Replay the calls' arguments first to warm-up, before measuring the actual replay time.
4144         this._replayCallArguments(stepNum);
4145
4146         var replayableCalls = this._traceLog.replayableCalls();
4147         var replayedCalls = [];
4148         replayedCalls.length = stepNum - this._nextReplayStep + 1;
4149
4150         var beforeTime = TypeUtils.now();
4151         for (var i = 0; this._nextReplayStep <= stepNum; ++this._nextReplayStep, ++i)
4152             replayedCalls[i] = replayableCalls[this._nextReplayStep].replay(this._replayWorldCache);
4153         var replayTime = Math.max(0, TypeUtils.now() - beforeTime);
4154
4155         for (var i = 0, call; call = replayedCalls[i]; ++i)
4156             call.resource().onCallReplayed(call);
4157
4158         return {
4159             replayTime: replayTime,
4160             lastCall: replayedCalls[replayedCalls.length - 1]
4161         };
4162     },
4163
4164     /**
4165      * @param {number} stepNum
4166      */
4167     _replayCallArguments: function(stepNum)
4168     {
4169         /**
4170          * @param {*} obj
4171          * @this {TraceLogPlayer}
4172          */
4173         function replayIfNotCreatedInThisTraceLog(obj)
4174         {
4175             if (!(obj instanceof ReplayableResource))
4176                 return;
4177             var replayableResource = /** @type {!ReplayableResource} */ (obj);
4178             if (!this._traceLog.createdInThisTraceLog(replayableResource.id()))
4179                 replayableResource.replay(this._replayWorldCache)
4180         }
4181         var replayableCalls = this._traceLog.replayableCalls();
4182         for (var i = this._nextReplayStep; i <= stepNum; ++i) {
4183             replayIfNotCreatedInThisTraceLog.call(this, replayableCalls[i].replayableResource());
4184             replayIfNotCreatedInThisTraceLog.call(this, replayableCalls[i].result());
4185             replayableCalls[i].args().forEach(replayIfNotCreatedInThisTraceLog.bind(this));
4186         }
4187     }
4188 }
4189
4190 /**
4191  * @constructor
4192  */
4193 function ResourceTrackingManager()
4194 {
4195     this._capturing = false;
4196     this._stopCapturingOnFrameEnd = false;
4197     this._lastTraceLog = null;
4198 }
4199
4200 ResourceTrackingManager.prototype = {
4201     /**
4202      * @return {boolean}
4203      */
4204     capturing: function()
4205     {
4206         return this._capturing;
4207     },
4208
4209     /**
4210      * @return {?TraceLog}
4211      */
4212     lastTraceLog: function()
4213     {
4214         return this._lastTraceLog;
4215     },
4216
4217     /**
4218      * @param {!Resource} resource
4219      */
4220     registerResource: function(resource)
4221     {
4222         resource.setManager(this);
4223     },
4224
4225     startCapturing: function()
4226     {
4227         if (!this._capturing)
4228             this._lastTraceLog = new TraceLog();
4229         this._capturing = true;
4230         this._stopCapturingOnFrameEnd = false;
4231     },
4232
4233     /**
4234      * @param {!TraceLog=} traceLog
4235      */
4236     stopCapturing: function(traceLog)
4237     {
4238         if (traceLog && this._lastTraceLog !== traceLog)
4239             return;
4240         this._capturing = false;
4241         this._stopCapturingOnFrameEnd = false;
4242         if (this._lastTraceLog)
4243             this._lastTraceLog.addFrameEndMark();
4244     },
4245
4246     /**
4247      * @param {!TraceLog} traceLog
4248      */
4249     dropTraceLog: function(traceLog)
4250     {
4251         this.stopCapturing(traceLog);
4252         if (this._lastTraceLog === traceLog)
4253             this._lastTraceLog = null;
4254     },
4255
4256     captureFrame: function()
4257     {
4258         this._lastTraceLog = new TraceLog();
4259         this._capturing = true;
4260         this._stopCapturingOnFrameEnd = true;
4261     },
4262
4263     /**
4264      * @param {!Resource} resource
4265      * @param {!Array|!Arguments} args
4266      */
4267     captureArguments: function(resource, args)
4268     {
4269         if (!this._capturing)
4270             return;
4271         this._lastTraceLog.captureResource(resource);
4272         for (var i = 0, n = args.length; i < n; ++i) {
4273             var res = Resource.forObject(args[i]);
4274             if (res)
4275                 this._lastTraceLog.captureResource(res);
4276         }
4277     },
4278
4279     /**
4280      * @param {!Call} call
4281      */
4282     captureCall: function(call)
4283     {
4284         if (!this._capturing)
4285             return;
4286         this._lastTraceLog.addCall(call);
4287     },
4288
4289     markFrameEnd: function()
4290     {
4291         if (!this._lastTraceLog)
4292             return;
4293         this._lastTraceLog.addFrameEndMark();
4294         if (this._stopCapturingOnFrameEnd && this._lastTraceLog.size())
4295             this.stopCapturing(this._lastTraceLog);
4296     }
4297 }
4298
4299 /**
4300  * @constructor
4301  */
4302 var InjectedCanvasModule = function()
4303 {
4304     /** @type {!ResourceTrackingManager} */
4305     this._manager = new ResourceTrackingManager();
4306     /** @type {number} */
4307     this._lastTraceLogId = 0;
4308     /** @type {!Object.<string, !TraceLog>} */
4309     this._traceLogs = {};
4310     /** @type {!Object.<string, !TraceLogPlayer>} */
4311     this._traceLogPlayers = {};
4312 }
4313
4314 InjectedCanvasModule.prototype = {
4315     /**
4316      * @param {!WebGLRenderingContext} glContext
4317      * @return {!Object}
4318      */
4319     wrapWebGLContext: function(glContext)
4320     {
4321         var resource = Resource.forObject(glContext) || new WebGLRenderingContextResource(glContext);
4322         this._manager.registerResource(resource);
4323         return resource.proxyObject();
4324     },
4325
4326     /**
4327      * @param {!CanvasRenderingContext2D} context
4328      * @return {!Object}
4329      */
4330     wrapCanvas2DContext: function(context)
4331     {
4332         var resource = Resource.forObject(context) || new CanvasRenderingContext2DResource(context);
4333         this._manager.registerResource(resource);
4334         return resource.proxyObject();
4335     },
4336
4337     /**
4338      * @return {!CanvasAgent.TraceLogId}
4339      */
4340     captureFrame: function()
4341     {
4342         return this._callStartCapturingFunction(this._manager.captureFrame);
4343     },
4344
4345     /**
4346      * @return {!CanvasAgent.TraceLogId}
4347      */
4348     startCapturing: function()
4349     {
4350         return this._callStartCapturingFunction(this._manager.startCapturing);
4351     },
4352
4353     markFrameEnd: function()
4354     {
4355         this._manager.markFrameEnd();
4356     },
4357
4358     /**
4359      * @param {function(this:ResourceTrackingManager)} func
4360      * @return {!CanvasAgent.TraceLogId}
4361      */
4362     _callStartCapturingFunction: function(func)
4363     {
4364         var oldTraceLog = this._manager.lastTraceLog();
4365         func.call(this._manager);
4366         var traceLog = /** @type {!TraceLog} */ (this._manager.lastTraceLog());
4367         if (traceLog === oldTraceLog) {
4368             for (var id in this._traceLogs) {
4369                 if (this._traceLogs[id] === traceLog)
4370                     return id;
4371             }
4372         }
4373         var id = this._makeTraceLogId();
4374         this._traceLogs[id] = traceLog;
4375         return id;
4376     },
4377
4378     /**
4379      * @param {!CanvasAgent.TraceLogId} id
4380      */
4381     stopCapturing: function(id)
4382     {
4383         var traceLog = this._traceLogs[id];
4384         if (traceLog)
4385             this._manager.stopCapturing(traceLog);
4386     },
4387
4388     /**
4389      * @param {!CanvasAgent.TraceLogId} id
4390      */
4391     dropTraceLog: function(id)
4392     {
4393         var traceLog = this._traceLogs[id];
4394         if (traceLog)
4395             this._manager.dropTraceLog(traceLog);
4396         delete this._traceLogs[id];
4397         delete this._traceLogPlayers[id];
4398         injectedScript.releaseObjectGroup(id);
4399     },
4400
4401     /**
4402      * @param {!CanvasAgent.TraceLogId} id
4403      * @param {number=} startOffset
4404      * @param {number=} maxLength
4405      * @return {!CanvasAgent.TraceLog|string}
4406      */
4407     traceLog: function(id, startOffset, maxLength)
4408     {
4409         var traceLog = this._traceLogs[id];
4410         if (!traceLog)
4411             return "Error: Trace log with the given ID not found.";
4412
4413         // Ensure last call ends a frame.
4414         traceLog.addFrameEndMark();
4415
4416         var replayableCalls = traceLog.replayableCalls();
4417         if (typeof startOffset !== "number")
4418             startOffset = 0;
4419         if (typeof maxLength !== "number")
4420             maxLength = replayableCalls.length;
4421
4422         var fromIndex = Math.max(0, startOffset);
4423         var toIndex = Math.min(replayableCalls.length - 1, fromIndex + maxLength - 1);
4424
4425         var alive = this._manager.capturing() && this._manager.lastTraceLog() === traceLog;
4426         var result = {
4427             id: id,
4428             /** @type {!Array.<!CanvasAgent.Call>} */
4429             calls: [],
4430             /** @type {!Array.<!CanvasAgent.CallArgument>} */
4431             contexts: [],
4432             alive: alive,
4433             startOffset: fromIndex,
4434             totalAvailableCalls: replayableCalls.length
4435         };
4436         /** @type {!Object.<string, boolean>} */
4437         var contextIds = {};
4438         for (var i = fromIndex; i <= toIndex; ++i) {
4439             var call = replayableCalls[i];
4440             var resource = call.replayableResource();
4441             var contextResource = resource.contextResource();
4442             var stackTrace = call.stackTrace();
4443             var callFrame = stackTrace ? stackTrace.callFrame(0) || {} : {};
4444             var item = CallFormatter.forResource(resource).formatCall(call);
4445             item.contextId = CallFormatter.makeStringResourceId(contextResource.id());
4446             item.sourceURL = callFrame.sourceURL;
4447             item.lineNumber = callFrame.lineNumber;
4448             item.columnNumber = callFrame.columnNumber;
4449             item.isFrameEndCall = traceLog.isFrameEndCallAt(i);
4450             result.calls.push(item);
4451             if (!contextIds[item.contextId]) {
4452                 contextIds[item.contextId] = true;
4453                 result.contexts.push(CallFormatter.forResource(resource).formatValue(contextResource));
4454             }
4455         }
4456         return result;
4457     },
4458
4459     /**
4460      * @param {!CanvasAgent.TraceLogId} traceLogId
4461      * @param {number} stepNo
4462      * @return {{resourceState: !CanvasAgent.ResourceState, replayTime: number}|string}
4463      */
4464     replayTraceLog: function(traceLogId, stepNo)
4465     {
4466         var traceLog = this._traceLogs[traceLogId];
4467         if (!traceLog)
4468             return "Error: Trace log with the given ID not found.";
4469         this._traceLogPlayers[traceLogId] = this._traceLogPlayers[traceLogId] || new TraceLogPlayer(traceLog);
4470         injectedScript.releaseObjectGroup(traceLogId);
4471
4472         var replayResult = this._traceLogPlayers[traceLogId].stepTo(stepNo);
4473         var resource = replayResult.lastCall.resource();
4474         var dataURL = resource.toDataURL();
4475         if (!dataURL) {
4476             resource = resource.contextResource();
4477             dataURL = resource.toDataURL();
4478         }
4479         return {
4480             resourceState: this._makeResourceState(resource.id(), traceLogId, resource, dataURL),
4481             replayTime: replayResult.replayTime
4482         };
4483     },
4484
4485     /**
4486      * @param {!CanvasAgent.TraceLogId} traceLogId
4487      * @param {!CanvasAgent.ResourceId} stringResourceId
4488      * @return {!CanvasAgent.ResourceState|string}
4489      */
4490     resourceState: function(traceLogId, stringResourceId)
4491     {
4492         var traceLog = this._traceLogs[traceLogId];
4493         if (!traceLog)
4494             return "Error: Trace log with the given ID not found.";
4495
4496         var parsedStringId1 = this._parseStringId(traceLogId);
4497         var parsedStringId2 = this._parseStringId(stringResourceId);
4498         if (parsedStringId1.injectedScriptId !== parsedStringId2.injectedScriptId)
4499             return "Error: Both IDs must point to the same injected script.";
4500
4501         var resourceId = parsedStringId2.resourceId;
4502         if (!resourceId)
4503             return "Error: Wrong resource ID: " + stringResourceId;
4504
4505         var traceLogPlayer = this._traceLogPlayers[traceLogId];
4506         var resource = traceLogPlayer && traceLogPlayer.replayWorldResource(resourceId);
4507         return this._makeResourceState(resourceId, traceLogId, resource);
4508     },
4509
4510     /**
4511      * @param {!CanvasAgent.TraceLogId} traceLogId
4512      * @param {number} callIndex
4513      * @param {number} argumentIndex
4514      * @param {string} objectGroup
4515      * @return {{result:(!RuntimeAgent.RemoteObject|undefined), resourceState:(!CanvasAgent.ResourceState|undefined)}|string}
4516      */
4517     evaluateTraceLogCallArgument: function(traceLogId, callIndex, argumentIndex, objectGroup)
4518     {
4519         var traceLog = this._traceLogs[traceLogId];
4520         if (!traceLog)
4521             return "Error: Trace log with the given ID not found.";
4522
4523         var replayableCall = traceLog.replayableCalls()[callIndex];
4524         if (!replayableCall)
4525             return "Error: No call found at index " + callIndex;
4526
4527         var value;
4528         if (replayableCall.isPropertySetter())
4529             value = replayableCall.propertyValue();
4530         else if (argumentIndex === -1)
4531             value = replayableCall.result();
4532         else {
4533             var args = replayableCall.args();
4534             if (argumentIndex < 0 || argumentIndex >= args.length)
4535                 return "Error: No argument found at index " + argumentIndex + " for call at index " + callIndex;
4536             value = args[argumentIndex];
4537         }
4538
4539         if (value instanceof ReplayableResource) {
4540             var traceLogPlayer = this._traceLogPlayers[traceLogId];
4541             var resource = traceLogPlayer && traceLogPlayer.replayWorldResource(value.id());
4542             var resourceState = this._makeResourceState(value.id(), traceLogId, resource);
4543             return { resourceState: resourceState };
4544         }
4545
4546         var remoteObject = injectedScript.wrapObject(value, objectGroup, true, false);
4547         return { result: remoteObject };
4548     },
4549
4550     /**
4551      * @return {!CanvasAgent.TraceLogId}
4552      */
4553     _makeTraceLogId: function()
4554     {
4555         return "{\"injectedScriptId\":" + injectedScriptId + ",\"traceLogId\":" + (++this._lastTraceLogId) + "}";
4556     },
4557
4558     /**
4559      * @param {number} resourceId
4560      * @param {!CanvasAgent.TraceLogId} traceLogId
4561      * @param {?Resource=} resource
4562      * @param {string=} overrideImageURL
4563      * @return {!CanvasAgent.ResourceState}
4564      */
4565     _makeResourceState: function(resourceId, traceLogId, resource, overrideImageURL)
4566     {
4567         var result = {
4568             id: CallFormatter.makeStringResourceId(resourceId),
4569             traceLogId: traceLogId
4570         };
4571         if (resource) {
4572             result.imageURL = overrideImageURL || resource.toDataURL();
4573             result.descriptors = CallFormatter.forResource(resource).formatResourceStateDescriptors(resource.currentState(), traceLogId);
4574         }
4575         return result;
4576     },
4577
4578     /**
4579      * @param {string} stringId
4580      * @return {{injectedScriptId: number, traceLogId: ?number, resourceId: ?number}}
4581      */
4582     _parseStringId: function(stringId)
4583     {
4584         return /** @type {?} */ (InjectedScriptHost.eval("(" + stringId + ")"));
4585     }
4586 }
4587
4588 var injectedCanvasModule = new InjectedCanvasModule();
4589 return injectedCanvasModule;
4590
4591 })