1 /* 2 * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. 3 * Copyright (C) 2007 Matt Lilek (pewtermoose (a] 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 /** 33 * @constructor 34 */ 35 WebInspector.Toolbar = function() 36 { 37 this.element = document.getElementById("toolbar"); 38 WebInspector.installDragHandle(this.element, this._toolbarDragStart.bind(this), this._toolbarDrag.bind(this), this._toolbarDragEnd.bind(this), "default"); 39 40 this._dropdownButton = document.getElementById("toolbar-dropdown-arrow"); 41 this._dropdownButton.addEventListener("click", this._toggleDropdown.bind(this), false); 42 43 this._panelsMenuButton = document.getElementById("toolbar-panels-menu"); 44 if (this._isToolbarCustomizable()) { 45 this._panelsMenuButton.addEventListener("mousedown", this._togglePanelsMenu.bind(this), false); 46 this._panelsMenuButton.removeStyleClass("hidden"); 47 } 48 49 document.getElementById("close-button-left").addEventListener("click", this._onClose, true); 50 document.getElementById("close-button-right").addEventListener("click", this._onClose, true); 51 52 this._panelDescriptors = []; 53 } 54 55 WebInspector.Toolbar.prototype = { 56 resize: function() 57 { 58 this._updateDropdownButtonAndHideDropdown(); 59 }, 60 61 /** 62 * @param {!WebInspector.PanelDescriptor} panelDescriptor 63 */ 64 addPanel: function(panelDescriptor) 65 { 66 this._panelDescriptors.push(panelDescriptor); 67 panelDescriptor._toolbarElement = this._createPanelToolbarItem(panelDescriptor); 68 if (!this._isToolbarCustomizable() || this._isPanelVisible(panelDescriptor.name())) 69 this.element.insertBefore(panelDescriptor._toolbarElement, this._panelInsertLocation(panelDescriptor)); 70 this._updatePanelsMenuState(); 71 this.resize(); 72 }, 73 74 /** 75 * @param {!WebInspector.PanelDescriptor} panelDescriptor 76 * @return {Element} 77 */ 78 _panelInsertLocation: function(panelDescriptor) 79 { 80 if (!this._isToolbarCustomizable()) 81 return null; 82 83 if (this._isDefaultPanel(panelDescriptor.name())) 84 return this._firstNonDefaultPanel || null; 85 86 if (!this._firstNonDefaultPanel) 87 this._firstNonDefaultPanel = panelDescriptor._toolbarElement; 88 return null; 89 }, 90 91 /** 92 * @param {!string} name 93 * @return {boolean} 94 */ 95 _isDefaultPanel: function(name) 96 { 97 var defaultPanels = { 98 "elements": true, 99 "resources": true, 100 "scripts": true, 101 "console": true, 102 "network": true, 103 "timeline": true, 104 }; 105 return !!defaultPanels[name]; 106 }, 107 108 /** 109 * @param {!string} name 110 * @return {boolean} 111 */ 112 _isPanelVisibleByDefault: function(name) 113 { 114 var visible = { 115 "elements": true, 116 "console": true, 117 "network": true, 118 "scripts": true, 119 "timeline": true, 120 "profiles": true, 121 "cpu-profiler": true, 122 "heap-profiler": true, 123 "audits": true, 124 "resources": true, 125 }; 126 return !!visible[name]; 127 }, 128 129 /** 130 * @return {boolean} 131 */ 132 _isToolbarCustomizable: function() 133 { 134 return WebInspector.experimentsSettings.customizableToolbar.isEnabled(); 135 }, 136 137 /** 138 * @param {!string} name 139 * @return {boolean} 140 */ 141 _isPanelVisible: function(name) 142 { 143 if (!this._isToolbarCustomizable()) 144 return true; 145 var visiblePanels = WebInspector.settings.visiblePanels.get(); 146 return visiblePanels.hasOwnProperty(name) ? visiblePanels[name] : this._isPanelVisibleByDefault(name); 147 }, 148 149 /** 150 * @param {!string} name 151 * @param {boolean} visible 152 */ 153 _setPanelVisible: function(name, visible) 154 { 155 var visiblePanels = WebInspector.settings.visiblePanels.get(); 156 visiblePanels[name] = visible; 157 WebInspector.settings.visiblePanels.set(visiblePanels); 158 }, 159 160 /** 161 * @param {!WebInspector.PanelDescriptor} panelDescriptor 162 */ 163 _hidePanel: function(panelDescriptor) 164 { 165 if (!this._isPanelVisible(panelDescriptor.name())) 166 return; 167 var switchToSibling = panelDescriptor._toolbarElement.nextSibling; 168 if (!switchToSibling || !switchToSibling.classList.contains("toggleable")) 169 switchToSibling = panelDescriptor._toolbarElement.previousSibling; 170 if (!switchToSibling || !switchToSibling.classList || !switchToSibling.classList.contains("toggleable")) 171 return; 172 this._setPanelVisible(panelDescriptor.name(), false); 173 this.element.removeChild(panelDescriptor._toolbarElement); 174 if (WebInspector.inspectorView.currentPanel().name === panelDescriptor.name()) { 175 for (var i = 0; i < this._panelDescriptors.length; ++i) { 176 var descr = this._panelDescriptors[i]; 177 if (descr._toolbarElement === switchToSibling) { 178 WebInspector.showPanel(descr.name()); 179 break; 180 } 181 } 182 } 183 this._updatePanelsMenuState(); 184 this.resize(); 185 }, 186 187 _updatePanelsMenuState: function() 188 { 189 if (this._panelDescriptors.every(function (descr) { return this._isPanelVisible(descr.name()); }, this) && this._allItemsFitOntoToolbar()) 190 document.getElementById("toolbar-panels-menu").addStyleClass("disabled"); 191 else 192 document.getElementById("toolbar-panels-menu").removeStyleClass("disabled"); 193 }, 194 195 /** 196 * @return {boolean} 197 */ 198 _allItemsFitOntoToolbar: function() 199 { 200 var toolbarItems = this.element.querySelectorAll(".toolbar-item.toggleable"); 201 return toolbarItems.length === 0 || this.element.scrollHeight < toolbarItems[0].offsetHeight * 2; 202 }, 203 204 /** 205 * @param {!WebInspector.PanelDescriptor} panelDescriptor 206 */ 207 _showPanel: function(panelDescriptor) 208 { 209 if (this._isPanelVisible(panelDescriptor.name())) 210 return; 211 this.element.appendChild(panelDescriptor._toolbarElement); 212 panelDescriptor._toolbarElement.removeStyleClass("hidden"); 213 this._setPanelVisible(panelDescriptor.name(), true); 214 this._updatePanelsMenuState(); 215 this.resize(); 216 }, 217 218 /** 219 * @param {WebInspector.PanelDescriptor} panelDescriptor 220 * @param {boolean=} noCloseButton 221 * @return {Element} 222 */ 223 _createPanelToolbarItem: function(panelDescriptor, noCloseButton) 224 { 225 var toolbarItem = document.createElement("button"); 226 toolbarItem.className = "toolbar-item toggleable"; 227 toolbarItem.panelDescriptor = panelDescriptor; 228 toolbarItem.addStyleClass(panelDescriptor.name()); 229 230 /** 231 * @param {Event} event 232 */ 233 function onContextMenuEvent(event) 234 { 235 var contextMenu = new WebInspector.ContextMenu(event); 236 contextMenu.appendItem(WebInspector.UIString("Close"), this._hidePanel.bind(this, panelDescriptor)); 237 contextMenu.show(); 238 } 239 if (!this._isDefaultPanel(panelDescriptor.name())) 240 toolbarItem.addEventListener("contextmenu", onContextMenuEvent.bind(this), true); 241 242 function onToolbarItemClicked() 243 { 244 this._showPanel(panelDescriptor); 245 this._updateDropdownButtonAndHideDropdown(); 246 WebInspector.inspectorView.setCurrentPanel(panelDescriptor.panel()); 247 } 248 toolbarItem.addEventListener("click", onToolbarItemClicked.bind(this), false); 249 250 function onToolbarItemCloseButtonClicked(event) 251 { 252 event.stopPropagation(); 253 this._hidePanel(panelDescriptor); 254 } 255 256 function panelSelected() 257 { 258 if (WebInspector.inspectorView.currentPanel() && panelDescriptor.name() === WebInspector.inspectorView.currentPanel().name) 259 toolbarItem.addStyleClass("toggled-on"); 260 else 261 toolbarItem.removeStyleClass("toggled-on"); 262 } 263 WebInspector.inspectorView.addEventListener(WebInspector.InspectorView.Events.PanelSelected, panelSelected); 264 265 toolbarItem.createChild("div", "toolbar-label").textContent = panelDescriptor.title(); 266 if (this._isToolbarCustomizable() && !this._isDefaultPanel(panelDescriptor.name()) && !noCloseButton) { 267 var closeButton = toolbarItem.createChild("div", "close-button"); 268 closeButton.addEventListener("click", onToolbarItemCloseButtonClicked.bind(this), false); 269 } 270 panelSelected(); 271 return toolbarItem; 272 }, 273 274 /** 275 * @return {boolean} 276 */ 277 _isDockedToBottom: function() 278 { 279 return !!WebInspector.dockController && WebInspector.dockController.dockSide() == WebInspector.DockController.State.DockedToBottom; 280 }, 281 282 /** 283 * @return {boolean} 284 */ 285 _isUndocked: function() 286 { 287 return !!WebInspector.dockController && WebInspector.dockController.dockSide() == WebInspector.DockController.State.Undocked; 288 }, 289 290 /** 291 * @return {boolean} 292 */ 293 _toolbarDragStart: function(event) 294 { 295 if (this._isUndocked()) 296 return false; 297 298 var target = event.target; 299 if (target.hasStyleClass("toolbar-item") && target.hasStyleClass("toggleable")) 300 return false; 301 302 if (target !== this.element && !target.hasStyleClass("toolbar-item")) 303 return false; 304 305 this._lastScreenX = event.screenX; 306 this._lastScreenY = event.screenY; 307 this._lastHeightDuringDrag = window.innerHeight; 308 this._startDistanceToRight = window.innerWidth - event.clientX; 309 this._startDinstanceToBottom = window.innerHeight - event.clientY; 310 return true; 311 }, 312 313 _toolbarDragEnd: function(event) 314 { 315 // We may not get the drag event at the end. 316 // Apply last changes manually. 317 this._toolbarDrag(event); 318 delete this._lastScreenX; 319 delete this._lastScreenY; 320 delete this._lastHeightDuringDrag; 321 delete this._startDistanceToRight; 322 delete this._startDinstanceToBottom; 323 }, 324 325 _toolbarDrag: function(event) 326 { 327 event.preventDefault(); 328 329 if (this._isUndocked()) 330 return this._toolbarDragMoveWindow(event); 331 332 return this._toolbarDragChangeDocking(event); 333 }, 334 335 _toolbarDragMoveWindow: function(event) 336 { 337 var x = event.screenX - this._lastScreenX; 338 var y = event.screenY - this._lastScreenY; 339 this._lastScreenX = event.screenX; 340 this._lastScreenY = event.screenY; 341 InspectorFrontendHost.moveWindowBy(x, y); 342 }, 343 344 _toolbarDragChangeDocking: function(event) 345 { 346 if (this._isDockedToBottom()) { 347 var distanceToRight = window.innerWidth - event.clientX; 348 if (distanceToRight < this._startDistanceToRight * 2 / 3) { 349 InspectorFrontendHost.requestSetDockSide(WebInspector.DockController.State.DockedToRight); 350 return true; 351 } 352 } else { 353 var distanceToBottom = window.innerHeight - event.clientY; 354 if (distanceToBottom < this._startDinstanceToBottom * 2 / 3) { 355 InspectorFrontendHost.requestSetDockSide(WebInspector.DockController.State.DockedToBottom); 356 return true; 357 } 358 } 359 }, 360 361 _onClose: function() 362 { 363 WebInspector.close(); 364 }, 365 366 _setDropdownVisible: function(visible) 367 { 368 if (!this._dropdown) { 369 if (!visible) 370 return; 371 this._dropdown = new WebInspector.ToolbarDropdown(this); 372 } 373 if (visible) 374 this._dropdown.show(); 375 else 376 this._dropdown.hide(); 377 }, 378 379 _toggleDropdown: function() 380 { 381 this._setDropdownVisible(!this._dropdown || !this._dropdown.visible); 382 }, 383 384 _togglePanelsMenu: function(event) 385 { 386 function activatePanel(panelDescriptor) 387 { 388 this._showPanel(panelDescriptor); 389 WebInspector.showPanel(panelDescriptor.name()); 390 } 391 392 var contextMenu = new WebInspector.ContextMenu(event); 393 var currentPanelName = WebInspector.inspectorView.currentPanel().name; 394 var toolbarItems = this.element.querySelectorAll(".toolbar-item.toggleable"); 395 for (var i = 0; i < toolbarItems.length; ++i) { 396 if (toolbarItems[i].offsetTop >= toolbarItems[0].offsetHeight) { 397 var descr = toolbarItems[i].panelDescriptor; 398 if (descr.name() === currentPanelName) 399 contextMenu.appendCheckboxItem(descr.title(), activatePanel.bind(this, descr), true); 400 else 401 contextMenu.appendItem(descr.title(), activatePanel.bind(this, descr)); 402 } 403 } 404 contextMenu.appendSeparator(); 405 for (var i = 0; i < this._panelDescriptors.length; ++i) { 406 var descr = this._panelDescriptors[i]; 407 if (this._isPanelVisible(descr.name())) 408 continue; 409 contextMenu.appendItem(descr.title(), activatePanel.bind(this, descr)); 410 } 411 412 contextMenu.showSoftMenu(); 413 }, 414 415 _updateDropdownButtonAndHideDropdown: function() 416 { 417 WebInspector.invokeOnceAfterBatchUpdate(this, this._innerUpdateDropdownButtonAndHideDropdown); 418 }, 419 420 _innerUpdateDropdownButtonAndHideDropdown: function() 421 { 422 if (this._isToolbarCustomizable()) { 423 this._updatePanelsMenuState(); 424 return; 425 } 426 this._setDropdownVisible(false); 427 428 if (this.element.scrollHeight > this.element.offsetHeight) 429 this._dropdownButton.removeStyleClass("hidden"); 430 else 431 this._dropdownButton.addStyleClass("hidden"); 432 } 433 } 434 435 /** 436 * @constructor 437 * @param {WebInspector.Toolbar} toolbar 438 */ 439 WebInspector.ToolbarDropdown = function(toolbar) 440 { 441 this._toolbar = toolbar; 442 this._arrow = document.getElementById("toolbar-dropdown-arrow"); 443 this.element = document.createElement("div"); 444 this.element.id = "toolbar-dropdown"; 445 this.element.className = "toolbar-small"; 446 this._contentElement = this.element.createChild("div", "scrollable-content"); 447 this._contentElement.tabIndex = 0; 448 this._contentElement.addEventListener("keydown", this._onKeyDown.bind(this), true); 449 } 450 451 WebInspector.ToolbarDropdown.prototype = { 452 show: function() 453 { 454 if (this.visible) 455 return; 456 var style = this.element.style; 457 this._populate(); 458 var top = this._arrow.totalOffsetTop() + this._arrow.clientHeight; 459 this._arrow.addStyleClass("dropdown-visible"); 460 this.element.style.top = top + "px"; 461 this.element.style.right = window.innerWidth - this._arrow.totalOffsetLeft() - this._arrow.clientWidth + "px"; 462 this._contentElement.style.maxHeight = window.innerHeight - top - 20 + "px"; 463 this._toolbar.element.appendChild(this.element); 464 }, 465 466 hide: function() 467 { 468 if (!this.visible) 469 return; 470 this._arrow.removeStyleClass("dropdown-visible"); 471 this.element.remove(); 472 this._contentElement.removeChildren(); 473 }, 474 475 get visible() 476 { 477 return !!this.element.parentNode; 478 }, 479 480 _populate: function() 481 { 482 var toolbarItems = this._toolbar.element.querySelectorAll(".toolbar-item.toggleable"); 483 484 var needsSeparator = false; 485 for (var i = 0; i < toolbarItems.length; ++i) { 486 if (toolbarItems[i].offsetTop >= toolbarItems[0].offsetHeight) { 487 this._contentElement.appendChild(this._toolbar._createPanelToolbarItem(toolbarItems[i].panelDescriptor, true)); 488 needsSeparator = true; 489 } 490 } 491 492 var panelDescriptors = this._toolbar._panelDescriptors; 493 for (var i = 0; i < panelDescriptors.length; ++i) { 494 var descr = panelDescriptors[i]; 495 if (this._toolbar._isPanelVisible(descr.name())) 496 continue; 497 if (needsSeparator) { 498 this._contentElement.createChild("div", "toolbar-items-separator"); 499 needsSeparator = false; 500 } 501 this._contentElement.appendChild(this._toolbar._createPanelToolbarItem(descr, true)); 502 } 503 }, 504 505 _onKeyDown: function(event) 506 { 507 if (event.keyCode !== WebInspector.KeyboardShortcut.Keys.Esc.code) 508 return; 509 event.consume(); 510 this.hide(); 511 } 512 } 513 514 /** 515 * @type {?WebInspector.Toolbar} 516 */ 517 WebInspector.toolbar = null; 518