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 * @constructor 32 * @extends {WebInspector.VBox} 33 * @implements {WebInspector.Searchable} 34 * @implements {WebInspector.TargetManager.Observer} 35 * @implements {WebInspector.ViewportControl.Provider} 36 * @param {boolean} hideContextSelector 37 */ 38 WebInspector.ConsoleView = function(hideContextSelector) 39 { 40 WebInspector.VBox.call(this); 41 this.registerRequiredCSS("filter.css"); 42 43 this._searchableView = new WebInspector.SearchableView(this); 44 this._searchableView.setMinimalSearchQuerySize(0); 45 this._searchableView.show(this.element); 46 47 this._contentsElement = this._searchableView.element; 48 this._contentsElement.classList.add("console-view"); 49 this._visibleViewMessages = []; 50 this._urlToMessageCount = {}; 51 this._hiddenByFilterCount = 0; 52 53 this._clearConsoleButton = new WebInspector.StatusBarButton(WebInspector.UIString("Clear console log."), "clear-status-bar-item"); 54 this._clearConsoleButton.addEventListener("click", this._requestClearMessages, this); 55 56 this._executionContextSelector = new WebInspector.StatusBarComboBox(this._executionContextChanged.bind(this), "console-context"); 57 58 /** 59 * @type {!Map.<!WebInspector.ExecutionContext, !Element>} 60 */ 61 this._optionByExecutionContext = new Map(); 62 63 this._filter = new WebInspector.ConsoleViewFilter(this); 64 this._filter.addEventListener(WebInspector.ConsoleViewFilter.Events.FilterChanged, this._updateMessageList.bind(this)); 65 66 if (hideContextSelector) 67 this._executionContextSelector.element.classList.add("hidden"); 68 69 this._filterBar = new WebInspector.FilterBar(); 70 71 var statusBarElement = this._contentsElement.createChild("div", "console-status-bar"); 72 statusBarElement.appendChild(this._clearConsoleButton.element); 73 statusBarElement.appendChild(this._filterBar.filterButton().element); 74 statusBarElement.appendChild(this._executionContextSelector.element); 75 76 this._filtersContainer = this._contentsElement.createChild("div", "console-filters-header hidden"); 77 this._filtersContainer.appendChild(this._filterBar.filtersElement()); 78 this._filterBar.addEventListener(WebInspector.FilterBar.Events.FiltersToggled, this._onFiltersToggled, this); 79 this._filterBar.setName("consoleView"); 80 this._filter.addFilters(this._filterBar); 81 82 this._viewport = new WebInspector.ViewportControl(this); 83 this._viewport.setStickToBottom(true); 84 this._viewport.contentElement().classList.add("console-group"); 85 this._viewport.contentElement().classList.add("console-group-messages"); 86 this._contentsElement.appendChild(this._viewport.element); 87 this._messagesElement = this._viewport.element; 88 this._messagesElement.id = "console-messages"; 89 this._messagesElement.classList.add("monospace"); 90 this._messagesElement.addEventListener("click", this._messagesClicked.bind(this), true); 91 this._scrolledToBottom = true; 92 93 this._filterStatusMessageElement = document.createElementWithClass("div", "console-message"); 94 this._messagesElement.insertBefore(this._filterStatusMessageElement, this._messagesElement.firstChild); 95 this._filterStatusTextElement = this._filterStatusMessageElement.createChild("span", "console-info"); 96 this._filterStatusMessageElement.createTextChild(" "); 97 var resetFiltersLink = this._filterStatusMessageElement.createChild("span", "console-info node-link"); 98 resetFiltersLink.textContent = WebInspector.UIString("Show all messages."); 99 resetFiltersLink.addEventListener("click", this._filter.reset.bind(this._filter), true); 100 101 this._topGroup = WebInspector.ConsoleGroup.createTopGroup(); 102 this._currentGroup = this._topGroup; 103 104 this._promptElement = this._messagesElement.createChild("div", "source-code"); 105 this._promptElement.id = "console-prompt"; 106 this._promptElement.spellcheck = false; 107 this._messagesElement.appendChild(this._promptElement); 108 this._messagesElement.appendChild(document.createElement("br")); 109 110 this._showAllMessagesCheckbox = new WebInspector.StatusBarCheckbox(WebInspector.UIString("Show all messages")); 111 this._showAllMessagesCheckbox.inputElement.checked = true; 112 this._showAllMessagesCheckbox.inputElement.addEventListener("change", this._updateMessageList.bind(this), false); 113 114 if (!WebInspector.experimentsSettings.workersInMainWindow.isEnabled()) 115 this._showAllMessagesCheckbox.element.classList.add("hidden"); 116 117 statusBarElement.appendChild(this._showAllMessagesCheckbox.element); 118 119 this._registerShortcuts(); 120 this.registerRequiredCSS("suggestBox.css"); 121 122 this._messagesElement.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this), false); 123 WebInspector.settings.monitoringXHREnabled.addChangeListener(this._monitoringXHREnabledSettingChanged, this); 124 125 this._linkifier = new WebInspector.Linkifier(); 126 127 /** @type {!Array.<!WebInspector.ConsoleViewMessage>} */ 128 this._consoleMessages = []; 129 130 this._prompt = new WebInspector.TextPromptWithHistory(WebInspector.ExecutionContextSelector.completionsForTextPromptInCurrentContext); 131 this._prompt.setSuggestBoxEnabled(true); 132 this._prompt.renderAsBlock(); 133 this._prompt.attach(this._promptElement); 134 this._prompt.proxyElement.addEventListener("keydown", this._promptKeyDown.bind(this), false); 135 this._prompt.setHistoryData(WebInspector.settings.consoleHistory.get()); 136 var historyData = WebInspector.settings.consoleHistory.get(); 137 this._prompt.setHistoryData(historyData); 138 139 this._updateFilterStatus(); 140 WebInspector.settings.consoleTimestampsEnabled.addChangeListener(this._consoleTimestampsSettingChanged, this); 141 142 this._registerWithMessageSink(); 143 WebInspector.targetManager.observeTargets(this); 144 WebInspector.context.addFlavorChangeListener(WebInspector.ExecutionContext, this._executionContextChangedExternally, this); 145 } 146 147 WebInspector.ConsoleView.prototype = { 148 /** 149 * @return {number} 150 */ 151 itemCount: function() 152 { 153 return this._visibleViewMessages.length; 154 }, 155 156 /** 157 * @param {number} index 158 * @return {?WebInspector.ViewportElement} 159 */ 160 itemElement: function(index) 161 { 162 return this._visibleViewMessages[index]; 163 }, 164 165 /** 166 * @param {number} index 167 * @return {number} 168 */ 169 fastHeight: function(index) 170 { 171 return this._visibleViewMessages[index].fastHeight(); 172 }, 173 174 /** 175 * @return {number} 176 */ 177 minimumRowHeight: function() 178 { 179 return 16; 180 }, 181 182 /** 183 * @param {!WebInspector.Target} target 184 */ 185 targetAdded: function(target) 186 { 187 /** 188 * @param {!WebInspector.ConsoleMessage} message 189 * @this {WebInspector.ConsoleView} 190 */ 191 function appendMessage(message) 192 { 193 var viewMessage = this._createViewMessage(message); 194 this._consoleMessageAdded(viewMessage); 195 } 196 197 target.consoleModel.addEventListener(WebInspector.ConsoleModel.Events.MessageAdded, this._onConsoleMessageAdded, this); 198 target.consoleModel.addEventListener(WebInspector.ConsoleModel.Events.ConsoleCleared, this._consoleCleared, this); 199 target.consoleModel.addEventListener(WebInspector.ConsoleModel.Events.CommandEvaluated, this._commandEvaluated, this); 200 target.consoleModel.messages.forEach(appendMessage, this); 201 this._viewport.invalidate(); 202 203 target.runtimeModel.executionContexts().forEach(this._executionContextCreated, this); 204 target.runtimeModel.addEventListener(WebInspector.RuntimeModel.Events.ExecutionContextCreated, this._onExecutionContextCreated, this); 205 target.runtimeModel.addEventListener(WebInspector.RuntimeModel.Events.ExecutionContextDestroyed, this._onExecutionContextDestroyed, this); 206 }, 207 208 /** 209 * @param {!WebInspector.Target} target 210 */ 211 targetRemoved: function(target) 212 { 213 target.consoleModel.removeEventListener(WebInspector.ConsoleModel.Events.MessageAdded, this._onConsoleMessageAdded, this); 214 target.consoleModel.removeEventListener(WebInspector.ConsoleModel.Events.ConsoleCleared, this._consoleCleared, this); 215 target.consoleModel.removeEventListener(WebInspector.ConsoleModel.Events.CommandEvaluated, this._commandEvaluated, this); 216 target.runtimeModel.removeEventListener(WebInspector.RuntimeModel.Events.ExecutionContextCreated, this._onExecutionContextCreated, this); 217 target.runtimeModel.removeEventListener(WebInspector.RuntimeModel.Events.ExecutionContextDestroyed, this._onExecutionContextDestroyed, this); 218 }, 219 220 _registerWithMessageSink: function() 221 { 222 WebInspector.messageSink.messages().forEach(this._addSinkMessage, this); 223 WebInspector.messageSink.addEventListener(WebInspector.MessageSink.Events.MessageAdded, messageAdded, this); 224 225 /** 226 * @param {!WebInspector.Event} event 227 * @this {WebInspector.ConsoleView} 228 */ 229 function messageAdded(event) 230 { 231 this._addSinkMessage(/** @type {!WebInspector.MessageSink.Message} */ (event.data)); 232 } 233 }, 234 235 /** 236 * @param {!WebInspector.MessageSink.Message} message 237 */ 238 _addSinkMessage: function(message) 239 { 240 var level = WebInspector.ConsoleMessage.MessageLevel.Debug; 241 switch (message.level) { 242 case WebInspector.MessageSink.MessageLevel.Error: 243 level = WebInspector.ConsoleMessage.MessageLevel.Error; 244 break; 245 case WebInspector.MessageSink.MessageLevel.Warning: 246 level = WebInspector.ConsoleMessage.MessageLevel.Warning; 247 break; 248 } 249 250 var consoleMessage = new WebInspector.ConsoleMessage(null, WebInspector.ConsoleMessage.MessageSource.Other, level, message.text, 251 undefined, undefined, undefined, undefined, undefined, undefined, undefined, message.timestamp); 252 this._addConsoleMessage(consoleMessage); 253 }, 254 255 /** 256 * @param {!WebInspector.Event} event 257 */ 258 _consoleTimestampsSettingChanged: function(event) 259 { 260 var enabled = /** @type {boolean} */ (event.data); 261 this._updateMessageList(); 262 this._consoleMessages.forEach(function(viewMessage) { 263 viewMessage.updateTimestamp(enabled); 264 }); 265 }, 266 267 /** 268 * @return {!Element} 269 */ 270 defaultFocusedElement: function() 271 { 272 return this._promptElement 273 }, 274 275 _onFiltersToggled: function(event) 276 { 277 var toggled = /** @type {boolean} */ (event.data); 278 this._filtersContainer.classList.toggle("hidden", !toggled); 279 }, 280 281 /** 282 * @param {!WebInspector.ExecutionContext} executionContext 283 * @return {string} 284 */ 285 _titleFor: function(executionContext) 286 { 287 var result = executionContext.name; 288 if (executionContext.isMainWorldContext && executionContext.frameId) { 289 var frame = executionContext.target().resourceTreeModel.frameForId(executionContext.frameId); 290 result = frame ? frame.displayName() : result; 291 } 292 293 if (!executionContext.isMainWorldContext) 294 result = "\u00a0\u00a0\u00a0\u00a0" + result; 295 296 var maxLength = 50; 297 return result.trimMiddle(maxLength); 298 }, 299 300 /** 301 * @param {!WebInspector.Event} event 302 */ 303 _onExecutionContextCreated: function(event) 304 { 305 var executionContext = /** @type {!WebInspector.ExecutionContext} */ (event.data); 306 this._executionContextCreated(executionContext); 307 }, 308 309 /** 310 * @param {!WebInspector.ExecutionContext} executionContext 311 */ 312 _executionContextCreated: function(executionContext) 313 { 314 var newOption = document.createElement("option"); 315 newOption.__executionContext = executionContext; 316 newOption.text = this._titleFor(executionContext); 317 this._optionByExecutionContext.put(executionContext, newOption); 318 var sameGroupExists = false; 319 var options = this._executionContextSelector.selectElement().options; 320 var insertBeforeOption = null; 321 for (var i = 0; i < options.length; ++i) { 322 var optionContext = options[i].__executionContext; 323 var isSameGroup = executionContext.target() === optionContext.target() && executionContext.frameId === optionContext.frameId; 324 sameGroupExists |= isSameGroup; 325 if ((isSameGroup && WebInspector.ExecutionContext.comparator(optionContext, executionContext) > 0) || (sameGroupExists && !isSameGroup)) { 326 insertBeforeOption = options[i]; 327 break; 328 } 329 } 330 this._executionContextSelector.selectElement().insertBefore(newOption, insertBeforeOption); 331 if (executionContext === WebInspector.context.flavor(WebInspector.ExecutionContext)) 332 this._executionContextSelector.select(newOption); 333 }, 334 335 /** 336 * @param {!WebInspector.Event} event 337 */ 338 _onExecutionContextDestroyed: function(event) 339 { 340 var executionContext = /** @type {!WebInspector.ExecutionContext} */ (event.data); 341 var option = this._optionByExecutionContext.remove(executionContext); 342 option.remove(); 343 }, 344 345 _executionContextChanged: function() 346 { 347 var newContext = this._currentExecutionContext(); 348 WebInspector.context.setFlavor(WebInspector.ExecutionContext, newContext); 349 this._prompt.clearAutoComplete(true); 350 if (!this._showAllMessagesCheckbox.checked()) 351 this._updateMessageList(); 352 }, 353 354 /** 355 * @param {!WebInspector.Event} event 356 */ 357 _executionContextChangedExternally: function(event) 358 { 359 var executionContext = /** @type {?WebInspector.ExecutionContext} */ (event.data); 360 if (!executionContext) 361 return; 362 363 var options = this._executionContextSelector.selectElement().options; 364 for (var i = 0; i < options.length; ++i) { 365 if (options[i].__executionContext === executionContext) 366 this._executionContextSelector.select(options[i]); 367 } 368 }, 369 370 /** 371 * @return {?WebInspector.ExecutionContext} 372 */ 373 _currentExecutionContext: function() 374 { 375 var option = this._executionContextSelector.selectedOption(); 376 return option ? option.__executionContext : null; 377 }, 378 379 willHide: function() 380 { 381 this._prompt.hideSuggestBox(); 382 this._prompt.clearAutoComplete(true); 383 }, 384 385 wasShown: function() 386 { 387 this._viewport.refresh(); 388 if (!this._prompt.isCaretInsidePrompt()) 389 this._prompt.moveCaretToEndOfPrompt(); 390 }, 391 392 focus: function() 393 { 394 if (this._promptElement === WebInspector.currentFocusElement()) 395 return; 396 WebInspector.setCurrentFocusElement(this._promptElement); 397 this._prompt.moveCaretToEndOfPrompt(); 398 }, 399 400 storeScrollPositions: function() 401 { 402 WebInspector.View.prototype.storeScrollPositions.call(this); 403 this._scrolledToBottom = this._messagesElement.isScrolledToBottom(); 404 }, 405 406 restoreScrollPositions: function() 407 { 408 if (this._scrolledToBottom) 409 this._immediatelyScrollIntoView(); 410 else 411 WebInspector.View.prototype.restoreScrollPositions.call(this); 412 }, 413 414 onResize: function() 415 { 416 this._scheduleViewportRefresh(); 417 this._prompt.hideSuggestBox(); 418 this.restoreScrollPositions(); 419 }, 420 421 _isScrollIntoViewScheduled: function() 422 { 423 return !!this._scrollIntoViewTimer; 424 }, 425 426 _scheduleViewportRefresh: function() 427 { 428 if (this._scrollIntoViewTimer) 429 return; 430 /** 431 * @this {WebInspector.ConsoleView} 432 */ 433 function scrollIntoView() 434 { 435 delete this._scrollIntoViewTimer; 436 this._viewport.invalidate(); 437 } 438 this._scrollIntoViewTimer = setTimeout(scrollIntoView.bind(this), 50); 439 }, 440 441 _immediatelyScrollIntoView: function() 442 { 443 this._promptElement.scrollIntoView(true); 444 this._cancelScheduledScrollIntoView(); 445 }, 446 447 _cancelScheduledScrollIntoView: function() 448 { 449 if (!this._isScrollIntoViewScheduled()) 450 return; 451 clearTimeout(this._scrollIntoViewTimer); 452 this._viewport.refresh(); 453 delete this._scrollIntoViewTimer; 454 }, 455 456 _updateFilterStatus: function() 457 { 458 this._filterStatusTextElement.textContent = WebInspector.UIString(this._hiddenByFilterCount === 1 ? "%d message is hidden by filters." : "%d messages are hidden by filters.", this._hiddenByFilterCount); 459 this._filterStatusMessageElement.style.display = this._hiddenByFilterCount ? "" : "none"; 460 }, 461 462 /** 463 * @param {!WebInspector.ConsoleViewMessage} viewMessage 464 */ 465 _consoleMessageAdded: function(viewMessage) 466 { 467 /** 468 * @param {!WebInspector.ConsoleViewMessage} viewMessage1 469 * @param {!WebInspector.ConsoleViewMessage} viewMessage2 470 * @return {number} 471 */ 472 function compareTimestamps(viewMessage1, viewMessage2) 473 { 474 return WebInspector.ConsoleMessage.timestampComparator(viewMessage1.consoleMessage(), viewMessage2.consoleMessage()); 475 } 476 var insertAt = insertionIndexForObjectInListSortedByFunction(viewMessage, this._consoleMessages, compareTimestamps, true); 477 this._consoleMessages.splice(insertAt, 0, viewMessage); 478 479 var message = viewMessage.consoleMessage(); 480 if (this._urlToMessageCount[message.url]) 481 this._urlToMessageCount[message.url]++; 482 else 483 this._urlToMessageCount[message.url] = 1; 484 485 if (this._tryToCollapseMessages(viewMessage, this._visibleViewMessages.peekLast())) 486 return; 487 488 if (this._filter.shouldBeVisible(viewMessage)) 489 this._showConsoleMessage(viewMessage) 490 else { 491 this._hiddenByFilterCount++; 492 this._updateFilterStatus(); 493 } 494 }, 495 496 /** 497 * @param {!WebInspector.Event} event 498 */ 499 _onConsoleMessageAdded: function(event) 500 { 501 var message = /** @type {!WebInspector.ConsoleMessage} */ (event.data); 502 this._addConsoleMessage(message); 503 }, 504 505 /** 506 * @param {!WebInspector.ConsoleMessage} message 507 */ 508 _addConsoleMessage: function(message) 509 { 510 var viewMessage = this._createViewMessage(message); 511 this._consoleMessageAdded(viewMessage); 512 this._scheduleViewportRefresh(); 513 }, 514 515 /** 516 * @param {!WebInspector.ConsoleViewMessage} viewMessage 517 */ 518 _showConsoleMessage: function(viewMessage) 519 { 520 var lastMessage = this._visibleViewMessages.peekLast(); 521 if (viewMessage.consoleMessage().type === WebInspector.ConsoleMessage.MessageType.EndGroup) { 522 if (lastMessage && !this._currentGroup.messagesHidden()) 523 lastMessage.incrementCloseGroupDecorationCount(); 524 this._currentGroup = this._currentGroup.parentGroup(); 525 return; 526 } 527 if (!this._currentGroup.messagesHidden()) { 528 var originatingMessage = viewMessage.consoleMessage().originatingMessage(); 529 if (lastMessage && originatingMessage && lastMessage.consoleMessage() === originatingMessage) 530 lastMessage.toMessageElement().classList.add("console-adjacent-user-command-result"); 531 532 this._visibleViewMessages.push(viewMessage); 533 534 if (this._searchRegex && viewMessage.matchesRegex(this._searchRegex)) { 535 this._searchResults.push(viewMessage); 536 this._searchableView.updateSearchMatchesCount(this._searchResults.length); 537 } 538 } 539 540 if (viewMessage.consoleMessage().isGroupStartMessage()) 541 this._currentGroup = new WebInspector.ConsoleGroup(this._currentGroup, viewMessage); 542 }, 543 544 /** 545 * @param {!WebInspector.ConsoleMessage} message 546 * @return {!WebInspector.ConsoleViewMessage} 547 */ 548 _createViewMessage: function(message) 549 { 550 var nestingLevel = this._currentGroup.nestingLevel(); 551 switch (message.type) { 552 case WebInspector.ConsoleMessage.MessageType.Command: 553 return new WebInspector.ConsoleCommand(message, nestingLevel); 554 case WebInspector.ConsoleMessage.MessageType.Result: 555 return new WebInspector.ConsoleCommandResult(message, this._linkifier, nestingLevel); 556 case WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed: 557 case WebInspector.ConsoleMessage.MessageType.StartGroup: 558 return new WebInspector.ConsoleGroupViewMessage(message, this._linkifier, nestingLevel); 559 default: 560 return new WebInspector.ConsoleViewMessage(message, this._linkifier, nestingLevel); 561 } 562 }, 563 564 _consoleCleared: function() 565 { 566 this._clearCurrentSearchResultHighlight(); 567 this._consoleMessages = []; 568 this._scrolledToBottom = true; 569 this._updateMessageList(); 570 571 if (this._searchRegex) 572 this._searchableView.updateSearchMatchesCount(0); 573 574 this._linkifier.reset(); 575 }, 576 577 _handleContextMenuEvent: function(event) 578 { 579 if (event.target.enclosingNodeOrSelfWithNodeName("a")) 580 return; 581 582 var contextMenu = new WebInspector.ContextMenu(event); 583 584 function monitoringXHRItemAction() 585 { 586 WebInspector.settings.monitoringXHREnabled.set(!WebInspector.settings.monitoringXHREnabled.get()); 587 } 588 contextMenu.appendCheckboxItem(WebInspector.UIString("Log XMLHttpRequests"), monitoringXHRItemAction, WebInspector.settings.monitoringXHREnabled.get()); 589 590 function preserveLogItemAction() 591 { 592 WebInspector.settings.preserveConsoleLog.set(!WebInspector.settings.preserveConsoleLog.get()); 593 } 594 contextMenu.appendCheckboxItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Preserve log upon navigation" : "Preserve Log upon Navigation"), preserveLogItemAction, WebInspector.settings.preserveConsoleLog.get()); 595 596 var sourceElement = event.target.enclosingNodeOrSelfWithClass("console-message-wrapper"); 597 var consoleMessage = sourceElement ? sourceElement.message.consoleMessage() : null; 598 599 var filterSubMenu = contextMenu.appendSubMenuItem(WebInspector.UIString("Filter")); 600 601 if (consoleMessage && consoleMessage.url) { 602 var menuTitle = WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Hide messages from %s" : "Hide Messages from %s", new WebInspector.ParsedURL(consoleMessage.url).displayName); 603 filterSubMenu.appendItem(menuTitle, this._filter.addMessageURLFilter.bind(this._filter, consoleMessage.url)); 604 } 605 606 filterSubMenu.appendSeparator(); 607 var unhideAll = filterSubMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Unhide all" : "Unhide All"), this._filter.removeMessageURLFilter.bind(this._filter)); 608 filterSubMenu.appendSeparator(); 609 610 var hasFilters = false; 611 612 for (var url in this._filter.messageURLFilters) { 613 filterSubMenu.appendCheckboxItem(String.sprintf("%s (%d)", new WebInspector.ParsedURL(url).displayName, this._urlToMessageCount[url]), this._filter.removeMessageURLFilter.bind(this._filter, url), true); 614 hasFilters = true; 615 } 616 617 filterSubMenu.setEnabled(hasFilters || (consoleMessage && consoleMessage.url)); 618 unhideAll.setEnabled(hasFilters); 619 620 contextMenu.appendSeparator(); 621 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Clear console" : "Clear Console"), this._requestClearMessages.bind(this)); 622 623 var request = consoleMessage ? consoleMessage.request : null; 624 if (request && request.type === WebInspector.resourceTypes.XHR) { 625 contextMenu.appendSeparator(); 626 contextMenu.appendItem(WebInspector.UIString("Replay XHR"), NetworkAgent.replayXHR.bind(null, request.requestId)); 627 } 628 629 contextMenu.show(); 630 }, 631 632 /** 633 * @param {!WebInspector.ConsoleViewMessage} lastMessage 634 * @param {?WebInspector.ConsoleViewMessage} viewMessage 635 * @return {boolean} 636 */ 637 _tryToCollapseMessages: function(lastMessage, viewMessage) 638 { 639 if (!WebInspector.settings.consoleTimestampsEnabled.get() && viewMessage && !lastMessage.consoleMessage().isGroupMessage() && lastMessage.consoleMessage().isEqual(viewMessage.consoleMessage())) { 640 viewMessage.incrementRepeatCount(); 641 return true; 642 } 643 644 return false; 645 }, 646 647 _updateMessageList: function() 648 { 649 this._topGroup = WebInspector.ConsoleGroup.createTopGroup(); 650 this._currentGroup = this._topGroup; 651 this._searchResults = []; 652 this._hiddenByFilterCount = 0; 653 for (var i = 0; i < this._visibleViewMessages.length; ++i) { 654 this._visibleViewMessages[i].resetCloseGroupDecorationCount(); 655 this._visibleViewMessages[i].resetIncrementRepeatCount(); 656 } 657 this._visibleViewMessages = []; 658 for (var i = 0; i < this._consoleMessages.length; ++i) { 659 var viewMessage = this._consoleMessages[i]; 660 if (this._tryToCollapseMessages(viewMessage, this._visibleViewMessages.peekLast())) 661 continue; 662 if (this._filter.shouldBeVisible(viewMessage)) 663 this._showConsoleMessage(viewMessage); 664 else 665 this._hiddenByFilterCount++; 666 } 667 this._updateFilterStatus(); 668 this._viewport.invalidate(); 669 }, 670 671 /** 672 * @param {!WebInspector.Event} event 673 */ 674 _monitoringXHREnabledSettingChanged: function(event) 675 { 676 var enabled = /** @type {boolean} */ (event.data); 677 WebInspector.targetManager.targets().forEach(function(target) {target.consoleAgent().setMonitoringXHREnabled(enabled);}); 678 }, 679 680 /** 681 * @param {?Event} event 682 */ 683 _messagesClicked: function(event) 684 { 685 if (!this._prompt.isCaretInsidePrompt() && window.getSelection().isCollapsed) 686 this._prompt.moveCaretToEndOfPrompt(); 687 var groupMessage = event.target.enclosingNodeOrSelfWithClass("console-group-title"); 688 if (!groupMessage) 689 return; 690 var consoleGroupViewMessage = groupMessage.parentElement.message; 691 consoleGroupViewMessage.setCollapsed(!consoleGroupViewMessage.collapsed()); 692 this._updateMessageList(); 693 }, 694 695 _registerShortcuts: function() 696 { 697 this._shortcuts = {}; 698 699 var shortcut = WebInspector.KeyboardShortcut; 700 var section = WebInspector.shortcutsScreen.section(WebInspector.UIString("Console")); 701 702 var shortcutL = shortcut.makeDescriptor("l", WebInspector.KeyboardShortcut.Modifiers.Ctrl); 703 this._shortcuts[shortcutL.key] = this._requestClearMessages.bind(this); 704 var keys = [shortcutL]; 705 if (WebInspector.isMac()) { 706 var shortcutK = shortcut.makeDescriptor("k", WebInspector.KeyboardShortcut.Modifiers.Meta); 707 this._shortcuts[shortcutK.key] = this._requestClearMessages.bind(this); 708 keys.unshift(shortcutK); 709 } 710 section.addAlternateKeys(keys, WebInspector.UIString("Clear console")); 711 712 section.addKey(shortcut.makeDescriptor(shortcut.Keys.Tab), WebInspector.UIString("Autocomplete common prefix")); 713 section.addKey(shortcut.makeDescriptor(shortcut.Keys.Right), WebInspector.UIString("Accept suggestion")); 714 715 var shortcutU = shortcut.makeDescriptor("u", WebInspector.KeyboardShortcut.Modifiers.Ctrl); 716 this._shortcuts[shortcutU.key] = this._clearPromptBackwards.bind(this); 717 section.addAlternateKeys([shortcutU], WebInspector.UIString("Clear console prompt")); 718 719 keys = [ 720 shortcut.makeDescriptor(shortcut.Keys.Down), 721 shortcut.makeDescriptor(shortcut.Keys.Up) 722 ]; 723 section.addRelatedKeys(keys, WebInspector.UIString("Next/previous line")); 724 725 if (WebInspector.isMac()) { 726 keys = [ 727 shortcut.makeDescriptor("N", shortcut.Modifiers.Alt), 728 shortcut.makeDescriptor("P", shortcut.Modifiers.Alt) 729 ]; 730 section.addRelatedKeys(keys, WebInspector.UIString("Next/previous command")); 731 } 732 733 section.addKey(shortcut.makeDescriptor(shortcut.Keys.Enter), WebInspector.UIString("Execute command")); 734 }, 735 736 _clearPromptBackwards: function() 737 { 738 this._prompt.text = ""; 739 }, 740 741 _requestClearMessages: function() 742 { 743 WebInspector.console.requestClearMessages(); 744 }, 745 746 _promptKeyDown: function(event) 747 { 748 if (isEnterKey(event)) { 749 this._enterKeyPressed(event); 750 return; 751 } 752 753 var shortcut = WebInspector.KeyboardShortcut.makeKeyFromEvent(event); 754 var handler = this._shortcuts[shortcut]; 755 if (handler) { 756 handler(); 757 event.preventDefault(); 758 } 759 }, 760 761 _enterKeyPressed: function(event) 762 { 763 if (event.altKey || event.ctrlKey || event.shiftKey) 764 return; 765 766 event.consume(true); 767 768 this._prompt.clearAutoComplete(true); 769 770 var str = this._prompt.text; 771 if (!str.length) 772 return; 773 this._appendCommand(str, true); 774 }, 775 776 /** 777 * @param {?WebInspector.RemoteObject} result 778 * @param {boolean} wasThrown 779 * @param {!WebInspector.ConsoleMessage} originatingConsoleMessage 780 */ 781 _printResult: function(result, wasThrown, originatingConsoleMessage) 782 { 783 if (!result) 784 return; 785 786 var target = result.target(); 787 /** 788 * @param {string=} url 789 * @param {number=} lineNumber 790 * @param {number=} columnNumber 791 */ 792 function addMessage(url, lineNumber, columnNumber) 793 { 794 var level = wasThrown ? WebInspector.ConsoleMessage.MessageLevel.Error : WebInspector.ConsoleMessage.MessageLevel.Log; 795 var message = new WebInspector.ConsoleMessage(target, WebInspector.ConsoleMessage.MessageSource.JS, level, "", WebInspector.ConsoleMessage.MessageType.Result, url, lineNumber, columnNumber, undefined, [result]); 796 message.setOriginatingMessage(originatingConsoleMessage); 797 target.consoleModel.addMessage(message); 798 } 799 800 if (result.type !== "function") { 801 addMessage(); 802 return; 803 } 804 805 result.functionDetails(didGetDetails); 806 807 /** 808 * @param {?DebuggerAgent.FunctionDetails} response 809 */ 810 function didGetDetails(response) 811 { 812 if (!response) { 813 addMessage(); 814 return; 815 } 816 817 var url; 818 var lineNumber; 819 var columnNumber; 820 var script = target.debuggerModel.scriptForId(response.location.scriptId); 821 if (script && script.sourceURL) { 822 url = script.sourceURL; 823 lineNumber = response.location.lineNumber + 1; 824 columnNumber = response.location.columnNumber + 1; 825 } 826 // FIXME: this should be using live location. 827 addMessage(url, lineNumber, columnNumber); 828 } 829 }, 830 831 /** 832 * @param {string} text 833 * @param {boolean} useCommandLineAPI 834 */ 835 _appendCommand: function(text, useCommandLineAPI) 836 { 837 838 this._prompt.text = ""; 839 var currentExecutionContext = WebInspector.context.flavor(WebInspector.ExecutionContext); 840 if (currentExecutionContext) 841 WebInspector.ConsoleModel.evaluateCommandInConsole(currentExecutionContext, text, useCommandLineAPI); 842 }, 843 844 /** 845 * @param {!WebInspector.Event} event 846 */ 847 _commandEvaluated: function(event) 848 { 849 var data = /**{{result: ?WebInspector.RemoteObject, wasThrown: boolean, text: string, commandMessage: !WebInspector.ConsoleMessage}} */ (event.data); 850 this._prompt.pushHistoryItem(data.text); 851 WebInspector.settings.consoleHistory.set(this._prompt.historyData.slice(-30)); 852 this._printResult(data.result, data.wasThrown, data.commandMessage); 853 }, 854 855 /** 856 * @return {!Array.<!Element>} 857 */ 858 elementsToRestoreScrollPositionsFor: function() 859 { 860 return [this._messagesElement]; 861 }, 862 863 searchCanceled: function() 864 { 865 this._clearCurrentSearchResultHighlight(); 866 delete this._searchResults; 867 delete this._searchRegex; 868 this._viewport.refresh(); 869 }, 870 871 /** 872 * @param {string} query 873 * @param {boolean} shouldJump 874 * @param {boolean=} jumpBackwards 875 */ 876 performSearch: function(query, shouldJump, jumpBackwards) 877 { 878 this.searchCanceled(); 879 this._searchableView.updateSearchMatchesCount(0); 880 this._searchRegex = createPlainTextSearchRegex(query, "gi"); 881 882 /** @type {!Array.<number>} */ 883 this._searchResults = []; 884 for (var i = 0; i < this._visibleViewMessages.length; i++) { 885 if (this._visibleViewMessages[i].matchesRegex(this._searchRegex)) 886 this._searchResults.push(i); 887 } 888 this._searchableView.updateSearchMatchesCount(this._searchResults.length); 889 this._currentSearchResultIndex = -1; 890 if (shouldJump && this._searchResults.length) 891 this._jumpToSearchResult(jumpBackwards ? -1 : 0); 892 this._viewport.refresh(); 893 }, 894 895 jumpToNextSearchResult: function() 896 { 897 if (!this._searchResults || !this._searchResults.length) 898 return; 899 this._jumpToSearchResult(this._currentSearchResultIndex + 1); 900 }, 901 902 jumpToPreviousSearchResult: function() 903 { 904 if (!this._searchResults || !this._searchResults.length) 905 return; 906 this._jumpToSearchResult(this._currentSearchResultIndex - 1); 907 }, 908 909 _clearCurrentSearchResultHighlight: function() 910 { 911 if (!this._searchResults) 912 return; 913 914 var highlightedViewMessage = this._visibleViewMessages[this._searchResults[this._currentSearchResultIndex]]; 915 if (highlightedViewMessage) 916 highlightedViewMessage.clearHighlight(); 917 this._currentSearchResultIndex = -1; 918 }, 919 920 _jumpToSearchResult: function(index) 921 { 922 index = mod(index, this._searchResults.length); 923 this._clearCurrentSearchResultHighlight(); 924 this._currentSearchResultIndex = index; 925 this._searchableView.updateCurrentMatchIndex(this._currentSearchResultIndex); 926 var currentViewMessageIndex = this._searchResults[index]; 927 this._viewport.scrollItemIntoView(currentViewMessageIndex); 928 this._visibleViewMessages[currentViewMessageIndex].highlightSearchResults(this._searchRegex); 929 }, 930 931 __proto__: WebInspector.VBox.prototype 932 } 933 934 /** 935 * @constructor 936 * @extends {WebInspector.Object} 937 * @param {!WebInspector.ConsoleView} view 938 */ 939 WebInspector.ConsoleViewFilter = function(view) 940 { 941 this._view = view; 942 this._messageURLFilters = WebInspector.settings.messageURLFilters.get(); 943 this._filterChanged = this.dispatchEventToListeners.bind(this, WebInspector.ConsoleViewFilter.Events.FilterChanged); 944 }; 945 946 WebInspector.ConsoleViewFilter.Events = { 947 FilterChanged: "FilterChanged" 948 }; 949 950 WebInspector.ConsoleViewFilter.prototype = { 951 addFilters: function(filterBar) 952 { 953 this._textFilterUI = new WebInspector.TextFilterUI(true); 954 this._textFilterUI.addEventListener(WebInspector.FilterUI.Events.FilterChanged, this._textFilterChanged, this); 955 filterBar.addFilter(this._textFilterUI); 956 957 var levels = [ 958 {name: "error", label: WebInspector.UIString("Errors")}, 959 {name: "warning", label: WebInspector.UIString("Warnings")}, 960 {name: "info", label: WebInspector.UIString("Info")}, 961 {name: "log", label: WebInspector.UIString("Logs")}, 962 {name: "debug", label: WebInspector.UIString("Debug")} 963 ]; 964 this._levelFilterUI = new WebInspector.NamedBitSetFilterUI(levels, WebInspector.settings.messageLevelFilters); 965 this._levelFilterUI.addEventListener(WebInspector.FilterUI.Events.FilterChanged, this._filterChanged, this); 966 filterBar.addFilter(this._levelFilterUI); 967 }, 968 969 _textFilterChanged: function(event) 970 { 971 this._filterRegex = this._textFilterUI.regex(); 972 973 this._filterChanged(); 974 }, 975 976 /** 977 * @param {string} url 978 */ 979 addMessageURLFilter: function(url) 980 { 981 this._messageURLFilters[url] = true; 982 WebInspector.settings.messageURLFilters.set(this._messageURLFilters); 983 this._filterChanged(); 984 }, 985 986 /** 987 * @param {string} url 988 */ 989 removeMessageURLFilter: function(url) 990 { 991 if (!url) 992 this._messageURLFilters = {}; 993 else 994 delete this._messageURLFilters[url]; 995 996 WebInspector.settings.messageURLFilters.set(this._messageURLFilters); 997 this._filterChanged(); 998 }, 999 1000 /** 1001 * @returns {!Object} 1002 */ 1003 get messageURLFilters() 1004 { 1005 return this._messageURLFilters; 1006 }, 1007 1008 /** 1009 * @param {!WebInspector.ConsoleViewMessage} viewMessage 1010 * @return {boolean} 1011 */ 1012 shouldBeVisible: function(viewMessage) 1013 { 1014 var message = viewMessage.consoleMessage(); 1015 var executionContext = WebInspector.context.flavor(WebInspector.ExecutionContext); 1016 if (!message.target()) 1017 return true; 1018 1019 if (!this._view._showAllMessagesCheckbox.checked() && executionContext && (message.target() !== executionContext.target() || message.executionContextId !== executionContext.id)) 1020 return false; 1021 1022 if (viewMessage.consoleMessage().isGroupMessage()) 1023 return true; 1024 1025 if (message.type === WebInspector.ConsoleMessage.MessageType.Result || message.type === WebInspector.ConsoleMessage.MessageType.Command) 1026 return true; 1027 1028 if (message.url && this._messageURLFilters[message.url]) 1029 return false; 1030 1031 if (message.level && !this._levelFilterUI.accept(message.level)) 1032 return false; 1033 1034 if (this._filterRegex) { 1035 this._filterRegex.lastIndex = 0; 1036 if (!viewMessage.matchesRegex(this._filterRegex)) 1037 return false; 1038 } 1039 1040 return true; 1041 }, 1042 1043 reset: function() 1044 { 1045 this._messageURLFilters = {}; 1046 WebInspector.settings.messageURLFilters.set(this._messageURLFilters); 1047 WebInspector.settings.messageLevelFilters.set({}); 1048 this._view._showAllMessagesCheckbox.inputElement.checked = true; 1049 this._textFilterUI.setValue(""); 1050 this._filterChanged(); 1051 }, 1052 1053 __proto__: WebInspector.Object.prototype 1054 }; 1055 1056 1057 /** 1058 * @constructor 1059 * @extends {WebInspector.ConsoleViewMessage} 1060 * @param {!WebInspector.ConsoleMessage} message 1061 * @param {number} nestingLevel 1062 */ 1063 WebInspector.ConsoleCommand = function(message, nestingLevel) 1064 { 1065 WebInspector.ConsoleViewMessage.call(this, message, null, nestingLevel); 1066 } 1067 1068 WebInspector.ConsoleCommand.prototype = { 1069 clearHighlight: function() 1070 { 1071 var highlightedMessage = this._formattedCommand; 1072 delete this._formattedCommand; 1073 this._formatCommand(); 1074 this._element.replaceChild(this._formattedCommand, highlightedMessage); 1075 }, 1076 1077 /** 1078 * @param {!RegExp} regexObject 1079 */ 1080 highlightSearchResults: function(regexObject) 1081 { 1082 regexObject.lastIndex = 0; 1083 var match = regexObject.exec(this.text); 1084 var matchRanges = []; 1085 while (match) { 1086 matchRanges.push(new WebInspector.SourceRange(match.index, match[0].length)); 1087 match = regexObject.exec(this.text); 1088 } 1089 WebInspector.highlightSearchResults(this._formattedCommand, matchRanges); 1090 this._element.scrollIntoViewIfNeeded(); 1091 }, 1092 1093 /** 1094 * @param {!RegExp} regexObject 1095 * @return {boolean} 1096 */ 1097 matchesRegex: function(regexObject) 1098 { 1099 regexObject.lastIndex = 0; 1100 return regexObject.test(this.text); 1101 }, 1102 1103 /** 1104 * @return {!Element} 1105 */ 1106 contentElement: function() 1107 { 1108 if (!this._element) { 1109 this._element = document.createElement("div"); 1110 this._element.message = this; 1111 this._element.className = "console-user-command"; 1112 1113 this._formatCommand(); 1114 this._element.appendChild(this._formattedCommand); 1115 } 1116 return this._element; 1117 }, 1118 1119 _formatCommand: function() 1120 { 1121 this._formattedCommand = document.createElement("span"); 1122 this._formattedCommand.className = "console-message-text source-code"; 1123 this._formattedCommand.textContent = this.text; 1124 }, 1125 1126 __proto__: WebInspector.ConsoleViewMessage.prototype 1127 } 1128 1129 /** 1130 * @constructor 1131 * @extends {WebInspector.ConsoleViewMessage} 1132 * @param {!WebInspector.ConsoleMessage} message 1133 * @param {!WebInspector.Linkifier} linkifier 1134 * @param {number} nestingLevel 1135 */ 1136 WebInspector.ConsoleCommandResult = function(message, linkifier, nestingLevel) 1137 { 1138 WebInspector.ConsoleViewMessage.call(this, message, linkifier, nestingLevel); 1139 } 1140 1141 WebInspector.ConsoleCommandResult.prototype = { 1142 /** 1143 * @override 1144 * @param {!WebInspector.RemoteObject} array 1145 * @return {boolean} 1146 */ 1147 useArrayPreviewInFormatter: function(array) 1148 { 1149 return false; 1150 }, 1151 1152 /** 1153 * @return {!Element} 1154 */ 1155 contentElement: function() 1156 { 1157 var element = WebInspector.ConsoleViewMessage.prototype.contentElement.call(this); 1158 element.classList.add("console-user-command-result"); 1159 return element; 1160 }, 1161 1162 __proto__: WebInspector.ConsoleViewMessage.prototype 1163 } 1164 1165 /** 1166 * @constructor 1167 * @param {?WebInspector.ConsoleGroup} parentGroup 1168 * @param {?WebInspector.ConsoleViewMessage} groupMessage 1169 */ 1170 WebInspector.ConsoleGroup = function(parentGroup, groupMessage) 1171 { 1172 this._parentGroup = parentGroup; 1173 this._nestingLevel = parentGroup ? parentGroup.nestingLevel() + 1 : 0; 1174 this._messagesHidden = groupMessage && groupMessage.collapsed() || this._parentGroup && this._parentGroup.messagesHidden(); 1175 } 1176 1177 /** 1178 * @return {!WebInspector.ConsoleGroup} 1179 */ 1180 WebInspector.ConsoleGroup.createTopGroup = function() 1181 { 1182 return new WebInspector.ConsoleGroup(null, null); 1183 } 1184 1185 WebInspector.ConsoleGroup.prototype = { 1186 /** 1187 * @return {boolean} 1188 */ 1189 messagesHidden: function() 1190 { 1191 return this._messagesHidden; 1192 }, 1193 1194 /** 1195 * @return {number} 1196 */ 1197 nestingLevel: function() 1198 { 1199 return this._nestingLevel; 1200 }, 1201 1202 /** 1203 * @return {?WebInspector.ConsoleGroup} 1204 */ 1205 parentGroup: function() 1206 { 1207 return this._parentGroup || this; 1208 }, 1209 } 1210 1211 /** 1212 * @constructor 1213 * @implements {WebInspector.ActionDelegate} 1214 */ 1215 WebInspector.ConsoleView.ShowConsoleActionDelegate = function() 1216 { 1217 } 1218 1219 WebInspector.ConsoleView.ShowConsoleActionDelegate.prototype = { 1220 /** 1221 * @return {boolean} 1222 */ 1223 handleAction: function() 1224 { 1225 WebInspector.console.show(); 1226 return true; 1227 } 1228 } 1229