1 /* 2 * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. 3 * Copyright (C) 2008 Matt Lilek <webkit (at) 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 importScript("CSSNamedFlowCollectionsView.js"); 32 importScript("CSSNamedFlowView.js"); 33 importScript("EventListenersSidebarPane.js"); 34 importScript("MetricsSidebarPane.js"); 35 importScript("OverridesView.js"); 36 importScript("PlatformFontsSidebarPane.js"); 37 importScript("PropertiesSidebarPane.js"); 38 importScript("RenderingOptionsView.js"); 39 importScript("StylesSidebarPane.js"); 40 41 /** 42 * @constructor 43 * @implements {WebInspector.ViewFactory} 44 * @implements {WebInspector.Searchable} 45 * @extends {WebInspector.Panel} 46 */ 47 WebInspector.ElementsPanel = function() 48 { 49 WebInspector.Panel.call(this, "elements"); 50 this.registerRequiredCSS("breadcrumbList.css"); 51 this.registerRequiredCSS("elementsPanel.css"); 52 this.registerRequiredCSS("textPrompt.css"); 53 this.setHideOnDetach(); 54 55 const initialSidebarWidth = 325; 56 const minimumContentWidthPercent = 0.34; 57 const initialSidebarHeight = 325; 58 const minimumContentHeightPercent = 0.34; 59 this.createSidebarView(this.element, WebInspector.SidebarView.SidebarPosition.End, initialSidebarWidth, initialSidebarHeight); 60 this.splitView.sidebarElement.classList.add("vbox"); 61 this.splitView.setSidebarElementConstraints(Preferences.minElementsSidebarWidth, Preferences.minElementsSidebarHeight); 62 this.splitView.setMainElementConstraints(minimumContentWidthPercent, minimumContentHeightPercent); 63 this.splitView.addEventListener(WebInspector.SidebarView.EventTypes.Resized, this._updateTreeOutlineVisibleWidth.bind(this)); 64 65 this._searchableView = new WebInspector.SearchableView(this); 66 this.splitView.mainElement.classList.add("vbox"); 67 this._searchableView.show(this.splitView.mainElement); 68 var stackElement = this._searchableView.element; 69 70 this.contentElement = stackElement.createChild("div"); 71 this.contentElement.id = "elements-content"; 72 this.contentElement.classList.add("outline-disclosure"); 73 this.contentElement.classList.add("source-code"); 74 if (!WebInspector.settings.domWordWrap.get()) 75 this.contentElement.classList.add("nowrap"); 76 WebInspector.settings.domWordWrap.addChangeListener(this._domWordWrapSettingChanged.bind(this)); 77 78 this.contentElement.addEventListener("contextmenu", this._contextMenuEventFired.bind(this), true); 79 this.splitView.sidebarElement.addEventListener("contextmenu", this._sidebarContextMenuEventFired.bind(this), false); 80 81 this.treeOutline = new WebInspector.ElementsTreeOutline(true, true, this._populateContextMenu.bind(this), this._setPseudoClassForNodeId.bind(this)); 82 this.treeOutline.wireToDomAgent(); 83 84 this.treeOutline.addEventListener(WebInspector.ElementsTreeOutline.Events.SelectedNodeChanged, this._selectedNodeChanged, this); 85 this.treeOutline.addEventListener(WebInspector.ElementsTreeOutline.Events.ElementsTreeUpdated, this._updateBreadcrumbIfNeeded, this); 86 87 var crumbsContainer = stackElement.createChild("div"); 88 crumbsContainer.id = "elements-crumbs"; 89 this.crumbsElement = crumbsContainer.createChild("div", "crumbs"); 90 this.crumbsElement.addEventListener("mousemove", this._mouseMovedInCrumbs.bind(this), false); 91 this.crumbsElement.addEventListener("mouseout", this._mouseMovedOutOfCrumbs.bind(this), false); 92 93 this.sidebarPanes = {}; 94 this.sidebarPanes.platformFonts = new WebInspector.PlatformFontsSidebarPane(); 95 this.sidebarPanes.computedStyle = new WebInspector.ComputedStyleSidebarPane(); 96 this.sidebarPanes.styles = new WebInspector.StylesSidebarPane(this.sidebarPanes.computedStyle, this._setPseudoClassForNodeId.bind(this)); 97 this.sidebarPanes.metrics = new WebInspector.MetricsSidebarPane(); 98 this.sidebarPanes.properties = new WebInspector.PropertiesSidebarPane(); 99 this.sidebarPanes.domBreakpoints = WebInspector.domBreakpointsSidebarPane.createProxy(this); 100 this.sidebarPanes.eventListeners = new WebInspector.EventListenersSidebarPane(); 101 102 this.sidebarPanes.styles.addEventListener(WebInspector.SidebarPane.EventTypes.wasShown, this.updateStyles.bind(this, false)); 103 this.sidebarPanes.metrics.addEventListener(WebInspector.SidebarPane.EventTypes.wasShown, this.updateMetrics.bind(this)); 104 this.sidebarPanes.platformFonts.addEventListener(WebInspector.SidebarPane.EventTypes.wasShown, this.updatePlatformFonts.bind(this)); 105 this.sidebarPanes.properties.addEventListener(WebInspector.SidebarPane.EventTypes.wasShown, this.updateProperties.bind(this)); 106 this.sidebarPanes.eventListeners.addEventListener(WebInspector.SidebarPane.EventTypes.wasShown, this.updateEventListeners.bind(this)); 107 108 this.sidebarPanes.styles.addEventListener("style edited", this._stylesPaneEdited, this); 109 this.sidebarPanes.styles.addEventListener("style property toggled", this._stylesPaneEdited, this); 110 this.sidebarPanes.metrics.addEventListener("metrics edited", this._metricsPaneEdited, this); 111 this._extensionSidebarPanes = []; 112 113 WebInspector.dockController.addEventListener(WebInspector.DockController.Events.DockSideChanged, this._dockSideChanged.bind(this)); 114 WebInspector.settings.splitVerticallyWhenDockedToRight.addChangeListener(this._dockSideChanged.bind(this)); 115 this._dockSideChanged(); 116 117 this._popoverHelper = new WebInspector.PopoverHelper(this.element, this._getPopoverAnchor.bind(this), this._showPopover.bind(this)); 118 this._popoverHelper.setTimeout(0); 119 120 WebInspector.domAgent.addEventListener(WebInspector.DOMAgent.Events.DocumentUpdated, this._documentUpdatedEvent, this); 121 WebInspector.settings.showShadowDOM.addChangeListener(this._showShadowDOMChanged.bind(this)); 122 123 if (WebInspector.domAgent.existingDocument()) 124 this._documentUpdated(WebInspector.domAgent.existingDocument()); 125 126 WebInspector.cssModel.addEventListener(WebInspector.CSSStyleModel.Events.ModelWasEnabled, this._updateSidebars, this); 127 } 128 129 WebInspector.ElementsPanel.prototype = { 130 _updateTreeOutlineVisibleWidth: function() 131 { 132 if (!this.treeOutline) 133 return; 134 135 var width = this.splitView.element.offsetWidth; 136 if (this.splitView.isVertical()) 137 width -= this.splitView.sidebarWidth(); 138 this.treeOutline.setVisibleWidth(width); 139 this.updateBreadcrumbSizes(); 140 }, 141 142 defaultFocusedElement: function() 143 { 144 return this.treeOutline.element; 145 }, 146 147 /** 148 * @return {!WebInspector.SearchableView} 149 */ 150 searchableView: function() 151 { 152 return this._searchableView; 153 }, 154 155 statusBarResized: function() 156 { 157 this.updateBreadcrumbSizes(); 158 }, 159 160 wasShown: function() 161 { 162 // Attach heavy component lazily 163 if (this.treeOutline.element.parentElement !== this.contentElement) 164 this.contentElement.appendChild(this.treeOutline.element); 165 166 WebInspector.Panel.prototype.wasShown.call(this); 167 168 this.updateBreadcrumb(); 169 this.treeOutline.updateSelection(); 170 this.treeOutline.setVisible(true); 171 172 if (!this.treeOutline.rootDOMNode) 173 WebInspector.domAgent.requestDocument(); 174 }, 175 176 willHide: function() 177 { 178 WebInspector.domAgent.hideDOMNodeHighlight(); 179 this.treeOutline.setVisible(false); 180 this._popoverHelper.hidePopover(); 181 182 // Detach heavy component on hide 183 this.contentElement.removeChild(this.treeOutline.element); 184 185 WebInspector.Panel.prototype.willHide.call(this); 186 }, 187 188 onResize: function() 189 { 190 this.treeOutline.updateSelection(); 191 this.updateBreadcrumbSizes(); 192 }, 193 194 /** 195 * @param {string=} id 196 * @return {?WebInspector.View} 197 */ 198 createView: function(id) 199 { 200 if (id === "emulation") { 201 if (!this._overridesView) 202 this._overridesView = new WebInspector.OverridesView(); 203 return this._overridesView; 204 } 205 if (id === "rendering") { 206 if (!this._renderingView) 207 this._renderingView = new WebInspector.RenderingOptionsView(); 208 return this._renderingView; 209 } 210 return null; 211 }, 212 213 /** 214 * @param {!DOMAgent.NodeId} nodeId 215 * @param {string} pseudoClass 216 * @param {boolean} enable 217 */ 218 _setPseudoClassForNodeId: function(nodeId, pseudoClass, enable) 219 { 220 var node = WebInspector.domAgent.nodeForId(nodeId); 221 if (!node) 222 return; 223 224 var pseudoClasses = node.getUserProperty(WebInspector.ElementsTreeOutline.PseudoStateDecorator.PropertyName); 225 if (enable) { 226 pseudoClasses = pseudoClasses || []; 227 if (pseudoClasses.indexOf(pseudoClass) >= 0) 228 return; 229 pseudoClasses.push(pseudoClass); 230 node.setUserProperty(WebInspector.ElementsTreeOutline.PseudoStateDecorator.PropertyName, pseudoClasses); 231 } else { 232 if (!pseudoClasses || pseudoClasses.indexOf(pseudoClass) < 0) 233 return; 234 pseudoClasses.remove(pseudoClass); 235 if (!pseudoClasses.length) 236 node.removeUserProperty(WebInspector.ElementsTreeOutline.PseudoStateDecorator.PropertyName); 237 } 238 239 this.treeOutline.updateOpenCloseTags(node); 240 WebInspector.cssModel.forcePseudoState(node.id, node.getUserProperty(WebInspector.ElementsTreeOutline.PseudoStateDecorator.PropertyName)); 241 this._metricsPaneEdited(); 242 this._stylesPaneEdited(); 243 244 WebInspector.notifications.dispatchEventToListeners(WebInspector.UserMetrics.UserAction, { 245 action: WebInspector.UserMetrics.UserActionNames.ForcedElementState, 246 selector: WebInspector.DOMPresentationUtils.appropriateSelectorFor(node, false), 247 enabled: enable, 248 state: pseudoClass 249 }); 250 }, 251 252 _selectedNodeChanged: function() 253 { 254 var selectedNode = this.selectedDOMNode(); 255 if (!selectedNode && this._lastValidSelectedNode) 256 this._selectedPathOnReset = this._lastValidSelectedNode.path(); 257 258 this.updateBreadcrumb(false); 259 260 this._updateSidebars(); 261 262 if (selectedNode) { 263 ConsoleAgent.addInspectedNode(selectedNode.id); 264 this._lastValidSelectedNode = selectedNode; 265 } 266 WebInspector.notifications.dispatchEventToListeners(WebInspector.ElementsTreeOutline.Events.SelectedNodeChanged); 267 }, 268 269 _updateSidebars: function() 270 { 271 for (var pane in this.sidebarPanes) 272 this.sidebarPanes[pane].needsUpdate = true; 273 274 this.updateStyles(true); 275 this.updateMetrics(); 276 this.updatePlatformFonts(); 277 this.updateProperties(); 278 this.updateEventListeners(); 279 }, 280 281 _reset: function() 282 { 283 delete this.currentQuery; 284 }, 285 286 _documentUpdatedEvent: function(event) 287 { 288 this._documentUpdated(event.data); 289 }, 290 291 _documentUpdated: function(inspectedRootDocument) 292 { 293 this._reset(); 294 this.searchCanceled(); 295 296 this.treeOutline.rootDOMNode = inspectedRootDocument; 297 298 if (!inspectedRootDocument) { 299 if (this.isShowing()) 300 WebInspector.domAgent.requestDocument(); 301 return; 302 } 303 304 WebInspector.domBreakpointsSidebarPane.restoreBreakpoints(); 305 306 /** 307 * @this {WebInspector.ElementsPanel} 308 * @param {?WebInspector.DOMNode} candidateFocusNode 309 */ 310 function selectNode(candidateFocusNode) 311 { 312 if (!candidateFocusNode) 313 candidateFocusNode = inspectedRootDocument.body || inspectedRootDocument.documentElement; 314 315 if (!candidateFocusNode) 316 return; 317 318 this.selectDOMNode(candidateFocusNode); 319 if (this.treeOutline.selectedTreeElement) 320 this.treeOutline.selectedTreeElement.expand(); 321 } 322 323 /** 324 * @param {?DOMAgent.NodeId} nodeId 325 * @this {WebInspector.ElementsPanel} 326 */ 327 function selectLastSelectedNode(nodeId) 328 { 329 if (this.selectedDOMNode()) { 330 // Focused node has been explicitly set while reaching out for the last selected node. 331 return; 332 } 333 var node = nodeId ? WebInspector.domAgent.nodeForId(nodeId) : null; 334 selectNode.call(this, node); 335 } 336 337 if (this._selectedPathOnReset) 338 WebInspector.domAgent.pushNodeByPathToFrontend(this._selectedPathOnReset, selectLastSelectedNode.bind(this)); 339 else 340 selectNode.call(this, null); 341 delete this._selectedPathOnReset; 342 }, 343 344 searchCanceled: function() 345 { 346 delete this._searchQuery; 347 this._hideSearchHighlights(); 348 349 this._searchableView.updateSearchMatchesCount(0); 350 351 delete this._currentSearchResultIndex; 352 delete this._searchResults; 353 WebInspector.domAgent.cancelSearch(); 354 }, 355 356 /** 357 * @param {string} query 358 * @param {boolean} shouldJump 359 */ 360 performSearch: function(query, shouldJump) 361 { 362 // Call searchCanceled since it will reset everything we need before doing a new search. 363 this.searchCanceled(); 364 365 const whitespaceTrimmedQuery = query.trim(); 366 if (!whitespaceTrimmedQuery.length) 367 return; 368 369 this._searchQuery = query; 370 371 /** 372 * @param {number} resultCount 373 * @this {WebInspector.ElementsPanel} 374 */ 375 function resultCountCallback(resultCount) 376 { 377 this._searchableView.updateSearchMatchesCount(resultCount); 378 if (!resultCount) 379 return; 380 381 this._searchResults = new Array(resultCount); 382 this._currentSearchResultIndex = -1; 383 if (shouldJump) 384 this.jumpToNextSearchResult(); 385 } 386 WebInspector.domAgent.performSearch(whitespaceTrimmedQuery, resultCountCallback.bind(this)); 387 }, 388 389 _contextMenuEventFired: function(event) 390 { 391 function toggleWordWrap() 392 { 393 WebInspector.settings.domWordWrap.set(!WebInspector.settings.domWordWrap.get()); 394 } 395 396 var contextMenu = new WebInspector.ContextMenu(event); 397 this.treeOutline.populateContextMenu(contextMenu, event); 398 399 if (WebInspector.experimentsSettings.cssRegions.isEnabled()) { 400 contextMenu.appendSeparator(); 401 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "CSS named flows\u2026" : "CSS Named Flows\u2026"), this._showNamedFlowCollections.bind(this)); 402 } 403 404 contextMenu.appendSeparator(); 405 contextMenu.appendCheckboxItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Word wrap" : "Word Wrap"), toggleWordWrap.bind(this), WebInspector.settings.domWordWrap.get()); 406 407 contextMenu.show(); 408 }, 409 410 _showNamedFlowCollections: function() 411 { 412 if (!WebInspector.cssNamedFlowCollectionsView) 413 WebInspector.cssNamedFlowCollectionsView = new WebInspector.CSSNamedFlowCollectionsView(); 414 WebInspector.cssNamedFlowCollectionsView.showInDrawer(); 415 }, 416 417 _domWordWrapSettingChanged: function(event) 418 { 419 if (event.data) 420 this.contentElement.classList.remove("nowrap"); 421 else 422 this.contentElement.classList.add("nowrap"); 423 424 var selectedNode = this.selectedDOMNode(); 425 if (!selectedNode) 426 return; 427 428 var treeElement = this.treeOutline.findTreeElement(selectedNode); 429 if (treeElement) 430 treeElement.updateSelection(); // Recalculate selection highlight dimensions. 431 }, 432 433 switchToAndFocus: function(node) 434 { 435 // Reset search restore. 436 this._searchableView.cancelSearch(); 437 WebInspector.inspectorView.setCurrentPanel(this); 438 this.selectDOMNode(node, true); 439 }, 440 441 _populateContextMenu: function(contextMenu, node) 442 { 443 // Add debbuging-related actions 444 contextMenu.appendSeparator(); 445 var pane = WebInspector.domBreakpointsSidebarPane; 446 pane.populateNodeContextMenu(node, contextMenu); 447 }, 448 449 _getPopoverAnchor: function(element) 450 { 451 var anchor = element.enclosingNodeOrSelfWithClass("webkit-html-resource-link"); 452 if (anchor) { 453 if (!anchor.href) 454 return null; 455 456 var resource = WebInspector.resourceTreeModel.resourceForURL(anchor.href); 457 if (!resource || resource.type !== WebInspector.resourceTypes.Image) 458 return null; 459 460 anchor.removeAttribute("title"); 461 } 462 return anchor; 463 }, 464 465 _loadDimensionsForNode: function(treeElement, callback) 466 { 467 // We get here for CSS properties, too, so bail out early for non-DOM treeElements. 468 if (treeElement.treeOutline !== this.treeOutline) { 469 callback(); 470 return; 471 } 472 473 var node = /** @type {!WebInspector.DOMNode} */ (treeElement.representedObject); 474 475 if (!node.nodeName() || node.nodeName().toLowerCase() !== "img") { 476 callback(); 477 return; 478 } 479 480 WebInspector.RemoteObject.resolveNode(node, "", resolvedNode); 481 482 function resolvedNode(object) 483 { 484 if (!object) { 485 callback(); 486 return; 487 } 488 489 object.callFunctionJSON(dimensions, undefined, callback); 490 object.release(); 491 492 /** 493 * @return {{ offsetWidth: number, offsetHeight: number, naturalWidth: number, naturalHeight: number }} 494 * @this {!Element} 495 */ 496 function dimensions() 497 { 498 return { offsetWidth: this.offsetWidth, offsetHeight: this.offsetHeight, naturalWidth: this.naturalWidth, naturalHeight: this.naturalHeight }; 499 } 500 } 501 }, 502 503 /** 504 * @param {!Element} anchor 505 * @param {!WebInspector.Popover} popover 506 */ 507 _showPopover: function(anchor, popover) 508 { 509 var listItem = anchor.enclosingNodeOrSelfWithNodeName("li"); 510 if (listItem && listItem.treeElement) 511 this._loadDimensionsForNode(listItem.treeElement, WebInspector.DOMPresentationUtils.buildImagePreviewContents.bind(WebInspector.DOMPresentationUtils, anchor.href, true, showPopover)); 512 else 513 WebInspector.DOMPresentationUtils.buildImagePreviewContents(anchor.href, true, showPopover); 514 515 /** 516 * @param {!Element=} contents 517 */ 518 function showPopover(contents) 519 { 520 if (!contents) 521 return; 522 popover.setCanShrink(false); 523 popover.show(contents, anchor); 524 } 525 }, 526 527 jumpToNextSearchResult: function() 528 { 529 if (!this._searchResults) 530 return; 531 532 this._hideSearchHighlights(); 533 if (++this._currentSearchResultIndex >= this._searchResults.length) 534 this._currentSearchResultIndex = 0; 535 536 this._highlightCurrentSearchResult(); 537 }, 538 539 jumpToPreviousSearchResult: function() 540 { 541 if (!this._searchResults) 542 return; 543 544 this._hideSearchHighlights(); 545 if (--this._currentSearchResultIndex < 0) 546 this._currentSearchResultIndex = (this._searchResults.length - 1); 547 548 this._highlightCurrentSearchResult(); 549 }, 550 551 _highlightCurrentSearchResult: function() 552 { 553 var index = this._currentSearchResultIndex; 554 var searchResults = this._searchResults; 555 var searchResult = searchResults[index]; 556 557 if (searchResult === null) { 558 this._searchableView.updateCurrentMatchIndex(index); 559 return; 560 } 561 562 /** 563 * @param {?WebInspector.DOMNode} node 564 * @this {WebInspector.ElementsPanel} 565 */ 566 function searchCallback(node) 567 { 568 searchResults[index] = node; 569 this._highlightCurrentSearchResult(); 570 } 571 572 if (typeof searchResult === "undefined") { 573 // No data for slot, request it. 574 WebInspector.domAgent.searchResult(index, searchCallback.bind(this)); 575 return; 576 } 577 578 this._searchableView.updateCurrentMatchIndex(index); 579 580 var treeElement = this.treeOutline.findTreeElement(searchResult); 581 if (treeElement) { 582 treeElement.highlightSearchResults(this._searchQuery); 583 treeElement.reveal(); 584 var matches = treeElement.listItemElement.getElementsByClassName("highlighted-search-result"); 585 if (matches.length) 586 matches[0].scrollIntoViewIfNeeded(); 587 } 588 }, 589 590 _hideSearchHighlights: function() 591 { 592 if (!this._searchResults) 593 return; 594 var searchResult = this._searchResults[this._currentSearchResultIndex]; 595 if (!searchResult) 596 return; 597 var treeElement = this.treeOutline.findTreeElement(searchResult); 598 if (treeElement) 599 treeElement.hideSearchHighlights(); 600 }, 601 602 /** 603 * @return {?WebInspector.DOMNode} 604 */ 605 selectedDOMNode: function() 606 { 607 return this.treeOutline.selectedDOMNode(); 608 }, 609 610 /** 611 * @param {boolean=} focus 612 */ 613 selectDOMNode: function(node, focus) 614 { 615 this.treeOutline.selectDOMNode(node, focus); 616 }, 617 618 /** 619 * @param {!WebInspector.Event} event 620 */ 621 _updateBreadcrumbIfNeeded: function(event) 622 { 623 var nodes = /** @type {!Array.<!WebInspector.DOMNode>} */ (event.data || []); 624 if (!nodes.length) 625 return; 626 627 var crumbs = this.crumbsElement; 628 for (var crumb = crumbs.firstChild; crumb; crumb = crumb.nextSibling) { 629 if (nodes.indexOf(crumb.representedObject) !== -1) { 630 this.updateBreadcrumb(true); 631 return; 632 } 633 } 634 }, 635 636 _stylesPaneEdited: function() 637 { 638 // Once styles are edited, the Metrics pane should be updated. 639 this.sidebarPanes.metrics.needsUpdate = true; 640 this.updateMetrics(); 641 this.sidebarPanes.platformFonts.needsUpdate = true; 642 this.updatePlatformFonts(); 643 }, 644 645 _metricsPaneEdited: function() 646 { 647 // Once metrics are edited, the Styles pane should be updated. 648 this.sidebarPanes.styles.needsUpdate = true; 649 this.updateStyles(true); 650 }, 651 652 _mouseMovedInCrumbs: function(event) 653 { 654 var nodeUnderMouse = document.elementFromPoint(event.pageX, event.pageY); 655 var crumbElement = nodeUnderMouse.enclosingNodeOrSelfWithClass("crumb"); 656 657 WebInspector.domAgent.highlightDOMNode(crumbElement ? crumbElement.representedObject.id : 0); 658 659 if ("_mouseOutOfCrumbsTimeout" in this) { 660 clearTimeout(this._mouseOutOfCrumbsTimeout); 661 delete this._mouseOutOfCrumbsTimeout; 662 } 663 }, 664 665 _mouseMovedOutOfCrumbs: function(event) 666 { 667 var nodeUnderMouse = document.elementFromPoint(event.pageX, event.pageY); 668 if (nodeUnderMouse && nodeUnderMouse.isDescendant(this.crumbsElement)) 669 return; 670 671 WebInspector.domAgent.hideDOMNodeHighlight(); 672 673 this._mouseOutOfCrumbsTimeout = setTimeout(this.updateBreadcrumbSizes.bind(this), 1000); 674 }, 675 676 /** 677 * @param {boolean=} forceUpdate 678 */ 679 updateBreadcrumb: function(forceUpdate) 680 { 681 if (!this.isShowing()) 682 return; 683 684 var crumbs = this.crumbsElement; 685 686 var handled = false; 687 var crumb = crumbs.firstChild; 688 while (crumb) { 689 if (crumb.representedObject === this.selectedDOMNode()) { 690 crumb.classList.add("selected"); 691 handled = true; 692 } else { 693 crumb.classList.remove("selected"); 694 } 695 696 crumb = crumb.nextSibling; 697 } 698 699 if (handled && !forceUpdate) { 700 // We don't need to rebuild the crumbs, but we need to adjust sizes 701 // to reflect the new focused or root node. 702 this.updateBreadcrumbSizes(); 703 return; 704 } 705 706 crumbs.removeChildren(); 707 708 var panel = this; 709 710 function selectCrumbFunction(event) 711 { 712 var crumb = event.currentTarget; 713 if (crumb.classList.contains("collapsed")) { 714 // Clicking a collapsed crumb will expose the hidden crumbs. 715 if (crumb === panel.crumbsElement.firstChild) { 716 // If the focused crumb is the first child, pick the farthest crumb 717 // that is still hidden. This allows the user to expose every crumb. 718 var currentCrumb = crumb; 719 while (currentCrumb) { 720 var hidden = currentCrumb.classList.contains("hidden"); 721 var collapsed = currentCrumb.classList.contains("collapsed"); 722 if (!hidden && !collapsed) 723 break; 724 crumb = currentCrumb; 725 currentCrumb = currentCrumb.nextSibling; 726 } 727 } 728 729 panel.updateBreadcrumbSizes(crumb); 730 } else 731 panel.selectDOMNode(crumb.representedObject, true); 732 733 event.preventDefault(); 734 } 735 736 for (var current = this.selectedDOMNode(); current; current = current.parentNode) { 737 if (current.nodeType() === Node.DOCUMENT_NODE) 738 continue; 739 740 crumb = document.createElement("span"); 741 crumb.className = "crumb"; 742 crumb.representedObject = current; 743 crumb.addEventListener("mousedown", selectCrumbFunction, false); 744 745 var crumbTitle = ""; 746 switch (current.nodeType()) { 747 case Node.ELEMENT_NODE: 748 if (current.pseudoType()) 749 crumbTitle = "::" + current.pseudoType(); 750 else 751 WebInspector.DOMPresentationUtils.decorateNodeLabel(current, crumb); 752 break; 753 754 case Node.TEXT_NODE: 755 crumbTitle = WebInspector.UIString("(text)"); 756 break; 757 758 case Node.COMMENT_NODE: 759 crumbTitle = "<!-->"; 760 break; 761 762 case Node.DOCUMENT_TYPE_NODE: 763 crumbTitle = "<!DOCTYPE>"; 764 break; 765 766 case Node.DOCUMENT_FRAGMENT_NODE: 767 crumbTitle = current.shadowRootType() ? "#shadow-root" : current.nodeNameInCorrectCase(); 768 break; 769 770 default: 771 crumbTitle = current.nodeNameInCorrectCase(); 772 } 773 774 if (!crumb.childNodes.length) { 775 var nameElement = document.createElement("span"); 776 nameElement.textContent = crumbTitle; 777 crumb.appendChild(nameElement); 778 crumb.title = crumbTitle; 779 } 780 781 if (current === this.selectedDOMNode()) 782 crumb.classList.add("selected"); 783 if (!crumbs.childNodes.length) 784 crumb.classList.add("end"); 785 786 crumbs.insertBefore(crumb, crumbs.firstChild); 787 } 788 789 if (crumbs.hasChildNodes()) 790 crumbs.lastChild.classList.add("start"); 791 792 this.updateBreadcrumbSizes(); 793 }, 794 795 /** 796 * @param {!Element=} focusedCrumb 797 */ 798 updateBreadcrumbSizes: function(focusedCrumb) 799 { 800 if (!this.isShowing()) 801 return; 802 803 if (document.body.offsetWidth <= 0) { 804 // The stylesheet hasn't loaded yet or the window is closed, 805 // so we can't calculate what is need. Return early. 806 return; 807 } 808 809 var crumbs = this.crumbsElement; 810 if (!crumbs.childNodes.length || crumbs.offsetWidth <= 0) 811 return; // No crumbs, do nothing. 812 813 // A Zero index is the right most child crumb in the breadcrumb. 814 var selectedIndex = 0; 815 var focusedIndex = 0; 816 var selectedCrumb; 817 818 var i = 0; 819 var crumb = crumbs.firstChild; 820 while (crumb) { 821 // Find the selected crumb and index. 822 if (!selectedCrumb && crumb.classList.contains("selected")) { 823 selectedCrumb = crumb; 824 selectedIndex = i; 825 } 826 827 // Find the focused crumb index. 828 if (crumb === focusedCrumb) 829 focusedIndex = i; 830 831 // Remove any styles that affect size before 832 // deciding to shorten any crumbs. 833 if (crumb !== crumbs.lastChild) 834 crumb.classList.remove("start"); 835 if (crumb !== crumbs.firstChild) 836 crumb.classList.remove("end"); 837 838 crumb.classList.remove("compact"); 839 crumb.classList.remove("collapsed"); 840 crumb.classList.remove("hidden"); 841 842 crumb = crumb.nextSibling; 843 ++i; 844 } 845 846 // Restore the start and end crumb classes in case they got removed in coalesceCollapsedCrumbs(). 847 // The order of the crumbs in the document is opposite of the visual order. 848 crumbs.firstChild.classList.add("end"); 849 crumbs.lastChild.classList.add("start"); 850 851 var contentElement = this.contentElement; 852 function crumbsAreSmallerThanContainer() 853 { 854 const rightPadding = 10; 855 return crumbs.offsetWidth + rightPadding < contentElement.offsetWidth; 856 } 857 858 if (crumbsAreSmallerThanContainer()) 859 return; // No need to compact the crumbs, they all fit at full size. 860 861 var BothSides = 0; 862 var AncestorSide = -1; 863 var ChildSide = 1; 864 865 /** 866 * @param {boolean=} significantCrumb 867 */ 868 function makeCrumbsSmaller(shrinkingFunction, direction, significantCrumb) 869 { 870 if (!significantCrumb) 871 significantCrumb = (focusedCrumb || selectedCrumb); 872 873 if (significantCrumb === selectedCrumb) 874 var significantIndex = selectedIndex; 875 else if (significantCrumb === focusedCrumb) 876 var significantIndex = focusedIndex; 877 else { 878 var significantIndex = 0; 879 for (var i = 0; i < crumbs.childNodes.length; ++i) { 880 if (crumbs.childNodes[i] === significantCrumb) { 881 significantIndex = i; 882 break; 883 } 884 } 885 } 886 887 function shrinkCrumbAtIndex(index) 888 { 889 var shrinkCrumb = crumbs.childNodes[index]; 890 if (shrinkCrumb && shrinkCrumb !== significantCrumb) 891 shrinkingFunction(shrinkCrumb); 892 if (crumbsAreSmallerThanContainer()) 893 return true; // No need to compact the crumbs more. 894 return false; 895 } 896 897 // Shrink crumbs one at a time by applying the shrinkingFunction until the crumbs 898 // fit in the container or we run out of crumbs to shrink. 899 if (direction) { 900 // Crumbs are shrunk on only one side (based on direction) of the signifcant crumb. 901 var index = (direction > 0 ? 0 : crumbs.childNodes.length - 1); 902 while (index !== significantIndex) { 903 if (shrinkCrumbAtIndex(index)) 904 return true; 905 index += (direction > 0 ? 1 : -1); 906 } 907 } else { 908 // Crumbs are shrunk in order of descending distance from the signifcant crumb, 909 // with a tie going to child crumbs. 910 var startIndex = 0; 911 var endIndex = crumbs.childNodes.length - 1; 912 while (startIndex != significantIndex || endIndex != significantIndex) { 913 var startDistance = significantIndex - startIndex; 914 var endDistance = endIndex - significantIndex; 915 if (startDistance >= endDistance) 916 var index = startIndex++; 917 else 918 var index = endIndex--; 919 if (shrinkCrumbAtIndex(index)) 920 return true; 921 } 922 } 923 924 // We are not small enough yet, return false so the caller knows. 925 return false; 926 } 927 928 function coalesceCollapsedCrumbs() 929 { 930 var crumb = crumbs.firstChild; 931 var collapsedRun = false; 932 var newStartNeeded = false; 933 var newEndNeeded = false; 934 while (crumb) { 935 var hidden = crumb.classList.contains("hidden"); 936 if (!hidden) { 937 var collapsed = crumb.classList.contains("collapsed"); 938 if (collapsedRun && collapsed) { 939 crumb.classList.add("hidden"); 940 crumb.classList.remove("compact"); 941 crumb.classList.remove("collapsed"); 942 943 if (crumb.classList.contains("start")) { 944 crumb.classList.remove("start"); 945 newStartNeeded = true; 946 } 947 948 if (crumb.classList.contains("end")) { 949 crumb.classList.remove("end"); 950 newEndNeeded = true; 951 } 952 953 continue; 954 } 955 956 collapsedRun = collapsed; 957 958 if (newEndNeeded) { 959 newEndNeeded = false; 960 crumb.classList.add("end"); 961 } 962 } else 963 collapsedRun = true; 964 crumb = crumb.nextSibling; 965 } 966 967 if (newStartNeeded) { 968 crumb = crumbs.lastChild; 969 while (crumb) { 970 if (!crumb.classList.contains("hidden")) { 971 crumb.classList.add("start"); 972 break; 973 } 974 crumb = crumb.previousSibling; 975 } 976 } 977 } 978 979 function compact(crumb) 980 { 981 if (crumb.classList.contains("hidden")) 982 return; 983 crumb.classList.add("compact"); 984 } 985 986 function collapse(crumb, dontCoalesce) 987 { 988 if (crumb.classList.contains("hidden")) 989 return; 990 crumb.classList.add("collapsed"); 991 crumb.classList.remove("compact"); 992 if (!dontCoalesce) 993 coalesceCollapsedCrumbs(); 994 } 995 996 if (!focusedCrumb) { 997 // When not focused on a crumb we can be biased and collapse less important 998 // crumbs that the user might not care much about. 999 1000 // Compact child crumbs. 1001 if (makeCrumbsSmaller(compact, ChildSide)) 1002 return; 1003 1004 // Collapse child crumbs. 1005 if (makeCrumbsSmaller(collapse, ChildSide)) 1006 return; 1007 } 1008 1009 // Compact ancestor crumbs, or from both sides if focused. 1010 if (makeCrumbsSmaller(compact, (focusedCrumb ? BothSides : AncestorSide))) 1011 return; 1012 1013 // Collapse ancestor crumbs, or from both sides if focused. 1014 if (makeCrumbsSmaller(collapse, (focusedCrumb ? BothSides : AncestorSide))) 1015 return; 1016 1017 if (!selectedCrumb) 1018 return; 1019 1020 // Compact the selected crumb. 1021 compact(selectedCrumb); 1022 if (crumbsAreSmallerThanContainer()) 1023 return; 1024 1025 // Collapse the selected crumb as a last resort. Pass true to prevent coalescing. 1026 collapse(selectedCrumb, true); 1027 }, 1028 1029 /** 1030 * @param {boolean=} forceUpdate 1031 */ 1032 updateStyles: function(forceUpdate) 1033 { 1034 if (!WebInspector.cssModel.isEnabled()) 1035 return; 1036 var stylesSidebarPane = this.sidebarPanes.styles; 1037 var computedStylePane = this.sidebarPanes.computedStyle; 1038 if ((!stylesSidebarPane.isShowing() && !computedStylePane.isShowing()) || !stylesSidebarPane.needsUpdate) 1039 return; 1040 1041 stylesSidebarPane.update(this.selectedDOMNode(), forceUpdate); 1042 stylesSidebarPane.needsUpdate = false; 1043 }, 1044 1045 updateMetrics: function() 1046 { 1047 if (!WebInspector.cssModel.isEnabled()) 1048 return; 1049 var metricsSidebarPane = this.sidebarPanes.metrics; 1050 if (!metricsSidebarPane.isShowing() || !metricsSidebarPane.needsUpdate) 1051 return; 1052 1053 metricsSidebarPane.update(this.selectedDOMNode()); 1054 metricsSidebarPane.needsUpdate = false; 1055 }, 1056 1057 updatePlatformFonts: function() 1058 { 1059 if (!WebInspector.cssModel.isEnabled()) 1060 return; 1061 var platformFontsSidebar = this.sidebarPanes.platformFonts; 1062 if (!platformFontsSidebar.isShowing() || !platformFontsSidebar.needsUpdate) 1063 return; 1064 1065 platformFontsSidebar.update(this.selectedDOMNode()); 1066 platformFontsSidebar.needsUpdate = false; 1067 }, 1068 1069 updateProperties: function() 1070 { 1071 var propertiesSidebarPane = this.sidebarPanes.properties; 1072 if (!propertiesSidebarPane.isShowing() || !propertiesSidebarPane.needsUpdate) 1073 return; 1074 1075 propertiesSidebarPane.update(this.selectedDOMNode()); 1076 propertiesSidebarPane.needsUpdate = false; 1077 }, 1078 1079 updateEventListeners: function() 1080 { 1081 var eventListenersSidebarPane = this.sidebarPanes.eventListeners; 1082 if (!eventListenersSidebarPane.isShowing() || !eventListenersSidebarPane.needsUpdate) 1083 return; 1084 1085 eventListenersSidebarPane.update(this.selectedDOMNode()); 1086 eventListenersSidebarPane.needsUpdate = false; 1087 }, 1088 1089 /** 1090 * @param {!KeyboardEvent} event 1091 */ 1092 handleShortcut: function(event) 1093 { 1094 /** 1095 * @this {WebInspector.ElementsPanel} 1096 */ 1097 function handleUndoRedo() 1098 { 1099 if (WebInspector.KeyboardShortcut.eventHasCtrlOrMeta(event) && !event.shiftKey && event.keyIdentifier === "U+005A") { // Z key 1100 WebInspector.domAgent.undo(this._updateSidebars.bind(this)); 1101 event.handled = true; 1102 return; 1103 } 1104 1105 var isRedoKey = WebInspector.isMac() ? event.metaKey && event.shiftKey && event.keyIdentifier === "U+005A" : // Z key 1106 event.ctrlKey && event.keyIdentifier === "U+0059"; // Y key 1107 if (isRedoKey) { 1108 DOMAgent.redo(this._updateSidebars.bind(this)); 1109 event.handled = true; 1110 } 1111 } 1112 1113 if (!this.treeOutline.editing()) { 1114 handleUndoRedo.call(this); 1115 if (event.handled) 1116 return; 1117 } 1118 1119 this.treeOutline.handleShortcut(event); 1120 }, 1121 1122 handleCopyEvent: function(event) 1123 { 1124 var currentFocusElement = WebInspector.currentFocusElement(); 1125 if (currentFocusElement && WebInspector.isBeingEdited(currentFocusElement)) 1126 return; 1127 1128 // Don't prevent the normal copy if the user has a selection. 1129 if (!window.getSelection().isCollapsed) 1130 return; 1131 event.clipboardData.clearData(); 1132 event.preventDefault(); 1133 this.selectedDOMNode().copyNode(); 1134 }, 1135 1136 sidebarResized: function(event) 1137 { 1138 this.treeOutline.updateSelection(); 1139 }, 1140 1141 revealAndSelectNode: function(nodeId) 1142 { 1143 WebInspector.inspectorView.setCurrentPanel(this); 1144 1145 var node = WebInspector.domAgent.nodeForId(nodeId); 1146 if (!node) 1147 return; 1148 1149 while (!WebInspector.ElementsTreeOutline.showShadowDOM() && node && node.isInShadowTree()) 1150 node = node.parentNode; 1151 1152 WebInspector.domAgent.highlightDOMNodeForTwoSeconds(nodeId); 1153 this.selectDOMNode(node, true); 1154 }, 1155 1156 /** 1157 * @param {!WebInspector.ContextMenu} contextMenu 1158 * @param {!Object} target 1159 */ 1160 appendApplicableItems: function(event, contextMenu, target) 1161 { 1162 /** 1163 * @param {?DOMAgent.NodeId} nodeId 1164 */ 1165 function selectNode(nodeId) 1166 { 1167 if (nodeId) 1168 WebInspector.domAgent.inspectElement(nodeId); 1169 } 1170 1171 /** 1172 * @param {!WebInspector.RemoteObject} remoteObject 1173 */ 1174 function revealElement(remoteObject) 1175 { 1176 remoteObject.pushNodeToFrontend(selectNode); 1177 } 1178 1179 var commandCallback; 1180 if (target instanceof WebInspector.RemoteObject) { 1181 var remoteObject = /** @type {!WebInspector.RemoteObject} */ (target); 1182 if (remoteObject.subtype === "node") 1183 commandCallback = revealElement.bind(this, remoteObject); 1184 } else if (target instanceof WebInspector.DOMNode) { 1185 var domNode = /** @type {!WebInspector.DOMNode} */ (target); 1186 if (domNode.id) 1187 commandCallback = WebInspector.domAgent.inspectElement.bind(WebInspector.domAgent, domNode.id); 1188 } 1189 if (!commandCallback) 1190 return; 1191 // Skip adding "Reveal..." menu item for our own tree outline. 1192 if (this.treeOutline.element.isAncestor(event.target)) 1193 return; 1194 contextMenu.appendItem(WebInspector.useLowerCaseMenuTitles() ? "Reveal in Elements panel" : "Reveal in Elements Panel", commandCallback); 1195 }, 1196 1197 _sidebarContextMenuEventFired: function(event) 1198 { 1199 var contextMenu = new WebInspector.ContextMenu(event); 1200 contextMenu.show(); 1201 }, 1202 1203 _dockSideChanged: function() 1204 { 1205 var dockSide = WebInspector.dockController.dockSide(); 1206 var vertically = dockSide === WebInspector.DockController.State.DockedToRight && WebInspector.settings.splitVerticallyWhenDockedToRight.get(); 1207 this._splitVertically(vertically); 1208 }, 1209 1210 _showShadowDOMChanged: function() 1211 { 1212 this.treeOutline.update(); 1213 }, 1214 1215 /** 1216 * @param {boolean} vertically 1217 */ 1218 _splitVertically: function(vertically) 1219 { 1220 if (this.sidebarPaneView && vertically === !this.splitView.isVertical()) 1221 return; 1222 1223 if (this.sidebarPaneView) 1224 this.sidebarPaneView.detach(); 1225 1226 this.splitView.setVertical(!vertically); 1227 1228 var computedPane = new WebInspector.SidebarPane(WebInspector.UIString("Computed")); 1229 computedPane.element.classList.add("composite"); 1230 computedPane.element.classList.add("fill"); 1231 var expandComputed = computedPane.expand.bind(computedPane); 1232 1233 computedPane.bodyElement.appendChild(this.sidebarPanes.computedStyle.titleElement); 1234 computedPane.bodyElement.classList.add("metrics-and-computed"); 1235 this.sidebarPanes.computedStyle.show(computedPane.bodyElement); 1236 this.sidebarPanes.computedStyle.setExpandCallback(expandComputed); 1237 1238 this.sidebarPanes.platformFonts.show(computedPane.bodyElement); 1239 1240 /** 1241 * @param {!WebInspector.SidebarPane} pane 1242 * @param {!Element=} beforeElement 1243 * @this {WebInspector.ElementsPanel} 1244 */ 1245 function showMetrics(pane, beforeElement) 1246 { 1247 this.sidebarPanes.metrics.show(pane.bodyElement, beforeElement); 1248 } 1249 1250 /** 1251 * @param {!WebInspector.Event} event 1252 * @this {WebInspector.ElementsPanel} 1253 */ 1254 function tabSelected(event) 1255 { 1256 var tabId = /** @type {string} */ (event.data.tabId); 1257 if (tabId === computedPane.title()) 1258 showMetrics.call(this, computedPane, this.sidebarPanes.computedStyle.element); 1259 if (tabId === stylesPane.title()) 1260 showMetrics.call(this, stylesPane); 1261 } 1262 1263 this.sidebarPaneView = new WebInspector.SidebarTabbedPane(); 1264 1265 if (vertically) { 1266 this.sidebarPanes.metrics.show(computedPane.bodyElement, this.sidebarPanes.computedStyle.element); 1267 this.sidebarPanes.metrics.setExpandCallback(expandComputed); 1268 1269 var compositePane = new WebInspector.SidebarPane(this.sidebarPanes.styles.title()); 1270 compositePane.element.classList.add("composite"); 1271 compositePane.element.classList.add("fill"); 1272 var expandComposite = compositePane.expand.bind(compositePane); 1273 1274 var splitView = new WebInspector.SplitView(true, "StylesPaneSplitRatio", 0.5); 1275 splitView.show(compositePane.bodyElement); 1276 1277 this.sidebarPanes.styles.show(splitView.firstElement()); 1278 splitView.firstElement().appendChild(this.sidebarPanes.styles.titleElement); 1279 this.sidebarPanes.styles.setExpandCallback(expandComposite); 1280 1281 computedPane.show(splitView.secondElement()); 1282 computedPane.setExpandCallback(expandComposite); 1283 1284 this.sidebarPaneView.addPane(compositePane); 1285 } else { 1286 var stylesPane = new WebInspector.SidebarPane(this.sidebarPanes.styles.title()); 1287 stylesPane.element.classList.add("composite"); 1288 stylesPane.element.classList.add("fill"); 1289 var expandStyles = stylesPane.expand.bind(stylesPane); 1290 stylesPane.bodyElement.classList.add("metrics-and-styles"); 1291 this.sidebarPanes.styles.show(stylesPane.bodyElement); 1292 this.sidebarPanes.styles.setExpandCallback(expandStyles); 1293 this.sidebarPanes.metrics.setExpandCallback(expandStyles); 1294 stylesPane.bodyElement.appendChild(this.sidebarPanes.styles.titleElement); 1295 1296 this.sidebarPaneView.addEventListener(WebInspector.TabbedPane.EventTypes.TabSelected, tabSelected, this); 1297 1298 showMetrics.call(this, stylesPane); 1299 this.sidebarPaneView.addPane(stylesPane); 1300 this.sidebarPaneView.addPane(computedPane); 1301 } 1302 1303 this.sidebarPaneView.addPane(this.sidebarPanes.eventListeners); 1304 this.sidebarPaneView.addPane(this.sidebarPanes.domBreakpoints); 1305 this.sidebarPaneView.addPane(this.sidebarPanes.properties); 1306 this._extensionSidebarPanesContainer = this.sidebarPaneView; 1307 1308 for (var i = 0; i < this._extensionSidebarPanes.length; ++i) 1309 this._extensionSidebarPanesContainer.addPane(this._extensionSidebarPanes[i]); 1310 1311 this.sidebarPaneView.show(this.splitView.sidebarElement); 1312 this.sidebarPanes.styles.expand(); 1313 }, 1314 1315 /** 1316 * @param {string} id 1317 * @param {!WebInspector.SidebarPane} pane 1318 */ 1319 addExtensionSidebarPane: function(id, pane) 1320 { 1321 this._extensionSidebarPanes.push(pane); 1322 this._extensionSidebarPanesContainer.addPane(pane); 1323 }, 1324 1325 __proto__: WebInspector.Panel.prototype 1326 } 1327