Toolbar.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517
  1. /*
  2. * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved.
  3. * Copyright (C) 2007 Matt Lilek (pewtermoose@gmail.com).
  4. * Copyright (C) 2009 Joseph Pecoraro
  5. * Copyright (C) 2011 Google Inc. All rights reserved.
  6. *
  7. * Redistribution and use in source and binary forms, with or without
  8. * modification, are permitted provided that the following conditions
  9. * are met:
  10. *
  11. * 1. Redistributions of source code must retain the above copyright
  12. * notice, this list of conditions and the following disclaimer.
  13. * 2. Redistributions in binary form must reproduce the above copyright
  14. * notice, this list of conditions and the following disclaimer in the
  15. * documentation and/or other materials provided with the distribution.
  16. * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
  17. * its contributors may be used to endorse or promote products derived
  18. * from this software without specific prior written permission.
  19. *
  20. * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
  21. * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  22. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  23. * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
  24. * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  25. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  26. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  27. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  28. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  29. * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  30. */
  31. /**
  32. * @constructor
  33. */
  34. WebInspector.Toolbar = function()
  35. {
  36. this.element = document.getElementById("toolbar");
  37. WebInspector.installDragHandle(this.element, this._toolbarDragStart.bind(this), this._toolbarDrag.bind(this), this._toolbarDragEnd.bind(this), "default");
  38. this._dropdownButton = document.getElementById("toolbar-dropdown-arrow");
  39. this._dropdownButton.addEventListener("click", this._toggleDropdown.bind(this), false);
  40. this._panelsMenuButton = document.getElementById("toolbar-panels-menu");
  41. if (this._isToolbarCustomizable()) {
  42. this._panelsMenuButton.addEventListener("mousedown", this._togglePanelsMenu.bind(this), false);
  43. this._panelsMenuButton.removeStyleClass("hidden");
  44. }
  45. document.getElementById("close-button-left").addEventListener("click", this._onClose, true);
  46. document.getElementById("close-button-right").addEventListener("click", this._onClose, true);
  47. this._panelDescriptors = [];
  48. }
  49. WebInspector.Toolbar.prototype = {
  50. resize: function()
  51. {
  52. this._updateDropdownButtonAndHideDropdown();
  53. },
  54. /**
  55. * @param {!WebInspector.PanelDescriptor} panelDescriptor
  56. */
  57. addPanel: function(panelDescriptor)
  58. {
  59. this._panelDescriptors.push(panelDescriptor);
  60. panelDescriptor._toolbarElement = this._createPanelToolbarItem(panelDescriptor);
  61. if (!this._isToolbarCustomizable() || this._isPanelVisible(panelDescriptor.name()))
  62. this.element.insertBefore(panelDescriptor._toolbarElement, this._panelInsertLocation(panelDescriptor));
  63. this._updatePanelsMenuState();
  64. this.resize();
  65. },
  66. /**
  67. * @param {!WebInspector.PanelDescriptor} panelDescriptor
  68. * @return {Element}
  69. */
  70. _panelInsertLocation: function(panelDescriptor)
  71. {
  72. if (!this._isToolbarCustomizable())
  73. return null;
  74. if (this._isDefaultPanel(panelDescriptor.name()))
  75. return this._firstNonDefaultPanel || null;
  76. if (!this._firstNonDefaultPanel)
  77. this._firstNonDefaultPanel = panelDescriptor._toolbarElement;
  78. return null;
  79. },
  80. /**
  81. * @param {!string} name
  82. * @return {boolean}
  83. */
  84. _isDefaultPanel: function(name)
  85. {
  86. var defaultPanels = {
  87. "elements": true,
  88. "resources": true,
  89. "scripts": true,
  90. "console": true,
  91. "network": true,
  92. "timeline": true,
  93. };
  94. return !!defaultPanels[name];
  95. },
  96. /**
  97. * @param {!string} name
  98. * @return {boolean}
  99. */
  100. _isPanelVisibleByDefault: function(name)
  101. {
  102. var visible = {
  103. "elements": true,
  104. "console": true,
  105. "network": true,
  106. "scripts": true,
  107. "timeline": true,
  108. "profiles": true,
  109. "cpu-profiler": true,
  110. "heap-profiler": true,
  111. "audits": true,
  112. "resources": true,
  113. };
  114. return !!visible[name];
  115. },
  116. /**
  117. * @return {boolean}
  118. */
  119. _isToolbarCustomizable: function()
  120. {
  121. return WebInspector.experimentsSettings.customizableToolbar.isEnabled();
  122. },
  123. /**
  124. * @param {!string} name
  125. * @return {boolean}
  126. */
  127. _isPanelVisible: function(name)
  128. {
  129. if (!this._isToolbarCustomizable())
  130. return true;
  131. var visiblePanels = WebInspector.settings.visiblePanels.get();
  132. return visiblePanels.hasOwnProperty(name) ? visiblePanels[name] : this._isPanelVisibleByDefault(name);
  133. },
  134. /**
  135. * @param {!string} name
  136. * @param {boolean} visible
  137. */
  138. _setPanelVisible: function(name, visible)
  139. {
  140. var visiblePanels = WebInspector.settings.visiblePanels.get();
  141. visiblePanels[name] = visible;
  142. WebInspector.settings.visiblePanels.set(visiblePanels);
  143. },
  144. /**
  145. * @param {!WebInspector.PanelDescriptor} panelDescriptor
  146. */
  147. _hidePanel: function(panelDescriptor)
  148. {
  149. if (!this._isPanelVisible(panelDescriptor.name()))
  150. return;
  151. var switchToSibling = panelDescriptor._toolbarElement.nextSibling;
  152. if (!switchToSibling || !switchToSibling.classList.contains("toggleable"))
  153. switchToSibling = panelDescriptor._toolbarElement.previousSibling;
  154. if (!switchToSibling || !switchToSibling.classList || !switchToSibling.classList.contains("toggleable"))
  155. return;
  156. this._setPanelVisible(panelDescriptor.name(), false);
  157. this.element.removeChild(panelDescriptor._toolbarElement);
  158. if (WebInspector.inspectorView.currentPanel().name === panelDescriptor.name()) {
  159. for (var i = 0; i < this._panelDescriptors.length; ++i) {
  160. var descr = this._panelDescriptors[i];
  161. if (descr._toolbarElement === switchToSibling) {
  162. WebInspector.showPanel(descr.name());
  163. break;
  164. }
  165. }
  166. }
  167. this._updatePanelsMenuState();
  168. this.resize();
  169. },
  170. _updatePanelsMenuState: function()
  171. {
  172. if (this._panelDescriptors.every(function (descr) { return this._isPanelVisible(descr.name()); }, this) && this._allItemsFitOntoToolbar())
  173. document.getElementById("toolbar-panels-menu").addStyleClass("disabled");
  174. else
  175. document.getElementById("toolbar-panels-menu").removeStyleClass("disabled");
  176. },
  177. /**
  178. * @return {boolean}
  179. */
  180. _allItemsFitOntoToolbar: function()
  181. {
  182. var toolbarItems = this.element.querySelectorAll(".toolbar-item.toggleable");
  183. return toolbarItems.length === 0 || this.element.scrollHeight < toolbarItems[0].offsetHeight * 2;
  184. },
  185. /**
  186. * @param {!WebInspector.PanelDescriptor} panelDescriptor
  187. */
  188. _showPanel: function(panelDescriptor)
  189. {
  190. if (this._isPanelVisible(panelDescriptor.name()))
  191. return;
  192. this.element.appendChild(panelDescriptor._toolbarElement);
  193. panelDescriptor._toolbarElement.removeStyleClass("hidden");
  194. this._setPanelVisible(panelDescriptor.name(), true);
  195. this._updatePanelsMenuState();
  196. this.resize();
  197. },
  198. /**
  199. * @param {WebInspector.PanelDescriptor} panelDescriptor
  200. * @param {boolean=} noCloseButton
  201. * @return {Element}
  202. */
  203. _createPanelToolbarItem: function(panelDescriptor, noCloseButton)
  204. {
  205. var toolbarItem = document.createElement("button");
  206. toolbarItem.className = "toolbar-item toggleable";
  207. toolbarItem.panelDescriptor = panelDescriptor;
  208. toolbarItem.addStyleClass(panelDescriptor.name());
  209. /**
  210. * @param {Event} event
  211. */
  212. function onContextMenuEvent(event)
  213. {
  214. var contextMenu = new WebInspector.ContextMenu(event);
  215. contextMenu.appendItem(WebInspector.UIString("Close"), this._hidePanel.bind(this, panelDescriptor));
  216. contextMenu.show();
  217. }
  218. if (!this._isDefaultPanel(panelDescriptor.name()))
  219. toolbarItem.addEventListener("contextmenu", onContextMenuEvent.bind(this), true);
  220. function onToolbarItemClicked()
  221. {
  222. this._showPanel(panelDescriptor);
  223. this._updateDropdownButtonAndHideDropdown();
  224. WebInspector.inspectorView.setCurrentPanel(panelDescriptor.panel());
  225. }
  226. toolbarItem.addEventListener("click", onToolbarItemClicked.bind(this), false);
  227. function onToolbarItemCloseButtonClicked(event)
  228. {
  229. event.stopPropagation();
  230. this._hidePanel(panelDescriptor);
  231. }
  232. function panelSelected()
  233. {
  234. if (WebInspector.inspectorView.currentPanel() && panelDescriptor.name() === WebInspector.inspectorView.currentPanel().name)
  235. toolbarItem.addStyleClass("toggled-on");
  236. else
  237. toolbarItem.removeStyleClass("toggled-on");
  238. }
  239. WebInspector.inspectorView.addEventListener(WebInspector.InspectorView.Events.PanelSelected, panelSelected);
  240. toolbarItem.createChild("div", "toolbar-label").textContent = panelDescriptor.title();
  241. if (this._isToolbarCustomizable() && !this._isDefaultPanel(panelDescriptor.name()) && !noCloseButton) {
  242. var closeButton = toolbarItem.createChild("div", "close-button");
  243. closeButton.addEventListener("click", onToolbarItemCloseButtonClicked.bind(this), false);
  244. }
  245. panelSelected();
  246. return toolbarItem;
  247. },
  248. /**
  249. * @return {boolean}
  250. */
  251. _isDockedToBottom: function()
  252. {
  253. return !!WebInspector.dockController && WebInspector.dockController.dockSide() == WebInspector.DockController.State.DockedToBottom;
  254. },
  255. /**
  256. * @return {boolean}
  257. */
  258. _isUndocked: function()
  259. {
  260. return !!WebInspector.dockController && WebInspector.dockController.dockSide() == WebInspector.DockController.State.Undocked;
  261. },
  262. /**
  263. * @return {boolean}
  264. */
  265. _toolbarDragStart: function(event)
  266. {
  267. if (this._isUndocked())
  268. return false;
  269. var target = event.target;
  270. if (target.hasStyleClass("toolbar-item") && target.hasStyleClass("toggleable"))
  271. return false;
  272. if (target !== this.element && !target.hasStyleClass("toolbar-item"))
  273. return false;
  274. this._lastScreenX = event.screenX;
  275. this._lastScreenY = event.screenY;
  276. this._lastHeightDuringDrag = window.innerHeight;
  277. this._startDistanceToRight = window.innerWidth - event.clientX;
  278. this._startDinstanceToBottom = window.innerHeight - event.clientY;
  279. return true;
  280. },
  281. _toolbarDragEnd: function(event)
  282. {
  283. // We may not get the drag event at the end.
  284. // Apply last changes manually.
  285. this._toolbarDrag(event);
  286. delete this._lastScreenX;
  287. delete this._lastScreenY;
  288. delete this._lastHeightDuringDrag;
  289. delete this._startDistanceToRight;
  290. delete this._startDinstanceToBottom;
  291. },
  292. _toolbarDrag: function(event)
  293. {
  294. event.preventDefault();
  295. if (this._isUndocked())
  296. return this._toolbarDragMoveWindow(event);
  297. return this._toolbarDragChangeDocking(event);
  298. },
  299. _toolbarDragMoveWindow: function(event)
  300. {
  301. var x = event.screenX - this._lastScreenX;
  302. var y = event.screenY - this._lastScreenY;
  303. this._lastScreenX = event.screenX;
  304. this._lastScreenY = event.screenY;
  305. InspectorFrontendHost.moveWindowBy(x, y);
  306. },
  307. _toolbarDragChangeDocking: function(event)
  308. {
  309. if (this._isDockedToBottom()) {
  310. var distanceToRight = window.innerWidth - event.clientX;
  311. if (distanceToRight < this._startDistanceToRight * 2 / 3) {
  312. InspectorFrontendHost.requestSetDockSide(WebInspector.DockController.State.DockedToRight);
  313. return true;
  314. }
  315. } else {
  316. var distanceToBottom = window.innerHeight - event.clientY;
  317. if (distanceToBottom < this._startDinstanceToBottom * 2 / 3) {
  318. InspectorFrontendHost.requestSetDockSide(WebInspector.DockController.State.DockedToBottom);
  319. return true;
  320. }
  321. }
  322. },
  323. _onClose: function()
  324. {
  325. WebInspector.close();
  326. },
  327. _setDropdownVisible: function(visible)
  328. {
  329. if (!this._dropdown) {
  330. if (!visible)
  331. return;
  332. this._dropdown = new WebInspector.ToolbarDropdown(this);
  333. }
  334. if (visible)
  335. this._dropdown.show();
  336. else
  337. this._dropdown.hide();
  338. },
  339. _toggleDropdown: function()
  340. {
  341. this._setDropdownVisible(!this._dropdown || !this._dropdown.visible);
  342. },
  343. _togglePanelsMenu: function(event)
  344. {
  345. function activatePanel(panelDescriptor)
  346. {
  347. this._showPanel(panelDescriptor);
  348. WebInspector.showPanel(panelDescriptor.name());
  349. }
  350. var contextMenu = new WebInspector.ContextMenu(event);
  351. var currentPanelName = WebInspector.inspectorView.currentPanel().name;
  352. var toolbarItems = this.element.querySelectorAll(".toolbar-item.toggleable");
  353. for (var i = 0; i < toolbarItems.length; ++i) {
  354. if (toolbarItems[i].offsetTop >= toolbarItems[0].offsetHeight) {
  355. var descr = toolbarItems[i].panelDescriptor;
  356. if (descr.name() === currentPanelName)
  357. contextMenu.appendCheckboxItem(descr.title(), activatePanel.bind(this, descr), true);
  358. else
  359. contextMenu.appendItem(descr.title(), activatePanel.bind(this, descr));
  360. }
  361. }
  362. contextMenu.appendSeparator();
  363. for (var i = 0; i < this._panelDescriptors.length; ++i) {
  364. var descr = this._panelDescriptors[i];
  365. if (this._isPanelVisible(descr.name()))
  366. continue;
  367. contextMenu.appendItem(descr.title(), activatePanel.bind(this, descr));
  368. }
  369. contextMenu.showSoftMenu();
  370. },
  371. _updateDropdownButtonAndHideDropdown: function()
  372. {
  373. WebInspector.invokeOnceAfterBatchUpdate(this, this._innerUpdateDropdownButtonAndHideDropdown);
  374. },
  375. _innerUpdateDropdownButtonAndHideDropdown: function()
  376. {
  377. if (this._isToolbarCustomizable()) {
  378. this._updatePanelsMenuState();
  379. return;
  380. }
  381. this._setDropdownVisible(false);
  382. if (this.element.scrollHeight > this.element.offsetHeight)
  383. this._dropdownButton.removeStyleClass("hidden");
  384. else
  385. this._dropdownButton.addStyleClass("hidden");
  386. }
  387. }
  388. /**
  389. * @constructor
  390. * @param {WebInspector.Toolbar} toolbar
  391. */
  392. WebInspector.ToolbarDropdown = function(toolbar)
  393. {
  394. this._toolbar = toolbar;
  395. this._arrow = document.getElementById("toolbar-dropdown-arrow");
  396. this.element = document.createElement("div");
  397. this.element.id = "toolbar-dropdown";
  398. this.element.className = "toolbar-small";
  399. this._contentElement = this.element.createChild("div", "scrollable-content");
  400. this._contentElement.tabIndex = 0;
  401. this._contentElement.addEventListener("keydown", this._onKeyDown.bind(this), true);
  402. }
  403. WebInspector.ToolbarDropdown.prototype = {
  404. show: function()
  405. {
  406. if (this.visible)
  407. return;
  408. var style = this.element.style;
  409. this._populate();
  410. var top = this._arrow.totalOffsetTop() + this._arrow.clientHeight;
  411. this._arrow.addStyleClass("dropdown-visible");
  412. this.element.style.top = top + "px";
  413. this.element.style.right = window.innerWidth - this._arrow.totalOffsetLeft() - this._arrow.clientWidth + "px";
  414. this._contentElement.style.maxHeight = window.innerHeight - top - 20 + "px";
  415. this._toolbar.element.appendChild(this.element);
  416. },
  417. hide: function()
  418. {
  419. if (!this.visible)
  420. return;
  421. this._arrow.removeStyleClass("dropdown-visible");
  422. this.element.remove();
  423. this._contentElement.removeChildren();
  424. },
  425. get visible()
  426. {
  427. return !!this.element.parentNode;
  428. },
  429. _populate: function()
  430. {
  431. var toolbarItems = this._toolbar.element.querySelectorAll(".toolbar-item.toggleable");
  432. var needsSeparator = false;
  433. for (var i = 0; i < toolbarItems.length; ++i) {
  434. if (toolbarItems[i].offsetTop >= toolbarItems[0].offsetHeight) {
  435. this._contentElement.appendChild(this._toolbar._createPanelToolbarItem(toolbarItems[i].panelDescriptor, true));
  436. needsSeparator = true;
  437. }
  438. }
  439. var panelDescriptors = this._toolbar._panelDescriptors;
  440. for (var i = 0; i < panelDescriptors.length; ++i) {
  441. var descr = panelDescriptors[i];
  442. if (this._toolbar._isPanelVisible(descr.name()))
  443. continue;
  444. if (needsSeparator) {
  445. this._contentElement.createChild("div", "toolbar-items-separator");
  446. needsSeparator = false;
  447. }
  448. this._contentElement.appendChild(this._toolbar._createPanelToolbarItem(descr, true));
  449. }
  450. },
  451. _onKeyDown: function(event)
  452. {
  453. if (event.keyCode !== WebInspector.KeyboardShortcut.Keys.Esc.code)
  454. return;
  455. event.consume();
  456. this.hide();
  457. }
  458. }
  459. /**
  460. * @type {?WebInspector.Toolbar}
  461. */
  462. WebInspector.toolbar = null;