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/** 32 * @constructor 33 * @implements {WebInspector.Searchable} 34 * @implements {WebInspector.TargetManager.Observer} 35 * @extends {WebInspector.Panel} 36 */ 37WebInspector.ElementsPanel = function() 38{ 39 WebInspector.Panel.call(this, "elements"); 40 this.registerRequiredCSS("elementsPanel.css"); 41 this.setHideOnDetach(); 42 43 this._splitView = new WebInspector.SplitView(true, true, "elementsPanelSplitViewState", 325, 325); 44 this._splitView.addEventListener(WebInspector.SplitView.Events.SidebarSizeChanged, this._updateTreeOutlineVisibleWidth.bind(this)); 45 this._splitView.show(this.element); 46 47 this._searchableView = new WebInspector.SearchableView(this); 48 this._searchableView.setMinimumSize(25, 19); 49 this._searchableView.show(this._splitView.mainElement()); 50 var stackElement = this._searchableView.element; 51 52 this.contentElement = stackElement.createChild("div"); 53 this.contentElement.id = "elements-content"; 54 this.contentElement.classList.add("outline-disclosure"); 55 this.contentElement.classList.add("source-code"); 56 if (!WebInspector.settings.domWordWrap.get()) 57 this.contentElement.classList.add("nowrap"); 58 WebInspector.settings.domWordWrap.addChangeListener(this._domWordWrapSettingChanged.bind(this)); 59 60 this._splitView.sidebarElement().addEventListener("contextmenu", this._sidebarContextMenuEventFired.bind(this), false); 61 62 var crumbsContainer = stackElement.createChild("div"); 63 crumbsContainer.id = "elements-crumbs"; 64 this.crumbsElement = crumbsContainer.createChild("div", "crumbs"); 65 this.crumbsElement.addEventListener("mousemove", this._mouseMovedInCrumbs.bind(this), false); 66 this.crumbsElement.addEventListener("mouseout", this._mouseMovedOutOfCrumbs.bind(this), false); 67 68 this.sidebarPanes = {}; 69 this.sidebarPanes.platformFonts = new WebInspector.PlatformFontsSidebarPane(); 70 this.sidebarPanes.computedStyle = new WebInspector.ComputedStyleSidebarPane(); 71 this.sidebarPanes.styles = new WebInspector.StylesSidebarPane(this.sidebarPanes.computedStyle, this._setPseudoClassForNode.bind(this)); 72 this.sidebarPanes.styles.addEventListener(WebInspector.StylesSidebarPane.Events.SelectorEditingStarted, this._onEditingSelectorStarted.bind(this)); 73 this.sidebarPanes.styles.addEventListener(WebInspector.StylesSidebarPane.Events.SelectorEditingEnded, this._onEditingSelectorEnded.bind(this)); 74 75 this._matchedStylesFilterBoxContainer = document.createElement("div"); 76 this._matchedStylesFilterBoxContainer.className = "sidebar-pane-filter-box"; 77 this._computedStylesFilterBoxContainer = document.createElement("div"); 78 this._computedStylesFilterBoxContainer.className = "sidebar-pane-filter-box"; 79 this.sidebarPanes.styles.setFilterBoxContainers(this._matchedStylesFilterBoxContainer, this._computedStylesFilterBoxContainer); 80 81 this.sidebarPanes.metrics = new WebInspector.MetricsSidebarPane(); 82 this.sidebarPanes.properties = new WebInspector.PropertiesSidebarPane(); 83 this.sidebarPanes.domBreakpoints = WebInspector.domBreakpointsSidebarPane.createProxy(this); 84 this.sidebarPanes.eventListeners = new WebInspector.EventListenersSidebarPane(); 85 86 this.sidebarPanes.styles.addEventListener(WebInspector.SidebarPane.EventTypes.wasShown, this.updateStyles.bind(this, false)); 87 this.sidebarPanes.metrics.addEventListener(WebInspector.SidebarPane.EventTypes.wasShown, this.updateMetrics.bind(this)); 88 this.sidebarPanes.platformFonts.addEventListener(WebInspector.SidebarPane.EventTypes.wasShown, this.updatePlatformFonts.bind(this)); 89 this.sidebarPanes.properties.addEventListener(WebInspector.SidebarPane.EventTypes.wasShown, this.updateProperties.bind(this)); 90 this.sidebarPanes.eventListeners.addEventListener(WebInspector.SidebarPane.EventTypes.wasShown, this.updateEventListeners.bind(this)); 91 92 this.sidebarPanes.styles.addEventListener("style edited", this._stylesPaneEdited, this); 93 this.sidebarPanes.styles.addEventListener("style property toggled", this._stylesPaneEdited, this); 94 this.sidebarPanes.metrics.addEventListener("metrics edited", this._metricsPaneEdited, this); 95 this._extensionSidebarPanes = []; 96 97 WebInspector.dockController.addEventListener(WebInspector.DockController.Events.DockSideChanged, this._dockSideChanged.bind(this)); 98 WebInspector.settings.splitVerticallyWhenDockedToRight.addChangeListener(this._dockSideChanged.bind(this)); 99 this._dockSideChanged(); 100 101 this._popoverHelper = new WebInspector.PopoverHelper(this.element, this._getPopoverAnchor.bind(this), this._showPopover.bind(this)); 102 this._popoverHelper.setTimeout(0); 103 104 /** @type {!Array.<!WebInspector.ElementsTreeOutline>} */ 105 this._treeOutlines = []; 106 /** @type {!Map.<!WebInspector.Target, !WebInspector.ElementsTreeOutline>} */ 107 this._targetToTreeOutline = new Map(); 108 WebInspector.targetManager.observeTargets(this); 109 WebInspector.settings.showUAShadowDOM.addChangeListener(this._showUAShadowDOMChanged.bind(this)); 110 WebInspector.targetManager.addModelListener(WebInspector.DOMModel, WebInspector.DOMModel.Events.DocumentUpdated, this._documentUpdatedEvent, this); 111 WebInspector.targetManager.addModelListener(WebInspector.CSSStyleModel, WebInspector.CSSStyleModel.Events.ModelWasEnabled, this._updateSidebars, this); 112} 113 114WebInspector.ElementsPanel.prototype = { 115 _onEditingSelectorStarted: function() 116 { 117 for (var i = 0; i < this._treeOutlines.length; ++i) 118 this._treeOutlines[i].setPickNodeMode(true); 119 }, 120 121 _onEditingSelectorEnded: function() 122 { 123 for (var i = 0; i < this._treeOutlines.length; ++i) 124 this._treeOutlines[i].setPickNodeMode(false); 125 }, 126 127 /** 128 * @param {!WebInspector.Target} target 129 */ 130 targetAdded: function(target) 131 { 132 var treeOutline = new WebInspector.ElementsTreeOutline(target, true, true, this._setPseudoClassForNode.bind(this)); 133 treeOutline.wireToDOMModel(); 134 treeOutline.addEventListener(WebInspector.ElementsTreeOutline.Events.SelectedNodeChanged, this._selectedNodeChanged, this); 135 treeOutline.addEventListener(WebInspector.ElementsTreeOutline.Events.NodePicked, this._onNodePicked, this); 136 treeOutline.addEventListener(WebInspector.ElementsTreeOutline.Events.ElementsTreeUpdated, this._updateBreadcrumbIfNeeded, this); 137 this._treeOutlines.push(treeOutline); 138 this._targetToTreeOutline.set(target, treeOutline); 139 140 // Perform attach if necessary. 141 if (this.isShowing()) 142 this.wasShown(); 143 }, 144 145 /** 146 * @param {!WebInspector.Target} target 147 */ 148 targetRemoved: function(target) 149 { 150 var treeOutline = this._targetToTreeOutline.remove(target); 151 treeOutline.unwireFromDOMModel(); 152 this._treeOutlines.remove(treeOutline); 153 treeOutline.element.remove(); 154 }, 155 156 /** 157 * @return {?WebInspector.ElementsTreeOutline} 158 */ 159 _firstTreeOutlineDeprecated: function() 160 { 161 return this._treeOutlines[0] || null; 162 }, 163 164 _updateTreeOutlineVisibleWidth: function() 165 { 166 if (!this._treeOutlines.length) 167 return; 168 169 var width = this._splitView.element.offsetWidth; 170 if (this._splitView.isVertical()) 171 width -= this._splitView.sidebarSize(); 172 for (var i = 0; i < this._treeOutlines.length; ++i) { 173 this._treeOutlines[i].setVisibleWidth(width); 174 this._treeOutlines[i].updateSelection(); 175 } 176 this.updateBreadcrumbSizes(); 177 }, 178 179 /** 180 * @return {!Element} 181 */ 182 defaultFocusedElement: function() 183 { 184 return this._treeOutlines.length ? this._treeOutlines[0].element : this.element; 185 }, 186 187 /** 188 * @return {!WebInspector.SearchableView} 189 */ 190 searchableView: function() 191 { 192 return this._searchableView; 193 }, 194 195 wasShown: function() 196 { 197 for (var i = 0; i < this._treeOutlines.length; ++i) { 198 var treeOutline = this._treeOutlines[i]; 199 // Attach heavy component lazily 200 if (treeOutline.element.parentElement !== this.contentElement) 201 this.contentElement.appendChild(treeOutline.element); 202 } 203 WebInspector.Panel.prototype.wasShown.call(this); 204 this.updateBreadcrumb(); 205 206 for (var i = 0; i < this._treeOutlines.length; ++i) { 207 var treeOutline = this._treeOutlines[i]; 208 treeOutline.updateSelection(); 209 treeOutline.setVisible(true); 210 211 if (!treeOutline.rootDOMNode) 212 if (treeOutline.domModel().existingDocument()) 213 this._documentUpdated(treeOutline.domModel(), treeOutline.domModel().existingDocument()); 214 else 215 treeOutline.domModel().requestDocument(); 216 } 217 218 }, 219 220 willHide: function() 221 { 222 for (var i = 0; i < this._treeOutlines.length; ++i) { 223 var treeOutline = this._treeOutlines[i]; 224 treeOutline.domModel().hideDOMNodeHighlight(); 225 treeOutline.setVisible(false); 226 // Detach heavy component on hide 227 this.contentElement.removeChild(treeOutline.element); 228 } 229 this._popoverHelper.hidePopover(); 230 WebInspector.Panel.prototype.willHide.call(this); 231 }, 232 233 onResize: function() 234 { 235 this._updateTreeOutlineVisibleWidth(); 236 }, 237 238 omitDefaultSelection: function() 239 { 240 this._omitDefaultSelection = true; 241 }, 242 243 stopOmittingDefaultSelection: function() 244 { 245 delete this._omitDefaultSelection; 246 }, 247 248 /** 249 * @param {!WebInspector.DOMNode} node 250 * @param {string} pseudoClass 251 * @param {boolean} enable 252 */ 253 _setPseudoClassForNode: function(node, pseudoClass, enable) 254 { 255 if (!node || !node.target().cssModel.forcePseudoState(node, pseudoClass, enable)) 256 return; 257 258 this._treeOutlineForNode(node).updateOpenCloseTags(node); 259 this._metricsPaneEdited(); 260 this._stylesPaneEdited(); 261 262 WebInspector.notifications.dispatchEventToListeners(WebInspector.UserMetrics.UserAction, { 263 action: WebInspector.UserMetrics.UserActionNames.ForcedElementState, 264 selector: WebInspector.DOMPresentationUtils.fullQualifiedSelector(node, false), 265 enabled: enable, 266 state: pseudoClass 267 }); 268 }, 269 270 /** 271 * @param {!WebInspector.Event} event 272 */ 273 _onNodePicked: function(event) 274 { 275 if (!this.sidebarPanes.styles.isEditingSelector()) 276 return; 277 this.sidebarPanes.styles.updateEditingSelectorForNode(/** @type {!WebInspector.DOMNode} */(event.data)); 278 }, 279 280 /** 281 * @param {!WebInspector.Event} event 282 */ 283 _selectedNodeChanged: function(event) 284 { 285 var selectedNode = /** @type {?WebInspector.DOMNode} */ (event.data); 286 for (var i = 0; i < this._treeOutlines.length; ++i) { 287 if (!selectedNode || selectedNode.domModel() !== this._treeOutlines[i].domModel()) 288 this._treeOutlines[i].selectDOMNode(null); 289 } 290 291 if (!selectedNode && this._lastValidSelectedNode) 292 this._selectedPathOnReset = this._lastValidSelectedNode.path(); 293 294 this.updateBreadcrumb(false); 295 296 this._updateSidebars(); 297 298 if (selectedNode) { 299 ConsoleAgent.addInspectedNode(selectedNode.id); 300 this._lastValidSelectedNode = selectedNode; 301 } 302 WebInspector.notifications.dispatchEventToListeners(WebInspector.NotificationService.Events.SelectedNodeChanged); 303 }, 304 305 _updateSidebars: function() 306 { 307 for (var pane in this.sidebarPanes) 308 this.sidebarPanes[pane].needsUpdate = true; 309 310 this.updateStyles(true); 311 this.updateMetrics(); 312 this.updatePlatformFonts(); 313 this.updateProperties(); 314 this.updateEventListeners(); 315 }, 316 317 _reset: function() 318 { 319 delete this.currentQuery; 320 }, 321 322 /** 323 * @param {!WebInspector.Event} event 324 */ 325 _documentUpdatedEvent: function(event) 326 { 327 this._documentUpdated(/** @type {!WebInspector.DOMModel} */ (event.target), /** @type {?WebInspector.DOMDocument} */ (event.data)); 328 }, 329 330 /** 331 * @param {!WebInspector.DOMModel} domModel 332 * @param {?WebInspector.DOMDocument} inspectedRootDocument 333 */ 334 _documentUpdated: function(domModel, inspectedRootDocument) 335 { 336 this._reset(); 337 this.searchCanceled(); 338 339 var treeOutline = this._targetToTreeOutline.get(domModel.target()); 340 treeOutline.rootDOMNode = inspectedRootDocument; 341 342 if (!inspectedRootDocument) { 343 if (this.isShowing()) 344 domModel.requestDocument(); 345 return; 346 } 347 348 WebInspector.domBreakpointsSidebarPane.restoreBreakpoints(domModel.target()); 349 350 /** 351 * @this {WebInspector.ElementsPanel} 352 * @param {?WebInspector.DOMNode} candidateFocusNode 353 */ 354 function selectNode(candidateFocusNode) 355 { 356 if (!candidateFocusNode) 357 candidateFocusNode = inspectedRootDocument.body || inspectedRootDocument.documentElement; 358 359 if (!candidateFocusNode) 360 return; 361 362 this.selectDOMNode(candidateFocusNode); 363 if (treeOutline.selectedTreeElement) 364 treeOutline.selectedTreeElement.expand(); 365 } 366 367 /** 368 * @param {?DOMAgent.NodeId} nodeId 369 * @this {WebInspector.ElementsPanel} 370 */ 371 function selectLastSelectedNode(nodeId) 372 { 373 if (this.selectedDOMNode()) { 374 // Focused node has been explicitly set while reaching out for the last selected node. 375 return; 376 } 377 var node = nodeId ? domModel.nodeForId(nodeId) : null; 378 selectNode.call(this, node); 379 } 380 381 if (this._omitDefaultSelection) 382 return; 383 384 if (this._selectedPathOnReset) 385 domModel.pushNodeByPathToFrontend(this._selectedPathOnReset, selectLastSelectedNode.bind(this)); 386 else 387 selectNode.call(this, null); 388 delete this._selectedPathOnReset; 389 }, 390 391 searchCanceled: function() 392 { 393 delete this._searchQuery; 394 this._hideSearchHighlights(); 395 396 this._searchableView.updateSearchMatchesCount(0); 397 398 delete this._currentSearchResultIndex; 399 delete this._searchResults; 400 401 var targets = WebInspector.targetManager.targets(); 402 for (var i = 0; i < targets.length; ++i) 403 targets[i].domModel.cancelSearch(); 404 }, 405 406 /** 407 * @param {string} query 408 * @param {boolean} shouldJump 409 * @param {boolean=} jumpBackwards 410 */ 411 performSearch: function(query, shouldJump, jumpBackwards) 412 { 413 // Call searchCanceled since it will reset everything we need before doing a new search. 414 this.searchCanceled(); 415 416 const whitespaceTrimmedQuery = query.trim(); 417 if (!whitespaceTrimmedQuery.length) 418 return; 419 420 this._searchQuery = query; 421 422 var targets = WebInspector.targetManager.targets(); 423 var promises = []; 424 for (var i = 0; i < targets.length; ++i) 425 promises.push(targets[i].domModel.performSearchPromise(whitespaceTrimmedQuery, WebInspector.settings.showUAShadowDOM.get())); 426 Promise.all(promises).then(resultCountCallback.bind(this)); 427 428 /** 429 * @param {!Array.<number>} resultCounts 430 * @this {WebInspector.ElementsPanel} 431 */ 432 function resultCountCallback(resultCounts) 433 { 434 /** 435 * @type {!Array.<{target: !WebInspector.Target, index: number, node: (?WebInspector.DOMNode|undefined)}>} 436 */ 437 this._searchResults = []; 438 for (var i = 0; i < resultCounts.length; ++i) { 439 var resultCount = resultCounts[i]; 440 for (var j = 0; j < resultCount; ++j) 441 this._searchResults.push({target: targets[i], index: j, node: undefined}); 442 } 443 this._searchableView.updateSearchMatchesCount(this._searchResults.length); 444 if (!this._searchResults.length) 445 return; 446 this._currentSearchResultIndex = -1; 447 448 if (shouldJump) 449 this._jumpToSearchResult(jumpBackwards ? -1 : 0); 450 } 451 }, 452 453 _domWordWrapSettingChanged: function(event) 454 { 455 if (event.data) 456 this.contentElement.classList.remove("nowrap"); 457 else 458 this.contentElement.classList.add("nowrap"); 459 460 var selectedNode = this.selectedDOMNode(); 461 if (!selectedNode) 462 return; 463 464 var treeElement = this._treeElementForNode(selectedNode); 465 if (treeElement) 466 treeElement.updateSelection(); // Recalculate selection highlight dimensions. 467 }, 468 469 switchToAndFocus: function(node) 470 { 471 // Reset search restore. 472 this._searchableView.cancelSearch(); 473 WebInspector.inspectorView.setCurrentPanel(this); 474 this.selectDOMNode(node, true); 475 }, 476 477 /** 478 * @param {!Element} element 479 * @param {!Event} event 480 * @return {!Element|!AnchorBox|undefined} 481 */ 482 _getPopoverAnchor: function(element, event) 483 { 484 var anchor = element.enclosingNodeOrSelfWithClass("webkit-html-resource-link"); 485 if (!anchor || !anchor.href) 486 return; 487 488 var treeOutlineElement = anchor.enclosingNodeOrSelfWithClass("elements-tree-outline"); 489 if (!treeOutlineElement) 490 return; 491 492 for (var i = 0; i < this._treeOutlines.length; ++i) { 493 if (this._treeOutlines[i].element !== treeOutlineElement) 494 continue; 495 496 var resource = this._treeOutlines[i].target().resourceTreeModel.resourceForURL(anchor.href); 497 if (!resource || resource.type !== WebInspector.resourceTypes.Image) 498 return; 499 anchor.removeAttribute("title"); 500 return anchor; 501 } 502 }, 503 504 /** 505 * @param {!WebInspector.DOMNode} node 506 * @param {function()} callback 507 */ 508 _loadDimensionsForNode: function(node, callback) 509 { 510 if (!node.nodeName() || node.nodeName().toLowerCase() !== "img") { 511 callback(); 512 return; 513 } 514 515 node.resolveToObject("", resolvedNode); 516 517 function resolvedNode(object) 518 { 519 if (!object) { 520 callback(); 521 return; 522 } 523 524 object.callFunctionJSON(dimensions, undefined, callback); 525 object.release(); 526 527 /** 528 * @return {!{offsetWidth: number, offsetHeight: number, naturalWidth: number, naturalHeight: number}} 529 * @suppressReceiverCheck 530 * @this {!Element} 531 */ 532 function dimensions() 533 { 534 return { offsetWidth: this.offsetWidth, offsetHeight: this.offsetHeight, naturalWidth: this.naturalWidth, naturalHeight: this.naturalHeight }; 535 } 536 } 537 }, 538 539 /** 540 * @param {!Element} anchor 541 * @param {!WebInspector.Popover} popover 542 */ 543 _showPopover: function(anchor, popover) 544 { 545 var listItem = anchor.enclosingNodeOrSelfWithNodeName("li"); 546 // We get here for CSS properties, too. 547 if (listItem && listItem.treeElement && listItem.treeElement.treeOutline instanceof WebInspector.ElementsTreeOutline) { 548 var node = /** @type {!WebInspector.DOMNode} */ (listItem.treeElement.representedObject); 549 this._loadDimensionsForNode(node, WebInspector.DOMPresentationUtils.buildImagePreviewContents.bind(WebInspector.DOMPresentationUtils, node.target(), anchor.href, true, showPopover)); 550 } else { 551 var node = this.selectedDOMNode(); 552 if (node) 553 WebInspector.DOMPresentationUtils.buildImagePreviewContents(node.target(), anchor.href, true, showPopover); 554 } 555 556 /** 557 * @param {!Element=} contents 558 */ 559 function showPopover(contents) 560 { 561 if (!contents) 562 return; 563 popover.setCanShrink(false); 564 popover.show(contents, anchor); 565 } 566 }, 567 568 _jumpToSearchResult: function(index) 569 { 570 this._hideSearchHighlights(); 571 this._currentSearchResultIndex = (index + this._searchResults.length) % this._searchResults.length; 572 this._highlightCurrentSearchResult(); 573 }, 574 575 jumpToNextSearchResult: function() 576 { 577 if (!this._searchResults) 578 return; 579 this._jumpToSearchResult(this._currentSearchResultIndex + 1); 580 }, 581 582 jumpToPreviousSearchResult: function() 583 { 584 if (!this._searchResults) 585 return; 586 this._jumpToSearchResult(this._currentSearchResultIndex - 1); 587 }, 588 589 _highlightCurrentSearchResult: function() 590 { 591 var index = this._currentSearchResultIndex; 592 var searchResults = this._searchResults; 593 var searchResult = searchResults[index]; 594 595 if (searchResult.node === null) { 596 this._searchableView.updateCurrentMatchIndex(index); 597 return; 598 } 599 600 /** 601 * @param {?WebInspector.DOMNode} node 602 * @this {WebInspector.ElementsPanel} 603 */ 604 function searchCallback(node) 605 { 606 searchResult.node = node; 607 this._highlightCurrentSearchResult(); 608 } 609 610 if (typeof searchResult.node === "undefined") { 611 // No data for slot, request it. 612 searchResult.target.domModel.searchResult(searchResult.index, searchCallback.bind(this)); 613 return; 614 } 615 616 this._searchableView.updateCurrentMatchIndex(index); 617 618 var treeElement = this._treeElementForNode(searchResult.node); 619 if (treeElement) { 620 treeElement.highlightSearchResults(this._searchQuery); 621 treeElement.reveal(); 622 var matches = treeElement.listItemElement.getElementsByClassName("highlighted-search-result"); 623 if (matches.length) 624 matches[0].scrollIntoViewIfNeeded(); 625 } 626 }, 627 628 _hideSearchHighlights: function() 629 { 630 if (!this._searchResults || !this._searchResults.length || this._currentSearchResultIndex < 0) 631 return; 632 var searchResult = this._searchResults[this._currentSearchResultIndex]; 633 if (!searchResult.node) 634 return; 635 var treeOutline = this._targetToTreeOutline.get(searchResult.target); 636 var treeElement = treeOutline.findTreeElement(searchResult.node); 637 if (treeElement) 638 treeElement.hideSearchHighlights(); 639 }, 640 641 /** 642 * @return {?WebInspector.DOMNode} 643 */ 644 selectedDOMNode: function() 645 { 646 for (var i = 0; i < this._treeOutlines.length; ++i) { 647 var treeOutline = this._treeOutlines[i]; 648 if (treeOutline.selectedDOMNode()) 649 return treeOutline.selectedDOMNode(); 650 } 651 return null; 652 }, 653 654 /** 655 * @param {!WebInspector.DOMNode} node 656 * @param {boolean=} focus 657 */ 658 selectDOMNode: function(node, focus) 659 { 660 for (var i = 0; i < this._treeOutlines.length; ++i) { 661 var treeOutline = this._treeOutlines[i]; 662 if (treeOutline.target() === node.target()) 663 treeOutline.selectDOMNode(node, focus); 664 else 665 treeOutline.selectDOMNode(null); 666 } 667 }, 668 669 /** 670 * @param {!WebInspector.Event} event 671 */ 672 _updateBreadcrumbIfNeeded: function(event) 673 { 674 var nodes = /** @type {!Array.<!WebInspector.DOMNode>} */ (event.data || []); 675 if (!nodes.length) 676 return; 677 678 var crumbs = this.crumbsElement; 679 for (var crumb = crumbs.firstChild; crumb; crumb = crumb.nextSibling) { 680 if (nodes.indexOf(crumb.representedObject) !== -1) { 681 this.updateBreadcrumb(true); 682 return; 683 } 684 } 685 }, 686 687 _stylesPaneEdited: function() 688 { 689 // Once styles are edited, the Metrics pane should be updated. 690 this.sidebarPanes.metrics.needsUpdate = true; 691 this.updateMetrics(); 692 this.sidebarPanes.platformFonts.needsUpdate = true; 693 this.updatePlatformFonts(); 694 }, 695 696 _metricsPaneEdited: function() 697 { 698 // Once metrics are edited, the Styles pane should be updated. 699 this.sidebarPanes.styles.needsUpdate = true; 700 this.updateStyles(true); 701 }, 702 703 _mouseMovedInCrumbs: function(event) 704 { 705 var nodeUnderMouse = document.elementFromPoint(event.pageX, event.pageY); 706 var crumbElement = nodeUnderMouse.enclosingNodeOrSelfWithClass("crumb"); 707 var node = /** @type {?WebInspector.DOMNode} */ (crumbElement ? crumbElement.representedObject : null); 708 if (node) 709 node.highlight(); 710 }, 711 712 _mouseMovedOutOfCrumbs: function(event) 713 { 714 var nodeUnderMouse = document.elementFromPoint(event.pageX, event.pageY); 715 if (nodeUnderMouse && nodeUnderMouse.isDescendant(this.crumbsElement)) 716 return; 717 718 for (var i = 0; i < this._treeOutlines.length; ++i) 719 this._treeOutlines[i].domModel().hideDOMNodeHighlight(); 720 }, 721 722 /** 723 * @param {boolean=} forceUpdate 724 */ 725 updateBreadcrumb: function(forceUpdate) 726 { 727 if (!this.isShowing()) 728 return; 729 730 var crumbs = this.crumbsElement; 731 732 var handled = false; 733 var crumb = crumbs.firstChild; 734 while (crumb) { 735 if (crumb.representedObject === this.selectedDOMNode()) { 736 crumb.classList.add("selected"); 737 handled = true; 738 } else { 739 crumb.classList.remove("selected"); 740 } 741 742 crumb = crumb.nextSibling; 743 } 744 745 if (handled && !forceUpdate) { 746 // We don't need to rebuild the crumbs, but we need to adjust sizes 747 // to reflect the new focused or root node. 748 this.updateBreadcrumbSizes(); 749 return; 750 } 751 752 crumbs.removeChildren(); 753 754 var panel = this; 755 756 function selectCrumbFunction(event) 757 { 758 var crumb = event.currentTarget; 759 if (crumb.classList.contains("collapsed")) { 760 // Clicking a collapsed crumb will expose the hidden crumbs. 761 if (crumb === panel.crumbsElement.firstChild) { 762 // If the focused crumb is the first child, pick the farthest crumb 763 // that is still hidden. This allows the user to expose every crumb. 764 var currentCrumb = crumb; 765 while (currentCrumb) { 766 var hidden = currentCrumb.classList.contains("hidden"); 767 var collapsed = currentCrumb.classList.contains("collapsed"); 768 if (!hidden && !collapsed) 769 break; 770 crumb = currentCrumb; 771 currentCrumb = currentCrumb.nextSibling; 772 } 773 } 774 775 panel.updateBreadcrumbSizes(crumb); 776 } else 777 panel.selectDOMNode(crumb.representedObject, true); 778 779 event.preventDefault(); 780 } 781 782 for (var current = this.selectedDOMNode(); current; current = current.parentNode) { 783 if (current.nodeType() === Node.DOCUMENT_NODE) 784 continue; 785 786 crumb = document.createElement("span"); 787 crumb.className = "crumb"; 788 crumb.representedObject = current; 789 crumb.addEventListener("mousedown", selectCrumbFunction, false); 790 791 var crumbTitle = ""; 792 switch (current.nodeType()) { 793 case Node.ELEMENT_NODE: 794 if (current.pseudoType()) 795 crumbTitle = "::" + current.pseudoType(); 796 else 797 WebInspector.DOMPresentationUtils.decorateNodeLabel(current, crumb); 798 break; 799 800 case Node.TEXT_NODE: 801 crumbTitle = WebInspector.UIString("(text)"); 802 break; 803 804 case Node.COMMENT_NODE: 805 crumbTitle = "<!-->"; 806 break; 807 808 case Node.DOCUMENT_TYPE_NODE: 809 crumbTitle = "<!DOCTYPE>"; 810 break; 811 812 case Node.DOCUMENT_FRAGMENT_NODE: 813 crumbTitle = current.shadowRootType() ? "#shadow-root" : current.nodeNameInCorrectCase(); 814 break; 815 816 default: 817 crumbTitle = current.nodeNameInCorrectCase(); 818 } 819 820 if (!crumb.childNodes.length) { 821 var nameElement = document.createElement("span"); 822 nameElement.textContent = crumbTitle; 823 crumb.appendChild(nameElement); 824 crumb.title = crumbTitle; 825 } 826 827 if (current === this.selectedDOMNode()) 828 crumb.classList.add("selected"); 829 crumbs.insertBefore(crumb, crumbs.firstChild); 830 } 831 832 this.updateBreadcrumbSizes(); 833 }, 834 835 /** 836 * @param {!Element=} focusedCrumb 837 */ 838 updateBreadcrumbSizes: function(focusedCrumb) 839 { 840 if (!this.isShowing()) 841 return; 842 843 var crumbs = this.crumbsElement; 844 if (!crumbs.firstChild) 845 return; 846 847 var selectedIndex = 0; 848 var focusedIndex = 0; 849 var selectedCrumb; 850 851 // Reset crumb styles. 852 for (var i = 0; i < crumbs.childNodes.length; ++i) { 853 var crumb = crumbs.childNodes[i]; 854 // Find the selected crumb and index. 855 if (!selectedCrumb && crumb.classList.contains("selected")) { 856 selectedCrumb = crumb; 857 selectedIndex = i; 858 } 859 860 // Find the focused crumb index. 861 if (crumb === focusedCrumb) 862 focusedIndex = i; 863 864 crumb.classList.remove("compact", "collapsed", "hidden"); 865 } 866 867 // Layout 1: Measure total and normal crumb sizes 868 var contentElementWidth = this.contentElement.offsetWidth; 869 var normalSizes = []; 870 for (var i = 0; i < crumbs.childNodes.length; ++i) { 871 var crumb = crumbs.childNodes[i]; 872 normalSizes[i] = crumb.offsetWidth; 873 } 874 875 // Layout 2: Measure collapsed crumb sizes 876 var compactSizes = []; 877 for (var i = 0; i < crumbs.childNodes.length; ++i) { 878 var crumb = crumbs.childNodes[i]; 879 crumb.classList.add("compact"); 880 } 881 for (var i = 0; i < crumbs.childNodes.length; ++i) { 882 var crumb = crumbs.childNodes[i]; 883 compactSizes[i] = crumb.offsetWidth; 884 } 885 886 // Layout 3: Measure collapsed crumb size 887 crumbs.firstChild.classList.add("collapsed"); 888 var collapsedSize = crumbs.firstChild.offsetWidth; 889 890 // Clean up. 891 for (var i = 0; i < crumbs.childNodes.length; ++i) { 892 var crumb = crumbs.childNodes[i]; 893 crumb.classList.remove("compact", "collapsed"); 894 } 895 896 function crumbsAreSmallerThanContainer() 897 { 898 var totalSize = 0; 899 for (var i = 0; i < crumbs.childNodes.length; ++i) { 900 var crumb = crumbs.childNodes[i]; 901 if (crumb.classList.contains("hidden")) 902 continue; 903 if (crumb.classList.contains("collapsed")) { 904 totalSize += collapsedSize; 905 continue; 906 } 907 totalSize += crumb.classList.contains("compact") ? compactSizes[i] : normalSizes[i]; 908 } 909 const rightPadding = 10; 910 return totalSize + rightPadding < contentElementWidth; 911 } 912 913 if (crumbsAreSmallerThanContainer()) 914 return; // No need to compact the crumbs, they all fit at full size. 915 916 var BothSides = 0; 917 var AncestorSide = -1; 918 var ChildSide = 1; 919 920 /** 921 * @param {function(!Element)} shrinkingFunction 922 * @param {number} direction 923 */ 924 function makeCrumbsSmaller(shrinkingFunction, direction) 925 { 926 var significantCrumb = focusedCrumb || selectedCrumb; 927 var significantIndex = significantCrumb === selectedCrumb ? selectedIndex : focusedIndex; 928 929 function shrinkCrumbAtIndex(index) 930 { 931 var shrinkCrumb = crumbs.childNodes[index]; 932 if (shrinkCrumb && shrinkCrumb !== significantCrumb) 933 shrinkingFunction(shrinkCrumb); 934 if (crumbsAreSmallerThanContainer()) 935 return true; // No need to compact the crumbs more. 936 return false; 937 } 938 939 // Shrink crumbs one at a time by applying the shrinkingFunction until the crumbs 940 // fit in the container or we run out of crumbs to shrink. 941 if (direction) { 942 // Crumbs are shrunk on only one side (based on direction) of the signifcant crumb. 943 var index = (direction > 0 ? 0 : crumbs.childNodes.length - 1); 944 while (index !== significantIndex) { 945 if (shrinkCrumbAtIndex(index)) 946 return true; 947 index += (direction > 0 ? 1 : -1); 948 } 949 } else { 950 // Crumbs are shrunk in order of descending distance from the signifcant crumb, 951 // with a tie going to child crumbs. 952 var startIndex = 0; 953 var endIndex = crumbs.childNodes.length - 1; 954 while (startIndex != significantIndex || endIndex != significantIndex) { 955 var startDistance = significantIndex - startIndex; 956 var endDistance = endIndex - significantIndex; 957 if (startDistance >= endDistance) 958 var index = startIndex++; 959 else 960 var index = endIndex--; 961 if (shrinkCrumbAtIndex(index)) 962 return true; 963 } 964 } 965 966 // We are not small enough yet, return false so the caller knows. 967 return false; 968 } 969 970 function coalesceCollapsedCrumbs() 971 { 972 var crumb = crumbs.firstChild; 973 var collapsedRun = false; 974 var newStartNeeded = false; 975 var newEndNeeded = false; 976 while (crumb) { 977 var hidden = crumb.classList.contains("hidden"); 978 if (!hidden) { 979 var collapsed = crumb.classList.contains("collapsed"); 980 if (collapsedRun && collapsed) { 981 crumb.classList.add("hidden"); 982 crumb.classList.remove("compact"); 983 crumb.classList.remove("collapsed"); 984 985 if (crumb.classList.contains("start")) { 986 crumb.classList.remove("start"); 987 newStartNeeded = true; 988 } 989 990 if (crumb.classList.contains("end")) { 991 crumb.classList.remove("end"); 992 newEndNeeded = true; 993 } 994 995 continue; 996 } 997 998 collapsedRun = collapsed; 999 1000 if (newEndNeeded) { 1001 newEndNeeded = false; 1002 crumb.classList.add("end"); 1003 } 1004 } else 1005 collapsedRun = true; 1006 crumb = crumb.nextSibling; 1007 } 1008 1009 if (newStartNeeded) { 1010 crumb = crumbs.lastChild; 1011 while (crumb) { 1012 if (!crumb.classList.contains("hidden")) { 1013 crumb.classList.add("start"); 1014 break; 1015 } 1016 crumb = crumb.previousSibling; 1017 } 1018 } 1019 } 1020 1021 /** 1022 * @param {!Element} crumb 1023 */ 1024 function compact(crumb) 1025 { 1026 if (crumb.classList.contains("hidden")) 1027 return; 1028 crumb.classList.add("compact"); 1029 } 1030 1031 /** 1032 * @param {!Element} crumb 1033 * @param {boolean=} dontCoalesce 1034 */ 1035 function collapse(crumb, dontCoalesce) 1036 { 1037 if (crumb.classList.contains("hidden")) 1038 return; 1039 crumb.classList.add("collapsed"); 1040 crumb.classList.remove("compact"); 1041 if (!dontCoalesce) 1042 coalesceCollapsedCrumbs(); 1043 } 1044 1045 if (!focusedCrumb) { 1046 // When not focused on a crumb we can be biased and collapse less important 1047 // crumbs that the user might not care much about. 1048 1049 // Compact child crumbs. 1050 if (makeCrumbsSmaller(compact, ChildSide)) 1051 return; 1052 1053 // Collapse child crumbs. 1054 if (makeCrumbsSmaller(collapse, ChildSide)) 1055 return; 1056 } 1057 1058 // Compact ancestor crumbs, or from both sides if focused. 1059 if (makeCrumbsSmaller(compact, focusedCrumb ? BothSides : AncestorSide)) 1060 return; 1061 1062 // Collapse ancestor crumbs, or from both sides if focused. 1063 if (makeCrumbsSmaller(collapse, focusedCrumb ? BothSides : AncestorSide)) 1064 return; 1065 1066 if (!selectedCrumb) 1067 return; 1068 1069 // Compact the selected crumb. 1070 compact(selectedCrumb); 1071 if (crumbsAreSmallerThanContainer()) 1072 return; 1073 1074 // Collapse the selected crumb as a last resort. Pass true to prevent coalescing. 1075 collapse(selectedCrumb, true); 1076 }, 1077 1078 /** 1079 * @return {boolean} 1080 */ 1081 _cssModelEnabledForSelectedNode: function() 1082 { 1083 if (!this.selectedDOMNode()) 1084 return true; 1085 return this.selectedDOMNode().target().cssModel.isEnabled(); 1086 }, 1087 1088 /** 1089 * @param {boolean=} forceUpdate 1090 */ 1091 updateStyles: function(forceUpdate) 1092 { 1093 if (!this._cssModelEnabledForSelectedNode()) 1094 return; 1095 var stylesSidebarPane = this.sidebarPanes.styles; 1096 var computedStylePane = this.sidebarPanes.computedStyle; 1097 if ((!stylesSidebarPane.isShowing() && !computedStylePane.isShowing()) || !stylesSidebarPane.needsUpdate) 1098 return; 1099 1100 stylesSidebarPane.update(this.selectedDOMNode(), forceUpdate); 1101 stylesSidebarPane.needsUpdate = false; 1102 }, 1103 1104 updateMetrics: function() 1105 { 1106 if (!this._cssModelEnabledForSelectedNode()) 1107 return; 1108 var metricsSidebarPane = this.sidebarPanes.metrics; 1109 if (!metricsSidebarPane.isShowing() || !metricsSidebarPane.needsUpdate) 1110 return; 1111 1112 metricsSidebarPane.update(this.selectedDOMNode()); 1113 metricsSidebarPane.needsUpdate = false; 1114 }, 1115 1116 updatePlatformFonts: function() 1117 { 1118 if (!this._cssModelEnabledForSelectedNode()) 1119 return; 1120 var platformFontsSidebar = this.sidebarPanes.platformFonts; 1121 if (!platformFontsSidebar.isShowing() || !platformFontsSidebar.needsUpdate) 1122 return; 1123 1124 platformFontsSidebar.update(this.selectedDOMNode()); 1125 platformFontsSidebar.needsUpdate = false; 1126 }, 1127 1128 updateProperties: function() 1129 { 1130 var propertiesSidebarPane = this.sidebarPanes.properties; 1131 if (!propertiesSidebarPane.isShowing() || !propertiesSidebarPane.needsUpdate) 1132 return; 1133 1134 propertiesSidebarPane.update(this.selectedDOMNode()); 1135 propertiesSidebarPane.needsUpdate = false; 1136 }, 1137 1138 updateEventListeners: function() 1139 { 1140 var eventListenersSidebarPane = this.sidebarPanes.eventListeners; 1141 if (!eventListenersSidebarPane.isShowing() || !eventListenersSidebarPane.needsUpdate) 1142 return; 1143 1144 eventListenersSidebarPane.update(this.selectedDOMNode()); 1145 eventListenersSidebarPane.needsUpdate = false; 1146 }, 1147 1148 /** 1149 * @param {!KeyboardEvent} event 1150 */ 1151 handleShortcut: function(event) 1152 { 1153 /** 1154 * @param {!WebInspector.ElementsTreeOutline} treeOutline 1155 * @this {WebInspector.ElementsPanel} 1156 */ 1157 function handleUndoRedo(treeOutline) 1158 { 1159 if (WebInspector.KeyboardShortcut.eventHasCtrlOrMeta(event) && !event.shiftKey && event.keyIdentifier === "U+005A") { // Z key 1160 treeOutline.target().domModel.undo(this._updateSidebars.bind(this)); 1161 event.handled = true; 1162 return; 1163 } 1164 1165 var isRedoKey = WebInspector.isMac() ? event.metaKey && event.shiftKey && event.keyIdentifier === "U+005A" : // Z key 1166 event.ctrlKey && event.keyIdentifier === "U+0059"; // Y key 1167 if (isRedoKey) { 1168 treeOutline.target().domModel.redo(this._updateSidebars.bind(this)); 1169 event.handled = true; 1170 } 1171 } 1172 1173 var treeOutline = null; 1174 for (var i = 0; i < this._treeOutlines.length; ++i) { 1175 if (this._treeOutlines[i].selectedDOMNode() === this._lastValidSelectedNode) 1176 treeOutline = this._treeOutlines[i]; 1177 } 1178 if (!treeOutline) 1179 return; 1180 1181 if (!treeOutline.editing()) { 1182 handleUndoRedo.call(this, treeOutline); 1183 if (event.handled) 1184 return; 1185 } 1186 1187 treeOutline.handleShortcut(event); 1188 }, 1189 1190 /** 1191 * @param {?WebInspector.DOMNode} node 1192 * @return {?WebInspector.ElementsTreeOutline} 1193 */ 1194 _treeOutlineForNode: function(node) 1195 { 1196 if (!node) 1197 return null; 1198 return this._targetToTreeOutline.get(node.target()) || null; 1199 }, 1200 1201 /** 1202 * @param {!WebInspector.DOMNode} node 1203 * @return {?WebInspector.ElementsTreeElement} 1204 */ 1205 _treeElementForNode: function(node) 1206 { 1207 var treeOutline = this._treeOutlineForNode(node); 1208 return /** @type {?WebInspector.ElementsTreeElement} */ (treeOutline.findTreeElement(node)); 1209 }, 1210 1211 /** 1212 * @param {!Event} event 1213 */ 1214 handleCopyEvent: function(event) 1215 { 1216 if (!WebInspector.currentFocusElement().enclosingNodeOrSelfWithClass("elements-tree-outline")) 1217 return; 1218 var treeOutline = this._treeOutlineForNode(this.selectedDOMNode()); 1219 if (treeOutline) 1220 treeOutline.handleCopyOrCutKeyboardEvent(false, event); 1221 }, 1222 1223 /** 1224 * @param {!Event} event 1225 */ 1226 handleCutEvent: function(event) 1227 { 1228 var treeOutline = this._treeOutlineForNode(this.selectedDOMNode()); 1229 if (treeOutline) 1230 treeOutline.handleCopyOrCutKeyboardEvent(true, event); 1231 }, 1232 1233 /** 1234 * @param {!Event} event 1235 */ 1236 handlePasteEvent: function(event) 1237 { 1238 var treeOutline = this._treeOutlineForNode(this.selectedDOMNode()); 1239 if (treeOutline) 1240 treeOutline.handlePasteKeyboardEvent(event); 1241 }, 1242 1243 /** 1244 * @param {!WebInspector.DOMNode} node 1245 * @return {!WebInspector.DOMNode} 1246 */ 1247 _leaveUserAgentShadowDOM: function(node) 1248 { 1249 var userAgentShadowRoot = node.ancestorUserAgentShadowRoot(); 1250 return userAgentShadowRoot ? /** @type {!WebInspector.DOMNode} */ (userAgentShadowRoot.parentNode) : node; 1251 }, 1252 1253 /** 1254 * @param {!WebInspector.DOMNode} node 1255 */ 1256 revealAndSelectNode: function(node) 1257 { 1258 WebInspector.inspectorView.setCurrentPanel(this); 1259 node = WebInspector.settings.showUAShadowDOM.get() ? node : this._leaveUserAgentShadowDOM(node); 1260 node.highlightForTwoSeconds(); 1261 this.selectDOMNode(node, true); 1262 }, 1263 1264 /** 1265 * @param {!Event} event 1266 * @param {!WebInspector.ContextMenu} contextMenu 1267 * @param {!Object} object 1268 */ 1269 appendApplicableItems: function(event, contextMenu, object) 1270 { 1271 if (!(object instanceof WebInspector.RemoteObject && (/** @type {!WebInspector.RemoteObject} */ (object)).isNode()) 1272 && !(object instanceof WebInspector.DOMNode) 1273 && !(object instanceof WebInspector.DeferredDOMNode)) { 1274 return; 1275 } 1276 1277 // Add debbuging-related actions 1278 if (object instanceof WebInspector.DOMNode) { 1279 contextMenu.appendSeparator(); 1280 WebInspector.domBreakpointsSidebarPane.populateNodeContextMenu(object, contextMenu); 1281 } 1282 1283 // Skip adding "Reveal..." menu item for our own tree outline. 1284 if (this.element.isAncestor(/** @type {!Node} */ (event.target))) 1285 return; 1286 var commandCallback = WebInspector.Revealer.reveal.bind(WebInspector.Revealer, object); 1287 1288 contextMenu.appendItem(WebInspector.useLowerCaseMenuTitles() ? "Reveal in Elements panel" : "Reveal in Elements Panel", commandCallback); 1289 }, 1290 1291 _sidebarContextMenuEventFired: function(event) 1292 { 1293 var contextMenu = new WebInspector.ContextMenu(event); 1294 contextMenu.show(); 1295 }, 1296 1297 _dockSideChanged: function() 1298 { 1299 var vertically = WebInspector.dockController.isVertical() && WebInspector.settings.splitVerticallyWhenDockedToRight.get(); 1300 this._splitVertically(vertically); 1301 }, 1302 1303 _showUAShadowDOMChanged: function() 1304 { 1305 for (var i = 0; i < this._treeOutlines.length; ++i) 1306 this._treeOutlines[i].update(); 1307 }, 1308 1309 /** 1310 * @param {boolean} vertically 1311 */ 1312 _splitVertically: function(vertically) 1313 { 1314 if (this.sidebarPaneView && vertically === !this._splitView.isVertical()) 1315 return; 1316 1317 if (this.sidebarPaneView) { 1318 this.sidebarPaneView.detach(); 1319 this._splitView.uninstallResizer(this.sidebarPaneView.headerElement()); 1320 } 1321 1322 this._splitView.setVertical(!vertically); 1323 1324 var computedPane = new WebInspector.SidebarPane(WebInspector.UIString("Computed")); 1325 computedPane.element.classList.add("composite"); 1326 computedPane.element.classList.add("fill"); 1327 var expandComputed = computedPane.expand.bind(computedPane); 1328 1329 computedPane.bodyElement.classList.add("metrics-and-computed"); 1330 this.sidebarPanes.computedStyle.setExpandCallback(expandComputed); 1331 1332 var matchedStylePanesWrapper = document.createElement("div"); 1333 matchedStylePanesWrapper.className = "style-panes-wrapper"; 1334 var computedStylePanesWrapper = document.createElement("div"); 1335 computedStylePanesWrapper.className = "style-panes-wrapper"; 1336 1337 /** 1338 * @param {boolean} inComputedStyle 1339 * @this {WebInspector.ElementsPanel} 1340 */ 1341 function showMetrics(inComputedStyle) 1342 { 1343 if (inComputedStyle) 1344 this.sidebarPanes.metrics.show(computedStylePanesWrapper, this.sidebarPanes.computedStyle.element); 1345 else 1346 this.sidebarPanes.metrics.show(matchedStylePanesWrapper); 1347 } 1348 1349 /** 1350 * @param {!WebInspector.Event} event 1351 * @this {WebInspector.ElementsPanel} 1352 */ 1353 function tabSelected(event) 1354 { 1355 var tabId = /** @type {string} */ (event.data.tabId); 1356 if (tabId === computedPane.title()) 1357 showMetrics.call(this, true); 1358 else if (tabId === stylesPane.title()) 1359 showMetrics.call(this, false); 1360 } 1361 1362 this.sidebarPaneView = new WebInspector.SidebarTabbedPane(); 1363 1364 if (vertically) { 1365 this._splitView.installResizer(this.sidebarPaneView.headerElement()); 1366 this.sidebarPanes.metrics.setExpandCallback(expandComputed); 1367 1368 var compositePane = new WebInspector.SidebarPane(this.sidebarPanes.styles.title()); 1369 compositePane.element.classList.add("composite"); 1370 compositePane.element.classList.add("fill"); 1371 var expandComposite = compositePane.expand.bind(compositePane); 1372 1373 var splitView = new WebInspector.SplitView(true, true, "stylesPaneSplitViewState", 0.5); 1374 splitView.show(compositePane.bodyElement); 1375 1376 splitView.mainElement().appendChild(matchedStylePanesWrapper); 1377 splitView.sidebarElement().appendChild(computedStylePanesWrapper); 1378 1379 this.sidebarPanes.styles.setExpandCallback(expandComposite); 1380 1381 computedPane.show(computedStylePanesWrapper); 1382 computedPane.setExpandCallback(expandComposite); 1383 1384 splitView.mainElement().appendChild(this._matchedStylesFilterBoxContainer); 1385 splitView.sidebarElement().appendChild(this._computedStylesFilterBoxContainer); 1386 1387 this.sidebarPaneView.addPane(compositePane); 1388 } else { 1389 var stylesPane = new WebInspector.SidebarPane(this.sidebarPanes.styles.title()); 1390 stylesPane.element.classList.add("composite"); 1391 stylesPane.element.classList.add("fill"); 1392 var expandStyles = stylesPane.expand.bind(stylesPane); 1393 stylesPane.bodyElement.classList.add("metrics-and-styles"); 1394 1395 stylesPane.bodyElement.appendChild(matchedStylePanesWrapper); 1396 computedPane.bodyElement.appendChild(computedStylePanesWrapper); 1397 1398 this.sidebarPanes.styles.setExpandCallback(expandStyles); 1399 this.sidebarPanes.metrics.setExpandCallback(expandStyles); 1400 1401 this.sidebarPaneView.addEventListener(WebInspector.TabbedPane.EventTypes.TabSelected, tabSelected, this); 1402 1403 stylesPane.bodyElement.appendChild(this._matchedStylesFilterBoxContainer); 1404 computedPane.bodyElement.appendChild(this._computedStylesFilterBoxContainer); 1405 1406 this.sidebarPaneView.addPane(stylesPane); 1407 this.sidebarPaneView.addPane(computedPane); 1408 } 1409 1410 this.sidebarPanes.styles.show(matchedStylePanesWrapper); 1411 this.sidebarPanes.computedStyle.show(computedStylePanesWrapper); 1412 matchedStylePanesWrapper.appendChild(this.sidebarPanes.styles.titleElement); 1413 showMetrics.call(this, vertically); 1414 this.sidebarPanes.platformFonts.show(computedStylePanesWrapper); 1415 1416 this.sidebarPaneView.addPane(this.sidebarPanes.eventListeners); 1417 this.sidebarPaneView.addPane(this.sidebarPanes.domBreakpoints); 1418 this.sidebarPaneView.addPane(this.sidebarPanes.properties); 1419 this._extensionSidebarPanesContainer = this.sidebarPaneView; 1420 1421 for (var i = 0; i < this._extensionSidebarPanes.length; ++i) 1422 this._extensionSidebarPanesContainer.addPane(this._extensionSidebarPanes[i]); 1423 1424 this.sidebarPaneView.show(this._splitView.sidebarElement()); 1425 this.sidebarPanes.styles.expand(); 1426 }, 1427 1428 /** 1429 * @param {string} id 1430 * @param {!WebInspector.SidebarPane} pane 1431 */ 1432 addExtensionSidebarPane: function(id, pane) 1433 { 1434 this._extensionSidebarPanes.push(pane); 1435 this._extensionSidebarPanesContainer.addPane(pane); 1436 }, 1437 1438 __proto__: WebInspector.Panel.prototype 1439} 1440 1441/** 1442 * @constructor 1443 * @implements {WebInspector.ContextMenu.Provider} 1444 */ 1445WebInspector.ElementsPanel.ContextMenuProvider = function() 1446{ 1447} 1448 1449WebInspector.ElementsPanel.ContextMenuProvider.prototype = { 1450 /** 1451 * @param {!Event} event 1452 * @param {!WebInspector.ContextMenu} contextMenu 1453 * @param {!Object} target 1454 */ 1455 appendApplicableItems: function(event, contextMenu, target) 1456 { 1457 /** @type {!WebInspector.ElementsPanel} */ (WebInspector.inspectorView.panel("elements")).appendApplicableItems(event, contextMenu, target); 1458 } 1459} 1460 1461/** 1462 * @constructor 1463 * @implements {WebInspector.Revealer} 1464 */ 1465WebInspector.ElementsPanel.DOMNodeRevealer = function() 1466{ 1467} 1468 1469WebInspector.ElementsPanel.DOMNodeRevealer.prototype = { 1470 /** 1471 * @param {!Object} node 1472 */ 1473 reveal: function(node) 1474 { 1475 if (WebInspector.inspectElementModeController && WebInspector.inspectElementModeController.enabled()) { 1476 InspectorFrontendHost.bringToFront(); 1477 WebInspector.inspectElementModeController.disable(); 1478 } 1479 1480 var panel = /** @type {!WebInspector.ElementsPanel} */ (WebInspector.inspectorView.panel("elements")); 1481 if (node instanceof WebInspector.DOMNode) 1482 panel.revealAndSelectNode(/** @type {!WebInspector.DOMNode} */ (node)); 1483 else if (node instanceof WebInspector.DeferredDOMNode) 1484 (/** @type {!WebInspector.DeferredDOMNode} */ (node)).resolve(onNodeResolved); 1485 1486 /** 1487 * @param {?WebInspector.DOMNode} resolvedNode 1488 */ 1489 function onNodeResolved(resolvedNode) 1490 { 1491 if (resolvedNode) 1492 panel.revealAndSelectNode(resolvedNode); 1493 } 1494 } 1495} 1496 1497/** 1498 * @constructor 1499 * @implements {WebInspector.Revealer} 1500 */ 1501WebInspector.ElementsPanel.NodeRemoteObjectRevealer = function() 1502{ 1503} 1504 1505WebInspector.ElementsPanel.NodeRemoteObjectRevealer.prototype = { 1506 /** 1507 * @param {!Object} remoteObject 1508 */ 1509 reveal: function(remoteObject) 1510 { 1511 revealElement(/** @type {!WebInspector.RemoteObject} */ (remoteObject)); 1512 1513 /** 1514 * @param {?WebInspector.RemoteObject} remoteObject 1515 */ 1516 function revealElement(remoteObject) 1517 { 1518 if (remoteObject) 1519 remoteObject.pushNodeToFrontend(selectNode.bind(null, remoteObject)); 1520 } 1521 1522 /** 1523 * @param {?WebInspector.RemoteObject} remoteObject 1524 * @param {?WebInspector.DOMNode} node 1525 */ 1526 function selectNode(remoteObject, node) 1527 { 1528 if (node) { 1529 WebInspector.Revealer.reveal(node); 1530 return; 1531 } 1532 if (!remoteObject || remoteObject.description !== "#text" || !remoteObject.isNode()) 1533 return; 1534 remoteObject.callFunction(parentElement, undefined, revealElement); 1535 } 1536 1537 /** 1538 * @suppressReceiverCheck 1539 * @this {Element} 1540 */ 1541 function parentElement() 1542 { 1543 return this.parentElement; 1544 } 1545 } 1546} 1547 1548/** 1549 * @constructor 1550 */ 1551WebInspector.ElementsPanel.NodeRemoteObjectInspector = function() 1552{ 1553} 1554 1555WebInspector.ElementsPanel.NodeRemoteObjectInspector.prototype = { 1556 /** 1557 * @param {!Object} object 1558 */ 1559 inspectNodeObject: function(object) 1560 { 1561 var remoteObject = /** @type {!WebInspector.RemoteObject} */ (object); 1562 if (!remoteObject.isNode()) { 1563 remoteObject.release(); 1564 return; 1565 } 1566 var elementsPanel = /** @type {!WebInspector.ElementsPanel} */ (WebInspector.inspectorView.panel("elements")); 1567 revealElement(remoteObject); 1568 1569 /** 1570 * @param {?WebInspector.RemoteObject} remoteObject 1571 */ 1572 function revealElement(remoteObject) 1573 { 1574 if (!remoteObject) 1575 return; 1576 remoteObject.pushNodeToFrontend(selectNode.bind(null, remoteObject)); 1577 elementsPanel.omitDefaultSelection(); 1578 WebInspector.inspectorView.setCurrentPanel(elementsPanel); 1579 } 1580 1581 /** 1582 * @param {!WebInspector.RemoteObject} remoteObject 1583 * @param {?WebInspector.DOMNode} node 1584 */ 1585 function selectNode(remoteObject, node) 1586 { 1587 elementsPanel.stopOmittingDefaultSelection(); 1588 if (node) { 1589 WebInspector.Revealer.reveal(node); 1590 if (!WebInspector._notFirstInspectElement && !WebInspector.inspectorView.drawerVisible()) 1591 InspectorFrontendHost.inspectElementCompleted(); 1592 WebInspector._notFirstInspectElement = true; 1593 remoteObject.release(); 1594 return; 1595 } 1596 if (remoteObject.description !== "#text" || !remoteObject.isNode()) { 1597 remoteObject.release(); 1598 return; 1599 } 1600 remoteObject.callFunction(parentElement, undefined, revealElement); 1601 } 1602 1603 /** 1604 * @suppressReceiverCheck 1605 * @this {Element} 1606 */ 1607 function parentElement() 1608 { 1609 return this.parentElement; 1610 } 1611 } 1612} 1613