blob: ba21e38bd530f24e0d603d9a28199dc9b4781beb [file] [log] [blame]
// Copyright 2016 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.
/**
* @implements {Common.OutputStream}
* @implements {Bindings.OutputStreamDelegate}
* @unrestricted
*/
Timeline.TimelineLoader = class {
/**
* @param {!SDK.TracingModel} model
* @param {!Timeline.TimelineLifecycleDelegate} delegate
*/
constructor(model, delegate) {
this._model = model;
this._delegate = delegate;
/** @type {?function()} */
this._canceledCallback = null;
this._state = Timeline.TimelineLoader.State.Initial;
this._buffer = '';
this._firstChunk = true;
this._loadedBytes = 0;
/** @type {number} */
this._totalSize;
this._jsonTokenizer = new Common.TextUtils.BalancedJSONTokenizer(this._writeBalancedJSON.bind(this), true);
}
/**
* @param {!SDK.TracingModel} model
* @param {!File} file
* @param {!Timeline.TimelineLifecycleDelegate} delegate
* @return {!Timeline.TimelineLoader}
*/
static loadFromFile(model, file, delegate) {
var loader = new Timeline.TimelineLoader(model, delegate);
var fileReader = Timeline.TimelineLoader._createFileReader(file, loader);
loader._canceledCallback = fileReader.cancel.bind(fileReader);
loader._totalSize = file.size;
fileReader.start(loader);
return loader;
}
/**
* @param {!SDK.TracingModel} model
* @param {string} url
* @param {!Timeline.TimelineLifecycleDelegate} delegate
* @return {!Timeline.TimelineLoader}
*/
static loadFromURL(model, url, delegate) {
var stream = new Timeline.TimelineLoader(model, delegate);
Host.ResourceLoader.loadAsStream(url, null, stream);
return stream;
}
/**
* @param {!File} file
* @param {!Bindings.OutputStreamDelegate} delegate
* @return {!Bindings.ChunkedReader}
*/
static _createFileReader(file, delegate) {
return new Bindings.ChunkedFileReader(file, Timeline.TimelineLoader.TransferChunkLengthBytes, delegate);
}
cancel() {
this._model.reset();
this._delegate.loadingComplete(false);
this._delegate = null;
if (this._canceledCallback)
this._canceledCallback();
}
/**
* @override
* @param {string} chunk
*/
write(chunk) {
if (!this._delegate)
return;
this._loadedBytes += chunk.length;
if (!this._firstChunk)
this._delegate.loadingProgress(this._totalSize ? this._loadedBytes / this._totalSize : undefined);
if (this._state === Timeline.TimelineLoader.State.Initial) {
if (chunk[0] === '{')
this._state = Timeline.TimelineLoader.State.LookingForEvents;
else if (chunk[0] === '[')
this._state = Timeline.TimelineLoader.State.ReadingEvents;
else {
this._reportErrorAndCancelLoading(Common.UIString('Malformed timeline data: Unknown JSON format'));
return;
}
}
if (this._state === Timeline.TimelineLoader.State.LookingForEvents) {
var objectName = '"traceEvents":';
var startPos = this._buffer.length - objectName.length;
this._buffer += chunk;
var pos = this._buffer.indexOf(objectName, startPos);
if (pos === -1)
return;
chunk = this._buffer.slice(pos + objectName.length);
this._state = Timeline.TimelineLoader.State.ReadingEvents;
}
if (this._state !== Timeline.TimelineLoader.State.ReadingEvents)
return;
if (this._jsonTokenizer.write(chunk))
return;
this._state = Timeline.TimelineLoader.State.SkippingTail;
if (this._firstChunk) {
this._reportErrorAndCancelLoading(Common.UIString('Malformed timeline input, wrong JSON brackets balance'));
return;
}
}
/**
* @param {string} data
*/
_writeBalancedJSON(data) {
var json = data + ']';
if (this._firstChunk) {
this._delegate.loadingStarted();
} else {
var commaIndex = json.indexOf(',');
if (commaIndex !== -1)
json = json.slice(commaIndex + 1);
json = '[' + json;
}
var items;
try {
items = /** @type {!Array.<!SDK.TracingManager.EventPayload>} */ (JSON.parse(json));
} catch (e) {
this._reportErrorAndCancelLoading(Common.UIString('Malformed timeline data: %s', e.toString()));
return;
}
if (this._firstChunk) {
this._firstChunk = false;
this._model.reset();
if (this._looksLikeAppVersion(items[0])) {
this._reportErrorAndCancelLoading(Common.UIString('Legacy Timeline format is not supported.'));
return;
}
}
try {
this._model.addEvents(items);
} catch (e) {
this._reportErrorAndCancelLoading(Common.UIString('Malformed timeline data: %s', e.toString()));
return;
}
}
/**
* @param {string=} message
*/
_reportErrorAndCancelLoading(message) {
if (message)
Common.console.error(message);
this.cancel();
}
/**
* @param {*} item
* @return {boolean}
*/
_looksLikeAppVersion(item) {
return typeof item === 'string' && item.indexOf('Chrome') !== -1;
}
/**
* @override
*/
close() {
this._model.tracingComplete();
if (this._delegate)
this._delegate.loadingComplete(true);
}
/**
* @override
*/
onTransferStarted() {
}
/**
* @override
* @param {!Bindings.ChunkedReader} reader
*/
onChunkTransferred(reader) {
}
/**
* @override
*/
onTransferFinished() {
}
/**
* @override
* @param {!Bindings.ChunkedReader} reader
* @param {!Event} event
*/
onError(reader, event) {
switch (event.target.error.name) {
case 'NotFoundError':
this._reportErrorAndCancelLoading(Common.UIString('File "%s" not found.', reader.fileName()));
break;
case 'NotReadableError':
this._reportErrorAndCancelLoading(Common.UIString('File "%s" is not readable', reader.fileName()));
break;
case 'AbortError':
break;
default:
this._reportErrorAndCancelLoading(
Common.UIString('An error occurred while reading the file "%s"', reader.fileName()));
}
}
};
Timeline.TimelineLoader.TransferChunkLengthBytes = 5000000;
/**
* @enum {symbol}
*/
Timeline.TimelineLoader.State = {
Initial: Symbol('Initial'),
LookingForEvents: Symbol('LookingForEvents'),
ReadingEvents: Symbol('ReadingEvents'),
SkippingTail: Symbol('SkippingTail')
};
/**
* @implements {Bindings.OutputStreamDelegate}
* @unrestricted
*/
Timeline.TracingTimelineSaver = class {
/**
* @override
*/
onTransferStarted() {
}
/**
* @override
*/
onTransferFinished() {
}
/**
* @override
* @param {!Bindings.ChunkedReader} reader
*/
onChunkTransferred(reader) {
}
/**
* @override
* @param {!Bindings.ChunkedReader} reader
* @param {!Event} event
*/
onError(reader, event) {
var error = event.target.error;
Common.console.error(
Common.UIString('Failed to save timeline: %s (%s, %s)', error.message, error.name, error.code));
}
};