| OLD | NEW |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | |
| 5 /** | 4 /** |
| 6 * @constructor | |
| 7 * @implements {WebInspector.OutputStream} | 5 * @implements {WebInspector.OutputStream} |
| 8 * @implements {WebInspector.OutputStreamDelegate} | 6 * @implements {WebInspector.OutputStreamDelegate} |
| 9 * @param {!WebInspector.TracingModel} model | 7 * @unrestricted |
| 10 * @param {!WebInspector.TimelineLifecycleDelegate} delegate | |
| 11 */ | 8 */ |
| 12 WebInspector.TimelineLoader = function(model, delegate) | 9 WebInspector.TimelineLoader = class { |
| 13 { | 10 /** |
| 11 * @param {!WebInspector.TracingModel} model |
| 12 * @param {!WebInspector.TimelineLifecycleDelegate} delegate |
| 13 */ |
| 14 constructor(model, delegate) { |
| 14 this._model = model; | 15 this._model = model; |
| 15 this._delegate = delegate; | 16 this._delegate = delegate; |
| 16 | 17 |
| 17 /** @type {?function()} */ | 18 /** @type {?function()} */ |
| 18 this._canceledCallback = null; | 19 this._canceledCallback = null; |
| 19 | 20 |
| 20 this._state = WebInspector.TimelineLoader.State.Initial; | 21 this._state = WebInspector.TimelineLoader.State.Initial; |
| 21 this._buffer = ""; | 22 this._buffer = ''; |
| 22 this._firstChunk = true; | 23 this._firstChunk = true; |
| 23 | 24 |
| 24 this._loadedBytes = 0; | 25 this._loadedBytes = 0; |
| 25 /** @type {number} */ | 26 /** @type {number} */ |
| 26 this._totalSize; | 27 this._totalSize; |
| 27 this._jsonTokenizer = new WebInspector.TextUtils.BalancedJSONTokenizer(this.
_writeBalancedJSON.bind(this), true); | 28 this._jsonTokenizer = new WebInspector.TextUtils.BalancedJSONTokenizer(this.
_writeBalancedJSON.bind(this), true); |
| 28 }; | 29 } |
| 29 | 30 |
| 30 /** | 31 /** |
| 31 * @param {!WebInspector.TracingModel} model | 32 * @param {!WebInspector.TracingModel} model |
| 32 * @param {!File} file | 33 * @param {!File} file |
| 33 * @param {!WebInspector.TimelineLifecycleDelegate} delegate | 34 * @param {!WebInspector.TimelineLifecycleDelegate} delegate |
| 34 * @return {!WebInspector.TimelineLoader} | 35 * @return {!WebInspector.TimelineLoader} |
| 35 */ | 36 */ |
| 36 WebInspector.TimelineLoader.loadFromFile = function(model, file, delegate) | 37 static loadFromFile(model, file, delegate) { |
| 37 { | |
| 38 var loader = new WebInspector.TimelineLoader(model, delegate); | 38 var loader = new WebInspector.TimelineLoader(model, delegate); |
| 39 var fileReader = WebInspector.TimelineLoader._createFileReader(file, loader)
; | 39 var fileReader = WebInspector.TimelineLoader._createFileReader(file, loader)
; |
| 40 loader._canceledCallback = fileReader.cancel.bind(fileReader); | 40 loader._canceledCallback = fileReader.cancel.bind(fileReader); |
| 41 loader._totalSize = file.size; | 41 loader._totalSize = file.size; |
| 42 fileReader.start(loader); | 42 fileReader.start(loader); |
| 43 return loader; | 43 return loader; |
| 44 }; | 44 } |
| 45 | 45 |
| 46 /** | 46 /** |
| 47 * @param {!WebInspector.TracingModel} model | 47 * @param {!WebInspector.TracingModel} model |
| 48 * @param {string} url | 48 * @param {string} url |
| 49 * @param {!WebInspector.TimelineLifecycleDelegate} delegate | 49 * @param {!WebInspector.TimelineLifecycleDelegate} delegate |
| 50 * @return {!WebInspector.TimelineLoader} | 50 * @return {!WebInspector.TimelineLoader} |
| 51 */ | 51 */ |
| 52 WebInspector.TimelineLoader.loadFromURL = function(model, url, delegate) | 52 static loadFromURL(model, url, delegate) { |
| 53 { | |
| 54 var stream = new WebInspector.TimelineLoader(model, delegate); | 53 var stream = new WebInspector.TimelineLoader(model, delegate); |
| 55 WebInspector.ResourceLoader.loadAsStream(url, null, stream); | 54 WebInspector.ResourceLoader.loadAsStream(url, null, stream); |
| 56 return stream; | 55 return stream; |
| 56 } |
| 57 |
| 58 /** |
| 59 * @param {!File} file |
| 60 * @param {!WebInspector.OutputStreamDelegate} delegate |
| 61 * @return {!WebInspector.ChunkedReader} |
| 62 */ |
| 63 static _createFileReader(file, delegate) { |
| 64 return new WebInspector.ChunkedFileReader(file, WebInspector.TimelineLoader.
TransferChunkLengthBytes, delegate); |
| 65 } |
| 66 |
| 67 cancel() { |
| 68 this._model.reset(); |
| 69 this._delegate.loadingComplete(false); |
| 70 this._delegate = null; |
| 71 if (this._canceledCallback) |
| 72 this._canceledCallback(); |
| 73 } |
| 74 |
| 75 /** |
| 76 * @override |
| 77 * @param {string} chunk |
| 78 */ |
| 79 write(chunk) { |
| 80 if (!this._delegate) |
| 81 return; |
| 82 this._loadedBytes += chunk.length; |
| 83 if (!this._firstChunk) |
| 84 this._delegate.loadingProgress(this._totalSize ? this._loadedBytes / this.
_totalSize : undefined); |
| 85 |
| 86 if (this._state === WebInspector.TimelineLoader.State.Initial) { |
| 87 if (chunk[0] === '{') |
| 88 this._state = WebInspector.TimelineLoader.State.LookingForEvents; |
| 89 else if (chunk[0] === '[') |
| 90 this._state = WebInspector.TimelineLoader.State.ReadingEvents; |
| 91 else { |
| 92 this._reportErrorAndCancelLoading(WebInspector.UIString('Malformed timel
ine data: Unknown JSON format')); |
| 93 return; |
| 94 } |
| 95 } |
| 96 |
| 97 if (this._state === WebInspector.TimelineLoader.State.LookingForEvents) { |
| 98 var objectName = '"traceEvents":'; |
| 99 var startPos = this._buffer.length - objectName.length; |
| 100 this._buffer += chunk; |
| 101 var pos = this._buffer.indexOf(objectName, startPos); |
| 102 if (pos === -1) |
| 103 return; |
| 104 chunk = this._buffer.slice(pos + objectName.length); |
| 105 this._state = WebInspector.TimelineLoader.State.ReadingEvents; |
| 106 } |
| 107 |
| 108 if (this._state !== WebInspector.TimelineLoader.State.ReadingEvents) |
| 109 return; |
| 110 if (this._jsonTokenizer.write(chunk)) |
| 111 return; |
| 112 this._state = WebInspector.TimelineLoader.State.SkippingTail; |
| 113 if (this._firstChunk) { |
| 114 this._reportErrorAndCancelLoading(WebInspector.UIString('Malformed timelin
e input, wrong JSON brackets balance')); |
| 115 return; |
| 116 } |
| 117 } |
| 118 |
| 119 /** |
| 120 * @param {string} data |
| 121 */ |
| 122 _writeBalancedJSON(data) { |
| 123 var json = data + ']'; |
| 124 |
| 125 if (this._firstChunk) { |
| 126 this._delegate.loadingStarted(); |
| 127 } else { |
| 128 var commaIndex = json.indexOf(','); |
| 129 if (commaIndex !== -1) |
| 130 json = json.slice(commaIndex + 1); |
| 131 json = '[' + json; |
| 132 } |
| 133 |
| 134 var items; |
| 135 try { |
| 136 items = /** @type {!Array.<!WebInspector.TracingManager.EventPayload>} */
(JSON.parse(json)); |
| 137 } catch (e) { |
| 138 this._reportErrorAndCancelLoading(WebInspector.UIString('Malformed timelin
e data: %s', e.toString())); |
| 139 return; |
| 140 } |
| 141 |
| 142 if (this._firstChunk) { |
| 143 this._firstChunk = false; |
| 144 this._model.reset(); |
| 145 if (this._looksLikeAppVersion(items[0])) { |
| 146 this._reportErrorAndCancelLoading(WebInspector.UIString('Legacy Timeline
format is not supported.')); |
| 147 return; |
| 148 } |
| 149 } |
| 150 |
| 151 try { |
| 152 this._model.addEvents(items); |
| 153 } catch (e) { |
| 154 this._reportErrorAndCancelLoading(WebInspector.UIString('Malformed timelin
e data: %s', e.toString())); |
| 155 return; |
| 156 } |
| 157 } |
| 158 |
| 159 /** |
| 160 * @param {string=} message |
| 161 */ |
| 162 _reportErrorAndCancelLoading(message) { |
| 163 if (message) |
| 164 WebInspector.console.error(message); |
| 165 this.cancel(); |
| 166 } |
| 167 |
| 168 /** |
| 169 * @param {*} item |
| 170 * @return {boolean} |
| 171 */ |
| 172 _looksLikeAppVersion(item) { |
| 173 return typeof item === 'string' && item.indexOf('Chrome') !== -1; |
| 174 } |
| 175 |
| 176 /** |
| 177 * @override |
| 178 */ |
| 179 close() { |
| 180 this._model.tracingComplete(); |
| 181 if (this._delegate) |
| 182 this._delegate.loadingComplete(true); |
| 183 } |
| 184 |
| 185 /** |
| 186 * @override |
| 187 */ |
| 188 onTransferStarted() { |
| 189 } |
| 190 |
| 191 /** |
| 192 * @override |
| 193 * @param {!WebInspector.ChunkedReader} reader |
| 194 */ |
| 195 onChunkTransferred(reader) { |
| 196 } |
| 197 |
| 198 /** |
| 199 * @override |
| 200 */ |
| 201 onTransferFinished() { |
| 202 } |
| 203 |
| 204 /** |
| 205 * @override |
| 206 * @param {!WebInspector.ChunkedReader} reader |
| 207 * @param {!Event} event |
| 208 */ |
| 209 onError(reader, event) { |
| 210 switch (event.target.error.name) { |
| 211 case 'NotFoundError': |
| 212 this._reportErrorAndCancelLoading(WebInspector.UIString('File "%s" not f
ound.', reader.fileName())); |
| 213 break; |
| 214 case 'NotReadableError': |
| 215 this._reportErrorAndCancelLoading(WebInspector.UIString('File "%s" is no
t readable', reader.fileName())); |
| 216 break; |
| 217 case 'AbortError': |
| 218 break; |
| 219 default: |
| 220 this._reportErrorAndCancelLoading( |
| 221 WebInspector.UIString('An error occurred while reading the file "%s"
', reader.fileName())); |
| 222 } |
| 223 } |
| 57 }; | 224 }; |
| 58 | 225 |
| 226 |
| 59 WebInspector.TimelineLoader.TransferChunkLengthBytes = 5000000; | 227 WebInspector.TimelineLoader.TransferChunkLengthBytes = 5000000; |
| 60 | 228 |
| 61 /** | |
| 62 * @param {!File} file | |
| 63 * @param {!WebInspector.OutputStreamDelegate} delegate | |
| 64 * @return {!WebInspector.ChunkedReader} | |
| 65 */ | |
| 66 WebInspector.TimelineLoader._createFileReader = function(file, delegate) | |
| 67 { | |
| 68 return new WebInspector.ChunkedFileReader(file, WebInspector.TimelineLoader.
TransferChunkLengthBytes, delegate); | |
| 69 }; | |
| 70 | 229 |
| 71 /** | 230 /** |
| 72 * @enum {symbol} | 231 * @enum {symbol} |
| 73 */ | 232 */ |
| 74 WebInspector.TimelineLoader.State = { | 233 WebInspector.TimelineLoader.State = { |
| 75 Initial: Symbol("Initial"), | 234 Initial: Symbol('Initial'), |
| 76 LookingForEvents: Symbol("LookingForEvents"), | 235 LookingForEvents: Symbol('LookingForEvents'), |
| 77 ReadingEvents: Symbol("ReadingEvents"), | 236 ReadingEvents: Symbol('ReadingEvents'), |
| 78 SkippingTail: Symbol("SkippingTail") | 237 SkippingTail: Symbol('SkippingTail') |
| 79 }; | 238 }; |
| 80 | 239 |
| 81 WebInspector.TimelineLoader.prototype = { | 240 /** |
| 82 cancel: function() | 241 * @implements {WebInspector.OutputStreamDelegate} |
| 83 { | 242 * @unrestricted |
| 84 this._model.reset(); | 243 */ |
| 85 this._delegate.loadingComplete(false); | 244 WebInspector.TracingTimelineSaver = class { |
| 86 this._delegate = null; | 245 /** |
| 87 if (this._canceledCallback) | 246 * @override |
| 88 this._canceledCallback(); | 247 */ |
| 89 }, | 248 onTransferStarted() { |
| 90 | 249 } |
| 91 /** | 250 |
| 92 * @override | 251 /** |
| 93 * @param {string} chunk | 252 * @override |
| 94 */ | 253 */ |
| 95 write: function(chunk) | 254 onTransferFinished() { |
| 96 { | 255 } |
| 97 if (!this._delegate) | 256 |
| 98 return; | 257 /** |
| 99 this._loadedBytes += chunk.length; | 258 * @override |
| 100 if (!this._firstChunk) | 259 * @param {!WebInspector.ChunkedReader} reader |
| 101 this._delegate.loadingProgress(this._totalSize ? this._loadedBytes /
this._totalSize : undefined); | 260 */ |
| 102 | 261 onChunkTransferred(reader) { |
| 103 if (this._state === WebInspector.TimelineLoader.State.Initial) { | 262 } |
| 104 if (chunk[0] === "{") | 263 |
| 105 this._state = WebInspector.TimelineLoader.State.LookingForEvents
; | 264 /** |
| 106 else if (chunk[0] === "[") | 265 * @override |
| 107 this._state = WebInspector.TimelineLoader.State.ReadingEvents; | 266 * @param {!WebInspector.ChunkedReader} reader |
| 108 else { | 267 * @param {!Event} event |
| 109 this._reportErrorAndCancelLoading(WebInspector.UIString("Malform
ed timeline data: Unknown JSON format")); | 268 */ |
| 110 return; | 269 onError(reader, event) { |
| 111 } | 270 var error = event.target.error; |
| 112 } | 271 WebInspector.console.error( |
| 113 | 272 WebInspector.UIString('Failed to save timeline: %s (%s, %s)', error.mess
age, error.name, error.code)); |
| 114 if (this._state === WebInspector.TimelineLoader.State.LookingForEvents)
{ | 273 } |
| 115 var objectName = "\"traceEvents\":"; | |
| 116 var startPos = this._buffer.length - objectName.length; | |
| 117 this._buffer += chunk; | |
| 118 var pos = this._buffer.indexOf(objectName, startPos); | |
| 119 if (pos === -1) | |
| 120 return; | |
| 121 chunk = this._buffer.slice(pos + objectName.length); | |
| 122 this._state = WebInspector.TimelineLoader.State.ReadingEvents; | |
| 123 } | |
| 124 | |
| 125 if (this._state !== WebInspector.TimelineLoader.State.ReadingEvents) | |
| 126 return; | |
| 127 if (this._jsonTokenizer.write(chunk)) | |
| 128 return; | |
| 129 this._state = WebInspector.TimelineLoader.State.SkippingTail; | |
| 130 if (this._firstChunk) { | |
| 131 this._reportErrorAndCancelLoading(WebInspector.UIString("Malformed t
imeline input, wrong JSON brackets balance")); | |
| 132 return; | |
| 133 } | |
| 134 }, | |
| 135 | |
| 136 /** | |
| 137 * @param {string} data | |
| 138 */ | |
| 139 _writeBalancedJSON: function(data) | |
| 140 { | |
| 141 var json = data + "]"; | |
| 142 | |
| 143 if (this._firstChunk) { | |
| 144 this._delegate.loadingStarted(); | |
| 145 } else { | |
| 146 var commaIndex = json.indexOf(","); | |
| 147 if (commaIndex !== -1) | |
| 148 json = json.slice(commaIndex + 1); | |
| 149 json = "[" + json; | |
| 150 } | |
| 151 | |
| 152 var items; | |
| 153 try { | |
| 154 items = /** @type {!Array.<!WebInspector.TracingManager.EventPayload
>} */ (JSON.parse(json)); | |
| 155 } catch (e) { | |
| 156 this._reportErrorAndCancelLoading(WebInspector.UIString("Malformed t
imeline data: %s", e.toString())); | |
| 157 return; | |
| 158 } | |
| 159 | |
| 160 if (this._firstChunk) { | |
| 161 this._firstChunk = false; | |
| 162 this._model.reset(); | |
| 163 if (this._looksLikeAppVersion(items[0])) { | |
| 164 this._reportErrorAndCancelLoading(WebInspector.UIString("Legacy
Timeline format is not supported.")); | |
| 165 return; | |
| 166 } | |
| 167 } | |
| 168 | |
| 169 try { | |
| 170 this._model.addEvents(items); | |
| 171 } catch (e) { | |
| 172 this._reportErrorAndCancelLoading(WebInspector.UIString("Malformed t
imeline data: %s", e.toString())); | |
| 173 return; | |
| 174 } | |
| 175 }, | |
| 176 | |
| 177 /** | |
| 178 * @param {string=} message | |
| 179 */ | |
| 180 _reportErrorAndCancelLoading: function(message) | |
| 181 { | |
| 182 if (message) | |
| 183 WebInspector.console.error(message); | |
| 184 this.cancel(); | |
| 185 }, | |
| 186 | |
| 187 /** | |
| 188 * @param {*} item | |
| 189 * @return {boolean} | |
| 190 */ | |
| 191 _looksLikeAppVersion: function(item) | |
| 192 { | |
| 193 return typeof item === "string" && item.indexOf("Chrome") !== -1; | |
| 194 }, | |
| 195 | |
| 196 /** | |
| 197 * @override | |
| 198 */ | |
| 199 close: function() | |
| 200 { | |
| 201 this._model.tracingComplete(); | |
| 202 if (this._delegate) | |
| 203 this._delegate.loadingComplete(true); | |
| 204 }, | |
| 205 | |
| 206 /** | |
| 207 * @override | |
| 208 */ | |
| 209 onTransferStarted: function() {}, | |
| 210 | |
| 211 /** | |
| 212 * @override | |
| 213 * @param {!WebInspector.ChunkedReader} reader | |
| 214 */ | |
| 215 onChunkTransferred: function(reader) {}, | |
| 216 | |
| 217 /** | |
| 218 * @override | |
| 219 */ | |
| 220 onTransferFinished: function() {}, | |
| 221 | |
| 222 /** | |
| 223 * @override | |
| 224 * @param {!WebInspector.ChunkedReader} reader | |
| 225 * @param {!Event} event | |
| 226 */ | |
| 227 onError: function(reader, event) | |
| 228 { | |
| 229 switch (event.target.error.name) { | |
| 230 case "NotFoundError": | |
| 231 this._reportErrorAndCancelLoading(WebInspector.UIString("File \"%s\"
not found.", reader.fileName())); | |
| 232 break; | |
| 233 case "NotReadableError": | |
| 234 this._reportErrorAndCancelLoading(WebInspector.UIString("File \"%s\"
is not readable", reader.fileName())); | |
| 235 break; | |
| 236 case "AbortError": | |
| 237 break; | |
| 238 default: | |
| 239 this._reportErrorAndCancelLoading(WebInspector.UIString("An error oc
curred while reading the file \"%s\"", reader.fileName())); | |
| 240 } | |
| 241 } | |
| 242 }; | 274 }; |
| 243 | |
| 244 /** | |
| 245 * @constructor | |
| 246 * @implements {WebInspector.OutputStreamDelegate} | |
| 247 */ | |
| 248 WebInspector.TracingTimelineSaver = function() | |
| 249 { | |
| 250 }; | |
| 251 | |
| 252 WebInspector.TracingTimelineSaver.prototype = { | |
| 253 /** | |
| 254 * @override | |
| 255 */ | |
| 256 onTransferStarted: function() { }, | |
| 257 | |
| 258 /** | |
| 259 * @override | |
| 260 */ | |
| 261 onTransferFinished: function() { }, | |
| 262 | |
| 263 /** | |
| 264 * @override | |
| 265 * @param {!WebInspector.ChunkedReader} reader | |
| 266 */ | |
| 267 onChunkTransferred: function(reader) { }, | |
| 268 | |
| 269 /** | |
| 270 * @override | |
| 271 * @param {!WebInspector.ChunkedReader} reader | |
| 272 * @param {!Event} event | |
| 273 */ | |
| 274 onError: function(reader, event) | |
| 275 { | |
| 276 var error = event.target.error; | |
| 277 WebInspector.console.error(WebInspector.UIString("Failed to save timelin
e: %s (%s, %s)", error.message, error.name, error.code)); | |
| 278 } | |
| 279 }; | |
| OLD | NEW |