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