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