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 26WebInspector.LogContentView = function(representedObject) 27{ 28 WebInspector.ContentView.call(this, representedObject); 29 30 this._nestingLevel = 0; 31 this._selectedMessages = []; 32 33 this.element.classList.add(WebInspector.LogContentView.StyleClassName); 34 35 this.messagesElement = document.createElement("div"); 36 this.messagesElement.className = "console-messages"; 37 this.messagesElement.tabIndex = 0; 38 this.messagesElement.setAttribute("role", "log"); 39 this.messagesElement.addEventListener("mousedown", this._mousedown.bind(this)); 40 this.messagesElement.addEventListener("focus", this._didFocus.bind(this)); 41 this.messagesElement.addEventListener("blur", this._didBlur.bind(this)); 42 this.messagesElement.addEventListener("keydown", this._keyDown.bind(this)); 43 this.messagesElement.addEventListener("click", this._click.bind(this), true); 44 this.messagesElement.addEventListener("dragstart", this._ondragstart.bind(this), true); 45 this.element.appendChild(this.messagesElement); 46 47 this.prompt = WebInspector.quickConsole.prompt; 48 49 this._keyboardShortcutCommandA = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.CommandOrControl, "A"); 50 this._keyboardShortcutEsc = new WebInspector.KeyboardShortcut(null, WebInspector.KeyboardShortcut.Key.Escape); 51 52 this._logViewController = new WebInspector.JavaScriptLogViewController(this.messagesElement, this.element, this.prompt, this, "console-prompt-history"); 53 54 this._searchBar = new WebInspector.SearchBar("log-search-bar", WebInspector.UIString("Filter Console Log"), this); 55 this._searchBar.addEventListener(WebInspector.SearchBar.Event.TextChanged, this._searchTextDidChange, this); 56 57 var scopeBarItems = [ 58 new WebInspector.ScopeBarItem(WebInspector.LogContentView.Scopes.All, WebInspector.UIString("All"), true), 59 new WebInspector.ScopeBarItem(WebInspector.LogContentView.Scopes.Errors, WebInspector.UIString("Errors")), 60 new WebInspector.ScopeBarItem(WebInspector.LogContentView.Scopes.Warnings, WebInspector.UIString("Warnings")), 61 new WebInspector.ScopeBarItem(WebInspector.LogContentView.Scopes.Logs, WebInspector.UIString("Logs")) 62 ]; 63 64 this._scopeBar = new WebInspector.ScopeBar("log-scope-bar", scopeBarItems, scopeBarItems[0]); 65 this._scopeBar.addEventListener(WebInspector.ScopeBar.Event.SelectionChanged, this._scopeBarSelectionDidChange, this); 66 67 var trashImage; 68 if (WebInspector.Platform.isLegacyMacOS) 69 trashImage = {src: "Images/Legacy/NavigationItemTrash.svg", width: 16, height: 16}; 70 else 71 trashImage = {src: "Images/NavigationItemTrash.svg", width: 15, height: 15}; 72 73 this._clearLogNavigationItem = new WebInspector.ButtonNavigationItem("clear-log", WebInspector.UIString("Clear log (%s or %s)").format(this._logViewController.messagesClearKeyboardShortcut.displayName, this._logViewController.messagesAlternateClearKeyboardShortcut.displayName), trashImage.src, trashImage.width, trashImage.height); 74 this._clearLogNavigationItem.addEventListener(WebInspector.ButtonNavigationItem.Event.Clicked, this._clearLog, this); 75 76 var toolTip = WebInspector.UIString("Show split console"); 77 var altToolTip = WebInspector.UIString("Show full-height console"); 78 79 this._toggleSplitNavigationItem = new WebInspector.ToggleButtonNavigationItem("split-toggle", toolTip, altToolTip, platformImagePath("SplitToggleDown.svg"), platformImagePath("SplitToggleUp.svg"), 16, 16); 80 this._toggleSplitNavigationItem.addEventListener(WebInspector.ButtonNavigationItem.Event.Clicked, this._toggleSplit, this); 81 this._toggleSplitNavigationItem.toggled = WebInspector.isShowingSplitConsole(); 82 83 this.messagesElement.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this), false); 84 85 WebInspector.logManager.addEventListener(WebInspector.LogManager.Event.Cleared, this._sessionsCleared, this); 86 WebInspector.logManager.addEventListener(WebInspector.LogManager.Event.SessionStarted, this._sessionStarted, this); 87 WebInspector.logManager.addEventListener(WebInspector.LogManager.Event.MessageAdded, this._messageAdded, this); 88 WebInspector.logManager.addEventListener(WebInspector.LogManager.Event.PreviousMessageRepeatCountUpdated, this._previousMessageRepeatCountUpdated, this); 89 WebInspector.logManager.addEventListener(WebInspector.LogManager.Event.ActiveLogCleared, this._activeLogCleared, this); 90} 91 92WebInspector.LogContentView.Scopes = { 93 All: "log-all", 94 Errors: "log-errors", 95 Warnings: "log-warnings", 96 Logs: "log-logs" 97}; 98 99WebInspector.LogContentView.StyleClassName = "log"; 100WebInspector.LogContentView.ItemWrapperStyleClassName = "console-item"; 101WebInspector.LogContentView.FilteredOutStyleClassName = "filtered-out"; 102WebInspector.LogContentView.SelectedStyleClassName = "selected"; 103WebInspector.LogContentView.SearchInProgressStyleClassName = "search-in-progress"; 104WebInspector.LogContentView.FilteredOutBySearchStyleClassName = "filtered-out-by-search"; 105WebInspector.LogContentView.HighlightedStyleClassName = "highlighted"; 106 107WebInspector.LogContentView.prototype = { 108 constructor: WebInspector.LogContentView, 109 110 // Public 111 112 get navigationItems() 113 { 114 return [this._searchBar, this._scopeBar, this._clearLogNavigationItem, this._toggleSplitNavigationItem]; 115 }, 116 117 get scopeBar() 118 { 119 return this._scopeBar; 120 }, 121 122 updateLayout: function() 123 { 124 WebInspector.ContentView.prototype.updateLayout.call(this); 125 126 this._scrollElementHeight = this.messagesElement.getBoundingClientRect().height; 127 }, 128 129 shown: function() 130 { 131 this._toggleSplitNavigationItem.toggled = WebInspector.isShowingSplitConsole(); 132 133 this.prompt.focus(); 134 }, 135 136 get scrollableElements() 137 { 138 return [this.element]; 139 }, 140 141 get shouldKeepElementsScrolledToBottom() 142 { 143 return true; 144 }, 145 146 get searchInProgress() 147 { 148 return this.messagesElement.classList.contains(WebInspector.LogContentView.SearchInProgressStyleClassName); 149 }, 150 151 didClearMessages: function() 152 { 153 if (this._ignoreDidClearMessages) 154 return; 155 WebInspector.logManager.requestClearMessages(); 156 }, 157 158 didAppendConsoleMessage: function(message) 159 { 160 WebInspector.quickConsole.updateLayout(); 161 162 // Nest the message. 163 if (message.type !== WebInspector.ConsoleMessage.MessageType.EndGroup) { 164 var x = 16 * this._nestingLevel; 165 var messageElement = message.toMessageElement(); 166 messageElement.style.left = x + "px"; 167 messageElement.style.width = "calc(100% - " + x + "px)"; 168 } 169 170 // Update the nesting level. 171 switch (message.type) { 172 case WebInspector.ConsoleMessage.MessageType.StartGroup: 173 case WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed: 174 ++this._nestingLevel; 175 break; 176 case WebInspector.ConsoleMessage.MessageType.EndGroup: 177 --this._nestingLevel; 178 break; 179 } 180 181 this._clearFocusableChildren(); 182 183 // Some results don't populate until further backend dispatches occur (like the DOM tree). 184 // We want to remove focusable children after those pending dispatches too. 185 InspectorBackend.runAfterPendingDispatches(this._clearFocusableChildren.bind(this)); 186 187 // We only auto show the console if the message is a result. 188 // This is when the user evaluated something directly in the prompt. 189 if (message.type !== WebInspector.ConsoleMessage.MessageType.Result) 190 return; 191 192 if (!WebInspector.isShowingConsoleView()) 193 WebInspector.showSplitConsole(); 194 195 this._logViewController.scrollToBottom(); 196 }, 197 198 promptDidChangeHeight: function() 199 { 200 WebInspector.quickConsole.updateLayout(); 201 }, 202 203 get supportsSave() 204 { 205 return true; 206 }, 207 208 get saveData() 209 { 210 return {url: "web-inspector:///Console.txt", content: this._formatMessagesAsData(false), forceSaveAs: true}; 211 }, 212 213 handleCopyEvent: function(event) 214 { 215 if (!this._selectedMessages.length) 216 return; 217 218 event.clipboardData.setData("text/plain", this._formatMessagesAsData(true)); 219 event.stopPropagation(); 220 event.preventDefault(); 221 }, 222 223 focusSearchBar: function() 224 { 225 this._searchBar.focus(); 226 }, 227 228 highlightPreviousSearchMatch: function() 229 { 230 if (!this.searchInProgress || isEmptyObject(this._searchMatches)) 231 return; 232 233 var index = this._selectedSearchMatch ? this._searchMatches.indexOf(this._selectedSearchMatch) : this._searchMatches.length; 234 this._highlightSearchMatchAtIndex(index - 1); 235 }, 236 237 highlightNextSearchMatch: function() 238 { 239 if (!this.searchInProgress || isEmptyObject(this._searchMatches)) 240 return; 241 242 var index = this._selectedSearchMatch ? this._searchMatches.indexOf(this._selectedSearchMatch) + 1 : 0; 243 this._highlightSearchMatchAtIndex(index); 244 }, 245 246 searchBarWantsToLoseFocus: function(searchBar) 247 { 248 if (this._selectedMessages.length) 249 this.messagesElement.focus(); 250 else 251 this.prompt.focus(); 252 }, 253 254 searchBarDidActivate: function(searchBar) 255 { 256 if (!isEmptyObject(this._searchMatches)) 257 this._highlightSearchMatchAtIndex(0); 258 this.prompt.focus(); 259 }, 260 261 // Private 262 263 _formatMessagesAsData: function(onlySelected) 264 { 265 var messages = this._allMessages(); 266 267 if (onlySelected) { 268 messages = this._allMessages().filter(function(message) { 269 return message.parentNode.classList.contains(WebInspector.LogContentView.SelectedStyleClassName); 270 }); 271 } 272 273 var data = ""; 274 275 var isPrefixOptional = messages.length <= 1 && onlySelected; 276 messages.forEach(function (messageElement, index) { 277 var messageObject = messageElement.message; 278 if (!messageObject) 279 messageObject = messageElement.command; 280 if (!messageObject) 281 return; 282 283 if (index > 0) 284 data += "\n"; 285 data += messageObject.toClipboardString(isPrefixOptional); 286 }); 287 288 return data; 289 }, 290 291 _sessionsCleared: function(event) 292 { 293 this._ignoreDidClearMessages = true; 294 this._logViewController.clear(); 295 this._ignoreDidClearMessages = false; 296 }, 297 298 _sessionStarted: function(event) 299 { 300 this._logViewController.startNewSession(); 301 }, 302 303 _messageAdded: function(event) 304 { 305 var message = this._logViewController.appendConsoleMessage(event.data.message); 306 if (message.type !== WebInspector.ConsoleMessage.MessageType.EndGroup) 307 this._filterMessages([message.toMessageElement()]); 308 }, 309 310 _previousMessageRepeatCountUpdated: function(event) 311 { 312 this._logViewController.updatePreviousMessageRepeatCount(event.data.count); 313 }, 314 315 _handleContextMenuEvent: function(event) 316 { 317 if (!window.getSelection().isCollapsed) { 318 // If there is a selection, we want to show our normal context menu 319 // (with Copy, etc.), and not Clear Log. 320 return; 321 } 322 323 // We don't want to show the custom menu for links in the console. 324 if (event.target.enclosingNodeOrSelfWithNodeName("a")) 325 return; 326 327 var contextMenu = new WebInspector.ContextMenu(event); 328 contextMenu.appendItem(WebInspector.UIString("Clear Log"), this._clearLog.bind(this)); 329 contextMenu.show(); 330 }, 331 332 _mousedown: function(event) 333 { 334 if (event.button !== 0 || event.ctrlKey) 335 return; 336 337 if (event.defaultPrevented) { 338 // Default was prevented on the event, so this means something deeper (like a disclosure triangle) 339 // handled the mouse down. In this case we want to clear the selection and don't make a new selection. 340 this._clearMessagesSelection(); 341 return; 342 } 343 344 if (!this._focused) { 345 this.messagesElement.focus(); 346 if (this._selectedMessages.length) 347 return; 348 } 349 350 this._mouseDownWrapper = event.target.enclosingNodeOrSelfWithClass(WebInspector.LogContentView.ItemWrapperStyleClassName); 351 this._mouseDownShiftKey = event.shiftKey; 352 this._mouseDownCommandKey = event.metaKey; 353 this._mouseMoveIsRowSelection = false; 354 355 window.addEventListener("mousemove", this); 356 window.addEventListener("mouseup", this); 357 }, 358 359 _targetInMessageCanBeSelected: function(target, message) 360 { 361 if (target.enclosingNodeOrSelfWithNodeName("a")) 362 return false; 363 return true; 364 }, 365 366 _mousemove: function(event) 367 { 368 var selection = window.getSelection(); 369 var wrapper = event.target.enclosingNodeOrSelfWithClass(WebInspector.LogContentView.ItemWrapperStyleClassName); 370 371 if (!wrapper) { 372 // No wrapper under the mouse, so look at the selection to try and find one. 373 if (!selection.isCollapsed) { 374 wrapper = selection.focusNode.parentNode.enclosingNodeOrSelfWithClass(WebInspector.LogContentView.ItemWrapperStyleClassName); 375 selection.removeAllRanges(); 376 } 377 378 if (!wrapper) 379 return; 380 } 381 382 if (!selection.isCollapsed) 383 this._clearMessagesSelection(); 384 385 if (wrapper === this._mouseDownWrapper && !this._mouseMoveIsRowSelection) 386 return; 387 388 selection.removeAllRanges(); 389 390 if (!this._mouseMoveIsRowSelection) 391 this._updateMessagesSelection(this._mouseDownWrapper.messageElement, this._mouseDownCommandKey, this._mouseDownShiftKey); 392 this._updateMessagesSelection(wrapper.messageElement, false, true); 393 394 this._mouseMoveIsRowSelection = true; 395 396 event.preventDefault(); 397 event.stopPropagation(); 398 }, 399 400 _mouseup: function(event) 401 { 402 window.removeEventListener("mousemove", this); 403 window.removeEventListener("mouseup", this); 404 405 var selection = window.getSelection(); 406 var wrapper = event.target.enclosingNodeOrSelfWithClass(WebInspector.LogContentView.ItemWrapperStyleClassName); 407 408 if (wrapper && (selection.isCollapsed || event.shiftKey)) { 409 selection.removeAllRanges(); 410 411 var message = wrapper.messageElement; 412 if (this._targetInMessageCanBeSelected(event.target, message)) { 413 var sameWrapper = wrapper === this._mouseDownWrapper; 414 this._mouseInteractionShouldPreventClickPropagation = !this._isMessageSelected(message); 415 this._updateMessagesSelection(message, sameWrapper ? this._mouseDownCommandKey : false, sameWrapper ? this._mouseDownShiftKey : true); 416 } 417 } else if (!selection.isCollapsed) { 418 // There is a text selection, clear the row selection. 419 this._clearMessagesSelection(); 420 } else if (!this._mouseDownWrapper) { 421 // The mouse didn't hit a console item, so clear the row selection. 422 this._clearMessagesSelection(); 423 424 // Focus the prompt. Focusing the prompt needs to happen after the click to work. 425 setTimeout(function () { this.prompt.focus() }.bind(this), 0); 426 } 427 428 delete this._mouseMoveIsRowSelection; 429 delete this._mouseDownWrapper; 430 delete this._mouseDownShiftKey; 431 delete this._mouseDownCommandKey; 432 }, 433 434 _click: function(event) 435 { 436 if (!this._mouseInteractionShouldPreventClickPropagation) 437 return; 438 439 event.stopPropagation(); 440 delete this._mouseInteractionShouldPreventClickPropagation; 441 }, 442 443 _ondragstart: function(event) 444 { 445 if (event.target.enclosingNodeOrSelfWithClass(WebInspector.DOMTreeOutline.StyleClassName)) { 446 event.stopPropagation(); 447 event.preventDefault(); 448 } 449 }, 450 451 handleEvent: function(event) 452 { 453 switch (event.type) { 454 case "mousemove": 455 this._mousemove(event); 456 break; 457 case "mouseup": 458 this._mouseup(event); 459 break; 460 } 461 }, 462 463 _updateMessagesSelection: function(message, multipleSelection, rangeSelection) 464 { 465 var alreadySelectedMessage = this._selectedMessages.contains(message); 466 if (alreadySelectedMessage && this._selectedMessages.length && multipleSelection) { 467 message.parentNode.classList.remove(WebInspector.LogContentView.SelectedStyleClassName); 468 this._selectedMessages.remove(message); 469 return; 470 } 471 472 if (!multipleSelection && !rangeSelection) 473 this._clearMessagesSelection(); 474 475 if (rangeSelection) { 476 var messages = this._visibleMessages(); 477 478 var refIndex = this._referenceMessageForRangeSelection ? messages.indexOf(this._referenceMessageForRangeSelection) : 0; 479 var targetIndex = messages.indexOf(message); 480 481 var newRange = [Math.min(refIndex, targetIndex), Math.max(refIndex, targetIndex)]; 482 483 if (this._selectionRange && this._selectionRange[0] === newRange[0] && this._selectionRange[1] === newRange[1]) 484 return; 485 486 var startIndex = this._selectionRange ? Math.min(this._selectionRange[0], newRange[0]) : newRange[0]; 487 var endIndex = this._selectionRange ? Math.max(this._selectionRange[1], newRange[1]) : newRange[1]; 488 489 for (var i = startIndex; i <= endIndex; ++i) { 490 var messageInRange = messages[i]; 491 if (i >= newRange[0] && i <= newRange[1] && !messageInRange.parentNode.classList.contains(WebInspector.LogContentView.SelectedStyleClassName)) { 492 messageInRange.parentNode.classList.add(WebInspector.LogContentView.SelectedStyleClassName); 493 this._selectedMessages.push(messageInRange); 494 } else if (i < newRange[0] || i > newRange[1] && messageInRange.parentNode.classList.contains(WebInspector.LogContentView.SelectedStyleClassName)) { 495 messageInRange.parentNode.classList.remove(WebInspector.LogContentView.SelectedStyleClassName); 496 this._selectedMessages.remove(messageInRange); 497 } 498 } 499 500 this._selectionRange = newRange; 501 } else { 502 message.parentNode.classList.add(WebInspector.LogContentView.SelectedStyleClassName); 503 this._selectedMessages.push(message); 504 } 505 506 if (!rangeSelection) 507 this._referenceMessageForRangeSelection = message; 508 509 if (!alreadySelectedMessage) 510 this._ensureMessageIsVisible(this._selectedMessages.lastValue); 511 }, 512 513 _ensureMessageIsVisible: function(message) 514 { 515 if (!message) 516 return; 517 518 var y = this._positionForMessage(message).y; 519 if (y < 0) { 520 this.element.scrollTop += y; 521 return; 522 } 523 524 var nextMessage = this._nextMessage(message); 525 if (nextMessage) { 526 y = this._positionForMessage(nextMessage).y; 527 if (y > this._scrollElementHeight) 528 this.element.scrollTop += y - this._scrollElementHeight; 529 } else { 530 y += message.getBoundingClientRect().height; 531 if (y > this._scrollElementHeight) 532 this.element.scrollTop += y - this._scrollElementHeight; 533 } 534 }, 535 536 _positionForMessage: function(message) 537 { 538 var pagePoint = window.webkitConvertPointFromNodeToPage(message, new WebKitPoint(0, 0)); 539 return window.webkitConvertPointFromPageToNode(this.element, pagePoint); 540 }, 541 542 _isMessageVisible: function(message) 543 { 544 var node = message.parentNode; 545 546 if (node.classList.contains(WebInspector.LogContentView.FilteredOutStyleClassName)) 547 return false; 548 549 if (this.searchInProgress && node.classList.contains(WebInspector.LogContentView.FilteredOutBySearchStyleClassName)) 550 return false; 551 552 if (message.classList.contains("console-group-title")) 553 node = node.parentNode.parentNode; 554 555 while (node && node !== this.messagesElement) { 556 if (node.classList.contains("collapsed")) 557 return false; 558 node = node.parentNode; 559 } 560 561 return true; 562 }, 563 564 _isMessageSelected: function(message) 565 { 566 return message.parentNode.classList.contains(WebInspector.LogContentView.SelectedStyleClassName); 567 }, 568 569 _clearMessagesSelection: function() 570 { 571 this._selectedMessages.forEach(function(message) { 572 message.parentNode.classList.remove(WebInspector.LogContentView.SelectedStyleClassName); 573 }); 574 this._selectedMessages = []; 575 delete this._referenceMessageForRangeSelection; 576 }, 577 578 _selectAllMessages: function() 579 { 580 this._clearMessagesSelection(); 581 582 var messages = this._visibleMessages(); 583 for (var i = 0; i < messages.length; ++i) { 584 var message = messages[i]; 585 message.parentNode.classList.add(WebInspector.LogContentView.SelectedStyleClassName); 586 this._selectedMessages.push(message); 587 } 588 }, 589 590 _allMessages: function() 591 { 592 return Array.prototype.slice.call(this.messagesElement.querySelectorAll(".console-message, .console-user-command")); 593 }, 594 595 _unfilteredMessages: function() 596 { 597 return this._allMessages().filter(function(message) { 598 return !message.parentNode.classList.contains(WebInspector.LogContentView.FilteredOutStyleClassName); 599 }); 600 }, 601 602 _visibleMessages: function() 603 { 604 var unfilteredMessages = this._unfilteredMessages(); 605 606 if (!this.searchInProgress) 607 return unfilteredMessages; 608 609 return unfilteredMessages.filter(function(message) { 610 return !message.parentNode.classList.contains(WebInspector.LogContentView.FilteredOutBySearchStyleClassName); 611 }); 612 }, 613 614 _activeLogCleared: function(event) 615 { 616 this._ignoreDidClearMessages = true; 617 this._logViewController.clear(); 618 this._ignoreDidClearMessages = false; 619 }, 620 621 _toggleSplit: function() 622 { 623 if (WebInspector.isShowingSplitConsole()) 624 WebInspector.showFullHeightConsole(); 625 else 626 WebInspector.showSplitConsole(); 627 }, 628 629 _clearLog: function() 630 { 631 this._logViewController.clear(); 632 }, 633 634 _scopeBarSelectionDidChange: function(event) 635 { 636 this._filterMessages(this._allMessages()); 637 }, 638 639 _filterMessages: function(messages) 640 { 641 var showsAll = this._scopeBar.item(WebInspector.LogContentView.Scopes.All).selected; 642 var showsErrors = this._scopeBar.item(WebInspector.LogContentView.Scopes.Errors).selected; 643 var showsWarnings = this._scopeBar.item(WebInspector.LogContentView.Scopes.Warnings).selected; 644 var showsLogs = this._scopeBar.item(WebInspector.LogContentView.Scopes.Logs).selected; 645 646 messages.forEach(function(message) { 647 var visible = showsAll || message.command instanceof WebInspector.ConsoleCommand || message.message instanceof WebInspector.ConsoleCommandResult; 648 if (!visible) { 649 switch(message.message.level) { 650 case WebInspector.ConsoleMessage.MessageLevel.Warning: 651 visible = showsWarnings; 652 break; 653 case WebInspector.ConsoleMessage.MessageLevel.Error: 654 visible = showsErrors; 655 break; 656 case WebInspector.ConsoleMessage.MessageLevel.Log: 657 visible = showsLogs; 658 break; 659 } 660 } 661 662 var classList = message.parentNode.classList; 663 if (visible) 664 classList.remove(WebInspector.LogContentView.FilteredOutStyleClassName); 665 else { 666 this._selectedMessages.remove(message); 667 classList.remove(WebInspector.LogContentView.SelectedStyleClassName); 668 classList.add(WebInspector.LogContentView.FilteredOutStyleClassName); 669 } 670 }.bind(this)); 671 672 this._performSearch(); 673 }, 674 675 _didFocus: function(event) 676 { 677 this._focused = true; 678 }, 679 680 _didBlur: function(event) 681 { 682 this._focused = false; 683 }, 684 685 _keyDown: function(event) 686 { 687 if (this._keyboardShortcutCommandA.matchesEvent(event)) 688 this._commandAWasPressed(event); 689 else if (this._keyboardShortcutEsc.matchesEvent(event)) 690 this._escapeWasPressed(event); 691 else if (event.keyIdentifier === "Up") 692 this._upArrowWasPressed(event); 693 else if (event.keyIdentifier === "Down") 694 this._downArrowWasPressed(event); 695 else if (event.keyIdentifier === "Left") 696 this._leftArrowWasPressed(event); 697 else if (event.keyIdentifier === "Right") 698 this._rightArrowWasPressed(event); 699 }, 700 701 _commandAWasPressed: function(event) 702 { 703 this._selectAllMessages(); 704 event.preventDefault(); 705 }, 706 707 _escapeWasPressed: function(event) 708 { 709 if (this._selectedMessages.length) 710 this._clearMessagesSelection(); 711 else 712 this.prompt.focus(); 713 714 event.preventDefault(); 715 }, 716 717 _upArrowWasPressed: function(event) 718 { 719 var messages = this._visibleMessages(); 720 721 if (!this._selectedMessages.length) { 722 if (messages.length) 723 this._updateMessagesSelection(messages.lastValue, false, false); 724 return; 725 } 726 727 var lastMessage = this._selectedMessages.lastValue; 728 var previousMessage = this._previousMessage(lastMessage); 729 if (previousMessage) 730 this._updateMessagesSelection(previousMessage, false, event.shiftKey); 731 else if (!event.shiftKey) { 732 this._clearMessagesSelection(); 733 this._updateMessagesSelection(messages[0], false, false); 734 } 735 736 event.preventDefault(); 737 }, 738 739 _downArrowWasPressed: function(event) 740 { 741 var messages = this._visibleMessages(); 742 743 if (!this._selectedMessages.length) { 744 if (messages.length) 745 this._updateMessagesSelection(messages[0], false, false); 746 return; 747 } 748 749 var lastMessage = this._selectedMessages.lastValue; 750 var nextMessage = this._nextMessage(lastMessage); 751 if (nextMessage) 752 this._updateMessagesSelection(nextMessage, false, event.shiftKey); 753 else if (!event.shiftKey) { 754 this._clearMessagesSelection(); 755 this._updateMessagesSelection(messages.lastValue, false, false); 756 } 757 758 event.preventDefault(); 759 }, 760 761 _leftArrowWasPressed: function(event) 762 { 763 if (this._selectedMessages.length !== 1) 764 return; 765 766 var currentMessage = this._selectedMessages[0]; 767 if (currentMessage.classList.contains("console-group-title")) 768 currentMessage.parentNode.parentNode.classList.add("collapsed"); 769 else { 770 var outlineTitle = currentMessage.querySelector("ol.outline-disclosure > li.parent"); 771 if (outlineTitle) { 772 if (event.altKey) 773 outlineTitle.treeElement.collapseRecursively(); 774 else 775 outlineTitle.treeElement.collapse(); 776 } else { 777 var outlineSection = currentMessage.querySelector(".console-formatted-object > .section"); 778 if (outlineSection) 779 outlineSection._section.collapse(); 780 } 781 } 782 }, 783 784 _rightArrowWasPressed: function(event) 785 { 786 if (this._selectedMessages.length !== 1) 787 return; 788 789 var currentMessage = this._selectedMessages[0]; 790 if (currentMessage.classList.contains("console-group-title")) 791 currentMessage.parentNode.parentNode.classList.remove("collapsed"); 792 else { 793 var outlineTitle = currentMessage.querySelector("ol.outline-disclosure > li.parent"); 794 if (outlineTitle) { 795 outlineTitle.treeElement.onexpand = function() { 796 setTimeout(function () { 797 this._ensureMessageIsVisible(currentMessage); 798 this._clearFocusableChildren(); 799 delete outlineTitle.treeElement.onexpand; 800 }.bind(this)); 801 }.bind(this); 802 803 if (event.altKey) 804 outlineTitle.treeElement.expandRecursively(); 805 else 806 outlineTitle.treeElement.expand(); 807 } else { 808 var outlineSection = currentMessage.querySelector(".console-formatted-object > .section"); 809 if (outlineSection) { 810 outlineSection._section.addEventListener(WebInspector.Section.Event.VisibleContentDidChange, this._propertiesSectionDidUpdateContent, this); 811 outlineSection._section.expand(); 812 } 813 } 814 } 815 }, 816 817 _propertiesSectionDidUpdateContent: function(event) 818 { 819 var section = event.target; 820 section.removeEventListener(WebInspector.Section.Event.VisibleContentDidChange, this._propertiesSectionDidUpdateContent, this); 821 822 var message = section.element.enclosingNodeOrSelfWithClass(WebInspector.LogContentView.ItemWrapperStyleClassName).messageElement; 823 if (!this._isMessageSelected(message)) 824 return; 825 826 setTimeout(function () { 827 this._ensureMessageIsVisible(message); 828 this._clearFocusableChildren(); 829 }.bind(this)); 830 }, 831 832 _previousMessage: function(message) 833 { 834 var messages = this._visibleMessages(); 835 for (var i = messages.indexOf(message) - 1; i >= 0; --i) { 836 if (this._isMessageVisible(messages[i])) 837 return messages[i]; 838 } 839 }, 840 841 _nextMessage: function(message) 842 { 843 var messages = this._visibleMessages(); 844 for (var i = messages.indexOf(message) + 1; i < messages.length; ++i) { 845 if (this._isMessageVisible(messages[i])) 846 return messages[i]; 847 } 848 }, 849 850 _clearFocusableChildren: function() 851 { 852 var focusableElements = this.messagesElement.querySelectorAll("[tabindex]"); 853 for (var i = 0, count = focusableElements.length; i < count; ++i) 854 focusableElements[i].removeAttribute("tabindex"); 855 }, 856 857 _searchTextDidChange: function(event) 858 { 859 this._performSearch(); 860 }, 861 862 _performSearch: function() 863 { 864 if (!isEmptyObject(this._searchHighlightDOMChanges)) 865 WebInspector.revertDomChanges(this._searchHighlightDOMChanges); 866 867 var searchTerms = this._searchBar.text; 868 869 if (searchTerms === "") { 870 delete this._selectedSearchMatch; 871 this._matchingSearchElements = []; 872 this.messagesElement.classList.remove(WebInspector.LogContentView.SearchInProgressStyleClassName); 873 return; 874 } 875 876 this.messagesElement.classList.add(WebInspector.LogContentView.SearchInProgressStyleClassName); 877 878 this._searchHighlightDOMChanges = []; 879 this._searchMatches = []; 880 this._selectedSearchMathIsValid = false; 881 882 var searchRegex = new RegExp(searchTerms.escapeForRegExp(), "gi"); 883 this._unfilteredMessages().forEach(function(message) { 884 var matchRanges = []; 885 var text = message.textContent; 886 var match = searchRegex.exec(text); 887 while (match) { 888 matchRanges.push({ offset: match.index, length: match[0].length }); 889 match = searchRegex.exec(text); 890 } 891 892 if (!isEmptyObject(matchRanges)) 893 this._highlightRanges(message, matchRanges); 894 895 var classList = message.parentNode.classList; 896 if (!isEmptyObject(matchRanges) || message.command instanceof WebInspector.ConsoleCommand || message.message instanceof WebInspector.ConsoleCommandResult) 897 classList.remove(WebInspector.LogContentView.FilteredOutBySearchStyleClassName); 898 else 899 classList.add(WebInspector.LogContentView.FilteredOutBySearchStyleClassName); 900 }, this); 901 902 if (!this._selectedSearchMathIsValid && this._selectedSearchMatch) { 903 this._selectedSearchMatch.highlight.classList.remove(WebInspector.LogContentView.SelectedStyleClassName); 904 delete this._selectedSearchMatch; 905 } 906 }, 907 908 _highlightRanges: function(message, matchRanges) 909 { 910 var highlightedElements = WebInspector.highlightRangesWithStyleClass(message, matchRanges, WebInspector.LogContentView.HighlightedStyleClassName, this._searchHighlightDOMChanges); 911 912 console.assert(highlightedElements.length === matchRanges.length); 913 914 matchRanges.forEach(function (range, index) { 915 this._searchMatches.push({ 916 message: message, 917 range: range, 918 highlight: highlightedElements[index] 919 }); 920 921 if (this._selectedSearchMatch && !this._selectedSearchMathIsValid && this._selectedSearchMatch.message === message) { 922 this._selectedSearchMathIsValid = this._rangesOverlap(this._selectedSearchMatch.range, range); 923 if (this._selectedSearchMathIsValid) { 924 delete this._selectedSearchMatch; 925 this._highlightSearchMatchAtIndex(this._searchMatches.length - 1); 926 } 927 } 928 }, this); 929 }, 930 931 _rangesOverlap: function(range1, range2) 932 { 933 return range1.offset <= range2.offset + range2.length && range2.offset <= range1.offset + range1.length; 934 }, 935 936 _highlightSearchMatchAtIndex: function(index) 937 { 938 if (index >= this._searchMatches.length) 939 index = 0; 940 else if (index < 0) 941 index = this._searchMatches.length - 1; 942 943 if (this._selectedSearchMatch) 944 this._selectedSearchMatch.highlight.classList.remove(WebInspector.LogContentView.SelectedStyleClassName); 945 946 this._selectedSearchMatch = this._searchMatches[index]; 947 this._selectedSearchMatch.highlight.classList.add(WebInspector.LogContentView.SelectedStyleClassName); 948 949 this._ensureMessageIsVisible(this._selectedSearchMatch.message); 950 } 951} 952 953WebInspector.LogContentView.prototype.__proto__ = WebInspector.ContentView.prototype; 954