| 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 /** | 4 /** |
| 5 * @unrestricted | 5 * @unrestricted |
| 6 */ | 6 */ |
| 7 Elements.ElementsBreadcrumbs = class extends UI.HBox { | 7 Elements.ElementsBreadcrumbs = class extends UI.HBox { |
| 8 constructor() { | 8 constructor() { |
| 9 super(true); | 9 super(true); |
| 10 this.registerRequiredCSS('elements/breadcrumbs.css'); | 10 this.registerRequiredCSS('elements/breadcrumbs.css'); |
| (...skipping 25 matching lines...) Expand all Loading... |
| 36 return; | 36 return; |
| 37 } | 37 } |
| 38 } | 38 } |
| 39 } | 39 } |
| 40 | 40 |
| 41 /** | 41 /** |
| 42 * @param {?SDK.DOMNode} node | 42 * @param {?SDK.DOMNode} node |
| 43 */ | 43 */ |
| 44 setSelectedNode(node) { | 44 setSelectedNode(node) { |
| 45 this._currentDOMNode = node; | 45 this._currentDOMNode = node; |
| 46 this.update(); | 46 this.crumbsElement.window().requestAnimationFrame(() => this.update()); |
| 47 } | 47 } |
| 48 | 48 |
| 49 _mouseMovedInCrumbs(event) { | 49 _mouseMovedInCrumbs(event) { |
| 50 var nodeUnderMouse = event.target; | 50 var nodeUnderMouse = event.target; |
| 51 var crumbElement = nodeUnderMouse.enclosingNodeOrSelfWithClass('crumb'); | 51 var crumbElement = nodeUnderMouse.enclosingNodeOrSelfWithClass('crumb'); |
| 52 var node = /** @type {?SDK.DOMNode} */ (crumbElement ? crumbElement[this._no
deSymbol] : null); | 52 var node = /** @type {?SDK.DOMNode} */ (crumbElement ? crumbElement[this._no
deSymbol] : null); |
| 53 if (node) | 53 if (node) |
| 54 node.highlight(); | 54 node.highlight(); |
| 55 } | 55 } |
| 56 | 56 |
| 57 _mouseMovedOutOfCrumbs(event) { | 57 _mouseMovedOutOfCrumbs(event) { |
| 58 if (this._currentDOMNode) | 58 if (this._currentDOMNode) |
| 59 SDK.DOMModel.hideDOMNodeHighlight(); | 59 SDK.DOMModel.hideDOMNodeHighlight(); |
| 60 } | 60 } |
| 61 | 61 |
| 62 |
| 63 /** |
| 64 * @param {!Event} event |
| 65 * @this {Elements.ElementsBreadcrumbs} |
| 66 */ |
| 67 _onClickCrumb(event) { |
| 68 event.preventDefault(); |
| 69 var crumb = /** @type {!Element} */ (event.currentTarget); |
| 70 if (!crumb.classList.contains('collapsed')) { |
| 71 this.dispatchEventToListeners(Elements.ElementsBreadcrumbs.Events.NodeSele
cted, crumb[this._nodeSymbol]); |
| 72 return; |
| 73 } |
| 74 |
| 75 // Clicking a collapsed crumb will expose the hidden crumbs. |
| 76 if (crumb === this.crumbsElement.firstChild) { |
| 77 // If the clicked crumb is the first child, pick the farthest crumb |
| 78 // that is still hidden. This allows the user to expose every crumb. |
| 79 var currentCrumb = crumb; |
| 80 while (currentCrumb) { |
| 81 var hidden = currentCrumb.classList.contains('hidden'); |
| 82 var collapsed = currentCrumb.classList.contains('collapsed'); |
| 83 if (!hidden && !collapsed) |
| 84 break; |
| 85 crumb = currentCrumb; |
| 86 currentCrumb = currentCrumb.nextSiblingElement; |
| 87 } |
| 88 } |
| 89 |
| 90 this.updateSizes(crumb); |
| 91 } |
| 92 |
| 93 /** |
| 94 * @param {!SDK.DOMNode} domNode |
| 95 * @return {?string} |
| 96 */ |
| 97 _determineElementTitle(domNode) { |
| 98 switch (domNode.nodeType()) { |
| 99 case Node.ELEMENT_NODE: |
| 100 if (domNode.pseudoType()) |
| 101 return '::' + domNode.pseudoType(); |
| 102 return null; |
| 103 case Node.TEXT_NODE: |
| 104 return Common.UIString('(text)'); |
| 105 case Node.COMMENT_NODE: |
| 106 return '<!-->'; |
| 107 case Node.DOCUMENT_TYPE_NODE: |
| 108 return '<!DOCTYPE>'; |
| 109 case Node.DOCUMENT_FRAGMENT_NODE: |
| 110 return domNode.shadowRootType() ? '#shadow-root' : domNode.nodeNameInCor
rectCase(); |
| 111 default: |
| 112 return domNode.nodeNameInCorrectCase(); |
| 113 } |
| 114 } |
| 115 |
| 62 /** | 116 /** |
| 63 * @param {boolean=} force | 117 * @param {boolean=} force |
| 64 */ | 118 */ |
| 65 update(force) { | 119 update(force) { |
| 66 if (!this.isShowing()) | 120 if (!this.isShowing()) |
| 67 return; | 121 return; |
| 68 | 122 |
| 69 var currentDOMNode = this._currentDOMNode; | 123 var currentDOMNode = this._currentDOMNode; |
| 70 var crumbs = this.crumbsElement; | 124 var crumbs = this.crumbsElement; |
| 71 | 125 |
| (...skipping 12 matching lines...) Expand all Loading... |
| 84 | 138 |
| 85 if (handled && !force) { | 139 if (handled && !force) { |
| 86 // We don't need to rebuild the crumbs, but we need to adjust sizes | 140 // We don't need to rebuild the crumbs, but we need to adjust sizes |
| 87 // to reflect the new focused or root node. | 141 // to reflect the new focused or root node. |
| 88 this.updateSizes(); | 142 this.updateSizes(); |
| 89 return; | 143 return; |
| 90 } | 144 } |
| 91 | 145 |
| 92 crumbs.removeChildren(); | 146 crumbs.removeChildren(); |
| 93 | 147 |
| 94 var panel = this; | |
| 95 | |
| 96 /** | |
| 97 * @param {!Event} event | |
| 98 * @this {Elements.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(Elements.ElementsBreadcrumbs.Events.NodeSe
lected, 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) { | 148 for (var current = currentDOMNode; current; current = current.parentNode) { |
| 128 if (current.nodeType() === Node.DOCUMENT_NODE) | 149 if (current.nodeType() === Node.DOCUMENT_NODE) |
| 129 continue; | 150 continue; |
| 130 | 151 |
| 131 crumb = createElementWithClass('span', 'crumb'); | 152 crumb = createElementWithClass('span', 'crumb'); |
| 132 crumb[this._nodeSymbol] = current; | 153 crumb[this._nodeSymbol] = current; |
| 133 crumb.addEventListener('mousedown', boundSelectCrumb, false); | 154 crumb.addEventListener('mousedown', this._onClickCrumb.bind(this), false); |
| 134 | 155 |
| 135 var crumbTitle = ''; | 156 var crumbTitle = this._determineElementTitle(current); |
| 136 switch (current.nodeType()) { | 157 if (crumbTitle) { |
| 137 case Node.ELEMENT_NODE: | |
| 138 if (current.pseudoType()) | |
| 139 crumbTitle = '::' + current.pseudoType(); | |
| 140 else | |
| 141 Components.DOMPresentationUtils.decorateNodeLabel(current, crumb); | |
| 142 break; | |
| 143 | |
| 144 case Node.TEXT_NODE: | |
| 145 crumbTitle = Common.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'); | 158 var nameElement = createElement('span'); |
| 166 nameElement.textContent = crumbTitle; | 159 nameElement.textContent = crumbTitle; |
| 167 crumb.appendChild(nameElement); | 160 crumb.appendChild(nameElement); |
| 168 crumb.title = crumbTitle; | 161 crumb.title = crumbTitle; |
| 162 } else { |
| 163 Components.DOMPresentationUtils.decorateNodeLabel(current, crumb); |
| 169 } | 164 } |
| 170 | 165 |
| 171 if (current === currentDOMNode) | 166 if (current === currentDOMNode) |
| 172 crumb.classList.add('selected'); | 167 crumb.classList.add('selected'); |
| 173 crumbs.insertBefore(crumb, crumbs.firstChild); | 168 crumbs.insertBefore(crumb, crumbs.firstChild); |
| 174 } | 169 } |
| 175 | 170 |
| 176 this.updateSizes(); | 171 this.updateSizes(); |
| 177 } | 172 } |
| 178 | 173 |
| 179 /** | 174 /** |
| 180 * @param {!Element=} focusedCrumb | 175 * @param {!Element=} focusedCrumb |
| 176 * @return {{selectedIndex: number, focusedIndex: number, selectedCrumb: ?Elem
ent}} |
| 181 */ | 177 */ |
| 182 updateSizes(focusedCrumb) { | 178 _resetCrumbStylesAndFindSelections(focusedCrumb) { |
| 183 if (!this.isShowing()) | |
| 184 return; | |
| 185 | |
| 186 var crumbs = this.crumbsElement; | 179 var crumbs = this.crumbsElement; |
| 187 if (!crumbs.firstChild) | |
| 188 return; | |
| 189 | |
| 190 var selectedIndex = 0; | 180 var selectedIndex = 0; |
| 191 var focusedIndex = 0; | 181 var focusedIndex = 0; |
| 192 var selectedCrumb; | 182 var selectedCrumb = null; |
| 193 | 183 |
| 194 // Reset crumb styles. | 184 // Reset crumb styles. |
| 195 for (var i = 0; i < crumbs.childNodes.length; ++i) { | 185 for (var i = 0; i < crumbs.childNodes.length; ++i) { |
| 196 var crumb = crumbs.children[i]; | 186 var crumb = crumbs.children[i]; |
| 197 // Find the selected crumb and index. | 187 // Find the selected crumb and index. |
| 198 if (!selectedCrumb && crumb.classList.contains('selected')) { | 188 if (!selectedCrumb && crumb.classList.contains('selected')) { |
| 199 selectedCrumb = crumb; | 189 selectedCrumb = crumb; |
| 200 selectedIndex = i; | 190 selectedIndex = i; |
| 201 } | 191 } |
| 202 | 192 |
| 203 // Find the focused crumb index. | 193 // Find the focused crumb index. |
| 204 if (crumb === focusedCrumb) | 194 if (crumb === focusedCrumb) |
| 205 focusedIndex = i; | 195 focusedIndex = i; |
| 206 | 196 |
| 207 crumb.classList.remove('compact', 'collapsed', 'hidden'); | 197 crumb.classList.remove('compact', 'collapsed', 'hidden'); |
| 208 } | 198 } |
| 209 | 199 |
| 210 // Layout 1: Measure total and normal crumb sizes | 200 return {selectedIndex: selectedIndex, focusedIndex: focusedIndex, selectedCr
umb: selectedCrumb}; |
| 211 var contentElementWidth = this.contentElement.offsetWidth; | 201 } |
| 202 |
| 203 /** |
| 204 * @return {{normal: !Array.<number>, compact: !Array.<number>, collapsed: num
ber, available: number}} |
| 205 */ |
| 206 _measureElementSizes() { |
| 207 var crumbs = this.crumbsElement; |
| 208 |
| 209 // Layout 1: Measure total and normal crumb sizes at the same time as a |
| 210 // dummy element for the collapsed size. |
| 211 var collapsedElement = createElementWithClass('span', 'crumb collapsed'); |
| 212 crumbs.insertBefore(collapsedElement, crumbs.firstChild); |
| 213 |
| 214 var available = crumbs.offsetWidth; |
| 215 var collapsed = collapsedElement.offsetWidth; |
| 216 |
| 212 var normalSizes = []; | 217 var normalSizes = []; |
| 213 for (var i = 0; i < crumbs.childNodes.length; ++i) { | 218 for (var i = 1; i < crumbs.childNodes.length; ++i) { |
| 214 var crumb = crumbs.childNodes[i]; | 219 var crumb = crumbs.childNodes[i]; |
| 215 normalSizes[i] = crumb.offsetWidth; | 220 normalSizes[i - 1] = crumb.offsetWidth; |
| 216 } | 221 } |
| 217 | 222 |
| 223 crumbs.removeChild(collapsedElement); |
| 224 |
| 218 // Layout 2: Measure collapsed crumb sizes | 225 // Layout 2: Measure collapsed crumb sizes |
| 219 var compactSizes = []; | 226 var compactSizes = []; |
| 220 for (var i = 0; i < crumbs.childNodes.length; ++i) { | 227 for (var i = 0; i < crumbs.childNodes.length; ++i) { |
| 221 var crumb = crumbs.childNodes[i]; | 228 var crumb = crumbs.childNodes[i]; |
| 222 crumb.classList.add('compact'); | 229 crumb.classList.add('compact'); |
| 223 } | 230 } |
| 224 for (var i = 0; i < crumbs.childNodes.length; ++i) { | 231 for (var i = 0; i < crumbs.childNodes.length; ++i) { |
| 225 var crumb = crumbs.childNodes[i]; | 232 var crumb = crumbs.childNodes[i]; |
| 226 compactSizes[i] = crumb.offsetWidth; | 233 compactSizes[i] = crumb.offsetWidth; |
| 227 } | 234 } |
| 228 | 235 |
| 229 // Layout 3: Measure collapsed crumb size | |
| 230 crumbs.firstChild.classList.add('collapsed'); | |
| 231 var collapsedSize = crumbs.firstChild.offsetWidth; | |
| 232 | |
| 233 // Clean up. | 236 // Clean up. |
| 234 for (var i = 0; i < crumbs.childNodes.length; ++i) { | 237 for (var i = 0; i < crumbs.childNodes.length; ++i) { |
| 235 var crumb = crumbs.childNodes[i]; | 238 var crumb = crumbs.childNodes[i]; |
| 236 crumb.classList.remove('compact', 'collapsed'); | 239 crumb.classList.remove('compact', 'collapsed'); |
| 237 } | 240 } |
| 238 | 241 |
| 242 return {normal: normalSizes, compact: compactSizes, collapsed: collapsed, av
ailable: available}; |
| 243 } |
| 244 |
| 245 /** |
| 246 * @param {!Element=} focusedCrumb |
| 247 */ |
| 248 updateSizes(focusedCrumb) { |
| 249 if (!this.isShowing()) |
| 250 return; |
| 251 |
| 252 var crumbs = this.crumbsElement; |
| 253 if (!crumbs.firstChild) |
| 254 return; |
| 255 |
| 256 var selections = this._resetCrumbStylesAndFindSelections(focusedCrumb); |
| 257 var sizes = this._measureElementSizes(); |
| 258 var selectedIndex = selections.selectedIndex; |
| 259 var focusedIndex = selections.focusedIndex; |
| 260 var selectedCrumb = selections.selectedCrumb; |
| 261 |
| 239 function crumbsAreSmallerThanContainer() { | 262 function crumbsAreSmallerThanContainer() { |
| 240 var totalSize = 0; | 263 var totalSize = 0; |
| 241 for (var i = 0; i < crumbs.childNodes.length; ++i) { | 264 for (var i = 0; i < crumbs.childNodes.length; ++i) { |
| 242 var crumb = crumbs.childNodes[i]; | 265 var crumb = crumbs.childNodes[i]; |
| 243 if (crumb.classList.contains('hidden')) | 266 if (crumb.classList.contains('hidden')) |
| 244 continue; | 267 continue; |
| 245 if (crumb.classList.contains('collapsed')) { | 268 if (crumb.classList.contains('collapsed')) { |
| 246 totalSize += collapsedSize; | 269 totalSize += sizes.collapsed; |
| 247 continue; | 270 continue; |
| 248 } | 271 } |
| 249 totalSize += crumb.classList.contains('compact') ? compactSizes[i] : nor
malSizes[i]; | 272 totalSize += crumb.classList.contains('compact') ? sizes.compact[i] : si
zes.normal[i]; |
| 250 } | 273 } |
| 251 const rightPadding = 10; | 274 const rightPadding = 10; |
| 252 return totalSize + rightPadding < contentElementWidth; | 275 return totalSize + rightPadding < sizes.available; |
| 253 } | 276 } |
| 254 | 277 |
| 255 if (crumbsAreSmallerThanContainer()) | 278 if (crumbsAreSmallerThanContainer()) |
| 256 return; // No need to compact the crumbs, they all fit at full size. | 279 return; // No need to compact the crumbs, they all fit at full size. |
| 257 | 280 |
| 258 var BothSides = 0; | 281 var BothSides = 0; |
| 259 var AncestorSide = -1; | 282 var AncestorSide = -1; |
| 260 var ChildSide = 1; | 283 var ChildSide = 1; |
| 261 | 284 |
| 262 /** | 285 /** |
| (...skipping 148 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 411 | 434 |
| 412 // Collapse the selected crumb as a last resort. Pass true to prevent coales
cing. | 435 // Collapse the selected crumb as a last resort. Pass true to prevent coales
cing. |
| 413 collapse(selectedCrumb, true); | 436 collapse(selectedCrumb, true); |
| 414 } | 437 } |
| 415 }; | 438 }; |
| 416 | 439 |
| 417 /** @enum {symbol} */ | 440 /** @enum {symbol} */ |
| 418 Elements.ElementsBreadcrumbs.Events = { | 441 Elements.ElementsBreadcrumbs.Events = { |
| 419 NodeSelected: Symbol('NodeSelected') | 442 NodeSelected: Symbol('NodeSelected') |
| 420 }; | 443 }; |
| OLD | NEW |