NavigationSidebarPanel.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508
  1. /*
  2. * Copyright (C) 2013 Apple Inc. All rights reserved.
  3. *
  4. * Redistribution and use in source and binary forms, with or without
  5. * modification, are permitted provided that the following conditions
  6. * are met:
  7. * 1. Redistributions of source code must retain the above copyright
  8. * notice, this list of conditions and the following disclaimer.
  9. * 2. Redistributions in binary form must reproduce the above copyright
  10. * notice, this list of conditions and the following disclaimer in the
  11. * documentation and/or other materials provided with the distribution.
  12. *
  13. * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
  14. * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
  15. * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  16. * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
  17. * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  18. * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  19. * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  20. * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  21. * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  22. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
  23. * THE POSSIBILITY OF SUCH DAMAGE.
  24. */
  25. WebInspector.NavigationSidebarPanel = function(identifier, displayName, image, keyboardShortcutKey, autoPruneOldTopLevelResourceTreeElements, autoHideToolbarItemWhenEmpty, wantsTopOverflowShadow, element, role, label) {
  26. if (keyboardShortcutKey)
  27. this._keyboardShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.Control, keyboardShortcutKey, this.toggle.bind(this));
  28. if (this._keyboardShortcut) {
  29. var showToolTip = WebInspector.UIString("Show the %s navigation sidebar (%s)").format(displayName, this._keyboardShortcut.displayName);
  30. var hideToolTip = WebInspector.UIString("Hide the %s navigation sidebar (%s)").format(displayName, this._keyboardShortcut.displayName);
  31. } else {
  32. var showToolTip = WebInspector.UIString("Show the %s navigation sidebar").format(displayName);
  33. var hideToolTip = WebInspector.UIString("Hide the %s navigation sidebar").format(displayName);
  34. }
  35. WebInspector.SidebarPanel.call(this, identifier, displayName, showToolTip, hideToolTip, image, element, role, label || displayName);
  36. this.element.classList.add(WebInspector.NavigationSidebarPanel.StyleClassName);
  37. this._autoHideToolbarItemWhenEmpty = autoHideToolbarItemWhenEmpty || false;
  38. if (autoHideToolbarItemWhenEmpty)
  39. this.toolbarItem.hidden = true;
  40. this._contentElement = document.createElement("div");
  41. this._contentElement.className = WebInspector.NavigationSidebarPanel.ContentElementStyleClassName;
  42. this._contentElement.addEventListener("scroll", this._updateContentOverflowShadowVisibility.bind(this));
  43. this.element.appendChild(this._contentElement);
  44. this._contentTreeOutline = this.createContentTreeOutline(true);
  45. this._filterBar = new WebInspector.FilterBar();
  46. this._filterBar.addEventListener(WebInspector.FilterBar.Event.TextFilterDidChange, this._updateFilter, this);
  47. this.element.appendChild(this._filterBar.element);
  48. this._bottomOverflowShadowElement = document.createElement("div");
  49. this._bottomOverflowShadowElement.className = WebInspector.NavigationSidebarPanel.OverflowShadowElementStyleClassName;
  50. this.element.appendChild(this._bottomOverflowShadowElement);
  51. if (wantsTopOverflowShadow) {
  52. this._topOverflowShadowElement = document.createElement("div");
  53. this._topOverflowShadowElement.classList.add(WebInspector.NavigationSidebarPanel.OverflowShadowElementStyleClassName);
  54. this._topOverflowShadowElement.classList.add(WebInspector.NavigationSidebarPanel.TopOverflowShadowElementStyleClassName);
  55. this.element.appendChild(this._topOverflowShadowElement);
  56. }
  57. window.addEventListener("resize", this._updateContentOverflowShadowVisibility.bind(this));
  58. this._filtersSetting = new WebInspector.Setting(identifier + "-navigation-sidebar-filters", {});
  59. this._filterBar.filters = this._filtersSetting.value;
  60. this._emptyContentPlaceholderElement = document.createElement("div");
  61. this._emptyContentPlaceholderElement.className = WebInspector.NavigationSidebarPanel.EmptyContentPlaceholderElementStyleClassName;
  62. this._emptyContentPlaceholderMessageElement = document.createElement("div");
  63. this._emptyContentPlaceholderMessageElement.className = WebInspector.NavigationSidebarPanel.EmptyContentPlaceholderMessageElementStyleClassName;
  64. this._emptyContentPlaceholderElement.appendChild(this._emptyContentPlaceholderMessageElement);
  65. this._generateStyleRulesIfNeeded();
  66. this._generateDisclosureTrianglesIfNeeded();
  67. if (autoPruneOldTopLevelResourceTreeElements) {
  68. WebInspector.Frame.addEventListener(WebInspector.Frame.Event.MainResourceDidChange, this._checkForOldResources, this);
  69. WebInspector.Frame.addEventListener(WebInspector.Frame.Event.ChildFrameWasRemoved, this._checkForOldResources, this);
  70. WebInspector.Frame.addEventListener(WebInspector.Frame.Event.ResourceWasRemoved, this._checkForOldResources, this);
  71. }
  72. };
  73. WebInspector.NavigationSidebarPanel.StyleClassName = "navigation";
  74. WebInspector.NavigationSidebarPanel.OverflowShadowElementStyleClassName = "overflow-shadow";
  75. WebInspector.NavigationSidebarPanel.TopOverflowShadowElementStyleClassName = "top";
  76. WebInspector.NavigationSidebarPanel.ContentElementStyleClassName = "content";
  77. WebInspector.NavigationSidebarPanel.ContentElementHiddenStyleClassName = "hidden";
  78. WebInspector.NavigationSidebarPanel.ContentTreeOutlineElementHiddenStyleClassName = "hidden";
  79. WebInspector.NavigationSidebarPanel.ContentTreeOutlineElementStyleClassName = "navigation-sidebar-panel-content-tree-outline";
  80. WebInspector.NavigationSidebarPanel.HideDisclosureButtonsStyleClassName = "hide-disclosure-buttons";
  81. WebInspector.NavigationSidebarPanel.EmptyContentPlaceholderElementStyleClassName = "empty-content-placeholder";
  82. WebInspector.NavigationSidebarPanel.EmptyContentPlaceholderMessageElementStyleClassName = "message";
  83. WebInspector.NavigationSidebarPanel.DisclosureTriangleOpenCanvasIdentifier = "navigation-sidebar-panel-disclosure-triangle-open";
  84. WebInspector.NavigationSidebarPanel.DisclosureTriangleClosedCanvasIdentifier = "navigation-sidebar-panel-disclosure-triangle-closed";
  85. WebInspector.NavigationSidebarPanel.DisclosureTriangleNormalCanvasIdentifierSuffix = "-normal";
  86. WebInspector.NavigationSidebarPanel.DisclosureTriangleSelectedCanvasIdentifierSuffix = "-selected";
  87. WebInspector.NavigationSidebarPanel.prototype = {
  88. constructor: WebInspector.NavigationSidebarPanel,
  89. // Public
  90. get contentElement()
  91. {
  92. return this._contentElement;
  93. },
  94. get contentTreeOutlineElement()
  95. {
  96. return this._contentTreeOutline.element;
  97. },
  98. get contentTreeOutline()
  99. {
  100. return this._contentTreeOutline;
  101. },
  102. set contentTreeOutline(newTreeOutline)
  103. {
  104. console.assert(newTreeOutline);
  105. if (!newTreeOutline)
  106. return;
  107. if (this._contentTreeOutline)
  108. this._contentTreeOutline.element.classList.add(WebInspector.NavigationSidebarPanel.ContentTreeOutlineElementHiddenStyleClassName);
  109. this._contentTreeOutline = newTreeOutline;
  110. this._contentTreeOutline.element.classList.remove(WebInspector.NavigationSidebarPanel.ContentTreeOutlineElementHiddenStyleClassName);
  111. this._updateFilter();
  112. },
  113. get contentTreeOutlineToAutoPrune()
  114. {
  115. return this._contentTreeOutline;
  116. },
  117. get filterBar()
  118. {
  119. return this._filterBar;
  120. },
  121. createContentTreeOutline: function(dontHideByDefault)
  122. {
  123. var contentTreeOutlineElement = document.createElement("ol");
  124. contentTreeOutlineElement.className = WebInspector.NavigationSidebarPanel.ContentTreeOutlineElementStyleClassName;
  125. if (!dontHideByDefault)
  126. contentTreeOutlineElement.classList.add(WebInspector.NavigationSidebarPanel.ContentTreeOutlineElementHiddenStyleClassName);
  127. this._contentElement.appendChild(contentTreeOutlineElement);
  128. var contentTreeOutline = new TreeOutline(contentTreeOutlineElement);
  129. contentTreeOutline.onadd = this._treeElementAddedOrChanged.bind(this);
  130. contentTreeOutline.onchange = this._treeElementAddedOrChanged.bind(this);
  131. contentTreeOutline.onexpand = this._treeElementExpandedOrCollapsed.bind(this);
  132. contentTreeOutline.oncollapse = this._treeElementExpandedOrCollapsed.bind(this);
  133. contentTreeOutline.allowsRepeatSelection = true;
  134. return contentTreeOutline;
  135. },
  136. treeElementForRepresentedObject: function(representedObject)
  137. {
  138. return this._contentTreeOutline.getCachedTreeElement(representedObject);
  139. },
  140. cookieForContentView: function(contentView)
  141. {
  142. // Implemented by subclasses.
  143. return null;
  144. },
  145. showContentViewForCookie: function(contentViewCookie)
  146. {
  147. // Implemented by subclasses.
  148. },
  149. showContentViewForCurrentSelection: function()
  150. {
  151. // Reselect the selected tree element to cause the content view to be shown as well. <rdar://problem/10854727>
  152. var selectedTreeElement = this._contentTreeOutline.selectedTreeElement;
  153. if (selectedTreeElement)
  154. selectedTreeElement.select();
  155. },
  156. showEmptyContentPlaceholder: function(message, hideToolbarItem)
  157. {
  158. console.assert(message);
  159. this._contentElement.classList.add(WebInspector.NavigationSidebarPanel.ContentElementHiddenStyleClassName);
  160. this._emptyContentPlaceholderMessageElement.textContent = message;
  161. this.element.appendChild(this._emptyContentPlaceholderElement);
  162. this._hideToolbarItemWhenEmpty = hideToolbarItem || false;
  163. this._updateToolbarItemVisibility();
  164. this._updateContentOverflowShadowVisibility();
  165. },
  166. hideEmptyContentPlaceholder: function()
  167. {
  168. this._contentElement.classList.remove(WebInspector.NavigationSidebarPanel.ContentElementHiddenStyleClassName);
  169. if (this._emptyContentPlaceholderElement.parentNode)
  170. this._emptyContentPlaceholderElement.parentNode.removeChild(this._emptyContentPlaceholderElement);
  171. this._hideToolbarItemWhenEmpty = false;
  172. this._updateToolbarItemVisibility();
  173. this._updateContentOverflowShadowVisibility();
  174. },
  175. updateEmptyContentPlaceholder: function(message)
  176. {
  177. this._updateToolbarItemVisibility();
  178. if (!this._contentTreeOutline.children.length) {
  179. // No tree elements, so no results.
  180. this.showEmptyContentPlaceholder(message);
  181. } else if (!this._emptyFilterResults) {
  182. // There are tree elements, and not all of them are hidden by the filter.
  183. this.hideEmptyContentPlaceholder();
  184. }
  185. },
  186. applyFiltersToTreeElement: function(treeElement)
  187. {
  188. if (!this._filterBar.hasActiveFilters()) {
  189. // No filters, so make everything visible.
  190. treeElement.hidden = false;
  191. // If this tree element was expanded during filtering, collapse it again.
  192. if (treeElement.expanded && treeElement.__wasExpandedDuringFiltering) {
  193. delete treeElement.__wasExpandedDuringFiltering;
  194. treeElement.collapse();
  195. }
  196. return;
  197. }
  198. // Get the filterable data from the tree element.
  199. var filterableData = treeElement.filterableData;
  200. if (!filterableData)
  201. return;
  202. var self = this;
  203. function matchTextFilter(input)
  204. {
  205. if (!self._textFilterRegex)
  206. return true;
  207. // Convert to a single item array if needed.
  208. if (!(input instanceof Array))
  209. input = [input];
  210. // Loop over all the inputs and try to match them.
  211. for (var i = 0; i < input.length; ++i) {
  212. if (!input[i])
  213. continue;
  214. if (self._textFilterRegex.test(input[i]))
  215. return true;
  216. }
  217. // No inputs matched.
  218. return false;
  219. }
  220. function makeVisible()
  221. {
  222. // Make this element visible.
  223. treeElement.hidden = false;
  224. // Make the ancestors visible and expand them.
  225. var currentAncestor = treeElement.parent;
  226. while (currentAncestor && !currentAncestor.root) {
  227. currentAncestor.hidden = false;
  228. if (!currentAncestor.expanded) {
  229. currentAncestor.__wasExpandedDuringFiltering = true;
  230. currentAncestor.expand();
  231. }
  232. currentAncestor = currentAncestor.parent;
  233. }
  234. }
  235. if (matchTextFilter(filterableData.text)) {
  236. // Make this element visible since it matches.
  237. makeVisible();
  238. return;
  239. }
  240. // Make this element invisible since it does not match.
  241. treeElement.hidden = true;
  242. },
  243. show: function()
  244. {
  245. if (!this.parentSidebar)
  246. return;
  247. WebInspector.SidebarPanel.prototype.show.call(this);
  248. this.contentTreeOutlineElement.focus();
  249. },
  250. shown: function()
  251. {
  252. WebInspector.SidebarPanel.prototype.shown.call(this);
  253. this._updateContentOverflowShadowVisibility();
  254. // Force the navigation item to be visible. This makes sure it is
  255. // always visible when the panel is shown.
  256. this.toolbarItem.hidden = false;
  257. },
  258. hidden: function()
  259. {
  260. WebInspector.SidebarPanel.prototype.hidden.call(this);
  261. this._updateToolbarItemVisibility();
  262. },
  263. // Private
  264. _updateContentOverflowShadowVisibility: function()
  265. {
  266. var scrollHeight = this._contentElement.scrollHeight;
  267. var offsetHeight = this._contentElement.offsetHeight;
  268. if (scrollHeight < offsetHeight) {
  269. if (this._topOverflowShadowElement)
  270. this._topOverflowShadowElement.style.opacity = 0;
  271. this._bottomOverflowShadowElement.style.opacity = 0;
  272. return;
  273. }
  274. const edgeThreshold = 10;
  275. var scrollTop = this._contentElement.scrollTop;
  276. var topCoverage = Math.min(scrollTop, edgeThreshold);
  277. var bottomCoverage = Math.max(0, (offsetHeight + scrollTop) - (scrollHeight - edgeThreshold))
  278. if (this._topOverflowShadowElement)
  279. this._topOverflowShadowElement.style.opacity = (topCoverage / edgeThreshold).toFixed(1);
  280. this._bottomOverflowShadowElement.style.opacity = (1 - (bottomCoverage / edgeThreshold)).toFixed(1);
  281. },
  282. _updateToolbarItemVisibility: function()
  283. {
  284. // Hide the navigation item if requested or auto-hiding and we are not visible and we are empty.
  285. var shouldHide = ((this._hideToolbarItemWhenEmpty || this._autoHideToolbarItemWhenEmpty) && !this.selected && !this._contentTreeOutline.children.length);
  286. this.toolbarItem.hidden = shouldHide;
  287. },
  288. _checkForEmptyFilterResults: function()
  289. {
  290. // No tree elements, so don't touch the empty content placeholder.
  291. if (!this._contentTreeOutline.children.length)
  292. return;
  293. // Iterate over all the top level tree elements. If any are visible, return early.
  294. var currentTreeElement = this._contentTreeOutline.children[0];
  295. while (currentTreeElement) {
  296. if (!currentTreeElement.hidden) {
  297. // Not hidden, so hide any empty content message.
  298. this.hideEmptyContentPlaceholder();
  299. this._emptyFilterResults = false;
  300. return;
  301. }
  302. currentTreeElement = currentTreeElement.nextSibling;
  303. }
  304. // All top level tree elements are hidden, so filtering hid everything. Show a message.
  305. this.showEmptyContentPlaceholder(WebInspector.UIString("No Filter Results"));
  306. this._emptyFilterResults = true;
  307. },
  308. _updateFilter: function()
  309. {
  310. var filters = this._filterBar.filters;
  311. this._textFilterRegex = simpleGlobStringToRegExp(filters.text, "i");
  312. this._filtersSetting.value = filters;
  313. // Update the whole tree.
  314. var currentTreeElement = this._contentTreeOutline.children[0];
  315. while (currentTreeElement && !currentTreeElement.root) {
  316. this.applyFiltersToTreeElement(currentTreeElement);
  317. currentTreeElement = currentTreeElement.traverseNextTreeElement(false, null, false);
  318. }
  319. this._checkForEmptyFilterResults();
  320. this._updateContentOverflowShadowVisibility();
  321. },
  322. _treeElementAddedOrChanged: function(treeElement)
  323. {
  324. // Apply the filters to the tree element and its descendants.
  325. var currentTreeElement = treeElement;
  326. while (currentTreeElement && !currentTreeElement.root) {
  327. this.applyFiltersToTreeElement(currentTreeElement);
  328. currentTreeElement = currentTreeElement.traverseNextTreeElement(false, treeElement, false);
  329. }
  330. this._checkForEmptyFilterResults();
  331. this._updateContentOverflowShadowVisibility();
  332. },
  333. _treeElementExpandedOrCollapsed: function(treeElement)
  334. {
  335. this._updateContentOverflowShadowVisibility();
  336. },
  337. _generateStyleRulesIfNeeded: function()
  338. {
  339. if (WebInspector.NavigationSidebarPanel._styleElement)
  340. return;
  341. WebInspector.NavigationSidebarPanel._styleElement = document.createElement("style");
  342. const maximumSidebarTreeDepth = 15;
  343. const baseLeftPadding = 5; // Matches the padding in NavigationSidebarPanel.css for the item class. Keep in sync.
  344. const depthPadding = 16;
  345. var styleText = "";
  346. var childrenSubstring = " > ";
  347. for (var i = 1; i <= maximumSidebarTreeDepth; ++i) {
  348. childrenSubstring += ".children > ";
  349. styleText += "." + WebInspector.NavigationSidebarPanel.ContentTreeOutlineElementStyleClassName + childrenSubstring + ".item { ";
  350. styleText += "padding-left: " + (baseLeftPadding + (depthPadding * i)) + "px; }\n";
  351. }
  352. WebInspector.NavigationSidebarPanel._styleElement.textContent = styleText;
  353. document.head.appendChild(WebInspector.NavigationSidebarPanel._styleElement);
  354. },
  355. _generateDisclosureTrianglesIfNeeded: function()
  356. {
  357. if (WebInspector.NavigationSidebarPanel._generatedDisclosureTriangles)
  358. return;
  359. // Set this early instead of in _generateDisclosureTriangle because we don't want multiple panels that are
  360. // created at the same time to duplicate the work (even though it would be harmless.)
  361. WebInspector.NavigationSidebarPanel._generatedDisclosureTriangles = true;
  362. var specifications = {};
  363. specifications[WebInspector.NavigationSidebarPanel.DisclosureTriangleNormalCanvasIdentifierSuffix] = {
  364. fillColor: [112, 126, 139],
  365. shadowColor: [255, 255, 255, 0.8],
  366. shadowOffsetX: 0,
  367. shadowOffsetY: 1,
  368. shadowBlur: 0
  369. };
  370. specifications[WebInspector.NavigationSidebarPanel.DisclosureTriangleSelectedCanvasIdentifierSuffix] = {
  371. fillColor: [255, 255, 255],
  372. shadowColor: [61, 91, 110, 0.8],
  373. shadowOffsetX: 0,
  374. shadowOffsetY: 1,
  375. shadowBlur: 2
  376. };
  377. generateColoredImagesForCSS("Images/DisclosureTriangleSmallOpen.pdf", specifications, 13, 13, WebInspector.NavigationSidebarPanel.DisclosureTriangleOpenCanvasIdentifier);
  378. generateColoredImagesForCSS("Images/DisclosureTriangleSmallClosed.pdf", specifications, 13, 13, WebInspector.NavigationSidebarPanel.DisclosureTriangleClosedCanvasIdentifier);
  379. },
  380. _checkForOldResources: function(event)
  381. {
  382. if (this._checkForOldResourcesTimeoutIdentifier)
  383. return;
  384. function delayedWork()
  385. {
  386. delete this._checkForOldResourcesTimeoutIdentifier;
  387. var contentTreeOutline = this.contentTreeOutlineToAutoPrune;
  388. // Check all the ResourceTreeElements at the top level to make sure their Resource still has a parentFrame in the frame hierarchy.
  389. // If the parentFrame is no longer in the frame hierarchy we know it was removed due to a navigation or some other page change and
  390. // we should remove the issues for that resource.
  391. for (var i = contentTreeOutline.children.length - 1; i >= 0; --i) {
  392. var treeElement = contentTreeOutline.children[i];
  393. if (!(treeElement instanceof WebInspector.ResourceTreeElement))
  394. continue;
  395. var resource = treeElement.resource;
  396. if (!resource.parentFrame || resource.parentFrame.isDetached())
  397. contentTreeOutline.removeChildAtIndex(i, true, true);
  398. }
  399. if (typeof this._updateEmptyContentPlaceholder === "function")
  400. this._updateEmptyContentPlaceholder();
  401. }
  402. // Check on a delay to coalesce multiple calls to _checkForOldResources.
  403. this._checkForOldResourcesTimeoutIdentifier = setTimeout(delayedWork.bind(this), 0);
  404. }
  405. };
  406. WebInspector.NavigationSidebarPanel.prototype.__proto__ = WebInspector.SidebarPanel.prototype;