OLD | NEW |
| (Empty) |
1 /* | |
2 * Copyright (C) 2013 Google Inc. All rights reserved. | |
3 * | |
4 * Redistribution and use in source and binary forms, with or without | |
5 * modification, are permitted provided that the following conditions are | |
6 * met: | |
7 * | |
8 * * Redistributions of source code must retain the above copyright | |
9 * notice, this list of conditions and the following disclaimer. | |
10 * * Redistributions in binary form must reproduce the above | |
11 * copyright notice, this list of conditions and the following disclaimer | |
12 * in the documentation and/or other materials provided with the | |
13 * distribution. | |
14 * * Neither the name of Google Inc. nor the names of its | |
15 * contributors may be used to endorse or promote products derived from | |
16 * this software without specific prior written permission. | |
17 * | |
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
29 */ | |
30 | |
31 /** | |
32 * @constructor | |
33 * @extends {WebInspector.VBox} | |
34 * @param {!WebInspector.CanvasTraceLogPlayerProxy} traceLogPlayer | |
35 */ | |
36 WebInspector.CanvasReplayStateView = function(traceLogPlayer) | |
37 { | |
38 WebInspector.VBox.call(this); | |
39 this.registerRequiredCSS("profiler/canvasProfiler.css"); | |
40 this.element.classList.add("canvas-replay-state-view"); | |
41 this._traceLogPlayer = traceLogPlayer; | |
42 | |
43 var controlsToolbar = new WebInspector.StatusBar(this.element); | |
44 this._prevButton = this._createControlButton(controlsToolbar, "play-backward
s-status-bar-item", WebInspector.UIString("Previous resource."), this._onResourc
eNavigationClick.bind(this, false)); | |
45 this._nextButton = this._createControlButton(controlsToolbar, "play-status-b
ar-item", WebInspector.UIString("Next resource."), this._onResourceNavigationCli
ck.bind(this, true)); | |
46 this._createControlButton(controlsToolbar, "refresh-status-bar-item", WebIns
pector.UIString("Refresh."), this._onStateRefreshClick.bind(this)); | |
47 | |
48 this._resourceSelector = new WebInspector.StatusBarComboBox(this._onReplayRe
sourceChanged.bind(this)); | |
49 this._currentOption = this._resourceSelector.createOption(WebInspector.UIStr
ing("<auto>"), WebInspector.UIString("Show state of the last replayed resource."
), ""); | |
50 controlsToolbar.appendStatusBarItem(this._resourceSelector); | |
51 | |
52 /** @type {!Object.<string, string>} */ | |
53 this._resourceIdToDescription = {}; | |
54 | |
55 /** @type {!Object.<string, !Object.<string, boolean>>} */ | |
56 this._gridNodesExpandedState = {}; | |
57 /** @type {!Object.<string, !{scrollTop: number, scrollLeft: number}>} */ | |
58 this._gridScrollPositions = {}; | |
59 | |
60 /** @type {?CanvasAgent.ResourceId} */ | |
61 this._currentResourceId = null; | |
62 /** @type {!Array.<!Element>} */ | |
63 this._prevOptionsStack = []; | |
64 /** @type {!Array.<!Element>} */ | |
65 this._nextOptionsStack = []; | |
66 | |
67 /** @type {!Array.<!WebInspector.DataGridNode>} */ | |
68 this._highlightedGridNodes = []; | |
69 | |
70 var columns = [ | |
71 {title: WebInspector.UIString("Name"), sortable: false, width: "50%", di
sclosure: true}, | |
72 {title: WebInspector.UIString("Value"), sortable: false, width: "50%"} | |
73 ]; | |
74 | |
75 this._stateGrid = new WebInspector.DataGrid(columns); | |
76 this._stateGrid.element.classList.add("fill"); | |
77 this._stateGrid.show(this.element); | |
78 | |
79 this._traceLogPlayer.addEventListener(WebInspector.CanvasTraceLogPlayerProxy
.Events.CanvasReplayStateChanged, this._onReplayResourceChanged, this); | |
80 this._traceLogPlayer.addEventListener(WebInspector.CanvasTraceLogPlayerProxy
.Events.CanvasTraceLogReceived, this._onCanvasTraceLogReceived, this); | |
81 this._traceLogPlayer.addEventListener(WebInspector.CanvasTraceLogPlayerProxy
.Events.CanvasResourceStateReceived, this._onCanvasResourceStateReceived, this); | |
82 | |
83 this._updateButtonsEnabledState(); | |
84 } | |
85 | |
86 WebInspector.CanvasReplayStateView.prototype = { | |
87 /** | |
88 * @param {string} resourceId | |
89 */ | |
90 selectResource: function(resourceId) | |
91 { | |
92 if (resourceId === this._resourceSelector.selectedOption().value) | |
93 return; | |
94 var option = this._resourceSelector.selectElement().firstChild; | |
95 for (var index = 0; option; ++index, option = option.nextSibling) { | |
96 if (resourceId === option.value) { | |
97 this._resourceSelector.setSelectedIndex(index); | |
98 this._onReplayResourceChanged(); | |
99 break; | |
100 } | |
101 } | |
102 }, | |
103 | |
104 /** | |
105 * @param {!WebInspector.StatusBar} toolbar | |
106 * @param {string} className | |
107 * @param {string} title | |
108 * @param {function(this:WebInspector.CanvasProfileView)} clickCallback | |
109 * @return {!WebInspector.StatusBarButton} | |
110 */ | |
111 _createControlButton: function(toolbar, className, title, clickCallback) | |
112 { | |
113 var button = new WebInspector.StatusBarButton(title, className); | |
114 toolbar.appendStatusBarItem(button); | |
115 | |
116 button.makeLongClickEnabled(); | |
117 button.addEventListener("click", clickCallback, this); | |
118 button.addEventListener("longClickDown", clickCallback, this); | |
119 button.addEventListener("longClickPress", clickCallback, this); | |
120 return button; | |
121 }, | |
122 | |
123 /** | |
124 * @param {boolean} forward | |
125 */ | |
126 _onResourceNavigationClick: function(forward) | |
127 { | |
128 var newOption = forward ? this._nextOptionsStack.pop() : this._prevOptio
nsStack.pop(); | |
129 if (!newOption) | |
130 return; | |
131 (forward ? this._prevOptionsStack : this._nextOptionsStack).push(this._c
urrentOption); | |
132 this._isNavigationButton = true; | |
133 this.selectResource(newOption.value); | |
134 delete this._isNavigationButton; | |
135 this._updateButtonsEnabledState(); | |
136 }, | |
137 | |
138 _onStateRefreshClick: function() | |
139 { | |
140 this._traceLogPlayer.clearResourceStates(); | |
141 }, | |
142 | |
143 _updateButtonsEnabledState: function() | |
144 { | |
145 this._prevButton.setEnabled(this._prevOptionsStack.length > 0); | |
146 this._nextButton.setEnabled(this._nextOptionsStack.length > 0); | |
147 }, | |
148 | |
149 _updateCurrentOption: function() | |
150 { | |
151 const maxStackSize = 256; | |
152 var selectedOption = this._resourceSelector.selectedOption(); | |
153 if (this._currentOption === selectedOption) | |
154 return; | |
155 if (!this._isNavigationButton) { | |
156 this._prevOptionsStack.push(this._currentOption); | |
157 this._nextOptionsStack = []; | |
158 if (this._prevOptionsStack.length > maxStackSize) | |
159 this._prevOptionsStack.shift(); | |
160 this._updateButtonsEnabledState(); | |
161 } | |
162 this._currentOption = selectedOption; | |
163 }, | |
164 | |
165 /** | |
166 * @param {!CanvasAgent.TraceLog} traceLog | |
167 */ | |
168 _collectResourcesFromTraceLog: function(traceLog) | |
169 { | |
170 /** @type {!Array.<!CanvasAgent.CallArgument>} */ | |
171 var collectedResources = []; | |
172 var calls = traceLog.calls; | |
173 for (var i = 0, n = calls.length; i < n; ++i) { | |
174 var call = calls[i]; | |
175 var args = call.arguments || []; | |
176 for (var j = 0; j < args.length; ++j) | |
177 this._collectResourceFromCallArgument(args[j], collectedResource
s); | |
178 this._collectResourceFromCallArgument(call.result, collectedResource
s); | |
179 this._collectResourceFromCallArgument(call.value, collectedResources
); | |
180 } | |
181 var contexts = traceLog.contexts; | |
182 for (var i = 0, n = contexts.length; i < n; ++i) | |
183 this._collectResourceFromCallArgument(contexts[i], collectedResource
s); | |
184 this._addCollectedResourcesToSelector(collectedResources); | |
185 }, | |
186 | |
187 /** | |
188 * @param {!CanvasAgent.ResourceState} resourceState | |
189 */ | |
190 _collectResourcesFromResourceState: function(resourceState) | |
191 { | |
192 /** @type {!Array.<!CanvasAgent.CallArgument>} */ | |
193 var collectedResources = []; | |
194 this._collectResourceFromResourceStateDescriptors(resourceState.descript
ors, collectedResources); | |
195 this._addCollectedResourcesToSelector(collectedResources); | |
196 }, | |
197 | |
198 /** | |
199 * @param {!Array.<!CanvasAgent.ResourceStateDescriptor>|undefined} descript
ors | |
200 * @param {!Array.<!CanvasAgent.CallArgument>} output | |
201 */ | |
202 _collectResourceFromResourceStateDescriptors: function(descriptors, output) | |
203 { | |
204 if (!descriptors) | |
205 return; | |
206 for (var i = 0, n = descriptors.length; i < n; ++i) { | |
207 var descriptor = descriptors[i]; | |
208 this._collectResourceFromCallArgument(descriptor.value, output); | |
209 this._collectResourceFromResourceStateDescriptors(descriptor.values,
output); | |
210 } | |
211 }, | |
212 | |
213 /** | |
214 * @param {!CanvasAgent.CallArgument|undefined} argument | |
215 * @param {!Array.<!CanvasAgent.CallArgument>} output | |
216 */ | |
217 _collectResourceFromCallArgument: function(argument, output) | |
218 { | |
219 if (!argument) | |
220 return; | |
221 var resourceId = argument.resourceId; | |
222 if (!resourceId || this._resourceIdToDescription[resourceId]) | |
223 return; | |
224 this._resourceIdToDescription[resourceId] = argument.description; | |
225 output.push(argument); | |
226 }, | |
227 | |
228 /** | |
229 * @param {!Array.<!CanvasAgent.CallArgument>} collectedResources | |
230 */ | |
231 _addCollectedResourcesToSelector: function(collectedResources) | |
232 { | |
233 if (!collectedResources.length) | |
234 return; | |
235 /** | |
236 * @param {!CanvasAgent.CallArgument} arg1 | |
237 * @param {!CanvasAgent.CallArgument} arg2 | |
238 * @return {number} | |
239 */ | |
240 function comparator(arg1, arg2) | |
241 { | |
242 var a = arg1.description; | |
243 var b = arg2.description; | |
244 return String.naturalOrderComparator(a, b); | |
245 } | |
246 collectedResources.sort(comparator); | |
247 | |
248 var selectElement = this._resourceSelector.selectElement(); | |
249 var currentOption = selectElement.firstChild; | |
250 currentOption = currentOption.nextSibling; // Skip the "<auto>" option. | |
251 for (var i = 0, n = collectedResources.length; i < n; ++i) { | |
252 var argument = collectedResources[i]; | |
253 while (currentOption && String.naturalOrderComparator(currentOption.
text, argument.description) < 0) | |
254 currentOption = currentOption.nextSibling; | |
255 var option = this._resourceSelector.createOption(argument.descriptio
n, WebInspector.UIString("Show state of this resource."), argument.resourceId); | |
256 if (currentOption) | |
257 selectElement.insertBefore(option, currentOption); | |
258 } | |
259 }, | |
260 | |
261 _onReplayResourceChanged: function() | |
262 { | |
263 this._updateCurrentOption(); | |
264 var selectedResourceId = this._resourceSelector.selectedOption().value; | |
265 | |
266 /** | |
267 * @param {?CanvasAgent.ResourceState} resourceState | |
268 * @this {WebInspector.CanvasReplayStateView} | |
269 */ | |
270 function didReceiveResourceState(resourceState) | |
271 { | |
272 if (selectedResourceId !== this._resourceSelector.selectedOption().v
alue) | |
273 return; | |
274 this._showResourceState(resourceState); | |
275 } | |
276 this._traceLogPlayer.getResourceState(selectedResourceId, didReceiveReso
urceState.bind(this)); | |
277 }, | |
278 | |
279 /** | |
280 * @param {!WebInspector.Event} event | |
281 */ | |
282 _onCanvasTraceLogReceived: function(event) | |
283 { | |
284 var traceLog = /** @type {!CanvasAgent.TraceLog} */ (event.data); | |
285 console.assert(traceLog); | |
286 this._collectResourcesFromTraceLog(traceLog); | |
287 }, | |
288 | |
289 /** | |
290 * @param {!WebInspector.Event} event | |
291 */ | |
292 _onCanvasResourceStateReceived: function(event) | |
293 { | |
294 var resourceState = /** @type {!CanvasAgent.ResourceState} */ (event.dat
a); | |
295 console.assert(resourceState); | |
296 this._collectResourcesFromResourceState(resourceState); | |
297 }, | |
298 | |
299 /** | |
300 * @param {?CanvasAgent.ResourceState} resourceState | |
301 */ | |
302 _showResourceState: function(resourceState) | |
303 { | |
304 this._saveExpandedState(); | |
305 this._saveScrollState(); | |
306 | |
307 var rootNode = this._stateGrid.rootNode(); | |
308 if (!resourceState) { | |
309 this._currentResourceId = null; | |
310 this._updateDataGridHighlights([]); | |
311 rootNode.removeChildren(); | |
312 return; | |
313 } | |
314 | |
315 var nodesToHighlight = []; | |
316 var nameToOldGridNodes = {}; | |
317 | |
318 /** | |
319 * @param {!Object} map | |
320 * @param {!WebInspector.DataGridNode=} node | |
321 */ | |
322 function populateNameToNodesMap(map, node) | |
323 { | |
324 if (!node) | |
325 return; | |
326 for (var i = 0, child; child = node.children[i]; ++i) { | |
327 var item = { | |
328 node: child, | |
329 children: {} | |
330 }; | |
331 map[child.name] = item; | |
332 populateNameToNodesMap(item.children, child); | |
333 } | |
334 } | |
335 populateNameToNodesMap(nameToOldGridNodes, rootNode); | |
336 rootNode.removeChildren(); | |
337 | |
338 /** | |
339 * @param {!CanvasAgent.ResourceStateDescriptor} d1 | |
340 * @param {!CanvasAgent.ResourceStateDescriptor} d2 | |
341 * @return {number} | |
342 */ | |
343 function comparator(d1, d2) | |
344 { | |
345 var hasChildren1 = !!d1.values; | |
346 var hasChildren2 = !!d2.values; | |
347 if (hasChildren1 !== hasChildren2) | |
348 return hasChildren1 ? 1 : -1; | |
349 return String.naturalOrderComparator(d1.name, d2.name); | |
350 } | |
351 /** | |
352 * @param {!Array.<!CanvasAgent.ResourceStateDescriptor>|undefined} desc
riptors | |
353 * @param {!WebInspector.DataGridNode} parent | |
354 * @param {!Object=} nameToOldChildren | |
355 * @this {WebInspector.CanvasReplayStateView} | |
356 */ | |
357 function appendResourceStateDescriptors(descriptors, parent, nameToOldCh
ildren) | |
358 { | |
359 descriptors = descriptors || []; | |
360 descriptors.sort(comparator); | |
361 var oldChildren = nameToOldChildren || {}; | |
362 for (var i = 0, n = descriptors.length; i < n; ++i) { | |
363 var descriptor = descriptors[i]; | |
364 var childNode = this._createDataGridNode(descriptor); | |
365 parent.appendChild(childNode); | |
366 var oldChildrenItem = oldChildren[childNode.name] || {}; | |
367 var oldChildNode = oldChildrenItem.node; | |
368 if (!oldChildNode || oldChildNode.element().textContent !== chil
dNode.element().textContent) | |
369 nodesToHighlight.push(childNode); | |
370 appendResourceStateDescriptors.call(this, descriptor.values, chi
ldNode, oldChildrenItem.children); | |
371 } | |
372 } | |
373 appendResourceStateDescriptors.call(this, resourceState.descriptors, roo
tNode, nameToOldGridNodes); | |
374 | |
375 var shouldHighlightChanges = (this._resourceKindId(this._currentResource
Id) === this._resourceKindId(resourceState.id)); | |
376 this._currentResourceId = resourceState.id; | |
377 this._restoreExpandedState(); | |
378 this._updateDataGridHighlights(shouldHighlightChanges ? nodesToHighlight
: []); | |
379 this._restoreScrollState(); | |
380 }, | |
381 | |
382 /** | |
383 * @param {!Array.<!WebInspector.DataGridNode>} nodes | |
384 */ | |
385 _updateDataGridHighlights: function(nodes) | |
386 { | |
387 for (var i = 0, n = this._highlightedGridNodes.length; i < n; ++i) | |
388 this._highlightedGridNodes[i].element().classList.remove("canvas-gri
d-node-highlighted"); | |
389 | |
390 this._highlightedGridNodes = nodes; | |
391 | |
392 for (var i = 0, n = this._highlightedGridNodes.length; i < n; ++i) { | |
393 var node = this._highlightedGridNodes[i]; | |
394 WebInspector.runCSSAnimationOnce(node.element(), "canvas-grid-node-h
ighlighted"); | |
395 node.reveal(); | |
396 } | |
397 }, | |
398 | |
399 /** | |
400 * @param {?CanvasAgent.ResourceId} resourceId | |
401 * @return {string} | |
402 */ | |
403 _resourceKindId: function(resourceId) | |
404 { | |
405 var description = (resourceId && this._resourceIdToDescription[resourceI
d]) || ""; | |
406 return description.replace(/\d+/g, ""); | |
407 }, | |
408 | |
409 /** | |
410 * @param {function(!WebInspector.DataGridNode, string):void} callback | |
411 */ | |
412 _forEachGridNode: function(callback) | |
413 { | |
414 /** | |
415 * @param {!WebInspector.DataGridNode} node | |
416 * @param {string} key | |
417 */ | |
418 function processRecursively(node, key) | |
419 { | |
420 for (var i = 0, child; child = node.children[i]; ++i) { | |
421 var childKey = key + "#" + child.name; | |
422 callback(child, childKey); | |
423 processRecursively(child, childKey); | |
424 } | |
425 } | |
426 processRecursively(this._stateGrid.rootNode(), ""); | |
427 }, | |
428 | |
429 _saveExpandedState: function() | |
430 { | |
431 if (!this._currentResourceId) | |
432 return; | |
433 var expandedState = {}; | |
434 var key = this._resourceKindId(this._currentResourceId); | |
435 this._gridNodesExpandedState[key] = expandedState; | |
436 /** | |
437 * @param {!WebInspector.DataGridNode} node | |
438 * @param {string} key | |
439 */ | |
440 function callback(node, key) | |
441 { | |
442 if (node.expanded) | |
443 expandedState[key] = true; | |
444 } | |
445 this._forEachGridNode(callback); | |
446 }, | |
447 | |
448 _restoreExpandedState: function() | |
449 { | |
450 if (!this._currentResourceId) | |
451 return; | |
452 var key = this._resourceKindId(this._currentResourceId); | |
453 var expandedState = this._gridNodesExpandedState[key]; | |
454 if (!expandedState) | |
455 return; | |
456 /** | |
457 * @param {!WebInspector.DataGridNode} node | |
458 * @param {string} key | |
459 */ | |
460 function callback(node, key) | |
461 { | |
462 if (expandedState[key]) | |
463 node.expand(); | |
464 } | |
465 this._forEachGridNode(callback); | |
466 }, | |
467 | |
468 _saveScrollState: function() | |
469 { | |
470 if (!this._currentResourceId) | |
471 return; | |
472 var key = this._resourceKindId(this._currentResourceId); | |
473 this._gridScrollPositions[key] = { | |
474 scrollTop: this._stateGrid.scrollContainer.scrollTop, | |
475 scrollLeft: this._stateGrid.scrollContainer.scrollLeft | |
476 }; | |
477 }, | |
478 | |
479 _restoreScrollState: function() | |
480 { | |
481 if (!this._currentResourceId) | |
482 return; | |
483 var key = this._resourceKindId(this._currentResourceId); | |
484 var scrollState = this._gridScrollPositions[key]; | |
485 if (!scrollState) | |
486 return; | |
487 this._stateGrid.scrollContainer.scrollTop = scrollState.scrollTop; | |
488 this._stateGrid.scrollContainer.scrollLeft = scrollState.scrollLeft; | |
489 }, | |
490 | |
491 /** | |
492 * @param {!CanvasAgent.ResourceStateDescriptor} descriptor | |
493 * @return {!WebInspector.DataGridNode} | |
494 */ | |
495 _createDataGridNode: function(descriptor) | |
496 { | |
497 var name = descriptor.name; | |
498 var callArgument = descriptor.value; | |
499 var target = this._traceLogPlayer.target(); | |
500 | |
501 /** @type {!Element|string} */ | |
502 var valueElement = callArgument ? WebInspector.CanvasProfileDataGridHelp
er.createCallArgumentElement(target, callArgument) : ""; | |
503 | |
504 /** @type {!Element|string} */ | |
505 var nameElement = name; | |
506 if (target && typeof descriptor.enumValueForName !== "undefined") | |
507 nameElement = WebInspector.CanvasProfileDataGridHelper.createEnumVal
ueElement(target, name, +descriptor.enumValueForName); | |
508 | |
509 if (descriptor.isArray && descriptor.values) { | |
510 if (typeof nameElement === "string") | |
511 nameElement += "[" + descriptor.values.length + "]"; | |
512 else { | |
513 var element = createElement("span"); | |
514 element.appendChild(nameElement); | |
515 element.createTextChild("[" + descriptor.values.length + "]"); | |
516 nameElement = element; | |
517 } | |
518 } | |
519 | |
520 var data = {}; | |
521 data[0] = nameElement; | |
522 data[1] = valueElement; | |
523 var node = new WebInspector.DataGridNode(data); | |
524 node.selectable = false; | |
525 node.name = name; | |
526 return node; | |
527 }, | |
528 | |
529 __proto__: WebInspector.VBox.prototype | |
530 } | |
OLD | NEW |