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