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 |