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