2 * Copyright (C) 2013 Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
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
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.
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.
32 * FIXME: ES5 strict mode check is suppressed due to multiple uses of arguments.callee.
34 * @suppress {es5Strict}
38 * @param {InjectedScriptHostClass} InjectedScriptHost
39 * @param {Window} inspectedWindow
40 * @param {number} injectedScriptId
41 * @param {!InjectedScript} injectedScript
43 (function (InjectedScriptHost, inspectedWindow, injectedScriptId, injectedScript) {
47 * http://www.khronos.org/registry/typedarray/specs/latest/#7
49 * @type {!Array.<function(new:ArrayBufferView, (!ArrayBuffer|!ArrayBufferView), number=, number=)>}
51 _typedArrayClasses: (function(typeNames) {
53 for (var i = 0, n = typeNames.length; i < n; ++i) {
54 if (inspectedWindow[typeNames[i]])
55 result.push(inspectedWindow[typeNames[i]]);
58 })(["Int8Array", "Uint8Array", "Uint8ClampedArray", "Int16Array", "Uint16Array", "Int32Array", "Uint32Array", "Float32Array", "Float64Array"]),
62 * @type {!Array.<string>}
64 _supportedPropertyPrefixes: ["webkit"],
68 * @return {function(new:ArrayBufferView, (!ArrayBuffer|!ArrayBufferView), number=, number=)|null}
70 typedArrayClass: function(array)
72 var classes = TypeUtils._typedArrayClasses;
73 for (var i = 0, n = classes.length; i < n; ++i) {
74 if (array instanceof classes[i])
89 var type = typeof obj;
90 if (type !== "object" && type !== "function")
93 // Handle Array and ArrayBuffer instances.
94 if (typeof obj.slice === "function") {
95 console.assert(obj instanceof Array || obj instanceof ArrayBuffer);
99 var typedArrayClass = TypeUtils.typedArrayClass(obj);
101 return new typedArrayClass(/** @type {!ArrayBufferView} */ (obj));
103 if (obj instanceof HTMLImageElement) {
104 var img = /** @type {!HTMLImageElement} */ (obj);
105 // Special case for Images with Blob URIs: cloneNode will fail if the Blob URI has already been revoked.
106 // FIXME: Maybe this is a bug in WebKit core?
107 if (/^blob:/.test(img.src))
108 return TypeUtils.cloneIntoCanvas(img);
109 return img.cloneNode(true);
112 if (obj instanceof HTMLCanvasElement)
113 return TypeUtils.cloneIntoCanvas(obj);
115 if (obj instanceof HTMLVideoElement)
116 return TypeUtils.cloneIntoCanvas(obj, obj.videoWidth, obj.videoHeight);
118 if (obj instanceof ImageData) {
119 var context = TypeUtils._dummyCanvas2dContext();
120 // FIXME: suppress type checks due to outdated builtin externs for createImageData.
121 var result = (/** @type {?} */ (context)).createImageData(obj);
122 for (var i = 0, n = obj.data.length; i < n; ++i)
123 result.data[i] = obj.data[i];
127 // Try to convert to a primitive value via valueOf().
128 if (typeof obj.valueOf === "function") {
129 var value = obj.valueOf();
130 var valueType = typeof value;
131 if (valueType !== "object" && valueType !== "function")
135 console.error("ASSERT_NOT_REACHED: failed to clone object: ", obj);
140 * @param {!HTMLImageElement|!HTMLCanvasElement|!HTMLVideoElement} obj
141 * @param {number=} width
142 * @param {number=} height
143 * @return {!HTMLCanvasElement}
145 cloneIntoCanvas: function(obj, width, height)
147 var canvas = /** @type {!HTMLCanvasElement} */ (inspectedWindow.document.createElement("canvas"));
148 canvas.width = width || +obj.width;
149 canvas.height = height || +obj.height;
150 var context = /** @type {!CanvasRenderingContext2D} */ (Resource.wrappedObject(canvas.getContext("2d")));
151 context.drawImage(obj, 0, 0);
156 * @param {?Object=} obj
159 cloneObject: function(obj)
165 result[key] = obj[key];
170 * @param {!Array.<string>} names
171 * @return {!Object.<string, boolean>}
173 createPrefixedPropertyNamesSet: function(names)
175 var result = Object.create(null);
176 for (var i = 0, name; name = names[i]; ++i) {
178 var suffix = name.substr(0, 1).toUpperCase() + name.substr(1);
179 for (var j = 0, prefix; prefix = TypeUtils._supportedPropertyPrefixes[j]; ++j)
180 result[prefix + suffix] = true;
191 return inspectedWindow.performance.now();
202 * @param {string} property
203 * @param {!Object} obj
206 isEnumPropertyName: function(property, obj)
208 return (/^[A-Z][A-Z0-9_]+$/.test(property) && typeof obj[property] === "number");
212 * @return {!CanvasRenderingContext2D}
214 _dummyCanvas2dContext: function()
216 var context = TypeUtils._dummyCanvas2dContextInstance;
218 var canvas = /** @type {!HTMLCanvasElement} */ (inspectedWindow.document.createElement("canvas"));
219 context = /** @type {!CanvasRenderingContext2D} */ (Resource.wrappedObject(canvas.getContext("2d")));
220 TypeUtils._dummyCanvas2dContextInstance = context;
226 /** @typedef {{name:string, valueIsEnum:(boolean|undefined), value:*, values:(!Array.<!TypeUtils.InternalResourceStateDescriptor>|undefined), isArray:(boolean|undefined)}} */
227 TypeUtils.InternalResourceStateDescriptor;
232 function StackTrace()
236 StackTrace.prototype = {
238 * @param {number} index
239 * @return {{sourceURL: string, lineNumber: number, columnNumber: number}|undefined}
241 callFrame: function(index)
247 * @param {number=} stackTraceLimit
248 * @param {?Function=} topMostFunctionToIgnore
249 * @return {?StackTrace}
251 StackTrace.create = function(stackTraceLimit, topMostFunctionToIgnore)
253 if (typeof Error.captureStackTrace === "function")
254 return new StackTraceV8(stackTraceLimit, topMostFunctionToIgnore || arguments.callee);
255 // FIXME: Support JSC, and maybe other browsers.
261 * @implements {StackTrace}
262 * @param {number=} stackTraceLimit
263 * @param {?Function=} topMostFunctionToIgnore
264 * @see http://code.google.com/p/v8/wiki/JavaScriptStackTraceApi
266 function StackTraceV8(stackTraceLimit, topMostFunctionToIgnore)
268 var oldPrepareStackTrace = Error.prepareStackTrace;
269 var oldStackTraceLimit = Error.stackTraceLimit;
270 if (typeof stackTraceLimit === "number")
271 Error.stackTraceLimit = stackTraceLimit;
274 * @param {!Object} error
275 * @param {!Array.<!CallSite>} structuredStackTrace
276 * @return {!Array.<{sourceURL: string, lineNumber: number, columnNumber: number}>}
278 Error.prepareStackTrace = function(error, structuredStackTrace)
280 return structuredStackTrace.map(function(callSite) {
282 sourceURL: callSite.getFileName(),
283 lineNumber: callSite.getLineNumber(),
284 columnNumber: callSite.getColumnNumber()
289 var holder = /** @type {{stack: !Array.<{sourceURL: string, lineNumber: number, columnNumber: number}>}} */ ({});
290 Error.captureStackTrace(holder, topMostFunctionToIgnore || arguments.callee);
291 this._stackTrace = holder.stack;
293 Error.stackTraceLimit = oldStackTraceLimit;
294 Error.prepareStackTrace = oldPrepareStackTrace;
297 StackTraceV8.prototype = {
300 * @param {number} index
301 * @return {{sourceURL: string, lineNumber: number, columnNumber: number}|undefined}
303 callFrame: function(index)
305 return this._stackTrace[index];
329 /** @type {!Object.<number, !T>} */
330 this._items = Object.create(null);
331 /** @type {number} */
336 * @param {number} key
341 return key in this._items;
345 * @param {number} key
346 * @return {T|undefined}
350 return this._items[key];
354 * @param {number} key
357 put: function(key, item)
361 this._items[key] = item;
367 * @param {?Resource|!Object} thisObject
368 * @param {string} functionName
369 * @param {!Array|!Arguments} args
370 * @param {!Resource|*=} result
371 * @param {?StackTrace=} stackTrace
373 function Call(thisObject, functionName, args, result, stackTrace)
375 this._thisObject = thisObject;
376 this._functionName = functionName;
377 this._args = Array.prototype.slice.call(args, 0);
378 this._result = result;
379 this._stackTrace = stackTrace || null;
381 if (!this._functionName)
382 console.assert(this._args.length === 2 && typeof this._args[0] === "string");
387 * @return {?Resource}
391 return Resource.forObject(this._thisObject);
397 functionName: function()
399 return this._functionName;
405 isPropertySetter: function()
407 return !this._functionName;
427 * @return {?StackTrace}
429 stackTrace: function()
431 return this._stackTrace;
435 * @param {?StackTrace} stackTrace
437 setStackTrace: function(stackTrace)
439 this._stackTrace = stackTrace;
445 setResult: function(result)
447 this._result = result;
451 * @param {string} name
452 * @param {?Object} attachment
454 setAttachment: function(name, attachment)
457 /** @type {?Object.<string, !Object>|undefined} */
458 this._attachments = this._attachments || Object.create(null);
459 this._attachments[name] = attachment;
460 } else if (this._attachments) {
461 delete this._attachments[name];
466 * @param {string} name
469 attachment: function(name)
471 return this._attachments ? (this._attachments[name] || null) : null;
478 this._freezed = true;
479 for (var i = 0, n = this._args.length; i < n; ++i) {
480 // FIXME: freeze the Resources also!
481 if (!Resource.forObject(this._args[i]))
482 this._args[i] = TypeUtils.clone(this._args[i]);
487 * @param {!Cache.<!ReplayableResource>} cache
488 * @return {!ReplayableCall}
490 toReplayable: function(cache)
493 var thisObject = /** @type {!ReplayableResource} */ (Resource.toReplayable(this._thisObject, cache));
494 var result = Resource.toReplayable(this._result, cache);
495 var args = this._args.map(function(obj) {
496 return Resource.toReplayable(obj, cache);
498 var attachments = TypeUtils.cloneObject(this._attachments);
499 return new ReplayableCall(thisObject, this._functionName, args, result, this._stackTrace, attachments);
503 * @param {!ReplayableCall} replayableCall
504 * @param {!Cache.<!Resource>} cache
507 replay: function(replayableCall, cache)
509 var replayableResult = replayableCall.result();
510 if (replayableResult instanceof ReplayableResource && !cache.has(replayableResult.id())) {
511 var resource = replayableResult.replay(cache);
512 console.assert(resource.calls().length > 0, "Expected create* call for the Resource");
513 return resource.calls()[0];
516 var replayObject = ReplayableResource.replay(replayableCall.replayableResource(), cache);
517 var replayArgs = replayableCall.args().map(function(obj) {
518 return ReplayableResource.replay(obj, cache);
520 var replayResult = undefined;
522 if (replayableCall.isPropertySetter())
523 replayObject[replayArgs[0]] = replayArgs[1];
525 var replayFunction = replayObject[replayableCall.functionName()];
526 console.assert(typeof replayFunction === "function", "Expected a function to replay");
527 replayResult = replayFunction.apply(replayObject, replayArgs);
529 if (replayableResult instanceof ReplayableResource) {
530 var resource = replayableResult.replay(cache);
531 if (!resource.wrappedObject())
532 resource.setWrappedObject(replayResult);
536 this._thisObject = replayObject;
537 this._functionName = replayableCall.functionName();
538 this._args = replayArgs;
539 this._result = replayResult;
540 this._stackTrace = replayableCall.stackTrace();
541 this._freezed = true;
542 var attachments = replayableCall.attachments();
543 this._attachments = attachments ? TypeUtils.cloneObject(attachments) : null;
550 * @param {!ReplayableResource} thisObject
551 * @param {string} functionName
552 * @param {!Array.<!ReplayableResource|*>} args
553 * @param {!ReplayableResource|*} result
554 * @param {?StackTrace} stackTrace
555 * @param {?Object.<string, !Object>} attachments
557 function ReplayableCall(thisObject, functionName, args, result, stackTrace, attachments)
559 this._thisObject = thisObject;
560 this._functionName = functionName;
562 this._result = result;
563 this._stackTrace = stackTrace;
565 this._attachments = attachments;
568 ReplayableCall.prototype = {
570 * @return {!ReplayableResource}
572 replayableResource: function()
574 return this._thisObject;
580 functionName: function()
582 return this._functionName;
588 isPropertySetter: function()
590 return !this._functionName;
596 propertyName: function()
598 console.assert(this.isPropertySetter());
599 return /** @type {string} */ (this._args[0]);
605 propertyValue: function()
607 console.assert(this.isPropertySetter());
608 return this._args[1];
612 * @return {!Array.<!ReplayableResource|*>}
620 * @return {!ReplayableResource|*}
628 * @return {?StackTrace}
630 stackTrace: function()
632 return this._stackTrace;
636 * @return {?Object.<string, !Object>}
638 attachments: function()
640 return this._attachments;
644 * @param {string} name
647 attachment: function(name)
649 return this._attachments && this._attachments[name];
653 * @param {!Cache.<!Resource>} cache
656 replay: function(cache)
658 var call = /** @type {!Call} */ (Object.create(Call.prototype));
659 return call.replay(this, cache);
665 * @param {!Object} wrappedObject
666 * @param {string} name
668 function Resource(wrappedObject, name)
670 /** @type {number} */
671 this._id = ++Resource._uniqueId;
672 /** @type {string} */
673 this._name = name || "Resource";
674 /** @type {number} */
675 this._kindId = Resource._uniqueKindIds[this._name] = (Resource._uniqueKindIds[this._name] || 0) + 1;
676 /** @type {?ResourceTrackingManager} */
677 this._resourceManager = null;
678 /** @type {!Array.<!Call>} */
681 * This is to prevent GC from collecting associated resources.
682 * Otherwise, for example in WebGL, subsequent calls to gl.getParameter()
683 * may return a recently created instance that is no longer bound to a
684 * Resource object (thus, no history to replay it later).
686 * @type {!Object.<string, !Resource>}
688 this._boundResources = Object.create(null);
689 this.setWrappedObject(wrappedObject);
695 Resource._uniqueId = 0;
698 * @type {!Object.<string, number>}
700 Resource._uniqueKindIds = {};
704 * @return {?Resource}
706 Resource.forObject = function(obj)
710 if (obj instanceof Resource)
712 if (typeof obj === "object")
713 return obj["__resourceObject"];
718 * @param {!Resource|*} obj
721 Resource.wrappedObject = function(obj)
723 var resource = Resource.forObject(obj);
724 return resource ? resource.wrappedObject() : obj;
728 * @param {!Resource|*} obj
729 * @param {!Cache.<!ReplayableResource>} cache
730 * @return {!ReplayableResource|*}
732 Resource.toReplayable = function(obj, cache)
734 var resource = Resource.forObject(obj);
735 return resource ? resource.toReplayable(cache) : obj;
738 Resource.prototype = {
758 description: function()
760 return this._name + "@" + this._kindId;
766 wrappedObject: function()
768 return this._wrappedObject;
772 * @param {!Object} value
774 setWrappedObject: function(value)
776 console.assert(value, "wrappedObject should not be NULL");
777 console.assert(!(value instanceof Resource), "Binding a Resource object to another Resource object?");
778 this._wrappedObject = value;
779 this._bindObjectToResource(value);
785 proxyObject: function()
787 if (!this._proxyObject)
788 this._proxyObject = this._wrapObject();
789 return this._proxyObject;
793 * @return {?ResourceTrackingManager}
797 return this._resourceManager;
801 * @param {!ResourceTrackingManager} value
803 setManager: function(value)
805 this._resourceManager = value;
809 * @return {!Array.<!Call>}
817 * @return {?ContextResource}
819 contextResource: function()
821 if (this instanceof ContextResource)
822 return /** @type {!ContextResource} */ (this);
824 if (this._calculatingContextResource)
827 this._calculatingContextResource = true;
829 for (var i = 0, n = this._calls.length; i < n; ++i) {
830 result = this._calls[i].resource().contextResource();
834 delete this._calculatingContextResource;
835 console.assert(result, "Failed to find context resource for " + this._name + "@" + this._kindId);
840 * @return {!Array.<!TypeUtils.InternalResourceStateDescriptor>}
842 currentState: function()
845 var proxyObject = this.proxyObject();
848 var statePropertyNames = this._proxyStatePropertyNames || [];
849 for (var i = 0, n = statePropertyNames.length; i < n; ++i) {
850 var pname = statePropertyNames[i];
851 result.push({ name: pname, value: proxyObject[pname] });
853 result.push({ name: "context", value: this.contextResource() });
860 toDataURL: function()
866 * @param {!Cache.<!ReplayableResource>} cache
867 * @return {!ReplayableResource}
869 toReplayable: function(cache)
871 var result = cache.get(this._id);
879 result = new ReplayableResource(this, data);
880 cache.put(this._id, result); // Put into the cache early to avoid loops.
881 data.calls = this._calls.map(function(call) {
882 return call.toReplayable(cache);
884 this._populateReplayableData(data, cache);
885 var contextResource = this.contextResource();
886 if (contextResource !== this)
887 data.contextResource = Resource.toReplayable(contextResource, cache);
892 * @param {!Object} data
893 * @param {!Cache.<!ReplayableResource>} cache
895 _populateReplayableData: function(data, cache)
897 // Do nothing. Should be overridden by subclasses.
901 * @param {!Object} data
902 * @param {!Cache.<!Resource>} cache
903 * @return {!Resource}
905 replay: function(data, cache)
907 var resource = cache.get(data.id);
911 this._name = data.name;
912 this._kindId = data.kindId;
913 this._resourceManager = null;
915 this._boundResources = Object.create(null);
916 this._wrappedObject = null;
917 cache.put(data.id, this); // Put into the cache early to avoid loops.
918 this._doReplayCalls(data, cache);
919 console.assert(this._wrappedObject, "Resource should be reconstructed!");
924 * @param {!Object} data
925 * @param {!Cache.<!Resource>} cache
927 _doReplayCalls: function(data, cache)
929 for (var i = 0, n = data.calls.length; i < n; ++i)
930 this._calls.push(data.calls[i].replay(cache));
934 * @param {!Call} call
936 pushCall: function(call)
939 this._calls.push(call);
943 * @param {!Call} call
945 onCallReplayed: function(call)
947 // Ignore by default.
951 * @param {!Object} object
953 _bindObjectToResource: function(object)
955 Object.defineProperty(object, "__resourceObject", {
964 * @param {string} key
967 _registerBoundResource: function(key, obj)
969 var resource = Resource.forObject(obj);
971 this._boundResources[key] = resource;
973 delete this._boundResources[key];
979 _wrapObject: function()
981 var wrappedObject = this.wrappedObject();
984 var proxy = Object.create(wrappedObject.__proto__); // In order to emulate "instanceof".
986 var customWrapFunctions = this._customWrapFunctions();
987 /** @type {!Array.<string>} */
988 this._proxyStatePropertyNames = [];
991 * @param {string} property
994 function processProperty(property)
996 if (typeof wrappedObject[property] === "function") {
997 var customWrapFunction = customWrapFunctions[property];
998 if (customWrapFunction)
999 proxy[property] = this._wrapCustomFunction(this, wrappedObject, wrappedObject[property], property, customWrapFunction);
1001 proxy[property] = this._wrapFunction(this, wrappedObject, wrappedObject[property], property);
1002 } else if (TypeUtils.isEnumPropertyName(property, wrappedObject)) {
1003 // Fast access to enums and constants.
1004 proxy[property] = wrappedObject[property];
1006 this._proxyStatePropertyNames.push(property);
1007 Object.defineProperty(proxy, property, {
1010 var obj = wrappedObject[property];
1011 var resource = Resource.forObject(obj);
1012 return resource ? resource : obj;
1014 set: this._wrapPropertySetter(this, wrappedObject, property),
1021 for (var property in wrappedObject) {
1023 processProperty.call(this, property);
1026 return wrappedObject; // Nothing to proxy.
1028 this._bindObjectToResource(proxy);
1033 * @param {!Resource} resource
1034 * @param {!Object} originalObject
1035 * @param {!Function} originalFunction
1036 * @param {string} functionName
1037 * @param {!Function} customWrapFunction
1038 * @return {!Function}
1040 _wrapCustomFunction: function(resource, originalObject, originalFunction, functionName, customWrapFunction)
1044 var manager = resource.manager();
1045 var isCapturing = manager && manager.capturing();
1047 manager.captureArguments(resource, arguments);
1048 var wrapFunction = new Resource.WrapFunction(originalObject, originalFunction, functionName, arguments);
1049 customWrapFunction.apply(wrapFunction, arguments);
1051 var call = wrapFunction.call();
1052 call.setStackTrace(StackTrace.create(1, arguments.callee));
1053 manager.captureCall(call);
1055 return wrapFunction.result();
1060 * @param {!Resource} resource
1061 * @param {!Object} originalObject
1062 * @param {!Function} originalFunction
1063 * @param {string} functionName
1064 * @return {!Function}
1066 _wrapFunction: function(resource, originalObject, originalFunction, functionName)
1070 var manager = resource.manager();
1071 if (!manager || !manager.capturing())
1072 return originalFunction.apply(originalObject, arguments);
1073 manager.captureArguments(resource, arguments);
1074 var result = originalFunction.apply(originalObject, arguments);
1075 var stackTrace = StackTrace.create(1, arguments.callee);
1076 var call = new Call(resource, functionName, arguments, result, stackTrace);
1077 manager.captureCall(call);
1083 * @param {!Resource} resource
1084 * @param {!Object} originalObject
1085 * @param {string} propertyName
1086 * @return {function(*)}
1088 _wrapPropertySetter: function(resource, originalObject, propertyName)
1090 return function(value)
1092 resource._registerBoundResource(propertyName, value);
1093 var manager = resource.manager();
1094 if (!manager || !manager.capturing()) {
1095 originalObject[propertyName] = Resource.wrappedObject(value);
1098 var args = [propertyName, value];
1099 manager.captureArguments(resource, args);
1100 originalObject[propertyName] = Resource.wrappedObject(value);
1101 var stackTrace = StackTrace.create(1, arguments.callee);
1102 var call = new Call(resource, "", args, undefined, stackTrace);
1103 manager.captureCall(call);
1108 * @return {!Object.<string, !Function>}
1110 _customWrapFunctions: function()
1112 return Object.create(null); // May be overridden by subclasses.
1118 * @param {!Object} originalObject
1119 * @param {!Function} originalFunction
1120 * @param {string} functionName
1121 * @param {!Array|!Arguments} args
1123 Resource.WrapFunction = function(originalObject, originalFunction, functionName, args)
1125 this._originalObject = originalObject;
1126 this._originalFunction = originalFunction;
1127 this._functionName = functionName;
1129 this._resource = Resource.forObject(originalObject);
1130 console.assert(this._resource, "Expected a wrapped call on a Resource object.");
1133 Resource.WrapFunction.prototype = {
1139 if (!this._executed) {
1140 this._executed = true;
1141 this._result = this._originalFunction.apply(this._originalObject, this._args);
1143 return this._result;
1152 this._call = new Call(this._resource, this._functionName, this._args, this.result());
1159 overrideResult: function(result)
1161 var call = this.call();
1162 call.setResult(result);
1163 this._result = result;
1168 * @param {function(new:Resource, !Object, string)} resourceConstructor
1169 * @param {string} resourceName
1170 * @return {function(this:Resource.WrapFunction)}
1172 Resource.WrapFunction.resourceFactoryMethod = function(resourceConstructor, resourceName)
1174 /** @this {Resource.WrapFunction} */
1177 var wrappedObject = /** @type {?Object} */ (this.result());
1180 var resource = new resourceConstructor(wrappedObject, resourceName);
1181 var manager = this._resource.manager();
1183 manager.registerResource(resource);
1184 this.overrideResult(resource.proxyObject());
1185 resource.pushCall(this.call());
1191 * @param {!Resource} originalResource
1192 * @param {!Object} data
1194 function ReplayableResource(originalResource, data)
1196 this._proto = originalResource.__proto__;
1200 ReplayableResource.prototype = {
1206 return this._data.id;
1214 return this._data.name;
1220 description: function()
1222 return this._data.name + "@" + this._data.kindId;
1226 * @return {!ReplayableResource}
1228 contextResource: function()
1230 return this._data.contextResource || this;
1234 * @param {!Cache.<!Resource>} cache
1235 * @return {!Resource}
1237 replay: function(cache)
1239 var result = /** @type {!Resource} */ (Object.create(this._proto));
1240 result = result.replay(this._data, cache)
1241 console.assert(result.__proto__ === this._proto, "Wrong type of a replay result");
1247 * @param {!ReplayableResource|*} obj
1248 * @param {!Cache.<!Resource>} cache
1251 ReplayableResource.replay = function(obj, cache)
1253 return (obj instanceof ReplayableResource) ? obj.replay(cache).wrappedObject() : obj;
1258 * @extends {Resource}
1259 * @param {!Object} wrappedObject
1260 * @param {string} name
1262 function ContextResource(wrappedObject, name)
1264 Resource.call(this, wrappedObject, name);
1267 ContextResource.prototype = {
1268 __proto__: Resource.prototype
1273 * @extends {Resource}
1274 * @param {!Object} wrappedObject
1275 * @param {string} name
1277 function LogEverythingResource(wrappedObject, name)
1279 Resource.call(this, wrappedObject, name);
1282 LogEverythingResource.prototype = {
1285 * @return {!Object.<string, !Function>}
1287 _customWrapFunctions: function()
1289 var wrapFunctions = Object.create(null);
1290 var wrappedObject = this.wrappedObject();
1291 if (wrappedObject) {
1292 for (var property in wrappedObject) {
1293 /** @this {Resource.WrapFunction} */
1294 wrapFunctions[property] = function()
1296 this._resource.pushCall(this.call());
1300 return wrapFunctions;
1303 __proto__: Resource.prototype
1306 ////////////////////////////////////////////////////////////////////////////////
1308 ////////////////////////////////////////////////////////////////////////////////
1312 * @extends {Resource}
1313 * @param {!Object} wrappedObject
1314 * @param {string} name
1316 function WebGLBoundResource(wrappedObject, name)
1318 Resource.call(this, wrappedObject, name);
1319 /** @type {!Object.<string, *>} */
1323 WebGLBoundResource.prototype = {
1326 * @param {!Object} data
1327 * @param {!Cache.<!ReplayableResource>} cache
1329 _populateReplayableData: function(data, cache)
1331 var state = this._state;
1333 Object.keys(state).forEach(function(parameter) {
1334 data.state[parameter] = Resource.toReplayable(state[parameter], cache);
1340 * @param {!Object} data
1341 * @param {!Cache.<!Resource>} cache
1343 _doReplayCalls: function(data, cache)
1345 var gl = this._replayContextResource(data, cache).wrappedObject();
1347 /** @type {!Object.<string, !Array.<string>>} */
1348 var bindingsData = {
1349 TEXTURE_2D: ["bindTexture", "TEXTURE_BINDING_2D"],
1350 TEXTURE_CUBE_MAP: ["bindTexture", "TEXTURE_BINDING_CUBE_MAP"],
1351 ARRAY_BUFFER: ["bindBuffer", "ARRAY_BUFFER_BINDING"],
1352 ELEMENT_ARRAY_BUFFER: ["bindBuffer", "ELEMENT_ARRAY_BUFFER_BINDING"],
1353 FRAMEBUFFER: ["bindFramebuffer", "FRAMEBUFFER_BINDING"],
1354 RENDERBUFFER: ["bindRenderbuffer", "RENDERBUFFER_BINDING"]
1356 var originalBindings = {};
1357 Object.keys(bindingsData).forEach(function(bindingTarget) {
1358 var bindingParameter = bindingsData[bindingTarget][1];
1359 originalBindings[bindingTarget] = gl.getParameter(gl[bindingParameter]);
1363 Object.keys(data.state).forEach(function(parameter) {
1364 state[parameter] = ReplayableResource.replay(data.state[parameter], cache);
1366 this._state = state;
1367 Resource.prototype._doReplayCalls.call(this, data, cache);
1369 Object.keys(bindingsData).forEach(function(bindingTarget) {
1370 var bindMethodName = bindingsData[bindingTarget][0];
1371 gl[bindMethodName].call(gl, gl[bindingTarget], originalBindings[bindingTarget]);
1376 * @param {!Object} data
1377 * @param {!Cache.<!Resource>} cache
1378 * @return {?WebGLRenderingContextResource}
1380 _replayContextResource: function(data, cache)
1382 var calls = /** @type {!Array.<!ReplayableCall>} */ (data.calls);
1383 for (var i = 0, n = calls.length; i < n; ++i) {
1384 var resource = ReplayableResource.replay(calls[i].replayableResource(), cache);
1385 var contextResource = WebGLRenderingContextResource.forObject(resource);
1386 if (contextResource)
1387 return contextResource;
1393 * @param {number} target
1394 * @param {string} bindMethodName
1396 pushBinding: function(target, bindMethodName)
1398 if (this._state.bindTarget !== target) {
1399 this._state.bindTarget = target;
1400 this.pushCall(new Call(WebGLRenderingContextResource.forObject(this), bindMethodName, [target, this]));
1404 __proto__: Resource.prototype
1409 * @extends {WebGLBoundResource}
1410 * @param {!Object} wrappedObject
1411 * @param {string} name
1413 function WebGLTextureResource(wrappedObject, name)
1415 WebGLBoundResource.call(this, wrappedObject, name);
1418 WebGLTextureResource.prototype = {
1420 * @override (overrides @return type)
1421 * @return {!WebGLTexture}
1423 wrappedObject: function()
1425 return this._wrappedObject;
1430 * @return {!Array.<!TypeUtils.InternalResourceStateDescriptor>}
1432 currentState: function()
1435 var glResource = WebGLRenderingContextResource.forObject(this);
1436 var gl = glResource.wrappedObject();
1437 var texture = this.wrappedObject();
1438 if (!gl || !texture)
1440 result.push({ name: "isTexture", value: gl.isTexture(texture) });
1441 result.push({ name: "context", value: this.contextResource() });
1443 var target = this._state.bindTarget;
1444 if (typeof target !== "number")
1447 var bindingParameter;
1450 bindingParameter = gl.TEXTURE_BINDING_2D;
1452 case gl.TEXTURE_CUBE_MAP:
1453 bindingParameter = gl.TEXTURE_BINDING_CUBE_MAP;
1456 console.error("ASSERT_NOT_REACHED: unknown texture target " + target);
1459 result.push({ name: "target", value: target, valueIsEnum: true });
1461 var oldTexture = /** @type {!WebGLTexture} */ (gl.getParameter(bindingParameter));
1462 if (oldTexture !== texture)
1463 gl.bindTexture(target, texture);
1465 var textureParameters = [
1466 "TEXTURE_MAG_FILTER",
1467 "TEXTURE_MIN_FILTER",
1470 "TEXTURE_MAX_ANISOTROPY_EXT" // EXT_texture_filter_anisotropic extension
1472 glResource.queryStateValues(gl.getTexParameter, target, textureParameters, result);
1474 if (oldTexture !== texture)
1475 gl.bindTexture(target, oldTexture);
1481 * @param {!Object} data
1482 * @param {!Cache.<!Resource>} cache
1484 _doReplayCalls: function(data, cache)
1486 var gl = this._replayContextResource(data, cache).wrappedObject();
1489 WebGLRenderingContextResource.PixelStoreParameters.forEach(function(parameter) {
1490 state[parameter] = gl.getParameter(gl[parameter]);
1493 WebGLBoundResource.prototype._doReplayCalls.call(this, data, cache);
1495 WebGLRenderingContextResource.PixelStoreParameters.forEach(function(parameter) {
1496 gl.pixelStorei(gl[parameter], state[parameter]);
1502 * @param {!Call} call
1504 pushCall: function(call)
1506 var gl = WebGLRenderingContextResource.forObject(call.resource()).wrappedObject();
1507 WebGLRenderingContextResource.PixelStoreParameters.forEach(function(parameter) {
1508 var value = gl.getParameter(gl[parameter]);
1509 if (this._state[parameter] !== value) {
1510 this._state[parameter] = value;
1511 var pixelStoreCall = new Call(gl, "pixelStorei", [gl[parameter], value]);
1512 WebGLBoundResource.prototype.pushCall.call(this, pixelStoreCall);
1516 // FIXME: remove any older calls that no longer contribute to the resource state.
1517 // FIXME: optimize memory usage: maybe it's more efficient to store one texImage2D call instead of many texSubImage2D.
1518 WebGLBoundResource.prototype.pushCall.call(this, call);
1522 * Handles: texParameteri, texParameterf
1523 * @param {!Call} call
1525 pushCall_texParameter: function(call)
1527 var args = call.args();
1528 var pname = args[1];
1529 var param = args[2];
1530 if (this._state[pname] !== param) {
1531 this._state[pname] = param;
1532 WebGLBoundResource.prototype.pushCall.call(this, call);
1537 * Handles: copyTexImage2D, copyTexSubImage2D
1538 * copyTexImage2D and copyTexSubImage2D define a texture image with pixels from the current framebuffer.
1539 * @param {!Call} call
1541 pushCall_copyTexImage2D: function(call)
1543 var glResource = WebGLRenderingContextResource.forObject(call.resource());
1544 var gl = glResource.wrappedObject();
1545 var framebufferResource = /** @type {!WebGLFramebufferResource} */ (glResource.currentBinding(gl.FRAMEBUFFER));
1546 if (framebufferResource)
1547 this.pushCall(new Call(glResource, "bindFramebuffer", [gl.FRAMEBUFFER, framebufferResource]));
1549 // FIXME: Implement this case.
1550 console.error("ASSERT_NOT_REACHED: Could not properly process a gl." + call.functionName() + " call while the DRAWING BUFFER is bound.");
1552 this.pushCall(call);
1555 __proto__: WebGLBoundResource.prototype
1560 * @extends {Resource}
1561 * @param {!Object} wrappedObject
1562 * @param {string} name
1564 function WebGLProgramResource(wrappedObject, name)
1566 Resource.call(this, wrappedObject, name);
1569 WebGLProgramResource.prototype = {
1571 * @override (overrides @return type)
1572 * @return {!WebGLProgram}
1574 wrappedObject: function()
1576 return this._wrappedObject;
1581 * @return {!Array.<!TypeUtils.InternalResourceStateDescriptor>}
1583 currentState: function()
1586 * @param {!Object} obj
1587 * @param {!Array.<!TypeUtils.InternalResourceStateDescriptor>} output
1589 function convertToStateDescriptors(obj, output)
1591 for (var pname in obj)
1592 output.push({ name: pname, value: obj[pname], valueIsEnum: (pname === "type") });
1596 var program = this.wrappedObject();
1599 var glResource = WebGLRenderingContextResource.forObject(this);
1600 var gl = glResource.wrappedObject();
1601 var programParameters = ["DELETE_STATUS", "LINK_STATUS", "VALIDATE_STATUS"];
1602 glResource.queryStateValues(gl.getProgramParameter, program, programParameters, result);
1603 result.push({ name: "getProgramInfoLog", value: gl.getProgramInfoLog(program) });
1604 result.push({ name: "isProgram", value: gl.isProgram(program) });
1605 result.push({ name: "context", value: this.contextResource() });
1608 var callFormatter = CallFormatter.forResource(this);
1609 var shaders = gl.getAttachedShaders(program) || [];
1610 var shaderDescriptors = [];
1611 for (var i = 0, n = shaders.length; i < n; ++i) {
1612 var shaderResource = Resource.forObject(shaders[i]);
1613 var pname = callFormatter.enumNameForValue(shaderResource.type());
1614 shaderDescriptors.push({ name: pname, value: shaderResource });
1616 result.push({ name: "ATTACHED_SHADERS", values: shaderDescriptors, isArray: true });
1619 var uniformDescriptors = [];
1620 var uniforms = this._activeUniforms(true);
1621 for (var i = 0, n = uniforms.length; i < n; ++i) {
1624 convertToStateDescriptors(uniforms[i], values);
1625 uniformDescriptors.push({ name: pname, values: values });
1627 result.push({ name: "ACTIVE_UNIFORMS", values: uniformDescriptors, isArray: true });
1629 // ACTIVE_ATTRIBUTES
1630 var attributesCount = /** @type {number} */ (gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES));
1631 var attributeDescriptors = [];
1632 for (var i = 0; i < attributesCount; ++i) {
1633 var activeInfo = gl.getActiveAttrib(program, i);
1638 convertToStateDescriptors(activeInfo, values);
1639 attributeDescriptors.push({ name: pname, values: values });
1641 result.push({ name: "ACTIVE_ATTRIBUTES", values: attributeDescriptors, isArray: true });
1647 * @param {boolean=} includeAllInfo
1648 * @return {!Array.<{name:string, type:number, value:*, size:(number|undefined)}>}
1650 _activeUniforms: function(includeAllInfo)
1653 var program = this.wrappedObject();
1657 var gl = WebGLRenderingContextResource.forObject(this).wrappedObject();
1658 var uniformsCount = /** @type {number} */ (gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS));
1659 for (var i = 0; i < uniformsCount; ++i) {
1660 var activeInfo = gl.getActiveUniform(program, i);
1663 var uniformLocation = gl.getUniformLocation(program, activeInfo.name);
1664 if (!uniformLocation)
1666 var value = gl.getUniform(program, uniformLocation);
1667 var item = Object.create(null);
1668 item.name = activeInfo.name;
1669 item.type = activeInfo.type;
1672 item.size = activeInfo.size;
1673 uniforms.push(item);
1680 * @param {!Object} data
1681 * @param {!Cache.<!ReplayableResource>} cache
1683 _populateReplayableData: function(data, cache)
1685 var glResource = WebGLRenderingContextResource.forObject(this);
1686 var originalErrors = glResource.getAllErrors();
1687 data.uniforms = this._activeUniforms();
1688 glResource.restoreErrors(originalErrors);
1693 * @param {!Object} data
1694 * @param {!Cache.<!Resource>} cache
1696 _doReplayCalls: function(data, cache)
1698 Resource.prototype._doReplayCalls.call(this, data, cache);
1699 var gl = WebGLRenderingContextResource.forObject(this).wrappedObject();
1700 var program = this.wrappedObject();
1702 var originalProgram = /** @type {!WebGLProgram} */ (gl.getParameter(gl.CURRENT_PROGRAM));
1703 var currentProgram = originalProgram;
1705 data.uniforms.forEach(function(uniform) {
1706 var uniformLocation = gl.getUniformLocation(program, uniform.name);
1707 if (!uniformLocation)
1709 if (currentProgram !== program) {
1710 currentProgram = program;
1711 gl.useProgram(program);
1713 var methodName = this._uniformMethodNameByType(gl, uniform.type);
1714 if (methodName.indexOf("Matrix") === -1)
1715 gl[methodName].call(gl, uniformLocation, uniform.value);
1717 gl[methodName].call(gl, uniformLocation, false, uniform.value);
1720 if (currentProgram !== originalProgram)
1721 gl.useProgram(originalProgram);
1725 * @param {!WebGLRenderingContext} gl
1726 * @param {number} type
1729 _uniformMethodNameByType: function(gl, type)
1731 var uniformMethodNames = WebGLProgramResource._uniformMethodNames;
1732 if (!uniformMethodNames) {
1733 uniformMethodNames = {};
1734 uniformMethodNames[gl.FLOAT] = "uniform1f";
1735 uniformMethodNames[gl.FLOAT_VEC2] = "uniform2fv";
1736 uniformMethodNames[gl.FLOAT_VEC3] = "uniform3fv";
1737 uniformMethodNames[gl.FLOAT_VEC4] = "uniform4fv";
1738 uniformMethodNames[gl.INT] = "uniform1i";
1739 uniformMethodNames[gl.BOOL] = "uniform1i";
1740 uniformMethodNames[gl.SAMPLER_2D] = "uniform1i";
1741 uniformMethodNames[gl.SAMPLER_CUBE] = "uniform1i";
1742 uniformMethodNames[gl.INT_VEC2] = "uniform2iv";
1743 uniformMethodNames[gl.BOOL_VEC2] = "uniform2iv";
1744 uniformMethodNames[gl.INT_VEC3] = "uniform3iv";
1745 uniformMethodNames[gl.BOOL_VEC3] = "uniform3iv";
1746 uniformMethodNames[gl.INT_VEC4] = "uniform4iv";
1747 uniformMethodNames[gl.BOOL_VEC4] = "uniform4iv";
1748 uniformMethodNames[gl.FLOAT_MAT2] = "uniformMatrix2fv";
1749 uniformMethodNames[gl.FLOAT_MAT3] = "uniformMatrix3fv";
1750 uniformMethodNames[gl.FLOAT_MAT4] = "uniformMatrix4fv";
1751 WebGLProgramResource._uniformMethodNames = uniformMethodNames;
1753 console.assert(uniformMethodNames[type], "Unknown uniform type " + type);
1754 return uniformMethodNames[type];
1759 * @param {!Call} call
1761 pushCall: function(call)
1763 // FIXME: remove any older calls that no longer contribute to the resource state.
1764 // FIXME: handle multiple attachShader && detachShader.
1765 Resource.prototype.pushCall.call(this, call);
1768 __proto__: Resource.prototype
1773 * @extends {Resource}
1774 * @param {!Object} wrappedObject
1775 * @param {string} name
1777 function WebGLShaderResource(wrappedObject, name)
1779 Resource.call(this, wrappedObject, name);
1782 WebGLShaderResource.prototype = {
1784 * @override (overrides @return type)
1785 * @return {!WebGLShader}
1787 wrappedObject: function()
1789 return this._wrappedObject;
1797 var call = this._calls[0];
1798 if (call && call.functionName() === "createShader")
1799 return call.args()[0];
1800 console.error("ASSERT_NOT_REACHED: Failed to restore shader type from the log.", call);
1806 * @return {!Array.<!TypeUtils.InternalResourceStateDescriptor>}
1808 currentState: function()
1811 var shader = this.wrappedObject();
1814 var glResource = WebGLRenderingContextResource.forObject(this);
1815 var gl = glResource.wrappedObject();
1816 var shaderParameters = ["SHADER_TYPE", "DELETE_STATUS", "COMPILE_STATUS"];
1817 glResource.queryStateValues(gl.getShaderParameter, shader, shaderParameters, result);
1818 result.push({ name: "getShaderInfoLog", value: gl.getShaderInfoLog(shader) });
1819 result.push({ name: "getShaderSource", value: gl.getShaderSource(shader) });
1820 result.push({ name: "isShader", value: gl.isShader(shader) });
1821 result.push({ name: "context", value: this.contextResource() });
1823 // getShaderPrecisionFormat
1824 var shaderType = this.type();
1825 var precisionValues = [];
1826 var precisionParameters = ["LOW_FLOAT", "MEDIUM_FLOAT", "HIGH_FLOAT", "LOW_INT", "MEDIUM_INT", "HIGH_INT"];
1827 for (var i = 0, pname; pname = precisionParameters[i]; ++i)
1828 precisionValues.push({ name: pname, value: gl.getShaderPrecisionFormat(shaderType, gl[pname]) });
1829 result.push({ name: "getShaderPrecisionFormat", values: precisionValues });
1836 * @param {!Call} call
1838 pushCall: function(call)
1840 // FIXME: remove any older calls that no longer contribute to the resource state.
1841 // FIXME: handle multiple shaderSource calls.
1842 Resource.prototype.pushCall.call(this, call);
1845 __proto__: Resource.prototype
1850 * @extends {WebGLBoundResource}
1851 * @param {!Object} wrappedObject
1852 * @param {string} name
1854 function WebGLBufferResource(wrappedObject, name)
1856 WebGLBoundResource.call(this, wrappedObject, name);
1859 WebGLBufferResource.prototype = {
1861 * @override (overrides @return type)
1862 * @return {!WebGLBuffer}
1864 wrappedObject: function()
1866 return this._wrappedObject;
1870 * @return {?ArrayBufferView}
1872 cachedBufferData: function()
1875 * Creates a view to a given buffer, does NOT copy the buffer.
1876 * @param {!ArrayBuffer|!ArrayBufferView} buffer
1877 * @return {!Uint8Array}
1879 function createUint8ArrayBufferView(buffer)
1881 return buffer instanceof ArrayBuffer ? new Uint8Array(buffer) : new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
1884 if (!this._cachedBufferData) {
1885 for (var i = this._calls.length - 1; i >= 0; --i) {
1886 var call = this._calls[i];
1887 if (call.functionName() === "bufferData") {
1888 var sizeOrData = /** @type {number|!ArrayBuffer|!ArrayBufferView} */ (call.args()[1]);
1889 if (typeof sizeOrData === "number")
1890 this._cachedBufferData = new ArrayBuffer(sizeOrData);
1892 this._cachedBufferData = sizeOrData;
1893 this._lastBufferSubDataIndex = i + 1;
1897 if (!this._cachedBufferData)
1901 // Apply any "bufferSubData" calls that have not been applied yet.
1903 while (this._lastBufferSubDataIndex < this._calls.length) {
1904 var call = this._calls[this._lastBufferSubDataIndex++];
1905 if (call.functionName() !== "bufferSubData")
1907 var offset = /** @type {number} */ (call.args()[1]);
1908 var data = /** @type {!ArrayBuffer|!ArrayBufferView} */ (call.args()[2]);
1909 var view = createUint8ArrayBufferView(data);
1910 if (!bufferDataView)
1911 bufferDataView = createUint8ArrayBufferView(this._cachedBufferData);
1912 bufferDataView.set(view, offset);
1914 var isFullReplacement = (offset === 0 && bufferDataView.length === view.length);
1915 if (this._cachedBufferData instanceof ArrayBuffer) {
1916 // The buffer data has no type yet. Try to guess from the "bufferSubData" call.
1917 var typedArrayClass = TypeUtils.typedArrayClass(data);
1918 if (typedArrayClass)
1919 this._cachedBufferData = new typedArrayClass(this._cachedBufferData); // Does not copy the buffer.
1920 } else if (isFullReplacement) {
1921 var typedArrayClass = TypeUtils.typedArrayClass(data);
1922 if (typedArrayClass) {
1923 var typedArrayData = /** @type {!ArrayBufferView} */ (data);
1924 this._cachedBufferData = new typedArrayClass(this._cachedBufferData.buffer, this._cachedBufferData.byteOffset, typedArrayData.length); // Does not copy the buffer.
1929 if (this._cachedBufferData instanceof ArrayBuffer) {
1930 // If we failed to guess the data type yet, use Uint8Array.
1931 return new Uint8Array(this._cachedBufferData);
1933 return this._cachedBufferData;
1938 * @return {!Array.<!TypeUtils.InternalResourceStateDescriptor>}
1940 currentState: function()
1943 var glResource = WebGLRenderingContextResource.forObject(this);
1944 var gl = glResource.wrappedObject();
1945 var buffer = this.wrappedObject();
1948 result.push({ name: "isBuffer", value: gl.isBuffer(buffer) });
1949 result.push({ name: "context", value: this.contextResource() });
1951 var target = this._state.bindTarget;
1952 if (typeof target !== "number")
1955 var bindingParameter;
1957 case gl.ARRAY_BUFFER:
1958 bindingParameter = gl.ARRAY_BUFFER_BINDING;
1960 case gl.ELEMENT_ARRAY_BUFFER:
1961 bindingParameter = gl.ELEMENT_ARRAY_BUFFER_BINDING;
1964 console.error("ASSERT_NOT_REACHED: unknown buffer target " + target);
1967 result.push({ name: "target", value: target, valueIsEnum: true });
1969 var oldBuffer = /** @type {!WebGLBuffer} */ (gl.getParameter(bindingParameter));
1970 if (oldBuffer !== buffer)
1971 gl.bindBuffer(target, buffer);
1973 var bufferParameters = ["BUFFER_SIZE", "BUFFER_USAGE"];
1974 glResource.queryStateValues(gl.getBufferParameter, target, bufferParameters, result);
1976 if (oldBuffer !== buffer)
1977 gl.bindBuffer(target, oldBuffer);
1980 var data = this.cachedBufferData();
1982 result.push({ name: "bufferData", value: data });
1984 console.error("Exception while restoring bufferData", e);
1991 * @param {!Call} call
1993 pushCall_bufferData: function(call)
1995 // FIXME: remove any older calls that no longer contribute to the resource state.
1996 delete this._cachedBufferData;
1997 delete this._lastBufferSubDataIndex;
1998 WebGLBoundResource.prototype.pushCall.call(this, call);
2002 * @param {!Call} call
2004 pushCall_bufferSubData: function(call)
2006 // FIXME: Optimize memory for bufferSubData.
2007 WebGLBoundResource.prototype.pushCall.call(this, call);
2010 __proto__: WebGLBoundResource.prototype
2015 * @extends {WebGLBoundResource}
2016 * @param {!Object} wrappedObject
2017 * @param {string} name
2019 function WebGLFramebufferResource(wrappedObject, name)
2021 WebGLBoundResource.call(this, wrappedObject, name);
2024 WebGLFramebufferResource.prototype = {
2026 * @override (overrides @return type)
2027 * @return {!WebGLFramebuffer}
2029 wrappedObject: function()
2031 return this._wrappedObject;
2036 * @return {!Array.<!TypeUtils.InternalResourceStateDescriptor>}
2038 currentState: function()
2041 var framebuffer = this.wrappedObject();
2044 var gl = WebGLRenderingContextResource.forObject(this).wrappedObject();
2046 var oldFramebuffer = /** @type {!WebGLFramebuffer} */ (gl.getParameter(gl.FRAMEBUFFER_BINDING));
2047 if (oldFramebuffer !== framebuffer)
2048 gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
2050 var attachmentParameters = ["COLOR_ATTACHMENT0", "DEPTH_ATTACHMENT", "STENCIL_ATTACHMENT"];
2051 var framebufferParameters = ["FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE", "FRAMEBUFFER_ATTACHMENT_OBJECT_NAME", "FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL", "FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE"];
2052 for (var i = 0, attachment; attachment = attachmentParameters[i]; ++i) {
2054 for (var j = 0, pname; pname = framebufferParameters[j]; ++j) {
2055 var value = gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl[attachment], gl[pname]);
2056 value = Resource.forObject(value) || value;
2057 values.push({ name: pname, value: value, valueIsEnum: WebGLRenderingContextResource.GetResultIsEnum[pname] });
2059 result.push({ name: attachment, values: values });
2061 result.push({ name: "isFramebuffer", value: gl.isFramebuffer(framebuffer) });
2062 result.push({ name: "context", value: this.contextResource() });
2064 if (oldFramebuffer !== framebuffer)
2065 gl.bindFramebuffer(gl.FRAMEBUFFER, oldFramebuffer);
2071 * @param {!Call} call
2073 pushCall: function(call)
2075 // FIXME: remove any older calls that no longer contribute to the resource state.
2076 WebGLBoundResource.prototype.pushCall.call(this, call);
2079 __proto__: WebGLBoundResource.prototype
2084 * @extends {WebGLBoundResource}
2085 * @param {!Object} wrappedObject
2086 * @param {string} name
2088 function WebGLRenderbufferResource(wrappedObject, name)
2090 WebGLBoundResource.call(this, wrappedObject, name);
2093 WebGLRenderbufferResource.prototype = {
2095 * @override (overrides @return type)
2096 * @return {!WebGLRenderbuffer}
2098 wrappedObject: function()
2100 return this._wrappedObject;
2105 * @return {!Array.<!TypeUtils.InternalResourceStateDescriptor>}
2107 currentState: function()
2110 var renderbuffer = this.wrappedObject();
2113 var glResource = WebGLRenderingContextResource.forObject(this);
2114 var gl = glResource.wrappedObject();
2116 var oldRenderbuffer = /** @type {!WebGLRenderbuffer} */ (gl.getParameter(gl.RENDERBUFFER_BINDING));
2117 if (oldRenderbuffer !== renderbuffer)
2118 gl.bindRenderbuffer(gl.RENDERBUFFER, renderbuffer);
2120 var renderbufferParameters = ["RENDERBUFFER_WIDTH", "RENDERBUFFER_HEIGHT", "RENDERBUFFER_INTERNAL_FORMAT", "RENDERBUFFER_RED_SIZE", "RENDERBUFFER_GREEN_SIZE", "RENDERBUFFER_BLUE_SIZE", "RENDERBUFFER_ALPHA_SIZE", "RENDERBUFFER_DEPTH_SIZE", "RENDERBUFFER_STENCIL_SIZE"];
2121 glResource.queryStateValues(gl.getRenderbufferParameter, gl.RENDERBUFFER, renderbufferParameters, result);
2122 result.push({ name: "isRenderbuffer", value: gl.isRenderbuffer(renderbuffer) });
2123 result.push({ name: "context", value: this.contextResource() });
2125 if (oldRenderbuffer !== renderbuffer)
2126 gl.bindRenderbuffer(gl.RENDERBUFFER, oldRenderbuffer);
2132 * @param {!Call} call
2134 pushCall: function(call)
2136 // FIXME: remove any older calls that no longer contribute to the resource state.
2137 WebGLBoundResource.prototype.pushCall.call(this, call);
2140 __proto__: WebGLBoundResource.prototype
2145 * @extends {Resource}
2146 * @param {!Object} wrappedObject
2147 * @param {string} name
2149 function WebGLUniformLocationResource(wrappedObject, name)
2151 Resource.call(this, wrappedObject, name);
2154 WebGLUniformLocationResource.prototype = {
2156 * @override (overrides @return type)
2157 * @return {!WebGLUniformLocation}
2159 wrappedObject: function()
2161 return this._wrappedObject;
2165 * @return {?WebGLProgramResource}
2169 var call = this._calls[0];
2170 if (call && call.functionName() === "getUniformLocation")
2171 return /** @type {!WebGLProgramResource} */ (Resource.forObject(call.args()[0]));
2172 console.error("ASSERT_NOT_REACHED: Failed to restore WebGLUniformLocation from the log.", call);
2181 var call = this._calls[0];
2182 if (call && call.functionName() === "getUniformLocation")
2183 return call.args()[1];
2184 console.error("ASSERT_NOT_REACHED: Failed to restore WebGLUniformLocation from the log.", call);
2190 * @return {!Array.<!TypeUtils.InternalResourceStateDescriptor>}
2192 currentState: function()
2195 var location = this.wrappedObject();
2198 var programResource = this.program();
2199 var program = programResource && programResource.wrappedObject();
2202 var gl = WebGLRenderingContextResource.forObject(this).wrappedObject();
2203 var uniformValue = gl.getUniform(program, location);
2204 var name = this.name();
2205 result.push({ name: "name", value: name });
2206 result.push({ name: "program", value: programResource });
2207 result.push({ name: "value", value: uniformValue });
2208 result.push({ name: "context", value: this.contextResource() });
2210 if (typeof this._type !== "number") {
2211 var altName = name + "[0]";
2212 var uniformsCount = /** @type {number} */ (gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS));
2213 for (var i = 0; i < uniformsCount; ++i) {
2214 var activeInfo = gl.getActiveUniform(program, i);
2217 if (activeInfo.name === name || activeInfo.name === altName) {
2218 this._type = activeInfo.type;
2219 this._size = activeInfo.size;
2220 if (activeInfo.name === name)
2225 if (typeof this._type === "number")
2226 result.push({ name: "type", value: this._type, valueIsEnum: true });
2227 if (typeof this._size === "number")
2228 result.push({ name: "size", value: this._size });
2235 * @param {!Object} data
2236 * @param {!Cache.<!ReplayableResource>} cache
2238 _populateReplayableData: function(data, cache)
2240 data.type = this._type;
2241 data.size = this._size;
2246 * @param {!Object} data
2247 * @param {!Cache.<!Resource>} cache
2249 _doReplayCalls: function(data, cache)
2251 this._type = data.type;
2252 this._size = data.size;
2253 Resource.prototype._doReplayCalls.call(this, data, cache);
2256 __proto__: Resource.prototype
2261 * @extends {ContextResource}
2262 * @param {!WebGLRenderingContext} glContext
2264 function WebGLRenderingContextResource(glContext)
2266 ContextResource.call(this, glContext, "WebGLRenderingContext");
2267 /** @type {?Object.<number, boolean>} */
2268 this._customErrors = null;
2269 /** @type {!Object.<string, string>} */
2270 this._extensions = {};
2271 /** @type {!Object.<string, number>} */
2272 this._extensionEnums = {};
2277 * @type {!Array.<string>}
2279 WebGLRenderingContextResource.GLCapabilities = [
2284 "POLYGON_OFFSET_FILL",
2285 "SAMPLE_ALPHA_TO_COVERAGE",
2293 * @type {!Array.<string>}
2295 WebGLRenderingContextResource.PixelStoreParameters = [
2298 "UNPACK_COLORSPACE_CONVERSION_WEBGL",
2299 "UNPACK_FLIP_Y_WEBGL",
2300 "UNPACK_PREMULTIPLY_ALPHA_WEBGL"
2305 * @type {!Array.<string>}
2307 WebGLRenderingContextResource.StateParameters = [
2309 "ARRAY_BUFFER_BINDING",
2313 "BLEND_EQUATION_ALPHA",
2314 "BLEND_EQUATION_RGB",
2317 "COLOR_CLEAR_VALUE",
2321 "DEPTH_CLEAR_VALUE",
2325 "ELEMENT_ARRAY_BUFFER_BINDING",
2326 "FRAGMENT_SHADER_DERIVATIVE_HINT_OES", // OES_standard_derivatives extension
2327 "FRAMEBUFFER_BINDING",
2329 "GENERATE_MIPMAP_HINT",
2332 "POLYGON_OFFSET_FACTOR",
2333 "POLYGON_OFFSET_UNITS",
2334 "RENDERBUFFER_BINDING",
2335 "SAMPLE_COVERAGE_INVERT",
2336 "SAMPLE_COVERAGE_VALUE",
2338 "STENCIL_BACK_FAIL",
2339 "STENCIL_BACK_FUNC",
2340 "STENCIL_BACK_PASS_DEPTH_FAIL",
2341 "STENCIL_BACK_PASS_DEPTH_PASS",
2343 "STENCIL_BACK_VALUE_MASK",
2344 "STENCIL_BACK_WRITEMASK",
2345 "STENCIL_CLEAR_VALUE",
2348 "STENCIL_PASS_DEPTH_FAIL",
2349 "STENCIL_PASS_DEPTH_PASS",
2351 "STENCIL_VALUE_MASK",
2352 "STENCIL_WRITEMASK",
2354 "UNPACK_COLORSPACE_CONVERSION_WEBGL",
2355 "UNPACK_FLIP_Y_WEBGL",
2356 "UNPACK_PREMULTIPLY_ALPHA_WEBGL",
2357 "VERTEX_ARRAY_BINDING_OES", // OES_vertex_array_object extension
2362 * True for those enums that return also an enum via a getter API method (e.g. getParameter, getShaderParameter, etc.).
2364 * @type {!Object.<string, boolean>}
2366 WebGLRenderingContextResource.GetResultIsEnum = TypeUtils.createPrefixedPropertyNamesSet([
2367 // gl.getParameter()
2371 "BLEND_EQUATION_ALPHA",
2372 "BLEND_EQUATION_RGB",
2378 "GENERATE_MIPMAP_HINT",
2379 "FRAGMENT_SHADER_DERIVATIVE_HINT_OES",
2380 "STENCIL_BACK_FAIL",
2381 "STENCIL_BACK_FUNC",
2382 "STENCIL_BACK_PASS_DEPTH_FAIL",
2383 "STENCIL_BACK_PASS_DEPTH_PASS",
2386 "STENCIL_PASS_DEPTH_FAIL",
2387 "STENCIL_PASS_DEPTH_PASS",
2388 "UNPACK_COLORSPACE_CONVERSION_WEBGL",
2389 // gl.getBufferParameter()
2391 // gl.getFramebufferAttachmentParameter()
2392 "FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE",
2393 // gl.getRenderbufferParameter()
2394 "RENDERBUFFER_INTERNAL_FORMAT",
2395 // gl.getTexParameter()
2396 "TEXTURE_MAG_FILTER",
2397 "TEXTURE_MIN_FILTER",
2400 // gl.getShaderParameter()
2402 // gl.getVertexAttrib()
2403 "VERTEX_ATTRIB_ARRAY_TYPE"
2408 * @type {!Object.<string, boolean>}
2410 WebGLRenderingContextResource.DrawingMethods = TypeUtils.createPrefixedPropertyNamesSet([
2418 * @return {?WebGLRenderingContextResource}
2420 WebGLRenderingContextResource.forObject = function(obj)
2422 var resource = Resource.forObject(obj);
2425 resource = resource.contextResource();
2426 return (resource instanceof WebGLRenderingContextResource) ? resource : null;
2429 WebGLRenderingContextResource.prototype = {
2431 * @override (overrides @return type)
2432 * @return {!WebGLRenderingContext}
2434 wrappedObject: function()
2436 return this._wrappedObject;
2443 toDataURL: function()
2445 return this.wrappedObject().canvas.toDataURL();
2449 * @return {!Array.<number>}
2451 getAllErrors: function()
2454 var gl = this.wrappedObject();
2457 var error = gl.getError();
2458 if (error === gl.NO_ERROR)
2460 this.clearError(error);
2464 if (this._customErrors) {
2465 for (var key in this._customErrors) {
2466 var error = Number(key);
2469 delete this._customErrors;
2475 * @param {!Array.<number>} errors
2477 restoreErrors: function(errors)
2479 var gl = this.wrappedObject();
2481 var wasError = false;
2482 while (gl.getError() !== gl.NO_ERROR)
2484 console.assert(!wasError, "Error(s) while capturing current WebGL state.");
2487 delete this._customErrors;
2489 this._customErrors = {};
2490 for (var i = 0, n = errors.length; i < n; ++i)
2491 this._customErrors[errors[i]] = true;
2496 * @param {number} error
2498 clearError: function(error)
2500 if (this._customErrors)
2501 delete this._customErrors[error];
2507 nextError: function()
2509 if (this._customErrors) {
2510 for (var key in this._customErrors) {
2511 var error = Number(key);
2512 delete this._customErrors[error];
2516 delete this._customErrors;
2517 var gl = this.wrappedObject();
2518 return gl ? gl.NO_ERROR : 0;
2522 * @param {string} name
2523 * @param {?Object} obj
2525 registerWebGLExtension: function(name, obj)
2527 // FIXME: Wrap OES_vertex_array_object extension.
2528 var lowerName = name.toLowerCase();
2529 if (obj && !this._extensions[lowerName]) {
2530 this._extensions[lowerName] = name;
2531 for (var property in obj) {
2532 if (TypeUtils.isEnumPropertyName(property, obj))
2533 this._extensionEnums[property] = /** @type {number} */ (obj[property]);
2539 * @param {string} name
2540 * @return {number|undefined}
2542 _enumValueForName: function(name)
2544 if (typeof this._extensionEnums[name] === "number")
2545 return this._extensionEnums[name];
2546 var gl = this.wrappedObject();
2547 return (typeof gl[name] === "number" ? gl[name] : undefined);
2551 * @param {function(this:WebGLRenderingContext, T, number):*} func
2552 * @param {T} targetOrWebGLObject
2553 * @param {!Array.<string>} pnames
2554 * @param {!Array.<!TypeUtils.InternalResourceStateDescriptor>} output
2557 queryStateValues: function(func, targetOrWebGLObject, pnames, output)
2559 var gl = this.wrappedObject();
2560 for (var i = 0, pname; pname = pnames[i]; ++i) {
2561 var enumValue = this._enumValueForName(pname);
2562 if (typeof enumValue !== "number")
2564 var value = func.call(gl, targetOrWebGLObject, enumValue);
2565 value = Resource.forObject(value) || value;
2566 output.push({ name: pname, value: value, valueIsEnum: WebGLRenderingContextResource.GetResultIsEnum[pname] });
2572 * @return {!Array.<!TypeUtils.InternalResourceStateDescriptor>}
2574 currentState: function()
2577 * @param {!Object} obj
2578 * @param {!Array.<!TypeUtils.InternalResourceStateDescriptor>} output
2580 function convertToStateDescriptors(obj, output)
2582 for (var pname in obj)
2583 output.push({ name: pname, value: obj[pname], valueIsEnum: WebGLRenderingContextResource.GetResultIsEnum[pname] });
2586 var gl = this.wrappedObject();
2587 var glState = this._internalCurrentState(null);
2589 // VERTEX_ATTRIB_ARRAYS
2590 var vertexAttribStates = [];
2591 for (var i = 0, n = glState.VERTEX_ATTRIB_ARRAYS.length; i < n; ++i) {
2594 convertToStateDescriptors(glState.VERTEX_ATTRIB_ARRAYS[i], values);
2595 vertexAttribStates.push({ name: pname, values: values });
2597 delete glState.VERTEX_ATTRIB_ARRAYS;
2600 var textureUnits = [];
2601 for (var i = 0, n = glState.TEXTURE_UNITS.length; i < n; ++i) {
2602 var pname = "TEXTURE" + i;
2604 convertToStateDescriptors(glState.TEXTURE_UNITS[i], values);
2605 textureUnits.push({ name: pname, values: values });
2607 delete glState.TEXTURE_UNITS;
2610 convertToStateDescriptors(glState, result);
2611 result.push({ name: "VERTEX_ATTRIB_ARRAYS", values: vertexAttribStates, isArray: true });
2612 result.push({ name: "TEXTURE_UNITS", values: textureUnits, isArray: true });
2614 var textureBindingParameters = ["TEXTURE_BINDING_2D", "TEXTURE_BINDING_CUBE_MAP"];
2615 for (var i = 0, pname; pname = textureBindingParameters[i]; ++i) {
2616 var value = gl.getParameter(gl[pname]);
2617 value = Resource.forObject(value) || value;
2618 result.push({ name: pname, value: value });
2621 // ENABLED_EXTENSIONS
2622 var enabledExtensions = [];
2623 for (var lowerName in this._extensions) {
2624 var pname = this._extensions[lowerName];
2625 var value = gl.getExtension(pname);
2626 value = Resource.forObject(value) || value;
2627 enabledExtensions.push({ name: pname, value: value });
2629 result.push({ name: "ENABLED_EXTENSIONS", values: enabledExtensions, isArray: true });
2635 * @param {?Cache.<!ReplayableResource>} cache
2636 * @return {!Object.<string, *>}
2638 _internalCurrentState: function(cache)
2641 * @param {!Resource|*} obj
2642 * @return {!Resource|!ReplayableResource|*}
2644 function maybeToReplayable(obj)
2646 return cache ? Resource.toReplayable(obj, cache) : (Resource.forObject(obj) || obj);
2649 var gl = this.wrappedObject();
2650 var originalErrors = this.getAllErrors();
2652 // Take a full GL state snapshot.
2653 var glState = Object.create(null);
2654 WebGLRenderingContextResource.GLCapabilities.forEach(function(parameter) {
2655 glState[parameter] = gl.isEnabled(gl[parameter]);
2657 for (var i = 0, pname; pname = WebGLRenderingContextResource.StateParameters[i]; ++i) {
2658 var enumValue = this._enumValueForName(pname);
2659 if (typeof enumValue === "number")
2660 glState[pname] = maybeToReplayable(gl.getParameter(enumValue));
2663 // VERTEX_ATTRIB_ARRAYS
2664 var maxVertexAttribs = /** @type {number} */ (gl.getParameter(gl.MAX_VERTEX_ATTRIBS));
2665 var vertexAttribParameters = [
2666 "VERTEX_ATTRIB_ARRAY_BUFFER_BINDING",
2667 "VERTEX_ATTRIB_ARRAY_ENABLED",
2668 "VERTEX_ATTRIB_ARRAY_SIZE",
2669 "VERTEX_ATTRIB_ARRAY_STRIDE",
2670 "VERTEX_ATTRIB_ARRAY_TYPE",
2671 "VERTEX_ATTRIB_ARRAY_NORMALIZED",
2672 "CURRENT_VERTEX_ATTRIB",
2673 "VERTEX_ATTRIB_ARRAY_DIVISOR_ANGLE" // ANGLE_instanced_arrays extension
2675 var vertexAttribStates = [];
2676 for (var index = 0; index < maxVertexAttribs; ++index) {
2677 var state = Object.create(null);
2678 for (var i = 0, pname; pname = vertexAttribParameters[i]; ++i) {
2679 var enumValue = this._enumValueForName(pname);
2680 if (typeof enumValue === "number")
2681 state[pname] = maybeToReplayable(gl.getVertexAttrib(index, enumValue));
2683 state.VERTEX_ATTRIB_ARRAY_POINTER = gl.getVertexAttribOffset(index, gl.VERTEX_ATTRIB_ARRAY_POINTER);
2684 vertexAttribStates.push(state);
2686 glState.VERTEX_ATTRIB_ARRAYS = vertexAttribStates;
2689 var savedActiveTexture = /** @type {number} */ (gl.getParameter(gl.ACTIVE_TEXTURE));
2690 var maxTextureImageUnits = /** @type {number} */ (gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS));
2691 var textureUnits = [];
2692 for (var i = 0; i < maxTextureImageUnits; ++i) {
2693 gl.activeTexture(gl.TEXTURE0 + i);
2694 var state = Object.create(null);
2695 state.TEXTURE_2D = maybeToReplayable(gl.getParameter(gl.TEXTURE_BINDING_2D));
2696 state.TEXTURE_CUBE_MAP = maybeToReplayable(gl.getParameter(gl.TEXTURE_BINDING_CUBE_MAP));
2697 textureUnits.push(state);
2699 glState.TEXTURE_UNITS = textureUnits;
2700 gl.activeTexture(savedActiveTexture);
2702 this.restoreErrors(originalErrors);
2708 * @param {!Object} data
2709 * @param {!Cache.<!ReplayableResource>} cache
2711 _populateReplayableData: function(data, cache)
2713 var gl = this.wrappedObject();
2714 data.originalCanvas = gl.canvas;
2715 data.originalContextAttributes = gl.getContextAttributes();
2716 data.extensions = TypeUtils.cloneObject(this._extensions);
2717 data.extensionEnums = TypeUtils.cloneObject(this._extensionEnums);
2718 data.glState = this._internalCurrentState(cache);
2723 * @param {!Object} data
2724 * @param {!Cache.<!Resource>} cache
2726 _doReplayCalls: function(data, cache)
2728 this._customErrors = null;
2729 this._extensions = TypeUtils.cloneObject(data.extensions) || {};
2730 this._extensionEnums = TypeUtils.cloneObject(data.extensionEnums) || {};
2732 var canvas = data.originalCanvas.cloneNode(true);
2733 var replayContext = null;
2734 var contextIds = ["experimental-webgl", "webkit-3d", "3d"];
2735 for (var i = 0, contextId; contextId = contextIds[i]; ++i) {
2736 replayContext = canvas.getContext(contextId, data.originalContextAttributes);
2741 console.assert(replayContext, "Failed to create a WebGLRenderingContext for the replay.");
2743 var gl = /** @type {!WebGLRenderingContext} */ (Resource.wrappedObject(replayContext));
2744 this.setWrappedObject(gl);
2746 // Enable corresponding WebGL extensions.
2747 for (var name in this._extensions)
2748 gl.getExtension(name);
2750 var glState = data.glState;
2751 gl.bindFramebuffer(gl.FRAMEBUFFER, /** @type {!WebGLFramebuffer} */ (ReplayableResource.replay(glState.FRAMEBUFFER_BINDING, cache)));
2752 gl.bindRenderbuffer(gl.RENDERBUFFER, /** @type {!WebGLRenderbuffer} */ (ReplayableResource.replay(glState.RENDERBUFFER_BINDING, cache)));
2754 // Enable or disable server-side GL capabilities.
2755 WebGLRenderingContextResource.GLCapabilities.forEach(function(parameter) {
2756 console.assert(parameter in glState);
2757 if (glState[parameter])
2758 gl.enable(gl[parameter]);
2760 gl.disable(gl[parameter]);
2763 gl.blendColor(glState.BLEND_COLOR[0], glState.BLEND_COLOR[1], glState.BLEND_COLOR[2], glState.BLEND_COLOR[3]);
2764 gl.blendEquationSeparate(glState.BLEND_EQUATION_RGB, glState.BLEND_EQUATION_ALPHA);
2765 gl.blendFuncSeparate(glState.BLEND_SRC_RGB, glState.BLEND_DST_RGB, glState.BLEND_SRC_ALPHA, glState.BLEND_DST_ALPHA);
2766 gl.clearColor(glState.COLOR_CLEAR_VALUE[0], glState.COLOR_CLEAR_VALUE[1], glState.COLOR_CLEAR_VALUE[2], glState.COLOR_CLEAR_VALUE[3]);
2767 gl.clearDepth(glState.DEPTH_CLEAR_VALUE);
2768 gl.clearStencil(glState.STENCIL_CLEAR_VALUE);
2769 gl.colorMask(glState.COLOR_WRITEMASK[0], glState.COLOR_WRITEMASK[1], glState.COLOR_WRITEMASK[2], glState.COLOR_WRITEMASK[3]);
2770 gl.cullFace(glState.CULL_FACE_MODE);
2771 gl.depthFunc(glState.DEPTH_FUNC);
2772 gl.depthMask(glState.DEPTH_WRITEMASK);
2773 gl.depthRange(glState.DEPTH_RANGE[0], glState.DEPTH_RANGE[1]);
2774 gl.frontFace(glState.FRONT_FACE);
2775 gl.hint(gl.GENERATE_MIPMAP_HINT, glState.GENERATE_MIPMAP_HINT);
2776 gl.lineWidth(glState.LINE_WIDTH);
2778 var enumValue = this._enumValueForName("FRAGMENT_SHADER_DERIVATIVE_HINT_OES");
2779 if (typeof enumValue === "number")
2780 gl.hint(enumValue, glState.FRAGMENT_SHADER_DERIVATIVE_HINT_OES);
2782 WebGLRenderingContextResource.PixelStoreParameters.forEach(function(parameter) {
2783 gl.pixelStorei(gl[parameter], glState[parameter]);
2786 gl.polygonOffset(glState.POLYGON_OFFSET_FACTOR, glState.POLYGON_OFFSET_UNITS);
2787 gl.sampleCoverage(glState.SAMPLE_COVERAGE_VALUE, glState.SAMPLE_COVERAGE_INVERT);
2788 gl.stencilFuncSeparate(gl.FRONT, glState.STENCIL_FUNC, glState.STENCIL_REF, glState.STENCIL_VALUE_MASK);
2789 gl.stencilFuncSeparate(gl.BACK, glState.STENCIL_BACK_FUNC, glState.STENCIL_BACK_REF, glState.STENCIL_BACK_VALUE_MASK);
2790 gl.stencilOpSeparate(gl.FRONT, glState.STENCIL_FAIL, glState.STENCIL_PASS_DEPTH_FAIL, glState.STENCIL_PASS_DEPTH_PASS);
2791 gl.stencilOpSeparate(gl.BACK, glState.STENCIL_BACK_FAIL, glState.STENCIL_BACK_PASS_DEPTH_FAIL, glState.STENCIL_BACK_PASS_DEPTH_PASS);
2792 gl.stencilMaskSeparate(gl.FRONT, glState.STENCIL_WRITEMASK);
2793 gl.stencilMaskSeparate(gl.BACK, glState.STENCIL_BACK_WRITEMASK);
2795 gl.scissor(glState.SCISSOR_BOX[0], glState.SCISSOR_BOX[1], glState.SCISSOR_BOX[2], glState.SCISSOR_BOX[3]);
2796 gl.viewport(glState.VIEWPORT[0], glState.VIEWPORT[1], glState.VIEWPORT[2], glState.VIEWPORT[3]);
2798 gl.useProgram(/** @type {!WebGLProgram} */ (ReplayableResource.replay(glState.CURRENT_PROGRAM, cache)));
2800 // VERTEX_ATTRIB_ARRAYS
2801 var maxVertexAttribs = /** @type {number} */ (gl.getParameter(gl.MAX_VERTEX_ATTRIBS));
2802 for (var i = 0; i < maxVertexAttribs; ++i) {
2803 var state = glState.VERTEX_ATTRIB_ARRAYS[i] || {};
2804 if (state.VERTEX_ATTRIB_ARRAY_ENABLED)
2805 gl.enableVertexAttribArray(i);
2807 gl.disableVertexAttribArray(i);
2808 if (state.CURRENT_VERTEX_ATTRIB)
2809 gl.vertexAttrib4fv(i, state.CURRENT_VERTEX_ATTRIB);
2810 var buffer = /** @type {!WebGLBuffer} */ (ReplayableResource.replay(state.VERTEX_ATTRIB_ARRAY_BUFFER_BINDING, cache));
2812 gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
2813 gl.vertexAttribPointer(i, state.VERTEX_ATTRIB_ARRAY_SIZE, state.VERTEX_ATTRIB_ARRAY_TYPE, state.VERTEX_ATTRIB_ARRAY_NORMALIZED, state.VERTEX_ATTRIB_ARRAY_STRIDE, state.VERTEX_ATTRIB_ARRAY_POINTER);
2816 gl.bindBuffer(gl.ARRAY_BUFFER, /** @type {!WebGLBuffer} */ (ReplayableResource.replay(glState.ARRAY_BUFFER_BINDING, cache)));
2817 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, /** @type {!WebGLBuffer} */ (ReplayableResource.replay(glState.ELEMENT_ARRAY_BUFFER_BINDING, cache)));
2820 var maxTextureImageUnits = /** @type {number} */ (gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS));
2821 for (var i = 0; i < maxTextureImageUnits; ++i) {
2822 gl.activeTexture(gl.TEXTURE0 + i);
2823 var state = glState.TEXTURE_UNITS[i] || {};
2824 gl.bindTexture(gl.TEXTURE_2D, /** @type {!WebGLTexture} */ (ReplayableResource.replay(state.TEXTURE_2D, cache)));
2825 gl.bindTexture(gl.TEXTURE_CUBE_MAP, /** @type {!WebGLTexture} */ (ReplayableResource.replay(state.TEXTURE_CUBE_MAP, cache)));
2827 gl.activeTexture(glState.ACTIVE_TEXTURE);
2829 ContextResource.prototype._doReplayCalls.call(this, data, cache);
2833 * @param {!Object|number} target
2834 * @return {?Resource}
2836 currentBinding: function(target)
2838 var resource = Resource.forObject(target);
2841 var gl = this.wrappedObject();
2842 var bindingParameter;
2844 target = +target; // Explicitly convert to a number.
2845 var bindMethodTarget = target;
2847 case gl.ARRAY_BUFFER:
2848 bindingParameter = gl.ARRAY_BUFFER_BINDING;
2849 bindMethodName = "bindBuffer";
2851 case gl.ELEMENT_ARRAY_BUFFER:
2852 bindingParameter = gl.ELEMENT_ARRAY_BUFFER_BINDING;
2853 bindMethodName = "bindBuffer";
2856 bindingParameter = gl.TEXTURE_BINDING_2D;
2857 bindMethodName = "bindTexture";
2859 case gl.TEXTURE_CUBE_MAP:
2860 case gl.TEXTURE_CUBE_MAP_POSITIVE_X:
2861 case gl.TEXTURE_CUBE_MAP_NEGATIVE_X:
2862 case gl.TEXTURE_CUBE_MAP_POSITIVE_Y:
2863 case gl.TEXTURE_CUBE_MAP_NEGATIVE_Y:
2864 case gl.TEXTURE_CUBE_MAP_POSITIVE_Z:
2865 case gl.TEXTURE_CUBE_MAP_NEGATIVE_Z:
2866 bindingParameter = gl.TEXTURE_BINDING_CUBE_MAP;
2867 bindMethodTarget = gl.TEXTURE_CUBE_MAP;
2868 bindMethodName = "bindTexture";
2870 case gl.FRAMEBUFFER:
2871 bindingParameter = gl.FRAMEBUFFER_BINDING;
2872 bindMethodName = "bindFramebuffer";
2874 case gl.RENDERBUFFER:
2875 bindingParameter = gl.RENDERBUFFER_BINDING;
2876 bindMethodName = "bindRenderbuffer";
2879 console.error("ASSERT_NOT_REACHED: unknown binding target " + target);
2882 resource = Resource.forObject(gl.getParameter(bindingParameter));
2884 resource.pushBinding(bindMethodTarget, bindMethodName);
2890 * @param {!Call} call
2892 onCallReplayed: function(call)
2894 var functionName = call.functionName();
2895 var args = call.args();
2896 switch (functionName) {
2898 case "bindFramebuffer":
2899 case "bindRenderbuffer":
2901 // Update BINDING state for Resources in the replay world.
2902 var resource = Resource.forObject(args[1]);
2904 resource.pushBinding(args[0], functionName);
2906 case "getExtension":
2907 this.registerWebGLExtension(args[0], /** @type {!Object} */ (call.result()));
2910 var resource = /** @type {!WebGLBufferResource} */ (this.currentBinding(args[0]));
2912 resource.pushCall_bufferData(call);
2914 case "bufferSubData":
2915 var resource = /** @type {!WebGLBufferResource} */ (this.currentBinding(args[0]));
2917 resource.pushCall_bufferSubData(call);
2924 * @return {!Object.<string, !Function>}
2926 _customWrapFunctions: function()
2928 var wrapFunctions = WebGLRenderingContextResource._wrapFunctions;
2929 if (!wrapFunctions) {
2930 wrapFunctions = Object.create(null);
2932 wrapFunctions["createBuffer"] = Resource.WrapFunction.resourceFactoryMethod(WebGLBufferResource, "WebGLBuffer");
2933 wrapFunctions["createShader"] = Resource.WrapFunction.resourceFactoryMethod(WebGLShaderResource, "WebGLShader");
2934 wrapFunctions["createProgram"] = Resource.WrapFunction.resourceFactoryMethod(WebGLProgramResource, "WebGLProgram");
2935 wrapFunctions["createTexture"] = Resource.WrapFunction.resourceFactoryMethod(WebGLTextureResource, "WebGLTexture");
2936 wrapFunctions["createFramebuffer"] = Resource.WrapFunction.resourceFactoryMethod(WebGLFramebufferResource, "WebGLFramebuffer");
2937 wrapFunctions["createRenderbuffer"] = Resource.WrapFunction.resourceFactoryMethod(WebGLRenderbufferResource, "WebGLRenderbuffer");
2938 wrapFunctions["getUniformLocation"] = Resource.WrapFunction.resourceFactoryMethod(WebGLUniformLocationResource, "WebGLUniformLocation");
2940 stateModifyingWrapFunction("bindAttribLocation");
2941 stateModifyingWrapFunction("compileShader");
2942 stateModifyingWrapFunction("detachShader");
2943 stateModifyingWrapFunction("linkProgram");
2944 stateModifyingWrapFunction("shaderSource");
2945 stateModifyingWrapFunction("bufferData", WebGLBufferResource.prototype.pushCall_bufferData);
2946 stateModifyingWrapFunction("bufferSubData", WebGLBufferResource.prototype.pushCall_bufferSubData);
2947 stateModifyingWrapFunction("compressedTexImage2D");
2948 stateModifyingWrapFunction("compressedTexSubImage2D");
2949 stateModifyingWrapFunction("copyTexImage2D", WebGLTextureResource.prototype.pushCall_copyTexImage2D);
2950 stateModifyingWrapFunction("copyTexSubImage2D", WebGLTextureResource.prototype.pushCall_copyTexImage2D);
2951 stateModifyingWrapFunction("generateMipmap");
2952 stateModifyingWrapFunction("texImage2D");
2953 stateModifyingWrapFunction("texSubImage2D");
2954 stateModifyingWrapFunction("texParameterf", WebGLTextureResource.prototype.pushCall_texParameter);
2955 stateModifyingWrapFunction("texParameteri", WebGLTextureResource.prototype.pushCall_texParameter);
2956 stateModifyingWrapFunction("renderbufferStorage");
2958 /** @this {Resource.WrapFunction} */
2959 wrapFunctions["getError"] = function()
2961 var gl = /** @type {!WebGLRenderingContext} */ (this._originalObject);
2962 var error = this.result();
2963 if (error !== gl.NO_ERROR)
2964 this._resource.clearError(error);
2966 error = this._resource.nextError();
2967 if (error !== gl.NO_ERROR)
2968 this.overrideResult(error);
2973 * @param {string} name
2974 * @this {Resource.WrapFunction}
2976 wrapFunctions["getExtension"] = function(name)
2978 this._resource.registerWebGLExtension(name, this.result());
2982 // Register bound WebGL resources.
2986 * @param {!WebGLProgram} program
2987 * @param {!WebGLShader} shader
2988 * @this {Resource.WrapFunction}
2990 wrapFunctions["attachShader"] = function(program, shader)
2992 var resource = this._resource.currentBinding(program);
2994 resource.pushCall(this.call());
2995 var shaderResource = /** @type {!WebGLShaderResource} */ (Resource.forObject(shader));
2996 if (shaderResource) {
2997 var shaderType = shaderResource.type();
2998 resource._registerBoundResource("__attachShader_" + shaderType, shaderResource);
3003 * @param {number} target
3004 * @param {number} attachment
3005 * @param {number} objectTarget
3006 * @param {!WebGLRenderbuffer|!WebGLTexture} obj
3007 * @this {Resource.WrapFunction}
3009 wrapFunctions["framebufferRenderbuffer"] = wrapFunctions["framebufferTexture2D"] = function(target, attachment, objectTarget, obj)
3011 var resource = this._resource.currentBinding(target);
3013 resource.pushCall(this.call());
3014 resource._registerBoundResource("__framebufferAttachmentObjectName", obj);
3018 * @param {number} target
3019 * @param {!Object} obj
3020 * @this {Resource.WrapFunction}
3022 wrapFunctions["bindBuffer"] = wrapFunctions["bindFramebuffer"] = wrapFunctions["bindRenderbuffer"] = function(target, obj)
3024 this._resource.currentBinding(target); // To call WebGLBoundResource.prototype.pushBinding().
3025 this._resource._registerBoundResource("__bindBuffer_" + target, obj);
3028 * @param {number} target
3029 * @param {!WebGLTexture} obj
3030 * @this {Resource.WrapFunction}
3032 wrapFunctions["bindTexture"] = function(target, obj)
3034 this._resource.currentBinding(target); // To call WebGLBoundResource.prototype.pushBinding().
3035 var gl = /** @type {!WebGLRenderingContext} */ (this._originalObject);
3036 var currentTextureBinding = /** @type {number} */ (gl.getParameter(gl.ACTIVE_TEXTURE));
3037 this._resource._registerBoundResource("__bindTexture_" + target + "_" + currentTextureBinding, obj);
3040 * @param {!WebGLProgram} program
3041 * @this {Resource.WrapFunction}
3043 wrapFunctions["useProgram"] = function(program)
3045 this._resource._registerBoundResource("__useProgram", program);
3048 * @param {number} index
3049 * @this {Resource.WrapFunction}
3051 wrapFunctions["vertexAttribPointer"] = function(index)
3053 var gl = /** @type {!WebGLRenderingContext} */ (this._originalObject);
3054 this._resource._registerBoundResource("__vertexAttribPointer_" + index, gl.getParameter(gl.ARRAY_BUFFER_BINDING));
3057 WebGLRenderingContextResource._wrapFunctions = wrapFunctions;
3061 * @param {string} methodName
3062 * @param {function(this:Resource, !Call)=} pushCallFunc
3064 function stateModifyingWrapFunction(methodName, pushCallFunc)
3068 * @param {!Object|number} target
3069 * @this {Resource.WrapFunction}
3071 wrapFunctions[methodName] = function(target)
3073 var resource = this._resource.currentBinding(target);
3075 pushCallFunc.call(resource, this.call());
3079 * @param {!Object|number} target
3080 * @this {Resource.WrapFunction}
3082 wrapFunctions[methodName] = function(target)
3084 var resource = this._resource.currentBinding(target);
3086 resource.pushCall(this.call());
3091 return wrapFunctions;
3094 __proto__: ContextResource.prototype
3097 ////////////////////////////////////////////////////////////////////////////////
3099 ////////////////////////////////////////////////////////////////////////////////
3103 * @extends {ContextResource}
3104 * @param {!CanvasRenderingContext2D} context
3106 function CanvasRenderingContext2DResource(context)
3108 ContextResource.call(this, context, "CanvasRenderingContext2D");
3113 * @type {!Array.<string>}
3115 CanvasRenderingContext2DResource.AttributeProperties = [
3127 "globalCompositeOperation",
3132 "imageSmoothingEnabled",
3134 "webkitLineDashOffset"
3139 * @type {!Array.<string>}
3141 CanvasRenderingContext2DResource.PathMethods = [
3155 * @type {!Array.<string>}
3157 CanvasRenderingContext2DResource.TransformationMatrixMethods = [
3167 * @type {!Object.<string, boolean>}
3169 CanvasRenderingContext2DResource.DrawingMethods = TypeUtils.createPrefixedPropertyNamesSet([
3172 "drawImageFromRect",
3173 "drawCustomFocusRing",
3174 "drawFocusIfNeeded",
3185 CanvasRenderingContext2DResource.prototype = {
3187 * @override (overrides @return type)
3188 * @return {!CanvasRenderingContext2D}
3190 wrappedObject: function()
3192 return this._wrappedObject;
3199 toDataURL: function()
3201 return this.wrappedObject().canvas.toDataURL();
3206 * @return {!Array.<!TypeUtils.InternalResourceStateDescriptor>}
3208 currentState: function()
3211 var state = this._internalCurrentState(null);
3212 for (var pname in state)
3213 result.push({ name: pname, value: state[pname] });
3214 result.push({ name: "context", value: this.contextResource() });
3219 * @param {?Cache.<!ReplayableResource>} cache
3220 * @return {!Object.<string, *>}
3222 _internalCurrentState: function(cache)
3225 * @param {!Resource|*} obj
3226 * @return {!Resource|!ReplayableResource|*}
3228 function maybeToReplayable(obj)
3230 return cache ? Resource.toReplayable(obj, cache) : (Resource.forObject(obj) || obj);
3233 var ctx = this.wrappedObject();
3234 var state = Object.create(null);
3235 CanvasRenderingContext2DResource.AttributeProperties.forEach(function(attribute) {
3236 if (attribute in ctx)
3237 state[attribute] = maybeToReplayable(ctx[attribute]);
3239 if (ctx.getLineDash)
3240 state.lineDash = ctx.getLineDash();
3245 * @param {?Object.<string, *>} state
3246 * @param {!Cache.<!Resource>} cache
3248 _applyAttributesState: function(state, cache)
3252 var ctx = this.wrappedObject();
3253 for (var attribute in state) {
3254 if (attribute === "lineDash") {
3255 if (ctx.setLineDash)
3256 ctx.setLineDash(/** @type {!Array.<number>} */ (state[attribute]));
3258 ctx[attribute] = ReplayableResource.replay(state[attribute], cache);
3264 * @param {!Object} data
3265 * @param {!Cache.<!ReplayableResource>} cache
3267 _populateReplayableData: function(data, cache)
3269 var ctx = this.wrappedObject();
3270 // FIXME: Convert resources in the state (CanvasGradient, CanvasPattern) to Replayable.
3271 data.currentAttributes = this._internalCurrentState(null);
3272 data.originalCanvasCloned = TypeUtils.cloneIntoCanvas(/** @type {!HTMLCanvasElement} */ (ctx.canvas));
3273 if (ctx.getContextAttributes)
3274 data.originalContextAttributes = ctx.getContextAttributes();
3279 * @param {!Object} data
3280 * @param {!Cache.<!Resource>} cache
3282 _doReplayCalls: function(data, cache)
3284 var canvas = TypeUtils.cloneIntoCanvas(data.originalCanvasCloned);
3285 var ctx = /** @type {!CanvasRenderingContext2D} */ (Resource.wrappedObject(canvas.getContext("2d", data.originalContextAttributes)));
3286 this.setWrappedObject(ctx);
3288 for (var i = 0, n = data.calls.length; i < n; ++i) {
3289 var replayableCall = /** @type {!ReplayableCall} */ (data.calls[i]);
3290 if (replayableCall.functionName() === "save")
3291 this._applyAttributesState(replayableCall.attachment("canvas2dAttributesState"), cache);
3292 this._calls.push(replayableCall.replay(cache));
3294 this._applyAttributesState(data.currentAttributes, cache);
3298 * @param {!Call} call
3300 pushCall_setTransform: function(call)
3302 var saveCallIndex = this._lastIndexOfMatchingSaveCall();
3303 var index = this._lastIndexOfAnyCall(CanvasRenderingContext2DResource.PathMethods);
3304 index = Math.max(index, saveCallIndex);
3305 if (this._removeCallsFromLog(CanvasRenderingContext2DResource.TransformationMatrixMethods, index + 1))
3306 this._removeAllObsoleteCallsFromLog();
3307 this.pushCall(call);
3311 * @param {!Call} call
3313 pushCall_beginPath: function(call)
3315 var index = this._lastIndexOfAnyCall(["clip"]);
3316 if (this._removeCallsFromLog(CanvasRenderingContext2DResource.PathMethods, index + 1))
3317 this._removeAllObsoleteCallsFromLog();
3318 this.pushCall(call);
3322 * @param {!Call} call
3324 pushCall_save: function(call)
3326 // FIXME: Convert resources in the state (CanvasGradient, CanvasPattern) to Replayable.
3327 call.setAttachment("canvas2dAttributesState", this._internalCurrentState(null));
3328 this.pushCall(call);
3332 * @param {!Call} call
3334 pushCall_restore: function(call)
3336 var lastIndexOfSave = this._lastIndexOfMatchingSaveCall();
3337 if (lastIndexOfSave === -1)
3339 this._calls[lastIndexOfSave].setAttachment("canvas2dAttributesState", null); // No longer needed, free memory.
3341 var modified = false;
3342 if (this._removeCallsFromLog(["clip"], lastIndexOfSave + 1))
3345 var lastIndexOfAnyPathMethod = this._lastIndexOfAnyCall(CanvasRenderingContext2DResource.PathMethods);
3346 var index = Math.max(lastIndexOfSave, lastIndexOfAnyPathMethod);
3347 if (this._removeCallsFromLog(CanvasRenderingContext2DResource.TransformationMatrixMethods, index + 1))
3351 this._removeAllObsoleteCallsFromLog();
3353 var lastCall = this._calls[this._calls.length - 1];
3354 if (lastCall && lastCall.functionName() === "save")
3357 this.pushCall(call);
3361 * @param {number=} fromIndex
3364 _lastIndexOfMatchingSaveCall: function(fromIndex)
3366 if (typeof fromIndex !== "number")
3367 fromIndex = this._calls.length - 1;
3369 fromIndex = Math.min(fromIndex, this._calls.length - 1);
3371 for (var i = fromIndex; i >= 0; --i) {
3372 var functionName = this._calls[i].functionName();
3373 if (functionName === "restore")
3375 else if (functionName === "save") {
3385 * @param {!Array.<string>} functionNames
3386 * @param {number=} fromIndex
3389 _lastIndexOfAnyCall: function(functionNames, fromIndex)
3391 if (typeof fromIndex !== "number")
3392 fromIndex = this._calls.length - 1;
3394 fromIndex = Math.min(fromIndex, this._calls.length - 1);
3395 for (var i = fromIndex; i >= 0; --i) {
3396 if (functionNames.indexOf(this._calls[i].functionName()) !== -1)
3402 _removeAllObsoleteCallsFromLog: function()
3404 // Remove all PATH methods between clip() and beginPath() calls.
3405 var lastIndexOfBeginPath = this._lastIndexOfAnyCall(["beginPath"]);
3406 while (lastIndexOfBeginPath !== -1) {
3407 var index = this._lastIndexOfAnyCall(["clip"], lastIndexOfBeginPath - 1);
3408 this._removeCallsFromLog(CanvasRenderingContext2DResource.PathMethods, index + 1, lastIndexOfBeginPath);
3409 lastIndexOfBeginPath = this._lastIndexOfAnyCall(["beginPath"], index - 1);
3412 // Remove all TRASFORMATION MATRIX methods before restore() or setTransform() but after any PATH or corresponding save() method.
3413 var lastRestore = this._lastIndexOfAnyCall(["restore", "setTransform"]);
3414 while (lastRestore !== -1) {
3415 var saveCallIndex = this._lastIndexOfMatchingSaveCall(lastRestore - 1);
3416 var index = this._lastIndexOfAnyCall(CanvasRenderingContext2DResource.PathMethods, lastRestore - 1);
3417 index = Math.max(index, saveCallIndex);
3418 this._removeCallsFromLog(CanvasRenderingContext2DResource.TransformationMatrixMethods, index + 1, lastRestore);
3419 lastRestore = this._lastIndexOfAnyCall(["restore", "setTransform"], index - 1);
3422 // Remove all save-restore consecutive pairs.
3423 var restoreCalls = 0;
3424 for (var i = this._calls.length - 1; i >= 0; --i) {
3425 var functionName = this._calls[i].functionName();
3426 if (functionName === "restore") {
3430 if (functionName === "save" && restoreCalls > 0) {
3431 var saveCallIndex = i;
3432 for (var j = i - 1; j >= 0 && i - j < restoreCalls; --j) {
3433 if (this._calls[j].functionName() === "save")
3438 this._calls.splice(saveCallIndex, (i - saveCallIndex + 1) * 2);
3446 * @param {!Array.<string>} functionNames
3447 * @param {number} fromIndex
3448 * @param {number=} toIndex
3451 _removeCallsFromLog: function(functionNames, fromIndex, toIndex)
3453 var oldLength = this._calls.length;
3454 if (typeof toIndex !== "number")
3455 toIndex = oldLength;
3457 toIndex = Math.min(toIndex, oldLength);
3458 var newIndex = Math.min(fromIndex, oldLength);
3459 for (var i = newIndex; i < toIndex; ++i) {
3460 var call = this._calls[i];
3461 if (functionNames.indexOf(call.functionName()) === -1)
3462 this._calls[newIndex++] = call;
3464 if (newIndex >= toIndex)
3466 this._calls.splice(newIndex, toIndex - newIndex);
3472 * @return {!Object.<string, !Function>}
3474 _customWrapFunctions: function()
3476 var wrapFunctions = CanvasRenderingContext2DResource._wrapFunctions;
3477 if (!wrapFunctions) {
3478 wrapFunctions = Object.create(null);
3480 wrapFunctions["createLinearGradient"] = Resource.WrapFunction.resourceFactoryMethod(LogEverythingResource, "CanvasGradient");
3481 wrapFunctions["createRadialGradient"] = Resource.WrapFunction.resourceFactoryMethod(LogEverythingResource, "CanvasGradient");
3482 wrapFunctions["createPattern"] = Resource.WrapFunction.resourceFactoryMethod(LogEverythingResource, "CanvasPattern");
3484 for (var i = 0, methodName; methodName = CanvasRenderingContext2DResource.TransformationMatrixMethods[i]; ++i)
3485 stateModifyingWrapFunction(methodName, methodName === "setTransform" ? this.pushCall_setTransform : undefined);
3486 for (var i = 0, methodName; methodName = CanvasRenderingContext2DResource.PathMethods[i]; ++i)
3487 stateModifyingWrapFunction(methodName, methodName === "beginPath" ? this.pushCall_beginPath : undefined);
3489 stateModifyingWrapFunction("save", this.pushCall_save);
3490 stateModifyingWrapFunction("restore", this.pushCall_restore);
3491 stateModifyingWrapFunction("clip");
3493 CanvasRenderingContext2DResource._wrapFunctions = wrapFunctions;
3497 * @param {string} methodName
3498 * @param {function(this:Resource, !Call)=} func
3500 function stateModifyingWrapFunction(methodName, func)
3503 /** @this {Resource.WrapFunction} */
3504 wrapFunctions[methodName] = function()
3506 func.call(this._resource, this.call());
3509 /** @this {Resource.WrapFunction} */
3510 wrapFunctions[methodName] = function()
3512 this._resource.pushCall(this.call());
3517 return wrapFunctions;
3520 __proto__: ContextResource.prototype
3525 * @param {!Object.<string, boolean>=} drawingMethodNames
3527 function CallFormatter(drawingMethodNames)
3529 this._drawingMethodNames = drawingMethodNames || Object.create(null);
3532 CallFormatter.prototype = {
3534 * @param {!ReplayableCall} replayableCall
3535 * @param {string=} objectGroup
3538 formatCall: function(replayableCall, objectGroup)
3541 var functionName = replayableCall.functionName();
3543 result.functionName = functionName;
3544 result.arguments = [];
3545 var args = replayableCall.args();
3546 for (var i = 0, n = args.length; i < n; ++i)
3547 result.arguments.push(this.formatValue(args[i], objectGroup));
3548 if (replayableCall.result() !== undefined)
3549 result.result = this.formatValue(replayableCall.result(), objectGroup);
3550 if (this._drawingMethodNames[functionName])
3551 result.isDrawingCall = true;
3553 result.property = replayableCall.propertyName();
3554 result.value = this.formatValue(replayableCall.propertyValue(), objectGroup);
3561 * @param {string=} objectGroup
3562 * @return {!CanvasAgent.CallArgument}
3564 formatValue: function(value, objectGroup)
3566 if (value instanceof Resource || value instanceof ReplayableResource) {
3568 description: value.description(),
3569 resourceId: CallFormatter.makeStringResourceId(value.id())
3573 var remoteObject = injectedScript.wrapObject(value, objectGroup || "", true, false);
3574 var description = remoteObject.description || ("" + value);
3577 description: description,
3578 type: /** @type {!CanvasAgent.CallArgumentType} */ (remoteObject.type)
3580 if (remoteObject.subtype)
3581 result.subtype = /** @type {!CanvasAgent.CallArgumentSubtype} */ (remoteObject.subtype);
3582 if (remoteObject.objectId) {
3584 result.remoteObject = remoteObject;
3586 injectedScript.releaseObject(remoteObject.objectId);
3592 * @param {string} name
3595 enumValueForName: function(name)
3601 * @param {number} value
3602 * @param {!Array.<string>=} options
3605 enumNameForValue: function(value, options)
3611 * @param {!Array.<!TypeUtils.InternalResourceStateDescriptor>} descriptors
3612 * @param {string=} objectGroup
3613 * @return {!Array.<!CanvasAgent.ResourceStateDescriptor>}
3615 formatResourceStateDescriptors: function(descriptors, objectGroup)
3618 for (var i = 0, n = descriptors.length; i < n; ++i) {
3619 var d = descriptors[i];
3622 item = { name: d.name, values: this.formatResourceStateDescriptors(d.values, objectGroup) };
3624 item = { name: d.name, value: this.formatValue(d.value, objectGroup) };
3625 if (d.valueIsEnum && typeof d.value === "number") {
3626 var enumName = this.enumNameForValue(d.value);
3628 item.value.enumName = enumName;
3631 var enumValue = this.enumValueForName(d.name);
3633 item.enumValueForName = enumValue;
3635 item.isArray = true;
3644 * @type {!Object.<string, !CallFormatter>}
3646 CallFormatter._formatters = {};
3649 * @param {string} resourceName
3650 * @param {!CallFormatter} callFormatter
3652 CallFormatter.register = function(resourceName, callFormatter)
3654 CallFormatter._formatters[resourceName] = callFormatter;
3658 * @param {!Resource|!ReplayableResource} resource
3659 * @return {!CallFormatter}
3661 CallFormatter.forResource = function(resource)
3663 var formatter = CallFormatter._formatters[resource.name()];
3665 var contextResource = resource.contextResource();
3666 formatter = (contextResource && CallFormatter._formatters[contextResource.name()]) || new CallFormatter();
3672 * @param {number} resourceId
3673 * @return {!CanvasAgent.ResourceId}
3675 CallFormatter.makeStringResourceId = function(resourceId)
3677 return "{\"injectedScriptId\":" + injectedScriptId + ",\"resourceId\":" + resourceId + "}";
3682 * @extends {CallFormatter}
3683 * @param {!Object.<string, boolean>} drawingMethodNames
3685 function WebGLCallFormatter(drawingMethodNames)
3687 CallFormatter.call(this, drawingMethodNames);
3691 * NOTE: The code below is generated from the IDL file by the script:
3692 * /devtools/scripts/check_injected_webgl_calls_info.py
3694 * @type {!Array.<{aname: string, enum: (!Array.<number>|undefined), bitfield: (!Array.<number>|undefined), returnType: string, hints: (!Array.<string>|undefined)}>}
3696 WebGLCallFormatter.EnumsInfo = [
3697 {"aname": "activeTexture", "enum": [0]},
3698 {"aname": "bindBuffer", "enum": [0]},
3699 {"aname": "bindFramebuffer", "enum": [0]},
3700 {"aname": "bindRenderbuffer", "enum": [0]},
3701 {"aname": "bindTexture", "enum": [0]},
3702 {"aname": "blendEquation", "enum": [0]},
3703 {"aname": "blendEquationSeparate", "enum": [0, 1]},
3704 {"aname": "blendFunc", "enum": [0, 1], "hints": ["ZERO", "ONE"]},
3705 {"aname": "blendFuncSeparate", "enum": [0, 1, 2, 3], "hints": ["ZERO", "ONE"]},
3706 {"aname": "bufferData", "enum": [0, 2]},
3707 {"aname": "bufferSubData", "enum": [0]},
3708 {"aname": "checkFramebufferStatus", "enum": [0], "returnType": "enum"},
3709 {"aname": "clear", "bitfield": [0]},
3710 {"aname": "compressedTexImage2D", "enum": [0, 2]},
3711 {"aname": "compressedTexSubImage2D", "enum": [0, 6]},
3712 {"aname": "copyTexImage2D", "enum": [0, 2]},
3713 {"aname": "copyTexSubImage2D", "enum": [0]},
3714 {"aname": "createShader", "enum": [0]},
3715 {"aname": "cullFace", "enum": [0]},
3716 {"aname": "depthFunc", "enum": [0]},
3717 {"aname": "disable", "enum": [0]},
3718 {"aname": "drawArrays", "enum": [0], "hints": ["POINTS", "LINES"]},
3719 {"aname": "drawElements", "enum": [0, 2], "hints": ["POINTS", "LINES"]},
3720 {"aname": "enable", "enum": [0]},
3721 {"aname": "framebufferRenderbuffer", "enum": [0, 1, 2]},
3722 {"aname": "framebufferTexture2D", "enum": [0, 1, 2]},
3723 {"aname": "frontFace", "enum": [0]},
3724 {"aname": "generateMipmap", "enum": [0]},
3725 {"aname": "getBufferParameter", "enum": [0, 1]},
3726 {"aname": "getError", "hints": ["NO_ERROR"], "returnType": "enum"},
3727 {"aname": "getFramebufferAttachmentParameter", "enum": [0, 1, 2]},
3728 {"aname": "getParameter", "enum": [0]},
3729 {"aname": "getProgramParameter", "enum": [1]},
3730 {"aname": "getRenderbufferParameter", "enum": [0, 1]},
3731 {"aname": "getShaderParameter", "enum": [1]},
3732 {"aname": "getShaderPrecisionFormat", "enum": [0, 1]},
3733 {"aname": "getTexParameter", "enum": [0, 1], "returnType": "enum"},
3734 {"aname": "getVertexAttrib", "enum": [1]},
3735 {"aname": "getVertexAttribOffset", "enum": [1]},
3736 {"aname": "hint", "enum": [0, 1]},
3737 {"aname": "isEnabled", "enum": [0]},
3738 {"aname": "pixelStorei", "enum": [0]},
3739 {"aname": "readPixels", "enum": [4, 5]},
3740 {"aname": "renderbufferStorage", "enum": [0, 1]},
3741 {"aname": "stencilFunc", "enum": [0]},
3742 {"aname": "stencilFuncSeparate", "enum": [0, 1]},
3743 {"aname": "stencilMaskSeparate", "enum": [0]},
3744 {"aname": "stencilOp", "enum": [0, 1, 2], "hints": ["ZERO", "ONE"]},
3745 {"aname": "stencilOpSeparate", "enum": [0, 1, 2, 3], "hints": ["ZERO", "ONE"]},
3746 {"aname": "texParameterf", "enum": [0, 1, 2]},
3747 {"aname": "texParameteri", "enum": [0, 1, 2]},
3748 {"aname": "texImage2D", "enum": [0, 2, 6, 7]},
3749 {"aname": "texImage2D", "enum": [0, 2, 3, 4]},
3750 {"aname": "texSubImage2D", "enum": [0, 6, 7]},
3751 {"aname": "texSubImage2D", "enum": [0, 4, 5]},
3752 {"aname": "vertexAttribPointer", "enum": [2]}
3755 WebGLCallFormatter.prototype = {
3758 * @param {!ReplayableCall} replayableCall
3759 * @param {string=} objectGroup
3762 formatCall: function(replayableCall, objectGroup)
3764 var result = CallFormatter.prototype.formatCall.call(this, replayableCall, objectGroup);
3765 if (!result.functionName)
3767 var enumsInfo = this._findEnumsInfo(replayableCall);
3770 var enumArgsIndexes = enumsInfo["enum"] || [];
3771 for (var i = 0, n = enumArgsIndexes.length; i < n; ++i) {
3772 var index = enumArgsIndexes[i];
3773 var callArgument = result.arguments[index];
3774 this._formatEnumValue(callArgument, enumsInfo["hints"]);
3776 var bitfieldArgsIndexes = enumsInfo["bitfield"] || [];
3777 for (var i = 0, n = bitfieldArgsIndexes.length; i < n; ++i) {
3778 var index = bitfieldArgsIndexes[i];
3779 var callArgument = result.arguments[index];
3780 this._formatEnumBitmaskValue(callArgument, enumsInfo["hints"]);
3782 if (enumsInfo.returnType === "enum")
3783 this._formatEnumValue(result.result, enumsInfo["hints"]);
3784 else if (enumsInfo.returnType === "bitfield")
3785 this._formatEnumBitmaskValue(result.result, enumsInfo["hints"]);
3791 * @param {string} name
3794 enumValueForName: function(name)
3797 if (name in this._enumNameToValue)
3798 return "" + this._enumNameToValue[name];
3804 * @param {number} value
3805 * @param {!Array.<string>=} options
3808 enumNameForValue: function(value, options)
3811 options = options || [];
3812 for (var i = 0, n = options.length; i < n; ++i) {
3813 if (this._enumNameToValue[options[i]] === value)
3816 var names = this._enumValueToNames[value];
3817 if (!names || names.length !== 1)
3823 * @param {!ReplayableCall} replayableCall
3826 _findEnumsInfo: function(replayableCall)
3828 function findMaxArgumentIndex(enumsInfo)
3831 var enumArgsIndexes = enumsInfo["enum"] || [];
3832 for (var i = 0, n = enumArgsIndexes.length; i < n; ++i)
3833 result = Math.max(result, enumArgsIndexes[i]);
3834 var bitfieldArgsIndexes = enumsInfo["bitfield"] || [];
3835 for (var i = 0, n = bitfieldArgsIndexes.length; i < n; ++i)
3836 result = Math.max(result, bitfieldArgsIndexes[i]);
3841 for (var i = 0, enumsInfo; enumsInfo = WebGLCallFormatter.EnumsInfo[i]; ++i) {
3842 if (enumsInfo["aname"] !== replayableCall.functionName())
3844 var argsCount = replayableCall.args().length;
3845 var maxArgumentIndex = findMaxArgumentIndex(enumsInfo);
3846 if (maxArgumentIndex >= argsCount)
3848 // To resolve ambiguity (see texImage2D, texSubImage2D) choose description with max argument indexes.
3849 if (!result || findMaxArgumentIndex(result) < maxArgumentIndex)
3856 * @param {?CanvasAgent.CallArgument|undefined} callArgument
3857 * @param {!Array.<string>=} options
3859 _formatEnumValue: function(callArgument, options)
3861 if (!callArgument || isNaN(callArgument.description))
3864 var value = +callArgument.description;
3865 var enumName = this.enumNameForValue(value, options);
3867 callArgument.enumName = enumName;
3871 * @param {?CanvasAgent.CallArgument|undefined} callArgument
3872 * @param {!Array.<string>=} options
3874 _formatEnumBitmaskValue: function(callArgument, options)
3876 if (!callArgument || isNaN(callArgument.description))
3879 var value = +callArgument.description;
3880 options = options || [];
3881 /** @type {!Array.<string>} */
3883 for (var i = 0, n = options.length; i < n; ++i) {
3884 var bitValue = this._enumNameToValue[options[i]] || 0;
3885 if (value & bitValue) {
3886 result.push(options[i]);
3891 var nextValue = value & (value - 1);
3892 var bitValue = value ^ nextValue;
3893 var names = this._enumValueToNames[bitValue];
3894 if (!names || names.length !== 1) {
3895 console.warn("Ambiguous WebGL enum names for value " + bitValue + ": " + names);
3898 result.push(names[0]);
3902 callArgument.enumName = result.join(" | ");
3905 _initialize: function()
3907 if (this._enumNameToValue)
3910 /** @type {!Object.<string, number>} */
3911 this._enumNameToValue = Object.create(null);
3912 /** @type {!Object.<number, !Array.<string>>} */
3913 this._enumValueToNames = Object.create(null);
3916 * @param {?Object} obj
3917 * @this WebGLCallFormatter
3919 function iterateWebGLEnums(obj)
3923 for (var property in obj) {
3924 if (TypeUtils.isEnumPropertyName(property, obj)) {
3925 var value = /** @type {number} */ (obj[property]);
3926 this._enumNameToValue[property] = value;
3927 var names = this._enumValueToNames[value];
3929 if (names.indexOf(property) === -1)
3930 names.push(property);
3932 this._enumValueToNames[value] = [property];
3938 * @param {!Array.<string>} values
3941 function commonSubstring(values)
3943 var length = values.length;
3944 for (var i = 0; i < length; ++i) {
3945 for (var j = 0; j < length; ++j) {
3946 if (values[j].indexOf(values[i]) === -1)
3955 var gl = this._createUninstrumentedWebGLRenderingContext();
3956 iterateWebGLEnums.call(this, gl);
3958 var extensions = gl.getSupportedExtensions() || [];
3959 for (var i = 0, n = extensions.length; i < n; ++i)
3960 iterateWebGLEnums.call(this, gl.getExtension(extensions[i]));
3962 // Sort to get rid of ambiguity.
3963 for (var value in this._enumValueToNames) {
3964 var numericValue = Number(value);
3965 var names = this._enumValueToNames[numericValue];
3966 if (names.length > 1) {
3967 // Choose one enum name if possible. For example:
3968 // [BLEND_EQUATION, BLEND_EQUATION_RGB] => BLEND_EQUATION
3969 // [COLOR_ATTACHMENT0, COLOR_ATTACHMENT0_WEBGL] => COLOR_ATTACHMENT0
3970 var common = commonSubstring(names);
3972 this._enumValueToNames[numericValue] = [common];
3974 this._enumValueToNames[numericValue] = names.sort();
3980 * @return {?WebGLRenderingContext}
3982 _createUninstrumentedWebGLRenderingContext: function()
3984 var canvas = /** @type {!HTMLCanvasElement} */ (inspectedWindow.document.createElement("canvas"));
3985 var contextIds = ["experimental-webgl", "webkit-3d", "3d"];
3986 for (var i = 0, contextId; contextId = contextIds[i]; ++i) {
3987 var context = canvas.getContext(contextId);
3989 return /** @type {!WebGLRenderingContext} */ (Resource.wrappedObject(context));
3994 __proto__: CallFormatter.prototype
3997 CallFormatter.register("CanvasRenderingContext2D", new CallFormatter(CanvasRenderingContext2DResource.DrawingMethods));
3998 CallFormatter.register("WebGLRenderingContext", new WebGLCallFormatter(WebGLRenderingContextResource.DrawingMethods));
4005 /** @type {!Array.<!ReplayableCall>} */
4006 this._replayableCalls = [];
4007 /** @type {!Cache.<!ReplayableResource>} */
4008 this._replayablesCache = new Cache();
4009 /** @type {!Object.<number, boolean>} */
4010 this._frameEndCallIndexes = {};
4011 /** @type {!Object.<number, boolean>} */
4012 this._resourcesCreatedInThisTraceLog = {};
4015 TraceLog.prototype = {
4021 return this._replayableCalls.length;
4025 * @return {!Array.<!ReplayableCall>}
4027 replayableCalls: function()
4029 return this._replayableCalls;
4033 * @param {number} id
4034 * @return {!ReplayableResource|undefined}
4036 replayableResource: function(id)
4038 return this._replayablesCache.get(id);
4042 * @param {number} resourceId
4045 createdInThisTraceLog: function(resourceId)
4047 return !!this._resourcesCreatedInThisTraceLog[resourceId];
4051 * @param {!Resource} resource
4053 captureResource: function(resource)
4055 resource.toReplayable(this._replayablesCache);
4059 * @param {!Call} call
4061 addCall: function(call)
4063 var resource = Resource.forObject(call.result());
4064 if (resource && !this._replayablesCache.has(resource.id()))
4065 this._resourcesCreatedInThisTraceLog[resource.id()] = true;
4066 this._replayableCalls.push(call.toReplayable(this._replayablesCache));
4069 addFrameEndMark: function()
4071 var index = this._replayableCalls.length - 1;
4073 this._frameEndCallIndexes[index] = true;
4077 * @param {number} index
4080 isFrameEndCallAt: function(index)
4082 return !!this._frameEndCallIndexes[index];
4088 * @param {!TraceLog} traceLog
4090 function TraceLogPlayer(traceLog)
4092 /** @type {!TraceLog} */
4093 this._traceLog = traceLog;
4094 /** @type {number} */
4095 this._nextReplayStep = 0;
4096 /** @type {!Cache.<!Resource>} */
4097 this._replayWorldCache = new Cache();
4100 TraceLogPlayer.prototype = {
4102 * @return {!TraceLog}
4104 traceLog: function()
4106 return this._traceLog;
4110 * @param {number} id
4111 * @return {!Resource|undefined}
4113 replayWorldResource: function(id)
4115 return this._replayWorldCache.get(id);
4121 nextReplayStep: function()
4123 return this._nextReplayStep;
4128 this._nextReplayStep = 0;
4129 this._replayWorldCache.reset();
4133 * @param {number} stepNum
4134 * @return {{replayTime:number, lastCall:(!Call)}}
4136 stepTo: function(stepNum)
4138 stepNum = Math.min(stepNum, this._traceLog.size() - 1);
4139 console.assert(stepNum >= 0);
4140 if (this._nextReplayStep > stepNum)
4143 // Replay the calls' arguments first to warm-up, before measuring the actual replay time.
4144 this._replayCallArguments(stepNum);
4146 var replayableCalls = this._traceLog.replayableCalls();
4147 var replayedCalls = [];
4148 replayedCalls.length = stepNum - this._nextReplayStep + 1;
4150 var beforeTime = TypeUtils.now();
4151 for (var i = 0; this._nextReplayStep <= stepNum; ++this._nextReplayStep, ++i)
4152 replayedCalls[i] = replayableCalls[this._nextReplayStep].replay(this._replayWorldCache);
4153 var replayTime = Math.max(0, TypeUtils.now() - beforeTime);
4155 for (var i = 0, call; call = replayedCalls[i]; ++i)
4156 call.resource().onCallReplayed(call);
4159 replayTime: replayTime,
4160 lastCall: replayedCalls[replayedCalls.length - 1]
4165 * @param {number} stepNum
4167 _replayCallArguments: function(stepNum)
4171 * @this {TraceLogPlayer}
4173 function replayIfNotCreatedInThisTraceLog(obj)
4175 if (!(obj instanceof ReplayableResource))
4177 var replayableResource = /** @type {!ReplayableResource} */ (obj);
4178 if (!this._traceLog.createdInThisTraceLog(replayableResource.id()))
4179 replayableResource.replay(this._replayWorldCache)
4181 var replayableCalls = this._traceLog.replayableCalls();
4182 for (var i = this._nextReplayStep; i <= stepNum; ++i) {
4183 replayIfNotCreatedInThisTraceLog.call(this, replayableCalls[i].replayableResource());
4184 replayIfNotCreatedInThisTraceLog.call(this, replayableCalls[i].result());
4185 replayableCalls[i].args().forEach(replayIfNotCreatedInThisTraceLog.bind(this));
4193 function ResourceTrackingManager()
4195 this._capturing = false;
4196 this._stopCapturingOnFrameEnd = false;
4197 this._lastTraceLog = null;
4200 ResourceTrackingManager.prototype = {
4204 capturing: function()
4206 return this._capturing;
4210 * @return {?TraceLog}
4212 lastTraceLog: function()
4214 return this._lastTraceLog;
4218 * @param {!Resource} resource
4220 registerResource: function(resource)
4222 resource.setManager(this);
4225 startCapturing: function()
4227 if (!this._capturing)
4228 this._lastTraceLog = new TraceLog();
4229 this._capturing = true;
4230 this._stopCapturingOnFrameEnd = false;
4234 * @param {!TraceLog=} traceLog
4236 stopCapturing: function(traceLog)
4238 if (traceLog && this._lastTraceLog !== traceLog)
4240 this._capturing = false;
4241 this._stopCapturingOnFrameEnd = false;
4242 if (this._lastTraceLog)
4243 this._lastTraceLog.addFrameEndMark();
4247 * @param {!TraceLog} traceLog
4249 dropTraceLog: function(traceLog)
4251 this.stopCapturing(traceLog);
4252 if (this._lastTraceLog === traceLog)
4253 this._lastTraceLog = null;
4256 captureFrame: function()
4258 this._lastTraceLog = new TraceLog();
4259 this._capturing = true;
4260 this._stopCapturingOnFrameEnd = true;
4264 * @param {!Resource} resource
4265 * @param {!Array|!Arguments} args
4267 captureArguments: function(resource, args)
4269 if (!this._capturing)
4271 this._lastTraceLog.captureResource(resource);
4272 for (var i = 0, n = args.length; i < n; ++i) {
4273 var res = Resource.forObject(args[i]);
4275 this._lastTraceLog.captureResource(res);
4280 * @param {!Call} call
4282 captureCall: function(call)
4284 if (!this._capturing)
4286 this._lastTraceLog.addCall(call);
4289 markFrameEnd: function()
4291 if (!this._lastTraceLog)
4293 this._lastTraceLog.addFrameEndMark();
4294 if (this._stopCapturingOnFrameEnd && this._lastTraceLog.size())
4295 this.stopCapturing(this._lastTraceLog);
4302 var InjectedCanvasModule = function()
4304 /** @type {!ResourceTrackingManager} */
4305 this._manager = new ResourceTrackingManager();
4306 /** @type {number} */
4307 this._lastTraceLogId = 0;
4308 /** @type {!Object.<string, !TraceLog>} */
4309 this._traceLogs = {};
4310 /** @type {!Object.<string, !TraceLogPlayer>} */
4311 this._traceLogPlayers = {};
4314 InjectedCanvasModule.prototype = {
4316 * @param {!WebGLRenderingContext} glContext
4319 wrapWebGLContext: function(glContext)
4321 var resource = Resource.forObject(glContext) || new WebGLRenderingContextResource(glContext);
4322 this._manager.registerResource(resource);
4323 return resource.proxyObject();
4327 * @param {!CanvasRenderingContext2D} context
4330 wrapCanvas2DContext: function(context)
4332 var resource = Resource.forObject(context) || new CanvasRenderingContext2DResource(context);
4333 this._manager.registerResource(resource);
4334 return resource.proxyObject();
4338 * @return {!CanvasAgent.TraceLogId}
4340 captureFrame: function()
4342 return this._callStartCapturingFunction(this._manager.captureFrame);
4346 * @return {!CanvasAgent.TraceLogId}
4348 startCapturing: function()
4350 return this._callStartCapturingFunction(this._manager.startCapturing);
4353 markFrameEnd: function()
4355 this._manager.markFrameEnd();
4359 * @param {function(this:ResourceTrackingManager)} func
4360 * @return {!CanvasAgent.TraceLogId}
4362 _callStartCapturingFunction: function(func)
4364 var oldTraceLog = this._manager.lastTraceLog();
4365 func.call(this._manager);
4366 var traceLog = /** @type {!TraceLog} */ (this._manager.lastTraceLog());
4367 if (traceLog === oldTraceLog) {
4368 for (var id in this._traceLogs) {
4369 if (this._traceLogs[id] === traceLog)
4373 var id = this._makeTraceLogId();
4374 this._traceLogs[id] = traceLog;
4379 * @param {!CanvasAgent.TraceLogId} id
4381 stopCapturing: function(id)
4383 var traceLog = this._traceLogs[id];
4385 this._manager.stopCapturing(traceLog);
4389 * @param {!CanvasAgent.TraceLogId} id
4391 dropTraceLog: function(id)
4393 var traceLog = this._traceLogs[id];
4395 this._manager.dropTraceLog(traceLog);
4396 delete this._traceLogs[id];
4397 delete this._traceLogPlayers[id];
4398 injectedScript.releaseObjectGroup(id);
4402 * @param {!CanvasAgent.TraceLogId} id
4403 * @param {number=} startOffset
4404 * @param {number=} maxLength
4405 * @return {!CanvasAgent.TraceLog|string}
4407 traceLog: function(id, startOffset, maxLength)
4409 var traceLog = this._traceLogs[id];
4411 return "Error: Trace log with the given ID not found.";
4413 // Ensure last call ends a frame.
4414 traceLog.addFrameEndMark();
4416 var replayableCalls = traceLog.replayableCalls();
4417 if (typeof startOffset !== "number")
4419 if (typeof maxLength !== "number")
4420 maxLength = replayableCalls.length;
4422 var fromIndex = Math.max(0, startOffset);
4423 var toIndex = Math.min(replayableCalls.length - 1, fromIndex + maxLength - 1);
4425 var alive = this._manager.capturing() && this._manager.lastTraceLog() === traceLog;
4428 /** @type {!Array.<!CanvasAgent.Call>} */
4430 /** @type {!Array.<!CanvasAgent.CallArgument>} */
4433 startOffset: fromIndex,
4434 totalAvailableCalls: replayableCalls.length
4436 /** @type {!Object.<string, boolean>} */
4437 var contextIds = {};
4438 for (var i = fromIndex; i <= toIndex; ++i) {
4439 var call = replayableCalls[i];
4440 var resource = call.replayableResource();
4441 var contextResource = resource.contextResource();
4442 var stackTrace = call.stackTrace();
4443 var callFrame = stackTrace ? stackTrace.callFrame(0) || {} : {};
4444 var item = CallFormatter.forResource(resource).formatCall(call);
4445 item.contextId = CallFormatter.makeStringResourceId(contextResource.id());
4446 item.sourceURL = callFrame.sourceURL;
4447 item.lineNumber = callFrame.lineNumber;
4448 item.columnNumber = callFrame.columnNumber;
4449 item.isFrameEndCall = traceLog.isFrameEndCallAt(i);
4450 result.calls.push(item);
4451 if (!contextIds[item.contextId]) {
4452 contextIds[item.contextId] = true;
4453 result.contexts.push(CallFormatter.forResource(resource).formatValue(contextResource));
4460 * @param {!CanvasAgent.TraceLogId} traceLogId
4461 * @param {number} stepNo
4462 * @return {{resourceState: !CanvasAgent.ResourceState, replayTime: number}|string}
4464 replayTraceLog: function(traceLogId, stepNo)
4466 var traceLog = this._traceLogs[traceLogId];
4468 return "Error: Trace log with the given ID not found.";
4469 this._traceLogPlayers[traceLogId] = this._traceLogPlayers[traceLogId] || new TraceLogPlayer(traceLog);
4470 injectedScript.releaseObjectGroup(traceLogId);
4472 var replayResult = this._traceLogPlayers[traceLogId].stepTo(stepNo);
4473 var resource = replayResult.lastCall.resource();
4474 var dataURL = resource.toDataURL();
4476 resource = resource.contextResource();
4477 dataURL = resource.toDataURL();
4480 resourceState: this._makeResourceState(resource.id(), traceLogId, resource, dataURL),
4481 replayTime: replayResult.replayTime
4486 * @param {!CanvasAgent.TraceLogId} traceLogId
4487 * @param {!CanvasAgent.ResourceId} stringResourceId
4488 * @return {!CanvasAgent.ResourceState|string}
4490 resourceState: function(traceLogId, stringResourceId)
4492 var traceLog = this._traceLogs[traceLogId];
4494 return "Error: Trace log with the given ID not found.";
4496 var parsedStringId1 = this._parseStringId(traceLogId);
4497 var parsedStringId2 = this._parseStringId(stringResourceId);
4498 if (parsedStringId1.injectedScriptId !== parsedStringId2.injectedScriptId)
4499 return "Error: Both IDs must point to the same injected script.";
4501 var resourceId = parsedStringId2.resourceId;
4503 return "Error: Wrong resource ID: " + stringResourceId;
4505 var traceLogPlayer = this._traceLogPlayers[traceLogId];
4506 var resource = traceLogPlayer && traceLogPlayer.replayWorldResource(resourceId);
4507 return this._makeResourceState(resourceId, traceLogId, resource);
4511 * @param {!CanvasAgent.TraceLogId} traceLogId
4512 * @param {number} callIndex
4513 * @param {number} argumentIndex
4514 * @param {string} objectGroup
4515 * @return {{result:(!RuntimeAgent.RemoteObject|undefined), resourceState:(!CanvasAgent.ResourceState|undefined)}|string}
4517 evaluateTraceLogCallArgument: function(traceLogId, callIndex, argumentIndex, objectGroup)
4519 var traceLog = this._traceLogs[traceLogId];
4521 return "Error: Trace log with the given ID not found.";
4523 var replayableCall = traceLog.replayableCalls()[callIndex];
4524 if (!replayableCall)
4525 return "Error: No call found at index " + callIndex;
4528 if (replayableCall.isPropertySetter())
4529 value = replayableCall.propertyValue();
4530 else if (argumentIndex === -1)
4531 value = replayableCall.result();
4533 var args = replayableCall.args();
4534 if (argumentIndex < 0 || argumentIndex >= args.length)
4535 return "Error: No argument found at index " + argumentIndex + " for call at index " + callIndex;
4536 value = args[argumentIndex];
4539 if (value instanceof ReplayableResource) {
4540 var traceLogPlayer = this._traceLogPlayers[traceLogId];
4541 var resource = traceLogPlayer && traceLogPlayer.replayWorldResource(value.id());
4542 var resourceState = this._makeResourceState(value.id(), traceLogId, resource);
4543 return { resourceState: resourceState };
4546 var remoteObject = injectedScript.wrapObject(value, objectGroup, true, false);
4547 return { result: remoteObject };
4551 * @return {!CanvasAgent.TraceLogId}
4553 _makeTraceLogId: function()
4555 return "{\"injectedScriptId\":" + injectedScriptId + ",\"traceLogId\":" + (++this._lastTraceLogId) + "}";
4559 * @param {number} resourceId
4560 * @param {!CanvasAgent.TraceLogId} traceLogId
4561 * @param {?Resource=} resource
4562 * @param {string=} overrideImageURL
4563 * @return {!CanvasAgent.ResourceState}
4565 _makeResourceState: function(resourceId, traceLogId, resource, overrideImageURL)
4568 id: CallFormatter.makeStringResourceId(resourceId),
4569 traceLogId: traceLogId
4572 result.imageURL = overrideImageURL || resource.toDataURL();
4573 result.descriptors = CallFormatter.forResource(resource).formatResourceStateDescriptors(resource.currentState(), traceLogId);
4579 * @param {string} stringId
4580 * @return {{injectedScriptId: number, traceLogId: ?number, resourceId: ?number}}
4582 _parseStringId: function(stringId)
4584 return InjectedScriptHost.eval("(" + stringId + ")");
4588 var injectedCanvasModule = new InjectedCanvasModule();
4589 return injectedCanvasModule;