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