OLD | NEW |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 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 | 5 * @unrestricted |
7 * @extends {WebInspector.HBox} | |
8 */ | 6 */ |
9 WebInspector.ElementsBreadcrumbs = function() | 7 WebInspector.ElementsBreadcrumbs = class extends WebInspector.HBox { |
10 { | 8 constructor() { |
11 WebInspector.HBox.call(this, true); | 9 super(true); |
12 this.registerRequiredCSS("elements/breadcrumbs.css"); | 10 this.registerRequiredCSS('elements/breadcrumbs.css'); |
13 | 11 |
14 this.crumbsElement = this.contentElement.createChild("div", "crumbs"); | 12 this.crumbsElement = this.contentElement.createChild('div', 'crumbs'); |
15 this.crumbsElement.addEventListener("mousemove", this._mouseMovedInCrumbs.bi
nd(this), false); | 13 this.crumbsElement.addEventListener('mousemove', this._mouseMovedInCrumbs.bi
nd(this), false); |
16 this.crumbsElement.addEventListener("mouseleave", this._mouseMovedOutOfCrumb
s.bind(this), false); | 14 this.crumbsElement.addEventListener('mouseleave', this._mouseMovedOutOfCrumb
s.bind(this), false); |
17 this._nodeSymbol = Symbol("node"); | 15 this._nodeSymbol = Symbol('node'); |
| 16 } |
| 17 |
| 18 /** |
| 19 * @override |
| 20 */ |
| 21 wasShown() { |
| 22 this.update(); |
| 23 } |
| 24 |
| 25 /** |
| 26 * @param {!Array.<!WebInspector.DOMNode>} nodes |
| 27 */ |
| 28 updateNodes(nodes) { |
| 29 if (!nodes.length) |
| 30 return; |
| 31 |
| 32 var crumbs = this.crumbsElement; |
| 33 for (var crumb = crumbs.firstChild; crumb; crumb = crumb.nextSibling) { |
| 34 if (nodes.indexOf(crumb[this._nodeSymbol]) !== -1) { |
| 35 this.update(true); |
| 36 return; |
| 37 } |
| 38 } |
| 39 } |
| 40 |
| 41 /** |
| 42 * @param {?WebInspector.DOMNode} node |
| 43 */ |
| 44 setSelectedNode(node) { |
| 45 this._currentDOMNode = node; |
| 46 this.update(); |
| 47 } |
| 48 |
| 49 _mouseMovedInCrumbs(event) { |
| 50 var nodeUnderMouse = event.target; |
| 51 var crumbElement = nodeUnderMouse.enclosingNodeOrSelfWithClass('crumb'); |
| 52 var node = /** @type {?WebInspector.DOMNode} */ (crumbElement ? crumbElement
[this._nodeSymbol] : null); |
| 53 if (node) |
| 54 node.highlight(); |
| 55 } |
| 56 |
| 57 _mouseMovedOutOfCrumbs(event) { |
| 58 if (this._currentDOMNode) |
| 59 WebInspector.DOMModel.hideDOMNodeHighlight(); |
| 60 } |
| 61 |
| 62 /** |
| 63 * @param {boolean=} force |
| 64 */ |
| 65 update(force) { |
| 66 if (!this.isShowing()) |
| 67 return; |
| 68 |
| 69 var currentDOMNode = this._currentDOMNode; |
| 70 var crumbs = this.crumbsElement; |
| 71 |
| 72 var handled = false; |
| 73 var crumb = crumbs.firstChild; |
| 74 while (crumb) { |
| 75 if (crumb[this._nodeSymbol] === currentDOMNode) { |
| 76 crumb.classList.add('selected'); |
| 77 handled = true; |
| 78 } else { |
| 79 crumb.classList.remove('selected'); |
| 80 } |
| 81 |
| 82 crumb = crumb.nextSibling; |
| 83 } |
| 84 |
| 85 if (handled && !force) { |
| 86 // We don't need to rebuild the crumbs, but we need to adjust sizes |
| 87 // to reflect the new focused or root node. |
| 88 this.updateSizes(); |
| 89 return; |
| 90 } |
| 91 |
| 92 crumbs.removeChildren(); |
| 93 |
| 94 var panel = this; |
| 95 |
| 96 /** |
| 97 * @param {!Event} event |
| 98 * @this {WebInspector.ElementsBreadcrumbs} |
| 99 */ |
| 100 function selectCrumb(event) { |
| 101 event.preventDefault(); |
| 102 var crumb = /** @type {!Element} */ (event.currentTarget); |
| 103 if (!crumb.classList.contains('collapsed')) { |
| 104 this.dispatchEventToListeners(WebInspector.ElementsBreadcrumbs.Events.No
deSelected, crumb[this._nodeSymbol]); |
| 105 return; |
| 106 } |
| 107 |
| 108 // Clicking a collapsed crumb will expose the hidden crumbs. |
| 109 if (crumb === panel.crumbsElement.firstChild) { |
| 110 // If the focused crumb is the first child, pick the farthest crumb |
| 111 // that is still hidden. This allows the user to expose every crumb. |
| 112 var currentCrumb = crumb; |
| 113 while (currentCrumb) { |
| 114 var hidden = currentCrumb.classList.contains('hidden'); |
| 115 var collapsed = currentCrumb.classList.contains('collapsed'); |
| 116 if (!hidden && !collapsed) |
| 117 break; |
| 118 crumb = currentCrumb; |
| 119 currentCrumb = currentCrumb.nextSiblingElement; |
| 120 } |
| 121 } |
| 122 |
| 123 this.updateSizes(crumb); |
| 124 } |
| 125 |
| 126 var boundSelectCrumb = selectCrumb.bind(this); |
| 127 for (var current = currentDOMNode; current; current = current.parentNode) { |
| 128 if (current.nodeType() === Node.DOCUMENT_NODE) |
| 129 continue; |
| 130 |
| 131 crumb = createElementWithClass('span', 'crumb'); |
| 132 crumb[this._nodeSymbol] = current; |
| 133 crumb.addEventListener('mousedown', boundSelectCrumb, false); |
| 134 |
| 135 var crumbTitle = ''; |
| 136 switch (current.nodeType()) { |
| 137 case Node.ELEMENT_NODE: |
| 138 if (current.pseudoType()) |
| 139 crumbTitle = '::' + current.pseudoType(); |
| 140 else |
| 141 WebInspector.DOMPresentationUtils.decorateNodeLabel(current, crumb); |
| 142 break; |
| 143 |
| 144 case Node.TEXT_NODE: |
| 145 crumbTitle = WebInspector.UIString('(text)'); |
| 146 break; |
| 147 |
| 148 case Node.COMMENT_NODE: |
| 149 crumbTitle = '<!-->'; |
| 150 break; |
| 151 |
| 152 case Node.DOCUMENT_TYPE_NODE: |
| 153 crumbTitle = '<!DOCTYPE>'; |
| 154 break; |
| 155 |
| 156 case Node.DOCUMENT_FRAGMENT_NODE: |
| 157 crumbTitle = current.shadowRootType() ? '#shadow-root' : current.nodeN
ameInCorrectCase(); |
| 158 break; |
| 159 |
| 160 default: |
| 161 crumbTitle = current.nodeNameInCorrectCase(); |
| 162 } |
| 163 |
| 164 if (!crumb.childNodes.length) { |
| 165 var nameElement = createElement('span'); |
| 166 nameElement.textContent = crumbTitle; |
| 167 crumb.appendChild(nameElement); |
| 168 crumb.title = crumbTitle; |
| 169 } |
| 170 |
| 171 if (current === currentDOMNode) |
| 172 crumb.classList.add('selected'); |
| 173 crumbs.insertBefore(crumb, crumbs.firstChild); |
| 174 } |
| 175 |
| 176 this.updateSizes(); |
| 177 } |
| 178 |
| 179 /** |
| 180 * @param {!Element=} focusedCrumb |
| 181 */ |
| 182 updateSizes(focusedCrumb) { |
| 183 if (!this.isShowing()) |
| 184 return; |
| 185 |
| 186 var crumbs = this.crumbsElement; |
| 187 if (!crumbs.firstChild) |
| 188 return; |
| 189 |
| 190 var selectedIndex = 0; |
| 191 var focusedIndex = 0; |
| 192 var selectedCrumb; |
| 193 |
| 194 // Reset crumb styles. |
| 195 for (var i = 0; i < crumbs.childNodes.length; ++i) { |
| 196 var crumb = crumbs.children[i]; |
| 197 // Find the selected crumb and index. |
| 198 if (!selectedCrumb && crumb.classList.contains('selected')) { |
| 199 selectedCrumb = crumb; |
| 200 selectedIndex = i; |
| 201 } |
| 202 |
| 203 // Find the focused crumb index. |
| 204 if (crumb === focusedCrumb) |
| 205 focusedIndex = i; |
| 206 |
| 207 crumb.classList.remove('compact', 'collapsed', 'hidden'); |
| 208 } |
| 209 |
| 210 // Layout 1: Measure total and normal crumb sizes |
| 211 var contentElementWidth = this.contentElement.offsetWidth; |
| 212 var normalSizes = []; |
| 213 for (var i = 0; i < crumbs.childNodes.length; ++i) { |
| 214 var crumb = crumbs.childNodes[i]; |
| 215 normalSizes[i] = crumb.offsetWidth; |
| 216 } |
| 217 |
| 218 // Layout 2: Measure collapsed crumb sizes |
| 219 var compactSizes = []; |
| 220 for (var i = 0; i < crumbs.childNodes.length; ++i) { |
| 221 var crumb = crumbs.childNodes[i]; |
| 222 crumb.classList.add('compact'); |
| 223 } |
| 224 for (var i = 0; i < crumbs.childNodes.length; ++i) { |
| 225 var crumb = crumbs.childNodes[i]; |
| 226 compactSizes[i] = crumb.offsetWidth; |
| 227 } |
| 228 |
| 229 // Layout 3: Measure collapsed crumb size |
| 230 crumbs.firstChild.classList.add('collapsed'); |
| 231 var collapsedSize = crumbs.firstChild.offsetWidth; |
| 232 |
| 233 // Clean up. |
| 234 for (var i = 0; i < crumbs.childNodes.length; ++i) { |
| 235 var crumb = crumbs.childNodes[i]; |
| 236 crumb.classList.remove('compact', 'collapsed'); |
| 237 } |
| 238 |
| 239 function crumbsAreSmallerThanContainer() { |
| 240 var totalSize = 0; |
| 241 for (var i = 0; i < crumbs.childNodes.length; ++i) { |
| 242 var crumb = crumbs.childNodes[i]; |
| 243 if (crumb.classList.contains('hidden')) |
| 244 continue; |
| 245 if (crumb.classList.contains('collapsed')) { |
| 246 totalSize += collapsedSize; |
| 247 continue; |
| 248 } |
| 249 totalSize += crumb.classList.contains('compact') ? compactSizes[i] : nor
malSizes[i]; |
| 250 } |
| 251 const rightPadding = 10; |
| 252 return totalSize + rightPadding < contentElementWidth; |
| 253 } |
| 254 |
| 255 if (crumbsAreSmallerThanContainer()) |
| 256 return; // No need to compact the crumbs, they all fit at full size. |
| 257 |
| 258 var BothSides = 0; |
| 259 var AncestorSide = -1; |
| 260 var ChildSide = 1; |
| 261 |
| 262 /** |
| 263 * @param {function(!Element)} shrinkingFunction |
| 264 * @param {number} direction |
| 265 */ |
| 266 function makeCrumbsSmaller(shrinkingFunction, direction) { |
| 267 var significantCrumb = focusedCrumb || selectedCrumb; |
| 268 var significantIndex = significantCrumb === selectedCrumb ? selectedIndex
: focusedIndex; |
| 269 |
| 270 function shrinkCrumbAtIndex(index) { |
| 271 var shrinkCrumb = crumbs.children[index]; |
| 272 if (shrinkCrumb && shrinkCrumb !== significantCrumb) |
| 273 shrinkingFunction(shrinkCrumb); |
| 274 if (crumbsAreSmallerThanContainer()) |
| 275 return true; // No need to compact the crumbs more. |
| 276 return false; |
| 277 } |
| 278 |
| 279 // Shrink crumbs one at a time by applying the shrinkingFunction until the
crumbs |
| 280 // fit in the container or we run out of crumbs to shrink. |
| 281 if (direction) { |
| 282 // Crumbs are shrunk on only one side (based on direction) of the signif
cant crumb. |
| 283 var index = (direction > 0 ? 0 : crumbs.childNodes.length - 1); |
| 284 while (index !== significantIndex) { |
| 285 if (shrinkCrumbAtIndex(index)) |
| 286 return true; |
| 287 index += (direction > 0 ? 1 : -1); |
| 288 } |
| 289 } else { |
| 290 // Crumbs are shrunk in order of descending distance from the signifcant
crumb, |
| 291 // with a tie going to child crumbs. |
| 292 var startIndex = 0; |
| 293 var endIndex = crumbs.childNodes.length - 1; |
| 294 while (startIndex !== significantIndex || endIndex !== significantIndex)
{ |
| 295 var startDistance = significantIndex - startIndex; |
| 296 var endDistance = endIndex - significantIndex; |
| 297 if (startDistance >= endDistance) |
| 298 var index = startIndex++; |
| 299 else |
| 300 var index = endIndex--; |
| 301 if (shrinkCrumbAtIndex(index)) |
| 302 return true; |
| 303 } |
| 304 } |
| 305 |
| 306 // We are not small enough yet, return false so the caller knows. |
| 307 return false; |
| 308 } |
| 309 |
| 310 function coalesceCollapsedCrumbs() { |
| 311 var crumb = crumbs.firstChild; |
| 312 var collapsedRun = false; |
| 313 var newStartNeeded = false; |
| 314 var newEndNeeded = false; |
| 315 while (crumb) { |
| 316 var hidden = crumb.classList.contains('hidden'); |
| 317 if (!hidden) { |
| 318 var collapsed = crumb.classList.contains('collapsed'); |
| 319 if (collapsedRun && collapsed) { |
| 320 crumb.classList.add('hidden'); |
| 321 crumb.classList.remove('compact'); |
| 322 crumb.classList.remove('collapsed'); |
| 323 |
| 324 if (crumb.classList.contains('start')) { |
| 325 crumb.classList.remove('start'); |
| 326 newStartNeeded = true; |
| 327 } |
| 328 |
| 329 if (crumb.classList.contains('end')) { |
| 330 crumb.classList.remove('end'); |
| 331 newEndNeeded = true; |
| 332 } |
| 333 |
| 334 continue; |
| 335 } |
| 336 |
| 337 collapsedRun = collapsed; |
| 338 |
| 339 if (newEndNeeded) { |
| 340 newEndNeeded = false; |
| 341 crumb.classList.add('end'); |
| 342 } |
| 343 } else { |
| 344 collapsedRun = true; |
| 345 } |
| 346 crumb = crumb.nextSibling; |
| 347 } |
| 348 |
| 349 if (newStartNeeded) { |
| 350 crumb = crumbs.lastChild; |
| 351 while (crumb) { |
| 352 if (!crumb.classList.contains('hidden')) { |
| 353 crumb.classList.add('start'); |
| 354 break; |
| 355 } |
| 356 crumb = crumb.previousSibling; |
| 357 } |
| 358 } |
| 359 } |
| 360 |
| 361 /** |
| 362 * @param {!Element} crumb |
| 363 */ |
| 364 function compact(crumb) { |
| 365 if (crumb.classList.contains('hidden')) |
| 366 return; |
| 367 crumb.classList.add('compact'); |
| 368 } |
| 369 |
| 370 /** |
| 371 * @param {!Element} crumb |
| 372 * @param {boolean=} dontCoalesce |
| 373 */ |
| 374 function collapse(crumb, dontCoalesce) { |
| 375 if (crumb.classList.contains('hidden')) |
| 376 return; |
| 377 crumb.classList.add('collapsed'); |
| 378 crumb.classList.remove('compact'); |
| 379 if (!dontCoalesce) |
| 380 coalesceCollapsedCrumbs(); |
| 381 } |
| 382 |
| 383 if (!focusedCrumb) { |
| 384 // When not focused on a crumb we can be biased and collapse less importan
t |
| 385 // crumbs that the user might not care much about. |
| 386 |
| 387 // Compact child crumbs. |
| 388 if (makeCrumbsSmaller(compact, ChildSide)) |
| 389 return; |
| 390 |
| 391 // Collapse child crumbs. |
| 392 if (makeCrumbsSmaller(collapse, ChildSide)) |
| 393 return; |
| 394 } |
| 395 |
| 396 // Compact ancestor crumbs, or from both sides if focused. |
| 397 if (makeCrumbsSmaller(compact, focusedCrumb ? BothSides : AncestorSide)) |
| 398 return; |
| 399 |
| 400 // Collapse ancestor crumbs, or from both sides if focused. |
| 401 if (makeCrumbsSmaller(collapse, focusedCrumb ? BothSides : AncestorSide)) |
| 402 return; |
| 403 |
| 404 if (!selectedCrumb) |
| 405 return; |
| 406 |
| 407 // Compact the selected crumb. |
| 408 compact(selectedCrumb); |
| 409 if (crumbsAreSmallerThanContainer()) |
| 410 return; |
| 411 |
| 412 // Collapse the selected crumb as a last resort. Pass true to prevent coales
cing. |
| 413 collapse(selectedCrumb, true); |
| 414 } |
18 }; | 415 }; |
19 | 416 |
20 /** @enum {symbol} */ | 417 /** @enum {symbol} */ |
21 WebInspector.ElementsBreadcrumbs.Events = { | 418 WebInspector.ElementsBreadcrumbs.Events = { |
22 NodeSelected: Symbol("NodeSelected") | 419 NodeSelected: Symbol('NodeSelected') |
23 }; | 420 }; |
24 | |
25 WebInspector.ElementsBreadcrumbs.prototype = { | |
26 wasShown: function() | |
27 { | |
28 this.update(); | |
29 }, | |
30 | |
31 /** | |
32 * @param {!Array.<!WebInspector.DOMNode>} nodes | |
33 */ | |
34 updateNodes: function(nodes) | |
35 { | |
36 if (!nodes.length) | |
37 return; | |
38 | |
39 var crumbs = this.crumbsElement; | |
40 for (var crumb = crumbs.firstChild; crumb; crumb = crumb.nextSibling) { | |
41 if (nodes.indexOf(crumb[this._nodeSymbol]) !== -1) { | |
42 this.update(true); | |
43 return; | |
44 } | |
45 } | |
46 }, | |
47 | |
48 /** | |
49 * @param {?WebInspector.DOMNode} node | |
50 */ | |
51 setSelectedNode: function(node) | |
52 { | |
53 this._currentDOMNode = node; | |
54 this.update(); | |
55 }, | |
56 | |
57 _mouseMovedInCrumbs: function(event) | |
58 { | |
59 var nodeUnderMouse = event.target; | |
60 var crumbElement = nodeUnderMouse.enclosingNodeOrSelfWithClass("crumb"); | |
61 var node = /** @type {?WebInspector.DOMNode} */ (crumbElement ? crumbEle
ment[this._nodeSymbol] : null); | |
62 if (node) | |
63 node.highlight(); | |
64 }, | |
65 | |
66 _mouseMovedOutOfCrumbs: function(event) | |
67 { | |
68 if (this._currentDOMNode) | |
69 WebInspector.DOMModel.hideDOMNodeHighlight(); | |
70 }, | |
71 | |
72 /** | |
73 * @param {boolean=} force | |
74 */ | |
75 update: function(force) | |
76 { | |
77 if (!this.isShowing()) | |
78 return; | |
79 | |
80 var currentDOMNode = this._currentDOMNode; | |
81 var crumbs = this.crumbsElement; | |
82 | |
83 var handled = false; | |
84 var crumb = crumbs.firstChild; | |
85 while (crumb) { | |
86 if (crumb[this._nodeSymbol] === currentDOMNode) { | |
87 crumb.classList.add("selected"); | |
88 handled = true; | |
89 } else { | |
90 crumb.classList.remove("selected"); | |
91 } | |
92 | |
93 crumb = crumb.nextSibling; | |
94 } | |
95 | |
96 if (handled && !force) { | |
97 // We don't need to rebuild the crumbs, but we need to adjust sizes | |
98 // to reflect the new focused or root node. | |
99 this.updateSizes(); | |
100 return; | |
101 } | |
102 | |
103 crumbs.removeChildren(); | |
104 | |
105 var panel = this; | |
106 | |
107 /** | |
108 * @param {!Event} event | |
109 * @this {WebInspector.ElementsBreadcrumbs} | |
110 */ | |
111 function selectCrumb(event) | |
112 { | |
113 event.preventDefault(); | |
114 var crumb = /** @type {!Element} */ (event.currentTarget); | |
115 if (!crumb.classList.contains("collapsed")) { | |
116 this.dispatchEventToListeners(WebInspector.ElementsBreadcrumbs.E
vents.NodeSelected, crumb[this._nodeSymbol]); | |
117 return; | |
118 } | |
119 | |
120 // Clicking a collapsed crumb will expose the hidden crumbs. | |
121 if (crumb === panel.crumbsElement.firstChild) { | |
122 // If the focused crumb is the first child, pick the farthest cr
umb | |
123 // that is still hidden. This allows the user to expose every cr
umb. | |
124 var currentCrumb = crumb; | |
125 while (currentCrumb) { | |
126 var hidden = currentCrumb.classList.contains("hidden"); | |
127 var collapsed = currentCrumb.classList.contains("collapsed")
; | |
128 if (!hidden && !collapsed) | |
129 break; | |
130 crumb = currentCrumb; | |
131 currentCrumb = currentCrumb.nextSiblingElement; | |
132 } | |
133 } | |
134 | |
135 this.updateSizes(crumb); | |
136 } | |
137 | |
138 var boundSelectCrumb = selectCrumb.bind(this); | |
139 for (var current = currentDOMNode; current; current = current.parentNode
) { | |
140 if (current.nodeType() === Node.DOCUMENT_NODE) | |
141 continue; | |
142 | |
143 crumb = createElementWithClass("span", "crumb"); | |
144 crumb[this._nodeSymbol] = current; | |
145 crumb.addEventListener("mousedown", boundSelectCrumb, false); | |
146 | |
147 var crumbTitle = ""; | |
148 switch (current.nodeType()) { | |
149 case Node.ELEMENT_NODE: | |
150 if (current.pseudoType()) | |
151 crumbTitle = "::" + current.pseudoType(); | |
152 else | |
153 WebInspector.DOMPresentationUtils.decorateNodeLabel(current,
crumb); | |
154 break; | |
155 | |
156 case Node.TEXT_NODE: | |
157 crumbTitle = WebInspector.UIString("(text)"); | |
158 break; | |
159 | |
160 case Node.COMMENT_NODE: | |
161 crumbTitle = "<!-->"; | |
162 break; | |
163 | |
164 case Node.DOCUMENT_TYPE_NODE: | |
165 crumbTitle = "<!DOCTYPE>"; | |
166 break; | |
167 | |
168 case Node.DOCUMENT_FRAGMENT_NODE: | |
169 crumbTitle = current.shadowRootType() ? "#shadow-root" : current
.nodeNameInCorrectCase(); | |
170 break; | |
171 | |
172 default: | |
173 crumbTitle = current.nodeNameInCorrectCase(); | |
174 } | |
175 | |
176 if (!crumb.childNodes.length) { | |
177 var nameElement = createElement("span"); | |
178 nameElement.textContent = crumbTitle; | |
179 crumb.appendChild(nameElement); | |
180 crumb.title = crumbTitle; | |
181 } | |
182 | |
183 if (current === currentDOMNode) | |
184 crumb.classList.add("selected"); | |
185 crumbs.insertBefore(crumb, crumbs.firstChild); | |
186 } | |
187 | |
188 this.updateSizes(); | |
189 }, | |
190 | |
191 /** | |
192 * @param {!Element=} focusedCrumb | |
193 */ | |
194 updateSizes: function(focusedCrumb) | |
195 { | |
196 if (!this.isShowing()) | |
197 return; | |
198 | |
199 var crumbs = this.crumbsElement; | |
200 if (!crumbs.firstChild) | |
201 return; | |
202 | |
203 var selectedIndex = 0; | |
204 var focusedIndex = 0; | |
205 var selectedCrumb; | |
206 | |
207 // Reset crumb styles. | |
208 for (var i = 0; i < crumbs.childNodes.length; ++i) { | |
209 var crumb = crumbs.children[i]; | |
210 // Find the selected crumb and index. | |
211 if (!selectedCrumb && crumb.classList.contains("selected")) { | |
212 selectedCrumb = crumb; | |
213 selectedIndex = i; | |
214 } | |
215 | |
216 // Find the focused crumb index. | |
217 if (crumb === focusedCrumb) | |
218 focusedIndex = i; | |
219 | |
220 crumb.classList.remove("compact", "collapsed", "hidden"); | |
221 } | |
222 | |
223 // Layout 1: Measure total and normal crumb sizes | |
224 var contentElementWidth = this.contentElement.offsetWidth; | |
225 var normalSizes = []; | |
226 for (var i = 0; i < crumbs.childNodes.length; ++i) { | |
227 var crumb = crumbs.childNodes[i]; | |
228 normalSizes[i] = crumb.offsetWidth; | |
229 } | |
230 | |
231 // Layout 2: Measure collapsed crumb sizes | |
232 var compactSizes = []; | |
233 for (var i = 0; i < crumbs.childNodes.length; ++i) { | |
234 var crumb = crumbs.childNodes[i]; | |
235 crumb.classList.add("compact"); | |
236 } | |
237 for (var i = 0; i < crumbs.childNodes.length; ++i) { | |
238 var crumb = crumbs.childNodes[i]; | |
239 compactSizes[i] = crumb.offsetWidth; | |
240 } | |
241 | |
242 // Layout 3: Measure collapsed crumb size | |
243 crumbs.firstChild.classList.add("collapsed"); | |
244 var collapsedSize = crumbs.firstChild.offsetWidth; | |
245 | |
246 // Clean up. | |
247 for (var i = 0; i < crumbs.childNodes.length; ++i) { | |
248 var crumb = crumbs.childNodes[i]; | |
249 crumb.classList.remove("compact", "collapsed"); | |
250 } | |
251 | |
252 function crumbsAreSmallerThanContainer() | |
253 { | |
254 var totalSize = 0; | |
255 for (var i = 0; i < crumbs.childNodes.length; ++i) { | |
256 var crumb = crumbs.childNodes[i]; | |
257 if (crumb.classList.contains("hidden")) | |
258 continue; | |
259 if (crumb.classList.contains("collapsed")) { | |
260 totalSize += collapsedSize; | |
261 continue; | |
262 } | |
263 totalSize += crumb.classList.contains("compact") ? compactSizes[
i] : normalSizes[i]; | |
264 } | |
265 const rightPadding = 10; | |
266 return totalSize + rightPadding < contentElementWidth; | |
267 } | |
268 | |
269 if (crumbsAreSmallerThanContainer()) | |
270 return; // No need to compact the crumbs, they all fit at full size. | |
271 | |
272 var BothSides = 0; | |
273 var AncestorSide = -1; | |
274 var ChildSide = 1; | |
275 | |
276 /** | |
277 * @param {function(!Element)} shrinkingFunction | |
278 * @param {number} direction | |
279 */ | |
280 function makeCrumbsSmaller(shrinkingFunction, direction) | |
281 { | |
282 var significantCrumb = focusedCrumb || selectedCrumb; | |
283 var significantIndex = significantCrumb === selectedCrumb ? selected
Index : focusedIndex; | |
284 | |
285 function shrinkCrumbAtIndex(index) | |
286 { | |
287 var shrinkCrumb = crumbs.children[index]; | |
288 if (shrinkCrumb && shrinkCrumb !== significantCrumb) | |
289 shrinkingFunction(shrinkCrumb); | |
290 if (crumbsAreSmallerThanContainer()) | |
291 return true; // No need to compact the crumbs more. | |
292 return false; | |
293 } | |
294 | |
295 // Shrink crumbs one at a time by applying the shrinkingFunction unt
il the crumbs | |
296 // fit in the container or we run out of crumbs to shrink. | |
297 if (direction) { | |
298 // Crumbs are shrunk on only one side (based on direction) of th
e signifcant crumb. | |
299 var index = (direction > 0 ? 0 : crumbs.childNodes.length - 1); | |
300 while (index !== significantIndex) { | |
301 if (shrinkCrumbAtIndex(index)) | |
302 return true; | |
303 index += (direction > 0 ? 1 : -1); | |
304 } | |
305 } else { | |
306 // Crumbs are shrunk in order of descending distance from the si
gnifcant crumb, | |
307 // with a tie going to child crumbs. | |
308 var startIndex = 0; | |
309 var endIndex = crumbs.childNodes.length - 1; | |
310 while (startIndex !== significantIndex || endIndex !== significa
ntIndex) { | |
311 var startDistance = significantIndex - startIndex; | |
312 var endDistance = endIndex - significantIndex; | |
313 if (startDistance >= endDistance) | |
314 var index = startIndex++; | |
315 else | |
316 var index = endIndex--; | |
317 if (shrinkCrumbAtIndex(index)) | |
318 return true; | |
319 } | |
320 } | |
321 | |
322 // We are not small enough yet, return false so the caller knows. | |
323 return false; | |
324 } | |
325 | |
326 function coalesceCollapsedCrumbs() | |
327 { | |
328 var crumb = crumbs.firstChild; | |
329 var collapsedRun = false; | |
330 var newStartNeeded = false; | |
331 var newEndNeeded = false; | |
332 while (crumb) { | |
333 var hidden = crumb.classList.contains("hidden"); | |
334 if (!hidden) { | |
335 var collapsed = crumb.classList.contains("collapsed"); | |
336 if (collapsedRun && collapsed) { | |
337 crumb.classList.add("hidden"); | |
338 crumb.classList.remove("compact"); | |
339 crumb.classList.remove("collapsed"); | |
340 | |
341 if (crumb.classList.contains("start")) { | |
342 crumb.classList.remove("start"); | |
343 newStartNeeded = true; | |
344 } | |
345 | |
346 if (crumb.classList.contains("end")) { | |
347 crumb.classList.remove("end"); | |
348 newEndNeeded = true; | |
349 } | |
350 | |
351 continue; | |
352 } | |
353 | |
354 collapsedRun = collapsed; | |
355 | |
356 if (newEndNeeded) { | |
357 newEndNeeded = false; | |
358 crumb.classList.add("end"); | |
359 } | |
360 } else { | |
361 collapsedRun = true; | |
362 } | |
363 crumb = crumb.nextSibling; | |
364 } | |
365 | |
366 if (newStartNeeded) { | |
367 crumb = crumbs.lastChild; | |
368 while (crumb) { | |
369 if (!crumb.classList.contains("hidden")) { | |
370 crumb.classList.add("start"); | |
371 break; | |
372 } | |
373 crumb = crumb.previousSibling; | |
374 } | |
375 } | |
376 } | |
377 | |
378 /** | |
379 * @param {!Element} crumb | |
380 */ | |
381 function compact(crumb) | |
382 { | |
383 if (crumb.classList.contains("hidden")) | |
384 return; | |
385 crumb.classList.add("compact"); | |
386 } | |
387 | |
388 /** | |
389 * @param {!Element} crumb | |
390 * @param {boolean=} dontCoalesce | |
391 */ | |
392 function collapse(crumb, dontCoalesce) | |
393 { | |
394 if (crumb.classList.contains("hidden")) | |
395 return; | |
396 crumb.classList.add("collapsed"); | |
397 crumb.classList.remove("compact"); | |
398 if (!dontCoalesce) | |
399 coalesceCollapsedCrumbs(); | |
400 } | |
401 | |
402 if (!focusedCrumb) { | |
403 // When not focused on a crumb we can be biased and collapse less im
portant | |
404 // crumbs that the user might not care much about. | |
405 | |
406 // Compact child crumbs. | |
407 if (makeCrumbsSmaller(compact, ChildSide)) | |
408 return; | |
409 | |
410 // Collapse child crumbs. | |
411 if (makeCrumbsSmaller(collapse, ChildSide)) | |
412 return; | |
413 } | |
414 | |
415 // Compact ancestor crumbs, or from both sides if focused. | |
416 if (makeCrumbsSmaller(compact, focusedCrumb ? BothSides : AncestorSide)) | |
417 return; | |
418 | |
419 // Collapse ancestor crumbs, or from both sides if focused. | |
420 if (makeCrumbsSmaller(collapse, focusedCrumb ? BothSides : AncestorSide)
) | |
421 return; | |
422 | |
423 if (!selectedCrumb) | |
424 return; | |
425 | |
426 // Compact the selected crumb. | |
427 compact(selectedCrumb); | |
428 if (crumbsAreSmallerThanContainer()) | |
429 return; | |
430 | |
431 // Collapse the selected crumb as a last resort. Pass true to prevent co
alescing. | |
432 collapse(selectedCrumb, true); | |
433 }, | |
434 | |
435 __proto__: WebInspector.HBox.prototype | |
436 }; | |
OLD | NEW |