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