Web Inspector: Request/Response toggles not working
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Views / HierarchicalPathNavigationItem.js
1 /*
2  * Copyright (C) 2013, 2015 Apple 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
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 WebInspector.HierarchicalPathNavigationItem = class HierarchicalPathNavigationItem extends WebInspector.NavigationItem
27 {
28     constructor(identifier, components)
29     {
30         super(identifier);
31
32         this.components = components;
33     }
34
35     // Public
36
37     get components()
38     {
39         return this._components;
40     }
41
42     set components(newComponents)
43     {
44         if (!newComponents)
45             newComponents = [];
46
47         let componentsEqual = function(a, b) {
48             let getRepresentedObjects = (component) => component.representedObject;
49             let representedObjectsA = a.map(getRepresentedObjects);
50             let representedObjectsB = b.map(getRepresentedObjects);
51             if (!Array.shallowEqual(representedObjectsA, representedObjectsB))
52                 return false;
53
54             let getExtraComparisonData = (component) => component.comparisonData;
55             let extraComparisonDataA = a.map(getExtraComparisonData);
56             let extraComparisonDataB = b.map(getExtraComparisonData);
57             return Array.shallowEqual(extraComparisonDataA, extraComparisonDataB);
58         };
59
60         if (this._components && componentsEqual(this._components, newComponents))
61             return;
62
63         for (var i = 0; this._components && i < this._components.length; ++i)
64             this._components[i].removeEventListener(WebInspector.HierarchicalPathComponent.Event.SiblingWasSelected, this._siblingPathComponentWasSelected, this);
65
66         // Make a shallow copy of the newComponents array using slice.
67         this._components = newComponents.slice(0);
68
69         for (var i = 0; i < this._components.length; ++i)
70             this._components[i].addEventListener(WebInspector.HierarchicalPathComponent.Event.SiblingWasSelected, this._siblingPathComponentWasSelected, this);
71
72         this.element.removeChildren();
73         delete this._collapsedComponent;
74
75         for (var i = 0; i < newComponents.length; ++i)
76             this.element.appendChild(newComponents[i].element);
77
78         // Update layout for the so other items can adjust to the extra space (or lack thereof) too.
79         if (this.parentNavigationBar)
80             this.parentNavigationBar.needsLayout();
81     }
82
83     get lastComponent()
84     {
85         return this._components.lastValue || null;
86     }
87
88     get alwaysShowLastPathComponentSeparator()
89     {
90         return this.element.classList.contains(WebInspector.HierarchicalPathNavigationItem.AlwaysShowLastPathComponentSeparatorStyleClassName);
91     }
92
93     set alwaysShowLastPathComponentSeparator(flag)
94     {
95         if (flag)
96             this.element.classList.add(WebInspector.HierarchicalPathNavigationItem.AlwaysShowLastPathComponentSeparatorStyleClassName);
97         else
98             this.element.classList.remove(WebInspector.HierarchicalPathNavigationItem.AlwaysShowLastPathComponentSeparatorStyleClassName);
99     }
100
101     updateLayout(expandOnly)
102     {
103         var navigationBar = this.parentNavigationBar;
104         if (!navigationBar)
105             return;
106
107         if (this._collapsedComponent) {
108             this.element.removeChild(this._collapsedComponent.element);
109             delete this._collapsedComponent;
110         }
111
112         // Expand our components to full width to test if the items can fit at full width.
113         for (var i = 0; i < this._components.length; ++i) {
114             this._components[i].hidden = false;
115             this._components[i].forcedWidth = null;
116         }
117
118         if (expandOnly)
119             return;
120
121         if (navigationBar.sizesToFit)
122             return;
123
124         // Iterate over all the other navigation items in the bar and calculate their width.
125         var totalOtherItemsWidth = 0;
126         for (var i = 0; i < navigationBar.navigationItems.length; ++i) {
127             // Skip ourself.
128             if (navigationBar.navigationItems[i] === this)
129                 continue;
130
131             // Skip flexible space items since they can take up no space at the minimum width.
132             if (navigationBar.navigationItems[i] instanceof WebInspector.FlexibleSpaceNavigationItem)
133                 continue;
134
135             totalOtherItemsWidth += navigationBar.navigationItems[i].element.realOffsetWidth;
136         }
137
138         // Calculate the width for all the components.
139         var thisItemWidth = 0;
140         var componentWidths = [];
141         for (var i = 0; i < this._components.length; ++i) {
142             var componentWidth = this._components[i].element.realOffsetWidth;
143             componentWidths.push(componentWidth);
144             thisItemWidth += componentWidth;
145         }
146
147         // If all our components fit with the other navigation items in the width of the bar,
148         // then we don't need to collapse any components.
149         var barWidth = navigationBar.element.realOffsetWidth;
150         if (totalOtherItemsWidth + thisItemWidth <= barWidth)
151             return;
152
153         // Calculate the width we need to remove from our components, then iterate over them
154         // and force their width to be smaller.
155         var widthToRemove = totalOtherItemsWidth + thisItemWidth - barWidth;
156         for (var i = 0; i < this._components.length; ++i) {
157             var componentWidth = componentWidths[i];
158
159             // Try to take the whole width we need to remove from each component.
160             var forcedWidth = componentWidth - widthToRemove;
161             this._components[i].forcedWidth = forcedWidth;
162
163             // Since components have a minimum width, we need to see how much was actually
164             // removed and subtract that from what remans to be removed.
165             componentWidths[i] = Math.max(this._components[i].minimumWidth, forcedWidth);
166             widthToRemove -= (componentWidth - componentWidths[i]);
167
168             // If there is nothing else to remove, then we can stop.
169             if (widthToRemove <= 0)
170                 break;
171         }
172
173         // If there is nothing else to remove, then we can stop.
174         if (widthToRemove <= 0)
175             return;
176
177         // If there are 3 or fewer components, then we can stop. Collapsing the middle of 3 components
178         // does not save more than a few pixels over just the icon, so it isn't worth it unless there
179         // are 4 or more components.
180         if (this._components.length <= 3)
181             return;
182
183         // We want to collapse the middle components, so find the nearest middle index.
184         var middle = this._components.length >> 1;
185         var distance = -1;
186         var i = middle;
187
188         // Create a component that will represent the hidden components with a ellipse as the display name.
189         this._collapsedComponent = new WebInspector.HierarchicalPathComponent(ellipsis, []);
190         this._collapsedComponent.collapsed = true;
191
192         // Insert it in the middle, it doesn't matter exactly where since the elements around it will be hidden soon.
193         this.element.insertBefore(this._collapsedComponent.element, this._components[middle].element);
194
195         // Add the width of the collapsed component to the width we need to remove.
196         widthToRemove += this._collapsedComponent.minimumWidth;
197
198         var hiddenDisplayNames = [];
199
200         // Loop through the components starting at the middle and fanning out in each direction.
201         while (i >= 0 && i <= this._components.length - 1) {
202             // Only hide components in the middle and never the ends.
203             if (i > 0 && i < this._components.length - 1) {
204                 var component = this._components[i];
205                 component.hidden = true;
206
207                 // Remember the displayName so it can be put in the tool tip of the collapsed component.
208                 if (distance > 0)
209                     hiddenDisplayNames.unshift(component.displayName);
210                 else
211                     hiddenDisplayNames.push(component.displayName);
212
213                 // Fully subtract the hidden component's width.
214                 widthToRemove -= componentWidths[i];
215
216                 // If there is nothing else to remove, then we can stop.
217                 if (widthToRemove <= 0)
218                     break;
219             }
220
221             // Calculate the next index.
222             i = middle + distance;
223
224             // Increment the distance when it is in the positive direction.
225             if (distance > 0)
226                 ++distance;
227
228             // Flip the direction of the distance.
229             distance *= -1;
230         }
231
232         // Set the tool tip of the collapsed component.
233         this._collapsedComponent.element.title = hiddenDisplayNames.join("\n");
234     }
235
236     // Protected
237
238     get additionalClassNames()
239     {
240         return ["hierarchical-path"];
241     }
242
243     // Private
244
245     _siblingPathComponentWasSelected(event)
246     {
247         this.dispatchEventToListeners(WebInspector.HierarchicalPathNavigationItem.Event.PathComponentWasSelected, event.data);
248     }
249 };
250
251 WebInspector.HierarchicalPathNavigationItem.AlwaysShowLastPathComponentSeparatorStyleClassName = "always-show-last-path-component-separator";
252
253 WebInspector.HierarchicalPathNavigationItem.Event = {
254     PathComponentWasSelected: "hierarchical-path-navigation-item-path-component-was-selected"
255 };