| Index: Source/devtools/front_end/TimelineModel.js
|
| diff --git a/Source/devtools/front_end/TimelineModel.js b/Source/devtools/front_end/TimelineModel.js
|
| index 3718e517d359a0beec4074af9fb3ce6eb8de05f8..bc6a5208039725c81e716e587903aa79a04165fb 100644
|
| --- a/Source/devtools/front_end/TimelineModel.js
|
| +++ b/Source/devtools/front_end/TimelineModel.js
|
| @@ -34,10 +34,12 @@
|
| */
|
| WebInspector.TimelineModel = function()
|
| {
|
| + this._payloads = [];
|
| this._records = [];
|
| this._minimumRecordTime = -1;
|
| this._maximumRecordTime = -1;
|
| this._stringPool = {};
|
| + this._bindings = new WebInspector.TimelineModel.InterRecordBindings();
|
|
|
| WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.EventTypes.TimelineEventRecorded, this._onRecordAdded, this);
|
| WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.EventTypes.TimelineStarted, this._onStarted, this);
|
| @@ -115,8 +117,47 @@ WebInspector.TimelineModel.Events = {
|
| RecordingStopped: "RecordingStopped"
|
| }
|
|
|
| +/**
|
| + * @param {!Array.<!WebInspector.TimelineModel.Record>} recordsArray
|
| + * @param {?function(!WebInspector.TimelineModel.Record)|?function(!WebInspector.TimelineModel.Record,number)} preOrderCallback
|
| + * @param {function(!WebInspector.TimelineModel.Record)|function(!WebInspector.TimelineModel.Record,number)=} postOrderCallback
|
| + */
|
| +WebInspector.TimelineModel.forAllRecords = function(recordsArray, preOrderCallback, postOrderCallback)
|
| +{
|
| + if (!recordsArray)
|
| + return;
|
| + var stack = [{array: recordsArray, index: 0}];
|
| + while (stack.length) {
|
| + var entry = stack[stack.length - 1];
|
| + var records = entry.array;
|
| + if (entry.index < records.length) {
|
| + var record = records[entry.index];
|
| + if (preOrderCallback && preOrderCallback(record, stack.length))
|
| + return;
|
| + if (record.children)
|
| + stack.push({array: record.children, index: 0, record: record});
|
| + else if (postOrderCallback && postOrderCallback(record, stack.length))
|
| + return;
|
| + ++entry.index;
|
| + } else {
|
| + if (entry.record && postOrderCallback && postOrderCallback(entry.record, stack.length))
|
| + return;
|
| + stack.pop();
|
| + }
|
| + }
|
| +}
|
| +
|
| WebInspector.TimelineModel.prototype = {
|
| /**
|
| + * @param {?function(!WebInspector.TimelineModel.Record)|?function(!WebInspector.TimelineModel.Record,number)} preOrderCallback
|
| + * @param {function(!WebInspector.TimelineModel.Record)|function(!WebInspector.TimelineModel.Record,number)=} postOrderCallback
|
| + */
|
| + forAllRecords: function(preOrderCallback, postOrderCallback)
|
| + {
|
| + WebInspector.TimelineModel.forAllRecords(this._records, preOrderCallback, postOrderCallback);
|
| + },
|
| +
|
| + /**
|
| * @param {boolean=} includeCounters
|
| */
|
| startRecording: function(includeCounters)
|
| @@ -149,7 +190,10 @@ WebInspector.TimelineModel.prototype = {
|
| WebInspector.timelineManager.stop(this._fireRecordingStopped.bind(this));
|
| },
|
|
|
| - get records()
|
| + /**
|
| + * @return {!Array.<!WebInspector.TimelineModel.Record>}
|
| + */
|
| + records: function()
|
| {
|
| return this._records;
|
| },
|
| @@ -198,17 +242,39 @@ WebInspector.TimelineModel.prototype = {
|
| },
|
|
|
| /**
|
| - * @param {!TimelineAgent.TimelineEvent} record
|
| + * @param {!TimelineAgent.TimelineEvent} payload
|
| */
|
| - _addRecord: function(record)
|
| + _addRecord: function(payload)
|
| {
|
| - this._internStringsAndAssignEndTime(record);
|
| + this._internStrings(payload);
|
| + this._payloads.push(payload);
|
| + this._updateBoundaries(payload);
|
| +
|
| + var record = this._innerAddRecord(payload, null);
|
| this._records.push(record);
|
| - this._updateBoundaries(record);
|
| +
|
| this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordAdded, record);
|
| },
|
|
|
| /**
|
| + * @param {!TimelineAgent.TimelineEvent} payload
|
| + * @param {?WebInspector.TimelineModel.Record} parentRecord
|
| + * @return {!WebInspector.TimelineModel.Record}
|
| + * @this {!WebInspector.TimelineModel}
|
| + */
|
| + _innerAddRecord: function(payload, parentRecord)
|
| + {
|
| + var record = new WebInspector.TimelineModel.Record(this, payload, parentRecord);
|
| + 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
|
| */
|
| @@ -256,7 +322,7 @@ WebInspector.TimelineModel.prototype = {
|
| if (!accepted)
|
| return;
|
| var saver = new WebInspector.TimelineSaver(stream);
|
| - saver.save(this._records, window.navigator.appVersion);
|
| + saver.save(this._payloads, window.navigator.appVersion);
|
| }
|
| stream.open(fileName, callback.bind(this));
|
| },
|
| @@ -264,9 +330,11 @@ WebInspector.TimelineModel.prototype = {
|
| reset: function()
|
| {
|
| this._records = [];
|
| + this._payloads = [];
|
| this._stringPool = {};
|
| this._minimumRecordTime = -1;
|
| this._maximumRecordTime = -1;
|
| + this._bindings._reset();
|
| this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordsCleared);
|
| },
|
|
|
| @@ -312,14 +380,8 @@ WebInspector.TimelineModel.prototype = {
|
| /**
|
| * @param {!TimelineAgent.TimelineEvent} record
|
| */
|
| - _internStringsAndAssignEndTime: function(record)
|
| + _internStrings: function(record)
|
| {
|
| - // We'd like to dump raw protocol in tests, so add an option to not assign implicit end time.
|
| - if (!WebInspector.TimelineModel["_doNotAssignEndTime"]) {
|
| - if (typeof record.startTime === "number" && typeof record.endTime !== "number")
|
| - record.endTime = record.startTime;
|
| - }
|
| -
|
| for (var name in record) {
|
| var value = record[name];
|
| if (typeof value !== "string")
|
| @@ -334,12 +396,421 @@ WebInspector.TimelineModel.prototype = {
|
|
|
| var children = record.children;
|
| for (var i = 0; children && i < children.length; ++i)
|
| - this._internStringsAndAssignEndTime(children[i]);
|
| + this._internStrings(children[i]);
|
| },
|
|
|
| __proto__: WebInspector.Object.prototype
|
| }
|
|
|
| +
|
| +/**
|
| + * @constructor
|
| + */
|
| +WebInspector.TimelineModel.InterRecordBindings = function() {
|
| + this._reset();
|
| +}
|
| +
|
| +WebInspector.TimelineModel.InterRecordBindings.prototype = {
|
| + _reset: function()
|
| + {
|
| + this._sendRequestRecords = {};
|
| + this._timerRecords = {};
|
| + this._requestAnimationFrameRecords = {};
|
| + this._layoutInvalidateStack = {};
|
| + this._lastScheduleStyleRecalculation = {};
|
| + this._webSocketCreateRecords = {};
|
| + }
|
| +}
|
| +
|
| +/**
|
| + * @constructor
|
| + * @param {!WebInspector.TimelineModel} model
|
| + * @param {!TimelineAgent.TimelineEvent} record
|
| + * @param {?WebInspector.TimelineModel.Record} parentRecord
|
| + */
|
| +WebInspector.TimelineModel.Record = function(model, record, parentRecord)
|
| +{
|
| + this._model = model;
|
| + var bindings = this._model._bindings;
|
| + this._aggregatedStats = {};
|
| + this._record = record;
|
| + this._children = [];
|
| + if (parentRecord) {
|
| + this.parent = parentRecord;
|
| + parentRecord.children.push(this);
|
| + }
|
| +
|
| + this._selfTime = this.endTime - this.startTime;
|
| + this._lastChildEndTime = this.endTime;
|
| + this._startTimeOffset = this.startTime - model.minimumRecordTime();
|
| +
|
| + if (record.data) {
|
| + if (record.data["url"])
|
| + this.url = record.data["url"];
|
| + if (record.data["rootNode"])
|
| + this._relatedBackendNodeId = record.data["rootNode"];
|
| + else if (record.data["elementId"])
|
| + this._relatedBackendNodeId = record.data["elementId"];
|
| + if (record.data["scriptName"]) {
|
| + this.scriptName = record.data["scriptName"];
|
| + this.scriptLine = record.data["scriptLine"];
|
| + }
|
| + }
|
| +
|
| + if (parentRecord && parentRecord.callSiteStackTrace)
|
| + this.callSiteStackTrace = parentRecord.callSiteStackTrace;
|
| +
|
| + var recordTypes = WebInspector.TimelineModel.RecordType;
|
| + switch (record.type) {
|
| + case recordTypes.ResourceSendRequest:
|
| + // Make resource receive record last since request was sent; make finish record last since response received.
|
| + bindings._sendRequestRecords[record.data["requestId"]] = this;
|
| + break;
|
| +
|
| + case recordTypes.ResourceReceiveResponse:
|
| + var sendRequestRecord = bindings._sendRequestRecords[record.data["requestId"]];
|
| + if (sendRequestRecord) // False if we started instrumentation in the middle of request.
|
| + this.url = sendRequestRecord.url;
|
| + break;
|
| +
|
| + case recordTypes.ResourceReceivedData:
|
| + case recordTypes.ResourceFinish:
|
| + var sendRequestRecord = bindings._sendRequestRecords[record.data["requestId"]];
|
| + if (sendRequestRecord) // False for main resource.
|
| + this.url = sendRequestRecord.url;
|
| + break;
|
| +
|
| + case recordTypes.TimerInstall:
|
| + this.timeout = record.data["timeout"];
|
| + this.singleShot = record.data["singleShot"];
|
| + bindings._timerRecords[record.data["timerId"]] = this;
|
| + break;
|
| +
|
| + case recordTypes.TimerFire:
|
| + var timerInstalledRecord = bindings._timerRecords[record.data["timerId"]];
|
| + if (timerInstalledRecord) {
|
| + this.callSiteStackTrace = timerInstalledRecord.stackTrace;
|
| + this.timeout = timerInstalledRecord.timeout;
|
| + this.singleShot = timerInstalledRecord.singleShot;
|
| + }
|
| + break;
|
| +
|
| + case recordTypes.RequestAnimationFrame:
|
| + bindings._requestAnimationFrameRecords[record.data["id"]] = this;
|
| + break;
|
| +
|
| + case recordTypes.FireAnimationFrame:
|
| + var requestAnimationRecord = bindings._requestAnimationFrameRecords[record.data["id"]];
|
| + if (requestAnimationRecord)
|
| + this.callSiteStackTrace = requestAnimationRecord.stackTrace;
|
| + break;
|
| +
|
| + case recordTypes.ConsoleTime:
|
| + var message = record.data["message"];
|
| + break;
|
| +
|
| + case recordTypes.ScheduleStyleRecalculation:
|
| + bindings._lastScheduleStyleRecalculation[this.frameId] = this;
|
| + break;
|
| +
|
| + case recordTypes.RecalculateStyles:
|
| + var scheduleStyleRecalculationRecord = bindings._lastScheduleStyleRecalculation[this.frameId];
|
| + if (!scheduleStyleRecalculationRecord)
|
| + break;
|
| + this.callSiteStackTrace = scheduleStyleRecalculationRecord.stackTrace;
|
| + break;
|
| +
|
| + case recordTypes.InvalidateLayout:
|
| + // Consider style recalculation as a reason for layout invalidation,
|
| + // but only if we had no earlier layout invalidation records.
|
| + var styleRecalcStack;
|
| + if (!bindings._layoutInvalidateStack[this.frameId]) {
|
| + if (parentRecord.type === recordTypes.RecalculateStyles)
|
| + styleRecalcStack = parentRecord.callSiteStackTrace;
|
| + }
|
| + bindings._layoutInvalidateStack[this.frameId] = styleRecalcStack || this.stackTrace;
|
| + break;
|
| +
|
| + case recordTypes.Layout:
|
| + var layoutInvalidateStack = bindings._layoutInvalidateStack[this.frameId];
|
| + if (layoutInvalidateStack)
|
| + this.callSiteStackTrace = layoutInvalidateStack;
|
| + if (this.stackTrace)
|
| + this.addWarning(WebInspector.UIString("Forced synchronous layout is a possible performance bottleneck."));
|
| +
|
| + bindings._layoutInvalidateStack[this.frameId] = null;
|
| + this.highlightQuad = record.data.root || WebInspector.TimelineModel._quadFromRectData(record.data);
|
| + this._relatedBackendNodeId = record.data["rootNode"];
|
| + break;
|
| +
|
| + case recordTypes.AutosizeText:
|
| + if (record.data.needsRelayout && parentRecord.type === recordTypes.Layout)
|
| + parentRecord.addWarning(WebInspector.UIString("Layout required two passes due to text autosizing, consider setting viewport."));
|
| + break;
|
| +
|
| + case recordTypes.Paint:
|
| + this.highlightQuad = record.data.clip || WebInspector.TimelineModel._quadFromRectData(record.data);
|
| + break;
|
| +
|
| + case recordTypes.WebSocketCreate:
|
| + this.webSocketURL = record.data["url"];
|
| + if (typeof record.data["webSocketProtocol"] !== "undefined")
|
| + this.webSocketProtocol = record.data["webSocketProtocol"];
|
| + bindings._webSocketCreateRecords[record.data["identifier"]] = this;
|
| + break;
|
| +
|
| + case recordTypes.WebSocketSendHandshakeRequest:
|
| + case recordTypes.WebSocketReceiveHandshakeResponse:
|
| + case recordTypes.WebSocketDestroy:
|
| + var webSocketCreateRecord = bindings._webSocketCreateRecords[record.data["identifier"]];
|
| + if (webSocketCreateRecord) { // False if we started instrumentation in the middle of request.
|
| + this.webSocketURL = webSocketCreateRecord.webSocketURL;
|
| + if (typeof webSocketCreateRecord.webSocketProtocol !== "undefined")
|
| + this.webSocketProtocol = webSocketCreateRecord.webSocketProtocol;
|
| + }
|
| + break;
|
| +
|
| + case recordTypes.EmbedderCallback:
|
| + this.embedderCallbackName = record.data["callbackName"];
|
| + break;
|
| + }
|
| +}
|
| +
|
| +WebInspector.TimelineModel.Record.prototype = {
|
| + get lastChildEndTime()
|
| + {
|
| + return this._lastChildEndTime;
|
| + },
|
| +
|
| + set lastChildEndTime(time)
|
| + {
|
| + this._lastChildEndTime = time;
|
| + },
|
| +
|
| + get selfTime()
|
| + {
|
| + return this._selfTime;
|
| + },
|
| +
|
| + get cpuTime()
|
| + {
|
| + return this._cpuTime;
|
| + },
|
| +
|
| + /**
|
| + * @return {boolean}
|
| + */
|
| + isRoot: function()
|
| + {
|
| + return this.type === WebInspector.TimelineModel.RecordType.Root;
|
| + },
|
| +
|
| + /**
|
| + * @return {!Array.<!WebInspector.TimelineModel.Record>}
|
| + */
|
| + get children()
|
| + {
|
| + return this._children;
|
| + },
|
| +
|
| + /**
|
| + * @return {!WebInspector.TimelineCategory}
|
| + */
|
| + get category()
|
| + {
|
| + return WebInspector.TimelineUIUtils.categoryForRecord(this);
|
| + },
|
| +
|
| + /**
|
| + * @return {string}
|
| + */
|
| + title: function()
|
| + {
|
| + return WebInspector.TimelineUIUtils.recordTitle(this);
|
| + },
|
| +
|
| + /**
|
| + * @return {number}
|
| + */
|
| + get startTime()
|
| + {
|
| + return this._startTime || this._record.startTime;
|
| + },
|
| +
|
| + set startTime(startTime)
|
| + {
|
| + this._startTime = startTime;
|
| + },
|
| +
|
| + /**
|
| + * @return {string|undefined}
|
| + */
|
| + get thread()
|
| + {
|
| + return this._record.thread;
|
| + },
|
| +
|
| + /**
|
| + * @return {number}
|
| + */
|
| + get startTimeOffset()
|
| + {
|
| + return this._startTimeOffset;
|
| + },
|
| +
|
| + /**
|
| + * @return {number}
|
| + */
|
| + get endTime()
|
| + {
|
| + return this._endTime || this._record.endTime || this._record.startTime;
|
| + },
|
| +
|
| + set endTime(endTime)
|
| + {
|
| + this._endTime = endTime;
|
| + },
|
| +
|
| + /**
|
| + * @return {!Object}
|
| + */
|
| + get data()
|
| + {
|
| + return this._record.data;
|
| + },
|
| +
|
| + /**
|
| + * @return {string}
|
| + */
|
| + get type()
|
| + {
|
| + return this._record.type;
|
| + },
|
| +
|
| + /**
|
| + * @return {string}
|
| + */
|
| + get frameId()
|
| + {
|
| + return this._record.frameId || "";
|
| + },
|
| +
|
| + /**
|
| + * @return {number}
|
| + */
|
| + get usedHeapSizeDelta()
|
| + {
|
| + return this._record.usedHeapSizeDelta || 0;
|
| + },
|
| +
|
| + /**
|
| + * @return {number}
|
| + */
|
| + get jsHeapSizeUsed()
|
| + {
|
| + return this._record.counters ? this._record.counters.jsHeapSizeUsed || 0 : 0;
|
| + },
|
| +
|
| + /**
|
| + * @return {!Object|undefined}
|
| + */
|
| + get counters()
|
| + {
|
| + return this._record.counters;
|
| + },
|
| +
|
| + /**
|
| + * @return {?Array.<!ConsoleAgent.CallFrame>}
|
| + */
|
| + get stackTrace()
|
| + {
|
| + 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);
|
| + },
|
| +
|
| + /**
|
| + * @return {number} nodeId
|
| + */
|
| + relatedBackendNodeId: function()
|
| + {
|
| + return this._relatedBackendNodeId;
|
| + },
|
| +
|
| + calculateAggregatedStats: function()
|
| + {
|
| + this._aggregatedStats = {};
|
| + this._cpuTime = this._selfTime;
|
| +
|
| + 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];
|
| + }
|
| + for (var category in this._aggregatedStats)
|
| + this._cpuTime += this._aggregatedStats[category];
|
| + this._aggregatedStats[this.category.name] = (this._aggregatedStats[this.category.name] || 0) + this._selfTime;
|
| + },
|
| +
|
| + get aggregatedStats()
|
| + {
|
| + return this._aggregatedStats;
|
| + },
|
| +
|
| + /**
|
| + * @param {string} message
|
| + */
|
| + addWarning: function(message)
|
| + {
|
| + if (this._warnings)
|
| + this._warnings.push(message);
|
| + else
|
| + this._warnings = [message];
|
| + },
|
| +
|
| + /**
|
| + * @return {!Object}
|
| + */
|
| + warnings: function()
|
| + {
|
| + return this._warnings;
|
| + },
|
| +
|
| + /**
|
| + * @param {!RegExp} regExp
|
| + * @return {boolean}
|
| + */
|
| + testContentMatching: function(regExp)
|
| + {
|
| + var tokens = [this.title()];
|
| + for (var key in this._record.data)
|
| + tokens.push(this._record.data[key])
|
| + return regExp.test(tokens.join("|"));
|
| + }
|
| +}
|
| +
|
| /**
|
| * @constructor
|
| * @implements {WebInspector.OutputStream}
|
| @@ -479,12 +950,12 @@ WebInspector.TimelineSaver = function(stream)
|
|
|
| WebInspector.TimelineSaver.prototype = {
|
| /**
|
| - * @param {!Array.<*>} records
|
| + * @param {!Array.<*>} payloads
|
| * @param {string} version
|
| */
|
| - save: function(records, version)
|
| + save: function(payloads, version)
|
| {
|
| - this._records = records;
|
| + this._payloads = payloads;
|
| this._recordIndex = 0;
|
| this._prologue = "[" + JSON.stringify(version);
|
|
|
| @@ -502,14 +973,14 @@ WebInspector.TimelineSaver.prototype = {
|
| length += this._prologue.length;
|
| delete this._prologue;
|
| } else {
|
| - if (this._recordIndex === this._records.length) {
|
| + if (this._recordIndex === this._payloads.length) {
|
| stream.close();
|
| return;
|
| }
|
| data.push("");
|
| }
|
| - while (this._recordIndex < this._records.length) {
|
| - var item = JSON.stringify(this._records[this._recordIndex]);
|
| + while (this._recordIndex < this._payloads.length) {
|
| + var item = JSON.stringify(this._payloads[this._recordIndex]);
|
| var itemLength = item.length + separator.length;
|
| if (length + itemLength > WebInspector.TimelineModel.TransferChunkLengthBytes)
|
| break;
|
| @@ -517,7 +988,7 @@ WebInspector.TimelineSaver.prototype = {
|
| data.push(item);
|
| ++this._recordIndex;
|
| }
|
| - if (this._recordIndex === this._records.length)
|
| + if (this._recordIndex === this._payloads.length)
|
| data.push(data.pop() + "]");
|
| stream.write(data.join(separator), this._writeNextChunk.bind(this));
|
| }
|
| @@ -559,4 +1030,19 @@ WebInspector.TimelineMergingRecordBuffer.prototype = {
|
| this._backgroundRecordsBuffer = [];
|
| return result;
|
| }
|
| -};
|
| +}
|
| +
|
| +/**
|
| + * @param {!Object} data
|
| + * @return {?Array.<number>}
|
| + */
|
| +WebInspector.TimelineModel._quadFromRectData = function(data)
|
| +{
|
| + if (typeof data["x"] === "undefined" || typeof data["y"] === "undefined")
|
| + return null;
|
| + var x0 = data["x"];
|
| + var x1 = data["x"] + data["width"];
|
| + var y0 = data["y"];
|
| + var y1 = data["y"] + data["height"];
|
| + return [x0, y0, x1, y0, x1, y1, x0, y1];
|
| +}
|
|
|