Home | History | Annotate | Download | only in front_end
      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.View}
     34  * @param {!WebInspector.CanvasTraceLogPlayerProxy} traceLogPlayer
     35  */
     36 WebInspector.CanvasReplayStateView = function(traceLogPlayer)
     37 {
     38     WebInspector.View.call(this);
     39     this.registerRequiredCSS("canvasProfiler.css");
     40     this.element.classList.add("canvas-replay-state-view");
     41     this._traceLogPlayer = traceLogPlayer;
     42 
     43     var controlsContainer = this.element.createChild("div", "status-bar");
     44     this._prevButton = this._createControlButton(controlsContainer, "canvas-replay-state-prev", WebInspector.UIString("Previous resource."), this._onResourceNavigationClick.bind(this, false));
     45     this._nextButton = this._createControlButton(controlsContainer, "canvas-replay-state-next", WebInspector.UIString("Next resource."), this._onResourceNavigationClick.bind(this, true));
     46     this._createControlButton(controlsContainer, "canvas-replay-state-refresh", WebInspector.UIString("Refresh."), this._onStateRefreshClick.bind(this));
     47 
     48     this._resourceSelector = new WebInspector.StatusBarComboBox(this._onReplayResourceChanged.bind(this));
     49     this._currentOption = this._resourceSelector.createOption(WebInspector.UIString("<auto>"), WebInspector.UIString("Show state of the last replayed resource."), "");
     50     controlsContainer.appendChild(this._resourceSelector.element);
     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%", disclosure: 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 {!Element} parent
    106      * @param {string} className
    107      * @param {string} title
    108      * @param {function(this:WebInspector.CanvasProfileView)} clickCallback
    109      * @return {!WebInspector.StatusBarButton}
    110      */
    111     _createControlButton: function(parent, className, title, clickCallback)
    112     {
    113         var button = new WebInspector.StatusBarButton(title, className + " canvas-replay-button");
    114         parent.appendChild(button.element);
    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._prevOptionsStack.pop();
    129         if (!newOption)
    130             return;
    131         (forward ? this._prevOptionsStack : this._nextOptionsStack).push(this._currentOption);
    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], collectedResources);
    178             this._collectResourceFromCallArgument(call.result, collectedResources);
    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], collectedResources);
    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.descriptors, collectedResources);
    195         this._addCollectedResourcesToSelector(collectedResources);
    196     },
    197 
    198     /**
    199      * @param {!Array.<!CanvasAgent.ResourceStateDescriptor>|undefined} descriptors
    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.description, 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().value)
    273                 return;
    274             this._showResourceState(resourceState);
    275         }
    276         this._traceLogPlayer.getResourceState(selectedResourceId, didReceiveResourceState.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.data);
    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} descriptors
    353          * @param {!WebInspector.DataGridNode} parent
    354          * @param {!Object=} nameToOldChildren
    355          * @this {WebInspector.CanvasReplayStateView}
    356          */
    357         function appendResourceStateDescriptors(descriptors, parent, nameToOldChildren)
    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 !== childNode.element.textContent)
    369                     nodesToHighlight.push(childNode);
    370                 appendResourceStateDescriptors.call(this, descriptor.values, childNode, oldChildrenItem.children);
    371             }
    372         }
    373         appendResourceStateDescriptors.call(this, resourceState.descriptors, rootNode, nameToOldGridNodes);
    374 
    375         var shouldHighlightChanges = (this._resourceKindId(this._currentResourceId) === 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             var node = this._highlightedGridNodes[i];
    389             node.element.classList.remove("canvas-grid-node-highlighted");
    390         }
    391 
    392         this._highlightedGridNodes = nodes;
    393 
    394         for (var i = 0, n = this._highlightedGridNodes.length; i < n; ++i) {
    395             var node = this._highlightedGridNodes[i];
    396             node.element.classList.add("canvas-grid-node-highlighted");
    397             node.reveal();
    398         }
    399     },
    400 
    401     /**
    402      * @param {?CanvasAgent.ResourceId} resourceId
    403      * @return {string}
    404      */
    405     _resourceKindId: function(resourceId)
    406     {
    407         var description = (resourceId && this._resourceIdToDescription[resourceId]) || "";
    408         return description.replace(/\d+/g, "");
    409     },
    410 
    411     /**
    412      * @param {function(!WebInspector.DataGridNode, string):void} callback
    413      */
    414     _forEachGridNode: function(callback)
    415     {
    416         /**
    417          * @param {!WebInspector.DataGridNode} node
    418          * @param {string} key
    419          */
    420         function processRecursively(node, key)
    421         {
    422             for (var i = 0, child; child = node.children[i]; ++i) {
    423                 var childKey = key + "#" + child.name;
    424                 callback(child, childKey);
    425                 processRecursively(child, childKey);
    426             }
    427         }
    428         processRecursively(this._stateGrid.rootNode(), "");
    429     },
    430 
    431     _saveExpandedState: function()
    432     {
    433         if (!this._currentResourceId)
    434             return;
    435         var expandedState = {};
    436         var key = this._resourceKindId(this._currentResourceId);
    437         this._gridNodesExpandedState[key] = expandedState;
    438         /**
    439          * @param {!WebInspector.DataGridNode} node
    440          * @param {string} key
    441          */
    442         function callback(node, key)
    443         {
    444             if (node.expanded)
    445                 expandedState[key] = true;
    446         }
    447         this._forEachGridNode(callback);
    448     },
    449 
    450     _restoreExpandedState: function()
    451     {
    452         if (!this._currentResourceId)
    453             return;
    454         var key = this._resourceKindId(this._currentResourceId);
    455         var expandedState = this._gridNodesExpandedState[key];
    456         if (!expandedState)
    457             return;
    458         /**
    459          * @param {!WebInspector.DataGridNode} node
    460          * @param {string} key
    461          */
    462         function callback(node, key)
    463         {
    464             if (expandedState[key])
    465                 node.expand();
    466         }
    467         this._forEachGridNode(callback);
    468     },
    469 
    470     _saveScrollState: function()
    471     {
    472         if (!this._currentResourceId)
    473             return;
    474         var key = this._resourceKindId(this._currentResourceId);
    475         this._gridScrollPositions[key] = {
    476             scrollTop: this._stateGrid.scrollContainer.scrollTop,
    477             scrollLeft: this._stateGrid.scrollContainer.scrollLeft
    478         };
    479     },
    480 
    481     _restoreScrollState: function()
    482     {
    483         if (!this._currentResourceId)
    484             return;
    485         var key = this._resourceKindId(this._currentResourceId);
    486         var scrollState = this._gridScrollPositions[key];
    487         if (!scrollState)
    488             return;
    489         this._stateGrid.scrollContainer.scrollTop = scrollState.scrollTop;
    490         this._stateGrid.scrollContainer.scrollLeft = scrollState.scrollLeft;
    491     },
    492 
    493     /**
    494      * @param {!CanvasAgent.ResourceStateDescriptor} descriptor
    495      * @return {!WebInspector.DataGridNode}
    496      */
    497     _createDataGridNode: function(descriptor)
    498     {
    499         var name = descriptor.name;
    500         var callArgument = descriptor.value;
    501 
    502         /** @type {!Element|string} */
    503         var valueElement = callArgument ? WebInspector.CanvasProfileDataGridHelper.createCallArgumentElement(callArgument) : "";
    504 
    505         /** @type {!Element|string} */
    506         var nameElement = name;
    507         if (typeof descriptor.enumValueForName !== "undefined")
    508             nameElement = WebInspector.CanvasProfileDataGridHelper.createEnumValueElement(name, +descriptor.enumValueForName);
    509 
    510         if (descriptor.isArray && descriptor.values) {
    511             if (typeof nameElement === "string")
    512                 nameElement += "[" + descriptor.values.length + "]";
    513             else {
    514                 var element = document.createElement("span");
    515                 element.appendChild(nameElement);
    516                 element.createTextChild("[" + descriptor.values.length + "]");
    517                 nameElement = element;
    518             }
    519         }
    520 
    521         var data = {};
    522         data[0] = nameElement;
    523         data[1] = valueElement;
    524         var node = new WebInspector.DataGridNode(data);
    525         node.selectable = false;
    526         node.name = name;
    527         return node;
    528     },
    529 
    530     __proto__: WebInspector.View.prototype
    531 }
    532