summaryrefslogtreecommitdiffstats
path: root/chromium/third_party/WebKit/Source/devtools/front_end/timeline/TimelineModelImpl.js
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/third_party/WebKit/Source/devtools/front_end/timeline/TimelineModelImpl.js')
-rw-r--r--chromium/third_party/WebKit/Source/devtools/front_end/timeline/TimelineModelImpl.js739
1 files changed, 739 insertions, 0 deletions
diff --git a/chromium/third_party/WebKit/Source/devtools/front_end/timeline/TimelineModelImpl.js b/chromium/third_party/WebKit/Source/devtools/front_end/timeline/TimelineModelImpl.js
new file mode 100644
index 00000000000..a18efee6063
--- /dev/null
+++ b/chromium/third_party/WebKit/Source/devtools/front_end/timeline/TimelineModelImpl.js
@@ -0,0 +1,739 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @constructor
+ * @extends {WebInspector.TimelineModel}
+ * @param {!WebInspector.TimelineManager} timelineManager
+ */
+WebInspector.TimelineModelImpl = function(timelineManager)
+{
+ WebInspector.TimelineModel.call(this, timelineManager.target());
+ this._timelineManager = timelineManager;
+ this._filters = [];
+ this._bindings = new WebInspector.TimelineModelImpl.InterRecordBindings();
+
+ this.reset();
+
+ this._timelineManager.addEventListener(WebInspector.TimelineManager.EventTypes.TimelineEventRecorded, this._onRecordAdded, this);
+ this._timelineManager.addEventListener(WebInspector.TimelineManager.EventTypes.TimelineStarted, this._onStarted, this);
+ this._timelineManager.addEventListener(WebInspector.TimelineManager.EventTypes.TimelineStopped, this._onStopped, this);
+ this._timelineManager.addEventListener(WebInspector.TimelineManager.EventTypes.TimelineProgress, this._onProgress, this);
+}
+
+WebInspector.TimelineModelImpl.TransferChunkLengthBytes = 5000000;
+
+WebInspector.TimelineModelImpl.prototype = {
+ /**
+ * @return {boolean}
+ */
+ loadedFromFile: function()
+ {
+ return this._loadedFromFile;
+ },
+
+ /**
+ * @param {boolean} captureStacks
+ * @param {boolean} captureMemory
+ * @param {boolean} capturePictures
+ */
+ startRecording: function(captureStacks, captureMemory, capturePictures)
+ {
+ console.assert(!capturePictures, "Legacy timeline does not support capturing pictures");
+ this._clientInitiatedRecording = true;
+ this.reset();
+ var maxStackFrames = captureStacks ? 30 : 0;
+ var includeGPUEvents = WebInspector.experimentsSettings.gpuTimeline.isEnabled();
+ var liveEvents = [ WebInspector.TimelineModel.RecordType.BeginFrame,
+ WebInspector.TimelineModel.RecordType.DrawFrame,
+ WebInspector.TimelineModel.RecordType.RequestMainThreadFrame,
+ WebInspector.TimelineModel.RecordType.ActivateLayerTree ];
+ this._timelineManager.start(maxStackFrames, WebInspector.experimentsSettings.timelineNoLiveUpdate.isEnabled(), liveEvents.join(","), captureMemory, includeGPUEvents, this._fireRecordingStarted.bind(this));
+ },
+
+ stopRecording: function()
+ {
+ if (!this._clientInitiatedRecording) {
+ this._timelineManager.start(undefined, undefined, undefined, undefined, undefined, stopTimeline.bind(this));
+ return;
+ }
+
+ /**
+ * Console started this one and we are just sniffing it. Initiate recording so that we
+ * could stop it.
+ * @this {WebInspector.TimelineModelImpl}
+ */
+ function stopTimeline()
+ {
+ this._timelineManager.stop(this._fireRecordingStopped.bind(this));
+ }
+
+ this._clientInitiatedRecording = false;
+ this._timelineManager.stop(this._fireRecordingStopped.bind(this));
+ },
+
+ /**
+ * @return {!Array.<!WebInspector.TimelineModel.Record>}
+ */
+ records: function()
+ {
+ return this._records;
+ },
+
+ /**
+ * @param {!WebInspector.Event} event
+ */
+ _onRecordAdded: function(event)
+ {
+ if (this._collectionEnabled)
+ this._addRecord(/** @type {!TimelineAgent.TimelineEvent} */(event.data));
+ },
+
+ /**
+ * @param {!WebInspector.Event} event
+ */
+ _onStarted: function(event)
+ {
+ if (event.data) {
+ // Started from console.
+ this._fireRecordingStarted();
+ }
+ },
+
+ /**
+ * @param {!WebInspector.Event} event
+ */
+ _onStopped: function(event)
+ {
+ // If we were buffering events, discard those that got through, the real ones are coming!
+ if (WebInspector.experimentsSettings.timelineNoLiveUpdate.isEnabled())
+ this.reset();
+ if (event.data) {
+ // Stopped from console.
+ this._fireRecordingStopped(null, null);
+ }
+ },
+
+ /**
+ * @param {!WebInspector.Event} event
+ */
+ _onProgress: function(event)
+ {
+ this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordingProgress, event.data);
+ },
+
+ _fireRecordingStarted: function()
+ {
+ this._collectionEnabled = true;
+ this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordingStarted);
+ },
+
+ /**
+ * @param {?Protocol.Error} error
+ * @param {?ProfilerAgent.CPUProfile} cpuProfile
+ */
+ _fireRecordingStopped: function(error, cpuProfile)
+ {
+ this._collectionEnabled = false;
+ if (cpuProfile)
+ WebInspector.TimelineJSProfileProcessor.mergeJSProfileIntoTimeline(this, cpuProfile);
+ this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordingStopped);
+ },
+
+ /**
+ * @param {!TimelineAgent.TimelineEvent} payload
+ */
+ _addRecord: function(payload)
+ {
+ this._internStrings(payload);
+ this._payloads.push(payload);
+
+ var record = this._innerAddRecord(payload, null);
+ this._updateBoundaries(record);
+ this._records.push(record);
+ if (record.type() === WebInspector.TimelineModel.RecordType.Program)
+ this._mainThreadTasks.push(record);
+ if (record.type() === WebInspector.TimelineModel.RecordType.GPUTask)
+ this._gpuThreadTasks.push(record);
+
+ this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordAdded, record);
+ },
+
+ /**
+ * @param {!TimelineAgent.TimelineEvent} payload
+ * @param {?WebInspector.TimelineModel.Record} parentRecord
+ * @return {!WebInspector.TimelineModel.Record}
+ */
+ _innerAddRecord: function(payload, parentRecord)
+ {
+ var record = new WebInspector.TimelineModel.RecordImpl(this, payload, parentRecord);
+ if (WebInspector.TimelineUIUtilsImpl.isEventDivider(record))
+ this._eventDividerRecords.push(record);
+
+ for (var i = 0; payload.children && i < payload.children.length; ++i)
+ this._innerAddRecord.call(this, payload.children[i], record);
+
+ record._calculateAggregatedStats();
+ if (parentRecord)
+ parentRecord._selfTime -= record.endTime() - record.startTime();
+ return record;
+ },
+
+ /**
+ * @param {!Blob} file
+ * @param {!WebInspector.Progress} progress
+ */
+ loadFromFile: function(file, progress)
+ {
+ var delegate = new WebInspector.TimelineModelLoadFromFileDelegate(this, progress);
+ var fileReader = this._createFileReader(file, delegate);
+ var loader = new WebInspector.TimelineModelLoader(this, fileReader, progress);
+ fileReader.start(loader);
+ },
+
+ /**
+ * @param {string} url
+ * @param {!WebInspector.Progress} progress
+ */
+ loadFromURL: function(url, progress)
+ {
+ var delegate = new WebInspector.TimelineModelLoadFromFileDelegate(this, progress);
+ var urlReader = new WebInspector.ChunkedXHRReader(url, delegate);
+ var loader = new WebInspector.TimelineModelLoader(this, urlReader, progress);
+ urlReader.start(loader);
+ },
+
+ _createFileReader: function(file, delegate)
+ {
+ return new WebInspector.ChunkedFileReader(file, WebInspector.TimelineModelImpl.TransferChunkLengthBytes, delegate);
+ },
+
+ _createFileWriter: function()
+ {
+ return new WebInspector.FileOutputStream();
+ },
+
+ saveToFile: function()
+ {
+ var now = new Date();
+ var fileName = "TimelineRawData-" + now.toISO8601Compact() + ".json";
+ var stream = this._createFileWriter();
+
+ /**
+ * @param {boolean} accepted
+ * @this {WebInspector.TimelineModelImpl}
+ */
+ function callback(accepted)
+ {
+ if (!accepted)
+ return;
+ var saver = new WebInspector.TimelineSaver(stream);
+ saver.save(this._payloads, window.navigator.appVersion);
+ }
+ stream.open(fileName, callback.bind(this));
+ },
+
+ reset: function()
+ {
+ this._loadedFromFile = false;
+ this._payloads = [];
+ this._stringPool = {};
+ this._bindings._reset();
+ WebInspector.TimelineModel.prototype.reset.call(this);
+ },
+
+ /**
+ * @param {!TimelineAgent.TimelineEvent} record
+ */
+ _internStrings: function(record)
+ {
+ for (var name in record) {
+ var value = record[name];
+ if (typeof value !== "string")
+ continue;
+
+ var interned = this._stringPool[value];
+ if (typeof interned === "string")
+ record[name] = interned;
+ else
+ this._stringPool[value] = value;
+ }
+
+ var children = record.children;
+ for (var i = 0; children && i < children.length; ++i)
+ this._internStrings(children[i]);
+ },
+
+ __proto__: WebInspector.TimelineModel.prototype
+}
+
+
+/**
+ * @constructor
+ */
+WebInspector.TimelineModelImpl.InterRecordBindings = function() {
+ this._reset();
+}
+
+WebInspector.TimelineModelImpl.InterRecordBindings.prototype = {
+ _reset: function()
+ {
+ this._sendRequestRecords = {};
+ this._timerRecords = {};
+ this._requestAnimationFrameRecords = {};
+ this._layoutInvalidate = {};
+ this._lastScheduleStyleRecalculation = {};
+ this._webSocketCreateRecords = {};
+ }
+}
+
+/**
+ * @constructor
+ * @implements {WebInspector.TimelineModel.Record}
+ * @param {!WebInspector.TimelineModelImpl} model
+ * @param {!TimelineAgent.TimelineEvent} timelineEvent
+ * @param {?WebInspector.TimelineModel.Record} parentRecord
+ */
+WebInspector.TimelineModel.RecordImpl = function(model, timelineEvent, parentRecord)
+{
+ this._model = model;
+ var bindings = this._model._bindings;
+ this._aggregatedStats = {};
+ this._record = timelineEvent;
+ this._children = [];
+ if (parentRecord) {
+ this.parent = parentRecord;
+ parentRecord.children().push(this);
+ }
+
+ this._selfTime = this.endTime() - this.startTime();
+
+ var recordTypes = WebInspector.TimelineModel.RecordType;
+ switch (timelineEvent.type) {
+ case recordTypes.ResourceSendRequest:
+ // Make resource receive record last since request was sent; make finish record last since response received.
+ bindings._sendRequestRecords[timelineEvent.data["requestId"]] = this;
+ break;
+
+ case recordTypes.ResourceReceiveResponse:
+ case recordTypes.ResourceReceivedData:
+ case recordTypes.ResourceFinish:
+ this._initiator = bindings._sendRequestRecords[timelineEvent.data["requestId"]];
+ break;
+
+ case recordTypes.TimerInstall:
+ bindings._timerRecords[timelineEvent.data["timerId"]] = this;
+ break;
+
+ case recordTypes.TimerFire:
+ this._initiator = bindings._timerRecords[timelineEvent.data["timerId"]];
+ break;
+
+ case recordTypes.RequestAnimationFrame:
+ bindings._requestAnimationFrameRecords[timelineEvent.data["id"]] = this;
+ break;
+
+ case recordTypes.FireAnimationFrame:
+ this._initiator = bindings._requestAnimationFrameRecords[timelineEvent.data["id"]];
+ break;
+
+ case recordTypes.ScheduleStyleRecalculation:
+ bindings._lastScheduleStyleRecalculation[this.frameId()] = this;
+ break;
+
+ case recordTypes.RecalculateStyles:
+ this._initiator = bindings._lastScheduleStyleRecalculation[this.frameId()];
+ break;
+
+ case recordTypes.InvalidateLayout:
+ // Consider style recalculation as a reason for layout invalidation,
+ // but only if we had no earlier layout invalidation records.
+ var layoutInitator = this;
+ if (!bindings._layoutInvalidate[this.frameId()] && parentRecord.type() === recordTypes.RecalculateStyles)
+ layoutInitator = parentRecord._initiator;
+ bindings._layoutInvalidate[this.frameId()] = layoutInitator;
+ break;
+
+ case recordTypes.Layout:
+ this._initiator = bindings._layoutInvalidate[this.frameId()];
+ bindings._layoutInvalidate[this.frameId()] = null;
+ if (this.stackTrace())
+ this.addWarning(WebInspector.UIString("Forced synchronous layout is a possible performance bottleneck."));
+ break;
+
+ case recordTypes.WebSocketCreate:
+ bindings._webSocketCreateRecords[timelineEvent.data["identifier"]] = this;
+ break;
+
+ case recordTypes.WebSocketSendHandshakeRequest:
+ case recordTypes.WebSocketReceiveHandshakeResponse:
+ case recordTypes.WebSocketDestroy:
+ this._initiator = bindings._webSocketCreateRecords[timelineEvent.data["identifier"]];
+ break;
+ }
+}
+
+WebInspector.TimelineModel.RecordImpl.prototype = {
+ /**
+ * @return {?Array.<!ConsoleAgent.CallFrame>}
+ */
+ callSiteStackTrace: function()
+ {
+ return this._initiator ? this._initiator.stackTrace() : null;
+ },
+
+ /**
+ * @return {?WebInspector.TimelineModel.Record}
+ */
+ initiator: function()
+ {
+ return this._initiator;
+ },
+
+ /**
+ * @return {!WebInspector.Target}
+ */
+ target: function()
+ {
+ return this._model.target();
+ },
+
+ /**
+ * @return {number}
+ */
+ selfTime: function()
+ {
+ return this._selfTime;
+ },
+
+ /**
+ * @return {!Array.<!WebInspector.TimelineModel.Record>}
+ */
+ children: function()
+ {
+ return this._children;
+ },
+
+ /**
+ * @return {!WebInspector.TimelineCategory}
+ */
+ category: function()
+ {
+ return WebInspector.TimelineUIUtils.recordStyle(this).category;
+ },
+
+ /**
+ * @return {number}
+ */
+ startTime: function()
+ {
+ return this._record.startTime;
+ },
+
+ /**
+ * @return {string|undefined}
+ */
+ thread: function()
+ {
+ return this._record.thread;
+ },
+
+ /**
+ * @return {number}
+ */
+ endTime: function()
+ {
+ return this._endTime || this._record.endTime || this._record.startTime;
+ },
+
+ /**
+ * @param {number} endTime
+ */
+ setEndTime: function(endTime)
+ {
+ this._endTime = endTime;
+ },
+
+ /**
+ * @return {!Object}
+ */
+ data: function()
+ {
+ return this._record.data;
+ },
+
+ /**
+ * @return {string}
+ */
+ type: function()
+ {
+ return this._record.type;
+ },
+
+ /**
+ * @return {string}
+ */
+ frameId: function()
+ {
+ return this._record.frameId || "";
+ },
+
+ /**
+ * @return {?Array.<!ConsoleAgent.CallFrame>}
+ */
+ stackTrace: function()
+ {
+ if (this._record.stackTrace && this._record.stackTrace.length)
+ return this._record.stackTrace;
+ return null;
+ },
+
+ /**
+ * @param {string} key
+ * @return {?Object}
+ */
+ getUserObject: function(key)
+ {
+ if (!this._userObjects)
+ return null;
+ return this._userObjects.get(key);
+ },
+
+ /**
+ * @param {string} key
+ * @param {?Object|undefined} value
+ */
+ setUserObject: function(key, value)
+ {
+ if (!this._userObjects)
+ this._userObjects = new StringMap();
+ this._userObjects.put(key, value);
+ },
+
+ _calculateAggregatedStats: function()
+ {
+ this._aggregatedStats = {};
+
+ for (var index = this._children.length; index; --index) {
+ var child = this._children[index - 1];
+ for (var category in child._aggregatedStats)
+ this._aggregatedStats[category] = (this._aggregatedStats[category] || 0) + child._aggregatedStats[category];
+ }
+ this._aggregatedStats[this.category().name] = (this._aggregatedStats[this.category().name] || 0) + this._selfTime;
+ },
+
+ /**
+ * @return {!Object.<string, number>}
+ */
+ aggregatedStats: function()
+ {
+ return this._aggregatedStats;
+ },
+
+ /**
+ * @param {string} message
+ */
+ addWarning: function(message)
+ {
+ if (!this._warnings)
+ this._warnings = [];
+ this._warnings.push(message);
+ },
+
+ /**
+ * @return {?Array.<string>}
+ */
+ warnings: function()
+ {
+ return this._warnings;
+ }
+}
+
+/**
+ * @constructor
+ * @implements {WebInspector.OutputStream}
+ * @param {!WebInspector.TimelineModel} model
+ * @param {!{cancel: function()}} reader
+ * @param {!WebInspector.Progress} progress
+ */
+WebInspector.TimelineModelLoader = function(model, reader, progress)
+{
+ this._model = model;
+ this._reader = reader;
+ this._progress = progress;
+ this._buffer = "";
+ this._firstChunk = true;
+}
+
+WebInspector.TimelineModelLoader.prototype = {
+ /**
+ * @param {string} chunk
+ */
+ write: function(chunk)
+ {
+ var data = this._buffer + chunk;
+ var lastIndex = 0;
+ var index;
+ do {
+ index = lastIndex;
+ lastIndex = WebInspector.TextUtils.findBalancedCurlyBrackets(data, index);
+ } while (lastIndex !== -1)
+
+ var json = data.slice(0, index) + "]";
+ this._buffer = data.slice(index);
+
+ if (!index)
+ return;
+
+ // Prepending "0" to turn string into valid JSON.
+ if (!this._firstChunk)
+ json = "[0" + json;
+
+ var items;
+ try {
+ items = /** @type {!Array.<!TimelineAgent.TimelineEvent>} */ (JSON.parse(json));
+ } catch (e) {
+ WebInspector.messageSink.addErrorMessage("Malformed timeline data.", true);
+ this._model.reset();
+ this._reader.cancel();
+ this._progress.done();
+ return;
+ }
+
+ if (this._firstChunk) {
+ this._version = items[0];
+ this._firstChunk = false;
+ this._model.reset();
+ }
+
+ // Skip 0-th element - it is either version or 0.
+ for (var i = 1, size = items.length; i < size; ++i)
+ this._model._addRecord(items[i]);
+ },
+
+ close: function()
+ {
+ this._model._loadedFromFile = true;
+ }
+}
+
+/**
+ * @constructor
+ * @implements {WebInspector.OutputStreamDelegate}
+ * @param {!WebInspector.TimelineModel} model
+ * @param {!WebInspector.Progress} progress
+ */
+WebInspector.TimelineModelLoadFromFileDelegate = function(model, progress)
+{
+ this._model = model;
+ this._progress = progress;
+}
+
+WebInspector.TimelineModelLoadFromFileDelegate.prototype = {
+ onTransferStarted: function()
+ {
+ this._progress.setTitle(WebInspector.UIString("Loading\u2026"));
+ },
+
+ /**
+ * @param {!WebInspector.ChunkedReader} reader
+ */
+ onChunkTransferred: function(reader)
+ {
+ if (this._progress.isCanceled()) {
+ reader.cancel();
+ this._progress.done();
+ this._model.reset();
+ return;
+ }
+
+ var totalSize = reader.fileSize();
+ if (totalSize) {
+ this._progress.setTotalWork(totalSize);
+ this._progress.setWorked(reader.loadedSize());
+ }
+ },
+
+ onTransferFinished: function()
+ {
+ this._progress.done();
+ },
+
+ /**
+ * @param {!WebInspector.ChunkedReader} reader
+ * @param {!Event} event
+ */
+ onError: function(reader, event)
+ {
+ this._progress.done();
+ this._model.reset();
+ switch (event.target.error.code) {
+ case FileError.NOT_FOUND_ERR:
+ WebInspector.messageSink.addErrorMessage(WebInspector.UIString("File \"%s\" not found.", reader.fileName()), true);
+ break;
+ case FileError.NOT_READABLE_ERR:
+ WebInspector.messageSink.addErrorMessage(WebInspector.UIString("File \"%s\" is not readable", reader.fileName()), true);
+ break;
+ case FileError.ABORT_ERR:
+ break;
+ default:
+ WebInspector.messageSink.addErrorMessage(WebInspector.UIString("An error occurred while reading the file \"%s\"", reader.fileName()), true);
+ }
+ }
+}
+
+/**
+ * @constructor
+ * @param {!WebInspector.OutputStream} stream
+ */
+WebInspector.TimelineSaver = function(stream)
+{
+ this._stream = stream;
+}
+
+WebInspector.TimelineSaver.prototype = {
+ /**
+ * @param {!Array.<*>} payloads
+ * @param {string} version
+ */
+ save: function(payloads, version)
+ {
+ this._payloads = payloads;
+ this._recordIndex = 0;
+ this._prologue = "[" + JSON.stringify(version);
+
+ this._writeNextChunk(this._stream);
+ },
+
+ _writeNextChunk: function(stream)
+ {
+ const separator = ",\n";
+ var data = [];
+ var length = 0;
+
+ if (this._prologue) {
+ data.push(this._prologue);
+ length += this._prologue.length;
+ delete this._prologue;
+ } else {
+ if (this._recordIndex === this._payloads.length) {
+ stream.close();
+ return;
+ }
+ data.push("");
+ }
+ while (this._recordIndex < this._payloads.length) {
+ var item = JSON.stringify(this._payloads[this._recordIndex]);
+ var itemLength = item.length + separator.length;
+ if (length + itemLength > WebInspector.TimelineModelImpl.TransferChunkLengthBytes)
+ break;
+ length += itemLength;
+ data.push(item);
+ ++this._recordIndex;
+ }
+ if (this._recordIndex === this._payloads.length)
+ data.push(data.pop() + "]");
+ stream.write(data.join(separator), this._writeNextChunk.bind(this));
+ }
+}