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 const ExpressionStopCharacters = " =:[({;,!+-*/&|^<>"; 31 32 WebInspector.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 52 this.topGroup = new WebInspector.ConsoleGroup(null, 0); 53 this.messagesElement.insertBefore(this.topGroup.element, this.promptElement); 54 this.groupLevel = 0; 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("divider"); 67 this.filterBarElement.appendChild(dividerElement); 68 } 69 70 var updateFilterHandler = this._updateFilter.bind(this); 71 function createFilterElement(category) { 72 var categoryElement = document.createElement("li"); 73 categoryElement.category = category; 74 categoryElement.addStyleClass(categoryElement.category); 75 categoryElement.addEventListener("click", updateFilterHandler, false); 76 77 var label = category.toString(); 78 categoryElement.appendChild(document.createTextNode(label)); 79 80 this.filterBarElement.appendChild(categoryElement); 81 return categoryElement; 82 } 83 84 this.allElement = createFilterElement.call(this, "All"); 85 createDividerElement.call(this); 86 this.errorElement = createFilterElement.call(this, "Errors"); 87 this.warningElement = createFilterElement.call(this, "Warnings"); 88 this.logElement = createFilterElement.call(this, "Logs"); 89 90 this.filter(this.allElement, false); 91 92 this._shortcuts = {}; 93 94 var shortcut; 95 var clearConsoleHandler = this.requestClearMessages.bind(this); 96 97 shortcut = WebInspector.KeyboardShortcut.makeKey("k", WebInspector.KeyboardShortcut.Modifiers.Meta); 98 this._shortcuts[shortcut] = clearConsoleHandler; 99 this._shortcuts[shortcut].isMacOnly = true; 100 shortcut = WebInspector.KeyboardShortcut.makeKey("l", WebInspector.KeyboardShortcut.Modifiers.Ctrl); 101 this._shortcuts[shortcut] = clearConsoleHandler; 102 103 // Since the Context Menu for the Console View will always be the same, we can create it in 104 // the constructor. 105 this._contextMenu = new WebInspector.ContextMenu(); 106 this._contextMenu.appendItem(WebInspector.UIString("Clear Console"), clearConsoleHandler); 107 this.messagesElement.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this), true); 108 109 this._customFormatters = { 110 "object": this._formatobject, 111 "array": this._formatarray, 112 "node": this._formatnode, 113 "string": this._formatstring 114 }; 115 } 116 117 WebInspector.ConsoleView.prototype = { 118 119 _updateFilter: function(e) 120 { 121 var isMac = WebInspector.isMac(); 122 var selectMultiple = false; 123 if (isMac && e.metaKey && !e.ctrlKey && !e.altKey && !e.shiftKey) 124 selectMultiple = true; 125 if (!isMac && e.ctrlKey && !e.metaKey && !e.altKey && !e.shiftKey) 126 selectMultiple = true; 127 128 this.filter(e.target, selectMultiple); 129 }, 130 131 filter: function(target, selectMultiple) 132 { 133 function unselectAll() 134 { 135 this.allElement.removeStyleClass("selected"); 136 this.errorElement.removeStyleClass("selected"); 137 this.warningElement.removeStyleClass("selected"); 138 this.logElement.removeStyleClass("selected"); 139 140 this.messagesElement.removeStyleClass("filter-all"); 141 this.messagesElement.removeStyleClass("filter-errors"); 142 this.messagesElement.removeStyleClass("filter-warnings"); 143 this.messagesElement.removeStyleClass("filter-logs"); 144 } 145 146 var targetFilterClass = "filter-" + target.category.toLowerCase(); 147 148 if (target.category == "All") { 149 if (target.hasStyleClass("selected")) { 150 // We can't unselect all, so we break early here 151 return; 152 } 153 154 unselectAll.call(this); 155 } else { 156 // Something other than all is being selected, so we want to unselect all 157 if (this.allElement.hasStyleClass("selected")) { 158 this.allElement.removeStyleClass("selected"); 159 this.messagesElement.removeStyleClass("filter-all"); 160 } 161 } 162 163 if (!selectMultiple) { 164 // If multiple selection is off, we want to unselect everything else 165 // and just select ourselves. 166 unselectAll.call(this); 167 168 target.addStyleClass("selected"); 169 this.messagesElement.addStyleClass(targetFilterClass); 170 171 return; 172 } 173 174 if (target.hasStyleClass("selected")) { 175 // If selectMultiple is turned on, and we were selected, we just 176 // want to unselect ourselves. 177 target.removeStyleClass("selected"); 178 this.messagesElement.removeStyleClass(targetFilterClass); 179 } else { 180 // If selectMultiple is turned on, and we weren't selected, we just 181 // want to select ourselves. 182 target.addStyleClass("selected"); 183 this.messagesElement.addStyleClass(targetFilterClass); 184 } 185 }, 186 187 _toggleConsoleButtonClicked: function() 188 { 189 this.drawer.visibleView = this; 190 }, 191 192 attach: function(mainElement, statusBarElement) 193 { 194 mainElement.appendChild(this.element); 195 statusBarElement.appendChild(this.clearButton); 196 statusBarElement.appendChild(this.filterBarElement); 197 }, 198 199 show: function() 200 { 201 this.toggleConsoleButton.addStyleClass("toggled-on"); 202 this.toggleConsoleButton.title = WebInspector.UIString("Hide console."); 203 if (!this.prompt.isCaretInsidePrompt()) 204 this.prompt.moveCaretToEndOfPrompt(); 205 }, 206 207 afterShow: function() 208 { 209 WebInspector.currentFocusElement = this.promptElement; 210 }, 211 212 hide: function() 213 { 214 this.toggleConsoleButton.removeStyleClass("toggled-on"); 215 this.toggleConsoleButton.title = WebInspector.UIString("Show console."); 216 }, 217 218 addMessage: function(msg) 219 { 220 if (msg instanceof WebInspector.ConsoleMessage && !(msg instanceof WebInspector.ConsoleCommandResult)) { 221 this._incrementErrorWarningCount(msg); 222 223 // Add message to the resource panel 224 if (msg.url in WebInspector.resourceURLMap) { 225 msg.resource = WebInspector.resourceURLMap[msg.url]; 226 if (WebInspector.panels.resources) 227 WebInspector.panels.resources.addMessageToResource(msg.resource, msg); 228 } 229 230 this.commandSincePreviousMessage = false; 231 this.previousMessage = msg; 232 } else if (msg instanceof WebInspector.ConsoleCommand) { 233 if (this.previousMessage) { 234 this.commandSincePreviousMessage = true; 235 } 236 } 237 238 this.messages.push(msg); 239 240 if (msg.type === WebInspector.ConsoleMessage.MessageType.EndGroup) { 241 if (this.groupLevel < 1) 242 return; 243 244 this.groupLevel--; 245 246 this.currentGroup = this.currentGroup.parentGroup; 247 } else { 248 if (msg.type === WebInspector.ConsoleMessage.MessageType.StartGroup) { 249 this.groupLevel++; 250 251 var group = new WebInspector.ConsoleGroup(this.currentGroup, this.groupLevel); 252 this.currentGroup.messagesElement.appendChild(group.element); 253 this.currentGroup = group; 254 } 255 256 this.currentGroup.addMessage(msg); 257 } 258 259 this.promptElement.scrollIntoView(false); 260 }, 261 262 updateMessageRepeatCount: function(count) 263 { 264 var msg = this.previousMessage; 265 var prevRepeatCount = msg.totalRepeatCount; 266 267 if (!this.commandSincePreviousMessage) { 268 msg.repeatDelta = count - prevRepeatCount; 269 msg.repeatCount = msg.repeatCount + msg.repeatDelta; 270 msg.totalRepeatCount = count; 271 msg._updateRepeatCount(); 272 this._incrementErrorWarningCount(msg); 273 } else { 274 msgCopy = new WebInspector.ConsoleMessage(msg.source, msg.type, msg.level, msg.line, msg.url, msg.groupLevel, count - prevRepeatCount); 275 msgCopy.totalRepeatCount = count; 276 msgCopy.setMessageBody(msg.args); 277 this.addMessage(msgCopy); 278 } 279 }, 280 281 _incrementErrorWarningCount: function(msg) 282 { 283 switch (msg.level) { 284 case WebInspector.ConsoleMessage.MessageLevel.Warning: 285 WebInspector.warnings += msg.repeatDelta; 286 break; 287 case WebInspector.ConsoleMessage.MessageLevel.Error: 288 WebInspector.errors += msg.repeatDelta; 289 break; 290 } 291 }, 292 293 requestClearMessages: function() 294 { 295 InjectedScriptAccess.getDefault().clearConsoleMessages(function() {}); 296 }, 297 298 clearMessages: function() 299 { 300 if (WebInspector.panels.resources) 301 WebInspector.panels.resources.clearMessages(); 302 303 this.messages = []; 304 305 this.groupLevel = 0; 306 this.currentGroup = this.topGroup; 307 this.topGroup.messagesElement.removeChildren(); 308 309 WebInspector.errors = 0; 310 WebInspector.warnings = 0; 311 312 delete this.commandSincePreviousMessage; 313 delete this.previousMessage; 314 }, 315 316 completions: function(wordRange, bestMatchOnly, completionsReadyCallback) 317 { 318 // Pass less stop characters to rangeOfWord so the range will be a more complete expression. 319 var expressionRange = wordRange.startContainer.rangeOfWord(wordRange.startOffset, ExpressionStopCharacters, this.promptElement, "backward"); 320 var expressionString = expressionRange.toString(); 321 var lastIndex = expressionString.length - 1; 322 323 var dotNotation = (expressionString[lastIndex] === "."); 324 var bracketNotation = (expressionString[lastIndex] === "["); 325 326 if (dotNotation || bracketNotation) 327 expressionString = expressionString.substr(0, lastIndex); 328 329 var prefix = wordRange.toString(); 330 if (!expressionString && !prefix) 331 return; 332 333 var reportCompletions = this._reportCompletions.bind(this, bestMatchOnly, completionsReadyCallback, dotNotation, bracketNotation, prefix); 334 // Collect comma separated object properties for the completion. 335 336 var includeInspectorCommandLineAPI = (!dotNotation && !bracketNotation); 337 var callFrameId = WebInspector.panels.scripts.selectedCallFrameId(); 338 var injectedScriptAccess; 339 if (WebInspector.panels.scripts && WebInspector.panels.scripts.paused) { 340 var selectedCallFrame = WebInspector.panels.scripts.sidebarPanes.callstack.selectedCallFrame; 341 injectedScriptAccess = InjectedScriptAccess.get(selectedCallFrame.injectedScriptId); 342 } else 343 injectedScriptAccess = InjectedScriptAccess.getDefault(); 344 injectedScriptAccess.getCompletions(expressionString, includeInspectorCommandLineAPI, callFrameId, reportCompletions); 345 }, 346 347 _reportCompletions: function(bestMatchOnly, completionsReadyCallback, dotNotation, bracketNotation, prefix, result, isException) { 348 if (isException) 349 return; 350 351 if (bracketNotation) { 352 if (prefix.length && prefix[0] === "'") 353 var quoteUsed = "'"; 354 else 355 var quoteUsed = "\""; 356 } 357 358 var results = []; 359 var properties = Object.sortedProperties(result); 360 361 for (var i = 0; i < properties.length; ++i) { 362 var property = properties[i]; 363 364 if (dotNotation && !/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(property)) 365 continue; 366 367 if (bracketNotation) { 368 if (!/^[0-9]+$/.test(property)) 369 property = quoteUsed + property.escapeCharacters(quoteUsed + "\\") + quoteUsed; 370 property += "]"; 371 } 372 373 if (property.length < prefix.length) 374 continue; 375 if (property.indexOf(prefix) !== 0) 376 continue; 377 378 results.push(property); 379 if (bestMatchOnly) 380 break; 381 } 382 completionsReadyCallback(results); 383 }, 384 385 _clearButtonClicked: function() 386 { 387 this.requestClearMessages(); 388 }, 389 390 _handleContextMenuEvent: function(event) 391 { 392 if (!window.getSelection().isCollapsed) { 393 // If there is a selection, we want to show our normal context menu 394 // (with Copy, etc.), and not Clear Console. 395 return; 396 } 397 398 this._contextMenu.show(event); 399 }, 400 401 _messagesSelectStart: function(event) 402 { 403 if (this._selectionTimeout) 404 clearTimeout(this._selectionTimeout); 405 406 this.prompt.clearAutoComplete(); 407 408 function moveBackIfOutside() 409 { 410 delete this._selectionTimeout; 411 if (!this.prompt.isCaretInsidePrompt() && window.getSelection().isCollapsed) 412 this.prompt.moveCaretToEndOfPrompt(); 413 this.prompt.autoCompleteSoon(); 414 } 415 416 this._selectionTimeout = setTimeout(moveBackIfOutside.bind(this), 100); 417 }, 418 419 _messagesClicked: function(event) 420 { 421 var link = event.target.enclosingNodeOrSelfWithNodeName("a"); 422 if (!link || !link.representedNode) 423 return; 424 425 WebInspector.updateFocusedNode(link.representedNode.id); 426 event.stopPropagation(); 427 event.preventDefault(); 428 }, 429 430 _promptKeyDown: function(event) 431 { 432 if (isEnterKey(event)) { 433 this._enterKeyPressed(event); 434 return; 435 } 436 437 var shortcut = WebInspector.KeyboardShortcut.makeKeyFromEvent(event); 438 var handler = this._shortcuts[shortcut]; 439 if (handler) { 440 if (!this._shortcuts[shortcut].isMacOnly || WebInspector.isMac()) { 441 handler(); 442 event.preventDefault(); 443 return; 444 } 445 } 446 }, 447 448 evalInInspectedWindow: function(expression, objectGroup, callback) 449 { 450 if (WebInspector.panels.scripts && WebInspector.panels.scripts.paused) { 451 WebInspector.panels.scripts.evaluateInSelectedCallFrame(expression, false, objectGroup, callback); 452 return; 453 } 454 this.doEvalInWindow(expression, objectGroup, callback); 455 }, 456 457 doEvalInWindow: function(expression, objectGroup, callback) 458 { 459 if (!expression) { 460 // There is no expression, so the completion should happen against global properties. 461 expression = "this"; 462 } 463 464 function evalCallback(result) 465 { 466 callback(result.value, result.isException); 467 }; 468 InjectedScriptAccess.getDefault().evaluate(expression, objectGroup, evalCallback); 469 }, 470 471 _enterKeyPressed: function(event) 472 { 473 if (event.altKey) 474 return; 475 476 event.preventDefault(); 477 event.stopPropagation(); 478 479 this.prompt.clearAutoComplete(true); 480 481 var str = this.prompt.text; 482 if (!str.length) 483 return; 484 485 var commandMessage = new WebInspector.ConsoleCommand(str); 486 this.addMessage(commandMessage); 487 488 var self = this; 489 function printResult(result, exception) 490 { 491 self.prompt.history.push(str); 492 self.prompt.historyOffset = 0; 493 self.prompt.text = ""; 494 self.addMessage(new WebInspector.ConsoleCommandResult(result, exception, commandMessage)); 495 } 496 this.evalInInspectedWindow(str, "console", printResult); 497 }, 498 499 _format: function(output, forceObjectFormat) 500 { 501 var isProxy = (output != null && typeof output === "object"); 502 var type = (forceObjectFormat ? "object" : Object.proxyType(output)); 503 504 var formatter = this._customFormatters[type]; 505 if (!formatter || !isProxy) { 506 formatter = this._formatvalue; 507 output = output.description || output; 508 } 509 510 var span = document.createElement("span"); 511 span.className = "console-formatted-" + type + " source-code"; 512 formatter.call(this, output, span); 513 return span; 514 }, 515 516 _formatvalue: function(val, elem) 517 { 518 elem.appendChild(document.createTextNode(val)); 519 }, 520 521 _formatobject: function(obj, elem) 522 { 523 elem.appendChild(new WebInspector.ObjectPropertiesSection(obj, obj.description, null, true).element); 524 }, 525 526 _formatnode: function(object, elem) 527 { 528 function printNode(nodeId) 529 { 530 if (!nodeId) 531 return; 532 var treeOutline = new WebInspector.ElementsTreeOutline(); 533 treeOutline.showInElementsPanelEnabled = true; 534 treeOutline.rootDOMNode = WebInspector.domAgent.nodeForId(nodeId); 535 treeOutline.element.addStyleClass("outline-disclosure"); 536 if (!treeOutline.children[0].hasChildren) 537 treeOutline.element.addStyleClass("single-node"); 538 elem.appendChild(treeOutline.element); 539 } 540 541 InjectedScriptAccess.get(object.injectedScriptId).pushNodeToFrontend(object, printNode); 542 }, 543 544 _formatarray: function(arr, elem) 545 { 546 InjectedScriptAccess.get(arr.injectedScriptId).getProperties(arr, false, false, this._printArray.bind(this, elem)); 547 }, 548 549 _formatstring: function(output, elem) 550 { 551 var span = document.createElement("span"); 552 span.className = "console-formatted-string source-code"; 553 span.appendChild(WebInspector.linkifyStringAsFragment(output.description)); 554 555 // Make black quotes. 556 elem.removeStyleClass("console-formatted-string"); 557 elem.appendChild(document.createTextNode("\"")); 558 elem.appendChild(span); 559 elem.appendChild(document.createTextNode("\"")); 560 }, 561 562 _printArray: function(elem, properties) 563 { 564 if (!properties) 565 return; 566 567 var elements = []; 568 for (var i = 0; i < properties.length; ++i) { 569 var name = properties[i].name; 570 if (name == parseInt(name)) 571 elements[name] = this._format(properties[i].value); 572 } 573 574 elem.appendChild(document.createTextNode("[")); 575 for (var i = 0; i < elements.length; ++i) { 576 var element = elements[i]; 577 if (element) 578 elem.appendChild(element); 579 else 580 elem.appendChild(document.createTextNode("undefined")) 581 if (i < elements.length - 1) 582 elem.appendChild(document.createTextNode(", ")); 583 } 584 elem.appendChild(document.createTextNode("]")); 585 } 586 } 587 588 WebInspector.ConsoleView.prototype.__proto__ = WebInspector.View.prototype; 589 590 WebInspector.ConsoleMessage = function(source, type, level, line, url, groupLevel, repeatCount) 591 { 592 this.source = source; 593 this.type = type; 594 this.level = level; 595 this.line = line; 596 this.url = url; 597 this.groupLevel = groupLevel; 598 this.repeatCount = repeatCount; 599 this.repeatDelta = repeatCount; 600 this.totalRepeatCount = repeatCount; 601 if (arguments.length > 7) 602 this.setMessageBody(Array.prototype.slice.call(arguments, 7)); 603 } 604 605 WebInspector.ConsoleMessage.prototype = { 606 setMessageBody: function(args) 607 { 608 this.args = args; 609 switch (this.type) { 610 case WebInspector.ConsoleMessage.MessageType.Trace: 611 var span = document.createElement("span"); 612 span.className = "console-formatted-trace source-code"; 613 var stack = Array.prototype.slice.call(args); 614 var funcNames = stack.map(function(f) { 615 return f || WebInspector.UIString("(anonymous function)"); 616 }); 617 span.appendChild(document.createTextNode(funcNames.join("\n"))); 618 this.formattedMessage = span; 619 break; 620 case WebInspector.ConsoleMessage.MessageType.Object: 621 this.formattedMessage = this._format(["%O", args[0]]); 622 break; 623 default: 624 this.formattedMessage = this._format(args); 625 break; 626 } 627 628 // This is used for inline message bubbles in SourceFrames, or other plain-text representations. 629 this.message = this.formattedMessage.textContent; 630 }, 631 632 isErrorOrWarning: function() 633 { 634 return (this.level === WebInspector.ConsoleMessage.MessageLevel.Warning || this.level === WebInspector.ConsoleMessage.MessageLevel.Error); 635 }, 636 637 _format: function(parameters) 638 { 639 // This node is used like a Builder. Values are continually appended onto it. 640 var formattedResult = document.createElement("span"); 641 if (!parameters.length) 642 return formattedResult; 643 644 // Formatting code below assumes that parameters are all wrappers whereas frontend console 645 // API allows passing arbitrary values as messages (strings, numbers, etc.). Wrap them here. 646 for (var i = 0; i < parameters.length; ++i) 647 if (typeof parameters[i] !== "object" && typeof parameters[i] !== "function") 648 parameters[i] = WebInspector.ObjectProxy.wrapPrimitiveValue(parameters[i]); 649 650 // There can be string log and string eval result. We distinguish between them based on message type. 651 var shouldFormatMessage = Object.proxyType(parameters[0]) === "string" && this.type !== WebInspector.ConsoleMessage.MessageType.Result; 652 653 // Multiple parameters with the first being a format string. Save unused substitutions. 654 if (shouldFormatMessage) { 655 // Multiple parameters with the first being a format string. Save unused substitutions. 656 var result = this._formatWithSubstitutionString(parameters, formattedResult); 657 parameters = result.unusedSubstitutions; 658 if (parameters.length) 659 formattedResult.appendChild(document.createTextNode(" ")); 660 } 661 662 // Single parameter, or unused substitutions from above. 663 for (var i = 0; i < parameters.length; ++i) { 664 // Inline strings when formatting. 665 if (shouldFormatMessage && parameters[i].type === "string") 666 formattedResult.appendChild(document.createTextNode(parameters[i].description)); 667 else 668 formattedResult.appendChild(WebInspector.console._format(parameters[i])); 669 if (i < parameters.length - 1) 670 formattedResult.appendChild(document.createTextNode(" ")); 671 } 672 return formattedResult; 673 }, 674 675 _formatWithSubstitutionString: function(parameters, formattedResult) 676 { 677 var formatters = {} 678 for (var i in String.standardFormatters) 679 formatters[i] = String.standardFormatters[i]; 680 681 function consoleFormatWrapper(force) 682 { 683 return function(obj) { 684 return WebInspector.console._format(obj, force); 685 }; 686 } 687 688 // Firebug uses %o for formatting objects. 689 formatters.o = consoleFormatWrapper(); 690 // Firebug allows both %i and %d for formatting integers. 691 formatters.i = formatters.d; 692 // Support %O to force object formatting, instead of the type-based %o formatting. 693 formatters.O = consoleFormatWrapper(true); 694 695 function append(a, b) 696 { 697 if (!(b instanceof Node)) 698 a.appendChild(WebInspector.linkifyStringAsFragment(b.toString())); 699 else 700 a.appendChild(b); 701 return a; 702 } 703 704 // String.format does treat formattedResult like a Builder, result is an object. 705 return String.format(parameters[0].description, parameters.slice(1), formatters, formattedResult, append); 706 }, 707 708 toMessageElement: function() 709 { 710 if (this._element) 711 return this._element; 712 713 var element = document.createElement("div"); 714 element.message = this; 715 element.className = "console-message"; 716 717 this._element = element; 718 719 switch (this.source) { 720 case WebInspector.ConsoleMessage.MessageSource.HTML: 721 element.addStyleClass("console-html-source"); 722 break; 723 case WebInspector.ConsoleMessage.MessageSource.WML: 724 element.addStyleClass("console-wml-source"); 725 break; 726 case WebInspector.ConsoleMessage.MessageSource.XML: 727 element.addStyleClass("console-xml-source"); 728 break; 729 case WebInspector.ConsoleMessage.MessageSource.JS: 730 element.addStyleClass("console-js-source"); 731 break; 732 case WebInspector.ConsoleMessage.MessageSource.CSS: 733 element.addStyleClass("console-css-source"); 734 break; 735 case WebInspector.ConsoleMessage.MessageSource.Other: 736 element.addStyleClass("console-other-source"); 737 break; 738 } 739 740 switch (this.level) { 741 case WebInspector.ConsoleMessage.MessageLevel.Tip: 742 element.addStyleClass("console-tip-level"); 743 break; 744 case WebInspector.ConsoleMessage.MessageLevel.Log: 745 element.addStyleClass("console-log-level"); 746 break; 747 case WebInspector.ConsoleMessage.MessageLevel.Debug: 748 element.addStyleClass("console-debug-level"); 749 break; 750 case WebInspector.ConsoleMessage.MessageLevel.Warning: 751 element.addStyleClass("console-warning-level"); 752 break; 753 case WebInspector.ConsoleMessage.MessageLevel.Error: 754 element.addStyleClass("console-error-level"); 755 break; 756 } 757 758 if (this.type === WebInspector.ConsoleMessage.MessageType.StartGroup) 759 element.addStyleClass("console-group-title"); 760 761 if (this.elementsTreeOutline) { 762 element.addStyleClass("outline-disclosure"); 763 element.appendChild(this.elementsTreeOutline.element); 764 return element; 765 } 766 767 if (this.url && this.url !== "undefined") { 768 var urlElement = document.createElement("a"); 769 urlElement.className = "console-message-url webkit-html-resource-link"; 770 urlElement.href = this.url; 771 urlElement.lineNumber = this.line; 772 773 if (this.source === WebInspector.ConsoleMessage.MessageSource.JS) 774 urlElement.preferredPanel = "scripts"; 775 776 if (this.line > 0) 777 urlElement.textContent = WebInspector.displayNameForURL(this.url) + ":" + this.line; 778 else 779 urlElement.textContent = WebInspector.displayNameForURL(this.url); 780 781 element.appendChild(urlElement); 782 } 783 784 var messageTextElement = document.createElement("span"); 785 messageTextElement.className = "console-message-text source-code"; 786 if (this.type === WebInspector.ConsoleMessage.MessageType.Assert) 787 messageTextElement.appendChild(document.createTextNode(WebInspector.UIString("Assertion failed: "))); 788 messageTextElement.appendChild(this.formattedMessage); 789 element.appendChild(messageTextElement); 790 791 if (this.repeatCount > 1) 792 this._updateRepeatCount(); 793 794 return element; 795 }, 796 797 _updateRepeatCount: function() { 798 if (!this.repeatCountElement) { 799 this.repeatCountElement = document.createElement("span"); 800 this.repeatCountElement.className = "bubble"; 801 802 this._element.insertBefore(this.repeatCountElement, this._element.firstChild); 803 this._element.addStyleClass("repeated-message"); 804 } 805 this.repeatCountElement.textContent = this.repeatCount; 806 }, 807 808 toString: function() 809 { 810 var sourceString; 811 switch (this.source) { 812 case WebInspector.ConsoleMessage.MessageSource.HTML: 813 sourceString = "HTML"; 814 break; 815 case WebInspector.ConsoleMessage.MessageSource.WML: 816 sourceString = "WML"; 817 break; 818 case WebInspector.ConsoleMessage.MessageSource.XML: 819 sourceString = "XML"; 820 break; 821 case WebInspector.ConsoleMessage.MessageSource.JS: 822 sourceString = "JS"; 823 break; 824 case WebInspector.ConsoleMessage.MessageSource.CSS: 825 sourceString = "CSS"; 826 break; 827 case WebInspector.ConsoleMessage.MessageSource.Other: 828 sourceString = "Other"; 829 break; 830 } 831 832 var typeString; 833 switch (this.type) { 834 case WebInspector.ConsoleMessage.MessageType.Log: 835 typeString = "Log"; 836 break; 837 case WebInspector.ConsoleMessage.MessageType.Object: 838 typeString = "Object"; 839 break; 840 case WebInspector.ConsoleMessage.MessageType.Trace: 841 typeString = "Trace"; 842 break; 843 case WebInspector.ConsoleMessage.MessageType.StartGroup: 844 typeString = "Start Group"; 845 break; 846 case WebInspector.ConsoleMessage.MessageType.EndGroup: 847 typeString = "End Group"; 848 break; 849 case WebInspector.ConsoleMessage.MessageType.Assert: 850 typeString = "Assert"; 851 break; 852 case WebInspector.ConsoleMessage.MessageType.Result: 853 typeString = "Result"; 854 break; 855 } 856 857 var levelString; 858 switch (this.level) { 859 case WebInspector.ConsoleMessage.MessageLevel.Tip: 860 levelString = "Tip"; 861 break; 862 case WebInspector.ConsoleMessage.MessageLevel.Log: 863 levelString = "Log"; 864 break; 865 case WebInspector.ConsoleMessage.MessageLevel.Warning: 866 levelString = "Warning"; 867 break; 868 case WebInspector.ConsoleMessage.MessageLevel.Debug: 869 levelString = "Debug"; 870 break; 871 case WebInspector.ConsoleMessage.MessageLevel.Error: 872 levelString = "Error"; 873 break; 874 } 875 876 return sourceString + " " + typeString + " " + levelString + ": " + this.formattedMessage.textContent + "\n" + this.url + " line " + this.line; 877 }, 878 879 isEqual: function(msg, disreguardGroup) 880 { 881 if (!msg) 882 return false; 883 884 var ret = (this.source == msg.source) 885 && (this.type == msg.type) 886 && (this.level == msg.level) 887 && (this.line == msg.line) 888 && (this.url == msg.url) 889 && (this.message == msg.message); 890 891 return (disreguardGroup ? ret : (ret && (this.groupLevel == msg.groupLevel))); 892 } 893 } 894 895 // Note: Keep these constants in sync with the ones in Console.h 896 WebInspector.ConsoleMessage.MessageSource = { 897 HTML: 0, 898 WML: 1, 899 XML: 2, 900 JS: 3, 901 CSS: 4, 902 Other: 5 903 } 904 905 WebInspector.ConsoleMessage.MessageType = { 906 Log: 0, 907 Object: 1, 908 Trace: 2, 909 StartGroup: 3, 910 EndGroup: 4, 911 Assert: 5, 912 Result: 6 913 } 914 915 WebInspector.ConsoleMessage.MessageLevel = { 916 Tip: 0, 917 Log: 1, 918 Warning: 2, 919 Error: 3, 920 Debug: 4 921 } 922 923 WebInspector.ConsoleCommand = function(command) 924 { 925 this.command = command; 926 } 927 928 WebInspector.ConsoleCommand.prototype = { 929 toMessageElement: function() 930 { 931 var element = document.createElement("div"); 932 element.command = this; 933 element.className = "console-user-command"; 934 935 var commandTextElement = document.createElement("span"); 936 commandTextElement.className = "console-message-text source-code"; 937 commandTextElement.textContent = this.command; 938 element.appendChild(commandTextElement); 939 940 return element; 941 } 942 } 943 944 WebInspector.ConsoleTextMessage = function(text, level) 945 { 946 level = level || WebInspector.ConsoleMessage.MessageLevel.Log; 947 WebInspector.ConsoleMessage.call(this, WebInspector.ConsoleMessage.MessageSource.JS, WebInspector.ConsoleMessage.MessageType.Log, level, 0, null, null, 1, text); 948 } 949 950 WebInspector.ConsoleTextMessage.prototype.__proto__ = WebInspector.ConsoleMessage.prototype; 951 952 WebInspector.ConsoleCommandResult = function(result, exception, originatingCommand) 953 { 954 var level = (exception ? WebInspector.ConsoleMessage.MessageLevel.Error : WebInspector.ConsoleMessage.MessageLevel.Log); 955 var message = result; 956 if (exception) { 957 // Distinguish between strings and errors (no need to quote latter). 958 message = WebInspector.ObjectProxy.wrapPrimitiveValue(result); 959 message.type = "error"; 960 } 961 var line = (exception ? result.line : -1); 962 var url = (exception ? result.sourceURL : null); 963 964 this.originatingCommand = originatingCommand; 965 966 WebInspector.ConsoleMessage.call(this, WebInspector.ConsoleMessage.MessageSource.JS, WebInspector.ConsoleMessage.MessageType.Result, level, line, url, null, 1, message); 967 } 968 969 WebInspector.ConsoleCommandResult.prototype = { 970 toMessageElement: function() 971 { 972 var element = WebInspector.ConsoleMessage.prototype.toMessageElement.call(this); 973 element.addStyleClass("console-user-command-result"); 974 return element; 975 } 976 } 977 978 WebInspector.ConsoleCommandResult.prototype.__proto__ = WebInspector.ConsoleMessage.prototype; 979 980 WebInspector.ConsoleGroup = function(parentGroup, level) 981 { 982 this.parentGroup = parentGroup; 983 this.level = level; 984 985 var element = document.createElement("div"); 986 element.className = "console-group"; 987 element.group = this; 988 this.element = element; 989 990 var messagesElement = document.createElement("div"); 991 messagesElement.className = "console-group-messages"; 992 element.appendChild(messagesElement); 993 this.messagesElement = messagesElement; 994 } 995 996 WebInspector.ConsoleGroup.prototype = { 997 addMessage: function(msg) 998 { 999 var element = msg.toMessageElement(); 1000 1001 if (msg.type === WebInspector.ConsoleMessage.MessageType.StartGroup) { 1002 this.messagesElement.parentNode.insertBefore(element, this.messagesElement); 1003 element.addEventListener("click", this._titleClicked.bind(this), true); 1004 } else 1005 this.messagesElement.appendChild(element); 1006 1007 if (element.previousSibling && msg.originatingCommand && element.previousSibling.command === msg.originatingCommand) 1008 element.previousSibling.addStyleClass("console-adjacent-user-command-result"); 1009 }, 1010 1011 _titleClicked: function(event) 1012 { 1013 var groupTitleElement = event.target.enclosingNodeOrSelfWithClass("console-group-title"); 1014 if (groupTitleElement) { 1015 var groupElement = groupTitleElement.enclosingNodeOrSelfWithClass("console-group"); 1016 if (groupElement) 1017 if (groupElement.hasStyleClass("collapsed")) 1018 groupElement.removeStyleClass("collapsed"); 1019 else 1020 groupElement.addStyleClass("collapsed"); 1021 groupTitleElement.scrollIntoViewIfNeeded(true); 1022 } 1023 1024 event.stopPropagation(); 1025 event.preventDefault(); 1026 } 1027 } 1028