1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
7 * @param {!WebInspector.TracingModel} tracingModel
8 * @param {!WebInspector.TimelineModel.Filter} recordFilter
9 * @extends {WebInspector.TimelineModel}
11 WebInspector.TracingTimelineModel = function(tracingModel, recordFilter)
13 WebInspector.TimelineModel.call(this);
14 this._tracingModel = tracingModel;
15 this._inspectedTargetEvents = [];
16 this._recordFilter = recordFilter;
17 this._tracingModel.addEventListener(WebInspector.TracingModel.Events.TracingStarted, this._onTracingStarted, this);
18 this._tracingModel.addEventListener(WebInspector.TracingModel.Events.TracingComplete, this._onTracingComplete, this);
22 WebInspector.TracingTimelineModel.RecordType = {
24 EventDispatch: "EventDispatch",
28 RequestMainThreadFrame: "RequestMainThreadFrame",
29 BeginFrame: "BeginFrame",
30 BeginMainThreadFrame: "BeginMainThreadFrame",
31 ActivateLayerTree: "ActivateLayerTree",
32 DrawFrame: "DrawFrame",
33 ScheduleStyleRecalculation: "ScheduleStyleRecalculation",
34 RecalculateStyles: "RecalculateStyles",
35 InvalidateLayout: "InvalidateLayout",
37 UpdateLayer: "UpdateLayer",
38 UpdateLayerTree: "UpdateLayerTree",
39 PaintSetup: "PaintSetup",
41 PaintImage: "PaintImage",
42 Rasterize: "Rasterize",
43 RasterTask: "RasterTask",
44 ScrollLayer: "ScrollLayer",
45 CompositeLayers: "CompositeLayers",
47 ParseHTML: "ParseHTML",
49 TimerInstall: "TimerInstall",
50 TimerRemove: "TimerRemove",
51 TimerFire: "TimerFire",
53 XHRReadyStateChange: "XHRReadyStateChange",
55 EvaluateScript: "EvaluateScript",
58 MarkDOMContent: "MarkDOMContent",
59 MarkFirstPaint: "MarkFirstPaint",
61 TimeStamp: "TimeStamp",
62 ConsoleTime: "ConsoleTime",
64 ResourceSendRequest: "ResourceSendRequest",
65 ResourceReceiveResponse: "ResourceReceiveResponse",
66 ResourceReceivedData: "ResourceReceivedData",
67 ResourceFinish: "ResourceFinish",
69 FunctionCall: "FunctionCall",
74 UpdateCounters: "UpdateCounters",
76 RequestAnimationFrame: "RequestAnimationFrame",
77 CancelAnimationFrame: "CancelAnimationFrame",
78 FireAnimationFrame: "FireAnimationFrame",
80 WebSocketCreate : "WebSocketCreate",
81 WebSocketSendHandshakeRequest : "WebSocketSendHandshakeRequest",
82 WebSocketReceiveHandshakeResponse : "WebSocketReceiveHandshakeResponse",
83 WebSocketDestroy : "WebSocketDestroy",
85 EmbedderCallback : "EmbedderCallback",
87 CallStack: "CallStack",
88 SetLayerTreeId: "SetLayerTreeId",
89 TracingStartedInPage: "TracingStartedInPage",
90 TracingStartedInWorker: "TracingStartedInWorker",
92 DecodeImage: "Decode Image",
93 ResizeImage: "Resize Image",
94 DrawLazyPixelRef: "Draw LazyPixelRef",
95 DecodeLazyPixelRef: "Decode LazyPixelRef",
97 LazyPixelRef: "LazyPixelRef",
98 LayerTreeHostImplSnapshot: "cc::LayerTreeHostImpl",
99 PictureSnapshot: "cc::Picture"
104 * @param {string} name
106 WebInspector.TracingTimelineModel.VirtualThread = function(name)
109 /** @type {!Array.<!WebInspector.TracingModel.Event>} */
113 WebInspector.TracingTimelineModel.prototype = {
115 * @param {boolean} captureStacks
116 * @param {boolean} captureMemory
117 * @param {boolean} capturePictures
119 startRecording: function(captureStacks, captureMemory, capturePictures)
121 function disabledByDefault(category)
123 return "disabled-by-default-" + category;
125 var categoriesArray = ["-*", disabledByDefault("devtools.timeline"), disabledByDefault("devtools.timeline.frame")];
127 categoriesArray.push(disabledByDefault("devtools.timeline.stack"));
128 if (WebInspector.experimentsSettings.timelineJSCPUProfile.isEnabled()) {
129 this._jsProfilerStarted = true;
130 this._currentTarget = WebInspector.context.flavor(WebInspector.Target);
131 this._configureCpuProfilerSamplingInterval();
132 this._currentTarget.profilerAgent().start();
135 if (capturePictures) {
136 categoriesArray = categoriesArray.concat([
137 disabledByDefault("devtools.timeline.layers"),
138 disabledByDefault("devtools.timeline.picture"),
139 disabledByDefault("blink.graphics_context_annotations")]);
141 var categories = categoriesArray.join(",");
142 this._startRecordingWithCategories(categories);
145 stopRecording: function()
147 this._stopCallbackBarrier = new CallbackBarrier();
148 if (this._jsProfilerStarted) {
149 this._currentTarget.profilerAgent().stop(this._stopCallbackBarrier.createCallback(this._didStopRecordingJSSamples.bind(this)));
150 this._jsProfilerStarted = false;
152 this._tracingModel.stop();
156 * @param {string} sessionId
157 * @param {!Array.<!WebInspector.TracingModel.EventPayload>} events
159 setEventsForTest: function(sessionId, events)
161 this._onTracingStarted();
162 this._tracingModel.setEventsForTest(sessionId, events);
163 this._onTracingComplete();
166 _configureCpuProfilerSamplingInterval: function()
168 var intervalUs = WebInspector.settings.highResolutionCpuProfiling.get() ? 100 : 1000;
169 this._currentTarget.profilerAgent().setSamplingInterval(intervalUs, didChangeInterval);
171 function didChangeInterval(error)
174 WebInspector.console.error(error);
179 * @param {string} categories
181 _startRecordingWithCategories: function(categories)
184 this._tracingModel.start(categories, "");
187 _onTracingStarted: function()
190 this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordingStarted);
193 _onTracingComplete: function()
195 if (this._stopCallbackBarrier)
196 this._stopCallbackBarrier.callWhenDone(this._didStopRecordingTraceEvents.bind(this));
198 this._didStopRecordingTraceEvents();
202 * @param {?Protocol.Error} error
203 * @param {?ProfilerAgent.CPUProfile} cpuProfile
205 _didStopRecordingJSSamples: function(error, cpuProfile)
208 WebInspector.console.error(error);
209 this._cpuProfile = cpuProfile;
212 _didStopRecordingTraceEvents: function()
214 this._stopCallbackBarrier = null;
215 var events = this._tracingModel.devtoolsPageMetadataEvents();
216 var workerMetadataEvents = this._tracingModel.devtoolsWorkerMetadataEvents();
217 events.sort(WebInspector.TracingModel.Event.compareStartTime);
219 this._resetProcessingState();
220 for (var i = 0, length = events.length; i < length; i++) {
221 var event = events[i];
222 var process = event.thread.process();
223 var startTime = event.startTime;
225 var endTime = Infinity;
227 endTime = events[i + 1].startTime;
229 var threads = process.sortedThreads();
230 for (var j = 0; j < threads.length; j++) {
231 var thread = threads[j];
232 if (thread.name() === "WebCore: Worker" && !workerMetadataEvents.some(function(e) { return e.thread === thread; }))
234 this._processThreadEvents(startTime, endTime, event.thread, thread);
237 this._resetProcessingState();
239 this._inspectedTargetEvents.sort(WebInspector.TracingModel.Event.compareStartTime);
241 if (this._cpuProfile) {
242 var jsSamples = WebInspector.TimelineJSProfileProcessor.generateTracingEventsFromCpuProfile(this, this._cpuProfile);
243 this._inspectedTargetEvents = this._inspectedTargetEvents.mergeOrdered(jsSamples, WebInspector.TracingModel.Event.orderedCompareStartTime);
244 this._setMainThreadEvents(this.mainThreadEvents().mergeOrdered(jsSamples, WebInspector.TracingModel.Event.orderedCompareStartTime));
245 this._cpuProfile = null;
248 this._buildTimelineRecords();
249 this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordingStopped);
255 minimumRecordTime: function()
257 return this._tracingModel.minimumRecordTime();
263 maximumRecordTime: function()
265 return this._tracingModel.maximumRecordTime();
269 * @return {!Array.<!WebInspector.TracingModel.Event>}
271 inspectedTargetEvents: function()
273 return this._inspectedTargetEvents;
277 * @return {!Array.<!WebInspector.TracingModel.Event>}
279 mainThreadEvents: function()
281 return this._mainThreadEvents;
285 * @param {!Array.<!WebInspector.TracingModel.Event>} events
287 _setMainThreadEvents: function(events)
289 this._mainThreadEvents = events;
293 * @return {!Array.<!WebInspector.TracingTimelineModel.VirtualThread>}
295 virtualThreads: function()
297 return this._virtualThreads;
301 * @param {!WebInspector.ChunkedFileReader} fileReader
302 * @param {!WebInspector.Progress} progress
303 * @return {!WebInspector.OutputStream}
305 createLoader: function(fileReader, progress)
307 return new WebInspector.TracingModelLoader(this, fileReader, progress);
311 * @param {!WebInspector.OutputStream} stream
313 writeToStream: function(stream)
315 var saver = new WebInspector.TracingTimelineSaver(stream);
316 var events = this._tracingModel.rawEvents();
322 this._virtualThreads = [];
323 this._mainThreadEvents = [];
324 this._inspectedTargetEvents = [];
325 WebInspector.TimelineModel.prototype.reset.call(this);
328 _buildTimelineRecords: function()
330 var recordStack = [];
331 var mainThreadEvents = this.mainThreadEvents();
332 for (var i = 0, size = mainThreadEvents.length; i < size; ++i) {
333 var event = mainThreadEvents[i];
334 while (recordStack.length) {
335 var top = recordStack.peekLast();
336 if (top._event.endTime >= event.startTime)
339 if (!recordStack.length)
340 this._addTopLevelRecord(top);
342 var record = new WebInspector.TracingTimelineModel.TraceEventRecord(this, event);
343 if (WebInspector.TracingTimelineUIUtils.isMarkerEvent(event))
344 this._eventDividerRecords.push(record);
345 if (!this._recordFilter.accept(record))
347 var parentRecord = recordStack.peekLast();
349 parentRecord._addChild(record);
351 recordStack.push(record);
353 if (recordStack.length)
354 this._addTopLevelRecord(recordStack[0]);
358 * @param {!WebInspector.TracingTimelineModel.TraceEventRecord} record
360 _addTopLevelRecord: function(record)
362 this._updateBoundaries(record);
363 this._records.push(record);
364 if (record.type() === WebInspector.TracingTimelineModel.RecordType.Program)
365 this._mainThreadTasks.push(record);
366 if (record.type() === WebInspector.TracingTimelineModel.RecordType.GPUTask)
367 this._gpuThreadTasks.push(record);
368 this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordAdded, record);
371 _resetProcessingState: function()
373 this._sendRequestEvents = {};
374 this._timerEvents = {};
375 this._requestAnimationFrameEvents = {};
376 this._layoutInvalidate = {};
377 this._lastScheduleStyleRecalculation = {};
378 this._webSocketCreateEvents = {};
379 this._paintImageEventByPixelRefId = {};
380 this._lastPaintForLayer = {};
381 this._lastRecalculateStylesEvent = null;
382 this._currentScriptEvent = null;
383 this._eventStack = [];
387 * @param {number} startTime
388 * @param {?number} endTime
389 * @param {!WebInspector.TracingModel.Thread} mainThread
390 * @param {!WebInspector.TracingModel.Thread} thread
392 _processThreadEvents: function(startTime, endTime, mainThread, thread)
394 var events = thread.events();
395 var length = events.length;
396 var i = events.lowerBound(startTime, function (time, event) { return time - event.startTime });
399 if (thread === mainThread) {
400 threadEvents = this._mainThreadEvents;
402 var virtualThread = new WebInspector.TracingTimelineModel.VirtualThread(thread.name());
403 threadEvents = virtualThread.events;
404 this._virtualThreads.push(virtualThread);
407 this._eventStack = [];
408 for (; i < length; i++) {
409 var event = events[i];
410 if (endTime && event.startTime >= endTime)
412 this._processEvent(event);
413 threadEvents.push(event);
414 this._inspectedTargetEvents.push(event);
419 * @param {!WebInspector.TracingModel.Event} event
421 _processEvent: function(event)
423 var recordTypes = WebInspector.TracingTimelineModel.RecordType;
425 var eventStack = this._eventStack;
426 while (eventStack.length && eventStack.peekLast().endTime < event.startTime)
428 var duration = event.duration;
430 if (eventStack.length) {
431 var parent = eventStack.peekLast();
432 parent.selfTime -= duration;
434 event.selfTime = duration;
435 eventStack.push(event);
438 if (this._currentScriptEvent && event.startTime > this._currentScriptEvent.endTime)
439 this._currentScriptEvent = null;
441 switch (event.name) {
442 case recordTypes.CallStack:
443 var lastMainThreadEvent = this.mainThreadEvents().peekLast();
444 if (lastMainThreadEvent && event.args["stack"] && event.args["stack"].length)
445 lastMainThreadEvent.stackTrace = event.args["stack"];
448 case recordTypes.ResourceSendRequest:
449 this._sendRequestEvents[event.args["data"]["requestId"]] = event;
450 event.imageURL = event.args["data"]["url"];
453 case recordTypes.ResourceReceiveResponse:
454 case recordTypes.ResourceReceivedData:
455 case recordTypes.ResourceFinish:
456 event.initiator = this._sendRequestEvents[event.args["data"]["requestId"]];
458 event.imageURL = event.initiator.imageURL;
461 case recordTypes.TimerInstall:
462 this._timerEvents[event.args["data"]["timerId"]] = event;
465 case recordTypes.TimerFire:
466 event.initiator = this._timerEvents[event.args["data"]["timerId"]];
469 case recordTypes.RequestAnimationFrame:
470 this._requestAnimationFrameEvents[event.args["data"]["id"]] = event;
473 case recordTypes.FireAnimationFrame:
474 event.initiator = this._requestAnimationFrameEvents[event.args["data"]["id"]];
477 case recordTypes.ScheduleStyleRecalculation:
478 this._lastScheduleStyleRecalculation[event.args["frame"]] = event;
481 case recordTypes.RecalculateStyles:
482 event.initiator = this._lastScheduleStyleRecalculation[event.args["frame"]];
483 this._lastRecalculateStylesEvent = event;
486 case recordTypes.InvalidateLayout:
487 // Consider style recalculation as a reason for layout invalidation,
488 // but only if we had no earlier layout invalidation records.
489 var layoutInitator = event;
490 var frameId = event.args["frame"];
491 if (!this._layoutInvalidate[frameId] && this._lastRecalculateStylesEvent && this._lastRecalculateStylesEvent.endTime > event.startTime)
492 layoutInitator = this._lastRecalculateStylesEvent.initiator;
493 this._layoutInvalidate[frameId] = layoutInitator;
496 case recordTypes.Layout:
497 var frameId = event.args["beginData"]["frame"];
498 event.initiator = this._layoutInvalidate[frameId];
499 event.backendNodeId = event.args["endData"]["rootNode"];
500 event.highlightQuad = event.args["endData"]["root"];
501 this._layoutInvalidate[frameId] = null;
502 if (this._currentScriptEvent)
503 event.warning = WebInspector.UIString("Forced synchronous layout is a possible performance bottleneck.");
506 case recordTypes.WebSocketCreate:
507 this._webSocketCreateEvents[event.args["data"]["identifier"]] = event;
510 case recordTypes.WebSocketSendHandshakeRequest:
511 case recordTypes.WebSocketReceiveHandshakeResponse:
512 case recordTypes.WebSocketDestroy:
513 event.initiator = this._webSocketCreateEvents[event.args["data"]["identifier"]];
516 case recordTypes.EvaluateScript:
517 case recordTypes.FunctionCall:
518 if (!this._currentScriptEvent)
519 this._currentScriptEvent = event;
522 case recordTypes.SetLayerTreeId:
523 this._inspectedTargetLayerTreeId = event.args["layerTreeId"];
526 case recordTypes.Paint:
527 event.highlightQuad = event.args["data"]["clip"];
528 event.backendNodeId = event.args["data"]["nodeId"];
529 var layerUpdateEvent = this._findAncestorEvent(recordTypes.UpdateLayer);
530 if (!layerUpdateEvent || layerUpdateEvent.args["layerTreeId"] !== this._inspectedTargetLayerTreeId)
532 // Only keep layer paint events, skip paints for subframes that get painted to the same layer as parent.
533 if (!event.args["data"]["layerId"])
535 this._lastPaintForLayer[layerUpdateEvent.args["layerId"]] = event;
538 case recordTypes.PictureSnapshot:
539 var layerUpdateEvent = this._findAncestorEvent(recordTypes.UpdateLayer);
540 if (!layerUpdateEvent || layerUpdateEvent.args["layerTreeId"] !== this._inspectedTargetLayerTreeId)
542 var paintEvent = this._lastPaintForLayer[layerUpdateEvent.args["layerId"]];
543 if (!paintEvent || !event.args["snapshot"] || !event.args["snapshot"]["params"])
545 paintEvent.picture = event.args["snapshot"]["skp64"];
546 paintEvent.layerRect = event.args["snapshot"]["params"]["layer_rect"];
549 case recordTypes.ScrollLayer:
550 event.backendNodeId = event.args["data"]["nodeId"];
553 case recordTypes.PaintImage:
554 event.backendNodeId = event.args["data"]["nodeId"];
555 event.imageURL = event.args["data"]["url"];
558 case recordTypes.DecodeImage:
559 case recordTypes.ResizeImage:
560 var paintImageEvent = this._findAncestorEvent(recordTypes.PaintImage);
561 if (!paintImageEvent) {
562 var decodeLazyPixelRefEvent = this._findAncestorEvent(recordTypes.DecodeLazyPixelRef);
563 paintImageEvent = decodeLazyPixelRefEvent && this._paintImageEventByPixelRefId[decodeLazyPixelRefEvent.args["LazyPixelRef"]];
565 if (!paintImageEvent)
567 event.backendNodeId = paintImageEvent.backendNodeId;
568 event.imageURL = paintImageEvent.imageURL;
571 case recordTypes.DrawLazyPixelRef:
572 var paintImageEvent = this._findAncestorEvent(recordTypes.PaintImage);
573 if (!paintImageEvent)
575 this._paintImageEventByPixelRefId[event.args["LazyPixelRef"]] = paintImageEvent;
576 event.backendNodeId = paintImageEvent.backendNodeId;
577 event.imageURL = paintImageEvent.imageURL;
583 * @param {string} name
584 * @return {?WebInspector.TracingModel.Event}
586 _findAncestorEvent: function(name)
588 for (var i = this._eventStack.length - 1; i >= 0; --i) {
589 var event = this._eventStack[i];
590 if (event.name === name)
596 __proto__: WebInspector.TimelineModel.prototype
602 WebInspector.TracingTimelineModel.Filter = function() { }
604 WebInspector.TracingTimelineModel.Filter.prototype = {
606 * @param {!WebInspector.TracingModel.Event} event
609 accept: function(event) { }
614 * @implements {WebInspector.TracingTimelineModel.Filter}
615 * @param {!Array.<string>} eventNames
617 WebInspector.TracingTimelineModel.EventNameFilter = function(eventNames)
619 this._eventNames = eventNames.keySet();
622 WebInspector.TracingTimelineModel.EventNameFilter.prototype = {
624 * @param {!WebInspector.TracingModel.Event} event
627 accept: function(event)
629 throw new Error("Not implemented.");
635 * @extends {WebInspector.TracingTimelineModel.EventNameFilter}
636 * @param {!Array.<string>} includeNames
638 WebInspector.TracingTimelineModel.InclusiveEventNameFilter = function(includeNames)
640 WebInspector.TracingTimelineModel.EventNameFilter.call(this, includeNames)
643 WebInspector.TracingTimelineModel.InclusiveEventNameFilter.prototype = {
646 * @param {!WebInspector.TracingModel.Event} event
649 accept: function(event)
651 return !!this._eventNames[event.name];
653 __proto__: WebInspector.TracingTimelineModel.EventNameFilter.prototype
658 * @extends {WebInspector.TracingTimelineModel.EventNameFilter}
659 * @param {!Array.<string>} excludeNames
661 WebInspector.TracingTimelineModel.ExclusiveEventNameFilter = function(excludeNames)
663 WebInspector.TracingTimelineModel.EventNameFilter.call(this, excludeNames)
666 WebInspector.TracingTimelineModel.ExclusiveEventNameFilter.prototype = {
669 * @param {!WebInspector.TracingModel.Event} event
672 accept: function(event)
674 return !this._eventNames[event.name];
676 __proto__: WebInspector.TracingTimelineModel.EventNameFilter.prototype
681 * @implements {WebInspector.TimelineModel.Record}
682 * @param {!WebInspector.TimelineModel} model
683 * @param {!WebInspector.TracingModel.Event} traceEvent
685 WebInspector.TracingTimelineModel.TraceEventRecord = function(model, traceEvent)
688 this._event = traceEvent;
689 traceEvent._timelineRecord = this;
693 WebInspector.TracingTimelineModel.TraceEventRecord.prototype = {
695 * @return {?Array.<!ConsoleAgent.CallFrame>}
697 callSiteStackTrace: function()
699 var initiator = this._event.initiator;
700 return initiator ? initiator.stackTrace : null;
704 * @return {?WebInspector.TimelineModel.Record}
706 initiator: function()
708 var initiator = this._event.initiator;
709 return initiator ? initiator._timelineRecord : null;
713 * @return {?WebInspector.Target}
717 return this._event.thread.target();
725 return this._event.selfTime;
729 * @return {!Array.<!WebInspector.TimelineModel.Record>}
733 return this._children;
739 startTime: function()
741 return this._event.startTime;
749 // FIXME: Should return the actual thread name.
750 return WebInspector.TimelineModel.MainThreadName;
758 return this._event.endTime || this._event.startTime;
762 * @param {number} endTime
764 setEndTime: function(endTime)
766 throw new Error("Unsupported operation setEndTime");
774 return this._event.args["data"];
782 return this._event.name;
790 switch (this._event.name) {
791 case WebInspector.TracingTimelineModel.RecordType.ScheduleStyleRecalculation:
792 case WebInspector.TracingTimelineModel.RecordType.RecalculateStyles:
793 case WebInspector.TracingTimelineModel.RecordType.InvalidateLayout:
794 return this._event.args["frameId"];
795 case WebInspector.TracingTimelineModel.RecordType.Layout:
796 return this._event.args["beginData"]["frameId"];
798 var data = this._event.args["data"];
799 return (data && data["frame"]) || "";
804 * @return {?Array.<!ConsoleAgent.CallFrame>}
806 stackTrace: function()
808 return this._event.stackTrace;
812 * @param {string} key
815 getUserObject: function(key)
817 if (key === "TimelineUIUtils::preview-element")
818 return this._event.previewElement;
819 throw new Error("Unexpected key: " + key);
823 * @param {string} key
824 * @param {?Object|undefined} value
826 setUserObject: function(key, value)
828 if (key !== "TimelineUIUtils::preview-element")
829 throw new Error("Unexpected key: " + key);
830 this._event.previewElement = /** @type {?Element} */ (value);
834 * @return {?Array.<string>}
838 if (this._event.warning)
839 return [this._event.warning];
844 * @return {!WebInspector.TracingModel.Event}
846 traceEvent: function()
852 * @param {!WebInspector.TracingTimelineModel.TraceEventRecord} child
854 _addChild: function(child)
856 this._children.push(child);
861 * @return {!WebInspector.TimelineModel}
863 timelineModel: function()
873 * @implements {WebInspector.OutputStream}
874 * @param {!WebInspector.TracingTimelineModel} model
875 * @param {!{cancel: function()}} reader
876 * @param {!WebInspector.Progress} progress
878 WebInspector.TracingModelLoader = function(model, reader, progress)
881 this._reader = reader;
882 this._progress = progress;
884 this._firstChunk = true;
885 this._loader = new WebInspector.TracingModel.Loader(model._tracingModel);
888 WebInspector.TracingModelLoader.prototype = {
890 * @param {string} chunk
892 write: function(chunk)
894 var data = this._buffer + chunk;
899 lastIndex = WebInspector.TextUtils.findBalancedCurlyBrackets(data, index);
900 } while (lastIndex !== -1)
902 var json = data.slice(0, index) + "]";
903 this._buffer = data.slice(index);
908 if (this._firstChunk) {
911 var commaIndex = json.indexOf(",");
912 if (commaIndex !== -1)
913 json = json.slice(commaIndex + 1);
919 items = /** @type {!Array.<!WebInspector.TracingModel.EventPayload>} */ (JSON.parse(json));
921 this._reportErrorAndCancelLoading("Malformed timeline data: " + e);
925 if (this._firstChunk) {
926 this._firstChunk = false;
927 if (this._looksLikeAppVersion(items[0])) {
928 this._reportErrorAndCancelLoading("Old Timeline format is not supported.");
934 this._loader.loadNextChunk(items);
936 this._reportErrorAndCancelLoading("Malformed timeline data: " + e);
941 _reportErrorAndCancelLoading: function(messsage)
943 WebInspector.console.error(messsage);
945 this._reader.cancel();
946 this._progress.done();
949 _looksLikeAppVersion: function(item)
951 return typeof item === "string" && item.indexOf("Chrome") !== -1;
956 this._loader.finish();
962 * @param {!WebInspector.OutputStream} stream
964 WebInspector.TracingTimelineSaver = function(stream)
966 this._stream = stream;
969 WebInspector.TracingTimelineSaver.prototype = {
971 * @param {!Array.<*>} payloads
973 save: function(payloads)
975 this._payloads = payloads;
976 this._recordIndex = 0;
977 this._writeNextChunk(this._stream);
980 _writeNextChunk: function(stream)
982 const separator = ",\n";
986 if (this._recordIndex === 0)
988 while (this._recordIndex < this._payloads.length) {
989 var item = JSON.stringify(this._payloads[this._recordIndex]);
990 var itemLength = item.length + separator.length;
991 if (length + itemLength > WebInspector.TimelineModelImpl.TransferChunkLengthBytes)
993 length += itemLength;
994 if (this._recordIndex > 0)
995 data.push(separator);
999 if (this._recordIndex === this._payloads.length)
1001 stream.write(data.join(""), this._didWriteNextChunk.bind(this, stream));
1004 _didWriteNextChunk: function(stream)
1006 if (this._recordIndex === this._payloads.length)
1009 this._writeNextChunk(stream);