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.unshift(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, WebInspector.moduleManager.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"; 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 63 if (WebInspector.experimentsSettings.devicesPanel.isEnabled()) { 64 this._remoteDeviceCountElement = this._rightToolbarElement.createChild("div", "hidden"); 65 this._remoteDeviceCountElement.addEventListener("click", this.showViewInDrawer.bind(this, "devices", true), false); 66 this._remoteDeviceCountElement.id = "remote-device-count"; 67 WebInspector.inspectorFrontendEventSink.addEventListener(WebInspector.InspectorView.Events.DeviceCountChanged, this._onDeviceCountChanged, this); 68 } 69 70 this._errorWarningCountElement = this._rightToolbarElement.createChild("div", "hidden"); 71 this._errorWarningCountElement.id = "error-warning-count"; 72 73 this._closeButtonToolbarItem = document.createElementWithClass("div", "toolbar-close-button-item"); 74 var closeButtonElement = this._closeButtonToolbarItem.createChild("div", "close-button"); 75 closeButtonElement.addEventListener("click", InspectorFrontendHost.closeWindow.bind(InspectorFrontendHost), true); 76 this._rightToolbarElement.appendChild(this._closeButtonToolbarItem); 77 78 this.appendToRightToolbar(this._drawer.toggleButtonElement()); 79 80 this._panels = {}; 81 // Used by tests. 82 WebInspector["panels"] = this._panels; 83 84 this._history = []; 85 this._historyIterator = -1; 86 document.addEventListener("keydown", this._keyDown.bind(this), false); 87 document.addEventListener("keypress", this._keyPress.bind(this), false); 88 this._panelDescriptors = {}; 89 90 // Windows and Mac have two different definitions of '[' and ']', so accept both of each. 91 this._openBracketIdentifiers = ["U+005B", "U+00DB"].keySet(); 92 this._closeBracketIdentifiers = ["U+005D", "U+00DD"].keySet(); 93 this._lastActivePanelSetting = WebInspector.settings.createSetting("lastActivePanel", "elements"); 94 95 this._loadPanelDesciptors(); 96 }; 97 98 WebInspector.InspectorView.Events = { 99 DeviceCountChanged: "DeviceCountChanged" 100 } 101 102 WebInspector.InspectorView.prototype = { 103 _loadPanelDesciptors: function() 104 { 105 WebInspector.startBatchUpdate(); 106 WebInspector.moduleManager.extensions(WebInspector.Panel).forEach(processPanelExtensions.bind(this)); 107 /** 108 * @param {!WebInspector.ModuleManager.Extension} extension 109 * @this {!WebInspector.InspectorView} 110 */ 111 function processPanelExtensions(extension) 112 { 113 this.addPanel(new WebInspector.ModuleManagerExtensionPanelDescriptor(extension)); 114 } 115 WebInspector.endBatchUpdate(); 116 }, 117 118 /** 119 * @param {!Element} element 120 */ 121 appendToLeftToolbar: function(element) 122 { 123 this._leftToolbarElement.appendChild(element); 124 }, 125 126 /** 127 * @param {!Element} element 128 */ 129 appendToRightToolbar: function(element) 130 { 131 this._rightToolbarElement.insertBefore(element, this._closeButtonToolbarItem); 132 }, 133 134 /** 135 * @param {!WebInspector.PanelDescriptor} panelDescriptor 136 */ 137 addPanel: function(panelDescriptor) 138 { 139 var panelName = panelDescriptor.name(); 140 this._panelDescriptors[panelName] = panelDescriptor; 141 this._tabbedPane.appendTab(panelName, panelDescriptor.title(), new WebInspector.View()); 142 if (this._lastActivePanelSetting.get() === panelName) 143 this._tabbedPane.selectTab(panelName); 144 }, 145 146 /** 147 * @param {string} panelName 148 * @return {boolean} 149 */ 150 hasPanel: function(panelName) 151 { 152 return !!this._panelDescriptors[panelName]; 153 }, 154 155 /** 156 * @param {string} panelName 157 * @return {?WebInspector.Panel} 158 */ 159 panel: function(panelName) 160 { 161 var panelDescriptor = this._panelDescriptors[panelName]; 162 var panelOrder = this._tabbedPane.allTabs(); 163 if (!panelDescriptor && panelOrder.length) 164 panelDescriptor = this._panelDescriptors[panelOrder[0]]; 165 var panel = panelDescriptor ? panelDescriptor.panel() : null; 166 if (panel) 167 this._panels[panelName] = panel; 168 return panel; 169 }, 170 171 /** 172 * @param {string} panelName 173 * @return {?WebInspector.Panel} 174 */ 175 showPanel: function(panelName) 176 { 177 var panel = this.panel(panelName); 178 if (panel) 179 this.setCurrentPanel(panel); 180 return panel; 181 }, 182 183 /** 184 * @return {!WebInspector.Panel} 185 */ 186 currentPanel: function() 187 { 188 return this._currentPanel; 189 }, 190 191 showInitialPanel: function() 192 { 193 this._tabbedPane.addEventListener(WebInspector.TabbedPane.EventTypes.TabSelected, this._tabSelected, this); 194 this._tabSelected(); 195 this._drawer.initialPanelShown(); 196 }, 197 198 showDrawerEditor: function() 199 { 200 this._drawer.showDrawerEditor(); 201 }, 202 203 /** 204 * @return {boolean} 205 */ 206 isDrawerEditorShown: function() 207 { 208 return this._drawer.isDrawerEditorShown(); 209 }, 210 211 hideDrawerEditor: function() 212 { 213 this._drawer.hideDrawerEditor(); 214 }, 215 216 /** 217 * @param {boolean} available 218 */ 219 setDrawerEditorAvailable: function(available) 220 { 221 this._drawer.setDrawerEditorAvailable(available); 222 }, 223 224 _tabSelected: function() 225 { 226 var panelName = this._tabbedPane.selectedTabId; 227 if (!panelName) 228 return; 229 var panel = this._panelDescriptors[this._tabbedPane.selectedTabId].panel(); 230 this._panels[panelName] = panel; 231 this._tabbedPane.changeTabView(panelName, panel); 232 233 this._currentPanel = panel; 234 this._lastActivePanelSetting.set(panel.name); 235 this._pushToHistory(panel.name); 236 WebInspector.userMetrics.panelShown(panel.name); 237 panel.focus(); 238 }, 239 240 /** 241 * @param {!WebInspector.Panel} x 242 */ 243 setCurrentPanel: function(x) 244 { 245 if (this._currentPanel === x) 246 return; 247 248 this._tabbedPane.changeTabView(x.name, x); 249 this._tabbedPane.selectTab(x.name); 250 }, 251 252 /** 253 * @param {string} id 254 */ 255 closeViewInDrawer: function(id) 256 { 257 this._drawer.closeView(id); 258 }, 259 260 /** 261 * @param {string} id 262 * @param {string} title 263 * @param {!WebInspector.View} view 264 */ 265 showCloseableViewInDrawer: function(id, title, view) 266 { 267 this._drawer.showCloseableView(id, title, view); 268 }, 269 270 showDrawer: function() 271 { 272 this._drawer.showDrawer(); 273 }, 274 275 /** 276 * @return {boolean} 277 */ 278 drawerVisible: function() 279 { 280 return this._drawer.isShowing(); 281 }, 282 283 /** 284 * @param {string} id 285 * @param {boolean=} immediate 286 */ 287 showViewInDrawer: function(id, immediate) 288 { 289 this._drawer.showView(id, immediate); 290 }, 291 292 /** 293 * @return {?string} 294 */ 295 selectedViewInDrawer: function() 296 { 297 return this._drawer.selectedViewId(); 298 }, 299 300 closeDrawer: function() 301 { 302 this._drawer.closeDrawer(); 303 }, 304 305 /** 306 * @return {!Element} 307 */ 308 defaultFocusedElement: function() 309 { 310 return this._currentPanel ? this._currentPanel.defaultFocusedElement() : null; 311 }, 312 313 _keyPress: function(event) 314 { 315 // BUG 104250: Windows 7 posts a WM_CHAR message upon the Ctrl+']' keypress. 316 // Any charCode < 32 is not going to be a valid keypress. 317 if (event.charCode < 32 && WebInspector.isWin()) 318 return; 319 clearTimeout(this._keyDownTimer); 320 delete this._keyDownTimer; 321 }, 322 323 _keyDown: function(event) 324 { 325 if (!WebInspector.KeyboardShortcut.eventHasCtrlOrMeta(event)) 326 return; 327 328 var keyboardEvent = /** @type {!KeyboardEvent} */ (event); 329 // Ctrl/Cmd + 1-9 should show corresponding panel. 330 var panelShortcutEnabled = WebInspector.settings.shortcutPanelSwitch.get(); 331 if (panelShortcutEnabled && !event.shiftKey && !event.altKey) { 332 var panelIndex = -1; 333 if (event.keyCode > 0x30 && event.keyCode < 0x3A) 334 panelIndex = event.keyCode - 0x31; 335 else if (event.keyCode > 0x60 && event.keyCode < 0x6A && keyboardEvent.location === KeyboardEvent.DOM_KEY_LOCATION_NUMPAD) 336 panelIndex = event.keyCode - 0x61; 337 if (panelIndex !== -1) { 338 var panelName = this._tabbedPane.allTabs()[panelIndex]; 339 if (panelName) { 340 if (!WebInspector.Dialog.currentInstance()) 341 this.showPanel(panelName); 342 event.consume(true); 343 } 344 return; 345 } 346 } 347 348 // BUG85312: On French AZERTY keyboards, AltGr-]/[ combinations (synonymous to Ctrl-Alt-]/[ on Windows) are used to enter ]/[, 349 // so for a ]/[-related keydown we delay the panel switch using a timer, to see if there is a keypress event following this one. 350 // If there is, we cancel the timer and do not consider this a panel switch. 351 if (!WebInspector.isWin() || (!this._openBracketIdentifiers[event.keyIdentifier] && !this._closeBracketIdentifiers[event.keyIdentifier])) { 352 this._keyDownInternal(event); 353 return; 354 } 355 356 this._keyDownTimer = setTimeout(this._keyDownInternal.bind(this, event), 0); 357 }, 358 359 _keyDownInternal: function(event) 360 { 361 var direction = 0; 362 363 if (this._openBracketIdentifiers[event.keyIdentifier]) 364 direction = -1; 365 366 if (this._closeBracketIdentifiers[event.keyIdentifier]) 367 direction = 1; 368 369 if (!direction) 370 return; 371 372 if (!event.shiftKey && !event.altKey) { 373 if (!WebInspector.Dialog.currentInstance()) 374 this._changePanelInDirection(direction); 375 event.consume(true); 376 return; 377 } 378 379 if (event.altKey && this._moveInHistory(direction)) 380 event.consume(true) 381 }, 382 383 _changePanelInDirection: function(direction) 384 { 385 var panelOrder = this._tabbedPane.allTabs(); 386 var index = panelOrder.indexOf(this.currentPanel().name); 387 index = (index + panelOrder.length + direction) % panelOrder.length; 388 this.showPanel(panelOrder[index]); 389 }, 390 391 _moveInHistory: function(move) 392 { 393 var newIndex = this._historyIterator + move; 394 if (newIndex >= this._history.length || newIndex < 0) 395 return false; 396 397 this._inHistory = true; 398 this._historyIterator = newIndex; 399 if (!WebInspector.Dialog.currentInstance()) 400 this.setCurrentPanel(this._panels[this._history[this._historyIterator]]); 401 delete this._inHistory; 402 403 return true; 404 }, 405 406 _pushToHistory: function(panelName) 407 { 408 if (this._inHistory) 409 return; 410 411 this._history.splice(this._historyIterator + 1, this._history.length - this._historyIterator - 1); 412 if (!this._history.length || this._history[this._history.length - 1] !== panelName) 413 this._history.push(panelName); 414 this._historyIterator = this._history.length - 1; 415 }, 416 417 onResize: function() 418 { 419 WebInspector.Dialog.modalHostRepositioned(); 420 }, 421 422 /** 423 * @return {!Element} 424 */ 425 topResizerElement: function() 426 { 427 return this._tabbedPane.headerElement(); 428 }, 429 430 _createImagedCounterElementIfNeeded: function(parent, count, id, styleName) 431 { 432 if (!count) 433 return; 434 435 var imageElement = parent.createChild("div", styleName); 436 var counterElement = parent.createChild("span"); 437 counterElement.id = id; 438 counterElement.textContent = count; 439 }, 440 441 /** 442 * @param {number} errors 443 * @param {number} warnings 444 */ 445 setErrorAndWarningCounts: function(errors, warnings) 446 { 447 if (this._errors === errors && this._warnings === warnings) 448 return; 449 this._errors = errors; 450 this._warnings = warnings; 451 this._errorWarningCountElement.classList.toggle("hidden", !errors && !warnings); 452 this._errorWarningCountElement.removeChildren(); 453 454 this._createImagedCounterElementIfNeeded(this._errorWarningCountElement, errors, "error-count", "error-icon-small"); 455 this._createImagedCounterElementIfNeeded(this._errorWarningCountElement, warnings, "warning-count", "warning-icon-small"); 456 457 var errorString = errors ? WebInspector.UIString("%d error%s", errors, errors > 1 ? "s" : "") : ""; 458 var warningString = warnings ? WebInspector.UIString("%d warning%s", warnings, warnings > 1 ? "s" : "") : ""; 459 var commaString = errors && warnings ? ", " : ""; 460 this._errorWarningCountElement.title = errorString + commaString + warningString; 461 this._tabbedPane.headerResized(); 462 }, 463 464 /** 465 * @param {!WebInspector.Event} event 466 */ 467 _onDeviceCountChanged: function(event) 468 { 469 var count = /** @type {number} */ (event.data); 470 if (count === this.deviceCount_) 471 return; 472 this.deviceCount_ = count; 473 this._remoteDeviceCountElement.classList.toggle("hidden", !count); 474 this._remoteDeviceCountElement.removeChildren(); 475 this._createImagedCounterElementIfNeeded(this._remoteDeviceCountElement, count, "device-count", "device-icon-small"); 476 this._remoteDeviceCountElement.title = WebInspector.UIString(((count > 1) ? "%d devices found" : "%d device found"), count); 477 this._tabbedPane.headerResized(); 478 }, 479 480 __proto__: WebInspector.VBox.prototype 481 }; 482 483 /** 484 * @type {!WebInspector.InspectorView} 485 */ 486 WebInspector.inspectorView; 487 488 /** 489 * @constructor 490 * @implements {WebInspector.ActionDelegate} 491 */ 492 WebInspector.InspectorView.DrawerToggleActionDelegate = function() 493 { 494 } 495 496 WebInspector.InspectorView.DrawerToggleActionDelegate.prototype = { 497 /** 498 * @return {boolean} 499 */ 500 handleAction: function() 501 { 502 if (WebInspector.inspectorView.drawerVisible()) { 503 WebInspector.inspectorView.closeDrawer(); 504 return true; 505 } 506 WebInspector.inspectorView.showDrawer(); 507 return true; 508 } 509 } 510 511 /** 512 * @constructor 513 * @extends {WebInspector.VBox} 514 */ 515 WebInspector.RootView = function() 516 { 517 WebInspector.VBox.call(this); 518 this.markAsRoot(); 519 this.element.classList.add("root-view"); 520 this.element.setAttribute("spellcheck", false); 521 window.addEventListener("resize", this.doResize.bind(this), true); 522 this._onScrollBound = this._onScroll.bind(this); 523 }; 524 525 WebInspector.RootView.prototype = { 526 attachToBody: function() 527 { 528 this.doResize(); 529 this.show(document.body); 530 }, 531 532 _onScroll: function() 533 { 534 // If we didn't have enough space at the start, we may have wrong scroll offsets. 535 if (document.body.scrollTop !== 0) 536 document.body.scrollTop = 0; 537 if (document.body.scrollLeft !== 0) 538 document.body.scrollLeft = 0; 539 }, 540 541 doResize: function() 542 { 543 var size = this.constraints().minimum; 544 var zoom = WebInspector.zoomManager.zoomFactor(); 545 var right = Math.min(0, window.innerWidth - size.width / zoom); 546 this.element.style.right = right + "px"; 547 var bottom = Math.min(0, window.innerHeight - size.height / zoom); 548 this.element.style.bottom = bottom + "px"; 549 550 if (window.innerWidth < size.width || window.innerHeight < size.height) 551 window.addEventListener("scroll", this._onScrollBound, false); 552 else 553 window.removeEventListener("scroll", this._onScrollBound, false); 554 555 WebInspector.VBox.prototype.doResize.call(this); 556 this._onScroll(); 557 }, 558 559 __proto__: WebInspector.VBox.prototype 560 }; 561