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