1 /* 2 * Copyright (C) 2011 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 copyrightdd 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 * @constructor 33 * @extends {WebInspector.Object} 34 */ 35 WebInspector.HeapSnapshotWorkerWrapper = function() 36 { 37 } 38 39 WebInspector.HeapSnapshotWorkerWrapper.prototype = { 40 postMessage: function(message) 41 { 42 }, 43 terminate: function() 44 { 45 }, 46 47 __proto__: WebInspector.Object.prototype 48 } 49 50 /** 51 * @constructor 52 * @extends {WebInspector.HeapSnapshotWorkerWrapper} 53 */ 54 WebInspector.HeapSnapshotRealWorker = function() 55 { 56 this._worker = new Worker("HeapSnapshotWorker.js"); 57 this._worker.addEventListener("message", this._messageReceived.bind(this), false); 58 } 59 60 WebInspector.HeapSnapshotRealWorker.prototype = { 61 _messageReceived: function(event) 62 { 63 var message = event.data; 64 this.dispatchEventToListeners("message", message); 65 }, 66 67 postMessage: function(message) 68 { 69 this._worker.postMessage(message); 70 }, 71 72 terminate: function() 73 { 74 this._worker.terminate(); 75 }, 76 77 __proto__: WebInspector.HeapSnapshotWorkerWrapper.prototype 78 } 79 80 81 /** 82 * @constructor 83 */ 84 WebInspector.AsyncTaskQueue = function() 85 { 86 this._queue = []; 87 this._isTimerSheduled = false; 88 } 89 90 WebInspector.AsyncTaskQueue.prototype = { 91 /** 92 * @param {function()} task 93 */ 94 addTask: function(task) 95 { 96 this._queue.push(task); 97 this._scheduleTimer(); 98 }, 99 100 _onTimeout: function() 101 { 102 this._isTimerSheduled = false; 103 var queue = this._queue; 104 this._queue = []; 105 for (var i = 0; i < queue.length; i++) { 106 try { 107 queue[i](); 108 } catch (e) { 109 console.error("Exception while running task: " + e.stack); 110 } 111 } 112 this._scheduleTimer(); 113 }, 114 115 _scheduleTimer: function() 116 { 117 if (this._queue.length && !this._isTimerSheduled) { 118 setTimeout(this._onTimeout.bind(this), 0); 119 this._isTimerSheduled = true; 120 } 121 } 122 } 123 124 /** 125 * @constructor 126 * @extends {WebInspector.HeapSnapshotWorkerWrapper} 127 */ 128 WebInspector.HeapSnapshotFakeWorker = function() 129 { 130 this._dispatcher = new WebInspector.HeapSnapshotWorkerDispatcher(window, this._postMessageFromWorker.bind(this)); 131 this._asyncTaskQueue = new WebInspector.AsyncTaskQueue(); 132 } 133 134 WebInspector.HeapSnapshotFakeWorker.prototype = { 135 postMessage: function(message) 136 { 137 /** 138 * @this {WebInspector.HeapSnapshotFakeWorker} 139 */ 140 function dispatch() 141 { 142 if (this._dispatcher) 143 this._dispatcher.dispatchMessage({data: message}); 144 } 145 this._asyncTaskQueue.addTask(dispatch.bind(this)); 146 }, 147 148 terminate: function() 149 { 150 this._dispatcher = null; 151 }, 152 153 _postMessageFromWorker: function(message) 154 { 155 /** 156 * @this {WebInspector.HeapSnapshotFakeWorker} 157 */ 158 function send() 159 { 160 this.dispatchEventToListeners("message", message); 161 } 162 this._asyncTaskQueue.addTask(send.bind(this)); 163 }, 164 165 __proto__: WebInspector.HeapSnapshotWorkerWrapper.prototype 166 } 167 168 169 /** 170 * @constructor 171 * @param {function(string, *)} eventHandler 172 * @extends {WebInspector.Object} 173 */ 174 WebInspector.HeapSnapshotWorkerProxy = function(eventHandler) 175 { 176 this._eventHandler = eventHandler; 177 this._nextObjectId = 1; 178 this._nextCallId = 1; 179 this._callbacks = []; 180 this._previousCallbacks = []; 181 // There is no support for workers in Chromium DRT. 182 this._worker = typeof InspectorTest === "undefined" ? new WebInspector.HeapSnapshotRealWorker() : new WebInspector.HeapSnapshotFakeWorker(); 183 this._worker.addEventListener("message", this._messageReceived, this); 184 } 185 186 WebInspector.HeapSnapshotWorkerProxy.prototype = { 187 /** 188 * @return {!WebInspector.HeapSnapshotLoaderProxy} 189 */ 190 createLoader: function(snapshotConstructorName, proxyConstructor) 191 { 192 var objectId = this._nextObjectId++; 193 var proxy = new WebInspector.HeapSnapshotLoaderProxy(this, objectId, snapshotConstructorName, proxyConstructor); 194 this._postMessage({callId: this._nextCallId++, disposition: "create", objectId: objectId, methodName: "WebInspector.HeapSnapshotLoader"}); 195 return proxy; 196 }, 197 198 dispose: function() 199 { 200 this._worker.terminate(); 201 if (this._interval) 202 clearInterval(this._interval); 203 }, 204 205 disposeObject: function(objectId) 206 { 207 this._postMessage({callId: this._nextCallId++, disposition: "dispose", objectId: objectId}); 208 }, 209 210 callGetter: function(callback, objectId, getterName) 211 { 212 var callId = this._nextCallId++; 213 this._callbacks[callId] = callback; 214 this._postMessage({callId: callId, disposition: "getter", objectId: objectId, methodName: getterName}); 215 }, 216 217 callFactoryMethod: function(callback, objectId, methodName, proxyConstructor) 218 { 219 var callId = this._nextCallId++; 220 var methodArguments = Array.prototype.slice.call(arguments, 4); 221 var newObjectId = this._nextObjectId++; 222 223 /** 224 * @this {WebInspector.HeapSnapshotWorkerProxy} 225 */ 226 function wrapCallback(remoteResult) 227 { 228 callback(remoteResult ? new proxyConstructor(this, newObjectId) : null); 229 } 230 231 if (callback) { 232 this._callbacks[callId] = wrapCallback.bind(this); 233 this._postMessage({callId: callId, disposition: "factory", objectId: objectId, methodName: methodName, methodArguments: methodArguments, newObjectId: newObjectId}); 234 return null; 235 } else { 236 this._postMessage({callId: callId, disposition: "factory", objectId: objectId, methodName: methodName, methodArguments: methodArguments, newObjectId: newObjectId}); 237 return new proxyConstructor(this, newObjectId); 238 } 239 }, 240 241 callMethod: function(callback, objectId, methodName) 242 { 243 var callId = this._nextCallId++; 244 var methodArguments = Array.prototype.slice.call(arguments, 3); 245 if (callback) 246 this._callbacks[callId] = callback; 247 this._postMessage({callId: callId, disposition: "method", objectId: objectId, methodName: methodName, methodArguments: methodArguments}); 248 }, 249 250 startCheckingForLongRunningCalls: function() 251 { 252 if (this._interval) 253 return; 254 this._checkLongRunningCalls(); 255 this._interval = setInterval(this._checkLongRunningCalls.bind(this), 300); 256 }, 257 258 _checkLongRunningCalls: function() 259 { 260 for (var callId in this._previousCallbacks) 261 if (!(callId in this._callbacks)) 262 delete this._previousCallbacks[callId]; 263 var hasLongRunningCalls = false; 264 for (callId in this._previousCallbacks) { 265 hasLongRunningCalls = true; 266 break; 267 } 268 this.dispatchEventToListeners("wait", hasLongRunningCalls); 269 for (callId in this._callbacks) 270 this._previousCallbacks[callId] = true; 271 }, 272 273 _findFunction: function(name) 274 { 275 var path = name.split("."); 276 var result = window; 277 for (var i = 0; i < path.length; ++i) 278 result = result[path[i]]; 279 return result; 280 }, 281 282 _messageReceived: function(event) 283 { 284 var data = event.data; 285 if (data.eventName) { 286 if (this._eventHandler) 287 this._eventHandler(data.eventName, data.data); 288 return; 289 } 290 if (data.error) { 291 if (data.errorMethodName) 292 WebInspector.log(WebInspector.UIString("An error happened when a call for method '%s' was requested", data.errorMethodName)); 293 WebInspector.log(data.errorCallStack); 294 delete this._callbacks[data.callId]; 295 return; 296 } 297 if (!this._callbacks[data.callId]) 298 return; 299 var callback = this._callbacks[data.callId]; 300 delete this._callbacks[data.callId]; 301 callback(data.result); 302 }, 303 304 _postMessage: function(message) 305 { 306 this._worker.postMessage(message); 307 }, 308 309 __proto__: WebInspector.Object.prototype 310 } 311 312 313 /** 314 * @constructor 315 */ 316 WebInspector.HeapSnapshotProxyObject = function(worker, objectId) 317 { 318 this._worker = worker; 319 this._objectId = objectId; 320 } 321 322 WebInspector.HeapSnapshotProxyObject.prototype = { 323 _callWorker: function(workerMethodName, args) 324 { 325 args.splice(1, 0, this._objectId); 326 return this._worker[workerMethodName].apply(this._worker, args); 327 }, 328 329 dispose: function() 330 { 331 this._worker.disposeObject(this._objectId); 332 }, 333 334 disposeWorker: function() 335 { 336 this._worker.dispose(); 337 }, 338 339 /** 340 * @param {...*} var_args 341 */ 342 callFactoryMethod: function(callback, methodName, proxyConstructor, var_args) 343 { 344 return this._callWorker("callFactoryMethod", Array.prototype.slice.call(arguments, 0)); 345 }, 346 347 callGetter: function(callback, getterName) 348 { 349 return this._callWorker("callGetter", Array.prototype.slice.call(arguments, 0)); 350 }, 351 352 /** 353 * @param {...*} var_args 354 */ 355 callMethod: function(callback, methodName, var_args) 356 { 357 return this._callWorker("callMethod", Array.prototype.slice.call(arguments, 0)); 358 }, 359 360 get worker() { 361 return this._worker; 362 } 363 }; 364 365 /** 366 * @constructor 367 * @extends {WebInspector.HeapSnapshotProxyObject} 368 * @implements {WebInspector.OutputStream} 369 */ 370 WebInspector.HeapSnapshotLoaderProxy = function(worker, objectId, snapshotConstructorName, proxyConstructor) 371 { 372 WebInspector.HeapSnapshotProxyObject.call(this, worker, objectId); 373 this._snapshotConstructorName = snapshotConstructorName; 374 this._proxyConstructor = proxyConstructor; 375 this._pendingSnapshotConsumers = []; 376 } 377 378 WebInspector.HeapSnapshotLoaderProxy.prototype = { 379 /** 380 * @param {function(!WebInspector.HeapSnapshotProxy)} callback 381 */ 382 addConsumer: function(callback) 383 { 384 this._pendingSnapshotConsumers.push(callback); 385 }, 386 387 /** 388 * @param {string} chunk 389 * @param {function(!WebInspector.OutputStream)=} callback 390 */ 391 write: function(chunk, callback) 392 { 393 this.callMethod(callback, "write", chunk); 394 }, 395 396 /** 397 * @param {function()=} callback 398 */ 399 close: function(callback) 400 { 401 /** 402 * @this {WebInspector.HeapSnapshotLoaderProxy} 403 */ 404 function buildSnapshot() 405 { 406 if (callback) 407 callback(); 408 this.callFactoryMethod(updateStaticData.bind(this), "buildSnapshot", this._proxyConstructor, this._snapshotConstructorName); 409 } 410 411 /** 412 * @this {WebInspector.HeapSnapshotLoaderProxy} 413 */ 414 function updateStaticData(snapshotProxy) 415 { 416 this.dispose(); 417 snapshotProxy.updateStaticData(notifyPendingConsumers.bind(this)); 418 } 419 420 /** 421 * @this {WebInspector.HeapSnapshotLoaderProxy} 422 */ 423 function notifyPendingConsumers(snapshotProxy) 424 { 425 for (var i = 0; i < this._pendingSnapshotConsumers.length; ++i) 426 this._pendingSnapshotConsumers[i](snapshotProxy); 427 this._pendingSnapshotConsumers = []; 428 } 429 430 this.callMethod(buildSnapshot.bind(this), "close"); 431 }, 432 433 __proto__: WebInspector.HeapSnapshotProxyObject.prototype 434 } 435 436 437 /** 438 * @constructor 439 * @extends {WebInspector.HeapSnapshotProxyObject} 440 */ 441 WebInspector.HeapSnapshotProxy = function(worker, objectId) 442 { 443 WebInspector.HeapSnapshotProxyObject.call(this, worker, objectId); 444 } 445 446 WebInspector.HeapSnapshotProxy.prototype = { 447 aggregates: function(sortedIndexes, key, filter, callback) 448 { 449 this.callMethod(callback, "aggregates", sortedIndexes, key, filter); 450 }, 451 452 aggregatesForDiff: function(callback) 453 { 454 this.callMethod(callback, "aggregatesForDiff"); 455 }, 456 457 calculateSnapshotDiff: function(baseSnapshotId, baseSnapshotAggregates, callback) 458 { 459 this.callMethod(callback, "calculateSnapshotDiff", baseSnapshotId, baseSnapshotAggregates); 460 }, 461 462 nodeClassName: function(snapshotObjectId, callback) 463 { 464 this.callMethod(callback, "nodeClassName", snapshotObjectId); 465 }, 466 467 dominatorIdsForNode: function(nodeIndex, callback) 468 { 469 this.callMethod(callback, "dominatorIdsForNode", nodeIndex); 470 }, 471 472 createEdgesProvider: function(nodeIndex, showHiddenData) 473 { 474 return this.callFactoryMethod(null, "createEdgesProvider", WebInspector.HeapSnapshotProviderProxy, nodeIndex, showHiddenData); 475 }, 476 477 createRetainingEdgesProvider: function(nodeIndex, showHiddenData) 478 { 479 return this.callFactoryMethod(null, "createRetainingEdgesProvider", WebInspector.HeapSnapshotProviderProxy, nodeIndex, showHiddenData); 480 }, 481 482 createAddedNodesProvider: function(baseSnapshotId, className) 483 { 484 return this.callFactoryMethod(null, "createAddedNodesProvider", WebInspector.HeapSnapshotProviderProxy, baseSnapshotId, className); 485 }, 486 487 createDeletedNodesProvider: function(nodeIndexes) 488 { 489 return this.callFactoryMethod(null, "createDeletedNodesProvider", WebInspector.HeapSnapshotProviderProxy, nodeIndexes); 490 }, 491 492 createNodesProvider: function(filter) 493 { 494 return this.callFactoryMethod(null, "createNodesProvider", WebInspector.HeapSnapshotProviderProxy, filter); 495 }, 496 497 createNodesProviderForClass: function(className, aggregatesKey) 498 { 499 return this.callFactoryMethod(null, "createNodesProviderForClass", WebInspector.HeapSnapshotProviderProxy, className, aggregatesKey); 500 }, 501 502 createNodesProviderForDominator: function(nodeIndex) 503 { 504 return this.callFactoryMethod(null, "createNodesProviderForDominator", WebInspector.HeapSnapshotProviderProxy, nodeIndex); 505 }, 506 507 maxJsNodeId: function(callback) 508 { 509 this.callMethod(callback, "maxJsNodeId"); 510 }, 511 512 allocationTracesTops: function(callback) 513 { 514 this.callMethod(callback, "allocationTracesTops"); 515 }, 516 517 allocationNodeCallers: function(nodeId, callback) 518 { 519 this.callMethod(callback, "allocationNodeCallers", nodeId); 520 }, 521 522 dispose: function() 523 { 524 this.disposeWorker(); 525 }, 526 527 get nodeCount() 528 { 529 return this._staticData.nodeCount; 530 }, 531 532 get rootNodeIndex() 533 { 534 return this._staticData.rootNodeIndex; 535 }, 536 537 updateStaticData: function(callback) 538 { 539 /** 540 * @this {WebInspector.HeapSnapshotProxy} 541 */ 542 function dataReceived(staticData) 543 { 544 this._staticData = staticData; 545 callback(this); 546 } 547 this.callMethod(dataReceived.bind(this), "updateStaticData"); 548 }, 549 550 get totalSize() 551 { 552 return this._staticData.totalSize; 553 }, 554 555 get uid() 556 { 557 return this._staticData.uid; 558 }, 559 560 __proto__: WebInspector.HeapSnapshotProxyObject.prototype 561 } 562 563 564 /** 565 * @constructor 566 * @extends {WebInspector.HeapSnapshotProxyObject} 567 */ 568 WebInspector.HeapSnapshotProviderProxy = function(worker, objectId) 569 { 570 WebInspector.HeapSnapshotProxyObject.call(this, worker, objectId); 571 } 572 573 WebInspector.HeapSnapshotProviderProxy.prototype = { 574 nodePosition: function(snapshotObjectId, callback) 575 { 576 this.callMethod(callback, "nodePosition", snapshotObjectId); 577 }, 578 579 isEmpty: function(callback) 580 { 581 this.callMethod(callback, "isEmpty"); 582 }, 583 584 serializeItemsRange: function(startPosition, endPosition, callback) 585 { 586 this.callMethod(callback, "serializeItemsRange", startPosition, endPosition); 587 }, 588 589 sortAndRewind: function(comparator, callback) 590 { 591 this.callMethod(callback, "sortAndRewind", comparator); 592 }, 593 594 __proto__: WebInspector.HeapSnapshotProxyObject.prototype 595 } 596 597