| 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 |