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 30const ExpressionStopCharacters = " =:[({;,!+-*/&|^<>"; 31 32WebInspector.ConsoleView = function(drawer) 33{ 34 WebInspector.View.call(this, document.getElementById("console-view")); 35 36 this.messages = []; 37 this.drawer = drawer; 38 39 this.clearButton = document.getElementById("clear-console-status-bar-item"); 40 this.clearButton.title = WebInspector.UIString("Clear console log."); 41 this.clearButton.addEventListener("click", this._clearButtonClicked.bind(this), false); 42 43 this.messagesElement = document.getElementById("console-messages"); 44 this.messagesElement.addEventListener("selectstart", this._messagesSelectStart.bind(this), false); 45 this.messagesElement.addEventListener("click", this._messagesClicked.bind(this), true); 46 47 this.promptElement = document.getElementById("console-prompt"); 48 this.promptElement.className = "source-code"; 49 this.promptElement.addEventListener("keydown", this._promptKeyDown.bind(this), true); 50 this.prompt = new WebInspector.TextPrompt(this.promptElement, this.completions.bind(this), ExpressionStopCharacters + "."); 51 this.prompt.history = WebInspector.settings.consoleHistory; 52 53 this.topGroup = new WebInspector.ConsoleGroup(null); 54 this.messagesElement.insertBefore(this.topGroup.element, this.promptElement); 55 this.currentGroup = this.topGroup; 56 57 this.toggleConsoleButton = document.getElementById("console-status-bar-item"); 58 this.toggleConsoleButton.title = WebInspector.UIString("Show console."); 59 this.toggleConsoleButton.addEventListener("click", this._toggleConsoleButtonClicked.bind(this), false); 60 61 // Will hold the list of filter elements 62 this.filterBarElement = document.getElementById("console-filter"); 63 64 function createDividerElement() { 65 var dividerElement = document.createElement("div"); 66 dividerElement.addStyleClass("scope-bar-divider"); 67 this.filterBarElement.appendChild(dividerElement); 68 } 69 70 var updateFilterHandler = this._updateFilter.bind(this); 71 function createFilterElement(category, label) { 72 var categoryElement = document.createElement("li"); 73 categoryElement.category = category; 74 categoryElement.className = category; 75 categoryElement.addEventListener("click", updateFilterHandler, false); 76 categoryElement.textContent = label; 77 78 this.filterBarElement.appendChild(categoryElement); 79 80 return categoryElement; 81 } 82 83 this.allElement = createFilterElement.call(this, "all", WebInspector.UIString("All")); 84 createDividerElement.call(this); 85 this.errorElement = createFilterElement.call(this, "errors", WebInspector.UIString("Errors")); 86 this.warningElement = createFilterElement.call(this, "warnings", WebInspector.UIString("Warnings")); 87 this.logElement = createFilterElement.call(this, "logs", WebInspector.UIString("Logs")); 88 89 this.filter(this.allElement, false); 90 this._registerShortcuts(); 91 92 this.messagesElement.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this), false); 93 94 this._customFormatters = { 95 "object": this._formatobject, 96 "array": this._formatarray, 97 "node": this._formatnode, 98 "string": this._formatstring 99 }; 100 101 this._registerConsoleDomainDispatcher(); 102} 103 104WebInspector.ConsoleView.prototype = { 105 _registerConsoleDomainDispatcher: function() { 106 var console = this; 107 var dispatcher = { 108 messageAdded: function(payload) 109 { 110 var consoleMessage = new WebInspector.ConsoleMessage( 111 payload.source, 112 payload.type, 113 payload.level, 114 payload.line, 115 payload.url, 116 payload.repeatCount, 117 payload.text, 118 payload.parameters, 119 payload.stackTrace, 120 payload.networkIdentifier); 121 console.addMessage(consoleMessage); 122 }, 123 124 messageRepeatCountUpdated: function(count) 125 { 126 var msg = console.previousMessage; 127 var prevRepeatCount = msg.totalRepeatCount; 128 129 if (!console.commandSincePreviousMessage) { 130 msg.repeatDelta = count - prevRepeatCount; 131 msg.repeatCount = msg.repeatCount + msg.repeatDelta; 132 msg.totalRepeatCount = count; 133 msg._updateRepeatCount(); 134 console._incrementErrorWarningCount(msg); 135 } else { 136 var msgCopy = new WebInspector.ConsoleMessage(msg.source, msg.type, msg.level, msg.line, msg.url, count - prevRepeatCount, msg._messageText, msg._parameters, msg._stackTrace, msg._requestId); 137 msgCopy.totalRepeatCount = count; 138 msgCopy._formatMessage(); 139 console.addMessage(msgCopy); 140 } 141 }, 142 143 messagesCleared: function() 144 { 145 console.clearMessages(); 146 }, 147 } 148 InspectorBackend.registerDomainDispatcher("Console", dispatcher); 149 }, 150 151 setConsoleMessageExpiredCount: function(count) 152 { 153 if (count) { 154 var message = String.sprintf(WebInspector.UIString("%d console messages are not shown."), count); 155 this.addMessage(WebInspector.ConsoleMessage.createTextMessage(message, WebInspector.ConsoleMessage.MessageLevel.Warning)); 156 } 157 }, 158 159 _updateFilter: function(e) 160 { 161 var isMac = WebInspector.isMac(); 162 var selectMultiple = false; 163 if (isMac && e.metaKey && !e.ctrlKey && !e.altKey && !e.shiftKey) 164 selectMultiple = true; 165 if (!isMac && e.ctrlKey && !e.metaKey && !e.altKey && !e.shiftKey) 166 selectMultiple = true; 167 168 this.filter(e.target, selectMultiple); 169 }, 170 171 filter: function(target, selectMultiple) 172 { 173 function unselectAll() 174 { 175 this.allElement.removeStyleClass("selected"); 176 this.errorElement.removeStyleClass("selected"); 177 this.warningElement.removeStyleClass("selected"); 178 this.logElement.removeStyleClass("selected"); 179 180 this.messagesElement.removeStyleClass("filter-all"); 181 this.messagesElement.removeStyleClass("filter-errors"); 182 this.messagesElement.removeStyleClass("filter-warnings"); 183 this.messagesElement.removeStyleClass("filter-logs"); 184 } 185 186 var targetFilterClass = "filter-" + target.category; 187 188 if (target.category === "all") { 189 if (target.hasStyleClass("selected")) { 190 // We can't unselect all, so we break early here 191 return; 192 } 193 194 unselectAll.call(this); 195 } else { 196 // Something other than all is being selected, so we want to unselect all 197 if (this.allElement.hasStyleClass("selected")) { 198 this.allElement.removeStyleClass("selected"); 199 this.messagesElement.removeStyleClass("filter-all"); 200 } 201 } 202 203 if (!selectMultiple) { 204 // If multiple selection is off, we want to unselect everything else 205 // and just select ourselves. 206 unselectAll.call(this); 207 208 target.addStyleClass("selected"); 209 this.messagesElement.addStyleClass(targetFilterClass); 210 211 return; 212 } 213 214 if (target.hasStyleClass("selected")) { 215 // If selectMultiple is turned on, and we were selected, we just 216 // want to unselect ourselves. 217 target.removeStyleClass("selected"); 218 this.messagesElement.removeStyleClass(targetFilterClass); 219 } else { 220 // If selectMultiple is turned on, and we weren't selected, we just 221 // want to select ourselves. 222 target.addStyleClass("selected"); 223 this.messagesElement.addStyleClass(targetFilterClass); 224 } 225 }, 226 227 _toggleConsoleButtonClicked: function() 228 { 229 this.drawer.visibleView = this; 230 }, 231 232 attach: function(mainElement, statusBarElement) 233 { 234 mainElement.appendChild(this.element); 235 statusBarElement.appendChild(this.clearButton); 236 statusBarElement.appendChild(this.filterBarElement); 237 }, 238 239 show: function() 240 { 241 this.toggleConsoleButton.addStyleClass("toggled-on"); 242 this.toggleConsoleButton.title = WebInspector.UIString("Hide console."); 243 if (!this.prompt.isCaretInsidePrompt()) 244 this.prompt.moveCaretToEndOfPrompt(); 245 }, 246 247 afterShow: function() 248 { 249 WebInspector.currentFocusElement = this.promptElement; 250 }, 251 252 hide: function() 253 { 254 this.toggleConsoleButton.removeStyleClass("toggled-on"); 255 this.toggleConsoleButton.title = WebInspector.UIString("Show console."); 256 }, 257 258 _scheduleScrollIntoView: function() 259 { 260 if (this._scrollIntoViewTimer) 261 return; 262 263 function scrollIntoView() 264 { 265 this.promptElement.scrollIntoView(true); 266 delete this._scrollIntoViewTimer; 267 } 268 this._scrollIntoViewTimer = setTimeout(scrollIntoView.bind(this), 20); 269 }, 270 271 addMessage: function(msg) 272 { 273 var shouldScrollToLastMessage = this.messagesElement.isScrolledToBottom(); 274 275 if (msg instanceof WebInspector.ConsoleMessage && !(msg instanceof WebInspector.ConsoleCommandResult)) { 276 this._incrementErrorWarningCount(msg); 277 WebInspector.resourceTreeModel.addConsoleMessage(msg); 278 WebInspector.panels.scripts.addConsoleMessage(msg); 279 this.commandSincePreviousMessage = false; 280 this.previousMessage = msg; 281 } else if (msg instanceof WebInspector.ConsoleCommand) { 282 if (this.previousMessage) { 283 this.commandSincePreviousMessage = true; 284 } 285 } 286 287 this.messages.push(msg); 288 289 if (msg.type === WebInspector.ConsoleMessage.MessageType.EndGroup) { 290 var parentGroup = this.currentGroup.parentGroup 291 if (parentGroup) 292 this.currentGroup = parentGroup; 293 } else { 294 if (msg.type === WebInspector.ConsoleMessage.MessageType.StartGroup || msg.type === WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed) { 295 var group = new WebInspector.ConsoleGroup(this.currentGroup); 296 this.currentGroup.messagesElement.appendChild(group.element); 297 this.currentGroup = group; 298 } 299 300 this.currentGroup.addMessage(msg); 301 } 302 303 // Always scroll when command result arrives. 304 if (shouldScrollToLastMessage || (msg instanceof WebInspector.ConsoleCommandResult)) 305 this._scheduleScrollIntoView(); 306 }, 307 308 _incrementErrorWarningCount: function(msg) 309 { 310 switch (msg.level) { 311 case WebInspector.ConsoleMessage.MessageLevel.Warning: 312 WebInspector.warnings += msg.repeatDelta; 313 break; 314 case WebInspector.ConsoleMessage.MessageLevel.Error: 315 WebInspector.errors += msg.repeatDelta; 316 break; 317 } 318 }, 319 320 requestClearMessages: function() 321 { 322 ConsoleAgent.clearConsoleMessages(); 323 }, 324 325 clearMessages: function() 326 { 327 WebInspector.resourceTreeModel.clearConsoleMessages(); 328 WebInspector.panels.scripts.clearConsoleMessages(); 329 330 this.messages = []; 331 332 this.currentGroup = this.topGroup; 333 this.topGroup.messagesElement.removeChildren(); 334 335 WebInspector.errors = 0; 336 WebInspector.warnings = 0; 337 338 delete this.commandSincePreviousMessage; 339 delete this.previousMessage; 340 }, 341 342 completions: function(wordRange, bestMatchOnly, completionsReadyCallback) 343 { 344 // Pass less stop characters to rangeOfWord so the range will be a more complete expression. 345 var expressionRange = wordRange.startContainer.rangeOfWord(wordRange.startOffset, ExpressionStopCharacters, this.promptElement, "backward"); 346 var expressionString = expressionRange.toString(); 347 var prefix = wordRange.toString(); 348 this._completions(expressionString, prefix, bestMatchOnly, completionsReadyCallback); 349 }, 350 351 _completions: function(expressionString, prefix, bestMatchOnly, completionsReadyCallback) 352 { 353 var lastIndex = expressionString.length - 1; 354 355 var dotNotation = (expressionString[lastIndex] === "."); 356 var bracketNotation = (expressionString[lastIndex] === "["); 357 358 if (dotNotation || bracketNotation) 359 expressionString = expressionString.substr(0, lastIndex); 360 361 if (!expressionString && !prefix) 362 return; 363 364 if (!expressionString && WebInspector.panels.scripts.paused) 365 WebInspector.panels.scripts.getSelectedCallFrameVariables(reportCompletions.bind(this)); 366 else 367 this.evalInInspectedWindow(expressionString, "completion", true, evaluated.bind(this)); 368 369 function evaluated(result) 370 { 371 if (!result) 372 return; 373 result.getAllProperties(evaluatedProperties.bind(this)); 374 } 375 376 function evaluatedProperties(properties) 377 { 378 RuntimeAgent.releaseObjectGroup("completion"); 379 var propertyNames = {}; 380 for (var i = 0; properties && i < properties.length; ++i) 381 propertyNames[properties[i].name] = true; 382 reportCompletions.call(this, propertyNames); 383 } 384 385 function reportCompletions(propertyNames) 386 { 387 var includeCommandLineAPI = (!dotNotation && !bracketNotation); 388 if (includeCommandLineAPI) { 389 const commandLineAPI = ["dir", "dirxml", "keys", "values", "profile", "profileEnd", "monitorEvents", "unmonitorEvents", "inspect", "copy", "clear"]; 390 for (var i = 0; i < commandLineAPI.length; ++i) 391 propertyNames[commandLineAPI[i]] = true; 392 } 393 394 this._reportCompletions(bestMatchOnly, completionsReadyCallback, dotNotation, bracketNotation, prefix, Object.keys(propertyNames)); 395 } 396 }, 397 398 _reportCompletions: function(bestMatchOnly, completionsReadyCallback, dotNotation, bracketNotation, prefix, properties) { 399 if (bracketNotation) { 400 if (prefix.length && prefix[0] === "'") 401 var quoteUsed = "'"; 402 else 403 var quoteUsed = "\""; 404 } 405 406 var results = []; 407 properties.sort(); 408 409 for (var i = 0; i < properties.length; ++i) { 410 var property = properties[i]; 411 412 if (dotNotation && !/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(property)) 413 continue; 414 415 if (bracketNotation) { 416 if (!/^[0-9]+$/.test(property)) 417 property = quoteUsed + property.escapeCharacters(quoteUsed + "\\") + quoteUsed; 418 property += "]"; 419 } 420 421 if (property.length < prefix.length) 422 continue; 423 if (property.indexOf(prefix) !== 0) 424 continue; 425 426 results.push(property); 427 if (bestMatchOnly) 428 break; 429 } 430 completionsReadyCallback(results); 431 }, 432 433 _clearButtonClicked: function() 434 { 435 this.requestClearMessages(); 436 }, 437 438 _handleContextMenuEvent: function(event) 439 { 440 if (!window.getSelection().isCollapsed) { 441 // If there is a selection, we want to show our normal context menu 442 // (with Copy, etc.), and not Clear Console. 443 return; 444 } 445 446 var itemAction = function () { 447 WebInspector.settings.monitoringXHREnabled = !WebInspector.settings.monitoringXHREnabled; 448 ConsoleAgent.setMonitoringXHREnabled(WebInspector.settings.monitoringXHREnabled); 449 }.bind(this); 450 var contextMenu = new WebInspector.ContextMenu(); 451 contextMenu.appendCheckboxItem(WebInspector.UIString("XMLHttpRequest logging"), itemAction, WebInspector.settings.monitoringXHREnabled) 452 contextMenu.appendItem(WebInspector.UIString("Clear Console"), this.requestClearMessages.bind(this)); 453 contextMenu.show(event); 454 }, 455 456 _messagesSelectStart: function(event) 457 { 458 if (this._selectionTimeout) 459 clearTimeout(this._selectionTimeout); 460 461 this.prompt.clearAutoComplete(); 462 463 function moveBackIfOutside() 464 { 465 delete this._selectionTimeout; 466 if (!this.prompt.isCaretInsidePrompt() && window.getSelection().isCollapsed) 467 this.prompt.moveCaretToEndOfPrompt(); 468 this.prompt.autoCompleteSoon(); 469 } 470 471 this._selectionTimeout = setTimeout(moveBackIfOutside.bind(this), 100); 472 }, 473 474 _messagesClicked: function(event) 475 { 476 var link = event.target.enclosingNodeOrSelfWithNodeName("a"); 477 if (!link || !link.representedNode) 478 return; 479 480 WebInspector.updateFocusedNode(link.representedNode.id); 481 event.stopPropagation(); 482 event.preventDefault(); 483 }, 484 485 _registerShortcuts: function() 486 { 487 this._shortcuts = {}; 488 489 var shortcut = WebInspector.KeyboardShortcut; 490 var shortcutK = shortcut.makeDescriptor("k", WebInspector.KeyboardShortcut.Modifiers.Meta); 491 // This case requires a separate bound function as its isMacOnly property should not be shared among different shortcut handlers. 492 this._shortcuts[shortcutK.key] = this.requestClearMessages.bind(this); 493 this._shortcuts[shortcutK.key].isMacOnly = true; 494 495 var clearConsoleHandler = this.requestClearMessages.bind(this); 496 var shortcutL = shortcut.makeDescriptor("l", WebInspector.KeyboardShortcut.Modifiers.Ctrl); 497 this._shortcuts[shortcutL.key] = clearConsoleHandler; 498 499 var section = WebInspector.shortcutsHelp.section(WebInspector.UIString("Console")); 500 var keys = WebInspector.isMac() ? [ shortcutK.name, shortcutL.name ] : [ shortcutL.name ]; 501 section.addAlternateKeys(keys, WebInspector.UIString("Clear Console")); 502 503 keys = [ 504 shortcut.shortcutToString(shortcut.Keys.Tab), 505 shortcut.shortcutToString(shortcut.Keys.Tab, shortcut.Modifiers.Shift) 506 ]; 507 section.addRelatedKeys(keys, WebInspector.UIString("Next/previous suggestion")); 508 section.addKey(shortcut.shortcutToString(shortcut.Keys.Right), WebInspector.UIString("Accept suggestion")); 509 keys = [ 510 shortcut.shortcutToString(shortcut.Keys.Down), 511 shortcut.shortcutToString(shortcut.Keys.Up) 512 ]; 513 section.addRelatedKeys(keys, WebInspector.UIString("Next/previous line")); 514 keys = [ 515 shortcut.shortcutToString("N", shortcut.Modifiers.Alt), 516 shortcut.shortcutToString("P", shortcut.Modifiers.Alt) 517 ]; 518 if (WebInspector.isMac()) 519 section.addRelatedKeys(keys, WebInspector.UIString("Next/previous command")); 520 section.addKey(shortcut.shortcutToString(shortcut.Keys.Enter), WebInspector.UIString("Execute command")); 521 }, 522 523 _promptKeyDown: function(event) 524 { 525 if (isEnterKey(event)) { 526 this._enterKeyPressed(event); 527 return; 528 } 529 530 var shortcut = WebInspector.KeyboardShortcut.makeKeyFromEvent(event); 531 var handler = this._shortcuts[shortcut]; 532 if (handler) { 533 if (!this._shortcuts[shortcut].isMacOnly || WebInspector.isMac()) { 534 handler(); 535 event.preventDefault(); 536 return; 537 } 538 } 539 }, 540 541 evalInInspectedWindow: function(expression, objectGroup, includeCommandLineAPI, callback) 542 { 543 if (WebInspector.panels.scripts && WebInspector.panels.scripts.paused) { 544 WebInspector.panels.scripts.evaluateInSelectedCallFrame(expression, objectGroup, includeCommandLineAPI, callback); 545 return; 546 } 547 548 if (!expression) { 549 // There is no expression, so the completion should happen against global properties. 550 expression = "this"; 551 } 552 553 function evalCallback(error, result) 554 { 555 if (!error) 556 callback(WebInspector.RemoteObject.fromPayload(result)); 557 } 558 RuntimeAgent.evaluate(expression, objectGroup, includeCommandLineAPI, evalCallback); 559 }, 560 561 _enterKeyPressed: function(event) 562 { 563 if (event.altKey || event.ctrlKey || event.shiftKey) 564 return; 565 566 event.preventDefault(); 567 event.stopPropagation(); 568 569 this.prompt.clearAutoComplete(true); 570 571 var str = this.prompt.text; 572 if (!str.length) 573 return; 574 575 var commandMessage = new WebInspector.ConsoleCommand(str); 576 this.addMessage(commandMessage); 577 578 var self = this; 579 function printResult(result) 580 { 581 self.prompt.history.push(str); 582 self.prompt.historyOffset = 0; 583 self.prompt.text = ""; 584 585 WebInspector.settings.consoleHistory = self.prompt.history.slice(-30); 586 587 self.addMessage(new WebInspector.ConsoleCommandResult(result, commandMessage)); 588 } 589 this.evalInInspectedWindow(str, "console", true, printResult); 590 }, 591 592 _format: function(output, forceObjectFormat) 593 { 594 var isProxy = (output != null && typeof output === "object"); 595 var type = (forceObjectFormat ? "object" : WebInspector.RemoteObject.type(output)); 596 597 var formatter = this._customFormatters[type]; 598 if (!formatter || !isProxy) { 599 formatter = this._formatvalue; 600 output = output.description; 601 } 602 603 var span = document.createElement("span"); 604 span.className = "console-formatted-" + type + " source-code"; 605 formatter.call(this, output, span); 606 return span; 607 }, 608 609 _formatvalue: function(val, elem) 610 { 611 elem.appendChild(document.createTextNode(val)); 612 }, 613 614 _formatobject: function(obj, elem) 615 { 616 elem.appendChild(new WebInspector.ObjectPropertiesSection(obj, obj.description, null, true).element); 617 }, 618 619 _formatnode: function(object, elem) 620 { 621 function printNode(nodeId) 622 { 623 if (!nodeId) { 624 // Sometimes DOM is loaded after the sync message is being formatted, so we get no 625 // nodeId here. So we fall back to object formatting here. 626 this._formatobject(object, elem); 627 return; 628 } 629 var treeOutline = new WebInspector.ElementsTreeOutline(); 630 treeOutline.showInElementsPanelEnabled = true; 631 treeOutline.rootDOMNode = WebInspector.domAgent.nodeForId(nodeId); 632 treeOutline.element.addStyleClass("outline-disclosure"); 633 if (!treeOutline.children[0].hasChildren) 634 treeOutline.element.addStyleClass("single-node"); 635 elem.appendChild(treeOutline.element); 636 } 637 object.pushNodeToFrontend(printNode.bind(this)); 638 }, 639 640 _formatarray: function(arr, elem) 641 { 642 arr.getOwnProperties(this._printArray.bind(this, elem)); 643 }, 644 645 _formatstring: function(output, elem) 646 { 647 var span = document.createElement("span"); 648 span.className = "console-formatted-string source-code"; 649 span.appendChild(WebInspector.linkifyStringAsFragment(output.description)); 650 651 // Make black quotes. 652 elem.removeStyleClass("console-formatted-string"); 653 elem.appendChild(document.createTextNode("\"")); 654 elem.appendChild(span); 655 elem.appendChild(document.createTextNode("\"")); 656 }, 657 658 _printArray: function(elem, properties) 659 { 660 if (!properties) 661 return; 662 663 var elements = []; 664 for (var i = 0; i < properties.length; ++i) { 665 var name = properties[i].name; 666 if (name == parseInt(name)) 667 elements[name] = this._formatAsArrayEntry(properties[i].value); 668 } 669 670 elem.appendChild(document.createTextNode("[")); 671 for (var i = 0; i < elements.length; ++i) { 672 var element = elements[i]; 673 if (element) 674 elem.appendChild(element); 675 else 676 elem.appendChild(document.createTextNode("undefined")) 677 if (i < elements.length - 1) 678 elem.appendChild(document.createTextNode(", ")); 679 } 680 elem.appendChild(document.createTextNode("]")); 681 }, 682 683 _formatAsArrayEntry: function(output) 684 { 685 // Prevent infinite expansion of cross-referencing arrays. 686 return this._format(output, WebInspector.RemoteObject.type(output) === "array"); 687 } 688} 689 690WebInspector.ConsoleView.prototype.__proto__ = WebInspector.View.prototype; 691 692WebInspector.ConsoleMessage = function(source, type, level, line, url, repeatCount, message, parameters, stackTrace, requestId) 693{ 694 this.source = source; 695 this.type = type; 696 this.level = level; 697 this.line = line; 698 this.url = url; 699 this.repeatCount = repeatCount; 700 this.repeatDelta = repeatCount; 701 this.totalRepeatCount = repeatCount; 702 this._messageText = message; 703 this._parameters = parameters; 704 this._stackTrace = stackTrace; 705 this._requestId = requestId; 706 707 if (stackTrace && stackTrace.length) { 708 var topCallFrame = stackTrace[0]; 709 if (!this.url) 710 this.url = topCallFrame.url; 711 if (!this.line) 712 this.line = topCallFrame.lineNumber; 713 } 714 715 this._formatMessage(); 716} 717 718WebInspector.ConsoleMessage.createTextMessage = function(text, level) 719{ 720 level = level || WebInspector.ConsoleMessage.MessageLevel.Log; 721 return new WebInspector.ConsoleMessage(WebInspector.ConsoleMessage.MessageSource.JS, WebInspector.ConsoleMessage.MessageType.Log, level, 0, null, 1, null, [text], null); 722} 723 724WebInspector.ConsoleMessage.prototype = { 725 _formatMessage: function() 726 { 727 var stackTrace = this._stackTrace; 728 var messageText; 729 switch (this.type) { 730 case WebInspector.ConsoleMessage.MessageType.Trace: 731 messageText = document.createTextNode("console.trace()"); 732 break; 733 case WebInspector.ConsoleMessage.MessageType.UncaughtException: 734 messageText = document.createTextNode(this._messageText); 735 break; 736 case WebInspector.ConsoleMessage.MessageType.NetworkError: 737 var resource = this._requestId && WebInspector.networkResourceById(this._requestId); 738 if (resource) { 739 stackTrace = resource.stackTrace; 740 741 messageText = document.createElement("span"); 742 messageText.appendChild(document.createTextNode(resource.requestMethod + " ")); 743 messageText.appendChild(WebInspector.linkifyURLAsNode(resource.url)); 744 if (resource.failed) 745 messageText.appendChild(document.createTextNode(" " + resource.localizedFailDescription)); 746 else 747 messageText.appendChild(document.createTextNode(" " + resource.statusCode + " (" + resource.statusText + ")")); 748 } else 749 messageText = this._format([this._messageText]); 750 break; 751 case WebInspector.ConsoleMessage.MessageType.Assert: 752 var args = [WebInspector.UIString("Assertion failed:")]; 753 if (this._parameters) 754 args = args.concat(this._parameters); 755 messageText = this._format(args); 756 break; 757 case WebInspector.ConsoleMessage.MessageType.Object: 758 var obj = this._parameters ? this._parameters[0] : undefined; 759 var args = ["%O", obj]; 760 messageText = this._format(args); 761 break; 762 default: 763 var args = this._parameters || [this._messageText]; 764 messageText = this._format(args); 765 break; 766 } 767 768 this._formattedMessage = document.createElement("span"); 769 this._formattedMessage.className = "console-message-text source-code"; 770 771 if (this.url && this.url !== "undefined") { 772 var urlElement = WebInspector.linkifyResourceAsNode(this.url, "scripts", this.line, "console-message-url"); 773 this._formattedMessage.appendChild(urlElement); 774 } 775 776 this._formattedMessage.appendChild(messageText); 777 778 if (this._stackTrace) { 779 switch (this.type) { 780 case WebInspector.ConsoleMessage.MessageType.Trace: 781 case WebInspector.ConsoleMessage.MessageType.UncaughtException: 782 case WebInspector.ConsoleMessage.MessageType.NetworkError: 783 case WebInspector.ConsoleMessage.MessageType.Assert: { 784 var ol = document.createElement("ol"); 785 ol.className = "outline-disclosure"; 786 var treeOutline = new TreeOutline(ol); 787 788 var content = this._formattedMessage; 789 var root = new TreeElement(content, null, true); 790 content.treeElementForTest = root; 791 treeOutline.appendChild(root); 792 if (this.type === WebInspector.ConsoleMessage.MessageType.Trace) 793 root.expand(); 794 795 this._populateStackTraceTreeElement(root); 796 this._formattedMessage = ol; 797 } 798 } 799 } 800 801 // This is used for inline message bubbles in SourceFrames, or other plain-text representations. 802 this.message = this._formattedMessage.textContent; 803 }, 804 805 isErrorOrWarning: function() 806 { 807 return (this.level === WebInspector.ConsoleMessage.MessageLevel.Warning || this.level === WebInspector.ConsoleMessage.MessageLevel.Error); 808 }, 809 810 _format: function(parameters) 811 { 812 // This node is used like a Builder. Values are continually appended onto it. 813 var formattedResult = document.createElement("span"); 814 if (!parameters.length) 815 return formattedResult; 816 817 // Formatting code below assumes that parameters are all wrappers whereas frontend console 818 // API allows passing arbitrary values as messages (strings, numbers, etc.). Wrap them here. 819 for (var i = 0; i < parameters.length; ++i) { 820 if (typeof parameters[i] === "object") 821 parameters[i] = WebInspector.RemoteObject.fromPayload(parameters[i]); 822 else 823 parameters[i] = WebInspector.RemoteObject.fromPrimitiveValue(parameters[i]); 824 } 825 826 // There can be string log and string eval result. We distinguish between them based on message type. 827 var shouldFormatMessage = WebInspector.RemoteObject.type(parameters[0]) === "string" && this.type !== WebInspector.ConsoleMessage.MessageType.Result; 828 829 // Multiple parameters with the first being a format string. Save unused substitutions. 830 if (shouldFormatMessage) { 831 // Multiple parameters with the first being a format string. Save unused substitutions. 832 var result = this._formatWithSubstitutionString(parameters, formattedResult); 833 parameters = result.unusedSubstitutions; 834 if (parameters.length) 835 formattedResult.appendChild(document.createTextNode(" ")); 836 } 837 838 // Single parameter, or unused substitutions from above. 839 for (var i = 0; i < parameters.length; ++i) { 840 // Inline strings when formatting. 841 if (shouldFormatMessage && parameters[i].type === "string") 842 formattedResult.appendChild(document.createTextNode(parameters[i].description)); 843 else 844 formattedResult.appendChild(WebInspector.console._format(parameters[i])); 845 if (i < parameters.length - 1) 846 formattedResult.appendChild(document.createTextNode(" ")); 847 } 848 return formattedResult; 849 }, 850 851 _formatWithSubstitutionString: function(parameters, formattedResult) 852 { 853 var formatters = {} 854 for (var i in String.standardFormatters) 855 formatters[i] = String.standardFormatters[i]; 856 857 function consoleFormatWrapper(force) 858 { 859 return function(obj) { 860 return WebInspector.console._format(obj, force); 861 }; 862 } 863 864 // Firebug uses %o for formatting objects. 865 formatters.o = consoleFormatWrapper(); 866 // Firebug allows both %i and %d for formatting integers. 867 formatters.i = formatters.d; 868 // Support %O to force object formatting, instead of the type-based %o formatting. 869 formatters.O = consoleFormatWrapper(true); 870 871 function append(a, b) 872 { 873 if (!(b instanceof Node)) 874 a.appendChild(WebInspector.linkifyStringAsFragment(b.toString())); 875 else 876 a.appendChild(b); 877 return a; 878 } 879 880 // String.format does treat formattedResult like a Builder, result is an object. 881 return String.format(parameters[0].description, parameters.slice(1), formatters, formattedResult, append); 882 }, 883 884 toMessageElement: function() 885 { 886 if (this._element) 887 return this._element; 888 889 var element = document.createElement("div"); 890 element.message = this; 891 element.className = "console-message"; 892 893 this._element = element; 894 895 switch (this.level) { 896 case WebInspector.ConsoleMessage.MessageLevel.Tip: 897 element.addStyleClass("console-tip-level"); 898 break; 899 case WebInspector.ConsoleMessage.MessageLevel.Log: 900 element.addStyleClass("console-log-level"); 901 break; 902 case WebInspector.ConsoleMessage.MessageLevel.Debug: 903 element.addStyleClass("console-debug-level"); 904 break; 905 case WebInspector.ConsoleMessage.MessageLevel.Warning: 906 element.addStyleClass("console-warning-level"); 907 break; 908 case WebInspector.ConsoleMessage.MessageLevel.Error: 909 element.addStyleClass("console-error-level"); 910 break; 911 } 912 913 if (this.type === WebInspector.ConsoleMessage.MessageType.StartGroup || this.type === WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed) 914 element.addStyleClass("console-group-title"); 915 916 if (this.elementsTreeOutline) { 917 element.addStyleClass("outline-disclosure"); 918 element.appendChild(this.elementsTreeOutline.element); 919 return element; 920 } 921 922 element.appendChild(this._formattedMessage); 923 924 if (this.repeatCount > 1) 925 this._updateRepeatCount(); 926 927 return element; 928 }, 929 930 _populateStackTraceTreeElement: function(parentTreeElement) 931 { 932 for (var i = 0; i < this._stackTrace.length; i++) { 933 var frame = this._stackTrace[i]; 934 935 var content = document.createElement("div"); 936 var messageTextElement = document.createElement("span"); 937 messageTextElement.className = "console-message-text source-code"; 938 var functionName = frame.functionName || WebInspector.UIString("(anonymous function)"); 939 messageTextElement.appendChild(document.createTextNode(functionName)); 940 content.appendChild(messageTextElement); 941 942 var urlElement = WebInspector.linkifyResourceAsNode(frame.url, "scripts", frame.lineNumber, "console-message-url"); 943 content.appendChild(urlElement); 944 945 var treeElement = new TreeElement(content); 946 parentTreeElement.appendChild(treeElement); 947 } 948 }, 949 950 _updateRepeatCount: function() { 951 if (!this.repeatCountElement) { 952 this.repeatCountElement = document.createElement("span"); 953 this.repeatCountElement.className = "bubble"; 954 955 this._element.insertBefore(this.repeatCountElement, this._element.firstChild); 956 this._element.addStyleClass("repeated-message"); 957 } 958 this.repeatCountElement.textContent = this.repeatCount; 959 }, 960 961 toString: function() 962 { 963 var sourceString; 964 switch (this.source) { 965 case WebInspector.ConsoleMessage.MessageSource.HTML: 966 sourceString = "HTML"; 967 break; 968 case WebInspector.ConsoleMessage.MessageSource.WML: 969 sourceString = "WML"; 970 break; 971 case WebInspector.ConsoleMessage.MessageSource.XML: 972 sourceString = "XML"; 973 break; 974 case WebInspector.ConsoleMessage.MessageSource.JS: 975 sourceString = "JS"; 976 break; 977 case WebInspector.ConsoleMessage.MessageSource.CSS: 978 sourceString = "CSS"; 979 break; 980 case WebInspector.ConsoleMessage.MessageSource.Other: 981 sourceString = "Other"; 982 break; 983 } 984 985 var typeString; 986 switch (this.type) { 987 case WebInspector.ConsoleMessage.MessageType.Log: 988 case WebInspector.ConsoleMessage.MessageType.UncaughtException: 989 case WebInspector.ConsoleMessage.MessageType.NetworkError: 990 typeString = "Log"; 991 break; 992 case WebInspector.ConsoleMessage.MessageType.Object: 993 typeString = "Object"; 994 break; 995 case WebInspector.ConsoleMessage.MessageType.Trace: 996 typeString = "Trace"; 997 break; 998 case WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed: 999 case WebInspector.ConsoleMessage.MessageType.StartGroup: 1000 typeString = "Start Group"; 1001 break; 1002 case WebInspector.ConsoleMessage.MessageType.EndGroup: 1003 typeString = "End Group"; 1004 break; 1005 case WebInspector.ConsoleMessage.MessageType.Assert: 1006 typeString = "Assert"; 1007 break; 1008 case WebInspector.ConsoleMessage.MessageType.Result: 1009 typeString = "Result"; 1010 break; 1011 } 1012 1013 var levelString; 1014 switch (this.level) { 1015 case WebInspector.ConsoleMessage.MessageLevel.Tip: 1016 levelString = "Tip"; 1017 break; 1018 case WebInspector.ConsoleMessage.MessageLevel.Log: 1019 levelString = "Log"; 1020 break; 1021 case WebInspector.ConsoleMessage.MessageLevel.Warning: 1022 levelString = "Warning"; 1023 break; 1024 case WebInspector.ConsoleMessage.MessageLevel.Debug: 1025 levelString = "Debug"; 1026 break; 1027 case WebInspector.ConsoleMessage.MessageLevel.Error: 1028 levelString = "Error"; 1029 break; 1030 } 1031 1032 return sourceString + " " + typeString + " " + levelString + ": " + this._formattedMessage.textContent + "\n" + this.url + " line " + this.line; 1033 }, 1034 1035 isEqual: function(msg) 1036 { 1037 if (!msg) 1038 return false; 1039 1040 if (this._stackTrace) { 1041 if (!msg._stackTrace) 1042 return false; 1043 var l = this._stackTrace; 1044 var r = msg._stackTrace; 1045 for (var i = 0; i < l.length; i++) { 1046 if (l[i].url !== r[i].url || 1047 l[i].functionName !== r[i].functionName || 1048 l[i].lineNumber !== r[i].lineNumber || 1049 l[i].columnNumber !== r[i].columnNumber) 1050 return false; 1051 } 1052 } 1053 1054 return (this.source === msg.source) 1055 && (this.type === msg.type) 1056 && (this.level === msg.level) 1057 && (this.line === msg.line) 1058 && (this.url === msg.url) 1059 && (this.message === msg.message) 1060 && (this._requestId === msg._requestId); 1061 } 1062} 1063 1064// Note: Keep these constants in sync with the ones in Console.h 1065WebInspector.ConsoleMessage.MessageSource = { 1066 HTML: "html", 1067 WML: "wml", 1068 XML: "xml", 1069 JS: "javascript", 1070 CSS: "css", 1071 Other: "other" 1072} 1073 1074WebInspector.ConsoleMessage.MessageType = { 1075 Log: "log", 1076 Object: "other", 1077 Trace: "trace", 1078 StartGroup: "startGroup", 1079 StartGroupCollapsed: "startGroupCollapsed", 1080 EndGroup: "endGroup", 1081 Assert: "assert", 1082 UncaughtException: "uncaughtException", 1083 NetworkError: "networkError", 1084 Result: "result" 1085} 1086 1087WebInspector.ConsoleMessage.MessageLevel = { 1088 Tip: "tip", 1089 Log: "log", 1090 Warning: "warning", 1091 Error: "error", 1092 Debug: "debug" 1093} 1094 1095WebInspector.ConsoleCommand = function(command) 1096{ 1097 this.command = command; 1098} 1099 1100WebInspector.ConsoleCommand.prototype = { 1101 toMessageElement: function() 1102 { 1103 var element = document.createElement("div"); 1104 element.command = this; 1105 element.className = "console-user-command"; 1106 1107 var commandTextElement = document.createElement("span"); 1108 commandTextElement.className = "console-message-text source-code"; 1109 commandTextElement.textContent = this.command; 1110 element.appendChild(commandTextElement); 1111 1112 return element; 1113 } 1114} 1115 1116WebInspector.ConsoleCommandResult = function(result, originatingCommand) 1117{ 1118 var level = (result.isError() ? WebInspector.ConsoleMessage.MessageLevel.Error : WebInspector.ConsoleMessage.MessageLevel.Log); 1119 this.originatingCommand = originatingCommand; 1120 WebInspector.ConsoleMessage.call(this, WebInspector.ConsoleMessage.MessageSource.JS, WebInspector.ConsoleMessage.MessageType.Result, level, -1, null, 1, null, [result]); 1121} 1122 1123WebInspector.ConsoleCommandResult.prototype = { 1124 toMessageElement: function() 1125 { 1126 var element = WebInspector.ConsoleMessage.prototype.toMessageElement.call(this); 1127 element.addStyleClass("console-user-command-result"); 1128 return element; 1129 } 1130} 1131 1132WebInspector.ConsoleCommandResult.prototype.__proto__ = WebInspector.ConsoleMessage.prototype; 1133 1134WebInspector.ConsoleGroup = function(parentGroup) 1135{ 1136 this.parentGroup = parentGroup; 1137 1138 var element = document.createElement("div"); 1139 element.className = "console-group"; 1140 element.group = this; 1141 this.element = element; 1142 1143 var messagesElement = document.createElement("div"); 1144 messagesElement.className = "console-group-messages"; 1145 element.appendChild(messagesElement); 1146 this.messagesElement = messagesElement; 1147} 1148 1149WebInspector.ConsoleGroup.prototype = { 1150 addMessage: function(msg) 1151 { 1152 var element = msg.toMessageElement(); 1153 1154 if (msg.type === WebInspector.ConsoleMessage.MessageType.StartGroup || msg.type === WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed) { 1155 this.messagesElement.parentNode.insertBefore(element, this.messagesElement); 1156 element.addEventListener("click", this._titleClicked.bind(this), false); 1157 var groupElement = element.enclosingNodeOrSelfWithClass("console-group"); 1158 if (groupElement && msg.type === WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed) 1159 groupElement.addStyleClass("collapsed"); 1160 } else 1161 this.messagesElement.appendChild(element); 1162 1163 if (element.previousSibling && msg.originatingCommand && element.previousSibling.command === msg.originatingCommand) 1164 element.previousSibling.addStyleClass("console-adjacent-user-command-result"); 1165 }, 1166 1167 _titleClicked: function(event) 1168 { 1169 var groupTitleElement = event.target.enclosingNodeOrSelfWithClass("console-group-title"); 1170 if (groupTitleElement) { 1171 var groupElement = groupTitleElement.enclosingNodeOrSelfWithClass("console-group"); 1172 if (groupElement) 1173 if (groupElement.hasStyleClass("collapsed")) 1174 groupElement.removeStyleClass("collapsed"); 1175 else 1176 groupElement.addStyleClass("collapsed"); 1177 groupTitleElement.scrollIntoViewIfNeeded(true); 1178 } 1179 1180 event.stopPropagation(); 1181 event.preventDefault(); 1182 } 1183} 1184 1185