1/* 2 * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. 3 * Copyright (C) 2009 Joseph Pecoraro 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of 15 * its contributors may be used to endorse or promote products derived 16 * from this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 19 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 25 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 */ 29 30/** 31 * @extends {WebInspector.View} 32 * @implements {WebInspector.Searchable} 33 * @constructor 34 * @param {boolean} hideContextSelector 35 */ 36WebInspector.ConsoleView = function(hideContextSelector) 37{ 38 WebInspector.View.call(this); 39 40 this.element.id = "console-view"; 41 this._visibleMessagesIndices = []; 42 this._urlToMessageCount = {}; 43 44 this._clearConsoleButton = new WebInspector.StatusBarButton(WebInspector.UIString("Clear console log."), "clear-status-bar-item"); 45 this._clearConsoleButton.addEventListener("click", this._requestClearMessages, this); 46 47 this._frameSelector = new WebInspector.StatusBarComboBox(this._frameChanged.bind(this), "console-context"); 48 this._contextSelector = new WebInspector.StatusBarComboBox(this._contextChanged.bind(this), "console-context"); 49 50 this._filter = new WebInspector.ConsoleViewFilter(); 51 this._filter.addEventListener(WebInspector.ConsoleViewFilter.Events.FilterChanged, this._updateMessageList.bind(this)); 52 53 if (hideContextSelector) { 54 this._frameSelector.element.addStyleClass("hidden"); 55 this._contextSelector.element.addStyleClass("hidden"); 56 } 57 58 this.messagesElement = document.createElement("div"); 59 this.messagesElement.id = "console-messages"; 60 this.messagesElement.className = "monospace"; 61 this.messagesElement.addEventListener("click", this._messagesClicked.bind(this), true); 62 this.element.appendChild(this.messagesElement); 63 this._scrolledToBottom = true; 64 65 this.promptElement = document.createElement("div"); 66 this.promptElement.id = "console-prompt"; 67 this.promptElement.className = "source-code"; 68 this.promptElement.spellcheck = false; 69 this.messagesElement.appendChild(this.promptElement); 70 this.messagesElement.appendChild(document.createElement("br")); 71 72 this.topGroup = new WebInspector.ConsoleGroup(null); 73 this.messagesElement.insertBefore(this.topGroup.element, this.promptElement); 74 this.currentGroup = this.topGroup; 75 76 this._registerShortcuts(); 77 this.registerRequiredCSS("textPrompt.css"); 78 79 this.messagesElement.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this), false); 80 81 WebInspector.settings.monitoringXHREnabled.addChangeListener(this._monitoringXHREnabledSettingChanged.bind(this)); 82 83 WebInspector.console.addEventListener(WebInspector.ConsoleModel.Events.MessageAdded, this._consoleMessageAdded, this); 84 WebInspector.console.addEventListener(WebInspector.ConsoleModel.Events.ConsoleCleared, this._consoleCleared, this); 85 86 this._linkifier = new WebInspector.Linkifier(); 87 88 this.prompt = new WebInspector.TextPromptWithHistory(WebInspector.runtimeModel.completionsForTextPrompt.bind(WebInspector.runtimeModel)); 89 this.prompt.setSuggestBoxEnabled("generic-suggest"); 90 this.prompt.renderAsBlock(); 91 this.prompt.attach(this.promptElement); 92 this.prompt.proxyElement.addEventListener("keydown", this._promptKeyDown.bind(this), false); 93 this.prompt.setHistoryData(WebInspector.settings.consoleHistory.get()); 94 95 WebInspector.runtimeModel.contextLists().forEach(this._addFrame, this); 96 WebInspector.runtimeModel.addEventListener(WebInspector.RuntimeModel.Events.FrameExecutionContextListAdded, this._frameAdded, this); 97 WebInspector.runtimeModel.addEventListener(WebInspector.RuntimeModel.Events.FrameExecutionContextListRemoved, this._frameRemoved, this); 98 99 this._filterStatusMessageElement = document.createElement("div"); 100 this._filterStatusMessageElement.classList.add("console-message"); 101 this._filterStatusTextElement = this._filterStatusMessageElement.createChild("span", "console-info"); 102 this._filterStatusMessageElement.createTextChild(" "); 103 var resetFiltersLink = this._filterStatusMessageElement.createChild("span", "console-info node-link"); 104 resetFiltersLink.textContent = WebInspector.UIString("Show all messages."); 105 resetFiltersLink.addEventListener("click", this._filter.reset.bind(this._filter), true); 106 107 this.messagesElement.insertBefore(this._filterStatusMessageElement, this.topGroup.element); 108 109 this._updateFilterStatus(); 110} 111 112WebInspector.ConsoleView.prototype = { 113 get statusBarItems() 114 { 115 return [this._clearConsoleButton.element, this._frameSelector.element, this._contextSelector.element, this._filter.sourceFilterButton.element, this._filter.filterBarElement]; 116 }, 117 118 /** 119 * @param {WebInspector.Event} event 120 */ 121 _frameAdded: function(event) 122 { 123 var contextList = /** @type {WebInspector.FrameExecutionContextList} */ (event.data); 124 this._addFrame(contextList); 125 }, 126 127 /** 128 * @param {WebInspector.FrameExecutionContextList} contextList 129 */ 130 _addFrame: function(contextList) 131 { 132 var option = this._frameSelector.createOption(contextList.displayName, contextList.url); 133 option._contextList = contextList; 134 contextList._consoleOption = option; 135 contextList.addEventListener(WebInspector.FrameExecutionContextList.EventTypes.ContextsUpdated, this._frameUpdated, this); 136 contextList.addEventListener(WebInspector.FrameExecutionContextList.EventTypes.ContextAdded, this._contextAdded, this); 137 this._frameChanged(); 138 }, 139 140 /** 141 * @param {WebInspector.Event} event 142 */ 143 _frameRemoved: function(event) 144 { 145 var contextList = /** @type {WebInspector.FrameExecutionContextList} */ (event.data); 146 this._frameSelector.removeOption(contextList._consoleOption); 147 this._frameChanged(); 148 }, 149 150 _frameChanged: function() 151 { 152 var context = this._currentFrame(); 153 if (!context) { 154 WebInspector.runtimeModel.setCurrentExecutionContext(null); 155 this._contextSelector.element.addStyleClass("hidden"); 156 return; 157 } 158 159 var executionContexts = context.executionContexts(); 160 if (executionContexts.length) 161 WebInspector.runtimeModel.setCurrentExecutionContext(executionContexts[0]); 162 163 if (executionContexts.length === 1) { 164 this._contextSelector.element.addStyleClass("hidden"); 165 return; 166 } 167 this._contextSelector.element.removeStyleClass("hidden"); 168 this._contextSelector.removeOptions(); 169 for (var i = 0; i < executionContexts.length; ++i) 170 this._appendContextOption(executionContexts[i]); 171 }, 172 173 /** 174 * @param {WebInspector.ExecutionContext} executionContext 175 */ 176 _appendContextOption: function(executionContext) 177 { 178 if (!WebInspector.runtimeModel.currentExecutionContext()) 179 WebInspector.runtimeModel.setCurrentExecutionContext(executionContext); 180 var option = this._contextSelector.createOption(executionContext.name, executionContext.id); 181 option._executionContext = executionContext; 182 }, 183 184 /** 185 * @param {Event} event 186 */ 187 _contextChanged: function(event) 188 { 189 var option = this._contextSelector.selectedOption(); 190 WebInspector.runtimeModel.setCurrentExecutionContext(option ? option._executionContext : null); 191 }, 192 193 /** 194 * @param {WebInspector.Event} event 195 */ 196 _frameUpdated: function(event) 197 { 198 var contextList = /** @type {WebInspector.FrameExecutionContextList} */ (event.data); 199 var option = contextList._consoleOption; 200 option.text = contextList.displayName; 201 option.title = contextList.url; 202 }, 203 204 /** 205 * @param {WebInspector.Event} event 206 */ 207 _contextAdded: function(event) 208 { 209 var contextList = /** @type {WebInspector.FrameExecutionContextList} */ (event.data); 210 if (contextList === this._currentFrame()) 211 this._frameChanged(); 212 }, 213 214 /** 215 * @return {WebInspector.FrameExecutionContextList|undefined} 216 */ 217 _currentFrame: function() 218 { 219 var option = this._frameSelector.selectedOption(); 220 return option ? option._contextList : undefined; 221 }, 222 223 willHide: function() 224 { 225 this.prompt.hideSuggestBox(); 226 this.prompt.clearAutoComplete(true); 227 }, 228 229 wasShown: function() 230 { 231 if (!this.prompt.isCaretInsidePrompt()) 232 this.prompt.moveCaretToEndOfPrompt(); 233 }, 234 235 afterShow: function() 236 { 237 WebInspector.setCurrentFocusElement(this.promptElement); 238 }, 239 240 storeScrollPositions: function() 241 { 242 WebInspector.View.prototype.storeScrollPositions.call(this); 243 this._scrolledToBottom = this.messagesElement.isScrolledToBottom(); 244 }, 245 246 restoreScrollPositions: function() 247 { 248 if (this._scrolledToBottom) 249 this._immediatelyScrollIntoView(); 250 else 251 WebInspector.View.prototype.restoreScrollPositions.call(this); 252 }, 253 254 onResize: function() 255 { 256 this.restoreScrollPositions(); 257 }, 258 259 _isScrollIntoViewScheduled: function() 260 { 261 return !!this._scrollIntoViewTimer; 262 }, 263 264 _scheduleScrollIntoView: function() 265 { 266 if (this._scrollIntoViewTimer) 267 return; 268 269 function scrollIntoView() 270 { 271 delete this._scrollIntoViewTimer; 272 this.promptElement.scrollIntoView(true); 273 } 274 this._scrollIntoViewTimer = setTimeout(scrollIntoView.bind(this), 20); 275 }, 276 277 _immediatelyScrollIntoView: function() 278 { 279 this.promptElement.scrollIntoView(true); 280 this._cancelScheduledScrollIntoView(); 281 }, 282 283 _cancelScheduledScrollIntoView: function() 284 { 285 if (!this._isScrollIntoViewScheduled()) 286 return; 287 288 clearTimeout(this._scrollIntoViewTimer); 289 delete this._scrollIntoViewTimer; 290 }, 291 292 /** 293 * @param {number=} count 294 */ 295 _updateFilterStatus: function(count) { 296 count = (typeof count === undefined) ? (WebInspector.console.messages.length - this._visibleMessagesIndices.length) : count; 297 this._filterStatusTextElement.textContent = WebInspector.UIString(count == 1 ? "%d message is hidden by filters." : "%d messages are hidden by filters.", count); 298 this._filterStatusMessageElement.style.display = count ? "" : "none"; 299 }, 300 301 /** 302 * @param {WebInspector.Event} event 303 */ 304 _consoleMessageAdded: function(event) 305 { 306 var message = /** @type {WebInspector.ConsoleMessage} */ (event.data); 307 var index = message.index; 308 309 if (this._urlToMessageCount[message.url]) 310 this._urlToMessageCount[message.url]++; 311 else 312 this._urlToMessageCount[message.url] = 1; 313 314 if (this._filter.shouldBeVisible(message)) 315 this._showConsoleMessage(index); 316 else 317 this._updateFilterStatus(); 318 }, 319 320 _showConsoleMessage: function(index) 321 { 322 var message = WebInspector.console.messages[index]; 323 324 // this.messagesElement.isScrolledToBottom() is forcing style recalculation. 325 // We just skip it if the scroll action has been scheduled. 326 if (!this._isScrollIntoViewScheduled() && ((message instanceof WebInspector.ConsoleCommandResult) || this.messagesElement.isScrolledToBottom())) 327 this._scheduleScrollIntoView(); 328 329 this._visibleMessagesIndices.push(index); 330 331 if (message.type === WebInspector.ConsoleMessage.MessageType.EndGroup) { 332 var parentGroup = this.currentGroup.parentGroup; 333 if (parentGroup) 334 this.currentGroup = parentGroup; 335 } else { 336 if (message.type === WebInspector.ConsoleMessage.MessageType.StartGroup || message.type === WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed) { 337 var group = new WebInspector.ConsoleGroup(this.currentGroup); 338 this.currentGroup.messagesElement.appendChild(group.element); 339 this.currentGroup = group; 340 message.group = group; 341 } 342 this.currentGroup.addMessage(message); 343 } 344 345 if (this._searchRegex && message.matchesRegex(this._searchRegex)) { 346 this._searchResultsIndices.push(index); 347 WebInspector.searchController.updateSearchMatchesCount(this._searchResultsIndices.length, this._searchProvider); 348 } 349 }, 350 351 _consoleCleared: function() 352 { 353 this._scrolledToBottom = true; 354 for (var i = 0; i < this._visibleMessagesIndices.length; ++i) 355 WebInspector.console.messages[this._visibleMessagesIndices[i]].willHide(); 356 this._visibleMessagesIndices = []; 357 this._searchResultsIndices = []; 358 359 if (this._searchRegex) 360 WebInspector.searchController.updateSearchMatchesCount(0, this._searchProvider); 361 362 this.currentGroup = this.topGroup; 363 this.topGroup.messagesElement.removeChildren(); 364 365 this._clearCurrentSearchResultHighlight(); 366 this._updateFilterStatus(0); 367 368 this._linkifier.reset(); 369 }, 370 371 _handleContextMenuEvent: function(event) 372 { 373 if (!window.getSelection().isCollapsed) { 374 // If there is a selection, we want to show our normal context menu 375 // (with Copy, etc.), and not Clear Console. 376 return; 377 } 378 379 if (event.target.enclosingNodeOrSelfWithNodeName("a")) 380 return; 381 382 var contextMenu = new WebInspector.ContextMenu(event); 383 384 function monitoringXHRItemAction() 385 { 386 WebInspector.settings.monitoringXHREnabled.set(!WebInspector.settings.monitoringXHREnabled.get()); 387 } 388 contextMenu.appendCheckboxItem(WebInspector.UIString("Log XMLHttpRequests"), monitoringXHRItemAction.bind(this), WebInspector.settings.monitoringXHREnabled.get()); 389 390 function preserveLogItemAction() 391 { 392 WebInspector.settings.preserveConsoleLog.set(!WebInspector.settings.preserveConsoleLog.get()); 393 } 394 contextMenu.appendCheckboxItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Preserve log upon navigation" : "Preserve Log upon Navigation"), preserveLogItemAction.bind(this), WebInspector.settings.preserveConsoleLog.get()); 395 396 var sourceElement = event.target.enclosingNodeOrSelfWithClass("console-message"); 397 398 var filterSubMenu = contextMenu.appendSubMenuItem(WebInspector.UIString("Filter")); 399 400 if (sourceElement && sourceElement.message.url) { 401 var menuTitle = WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Hide messages from %s" : "Hide Messages from %s", new WebInspector.ParsedURL(sourceElement.message.url).displayName); 402 filterSubMenu.appendItem(menuTitle, this._filter.addMessageURLFilter.bind(this._filter, sourceElement.message.url)); 403 } 404 405 filterSubMenu.appendSeparator(); 406 var unhideAll = filterSubMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Unhide all" : "Unhide All"), this._filter.removeMessageURLFilter.bind(this._filter)); 407 filterSubMenu.appendSeparator(); 408 409 var hasFilters = false; 410 411 for (var url in this._filter.messageURLFilters) { 412 filterSubMenu.appendCheckboxItem(String.sprintf("%s (%d)", new WebInspector.ParsedURL(url).displayName, this._urlToMessageCount[url]), this._filter.removeMessageURLFilter.bind(this._filter, url), true); 413 hasFilters = true; 414 } 415 416 filterSubMenu.setEnabled(hasFilters || (sourceElement && sourceElement.message.url)); 417 unhideAll.setEnabled(hasFilters); 418 419 contextMenu.appendSeparator(); 420 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Clear console" : "Clear Console"), this._requestClearMessages.bind(this)); 421 422 var request = (sourceElement && sourceElement.message) ? sourceElement.message.request() : null; 423 if (request && request.type === WebInspector.resourceTypes.XHR) { 424 contextMenu.appendSeparator(); 425 contextMenu.appendItem(WebInspector.UIString("Replay XHR"), NetworkAgent.replayXHR.bind(null, request.requestId)); 426 } 427 428 contextMenu.show(); 429 }, 430 431 _updateMessageList: function() 432 { 433 var group = this.topGroup; 434 var sourceMessages = WebInspector.console.messages; 435 var visibleMessageIndex = 0; 436 var newVisibleMessages = []; 437 438 if (this._searchRegex) 439 this._searchResultsIndices = []; 440 441 var anchor = null; 442 for (var i = 0; i < sourceMessages.length; ++i) { 443 var sourceMessage = sourceMessages[i]; 444 var visibleMessage = WebInspector.console.messages[this._visibleMessagesIndices[visibleMessageIndex]]; 445 446 if (visibleMessage === sourceMessage) { 447 if (this._filter.shouldBeVisible(visibleMessage)) { 448 newVisibleMessages.push(this._visibleMessagesIndices[visibleMessageIndex]); 449 450 if (this._searchRegex && sourceMessage.matchesRegex(this._searchRegex)) 451 this._searchResultsIndices.push(i); 452 453 if (sourceMessage.type === WebInspector.ConsoleMessage.MessageType.EndGroup) { 454 anchor = group.element; 455 group = group.parentGroup || group; 456 } else if (sourceMessage.type === WebInspector.ConsoleMessage.MessageType.StartGroup || sourceMessage.type === WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed) { 457 group = sourceMessage.group; 458 anchor = group.messagesElement.firstChild; 459 } else 460 anchor = visibleMessage.toMessageElement(); 461 } else { 462 visibleMessage.willHide(); 463 visibleMessage.toMessageElement().remove(); 464 } 465 ++visibleMessageIndex; 466 } else { 467 if (this._filter.shouldBeVisible(sourceMessage)) { 468 469 if (this._searchRegex && sourceMessage.matchesRegex(this._searchRegex)) 470 this._searchResultsIndices.push(i); 471 472 group.addMessage(sourceMessage, anchor ? anchor.nextSibling : group.messagesElement.firstChild); 473 newVisibleMessages.push(i); 474 anchor = sourceMessage.toMessageElement(); 475 } 476 } 477 } 478 479 if (this._searchRegex) 480 WebInspector.searchController.updateSearchMatchesCount(this._searchResultsIndices.length, this._searchProvider); 481 482 this._visibleMessagesIndices = newVisibleMessages; 483 this._updateFilterStatus(); 484 }, 485 486 _monitoringXHREnabledSettingChanged: function(event) 487 { 488 ConsoleAgent.setMonitoringXHREnabled(event.data); 489 }, 490 491 _messagesClicked: function() 492 { 493 if (!this.prompt.isCaretInsidePrompt() && window.getSelection().isCollapsed) 494 this.prompt.moveCaretToEndOfPrompt(); 495 }, 496 497 _registerShortcuts: function() 498 { 499 this._shortcuts = {}; 500 501 var shortcut = WebInspector.KeyboardShortcut; 502 var section = WebInspector.shortcutsScreen.section(WebInspector.UIString("Console")); 503 504 var shortcutL = shortcut.makeDescriptor("l", WebInspector.KeyboardShortcut.Modifiers.Ctrl); 505 this._shortcuts[shortcutL.key] = this._requestClearMessages.bind(this); 506 var keys = [shortcutL]; 507 if (WebInspector.isMac()) { 508 var shortcutK = shortcut.makeDescriptor("k", WebInspector.KeyboardShortcut.Modifiers.Meta); 509 this._shortcuts[shortcutK.key] = this._requestClearMessages.bind(this); 510 keys.unshift(shortcutK); 511 } 512 section.addAlternateKeys(keys, WebInspector.UIString("Clear console")); 513 514 section.addKey(shortcut.makeDescriptor(shortcut.Keys.Tab), WebInspector.UIString("Autocomplete common prefix")); 515 section.addKey(shortcut.makeDescriptor(shortcut.Keys.Right), WebInspector.UIString("Accept suggestion")); 516 517 keys = [ 518 shortcut.makeDescriptor(shortcut.Keys.Down), 519 shortcut.makeDescriptor(shortcut.Keys.Up) 520 ]; 521 section.addRelatedKeys(keys, WebInspector.UIString("Next/previous line")); 522 523 if (WebInspector.isMac()) { 524 keys = [ 525 shortcut.makeDescriptor("N", shortcut.Modifiers.Alt), 526 shortcut.makeDescriptor("P", shortcut.Modifiers.Alt) 527 ]; 528 section.addRelatedKeys(keys, WebInspector.UIString("Next/previous command")); 529 } 530 531 section.addKey(shortcut.makeDescriptor(shortcut.Keys.Enter), WebInspector.UIString("Execute command")); 532 }, 533 534 _requestClearMessages: function() 535 { 536 WebInspector.console.requestClearMessages(); 537 }, 538 539 _promptKeyDown: function(event) 540 { 541 if (isEnterKey(event)) { 542 this._enterKeyPressed(event); 543 return; 544 } 545 546 var shortcut = WebInspector.KeyboardShortcut.makeKeyFromEvent(event); 547 var handler = this._shortcuts[shortcut]; 548 if (handler) { 549 handler(); 550 event.preventDefault(); 551 } 552 }, 553 554 evaluateUsingTextPrompt: function(expression, showResultOnly) 555 { 556 this._appendCommand(expression, this.prompt.text, false, showResultOnly); 557 }, 558 559 _enterKeyPressed: function(event) 560 { 561 if (event.altKey || event.ctrlKey || event.shiftKey) 562 return; 563 564 event.consume(true); 565 566 this.prompt.clearAutoComplete(true); 567 568 var str = this.prompt.text; 569 if (!str.length) 570 return; 571 this._appendCommand(str, "", true, false); 572 }, 573 574 _printResult: function(result, wasThrown, originatingCommand) 575 { 576 if (!result) 577 return; 578 var message = new WebInspector.ConsoleCommandResult(result, wasThrown, originatingCommand, this._linkifier); 579 WebInspector.console.addMessage(message); 580 }, 581 582 _appendCommand: function(text, newPromptText, useCommandLineAPI, showResultOnly) 583 { 584 if (!showResultOnly) { 585 var commandMessage = new WebInspector.ConsoleCommand(text); 586 WebInspector.console.addMessage(commandMessage); 587 } 588 this.prompt.text = newPromptText; 589 590 function printResult(result, wasThrown) 591 { 592 if (!result) 593 return; 594 595 if (!showResultOnly) { 596 this.prompt.pushHistoryItem(text); 597 WebInspector.settings.consoleHistory.set(this.prompt.historyData.slice(-30)); 598 } 599 600 this._printResult(result, wasThrown, commandMessage); 601 } 602 WebInspector.runtimeModel.evaluate(text, "console", useCommandLineAPI, false, false, true, printResult.bind(this)); 603 604 WebInspector.userMetrics.ConsoleEvaluated.record(); 605 }, 606 607 elementsToRestoreScrollPositionsFor: function() 608 { 609 return [this.messagesElement]; 610 }, 611 612 searchCanceled: function() 613 { 614 this._clearCurrentSearchResultHighlight(); 615 delete this._searchProvider; 616 delete this._searchResultsIndices; 617 delete this._searchRegex; 618 }, 619 620 canSearchAndReplace: function() 621 { 622 return false; 623 }, 624 625 canFilter: function() 626 { 627 return true; 628 }, 629 630 /** 631 * @param {string} query 632 * @param {boolean} shouldJump 633 * @param {WebInspector.Searchable=} self 634 */ 635 performSearch: function(query, shouldJump, self) 636 { 637 this.searchCanceled(); 638 this._searchProvider = self || this; 639 WebInspector.searchController.updateSearchMatchesCount(0, this._searchProvider); 640 this._searchRegex = createPlainTextSearchRegex(query, "gi"); 641 642 this._searchResultsIndices = []; 643 for (var i = 0; i < this._visibleMessagesIndices.length; i++) { 644 if (WebInspector.console.messages[this._visibleMessagesIndices[i]].matchesRegex(this._searchRegex)) 645 this._searchResultsIndices.push(this._visibleMessagesIndices[i]); 646 } 647 WebInspector.searchController.updateSearchMatchesCount(this._searchResultsIndices.length, this._searchProvider); 648 this._currentSearchResultIndex = -1; 649 if (shouldJump && this._searchResultsIndices.length) 650 this._jumpToSearchResult(0, self); 651 }, 652 653 /** 654 * @return {number} 655 */ 656 minimalSearchQuerySize: function() 657 { 658 return 0; 659 }, 660 661 /** 662 * @param {string} query 663 */ 664 performFilter: function(query) 665 { 666 this._filter.performFilter(query); 667 }, 668 669 /** 670 * @param {WebInspector.Searchable=} self 671 */ 672 jumpToNextSearchResult: function(self) 673 { 674 if (!this._searchResultsIndices || !this._searchResultsIndices.length) 675 return; 676 this._jumpToSearchResult((this._currentSearchResultIndex + 1) % this._searchResultsIndices.length, self); 677 }, 678 679 /** 680 * @param {WebInspector.Searchable=} self 681 */ 682 jumpToPreviousSearchResult: function(self) 683 { 684 if (!this._searchResultsIndices || !this._searchResultsIndices.length) 685 return; 686 var index = this._currentSearchResultIndex - 1; 687 if (index === -1) 688 index = this._searchResultsIndices.length - 1; 689 this._jumpToSearchResult(index, self); 690 }, 691 692 _clearCurrentSearchResultHighlight: function() 693 { 694 if (!this._searchResultsIndices) 695 return; 696 var highlightedMessage = WebInspector.console.messages[this._searchResultsIndices[this._currentSearchResultIndex]]; 697 if (highlightedMessage) 698 highlightedMessage.clearHighlight(); 699 this._currentSearchResultIndex = -1; 700 }, 701 702 _jumpToSearchResult: function(index, self) 703 { 704 this._clearCurrentSearchResultHighlight(); 705 this._currentSearchResultIndex = index; 706 WebInspector.searchController.updateCurrentMatchIndex(this._currentSearchResultIndex, this._searchProvider); 707 WebInspector.console.messages[this._searchResultsIndices[index]].highlightSearchResults(this._searchRegex); 708 }, 709 710 __proto__: WebInspector.View.prototype 711} 712 713/** 714 * @extends {WebInspector.Object} 715 * @constructor 716 */ 717WebInspector.ConsoleViewFilter = function() 718{ 719 this._messageURLFilters = WebInspector.settings.messageURLFilters.get(); 720 this._messageSourceFilters = WebInspector.settings.messageSourceFilters.get(); 721 this._messageLevelFilters = WebInspector.settings.messageLevelFilters.get(); 722 723 this._sourceToKeyMap = {}; 724 725 for (var key in WebInspector.ConsoleViewFilter._messageSourceGroups) { 726 if (!WebInspector.ConsoleViewFilter._messageSourceGroups[key].sources) { 727 console.assert(!this._otherKey); 728 this._otherKey = key; 729 continue; 730 } 731 732 for (var i = 0; i < WebInspector.ConsoleViewFilter._messageSourceGroups[key].sources.length; ++i) 733 this._sourceToKeyMap[WebInspector.ConsoleViewFilter._messageSourceGroups[key].sources[i]] = key; 734 } 735 736 this._filterChanged = this.dispatchEventToListeners.bind(this, WebInspector.ConsoleViewFilter.Events.FilterChanged); 737 738 WebInspector.settings.messageSourceFilters.addChangeListener(this._updateSourceFilterButton.bind(this)); 739 WebInspector.settings.messageLevelFilters.addChangeListener(this._updateLevelFilterBar.bind(this)); 740 741 this.sourceFilterButton = new WebInspector.StatusBarButton(WebInspector.UIString("Filter"), "console-filter", 2); 742 this.sourceFilterButton.element.addEventListener("mousedown", this._handleSourceFilterButtonClick.bind(this), false); 743 744 this._filterBarElements = []; 745 746 this.filterBarElement = document.createElement("div"); 747 this.filterBarElement.className = "scope-bar status-bar-item"; 748 749 this._createLevelFilterBarElement("all", WebInspector.UIString("All")); 750 751 var dividerElement = document.createElement("div"); 752 dividerElement.addStyleClass("scope-bar-divider"); 753 this.filterBarElement.appendChild(dividerElement); 754 755 this._createLevelFilterBarElement("error", WebInspector.UIString("Errors")); 756 this._createLevelFilterBarElement("warning", WebInspector.UIString("Warnings")); 757 this._createLevelFilterBarElement("log", WebInspector.UIString("Logs")); 758 this._createLevelFilterBarElement("debug", WebInspector.UIString("Debug")); 759 760 this._updateLevelFilterBar(); 761 this._updateSourceFilterButton(); 762}; 763 764WebInspector.ConsoleViewFilter.Events = { 765 FilterChanged: "FilterChanged" 766}; 767 768WebInspector.ConsoleViewFilter._messageSourceGroups = { 769 JS: { sources: [WebInspector.ConsoleMessage.MessageSource.JS], title: "JavaScript", styleClass: "filter-type-javascript"}, 770 Network: { sources: [WebInspector.ConsoleMessage.MessageSource.Network], title: "Network", styleClass: "filter-type-network"}, 771 Logging: { sources: [WebInspector.ConsoleMessage.MessageSource.ConsoleAPI], title: "Logging", styleClass: "filter-type-logging"}, 772 CSS: { sources: [WebInspector.ConsoleMessage.MessageSource.CSS], title: "CSS", styleClass: "filter-type-css"}, 773 Other: { title: "Other", styleClass: "filter-type-other"} 774}; 775 776WebInspector.ConsoleViewFilter.prototype = { 777 /** 778 * @param {string} url 779 */ 780 addMessageURLFilter: function(url) 781 { 782 this._messageURLFilters[url] = true; 783 WebInspector.settings.messageURLFilters.set(this._messageURLFilters); 784 this._filterChanged(); 785 }, 786 787 /** 788 * @param {string} url 789 */ 790 removeMessageURLFilter: function(url) 791 { 792 if (!url) 793 this._messageURLFilters = {}; 794 else 795 delete this._messageURLFilters[url]; 796 797 WebInspector.settings.messageURLFilters.set(this._messageURLFilters); 798 this._filterChanged(); 799 }, 800 801 /** 802 * @returns {Object} 803 */ 804 get messageURLFilters() 805 { 806 return this._messageURLFilters; 807 }, 808 809 /** 810 * @param {WebInspector.ConsoleMessage} message 811 * @return {boolean} 812 */ 813 shouldBeVisible: function(message) 814 { 815 if ((message.type === WebInspector.ConsoleMessage.MessageType.StartGroup || message.type === WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed || message.type === WebInspector.ConsoleMessage.MessageType.EndGroup)) 816 return true; 817 818 if (message.url && this._messageURLFilters[message.url]) 819 return false; 820 821 if (message.level && this._messageLevelFilters[message.level]) 822 return false; 823 824 if (this._filterRegex) { 825 this._filterRegex.lastIndex = 0; 826 if (!message.matchesRegex(this._filterRegex)) 827 return false; 828 } 829 830 // We store group keys, and we have resolved group by message source 831 if (message.source) { 832 if (this._sourceToKeyMap[message.source]) 833 return !this._messageSourceFilters[this._sourceToKeyMap[message.source]]; 834 else 835 return !this._messageSourceFilters[this._otherKey]; 836 } 837 838 839 return true; 840 }, 841 842 reset: function() 843 { 844 this._messageSourceFilters = {}; 845 WebInspector.settings.messageSourceFilters.set(this._messageSourceFilters); 846 this._messageURLFilters = {}; 847 WebInspector.settings.messageURLFilters.set(this._messageURLFilters); 848 this._messageLevelFilters = {}; 849 WebInspector.settings.messageLevelFilters.set(this._messageLevelFilters); 850 this._filterChanged(); 851 }, 852 853 /** 854 * @param {string} query 855 */ 856 performFilter: function(query) 857 { 858 if (!query) 859 delete this._filterRegex; 860 else 861 this._filterRegex = createPlainTextSearchRegex(query, "gi"); 862 863 this._filterChanged(); 864 }, 865 866 /** 867 * @param {string} sourceGroup 868 * @private 869 */ 870 _toggleMessageSourceFilter: function(sourceGroup) 871 { 872 if (!this._messageSourceFilters[sourceGroup]) 873 this._messageSourceFilters[sourceGroup] = true; 874 else 875 delete this._messageSourceFilters[sourceGroup]; 876 877 WebInspector.settings.messageSourceFilters.set(this._messageSourceFilters); 878 this._filterChanged(); 879 }, 880 881 /** 882 * @private 883 */ 884 _updateSourceFilterButton: function() 885 { 886 var hasActiveSourceFilter = false; 887 for (var sourceGroup in WebInspector.ConsoleViewFilter._messageSourceGroups) { 888 if (this._messageSourceFilters[sourceGroup]) { 889 hasActiveSourceFilter = true; 890 break; 891 } 892 } 893 894 this.sourceFilterButton.state = hasActiveSourceFilter; 895 }, 896 897 /** 898 * @param {Event} event 899 * @returns {WebInspector.ContextMenu} 900 * @private 901 */ 902 _createSourceFilterMenu: function(event) 903 { 904 var menu = new WebInspector.ContextMenu(event); 905 906 for (var sourceGroup in WebInspector.ConsoleViewFilter._messageSourceGroups) { 907 var filter = WebInspector.ConsoleViewFilter._messageSourceGroups[sourceGroup]; 908 909 menu.appendCheckboxItem(WebInspector.UIString(WebInspector.UIString(filter.title)), this._toggleMessageSourceFilter.bind(this, sourceGroup), !this._messageSourceFilters[sourceGroup]); 910 } 911 912 return menu; 913 }, 914 915 /** 916 * @param {string} level 917 * @param {string} label 918 * @private 919 */ 920 _createLevelFilterBarElement: function(level, label) 921 { 922 var categoryElement = document.createElement("li"); 923 categoryElement.category = level; 924 categoryElement.className = level; 925 categoryElement.textContent = label; 926 categoryElement.addEventListener("click", this._toggleLevelFilter.bind(this, level), false); 927 928 this._filterBarElements[level] = categoryElement; 929 this.filterBarElement.appendChild(categoryElement); 930 }, 931 932 /** 933 * @param {string} level 934 * @param {Event} event 935 * @private 936 */ 937 _toggleLevelFilter: function(level, event) 938 { 939 var isMac = WebInspector.isMac(); 940 var selectMultiple = false; 941 if (isMac && event.metaKey && !event.ctrlKey && !event.altKey && !event.shiftKey) 942 selectMultiple = true; 943 if (!isMac && event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey) 944 selectMultiple = true; 945 946 if (level === "all") 947 this._messageLevelFilters = {}; 948 else { 949 if (!selectMultiple) { 950 this._messageLevelFilters = {error: true, warning: true, log: true, debug: true}; 951 delete this._messageLevelFilters[level]; 952 } else { 953 if (this._messageLevelFilters[level]) 954 delete this._messageLevelFilters[level]; 955 else 956 this._messageLevelFilters[level] = true; 957 } 958 } 959 960 WebInspector.settings.messageLevelFilters.set(this._messageLevelFilters); 961 this._filterChanged(); 962 }, 963 964 /** 965 * @private 966 */ 967 _updateLevelFilterBar: function() 968 { 969 var all = !(this._messageLevelFilters["error"] || this._messageLevelFilters["warning"] || this._messageLevelFilters["log"] || this._messageLevelFilters["debug"]); 970 971 this._filterBarElements["all"].enableStyleClass("selected", all); 972 973 this._filterBarElements["error"].enableStyleClass("selected", !all && !this._messageLevelFilters["error"]); 974 this._filterBarElements["warning"].enableStyleClass("selected", !all && !this._messageLevelFilters["warning"]); 975 this._filterBarElements["log"].enableStyleClass("selected", !all && !this._messageLevelFilters["log"]); 976 this._filterBarElements["debug"].enableStyleClass("selected", !all && !this._messageLevelFilters["debug"]); 977 }, 978 979 /** 980 * @param {Event} event 981 * @private 982 */ 983 _handleSourceFilterButtonClick: function(event) 984 { 985 if (!event.button) 986 this._createSourceFilterMenu(event).showSoftMenu(); 987 }, 988 989 __proto__: WebInspector.Object.prototype 990}; 991 992 993/** 994 * @constructor 995 * @extends WebInspector.ConsoleMessage 996 */ 997WebInspector.ConsoleCommand = function(text) 998{ 999 this.text = text; 1000} 1001 1002WebInspector.ConsoleCommand.prototype = { 1003 wasShown: function() 1004 { 1005 }, 1006 1007 willHide: function() 1008 { 1009 }, 1010 1011 clearHighlight: function() 1012 { 1013 var highlightedMessage = this._formattedCommand; 1014 delete this._formattedCommand; 1015 this._formatCommand(); 1016 this._element.replaceChild(this._formattedCommand, highlightedMessage); 1017 }, 1018 1019 highlightSearchResults: function(regexObject) 1020 { 1021 regexObject.lastIndex = 0; 1022 var match = regexObject.exec(this.text); 1023 var matchRanges = []; 1024 while (match) { 1025 matchRanges.push({ offset: match.index, length: match[0].length }); 1026 match = regexObject.exec(this.text); 1027 } 1028 WebInspector.highlightSearchResults(this._formattedCommand, matchRanges); 1029 this._element.scrollIntoViewIfNeeded(); 1030 }, 1031 1032 matchesRegex: function(regexObject) 1033 { 1034 regexObject.lastIndex = 0; 1035 return regexObject.test(this.text); 1036 }, 1037 1038 toMessageElement: function() 1039 { 1040 if (!this._element) { 1041 this._element = document.createElement("div"); 1042 this._element.command = this; 1043 this._element.className = "console-user-command"; 1044 1045 this._formatCommand(); 1046 this._element.appendChild(this._formattedCommand); 1047 } 1048 return this._element; 1049 }, 1050 1051 _formatCommand: function() 1052 { 1053 this._formattedCommand = document.createElement("span"); 1054 this._formattedCommand.className = "console-message-text source-code"; 1055 this._formattedCommand.textContent = this.text; 1056 }, 1057 1058 __proto__: WebInspector.ConsoleMessage.prototype 1059} 1060 1061/** 1062 * @extends {WebInspector.ConsoleMessageImpl} 1063 * @constructor 1064 * @param {boolean} result 1065 * @param {boolean} wasThrown 1066 * @param {WebInspector.ConsoleCommand} originatingCommand 1067 * @param {WebInspector.Linkifier} linkifier 1068 */ 1069WebInspector.ConsoleCommandResult = function(result, wasThrown, originatingCommand, linkifier) 1070{ 1071 var level = (wasThrown ? WebInspector.ConsoleMessage.MessageLevel.Error : WebInspector.ConsoleMessage.MessageLevel.Log); 1072 this.originatingCommand = originatingCommand; 1073 WebInspector.ConsoleMessageImpl.call(this, WebInspector.ConsoleMessage.MessageSource.JS, level, "", linkifier, WebInspector.ConsoleMessage.MessageType.Result, undefined, undefined, undefined, undefined, [result]); 1074} 1075 1076WebInspector.ConsoleCommandResult.prototype = { 1077 /** 1078 * @override 1079 * @param {WebInspector.RemoteObject} array 1080 * @return {boolean} 1081 */ 1082 useArrayPreviewInFormatter: function(array) 1083 { 1084 return false; 1085 }, 1086 1087 toMessageElement: function() 1088 { 1089 var element = WebInspector.ConsoleMessageImpl.prototype.toMessageElement.call(this); 1090 element.addStyleClass("console-user-command-result"); 1091 return element; 1092 }, 1093 1094 __proto__: WebInspector.ConsoleMessageImpl.prototype 1095} 1096 1097/** 1098 * @constructor 1099 */ 1100WebInspector.ConsoleGroup = function(parentGroup) 1101{ 1102 this.parentGroup = parentGroup; 1103 1104 var element = document.createElement("div"); 1105 element.className = "console-group"; 1106 element.group = this; 1107 this.element = element; 1108 1109 if (parentGroup) { 1110 var bracketElement = document.createElement("div"); 1111 bracketElement.className = "console-group-bracket"; 1112 element.appendChild(bracketElement); 1113 } 1114 1115 var messagesElement = document.createElement("div"); 1116 messagesElement.className = "console-group-messages"; 1117 element.appendChild(messagesElement); 1118 this.messagesElement = messagesElement; 1119} 1120 1121WebInspector.ConsoleGroup.prototype = { 1122 /** 1123 * @param {WebInspector.ConsoleMessage} message 1124 * @param {Node=} node 1125 */ 1126 addMessage: function(message, node) 1127 { 1128 var element = message.toMessageElement(); 1129 1130 if (message.type === WebInspector.ConsoleMessage.MessageType.StartGroup || message.type === WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed) { 1131 this.messagesElement.parentNode.insertBefore(element, this.messagesElement); 1132 element.addEventListener("click", this._titleClicked.bind(this), false); 1133 var groupElement = element.enclosingNodeOrSelfWithClass("console-group"); 1134 if (groupElement && message.type === WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed) 1135 groupElement.addStyleClass("collapsed"); 1136 } else { 1137 this.messagesElement.insertBefore(element, node || null); 1138 message.wasShown(); 1139 } 1140 1141 if (element.previousSibling && message.originatingCommand && element.previousSibling.command === message.originatingCommand) 1142 element.previousSibling.addStyleClass("console-adjacent-user-command-result"); 1143 }, 1144 1145 _titleClicked: function(event) 1146 { 1147 var groupTitleElement = event.target.enclosingNodeOrSelfWithClass("console-group-title"); 1148 if (groupTitleElement) { 1149 var groupElement = groupTitleElement.enclosingNodeOrSelfWithClass("console-group"); 1150 if (groupElement) 1151 if (groupElement.hasStyleClass("collapsed")) 1152 groupElement.removeStyleClass("collapsed"); 1153 else 1154 groupElement.addStyleClass("collapsed"); 1155 groupTitleElement.scrollIntoViewIfNeeded(true); 1156 } 1157 1158 event.consume(true); 1159 } 1160} 1161 1162/** 1163 * @type {?WebInspector.ConsoleView} 1164 */ 1165WebInspector.consoleView = null; 1166 1167WebInspector.ConsoleMessage.create = function(source, level, message, type, url, line, column, repeatCount, parameters, stackTrace, requestId, isOutdated) 1168{ 1169 return new WebInspector.ConsoleMessageImpl(source, level, message, WebInspector.consoleView._linkifier, type, url, line, column, repeatCount, parameters, stackTrace, requestId, isOutdated); 1170} 1171