OLD | NEW |
(Empty) | |
| 1 /* |
| 2 * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. |
| 3 * Copyright (C) 2008 Matt Lilek <webkit@mattlilek.com> |
| 4 * Copyright (C) 2009 Joseph Pecoraro |
| 5 * |
| 6 * Redistribution and use in source and binary forms, with or without |
| 7 * modification, are permitted provided that the following conditions |
| 8 * are met: |
| 9 * |
| 10 * 1. Redistributions of source code must retain the above copyright |
| 11 * notice, this list of conditions and the following disclaimer. |
| 12 * 2. Redistributions in binary form must reproduce the above copyright |
| 13 * notice, this list of conditions and the following disclaimer in the |
| 14 * documentation and/or other materials provided with the distribution. |
| 15 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of |
| 16 * its contributors may be used to endorse or promote products derived |
| 17 * from this software without specific prior written permission. |
| 18 * |
| 19 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY |
| 20 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| 21 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| 22 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY |
| 23 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| 24 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| 25 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| 26 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
| 28 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 29 */ |
| 30 |
| 31 WebInspector.ElementsPanel = function() |
| 32 { |
| 33 WebInspector.Panel.call(this); |
| 34 |
| 35 this.element.addStyleClass("elements"); |
| 36 |
| 37 this.contentElement = document.createElement("div"); |
| 38 this.contentElement.id = "elements-content"; |
| 39 this.contentElement.className = "outline-disclosure"; |
| 40 |
| 41 this.treeOutline = new WebInspector.ElementsTreeOutline(); |
| 42 this.treeOutline.panel = this; |
| 43 this.treeOutline.includeRootDOMNode = false; |
| 44 this.treeOutline.selectEnabled = true; |
| 45 |
| 46 this.treeOutline.focusedNodeChanged = function(forceUpdate) |
| 47 { |
| 48 if (this.panel.visible && WebInspector.currentFocusElement !== document.
getElementById("search")) |
| 49 WebInspector.currentFocusElement = document.getElementById("main-pan
els"); |
| 50 |
| 51 this.panel.updateBreadcrumb(forceUpdate); |
| 52 |
| 53 for (var pane in this.panel.sidebarPanes) |
| 54 this.panel.sidebarPanes[pane].needsUpdate = true; |
| 55 |
| 56 this.panel.updateStyles(true); |
| 57 this.panel.updateMetrics(); |
| 58 this.panel.updateProperties(); |
| 59 this.panel.updateEventListeners(); |
| 60 |
| 61 if (InspectorController.searchingForNode()) { |
| 62 InspectorController.toggleNodeSearch(); |
| 63 this.panel.nodeSearchButton.toggled = false; |
| 64 } |
| 65 if (this._focusedDOMNode) |
| 66 InjectedScriptAccess.addInspectedNode(this._focusedDOMNode.id, funct
ion() {}); |
| 67 }; |
| 68 |
| 69 this.contentElement.appendChild(this.treeOutline.element); |
| 70 |
| 71 this.crumbsElement = document.createElement("div"); |
| 72 this.crumbsElement.className = "crumbs"; |
| 73 this.crumbsElement.addEventListener("mousemove", this._mouseMovedInCrumbs.bi
nd(this), false); |
| 74 this.crumbsElement.addEventListener("mouseout", this._mouseMovedOutOfCrumbs.
bind(this), false); |
| 75 |
| 76 this.sidebarPanes = {}; |
| 77 this.sidebarPanes.styles = new WebInspector.StylesSidebarPane(); |
| 78 this.sidebarPanes.metrics = new WebInspector.MetricsSidebarPane(); |
| 79 this.sidebarPanes.properties = new WebInspector.PropertiesSidebarPane(); |
| 80 this.sidebarPanes.eventListeners = new WebInspector.EventListenersSidebarPan
e(); |
| 81 |
| 82 this.sidebarPanes.styles.onexpand = this.updateStyles.bind(this); |
| 83 this.sidebarPanes.metrics.onexpand = this.updateMetrics.bind(this); |
| 84 this.sidebarPanes.properties.onexpand = this.updateProperties.bind(this); |
| 85 this.sidebarPanes.eventListeners.onexpand = this.updateEventListeners.bind(t
his); |
| 86 |
| 87 this.sidebarPanes.styles.expanded = true; |
| 88 |
| 89 this.sidebarPanes.styles.addEventListener("style edited", this._stylesPaneEd
ited, this); |
| 90 this.sidebarPanes.styles.addEventListener("style property toggled", this._st
ylesPaneEdited, this); |
| 91 this.sidebarPanes.metrics.addEventListener("metrics edited", this._metricsPa
neEdited, this); |
| 92 |
| 93 this.sidebarElement = document.createElement("div"); |
| 94 this.sidebarElement.id = "elements-sidebar"; |
| 95 |
| 96 this.sidebarElement.appendChild(this.sidebarPanes.styles.element); |
| 97 this.sidebarElement.appendChild(this.sidebarPanes.metrics.element); |
| 98 this.sidebarElement.appendChild(this.sidebarPanes.properties.element); |
| 99 this.sidebarElement.appendChild(this.sidebarPanes.eventListeners.element); |
| 100 |
| 101 this.sidebarResizeElement = document.createElement("div"); |
| 102 this.sidebarResizeElement.className = "sidebar-resizer-vertical"; |
| 103 this.sidebarResizeElement.addEventListener("mousedown", this.rightSidebarRes
izerDragStart.bind(this), false); |
| 104 |
| 105 this.nodeSearchButton = new WebInspector.StatusBarButton(WebInspector.UIStri
ng("Select an element in the page to inspect it."), "node-search-status-bar-item
"); |
| 106 this.nodeSearchButton.addEventListener("click", this._nodeSearchButtonClicke
d.bind(this), false); |
| 107 |
| 108 this.searchingForNode = false; |
| 109 |
| 110 this.element.appendChild(this.contentElement); |
| 111 this.element.appendChild(this.sidebarElement); |
| 112 this.element.appendChild(this.sidebarResizeElement); |
| 113 |
| 114 this._changedStyles = {}; |
| 115 |
| 116 this.reset(); |
| 117 } |
| 118 |
| 119 WebInspector.ElementsPanel.prototype = { |
| 120 toolbarItemClass: "elements", |
| 121 |
| 122 get toolbarItemLabel() |
| 123 { |
| 124 return WebInspector.UIString("Elements"); |
| 125 }, |
| 126 |
| 127 get statusBarItems() |
| 128 { |
| 129 return [this.nodeSearchButton.element, this.crumbsElement]; |
| 130 }, |
| 131 |
| 132 updateStatusBarItems: function() |
| 133 { |
| 134 this.updateBreadcrumbSizes(); |
| 135 }, |
| 136 |
| 137 show: function() |
| 138 { |
| 139 WebInspector.Panel.prototype.show.call(this); |
| 140 this.sidebarResizeElement.style.right = (this.sidebarElement.offsetWidth
- 3) + "px"; |
| 141 this.updateBreadcrumb(); |
| 142 this.treeOutline.updateSelection(); |
| 143 if (this.recentlyModifiedNodes.length) |
| 144 this._updateModifiedNodes(); |
| 145 }, |
| 146 |
| 147 hide: function() |
| 148 { |
| 149 WebInspector.Panel.prototype.hide.call(this); |
| 150 |
| 151 WebInspector.hoveredDOMNode = null; |
| 152 |
| 153 if (InspectorController.searchingForNode()) { |
| 154 InspectorController.toggleNodeSearch(); |
| 155 this.nodeSearchButton.toggled = false; |
| 156 } |
| 157 }, |
| 158 |
| 159 resize: function() |
| 160 { |
| 161 this.treeOutline.updateSelection(); |
| 162 this.updateBreadcrumbSizes(); |
| 163 }, |
| 164 |
| 165 reset: function() |
| 166 { |
| 167 this.rootDOMNode = null; |
| 168 this.focusedDOMNode = null; |
| 169 |
| 170 WebInspector.hoveredDOMNode = null; |
| 171 |
| 172 if (InspectorController.searchingForNode()) { |
| 173 InspectorController.toggleNodeSearch(); |
| 174 this.nodeSearchButton.toggled = false; |
| 175 } |
| 176 |
| 177 this.recentlyModifiedNodes = []; |
| 178 |
| 179 delete this.currentQuery; |
| 180 this.searchCanceled(); |
| 181 |
| 182 var domWindow = WebInspector.domAgent.domWindow; |
| 183 if (!domWindow || !domWindow.document || !domWindow.document.firstChild) |
| 184 return; |
| 185 |
| 186 // If the window isn't visible, return early so the DOM tree isn't built |
| 187 // and mutation event listeners are not added. |
| 188 if (!InspectorController.isWindowVisible()) |
| 189 return; |
| 190 |
| 191 var inspectedRootDocument = domWindow.document; |
| 192 inspectedRootDocument.addEventListener("DOMNodeInserted", this._nodeInse
rted.bind(this)); |
| 193 inspectedRootDocument.addEventListener("DOMNodeRemoved", this._nodeRemov
ed.bind(this)); |
| 194 |
| 195 this.rootDOMNode = inspectedRootDocument; |
| 196 |
| 197 var canidateFocusNode = inspectedRootDocument.body || inspectedRootDocum
ent.documentElement; |
| 198 if (canidateFocusNode) { |
| 199 this.treeOutline.suppressSelectHighlight = true; |
| 200 this.focusedDOMNode = canidateFocusNode; |
| 201 this.treeOutline.suppressSelectHighlight = false; |
| 202 |
| 203 if (this.treeOutline.selectedTreeElement) |
| 204 this.treeOutline.selectedTreeElement.expand(); |
| 205 } |
| 206 }, |
| 207 |
| 208 searchCanceled: function() |
| 209 { |
| 210 if (this._searchResults) { |
| 211 for (var i = 0; i < this._searchResults.length; ++i) { |
| 212 var treeElement = this.treeOutline.findTreeElement(this._searchR
esults[i]); |
| 213 if (treeElement) |
| 214 treeElement.highlighted = false; |
| 215 } |
| 216 } |
| 217 |
| 218 WebInspector.updateSearchMatchesCount(0, this); |
| 219 |
| 220 this._currentSearchResultIndex = 0; |
| 221 this._searchResults = []; |
| 222 InjectedScriptAccess.searchCanceled(function() {}); |
| 223 }, |
| 224 |
| 225 performSearch: function(query) |
| 226 { |
| 227 // Call searchCanceled since it will reset everything we need before doi
ng a new search. |
| 228 this.searchCanceled(); |
| 229 |
| 230 const whitespaceTrimmedQuery = query.trimWhitespace(); |
| 231 if (!whitespaceTrimmedQuery.length) |
| 232 return; |
| 233 |
| 234 this._updatedMatchCountOnce = false; |
| 235 this._matchesCountUpdateTimeout = null; |
| 236 |
| 237 InjectedScriptAccess.performSearch(whitespaceTrimmedQuery, function() {}
); |
| 238 }, |
| 239 |
| 240 _updateMatchesCount: function() |
| 241 { |
| 242 WebInspector.updateSearchMatchesCount(this._searchResults.length, this); |
| 243 this._matchesCountUpdateTimeout = null; |
| 244 this._updatedMatchCountOnce = true; |
| 245 }, |
| 246 |
| 247 _updateMatchesCountSoon: function() |
| 248 { |
| 249 if (!this._updatedMatchCountOnce) |
| 250 return this._updateMatchesCount(); |
| 251 if (this._matchesCountUpdateTimeout) |
| 252 return; |
| 253 // Update the matches count every half-second so it doesn't feel twitchy
. |
| 254 this._matchesCountUpdateTimeout = setTimeout(this._updateMatchesCount.bi
nd(this), 500); |
| 255 }, |
| 256 |
| 257 addNodesToSearchResult: function(nodeIds) |
| 258 { |
| 259 if (!nodeIds) |
| 260 return; |
| 261 |
| 262 var nodeIdsArray = nodeIds.split(","); |
| 263 for (var i = 0; i < nodeIdsArray.length; ++i) { |
| 264 var nodeId = nodeIdsArray[i]; |
| 265 var node = WebInspector.domAgent.nodeForId(nodeId); |
| 266 if (!node) |
| 267 continue; |
| 268 |
| 269 if (!this._searchResults.length) { |
| 270 this._currentSearchResultIndex = 0; |
| 271 |
| 272 // Only change the focusedDOMNode if the search was manually per
formed, because |
| 273 // the search may have been performed programmatically and we wo
uldn't want to |
| 274 // change the current focusedDOMNode. |
| 275 if (WebInspector.currentFocusElement === document.getElementById
("search")) |
| 276 this.focusedDOMNode = node; |
| 277 } |
| 278 |
| 279 this._searchResults.push(node); |
| 280 |
| 281 // Highlight the tree element to show it matched the search. |
| 282 // FIXME: highlight the substrings in text nodes and attributes. |
| 283 var treeElement = this.treeOutline.findTreeElement(node); |
| 284 if (treeElement) |
| 285 treeElement.highlighted = true; |
| 286 } |
| 287 |
| 288 this._updateMatchesCountSoon(); |
| 289 }, |
| 290 |
| 291 jumpToNextSearchResult: function() |
| 292 { |
| 293 if (!this._searchResults || !this._searchResults.length) |
| 294 return; |
| 295 if (++this._currentSearchResultIndex >= this._searchResults.length) |
| 296 this._currentSearchResultIndex = 0; |
| 297 this.focusedDOMNode = this._searchResults[this._currentSearchResultIndex
]; |
| 298 }, |
| 299 |
| 300 jumpToPreviousSearchResult: function() |
| 301 { |
| 302 if (!this._searchResults || !this._searchResults.length) |
| 303 return; |
| 304 if (--this._currentSearchResultIndex < 0) |
| 305 this._currentSearchResultIndex = (this._searchResults.length - 1); |
| 306 this.focusedDOMNode = this._searchResults[this._currentSearchResultIndex
]; |
| 307 }, |
| 308 |
| 309 renameSelector: function(oldIdentifier, newIdentifier, oldSelector, newSelec
tor) |
| 310 { |
| 311 // TODO: Implement Shifting the oldSelector, and its contents to a newSe
lector |
| 312 }, |
| 313 |
| 314 addStyleChange: function(identifier, style, property) |
| 315 { |
| 316 if (!style.parentRule) |
| 317 return; |
| 318 |
| 319 var selector = style.parentRule.selectorText; |
| 320 if (!this._changedStyles[identifier]) |
| 321 this._changedStyles[identifier] = {}; |
| 322 |
| 323 if (!this._changedStyles[identifier][selector]) |
| 324 this._changedStyles[identifier][selector] = {}; |
| 325 |
| 326 if (!this._changedStyles[identifier][selector][property]) |
| 327 WebInspector.styleChanges += 1; |
| 328 |
| 329 this._changedStyles[identifier][selector][property] = style.getPropertyV
alue(property); |
| 330 }, |
| 331 |
| 332 removeStyleChange: function(identifier, style, property) |
| 333 { |
| 334 if (!style.parentRule) |
| 335 return; |
| 336 |
| 337 var selector = style.parentRule.selectorText; |
| 338 if (!this._changedStyles[identifier] || !this._changedStyles[identifier]
[selector]) |
| 339 return; |
| 340 |
| 341 if (this._changedStyles[identifier][selector][property]) { |
| 342 delete this._changedStyles[identifier][selector][property]; |
| 343 WebInspector.styleChanges -= 1; |
| 344 } |
| 345 }, |
| 346 |
| 347 generateStylesheet: function() |
| 348 { |
| 349 if (!WebInspector.styleChanges) |
| 350 return; |
| 351 |
| 352 // Merge Down to Just Selectors |
| 353 var mergedSelectors = {}; |
| 354 for (var identifier in this._changedStyles) { |
| 355 for (var selector in this._changedStyles[identifier]) { |
| 356 if (!mergedSelectors[selector]) |
| 357 mergedSelectors[selector] = this._changedStyles[identifier][
selector]; |
| 358 else { // merge on selector |
| 359 var merge = {}; |
| 360 for (var property in mergedSelectors[selector]) |
| 361 merge[property] = mergedSelectors[selector][property]; |
| 362 for (var property in this._changedStyles[identifier][selecto
r]) { |
| 363 if (!merge[property]) |
| 364 merge[property] = this._changedStyles[identifier][se
lector][property]; |
| 365 else { // merge on property within a selector, include c
omment to notify user |
| 366 var value1 = merge[property]; |
| 367 var value2 = this._changedStyles[identifier][selecto
r][property]; |
| 368 |
| 369 if (value1 === value2) |
| 370 merge[property] = [value1]; |
| 371 else if (value1 instanceof Array) |
| 372 merge[property].push(value2); |
| 373 else |
| 374 merge[property] = [value1, value2]; |
| 375 } |
| 376 } |
| 377 mergedSelectors[selector] = merge; |
| 378 } |
| 379 } |
| 380 } |
| 381 |
| 382 var builder = []; |
| 383 builder.push("/**"); |
| 384 builder.push(" * Inspector Generated Stylesheet"); // UIString? |
| 385 builder.push(" */\n"); |
| 386 |
| 387 var indent = " "; |
| 388 function displayProperty(property, value, comment) { |
| 389 if (comment) |
| 390 return indent + "/* " + property + ": " + value + "; */"; |
| 391 else |
| 392 return indent + property + ": " + value + ";"; |
| 393 } |
| 394 |
| 395 for (var selector in mergedSelectors) { |
| 396 var psuedoStyle = mergedSelectors[selector]; |
| 397 var properties = Object.properties(psuedoStyle); |
| 398 if (properties.length) { |
| 399 builder.push(selector + " {"); |
| 400 for (var i = 0; i < properties.length; ++i) { |
| 401 var property = properties[i]; |
| 402 var value = psuedoStyle[property]; |
| 403 if (!(value instanceof Array)) |
| 404 builder.push(displayProperty(property, value)); |
| 405 else { |
| 406 if (value.length === 1) |
| 407 builder.push(displayProperty(property, value) + " /*
merged from equivalent edits */"); // UIString? |
| 408 else { |
| 409 builder.push(indent + "/* There was a Conflict... Th
ere were Multiple Edits for '" + property + "' */"); // UIString? |
| 410 for (var j = 0; j < value.length; ++j) |
| 411 builder.push(displayProperty(property, value, tr
ue)); |
| 412 } |
| 413 } |
| 414 } |
| 415 builder.push("}\n"); |
| 416 } |
| 417 } |
| 418 |
| 419 WebInspector.showConsole(); |
| 420 WebInspector.console.addMessage(new WebInspector.ConsoleTextMessage(buil
der.join("\n"))); |
| 421 }, |
| 422 |
| 423 get rootDOMNode() |
| 424 { |
| 425 return this.treeOutline.rootDOMNode; |
| 426 }, |
| 427 |
| 428 set rootDOMNode(x) |
| 429 { |
| 430 this.treeOutline.rootDOMNode = x; |
| 431 }, |
| 432 |
| 433 get focusedDOMNode() |
| 434 { |
| 435 return this.treeOutline.focusedDOMNode; |
| 436 }, |
| 437 |
| 438 set focusedDOMNode(x) |
| 439 { |
| 440 this.treeOutline.focusedDOMNode = x; |
| 441 }, |
| 442 |
| 443 _nodeInserted: function(event) |
| 444 { |
| 445 this.recentlyModifiedNodes.push({node: event.target, parent: event.relat
edNode, inserted: true}); |
| 446 if (this.visible) |
| 447 this._updateModifiedNodesSoon(); |
| 448 }, |
| 449 |
| 450 _nodeRemoved: function(event) |
| 451 { |
| 452 this.recentlyModifiedNodes.push({node: event.target, parent: event.relat
edNode, removed: true}); |
| 453 if (this.visible) |
| 454 this._updateModifiedNodesSoon(); |
| 455 }, |
| 456 |
| 457 _updateModifiedNodesSoon: function() |
| 458 { |
| 459 if ("_updateModifiedNodesTimeout" in this) |
| 460 return; |
| 461 this._updateModifiedNodesTimeout = setTimeout(this._updateModifiedNodes.
bind(this), 0); |
| 462 }, |
| 463 |
| 464 _updateModifiedNodes: function() |
| 465 { |
| 466 if ("_updateModifiedNodesTimeout" in this) { |
| 467 clearTimeout(this._updateModifiedNodesTimeout); |
| 468 delete this._updateModifiedNodesTimeout; |
| 469 } |
| 470 |
| 471 var updatedParentTreeElements = []; |
| 472 var updateBreadcrumbs = false; |
| 473 |
| 474 for (var i = 0; i < this.recentlyModifiedNodes.length; ++i) { |
| 475 var replaced = this.recentlyModifiedNodes[i].replaced; |
| 476 var parent = this.recentlyModifiedNodes[i].parent; |
| 477 if (!parent) |
| 478 continue; |
| 479 |
| 480 var parentNodeItem = this.treeOutline.findTreeElement(parent); |
| 481 if (parentNodeItem && !parentNodeItem.alreadyUpdatedChildren) { |
| 482 parentNodeItem.updateChildren(replaced); |
| 483 parentNodeItem.alreadyUpdatedChildren = true; |
| 484 updatedParentTreeElements.push(parentNodeItem); |
| 485 } |
| 486 |
| 487 if (!updateBreadcrumbs && (this.focusedDOMNode === parent || isAnces
torNode(this.focusedDOMNode, parent))) |
| 488 updateBreadcrumbs = true; |
| 489 } |
| 490 |
| 491 for (var i = 0; i < updatedParentTreeElements.length; ++i) |
| 492 delete updatedParentTreeElements[i].alreadyUpdatedChildren; |
| 493 |
| 494 this.recentlyModifiedNodes = []; |
| 495 |
| 496 if (updateBreadcrumbs) |
| 497 this.updateBreadcrumb(true); |
| 498 }, |
| 499 |
| 500 _stylesPaneEdited: function() |
| 501 { |
| 502 this.sidebarPanes.metrics.needsUpdate = true; |
| 503 this.updateMetrics(); |
| 504 }, |
| 505 |
| 506 _metricsPaneEdited: function() |
| 507 { |
| 508 this.sidebarPanes.styles.needsUpdate = true; |
| 509 this.updateStyles(true); |
| 510 }, |
| 511 |
| 512 _mouseMovedInCrumbs: function(event) |
| 513 { |
| 514 var nodeUnderMouse = document.elementFromPoint(event.pageX, event.pageY)
; |
| 515 var crumbElement = nodeUnderMouse.enclosingNodeOrSelfWithClass("crumb"); |
| 516 |
| 517 WebInspector.hoveredDOMNode = (crumbElement ? crumbElement.representedOb
ject : null); |
| 518 |
| 519 if ("_mouseOutOfCrumbsTimeout" in this) { |
| 520 clearTimeout(this._mouseOutOfCrumbsTimeout); |
| 521 delete this._mouseOutOfCrumbsTimeout; |
| 522 } |
| 523 }, |
| 524 |
| 525 _mouseMovedOutOfCrumbs: function(event) |
| 526 { |
| 527 var nodeUnderMouse = document.elementFromPoint(event.pageX, event.pageY)
; |
| 528 if (nodeUnderMouse.isDescendant(this.crumbsElement)) |
| 529 return; |
| 530 |
| 531 WebInspector.hoveredDOMNode = null; |
| 532 |
| 533 this._mouseOutOfCrumbsTimeout = setTimeout(this.updateBreadcrumbSizes.bi
nd(this), 1000); |
| 534 }, |
| 535 |
| 536 updateBreadcrumb: function(forceUpdate) |
| 537 { |
| 538 if (!this.visible) |
| 539 return; |
| 540 |
| 541 var crumbs = this.crumbsElement; |
| 542 |
| 543 var handled = false; |
| 544 var foundRoot = false; |
| 545 var crumb = crumbs.firstChild; |
| 546 while (crumb) { |
| 547 if (crumb.representedObject === this.rootDOMNode) |
| 548 foundRoot = true; |
| 549 |
| 550 if (foundRoot) |
| 551 crumb.addStyleClass("dimmed"); |
| 552 else |
| 553 crumb.removeStyleClass("dimmed"); |
| 554 |
| 555 if (crumb.representedObject === this.focusedDOMNode) { |
| 556 crumb.addStyleClass("selected"); |
| 557 handled = true; |
| 558 } else { |
| 559 crumb.removeStyleClass("selected"); |
| 560 } |
| 561 |
| 562 crumb = crumb.nextSibling; |
| 563 } |
| 564 |
| 565 if (handled && !forceUpdate) { |
| 566 // We don't need to rebuild the crumbs, but we need to adjust sizes |
| 567 // to reflect the new focused or root node. |
| 568 this.updateBreadcrumbSizes(); |
| 569 return; |
| 570 } |
| 571 |
| 572 crumbs.removeChildren(); |
| 573 |
| 574 var panel = this; |
| 575 |
| 576 function selectCrumbFunction(event) |
| 577 { |
| 578 var crumb = event.currentTarget; |
| 579 if (crumb.hasStyleClass("collapsed")) { |
| 580 // Clicking a collapsed crumb will expose the hidden crumbs. |
| 581 if (crumb === panel.crumbsElement.firstChild) { |
| 582 // If the focused crumb is the first child, pick the farthes
t crumb |
| 583 // that is still hidden. This allows the user to expose ever
y crumb. |
| 584 var currentCrumb = crumb; |
| 585 while (currentCrumb) { |
| 586 var hidden = currentCrumb.hasStyleClass("hidden"); |
| 587 var collapsed = currentCrumb.hasStyleClass("collapsed"); |
| 588 if (!hidden && !collapsed) |
| 589 break; |
| 590 crumb = currentCrumb; |
| 591 currentCrumb = currentCrumb.nextSibling; |
| 592 } |
| 593 } |
| 594 |
| 595 panel.updateBreadcrumbSizes(crumb); |
| 596 } else { |
| 597 // Clicking a dimmed crumb or double clicking (event.detail >= 2
) |
| 598 // will change the root node in addition to the focused node. |
| 599 if (event.detail >= 2 || crumb.hasStyleClass("dimmed")) |
| 600 panel.rootDOMNode = crumb.representedObject.parentNode; |
| 601 panel.focusedDOMNode = crumb.representedObject; |
| 602 } |
| 603 |
| 604 event.preventDefault(); |
| 605 } |
| 606 |
| 607 foundRoot = false; |
| 608 for (var current = this.focusedDOMNode; current; current = current.paren
tNode) { |
| 609 if (current.nodeType === Node.DOCUMENT_NODE) |
| 610 continue; |
| 611 |
| 612 if (current === this.rootDOMNode) |
| 613 foundRoot = true; |
| 614 |
| 615 var crumb = document.createElement("span"); |
| 616 crumb.className = "crumb"; |
| 617 crumb.representedObject = current; |
| 618 crumb.addEventListener("mousedown", selectCrumbFunction, false); |
| 619 |
| 620 var crumbTitle; |
| 621 switch (current.nodeType) { |
| 622 case Node.ELEMENT_NODE: |
| 623 crumbTitle = current.nodeName.toLowerCase(); |
| 624 |
| 625 var nameElement = document.createElement("span"); |
| 626 nameElement.textContent = crumbTitle; |
| 627 crumb.appendChild(nameElement); |
| 628 |
| 629 var idAttribute = current.getAttribute("id"); |
| 630 if (idAttribute) { |
| 631 var idElement = document.createElement("span"); |
| 632 crumb.appendChild(idElement); |
| 633 |
| 634 var part = "#" + idAttribute; |
| 635 crumbTitle += part; |
| 636 idElement.appendChild(document.createTextNode(part)); |
| 637 |
| 638 // Mark the name as extra, since the ID is more importan
t. |
| 639 nameElement.className = "extra"; |
| 640 } |
| 641 |
| 642 var classAttribute = current.getAttribute("class"); |
| 643 if (classAttribute) { |
| 644 var classes = classAttribute.split(/\s+/); |
| 645 var foundClasses = {}; |
| 646 |
| 647 if (classes.length) { |
| 648 var classesElement = document.createElement("span"); |
| 649 classesElement.className = "extra"; |
| 650 crumb.appendChild(classesElement); |
| 651 |
| 652 for (var i = 0; i < classes.length; ++i) { |
| 653 var className = classes[i]; |
| 654 if (className && !(className in foundClasses)) { |
| 655 var part = "." + className; |
| 656 crumbTitle += part; |
| 657 classesElement.appendChild(document.createTe
xtNode(part)); |
| 658 foundClasses[className] = true; |
| 659 } |
| 660 } |
| 661 } |
| 662 } |
| 663 |
| 664 break; |
| 665 |
| 666 case Node.TEXT_NODE: |
| 667 if (isNodeWhitespace.call(current)) |
| 668 crumbTitle = WebInspector.UIString("(whitespace)"); |
| 669 else |
| 670 crumbTitle = WebInspector.UIString("(text)"); |
| 671 break |
| 672 |
| 673 case Node.COMMENT_NODE: |
| 674 crumbTitle = "<!-->"; |
| 675 break; |
| 676 |
| 677 case Node.DOCUMENT_TYPE_NODE: |
| 678 crumbTitle = "<!DOCTYPE>"; |
| 679 break; |
| 680 |
| 681 default: |
| 682 crumbTitle = current.nodeName.toLowerCase(); |
| 683 } |
| 684 |
| 685 if (!crumb.childNodes.length) { |
| 686 var nameElement = document.createElement("span"); |
| 687 nameElement.textContent = crumbTitle; |
| 688 crumb.appendChild(nameElement); |
| 689 } |
| 690 |
| 691 crumb.title = crumbTitle; |
| 692 |
| 693 if (foundRoot) |
| 694 crumb.addStyleClass("dimmed"); |
| 695 if (current === this.focusedDOMNode) |
| 696 crumb.addStyleClass("selected"); |
| 697 if (!crumbs.childNodes.length) |
| 698 crumb.addStyleClass("end"); |
| 699 |
| 700 crumbs.appendChild(crumb); |
| 701 } |
| 702 |
| 703 if (crumbs.hasChildNodes()) |
| 704 crumbs.lastChild.addStyleClass("start"); |
| 705 |
| 706 this.updateBreadcrumbSizes(); |
| 707 }, |
| 708 |
| 709 updateBreadcrumbSizes: function(focusedCrumb) |
| 710 { |
| 711 if (!this.visible) |
| 712 return; |
| 713 |
| 714 if (document.body.offsetWidth <= 0) { |
| 715 // The stylesheet hasn't loaded yet or the window is closed, |
| 716 // so we can't calculate what is need. Return early. |
| 717 return; |
| 718 } |
| 719 |
| 720 var crumbs = this.crumbsElement; |
| 721 if (!crumbs.childNodes.length || crumbs.offsetWidth <= 0) |
| 722 return; // No crumbs, do nothing. |
| 723 |
| 724 // A Zero index is the right most child crumb in the breadcrumb. |
| 725 var selectedIndex = 0; |
| 726 var focusedIndex = 0; |
| 727 var selectedCrumb; |
| 728 |
| 729 var i = 0; |
| 730 var crumb = crumbs.firstChild; |
| 731 while (crumb) { |
| 732 // Find the selected crumb and index. |
| 733 if (!selectedCrumb && crumb.hasStyleClass("selected")) { |
| 734 selectedCrumb = crumb; |
| 735 selectedIndex = i; |
| 736 } |
| 737 |
| 738 // Find the focused crumb index. |
| 739 if (crumb === focusedCrumb) |
| 740 focusedIndex = i; |
| 741 |
| 742 // Remove any styles that affect size before |
| 743 // deciding to shorten any crumbs. |
| 744 if (crumb !== crumbs.lastChild) |
| 745 crumb.removeStyleClass("start"); |
| 746 if (crumb !== crumbs.firstChild) |
| 747 crumb.removeStyleClass("end"); |
| 748 |
| 749 crumb.removeStyleClass("compact"); |
| 750 crumb.removeStyleClass("collapsed"); |
| 751 crumb.removeStyleClass("hidden"); |
| 752 |
| 753 crumb = crumb.nextSibling; |
| 754 ++i; |
| 755 } |
| 756 |
| 757 // Restore the start and end crumb classes in case they got removed in c
oalesceCollapsedCrumbs(). |
| 758 // The order of the crumbs in the document is opposite of the visual ord
er. |
| 759 crumbs.firstChild.addStyleClass("end"); |
| 760 crumbs.lastChild.addStyleClass("start"); |
| 761 |
| 762 function crumbsAreSmallerThanContainer() |
| 763 { |
| 764 var rightPadding = 20; |
| 765 var errorWarningElement = document.getElementById("error-warning-cou
nt"); |
| 766 if (!WebInspector.drawer.visible && errorWarningElement) |
| 767 rightPadding += errorWarningElement.offsetWidth; |
| 768 return ((crumbs.totalOffsetLeft + crumbs.offsetWidth + rightPadding)
< window.innerWidth); |
| 769 } |
| 770 |
| 771 if (crumbsAreSmallerThanContainer()) |
| 772 return; // No need to compact the crumbs, they all fit at full size. |
| 773 |
| 774 var BothSides = 0; |
| 775 var AncestorSide = -1; |
| 776 var ChildSide = 1; |
| 777 |
| 778 function makeCrumbsSmaller(shrinkingFunction, direction, significantCrum
b) |
| 779 { |
| 780 if (!significantCrumb) |
| 781 significantCrumb = (focusedCrumb || selectedCrumb); |
| 782 |
| 783 if (significantCrumb === selectedCrumb) |
| 784 var significantIndex = selectedIndex; |
| 785 else if (significantCrumb === focusedCrumb) |
| 786 var significantIndex = focusedIndex; |
| 787 else { |
| 788 var significantIndex = 0; |
| 789 for (var i = 0; i < crumbs.childNodes.length; ++i) { |
| 790 if (crumbs.childNodes[i] === significantCrumb) { |
| 791 significantIndex = i; |
| 792 break; |
| 793 } |
| 794 } |
| 795 } |
| 796 |
| 797 function shrinkCrumbAtIndex(index) |
| 798 { |
| 799 var shrinkCrumb = crumbs.childNodes[index]; |
| 800 if (shrinkCrumb && shrinkCrumb !== significantCrumb) |
| 801 shrinkingFunction(shrinkCrumb); |
| 802 if (crumbsAreSmallerThanContainer()) |
| 803 return true; // No need to compact the crumbs more. |
| 804 return false; |
| 805 } |
| 806 |
| 807 // Shrink crumbs one at a time by applying the shrinkingFunction unt
il the crumbs |
| 808 // fit in the container or we run out of crumbs to shrink. |
| 809 if (direction) { |
| 810 // Crumbs are shrunk on only one side (based on direction) of th
e signifcant crumb. |
| 811 var index = (direction > 0 ? 0 : crumbs.childNodes.length - 1); |
| 812 while (index !== significantIndex) { |
| 813 if (shrinkCrumbAtIndex(index)) |
| 814 return true; |
| 815 index += (direction > 0 ? 1 : -1); |
| 816 } |
| 817 } else { |
| 818 // Crumbs are shrunk in order of descending distance from the si
gnifcant crumb, |
| 819 // with a tie going to child crumbs. |
| 820 var startIndex = 0; |
| 821 var endIndex = crumbs.childNodes.length - 1; |
| 822 while (startIndex != significantIndex || endIndex != significant
Index) { |
| 823 var startDistance = significantIndex - startIndex; |
| 824 var endDistance = endIndex - significantIndex; |
| 825 if (startDistance >= endDistance) |
| 826 var index = startIndex++; |
| 827 else |
| 828 var index = endIndex--; |
| 829 if (shrinkCrumbAtIndex(index)) |
| 830 return true; |
| 831 } |
| 832 } |
| 833 |
| 834 // We are not small enough yet, return false so the caller knows. |
| 835 return false; |
| 836 } |
| 837 |
| 838 function coalesceCollapsedCrumbs() |
| 839 { |
| 840 var crumb = crumbs.firstChild; |
| 841 var collapsedRun = false; |
| 842 var newStartNeeded = false; |
| 843 var newEndNeeded = false; |
| 844 while (crumb) { |
| 845 var hidden = crumb.hasStyleClass("hidden"); |
| 846 if (!hidden) { |
| 847 var collapsed = crumb.hasStyleClass("collapsed"); |
| 848 if (collapsedRun && collapsed) { |
| 849 crumb.addStyleClass("hidden"); |
| 850 crumb.removeStyleClass("compact"); |
| 851 crumb.removeStyleClass("collapsed"); |
| 852 |
| 853 if (crumb.hasStyleClass("start")) { |
| 854 crumb.removeStyleClass("start"); |
| 855 newStartNeeded = true; |
| 856 } |
| 857 |
| 858 if (crumb.hasStyleClass("end")) { |
| 859 crumb.removeStyleClass("end"); |
| 860 newEndNeeded = true; |
| 861 } |
| 862 |
| 863 continue; |
| 864 } |
| 865 |
| 866 collapsedRun = collapsed; |
| 867 |
| 868 if (newEndNeeded) { |
| 869 newEndNeeded = false; |
| 870 crumb.addStyleClass("end"); |
| 871 } |
| 872 } else |
| 873 collapsedRun = true; |
| 874 crumb = crumb.nextSibling; |
| 875 } |
| 876 |
| 877 if (newStartNeeded) { |
| 878 crumb = crumbs.lastChild; |
| 879 while (crumb) { |
| 880 if (!crumb.hasStyleClass("hidden")) { |
| 881 crumb.addStyleClass("start"); |
| 882 break; |
| 883 } |
| 884 crumb = crumb.previousSibling; |
| 885 } |
| 886 } |
| 887 } |
| 888 |
| 889 function compact(crumb) |
| 890 { |
| 891 if (crumb.hasStyleClass("hidden")) |
| 892 return; |
| 893 crumb.addStyleClass("compact"); |
| 894 } |
| 895 |
| 896 function collapse(crumb, dontCoalesce) |
| 897 { |
| 898 if (crumb.hasStyleClass("hidden")) |
| 899 return; |
| 900 crumb.addStyleClass("collapsed"); |
| 901 crumb.removeStyleClass("compact"); |
| 902 if (!dontCoalesce) |
| 903 coalesceCollapsedCrumbs(); |
| 904 } |
| 905 |
| 906 function compactDimmed(crumb) |
| 907 { |
| 908 if (crumb.hasStyleClass("dimmed")) |
| 909 compact(crumb); |
| 910 } |
| 911 |
| 912 function collapseDimmed(crumb) |
| 913 { |
| 914 if (crumb.hasStyleClass("dimmed")) |
| 915 collapse(crumb); |
| 916 } |
| 917 |
| 918 if (!focusedCrumb) { |
| 919 // When not focused on a crumb we can be biased and collapse less im
portant |
| 920 // crumbs that the user might not care much about. |
| 921 |
| 922 // Compact child crumbs. |
| 923 if (makeCrumbsSmaller(compact, ChildSide)) |
| 924 return; |
| 925 |
| 926 // Collapse child crumbs. |
| 927 if (makeCrumbsSmaller(collapse, ChildSide)) |
| 928 return; |
| 929 |
| 930 // Compact dimmed ancestor crumbs. |
| 931 if (makeCrumbsSmaller(compactDimmed, AncestorSide)) |
| 932 return; |
| 933 |
| 934 // Collapse dimmed ancestor crumbs. |
| 935 if (makeCrumbsSmaller(collapseDimmed, AncestorSide)) |
| 936 return; |
| 937 } |
| 938 |
| 939 // Compact ancestor crumbs, or from both sides if focused. |
| 940 if (makeCrumbsSmaller(compact, (focusedCrumb ? BothSides : AncestorSide)
)) |
| 941 return; |
| 942 |
| 943 // Collapse ancestor crumbs, or from both sides if focused. |
| 944 if (makeCrumbsSmaller(collapse, (focusedCrumb ? BothSides : AncestorSide
))) |
| 945 return; |
| 946 |
| 947 if (!selectedCrumb) |
| 948 return; |
| 949 |
| 950 // Compact the selected crumb. |
| 951 compact(selectedCrumb); |
| 952 if (crumbsAreSmallerThanContainer()) |
| 953 return; |
| 954 |
| 955 // Collapse the selected crumb as a last resort. Pass true to prevent co
alescing. |
| 956 collapse(selectedCrumb, true); |
| 957 }, |
| 958 |
| 959 updateStyles: function(forceUpdate) |
| 960 { |
| 961 var stylesSidebarPane = this.sidebarPanes.styles; |
| 962 if (!stylesSidebarPane.expanded || !stylesSidebarPane.needsUpdate) |
| 963 return; |
| 964 |
| 965 stylesSidebarPane.update(this.focusedDOMNode, null, forceUpdate); |
| 966 stylesSidebarPane.needsUpdate = false; |
| 967 }, |
| 968 |
| 969 updateMetrics: function() |
| 970 { |
| 971 var metricsSidebarPane = this.sidebarPanes.metrics; |
| 972 if (!metricsSidebarPane.expanded || !metricsSidebarPane.needsUpdate) |
| 973 return; |
| 974 |
| 975 metricsSidebarPane.update(this.focusedDOMNode); |
| 976 metricsSidebarPane.needsUpdate = false; |
| 977 }, |
| 978 |
| 979 updateProperties: function() |
| 980 { |
| 981 var propertiesSidebarPane = this.sidebarPanes.properties; |
| 982 if (!propertiesSidebarPane.expanded || !propertiesSidebarPane.needsUpdat
e) |
| 983 return; |
| 984 |
| 985 propertiesSidebarPane.update(this.focusedDOMNode); |
| 986 propertiesSidebarPane.needsUpdate = false; |
| 987 }, |
| 988 |
| 989 updateEventListeners: function() |
| 990 { |
| 991 var eventListenersSidebarPane = this.sidebarPanes.eventListeners; |
| 992 if (!eventListenersSidebarPane.expanded || !eventListenersSidebarPane.ne
edsUpdate) |
| 993 return; |
| 994 |
| 995 eventListenersSidebarPane.update(this.focusedDOMNode); |
| 996 eventListenersSidebarPane.needsUpdate = false; |
| 997 }, |
| 998 |
| 999 handleKeyEvent: function(event) |
| 1000 { |
| 1001 this.treeOutline.handleKeyEvent(event); |
| 1002 }, |
| 1003 |
| 1004 handleCopyEvent: function(event) |
| 1005 { |
| 1006 // Don't prevent the normal copy if the user has a selection. |
| 1007 if (!window.getSelection().isCollapsed) |
| 1008 return; |
| 1009 event.clipboardData.clearData(); |
| 1010 event.preventDefault(); |
| 1011 InspectorController.copyNode(this.focusedDOMNode.id); |
| 1012 }, |
| 1013 |
| 1014 rightSidebarResizerDragStart: function(event) |
| 1015 { |
| 1016 WebInspector.elementDragStart(this.sidebarElement, this.rightSidebarResi
zerDrag.bind(this), this.rightSidebarResizerDragEnd.bind(this), event, "col-resi
ze"); |
| 1017 }, |
| 1018 |
| 1019 rightSidebarResizerDragEnd: function(event) |
| 1020 { |
| 1021 WebInspector.elementDragEnd(event); |
| 1022 }, |
| 1023 |
| 1024 rightSidebarResizerDrag: function(event) |
| 1025 { |
| 1026 var x = event.pageX; |
| 1027 var newWidth = Number.constrain(window.innerWidth - x, Preferences.minEl
ementsSidebarWidth, window.innerWidth * 0.66); |
| 1028 |
| 1029 this.sidebarElement.style.width = newWidth + "px"; |
| 1030 this.contentElement.style.right = newWidth + "px"; |
| 1031 this.sidebarResizeElement.style.right = (newWidth - 3) + "px"; |
| 1032 |
| 1033 this.treeOutline.updateSelection(); |
| 1034 |
| 1035 event.preventDefault(); |
| 1036 }, |
| 1037 |
| 1038 _nodeSearchButtonClicked: function(event) |
| 1039 { |
| 1040 InspectorController.toggleNodeSearch(); |
| 1041 |
| 1042 this.nodeSearchButton.toggled = InspectorController.searchingForNode(); |
| 1043 } |
| 1044 } |
| 1045 |
| 1046 WebInspector.ElementsPanel.prototype.__proto__ = WebInspector.Panel.prototype; |
OLD | NEW |