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