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