ElementsPanel.js 41 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172
  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. importScript("CSSNamedFlowCollectionsView.js");
  31. importScript("CSSNamedFlowView.js");
  32. importScript("EventListenersSidebarPane.js");
  33. importScript("MetricsSidebarPane.js");
  34. importScript("PropertiesSidebarPane.js");
  35. importScript("StylesSidebarPane.js");
  36. /**
  37. * @constructor
  38. * @extends {WebInspector.Panel}
  39. */
  40. WebInspector.ElementsPanel = function()
  41. {
  42. WebInspector.Panel.call(this, "elements");
  43. this.registerRequiredCSS("breadcrumbList.css");
  44. this.registerRequiredCSS("elementsPanel.css");
  45. this.registerRequiredCSS("textPrompt.css");
  46. this.setHideOnDetach();
  47. const initialSidebarWidth = 325;
  48. const minimumContentWidthPercent = 34;
  49. const initialSidebarHeight = 325;
  50. const minimumContentHeightPercent = 34;
  51. this.createSidebarView(this.element, WebInspector.SidebarView.SidebarPosition.End, initialSidebarWidth, initialSidebarHeight);
  52. this.splitView.setMinimumSidebarWidth(Preferences.minElementsSidebarWidth);
  53. this.splitView.setMinimumMainWidthPercent(minimumContentWidthPercent);
  54. this.splitView.setMinimumSidebarHeight(Preferences.minElementsSidebarHeight);
  55. this.splitView.setMinimumMainHeightPercent(minimumContentHeightPercent);
  56. this.contentElement = this.splitView.mainElement;
  57. this.contentElement.id = "elements-content";
  58. this.contentElement.addStyleClass("outline-disclosure");
  59. this.contentElement.addStyleClass("source-code");
  60. if (!WebInspector.settings.domWordWrap.get())
  61. this.contentElement.classList.add("nowrap");
  62. WebInspector.settings.domWordWrap.addChangeListener(this._domWordWrapSettingChanged.bind(this));
  63. this.contentElement.addEventListener("contextmenu", this._contextMenuEventFired.bind(this), true);
  64. this.splitView.sidebarElement.addEventListener("contextmenu", this._sidebarContextMenuEventFired.bind(this), false);
  65. this.treeOutline = new WebInspector.ElementsTreeOutline(true, true, false, this._populateContextMenu.bind(this), this._setPseudoClassForNodeId.bind(this));
  66. this.treeOutline.wireToDomAgent();
  67. this.treeOutline.addEventListener(WebInspector.ElementsTreeOutline.Events.SelectedNodeChanged, this._selectedNodeChanged, this);
  68. this.crumbsElement = document.createElement("div");
  69. this.crumbsElement.className = "crumbs";
  70. this.crumbsElement.addEventListener("mousemove", this._mouseMovedInCrumbs.bind(this), false);
  71. this.crumbsElement.addEventListener("mouseout", this._mouseMovedOutOfCrumbs.bind(this), false);
  72. this.sidebarPanes = {};
  73. this.sidebarPanes.computedStyle = new WebInspector.ComputedStyleSidebarPane();
  74. this.sidebarPanes.styles = new WebInspector.StylesSidebarPane(this.sidebarPanes.computedStyle, this._setPseudoClassForNodeId.bind(this));
  75. this.sidebarPanes.metrics = new WebInspector.MetricsSidebarPane();
  76. this.sidebarPanes.properties = new WebInspector.PropertiesSidebarPane();
  77. this.sidebarPanes.domBreakpoints = WebInspector.domBreakpointsSidebarPane.createProxy(this);
  78. this.sidebarPanes.eventListeners = new WebInspector.EventListenersSidebarPane();
  79. this.sidebarPanes.styles.addEventListener(WebInspector.SidebarPane.EventTypes.wasShown, this.updateStyles.bind(this, false));
  80. this.sidebarPanes.metrics.addEventListener(WebInspector.SidebarPane.EventTypes.wasShown, this.updateMetrics.bind(this));
  81. this.sidebarPanes.properties.addEventListener(WebInspector.SidebarPane.EventTypes.wasShown, this.updateProperties.bind(this));
  82. this.sidebarPanes.eventListeners.addEventListener(WebInspector.SidebarPane.EventTypes.wasShown, this.updateEventListeners.bind(this));
  83. this.sidebarPanes.styles.addEventListener("style edited", this._stylesPaneEdited, this);
  84. this.sidebarPanes.styles.addEventListener("style property toggled", this._stylesPaneEdited, this);
  85. this.sidebarPanes.metrics.addEventListener("metrics edited", this._metricsPaneEdited, this);
  86. WebInspector.dockController.addEventListener(WebInspector.DockController.Events.DockSideChanged, this._dockSideChanged.bind(this));
  87. WebInspector.settings.splitVerticallyWhenDockedToRight.addChangeListener(this._dockSideChanged.bind(this));
  88. this._dockSideChanged();
  89. this._popoverHelper = new WebInspector.PopoverHelper(this.element, this._getPopoverAnchor.bind(this), this._showPopover.bind(this));
  90. this._popoverHelper.setTimeout(0);
  91. WebInspector.domAgent.addEventListener(WebInspector.DOMAgent.Events.AttrModified, this._updateBreadcrumbIfNeeded, this);
  92. WebInspector.domAgent.addEventListener(WebInspector.DOMAgent.Events.AttrRemoved, this._updateBreadcrumbIfNeeded, this);
  93. WebInspector.domAgent.addEventListener(WebInspector.DOMAgent.Events.NodeRemoved, this._nodeRemoved, this);
  94. WebInspector.domAgent.addEventListener(WebInspector.DOMAgent.Events.DocumentUpdated, this._documentUpdatedEvent, this);
  95. WebInspector.domAgent.addEventListener(WebInspector.DOMAgent.Events.InspectElementRequested, this._inspectElementRequested, this);
  96. if (WebInspector.domAgent.existingDocument())
  97. this._documentUpdated(WebInspector.domAgent.existingDocument());
  98. }
  99. WebInspector.ElementsPanel.prototype = {
  100. statusBarItems: function()
  101. {
  102. return [this.crumbsElement];
  103. },
  104. defaultFocusedElement: function()
  105. {
  106. return this.treeOutline.element;
  107. },
  108. statusBarResized: function()
  109. {
  110. this.updateBreadcrumbSizes();
  111. },
  112. wasShown: function()
  113. {
  114. // Attach heavy component lazily
  115. if (this.treeOutline.element.parentElement !== this.contentElement)
  116. this.contentElement.appendChild(this.treeOutline.element);
  117. WebInspector.Panel.prototype.wasShown.call(this);
  118. this.updateBreadcrumb();
  119. this.treeOutline.updateSelection();
  120. this.treeOutline.setVisible(true);
  121. if (!this.treeOutline.rootDOMNode)
  122. WebInspector.domAgent.requestDocument();
  123. },
  124. willHide: function()
  125. {
  126. WebInspector.domAgent.hideDOMNodeHighlight();
  127. this.treeOutline.setVisible(false);
  128. this._popoverHelper.hidePopover();
  129. // Detach heavy component on hide
  130. this.contentElement.removeChild(this.treeOutline.element);
  131. WebInspector.Panel.prototype.willHide.call(this);
  132. },
  133. onResize: function()
  134. {
  135. this.treeOutline.updateSelection();
  136. this.updateBreadcrumbSizes();
  137. },
  138. /**
  139. * @param {DOMAgent.NodeId} nodeId
  140. * @param {string} pseudoClass
  141. * @param {boolean} enable
  142. */
  143. _setPseudoClassForNodeId: function(nodeId, pseudoClass, enable)
  144. {
  145. var node = WebInspector.domAgent.nodeForId(nodeId);
  146. if (!node)
  147. return;
  148. var pseudoClasses = node.getUserProperty(WebInspector.ElementsTreeOutline.PseudoStateDecorator.PropertyName);
  149. if (enable) {
  150. pseudoClasses = pseudoClasses || [];
  151. if (pseudoClasses.indexOf(pseudoClass) >= 0)
  152. return;
  153. pseudoClasses.push(pseudoClass);
  154. node.setUserProperty(WebInspector.ElementsTreeOutline.PseudoStateDecorator.PropertyName, pseudoClasses);
  155. } else {
  156. if (!pseudoClasses || pseudoClasses.indexOf(pseudoClass) < 0)
  157. return;
  158. pseudoClasses.remove(pseudoClass);
  159. if (!pseudoClasses.length)
  160. node.removeUserProperty(WebInspector.ElementsTreeOutline.PseudoStateDecorator.PropertyName);
  161. }
  162. this.treeOutline.updateOpenCloseTags(node);
  163. WebInspector.cssModel.forcePseudoState(node.id, node.getUserProperty(WebInspector.ElementsTreeOutline.PseudoStateDecorator.PropertyName));
  164. this._metricsPaneEdited();
  165. this._stylesPaneEdited();
  166. WebInspector.notifications.dispatchEventToListeners(WebInspector.UserMetrics.UserAction, {
  167. action: WebInspector.UserMetrics.UserActionNames.ForcedElementState,
  168. selector: node.appropriateSelectorFor(false),
  169. enabled: enable,
  170. state: pseudoClass
  171. });
  172. },
  173. _selectedNodeChanged: function()
  174. {
  175. var selectedNode = this.selectedDOMNode();
  176. if (!selectedNode && this._lastValidSelectedNode)
  177. this._selectedPathOnReset = this._lastValidSelectedNode.path();
  178. this.updateBreadcrumb(false);
  179. this._updateSidebars();
  180. if (selectedNode) {
  181. ConsoleAgent.addInspectedNode(selectedNode.id);
  182. this._lastValidSelectedNode = selectedNode;
  183. }
  184. WebInspector.notifications.dispatchEventToListeners(WebInspector.ElementsTreeOutline.Events.SelectedNodeChanged);
  185. },
  186. _updateSidebars: function()
  187. {
  188. for (var pane in this.sidebarPanes)
  189. this.sidebarPanes[pane].needsUpdate = true;
  190. this.updateStyles(true);
  191. this.updateMetrics();
  192. this.updateProperties();
  193. this.updateEventListeners();
  194. },
  195. _reset: function()
  196. {
  197. delete this.currentQuery;
  198. },
  199. _documentUpdatedEvent: function(event)
  200. {
  201. this._documentUpdated(event.data);
  202. },
  203. _documentUpdated: function(inspectedRootDocument)
  204. {
  205. this._reset();
  206. this.searchCanceled();
  207. this.treeOutline.rootDOMNode = inspectedRootDocument;
  208. if (!inspectedRootDocument) {
  209. if (this.isShowing())
  210. WebInspector.domAgent.requestDocument();
  211. return;
  212. }
  213. WebInspector.domBreakpointsSidebarPane.restoreBreakpoints();
  214. /**
  215. * @this {WebInspector.ElementsPanel}
  216. * @param {WebInspector.DOMNode=} candidateFocusNode
  217. */
  218. function selectNode(candidateFocusNode)
  219. {
  220. if (!candidateFocusNode)
  221. candidateFocusNode = inspectedRootDocument.body || inspectedRootDocument.documentElement;
  222. if (!candidateFocusNode)
  223. return;
  224. this.selectDOMNode(candidateFocusNode);
  225. if (this.treeOutline.selectedTreeElement)
  226. this.treeOutline.selectedTreeElement.expand();
  227. }
  228. function selectLastSelectedNode(nodeId)
  229. {
  230. if (this.selectedDOMNode()) {
  231. // Focused node has been explicitly set while reaching out for the last selected node.
  232. return;
  233. }
  234. var node = nodeId ? WebInspector.domAgent.nodeForId(nodeId) : null;
  235. selectNode.call(this, node);
  236. }
  237. if (this._selectedPathOnReset)
  238. WebInspector.domAgent.pushNodeByPathToFrontend(this._selectedPathOnReset, selectLastSelectedNode.bind(this));
  239. else
  240. selectNode.call(this);
  241. delete this._selectedPathOnReset;
  242. },
  243. searchCanceled: function()
  244. {
  245. delete this._searchQuery;
  246. this._hideSearchHighlights();
  247. WebInspector.searchController.updateSearchMatchesCount(0, this);
  248. delete this._currentSearchResultIndex;
  249. delete this._searchResults;
  250. WebInspector.domAgent.cancelSearch();
  251. },
  252. /**
  253. * @param {string} query
  254. */
  255. performSearch: function(query)
  256. {
  257. // Call searchCanceled since it will reset everything we need before doing a new search.
  258. this.searchCanceled();
  259. const whitespaceTrimmedQuery = query.trim();
  260. if (!whitespaceTrimmedQuery.length)
  261. return;
  262. this._searchQuery = query;
  263. /**
  264. * @param {number} resultCount
  265. */
  266. function resultCountCallback(resultCount)
  267. {
  268. WebInspector.searchController.updateSearchMatchesCount(resultCount, this);
  269. if (!resultCount)
  270. return;
  271. this._searchResults = new Array(resultCount);
  272. this._currentSearchResultIndex = -1;
  273. this.jumpToNextSearchResult();
  274. }
  275. WebInspector.domAgent.performSearch(whitespaceTrimmedQuery, resultCountCallback.bind(this));
  276. },
  277. _contextMenuEventFired: function(event)
  278. {
  279. function toggleWordWrap()
  280. {
  281. WebInspector.settings.domWordWrap.set(!WebInspector.settings.domWordWrap.get());
  282. }
  283. var contextMenu = new WebInspector.ContextMenu(event);
  284. this.treeOutline.populateContextMenu(contextMenu, event);
  285. if (WebInspector.experimentsSettings.cssRegions.isEnabled()) {
  286. contextMenu.appendSeparator();
  287. contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "CSS named flows\u2026" : "CSS Named Flows\u2026"), this._showNamedFlowCollections.bind(this));
  288. }
  289. contextMenu.appendSeparator();
  290. contextMenu.appendCheckboxItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Word wrap" : "Word Wrap"), toggleWordWrap.bind(this), WebInspector.settings.domWordWrap.get());
  291. contextMenu.show();
  292. },
  293. _showNamedFlowCollections: function()
  294. {
  295. if (!WebInspector.cssNamedFlowCollectionsView)
  296. WebInspector.cssNamedFlowCollectionsView = new WebInspector.CSSNamedFlowCollectionsView();
  297. WebInspector.cssNamedFlowCollectionsView.showInDrawer();
  298. },
  299. _domWordWrapSettingChanged: function(event)
  300. {
  301. if (event.data)
  302. this.contentElement.removeStyleClass("nowrap");
  303. else
  304. this.contentElement.addStyleClass("nowrap");
  305. var selectedNode = this.selectedDOMNode();
  306. if (!selectedNode)
  307. return;
  308. var treeElement = this.treeOutline.findTreeElement(selectedNode);
  309. if (treeElement)
  310. treeElement.updateSelection(); // Recalculate selection highlight dimensions.
  311. },
  312. switchToAndFocus: function(node)
  313. {
  314. // Reset search restore.
  315. WebInspector.searchController.cancelSearch();
  316. WebInspector.inspectorView.setCurrentPanel(this);
  317. this.selectDOMNode(node, true);
  318. },
  319. _populateContextMenu: function(contextMenu, node)
  320. {
  321. // Add debbuging-related actions
  322. contextMenu.appendSeparator();
  323. var pane = WebInspector.domBreakpointsSidebarPane;
  324. pane.populateNodeContextMenu(node, contextMenu);
  325. },
  326. _getPopoverAnchor: function(element)
  327. {
  328. var anchor = element.enclosingNodeOrSelfWithClass("webkit-html-resource-link");
  329. if (anchor) {
  330. if (!anchor.href)
  331. return null;
  332. var resource = WebInspector.resourceTreeModel.resourceForURL(anchor.href);
  333. if (!resource || resource.type !== WebInspector.resourceTypes.Image)
  334. return null;
  335. anchor.removeAttribute("title");
  336. }
  337. return anchor;
  338. },
  339. _loadDimensionsForNode: function(treeElement, callback)
  340. {
  341. // We get here for CSS properties, too, so bail out early for non-DOM treeElements.
  342. if (treeElement.treeOutline !== this.treeOutline) {
  343. callback();
  344. return;
  345. }
  346. var node = /** @type {WebInspector.DOMNode} */ (treeElement.representedObject);
  347. if (!node.nodeName() || node.nodeName().toLowerCase() !== "img") {
  348. callback();
  349. return;
  350. }
  351. WebInspector.RemoteObject.resolveNode(node, "", resolvedNode);
  352. function resolvedNode(object)
  353. {
  354. if (!object) {
  355. callback();
  356. return;
  357. }
  358. object.callFunctionJSON(dimensions, undefined, callback);
  359. object.release();
  360. function dimensions()
  361. {
  362. return { offsetWidth: this.offsetWidth, offsetHeight: this.offsetHeight, naturalWidth: this.naturalWidth, naturalHeight: this.naturalHeight };
  363. }
  364. }
  365. },
  366. /**
  367. * @param {Element} anchor
  368. * @param {WebInspector.Popover} popover
  369. */
  370. _showPopover: function(anchor, popover)
  371. {
  372. var listItem = anchor.enclosingNodeOrSelfWithNodeName("li");
  373. if (listItem && listItem.treeElement)
  374. this._loadDimensionsForNode(listItem.treeElement, WebInspector.DOMPresentationUtils.buildImagePreviewContents.bind(WebInspector.DOMPresentationUtils, anchor.href, true, showPopover));
  375. else
  376. WebInspector.DOMPresentationUtils.buildImagePreviewContents(anchor.href, true, showPopover);
  377. /**
  378. * @param {Element=} contents
  379. */
  380. function showPopover(contents)
  381. {
  382. if (!contents)
  383. return;
  384. popover.setCanShrink(false);
  385. popover.show(contents, anchor);
  386. }
  387. },
  388. jumpToNextSearchResult: function()
  389. {
  390. if (!this._searchResults)
  391. return;
  392. this._hideSearchHighlights();
  393. if (++this._currentSearchResultIndex >= this._searchResults.length)
  394. this._currentSearchResultIndex = 0;
  395. this._highlightCurrentSearchResult();
  396. },
  397. jumpToPreviousSearchResult: function()
  398. {
  399. if (!this._searchResults)
  400. return;
  401. this._hideSearchHighlights();
  402. if (--this._currentSearchResultIndex < 0)
  403. this._currentSearchResultIndex = (this._searchResults.length - 1);
  404. this._highlightCurrentSearchResult();
  405. },
  406. _highlightCurrentSearchResult: function()
  407. {
  408. var index = this._currentSearchResultIndex;
  409. var searchResults = this._searchResults;
  410. var searchResult = searchResults[index];
  411. if (searchResult === null) {
  412. WebInspector.searchController.updateCurrentMatchIndex(index, this);
  413. return;
  414. }
  415. if (typeof searchResult === "undefined") {
  416. // No data for slot, request it.
  417. function callback(node)
  418. {
  419. searchResults[index] = node || null;
  420. this._highlightCurrentSearchResult();
  421. }
  422. WebInspector.domAgent.searchResult(index, callback.bind(this));
  423. return;
  424. }
  425. WebInspector.searchController.updateCurrentMatchIndex(index, this);
  426. var treeElement = this.treeOutline.findTreeElement(searchResult);
  427. if (treeElement) {
  428. treeElement.highlightSearchResults(this._searchQuery);
  429. treeElement.reveal();
  430. var matches = treeElement.listItemElement.getElementsByClassName("webkit-search-result");
  431. if (matches.length)
  432. matches[0].scrollIntoViewIfNeeded();
  433. }
  434. },
  435. _hideSearchHighlights: function()
  436. {
  437. if (!this._searchResults)
  438. return;
  439. var searchResult = this._searchResults[this._currentSearchResultIndex];
  440. if (!searchResult)
  441. return;
  442. var treeElement = this.treeOutline.findTreeElement(searchResult);
  443. if (treeElement)
  444. treeElement.hideSearchHighlights();
  445. },
  446. selectedDOMNode: function()
  447. {
  448. return this.treeOutline.selectedDOMNode();
  449. },
  450. /**
  451. * @param {boolean=} focus
  452. */
  453. selectDOMNode: function(node, focus)
  454. {
  455. this.treeOutline.selectDOMNode(node, focus);
  456. },
  457. _nodeRemoved: function(event)
  458. {
  459. if (!this.isShowing())
  460. return;
  461. var crumbs = this.crumbsElement;
  462. for (var crumb = crumbs.firstChild; crumb; crumb = crumb.nextSibling) {
  463. if (crumb.representedObject === event.data.node) {
  464. this.updateBreadcrumb(true);
  465. return;
  466. }
  467. }
  468. },
  469. _stylesPaneEdited: function()
  470. {
  471. // Once styles are edited, the Metrics pane should be updated.
  472. this.sidebarPanes.metrics.needsUpdate = true;
  473. this.updateMetrics();
  474. },
  475. _metricsPaneEdited: function()
  476. {
  477. // Once metrics are edited, the Styles pane should be updated.
  478. this.sidebarPanes.styles.needsUpdate = true;
  479. this.updateStyles(true);
  480. },
  481. _mouseMovedInCrumbs: function(event)
  482. {
  483. var nodeUnderMouse = document.elementFromPoint(event.pageX, event.pageY);
  484. var crumbElement = nodeUnderMouse.enclosingNodeOrSelfWithClass("crumb");
  485. WebInspector.domAgent.highlightDOMNode(crumbElement ? crumbElement.representedObject.id : 0);
  486. if ("_mouseOutOfCrumbsTimeout" in this) {
  487. clearTimeout(this._mouseOutOfCrumbsTimeout);
  488. delete this._mouseOutOfCrumbsTimeout;
  489. }
  490. },
  491. _mouseMovedOutOfCrumbs: function(event)
  492. {
  493. var nodeUnderMouse = document.elementFromPoint(event.pageX, event.pageY);
  494. if (nodeUnderMouse && nodeUnderMouse.isDescendant(this.crumbsElement))
  495. return;
  496. WebInspector.domAgent.hideDOMNodeHighlight();
  497. this._mouseOutOfCrumbsTimeout = setTimeout(this.updateBreadcrumbSizes.bind(this), 1000);
  498. },
  499. _updateBreadcrumbIfNeeded: function(event)
  500. {
  501. var name = event.data.name;
  502. if (name !== "class" && name !== "id")
  503. return;
  504. var node = /** @type {WebInspector.DOMNode} */ (event.data.node);
  505. var crumbs = this.crumbsElement;
  506. var crumb = crumbs.firstChild;
  507. while (crumb) {
  508. if (crumb.representedObject === node) {
  509. this.updateBreadcrumb(true);
  510. break;
  511. }
  512. crumb = crumb.nextSibling;
  513. }
  514. },
  515. /**
  516. * @param {boolean=} forceUpdate
  517. */
  518. updateBreadcrumb: function(forceUpdate)
  519. {
  520. if (!this.isShowing())
  521. return;
  522. var crumbs = this.crumbsElement;
  523. var handled = false;
  524. var crumb = crumbs.firstChild;
  525. while (crumb) {
  526. if (crumb.representedObject === this.selectedDOMNode()) {
  527. crumb.addStyleClass("selected");
  528. handled = true;
  529. } else {
  530. crumb.removeStyleClass("selected");
  531. }
  532. crumb = crumb.nextSibling;
  533. }
  534. if (handled && !forceUpdate) {
  535. // We don't need to rebuild the crumbs, but we need to adjust sizes
  536. // to reflect the new focused or root node.
  537. this.updateBreadcrumbSizes();
  538. return;
  539. }
  540. crumbs.removeChildren();
  541. var panel = this;
  542. function selectCrumbFunction(event)
  543. {
  544. var crumb = event.currentTarget;
  545. if (crumb.hasStyleClass("collapsed")) {
  546. // Clicking a collapsed crumb will expose the hidden crumbs.
  547. if (crumb === panel.crumbsElement.firstChild) {
  548. // If the focused crumb is the first child, pick the farthest crumb
  549. // that is still hidden. This allows the user to expose every crumb.
  550. var currentCrumb = crumb;
  551. while (currentCrumb) {
  552. var hidden = currentCrumb.hasStyleClass("hidden");
  553. var collapsed = currentCrumb.hasStyleClass("collapsed");
  554. if (!hidden && !collapsed)
  555. break;
  556. crumb = currentCrumb;
  557. currentCrumb = currentCrumb.nextSibling;
  558. }
  559. }
  560. panel.updateBreadcrumbSizes(crumb);
  561. } else
  562. panel.selectDOMNode(crumb.representedObject, true);
  563. event.preventDefault();
  564. }
  565. for (var current = this.selectedDOMNode(); current; current = current.parentNode) {
  566. if (current.nodeType() === Node.DOCUMENT_NODE)
  567. continue;
  568. crumb = document.createElement("span");
  569. crumb.className = "crumb";
  570. crumb.representedObject = current;
  571. crumb.addEventListener("mousedown", selectCrumbFunction, false);
  572. var crumbTitle;
  573. switch (current.nodeType()) {
  574. case Node.ELEMENT_NODE:
  575. WebInspector.DOMPresentationUtils.decorateNodeLabel(current, crumb);
  576. break;
  577. case Node.TEXT_NODE:
  578. crumbTitle = WebInspector.UIString("(text)");
  579. break
  580. case Node.COMMENT_NODE:
  581. crumbTitle = "<!-->";
  582. break;
  583. case Node.DOCUMENT_TYPE_NODE:
  584. crumbTitle = "<!DOCTYPE>";
  585. break;
  586. default:
  587. crumbTitle = current.nodeNameInCorrectCase();
  588. }
  589. if (!crumb.childNodes.length) {
  590. var nameElement = document.createElement("span");
  591. nameElement.textContent = crumbTitle;
  592. crumb.appendChild(nameElement);
  593. crumb.title = crumbTitle;
  594. }
  595. if (current === this.selectedDOMNode())
  596. crumb.addStyleClass("selected");
  597. if (!crumbs.childNodes.length)
  598. crumb.addStyleClass("end");
  599. crumbs.appendChild(crumb);
  600. }
  601. if (crumbs.hasChildNodes())
  602. crumbs.lastChild.addStyleClass("start");
  603. this.updateBreadcrumbSizes();
  604. },
  605. /**
  606. * @param {Element=} focusedCrumb
  607. */
  608. updateBreadcrumbSizes: function(focusedCrumb)
  609. {
  610. if (!this.isShowing())
  611. return;
  612. if (document.body.offsetWidth <= 0) {
  613. // The stylesheet hasn't loaded yet or the window is closed,
  614. // so we can't calculate what is need. Return early.
  615. return;
  616. }
  617. var crumbs = this.crumbsElement;
  618. if (!crumbs.childNodes.length || crumbs.offsetWidth <= 0)
  619. return; // No crumbs, do nothing.
  620. // A Zero index is the right most child crumb in the breadcrumb.
  621. var selectedIndex = 0;
  622. var focusedIndex = 0;
  623. var selectedCrumb;
  624. var i = 0;
  625. var crumb = crumbs.firstChild;
  626. while (crumb) {
  627. // Find the selected crumb and index.
  628. if (!selectedCrumb && crumb.hasStyleClass("selected")) {
  629. selectedCrumb = crumb;
  630. selectedIndex = i;
  631. }
  632. // Find the focused crumb index.
  633. if (crumb === focusedCrumb)
  634. focusedIndex = i;
  635. // Remove any styles that affect size before
  636. // deciding to shorten any crumbs.
  637. if (crumb !== crumbs.lastChild)
  638. crumb.removeStyleClass("start");
  639. if (crumb !== crumbs.firstChild)
  640. crumb.removeStyleClass("end");
  641. crumb.removeStyleClass("compact");
  642. crumb.removeStyleClass("collapsed");
  643. crumb.removeStyleClass("hidden");
  644. crumb = crumb.nextSibling;
  645. ++i;
  646. }
  647. // Restore the start and end crumb classes in case they got removed in coalesceCollapsedCrumbs().
  648. // The order of the crumbs in the document is opposite of the visual order.
  649. crumbs.firstChild.addStyleClass("end");
  650. crumbs.lastChild.addStyleClass("start");
  651. function crumbsAreSmallerThanContainer()
  652. {
  653. var rightPadding = 20;
  654. var errorWarningElement = document.getElementById("error-warning-count");
  655. if (!WebInspector.drawer.visible && errorWarningElement)
  656. rightPadding += errorWarningElement.offsetWidth;
  657. return ((crumbs.totalOffsetLeft() + crumbs.offsetWidth + rightPadding) < window.innerWidth);
  658. }
  659. if (crumbsAreSmallerThanContainer())
  660. return; // No need to compact the crumbs, they all fit at full size.
  661. var BothSides = 0;
  662. var AncestorSide = -1;
  663. var ChildSide = 1;
  664. /**
  665. * @param {boolean=} significantCrumb
  666. */
  667. function makeCrumbsSmaller(shrinkingFunction, direction, significantCrumb)
  668. {
  669. if (!significantCrumb)
  670. significantCrumb = (focusedCrumb || selectedCrumb);
  671. if (significantCrumb === selectedCrumb)
  672. var significantIndex = selectedIndex;
  673. else if (significantCrumb === focusedCrumb)
  674. var significantIndex = focusedIndex;
  675. else {
  676. var significantIndex = 0;
  677. for (var i = 0; i < crumbs.childNodes.length; ++i) {
  678. if (crumbs.childNodes[i] === significantCrumb) {
  679. significantIndex = i;
  680. break;
  681. }
  682. }
  683. }
  684. function shrinkCrumbAtIndex(index)
  685. {
  686. var shrinkCrumb = crumbs.childNodes[index];
  687. if (shrinkCrumb && shrinkCrumb !== significantCrumb)
  688. shrinkingFunction(shrinkCrumb);
  689. if (crumbsAreSmallerThanContainer())
  690. return true; // No need to compact the crumbs more.
  691. return false;
  692. }
  693. // Shrink crumbs one at a time by applying the shrinkingFunction until the crumbs
  694. // fit in the container or we run out of crumbs to shrink.
  695. if (direction) {
  696. // Crumbs are shrunk on only one side (based on direction) of the signifcant crumb.
  697. var index = (direction > 0 ? 0 : crumbs.childNodes.length - 1);
  698. while (index !== significantIndex) {
  699. if (shrinkCrumbAtIndex(index))
  700. return true;
  701. index += (direction > 0 ? 1 : -1);
  702. }
  703. } else {
  704. // Crumbs are shrunk in order of descending distance from the signifcant crumb,
  705. // with a tie going to child crumbs.
  706. var startIndex = 0;
  707. var endIndex = crumbs.childNodes.length - 1;
  708. while (startIndex != significantIndex || endIndex != significantIndex) {
  709. var startDistance = significantIndex - startIndex;
  710. var endDistance = endIndex - significantIndex;
  711. if (startDistance >= endDistance)
  712. var index = startIndex++;
  713. else
  714. var index = endIndex--;
  715. if (shrinkCrumbAtIndex(index))
  716. return true;
  717. }
  718. }
  719. // We are not small enough yet, return false so the caller knows.
  720. return false;
  721. }
  722. function coalesceCollapsedCrumbs()
  723. {
  724. var crumb = crumbs.firstChild;
  725. var collapsedRun = false;
  726. var newStartNeeded = false;
  727. var newEndNeeded = false;
  728. while (crumb) {
  729. var hidden = crumb.hasStyleClass("hidden");
  730. if (!hidden) {
  731. var collapsed = crumb.hasStyleClass("collapsed");
  732. if (collapsedRun && collapsed) {
  733. crumb.addStyleClass("hidden");
  734. crumb.removeStyleClass("compact");
  735. crumb.removeStyleClass("collapsed");
  736. if (crumb.hasStyleClass("start")) {
  737. crumb.removeStyleClass("start");
  738. newStartNeeded = true;
  739. }
  740. if (crumb.hasStyleClass("end")) {
  741. crumb.removeStyleClass("end");
  742. newEndNeeded = true;
  743. }
  744. continue;
  745. }
  746. collapsedRun = collapsed;
  747. if (newEndNeeded) {
  748. newEndNeeded = false;
  749. crumb.addStyleClass("end");
  750. }
  751. } else
  752. collapsedRun = true;
  753. crumb = crumb.nextSibling;
  754. }
  755. if (newStartNeeded) {
  756. crumb = crumbs.lastChild;
  757. while (crumb) {
  758. if (!crumb.hasStyleClass("hidden")) {
  759. crumb.addStyleClass("start");
  760. break;
  761. }
  762. crumb = crumb.previousSibling;
  763. }
  764. }
  765. }
  766. function compact(crumb)
  767. {
  768. if (crumb.hasStyleClass("hidden"))
  769. return;
  770. crumb.addStyleClass("compact");
  771. }
  772. function collapse(crumb, dontCoalesce)
  773. {
  774. if (crumb.hasStyleClass("hidden"))
  775. return;
  776. crumb.addStyleClass("collapsed");
  777. crumb.removeStyleClass("compact");
  778. if (!dontCoalesce)
  779. coalesceCollapsedCrumbs();
  780. }
  781. if (!focusedCrumb) {
  782. // When not focused on a crumb we can be biased and collapse less important
  783. // crumbs that the user might not care much about.
  784. // Compact child crumbs.
  785. if (makeCrumbsSmaller(compact, ChildSide))
  786. return;
  787. // Collapse child crumbs.
  788. if (makeCrumbsSmaller(collapse, ChildSide))
  789. return;
  790. }
  791. // Compact ancestor crumbs, or from both sides if focused.
  792. if (makeCrumbsSmaller(compact, (focusedCrumb ? BothSides : AncestorSide)))
  793. return;
  794. // Collapse ancestor crumbs, or from both sides if focused.
  795. if (makeCrumbsSmaller(collapse, (focusedCrumb ? BothSides : AncestorSide)))
  796. return;
  797. if (!selectedCrumb)
  798. return;
  799. // Compact the selected crumb.
  800. compact(selectedCrumb);
  801. if (crumbsAreSmallerThanContainer())
  802. return;
  803. // Collapse the selected crumb as a last resort. Pass true to prevent coalescing.
  804. collapse(selectedCrumb, true);
  805. },
  806. /**
  807. * @param {boolean=} forceUpdate
  808. */
  809. updateStyles: function(forceUpdate)
  810. {
  811. var stylesSidebarPane = this.sidebarPanes.styles;
  812. var computedStylePane = this.sidebarPanes.computedStyle;
  813. if ((!stylesSidebarPane.isShowing() && !computedStylePane.isShowing()) || !stylesSidebarPane.needsUpdate)
  814. return;
  815. stylesSidebarPane.update(this.selectedDOMNode(), forceUpdate);
  816. stylesSidebarPane.needsUpdate = false;
  817. },
  818. updateMetrics: function()
  819. {
  820. var metricsSidebarPane = this.sidebarPanes.metrics;
  821. if (!metricsSidebarPane.isShowing() || !metricsSidebarPane.needsUpdate)
  822. return;
  823. metricsSidebarPane.update(this.selectedDOMNode());
  824. metricsSidebarPane.needsUpdate = false;
  825. },
  826. updateProperties: function()
  827. {
  828. var propertiesSidebarPane = this.sidebarPanes.properties;
  829. if (!propertiesSidebarPane.isShowing() || !propertiesSidebarPane.needsUpdate)
  830. return;
  831. propertiesSidebarPane.update(this.selectedDOMNode());
  832. propertiesSidebarPane.needsUpdate = false;
  833. },
  834. updateEventListeners: function()
  835. {
  836. var eventListenersSidebarPane = this.sidebarPanes.eventListeners;
  837. if (!eventListenersSidebarPane.isShowing() || !eventListenersSidebarPane.needsUpdate)
  838. return;
  839. eventListenersSidebarPane.update(this.selectedDOMNode());
  840. eventListenersSidebarPane.needsUpdate = false;
  841. },
  842. handleShortcut: function(event)
  843. {
  844. function handleUndoRedo()
  845. {
  846. if (WebInspector.KeyboardShortcut.eventHasCtrlOrMeta(event) && !event.shiftKey && event.keyIdentifier === "U+005A") { // Z key
  847. WebInspector.domAgent.undo(this._updateSidebars.bind(this));
  848. event.handled = true;
  849. return;
  850. }
  851. var isRedoKey = WebInspector.isMac() ? event.metaKey && event.shiftKey && event.keyIdentifier === "U+005A" : // Z key
  852. event.ctrlKey && event.keyIdentifier === "U+0059"; // Y key
  853. if (isRedoKey) {
  854. DOMAgent.redo(this._updateSidebars.bind(this));
  855. event.handled = true;
  856. }
  857. }
  858. if (!this.treeOutline.editing()) {
  859. handleUndoRedo.call(this);
  860. if (event.handled)
  861. return;
  862. }
  863. this.treeOutline.handleShortcut(event);
  864. },
  865. handleCopyEvent: function(event)
  866. {
  867. // Don't prevent the normal copy if the user has a selection.
  868. if (!window.getSelection().isCollapsed)
  869. return;
  870. event.clipboardData.clearData();
  871. event.preventDefault();
  872. this.selectedDOMNode().copyNode();
  873. },
  874. sidebarResized: function(event)
  875. {
  876. this.treeOutline.updateSelection();
  877. },
  878. _inspectElementRequested: function(event)
  879. {
  880. var node = event.data;
  881. this.revealAndSelectNode(node.id);
  882. },
  883. revealAndSelectNode: function(nodeId)
  884. {
  885. WebInspector.inspectorView.setCurrentPanel(this);
  886. var node = WebInspector.domAgent.nodeForId(nodeId);
  887. if (!node)
  888. return;
  889. WebInspector.domAgent.highlightDOMNodeForTwoSeconds(nodeId);
  890. this.selectDOMNode(node, true);
  891. },
  892. /**
  893. * @param {WebInspector.ContextMenu} contextMenu
  894. * @param {Object} target
  895. */
  896. appendApplicableItems: function(event, contextMenu, target)
  897. {
  898. if (!(target instanceof WebInspector.RemoteObject))
  899. return;
  900. var remoteObject = /** @type {WebInspector.RemoteObject} */ (target);
  901. if (remoteObject.subtype !== "node")
  902. return;
  903. function selectNode(nodeId)
  904. {
  905. if (nodeId)
  906. WebInspector.domAgent.inspectElement(nodeId);
  907. }
  908. function revealElement()
  909. {
  910. remoteObject.pushNodeToFrontend(selectNode);
  911. }
  912. contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Reveal in Elements panel" : "Reveal in Elements Panel"), revealElement.bind(this));
  913. },
  914. _sidebarContextMenuEventFired: function(event)
  915. {
  916. var contextMenu = new WebInspector.ContextMenu(event);
  917. contextMenu.show();
  918. },
  919. _dockSideChanged: function()
  920. {
  921. var dockSide = WebInspector.dockController.dockSide();
  922. var vertically = dockSide === WebInspector.DockController.State.DockedToRight && WebInspector.settings.splitVerticallyWhenDockedToRight.get();
  923. this._splitVertically(vertically);
  924. },
  925. /**
  926. * @param {boolean} vertically
  927. */
  928. _splitVertically: function(vertically)
  929. {
  930. if (this.sidebarPaneView && vertically === !this.splitView.isVertical())
  931. return;
  932. if (this.sidebarPaneView)
  933. this.sidebarPaneView.detach();
  934. this.splitView.setVertical(!vertically);
  935. if (!vertically) {
  936. this.sidebarPaneView = new WebInspector.SidebarPaneStack();
  937. for (var pane in this.sidebarPanes)
  938. this.sidebarPaneView.addPane(this.sidebarPanes[pane]);
  939. } else {
  940. this.sidebarPaneView = new WebInspector.SidebarTabbedPane();
  941. var compositePane = new WebInspector.SidebarPane(this.sidebarPanes.styles.title());
  942. compositePane.element.addStyleClass("composite");
  943. compositePane.element.addStyleClass("fill");
  944. var expandComposite = compositePane.expand.bind(compositePane);
  945. var splitView = new WebInspector.SplitView(true, "StylesPaneSplitRatio", 0.5);
  946. splitView.show(compositePane.bodyElement);
  947. this.sidebarPanes.styles.show(splitView.firstElement());
  948. splitView.firstElement().appendChild(this.sidebarPanes.styles.titleElement);
  949. this.sidebarPanes.styles.setExpandCallback(expandComposite);
  950. this.sidebarPanes.metrics.show(splitView.secondElement());
  951. this.sidebarPanes.metrics.setExpandCallback(expandComposite);
  952. splitView.secondElement().appendChild(this.sidebarPanes.computedStyle.titleElement);
  953. splitView.secondElement().addStyleClass("metrics-and-computed");
  954. this.sidebarPanes.computedStyle.show(splitView.secondElement());
  955. this.sidebarPanes.computedStyle.setExpandCallback(expandComposite);
  956. this.sidebarPaneView.addPane(compositePane);
  957. this.sidebarPaneView.addPane(this.sidebarPanes.properties);
  958. this.sidebarPaneView.addPane(this.sidebarPanes.domBreakpoints);
  959. this.sidebarPaneView.addPane(this.sidebarPanes.eventListeners);
  960. }
  961. this.sidebarPaneView.show(this.splitView.sidebarElement);
  962. this.sidebarPanes.styles.expand();
  963. },
  964. /**
  965. * @param {string} id
  966. * @param {WebInspector.SidebarPane} pane
  967. */
  968. addExtensionSidebarPane: function(id, pane)
  969. {
  970. this.sidebarPanes[id] = pane;
  971. this.sidebarPaneView.addPane(pane);
  972. },
  973. __proto__: WebInspector.Panel.prototype
  974. }