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