1 /* 2 * Copyright (C) 2011 Google 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 are 6 * met: 7 * 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above 11 * copyright notice, this list of conditions and the following disclaimer 12 * in the documentation and/or other materials provided with the 13 * distribution. 14 * * Neither the name of Google Inc. nor the names of its 15 * contributors may be used to endorse or promote products derived from 16 * this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31 /** 32 * @constructor 33 * @extends {WebInspector.VBox} 34 */ 35 WebInspector.InspectorView = function() 36 { 37 WebInspector.VBox.call(this); 38 WebInspector.Dialog.setModalHostView(this); 39 WebInspector.GlassPane.DefaultFocusedViewStack.push(this); 40 this.setMinimumSize(180, 72); 41 42 // DevTools sidebar is a vertical split of panels tabbed pane and a drawer. 43 this._drawerSplitView = new WebInspector.SplitView(false, true, "Inspector.drawerSplitViewState", 200, 200); 44 this._drawerSplitView.hideSidebar(); 45 this._drawerSplitView.enableShowModeSaving(); 46 this._drawerSplitView.show(this.element); 47 48 this._tabbedPane = new WebInspector.TabbedPane(); 49 this._tabbedPane.setRetainTabOrder(true, self.runtime.orderComparator(WebInspector.Panel, "name", "order")); 50 this._tabbedPane.show(this._drawerSplitView.mainElement()); 51 this._drawer = new WebInspector.Drawer(this._drawerSplitView); 52 53 // Patch tabbed pane header with toolbar actions. 54 this._toolbarElement = document.createElement("div"); 55 this._toolbarElement.className = "toolbar toolbar-background toolbar-colors"; 56 var headerElement = this._tabbedPane.headerElement(); 57 headerElement.parentElement.insertBefore(this._toolbarElement, headerElement); 58 59 this._leftToolbarElement = this._toolbarElement.createChild("div", "toolbar-controls-left"); 60 this._toolbarElement.appendChild(headerElement); 61 this._rightToolbarElement = this._toolbarElement.createChild("div", "toolbar-controls-right"); 62 this._toolbarItems = []; 63 64 this._closeButtonToolbarItem = document.createElementWithClass("div", "toolbar-close-button-item"); 65 var closeButtonElement = this._closeButtonToolbarItem.createChild("div", "close-button"); 66 closeButtonElement.addEventListener("click", InspectorFrontendHost.closeWindow.bind(InspectorFrontendHost), true); 67 this._rightToolbarElement.appendChild(this._closeButtonToolbarItem); 68 69 this._panels = {}; 70 // Used by tests. 71 WebInspector["panels"] = this._panels; 72 73 this._history = []; 74 this._historyIterator = -1; 75 document.addEventListener("keydown", this._keyDown.bind(this), false); 76 document.addEventListener("keypress", this._keyPress.bind(this), false); 77 this._panelDescriptors = {}; 78 79 // Windows and Mac have two different definitions of '[' and ']', so accept both of each. 80 this._openBracketIdentifiers = ["U+005B", "U+00DB"].keySet(); 81 this._closeBracketIdentifiers = ["U+005D", "U+00DD"].keySet(); 82 this._lastActivePanelSetting = WebInspector.settings.createSetting("lastActivePanel", "elements"); 83 84 // FIXME(399531): enable timelineOnTraceEvents experiment when running layout tests under inspector/tracing/. This code 85 // should be removed along with the old Timeline implementation once we move tracing based Timeline out of experimental. 86 if ("tracing" === this._lastActivePanelSetting.get()) { 87 Runtime.experiments.setEnabled("timelineOnTraceEvents", true); 88 this._lastActivePanelSetting.set("timeline"); 89 } 90 91 this._loadPanelDesciptors(); 92 93 InspectorFrontendHost.events.addEventListener(InspectorFrontendHostAPI.Events.ShowConsole, this.showPanel.bind(this, "console")); 94 }; 95 96 WebInspector.InspectorView.prototype = { 97 _loadPanelDesciptors: function() 98 { 99 WebInspector.startBatchUpdate(); 100 self.runtime.extensions(WebInspector.Panel).forEach(processPanelExtensions.bind(this)); 101 /** 102 * @param {!Runtime.Extension} extension 103 * @this {!WebInspector.InspectorView} 104 */ 105 function processPanelExtensions(extension) 106 { 107 this.addPanel(new WebInspector.RuntimeExtensionPanelDescriptor(extension)); 108 } 109 WebInspector.endBatchUpdate(); 110 }, 111 112 /** 113 * @param {!WebInspector.StatusBarItem} item 114 */ 115 appendToLeftToolbar: function(item) 116 { 117 this._toolbarItems.push(item); 118 this._leftToolbarElement.appendChild(item.element); 119 }, 120 121 /** 122 * @param {!WebInspector.StatusBarItem} item 123 */ 124 appendToRightToolbar: function(item) 125 { 126 this._toolbarItems.push(item); 127 this._rightToolbarElement.insertBefore(item.element, this._closeButtonToolbarItem); 128 }, 129 130 /** 131 * @param {!WebInspector.PanelDescriptor} panelDescriptor 132 */ 133 addPanel: function(panelDescriptor) 134 { 135 var panelName = panelDescriptor.name(); 136 this._panelDescriptors[panelName] = panelDescriptor; 137 this._tabbedPane.appendTab(panelName, panelDescriptor.title(), new WebInspector.View()); 138 if (this._lastActivePanelSetting.get() === panelName) 139 this._tabbedPane.selectTab(panelName); 140 }, 141 142 /** 143 * @param {string} panelName 144 * @return {boolean} 145 */ 146 hasPanel: function(panelName) 147 { 148 return !!this._panelDescriptors[panelName]; 149 }, 150 151 /** 152 * @param {string} panelName 153 * @return {?WebInspector.Panel} 154 */ 155 panel: function(panelName) 156 { 157 var panelDescriptor = this._panelDescriptors[panelName]; 158 var panelOrder = this._tabbedPane.allTabs(); 159 if (!panelDescriptor && panelOrder.length) 160 panelDescriptor = this._panelDescriptors[panelOrder[0]]; 161 var panel = panelDescriptor ? panelDescriptor.panel() : null; 162 if (panel) 163 this._panels[panelName] = panel; 164 return panel; 165 }, 166 167 /** 168 * @param {boolean} locked 169 */ 170 setCurrentPanelLocked: function(locked) 171 { 172 this._currentPanelLocked = locked; 173 this._tabbedPane.setCurrentTabLocked(locked); 174 for (var i = 0; i < this._toolbarItems.length; ++i) 175 this._toolbarItems[i].setEnabled(!locked); 176 }, 177 178 /** 179 * @param {string} panelName 180 * @return {?WebInspector.Panel} 181 */ 182 showPanel: function(panelName) 183 { 184 if (this._currentPanelLocked) 185 return this._currentPanel === this._panels[panelName] ? this._currentPanel : null; 186 187 var panel = this.panel(panelName); 188 if (panel) 189 this.setCurrentPanel(panel); 190 return panel; 191 }, 192 193 /** 194 * @return {!WebInspector.Panel} 195 */ 196 currentPanel: function() 197 { 198 return this._currentPanel; 199 }, 200 201 showInitialPanel: function() 202 { 203 this._tabbedPane.addEventListener(WebInspector.TabbedPane.EventTypes.TabSelected, this._tabSelected, this); 204 this._tabSelected(); 205 this._drawer.initialPanelShown(); 206 }, 207 208 _tabSelected: function() 209 { 210 var panelName = this._tabbedPane.selectedTabId; 211 if (!panelName) 212 return; 213 var panel = this._panelDescriptors[this._tabbedPane.selectedTabId].panel(); 214 this._panels[panelName] = panel; 215 this._tabbedPane.changeTabView(panelName, panel); 216 217 this._currentPanel = panel; 218 this._lastActivePanelSetting.set(panel.name); 219 this._pushToHistory(panel.name); 220 WebInspector.userMetrics.panelShown(panel.name); 221 panel.focus(); 222 }, 223 224 /** 225 * @param {!WebInspector.Panel} x 226 */ 227 setCurrentPanel: function(x) 228 { 229 if (this._currentPanelLocked) 230 return; 231 InspectorFrontendHost.bringToFront(); 232 if (this._currentPanel === x) 233 return; 234 235 this._tabbedPane.changeTabView(x.name, x); 236 this._tabbedPane.selectTab(x.name); 237 }, 238 239 /** 240 * @param {string} id 241 */ 242 closeViewInDrawer: function(id) 243 { 244 this._drawer.closeView(id); 245 }, 246 247 /** 248 * @param {string} id 249 * @param {string} title 250 * @param {!WebInspector.View} view 251 */ 252 showCloseableViewInDrawer: function(id, title, view) 253 { 254 this._drawer.showCloseableView(id, title, view); 255 }, 256 257 showDrawer: function() 258 { 259 this._drawer.showDrawer(); 260 }, 261 262 /** 263 * @return {boolean} 264 */ 265 drawerVisible: function() 266 { 267 return this._drawer.isShowing(); 268 }, 269 270 /** 271 * @param {string} id 272 * @param {boolean=} immediate 273 */ 274 showViewInDrawer: function(id, immediate) 275 { 276 this._drawer.showView(id, immediate); 277 }, 278 279 /** 280 * @return {?string} 281 */ 282 selectedViewInDrawer: function() 283 { 284 return this._drawer.selectedViewId(); 285 }, 286 287 closeDrawer: function() 288 { 289 this._drawer.closeDrawer(); 290 }, 291 292 /** 293 * @return {!Element} 294 */ 295 defaultFocusedElement: function() 296 { 297 return this._currentPanel ? this._currentPanel.defaultFocusedElement() : null; 298 }, 299 300 _keyPress: function(event) 301 { 302 // BUG 104250: Windows 7 posts a WM_CHAR message upon the Ctrl+']' keypress. 303 // Any charCode < 32 is not going to be a valid keypress. 304 if (event.charCode < 32 && WebInspector.isWin()) 305 return; 306 clearTimeout(this._keyDownTimer); 307 delete this._keyDownTimer; 308 }, 309 310 _keyDown: function(event) 311 { 312 if (!WebInspector.KeyboardShortcut.eventHasCtrlOrMeta(event)) 313 return; 314 315 var keyboardEvent = /** @type {!KeyboardEvent} */ (event); 316 // Ctrl/Cmd + 1-9 should show corresponding panel. 317 var panelShortcutEnabled = WebInspector.settings.shortcutPanelSwitch.get(); 318 if (panelShortcutEnabled && !event.shiftKey && !event.altKey) { 319 var panelIndex = -1; 320 if (event.keyCode > 0x30 && event.keyCode < 0x3A) 321 panelIndex = event.keyCode - 0x31; 322 else if (event.keyCode > 0x60 && event.keyCode < 0x6A && keyboardEvent.location === KeyboardEvent.DOM_KEY_LOCATION_NUMPAD) 323 panelIndex = event.keyCode - 0x61; 324 if (panelIndex !== -1) { 325 var panelName = this._tabbedPane.allTabs()[panelIndex]; 326 if (panelName) { 327 if (!WebInspector.Dialog.currentInstance() && !this._currentPanelLocked) 328 this.showPanel(panelName); 329 event.consume(true); 330 } 331 return; 332 } 333 } 334 335 // BUG85312: On French AZERTY keyboards, AltGr-]/[ combinations (synonymous to Ctrl-Alt-]/[ on Windows) are used to enter ]/[, 336 // so for a ]/[-related keydown we delay the panel switch using a timer, to see if there is a keypress event following this one. 337 // If there is, we cancel the timer and do not consider this a panel switch. 338 if (!WebInspector.isWin() || (!this._openBracketIdentifiers[event.keyIdentifier] && !this._closeBracketIdentifiers[event.keyIdentifier])) { 339 this._keyDownInternal(event); 340 return; 341 } 342 343 this._keyDownTimer = setTimeout(this._keyDownInternal.bind(this, event), 0); 344 }, 345 346 _keyDownInternal: function(event) 347 { 348 if (this._currentPanelLocked) 349 return; 350 351 var direction = 0; 352 353 if (this._openBracketIdentifiers[event.keyIdentifier]) 354 direction = -1; 355 356 if (this._closeBracketIdentifiers[event.keyIdentifier]) 357 direction = 1; 358 359 if (!direction) 360 return; 361 362 if (!event.shiftKey && !event.altKey) { 363 if (!WebInspector.Dialog.currentInstance()) 364 this._changePanelInDirection(direction); 365 event.consume(true); 366 return; 367 } 368 369 if (event.altKey && this._moveInHistory(direction)) 370 event.consume(true) 371 }, 372 373 _changePanelInDirection: function(direction) 374 { 375 var panelOrder = this._tabbedPane.allTabs(); 376 var index = panelOrder.indexOf(this.currentPanel().name); 377 index = (index + panelOrder.length + direction) % panelOrder.length; 378 this.showPanel(panelOrder[index]); 379 }, 380 381 _moveInHistory: function(move) 382 { 383 var newIndex = this._historyIterator + move; 384 if (newIndex >= this._history.length || newIndex < 0) 385 return false; 386 387 this._inHistory = true; 388 this._historyIterator = newIndex; 389 if (!WebInspector.Dialog.currentInstance()) 390 this.setCurrentPanel(this._panels[this._history[this._historyIterator]]); 391 delete this._inHistory; 392 393 return true; 394 }, 395 396 _pushToHistory: function(panelName) 397 { 398 if (this._inHistory) 399 return; 400 401 this._history.splice(this._historyIterator + 1, this._history.length - this._historyIterator - 1); 402 if (!this._history.length || this._history[this._history.length - 1] !== panelName) 403 this._history.push(panelName); 404 this._historyIterator = this._history.length - 1; 405 }, 406 407 onResize: function() 408 { 409 WebInspector.Dialog.modalHostRepositioned(); 410 }, 411 412 /** 413 * @return {!Element} 414 */ 415 topResizerElement: function() 416 { 417 return this._tabbedPane.headerElement(); 418 }, 419 420 toolbarItemResized: function() 421 { 422 this._tabbedPane.headerResized(); 423 }, 424 425 __proto__: WebInspector.VBox.prototype 426 }; 427 428 /** 429 * @type {!WebInspector.InspectorView} 430 */ 431 WebInspector.inspectorView; 432 433 /** 434 * @constructor 435 * @implements {WebInspector.ActionDelegate} 436 */ 437 WebInspector.InspectorView.DrawerToggleActionDelegate = function() 438 { 439 } 440 441 WebInspector.InspectorView.DrawerToggleActionDelegate.prototype = { 442 /** 443 * @return {boolean} 444 */ 445 handleAction: function() 446 { 447 if (WebInspector.inspectorView.drawerVisible()) { 448 WebInspector.inspectorView.closeDrawer(); 449 return true; 450 } 451 WebInspector.inspectorView.showDrawer(); 452 return true; 453 } 454 } 455 456 /** 457 * @constructor 458 * @implements {WebInspector.StatusBarItem.Provider} 459 */ 460 WebInspector.InspectorView.ToggleDrawerButtonProvider = function() 461 { 462 } 463 464 WebInspector.InspectorView.ToggleDrawerButtonProvider.prototype = { 465 /** 466 * @return {?WebInspector.StatusBarItem} 467 */ 468 item: function() 469 { 470 return WebInspector.inspectorView._drawer.toggleButton(); 471 } 472 } 473