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