• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
45var 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)}} */
227TypeUtils.InternalResourceStateDescriptor;
228
229/**
230 * @interface
231 */
232function StackTrace()
233{
234}
235
236StackTrace.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 */
251StackTrace.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 */
266function 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
297StackTraceV8.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 */
313function Cache()
314{
315    this.reset();
316}
317
318Cache.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 */
373function 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
385Call.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 */
557function 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
568ReplayableCall.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 */
668function 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 */
695Resource._uniqueId = 0;
696
697/**
698 * @type {!Object.<string, number>}
699 */
700Resource._uniqueKindIds = {};
701
702/**
703 * @param {*} obj
704 * @return {?Resource}
705 */
706Resource.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 */
721Resource.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 */
732Resource.toReplayable = function(obj, cache)
733{
734    var resource = Resource.forObject(obj);
735    return resource ? resource.toReplayable(cache) : obj;
736}
737
738Resource.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 */
1123Resource.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
1133Resource.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 */
1172Resource.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 */
1194function ReplayableResource(originalResource, data)
1195{
1196    this._proto = originalResource.__proto__;
1197    this._data = data;
1198}
1199
1200ReplayableResource.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 */
1251ReplayableResource.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 */
1262function ContextResource(wrappedObject, name)
1263{
1264    Resource.call(this, wrappedObject, name);
1265}
1266
1267ContextResource.prototype = {
1268    __proto__: Resource.prototype
1269}
1270
1271/**
1272 * @constructor
1273 * @extends {Resource}
1274 * @param {!Object} wrappedObject
1275 * @param {string} name
1276 */
1277function LogEverythingResource(wrappedObject, name)
1278{
1279    Resource.call(this, wrappedObject, name);
1280}
1281
1282LogEverythingResource.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 */
1316function WebGLBoundResource(wrappedObject, name)
1317{
1318    Resource.call(this, wrappedObject, name);
1319    /** @type {!Object.<string, *>} */
1320    this._state = {};
1321}
1322
1323WebGLBoundResource.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 */
1413function WebGLTextureResource(wrappedObject, name)
1414{
1415    WebGLBoundResource.call(this, wrappedObject, name);
1416}
1417
1418WebGLTextureResource.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 */
1564function WebGLProgramResource(wrappedObject, name)
1565{
1566    Resource.call(this, wrappedObject, name);
1567}
1568
1569WebGLProgramResource.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 */
1777function WebGLShaderResource(wrappedObject, name)
1778{
1779    Resource.call(this, wrappedObject, name);
1780}
1781
1782WebGLShaderResource.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 */
1854function WebGLBufferResource(wrappedObject, name)
1855{
1856    WebGLBoundResource.call(this, wrappedObject, name);
1857}
1858
1859WebGLBufferResource.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 */
2019function WebGLFramebufferResource(wrappedObject, name)
2020{
2021    WebGLBoundResource.call(this, wrappedObject, name);
2022}
2023
2024WebGLFramebufferResource.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 */
2088function WebGLRenderbufferResource(wrappedObject, name)
2089{
2090    WebGLBoundResource.call(this, wrappedObject, name);
2091}
2092
2093WebGLRenderbufferResource.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 */
2149function WebGLUniformLocationResource(wrappedObject, name)
2150{
2151    Resource.call(this, wrappedObject, name);
2152}
2153
2154WebGLUniformLocationResource.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 */
2264function 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 */
2279WebGLRenderingContextResource.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 */
2295WebGLRenderingContextResource.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 */
2307WebGLRenderingContextResource.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 */
2366WebGLRenderingContextResource.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 */
2410WebGLRenderingContextResource.DrawingMethods = TypeUtils.createPrefixedPropertyNamesSet([
2411    "clear",
2412    "drawArrays",
2413    "drawElements"
2414]);
2415
2416/**
2417 * @param {*} obj
2418 * @return {?WebGLRenderingContextResource}
2419 */
2420WebGLRenderingContextResource.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
2429WebGLRenderingContextResource.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 */
3106function CanvasRenderingContext2DResource(context)
3107{
3108    ContextResource.call(this, context, "CanvasRenderingContext2D");
3109}
3110
3111/**
3112 * @const
3113 * @type {!Array.<string>}
3114 */
3115CanvasRenderingContext2DResource.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 */
3141CanvasRenderingContext2DResource.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 */
3157CanvasRenderingContext2DResource.TransformationMatrixMethods = [
3158    "scale",
3159    "rotate",
3160    "translate",
3161    "transform",
3162    "setTransform"
3163];
3164
3165/**
3166 * @const
3167 * @type {!Object.<string, boolean>}
3168 */
3169CanvasRenderingContext2DResource.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
3185CanvasRenderingContext2DResource.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 */
3527function CallFormatter(drawingMethodNames)
3528{
3529    this._drawingMethodNames = drawingMethodNames || Object.create(null);
3530}
3531
3532CallFormatter.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 */
3646CallFormatter._formatters = {};
3647
3648/**
3649 * @param {string} resourceName
3650 * @param {!CallFormatter} callFormatter
3651 */
3652CallFormatter.register = function(resourceName, callFormatter)
3653{
3654    CallFormatter._formatters[resourceName] = callFormatter;
3655}
3656
3657/**
3658 * @param {!Resource|!ReplayableResource} resource
3659 * @return {!CallFormatter}
3660 */
3661CallFormatter.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 */
3675CallFormatter.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 */
3685function 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 */
3696WebGLCallFormatter.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
3755WebGLCallFormatter.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
3997CallFormatter.register("CanvasRenderingContext2D", new CallFormatter(CanvasRenderingContext2DResource.DrawingMethods));
3998CallFormatter.register("WebGLRenderingContext", new WebGLCallFormatter(WebGLRenderingContextResource.DrawingMethods));
3999
4000/**
4001 * @constructor
4002 */
4003function 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
4015TraceLog.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 */
4090function 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
4100TraceLogPlayer.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 */
4193function ResourceTrackingManager()
4194{
4195    this._capturing = false;
4196    this._stopCapturingOnFrameEnd = false;
4197    this._lastTraceLog = null;
4198}
4199
4200ResourceTrackingManager.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 */
4302var 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
4314InjectedCanvasModule.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 InjectedScriptHost.evaluate("(" + stringId + ")");
4585    }
4586}
4587
4588var injectedCanvasModule = new InjectedCanvasModule();
4589return injectedCanvasModule;
4590
4591})
4592