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