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