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