1 /* 2 * Copyright (C) 2011 Google Inc. All rights reserved. 3 * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. 4 * Copyright (C) 2009 Joseph Pecoraro 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of 16 * its contributors may be used to endorse or promote products derived 17 * from this software without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 20 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 23 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 24 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 26 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 28 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31 /** 32 * @constructor 33 * @implements {WebInspector.ViewportElement} 34 * @param {!WebInspector.ConsoleMessage} consoleMessage 35 * @param {?WebInspector.Linkifier} linkifier 36 * @param {number} nestingLevel 37 */ 38 WebInspector.ConsoleViewMessage = function(consoleMessage, linkifier, nestingLevel) 39 { 40 this._message = consoleMessage; 41 this._linkifier = linkifier; 42 this._repeatCount = 1; 43 this._closeGroupDecorationCount = 0; 44 this._nestingLevel = nestingLevel; 45 46 /** @type {!Array.<!WebInspector.DataGrid>} */ 47 this._dataGrids = []; 48 /** @type {!Map.<!WebInspector.DataGrid, ?Element>} */ 49 this._dataGridParents = new Map(); 50 51 /** @type {!Object.<string, function(!WebInspector.RemoteObject, !Element, boolean=)>} */ 52 this._customFormatters = { 53 "object": this._formatParameterAsObject, 54 "array": this._formatParameterAsArray, 55 "node": this._formatParameterAsNode, 56 "map": this._formatParameterAsObject, 57 "set": this._formatParameterAsObject, 58 "string": this._formatParameterAsString 59 }; 60 } 61 62 WebInspector.ConsoleViewMessage.prototype = { 63 /** 64 * @return {?WebInspector.Target} 65 */ 66 _target: function() 67 { 68 return this.consoleMessage().target(); 69 }, 70 71 /** 72 * @return {!Element} 73 */ 74 element: function() 75 { 76 return this.toMessageElement(); 77 }, 78 79 wasShown: function() 80 { 81 for (var i = 0; this._dataGrids && i < this._dataGrids.length; ++i) { 82 var dataGrid = this._dataGrids[i]; 83 var parentElement = this._dataGridParents.get(dataGrid) || null; 84 dataGrid.show(parentElement); 85 dataGrid.updateWidths(); 86 } 87 }, 88 89 cacheFastHeight: function() 90 { 91 this._cachedHeight = this.contentElement().offsetHeight; 92 }, 93 94 willHide: function() 95 { 96 for (var i = 0; this._dataGrids && i < this._dataGrids.length; ++i) { 97 var dataGrid = this._dataGrids[i]; 98 this._dataGridParents.set(dataGrid, dataGrid.element.parentElement); 99 dataGrid.detach(); 100 } 101 }, 102 103 /** 104 * @return {number} 105 */ 106 fastHeight: function() 107 { 108 if (this._cachedHeight) 109 return this._cachedHeight; 110 const defaultConsoleRowHeight = 16; 111 if (this._message.type === WebInspector.ConsoleMessage.MessageType.Table) { 112 var table = this._message.parameters[0]; 113 if (table && table.preview) 114 return defaultConsoleRowHeight * table.preview.properties.length; 115 } 116 return defaultConsoleRowHeight; 117 }, 118 119 /** 120 * @return {!WebInspector.ConsoleMessage} 121 */ 122 consoleMessage: function() 123 { 124 return this._message; 125 }, 126 127 _formatMessage: function() 128 { 129 this._formattedMessage = document.createElement("span"); 130 this._formattedMessage.className = "console-message-text source-code"; 131 132 /** 133 * @param {string} title 134 * @return {!Element} 135 * @this {WebInspector.ConsoleMessage} 136 */ 137 function linkifyRequest(title) 138 { 139 return WebInspector.Linkifier.linkifyUsingRevealer(/** @type {!WebInspector.NetworkRequest} */ (this.request), title, this.url); 140 } 141 142 var consoleMessage = this._message; 143 if (!this._messageElement) { 144 if (consoleMessage.source === WebInspector.ConsoleMessage.MessageSource.ConsoleAPI) { 145 switch (consoleMessage.type) { 146 case WebInspector.ConsoleMessage.MessageType.Trace: 147 this._messageElement = this._format(consoleMessage.parameters || ["console.trace()"]); 148 break; 149 case WebInspector.ConsoleMessage.MessageType.Clear: 150 this._messageElement = document.createTextNode(WebInspector.UIString("Console was cleared")); 151 this._formattedMessage.classList.add("console-info"); 152 break; 153 case WebInspector.ConsoleMessage.MessageType.Assert: 154 var args = [WebInspector.UIString("Assertion failed:")]; 155 if (consoleMessage.parameters) 156 args = args.concat(consoleMessage.parameters); 157 this._messageElement = this._format(args); 158 break; 159 case WebInspector.ConsoleMessage.MessageType.Dir: 160 var obj = consoleMessage.parameters ? consoleMessage.parameters[0] : undefined; 161 var args = ["%O", obj]; 162 this._messageElement = this._format(args); 163 break; 164 case WebInspector.ConsoleMessage.MessageType.Profile: 165 case WebInspector.ConsoleMessage.MessageType.ProfileEnd: 166 this._messageElement = this._format([consoleMessage.messageText]); 167 break; 168 default: 169 var args = consoleMessage.parameters || [consoleMessage.messageText]; 170 this._messageElement = this._format(args); 171 } 172 } else if (consoleMessage.source === WebInspector.ConsoleMessage.MessageSource.Network) { 173 if (consoleMessage.request) { 174 this._messageElement = document.createElement("span"); 175 if (consoleMessage.level === WebInspector.ConsoleMessage.MessageLevel.Error) { 176 this._messageElement.createTextChildren(consoleMessage.request.requestMethod, " "); 177 this._messageElement.appendChild(WebInspector.Linkifier.linkifyUsingRevealer(consoleMessage.request, consoleMessage.request.url, consoleMessage.request.url)); 178 if (consoleMessage.request.failed) 179 this._messageElement.createTextChildren(" ", consoleMessage.request.localizedFailDescription); 180 else 181 this._messageElement.createTextChildren(" ", String(consoleMessage.request.statusCode), " (", consoleMessage.request.statusText, ")"); 182 } else { 183 var fragment = WebInspector.linkifyStringAsFragmentWithCustomLinkifier(consoleMessage.messageText, linkifyRequest.bind(consoleMessage)); 184 this._messageElement.appendChild(fragment); 185 } 186 } else { 187 var url = consoleMessage.url; 188 if (url) { 189 var isExternal = !WebInspector.resourceForURL(url) && !WebInspector.workspace.uiSourceCodeForURL(url); 190 this._anchorElement = WebInspector.linkifyURLAsNode(url, url, "console-message-url", isExternal); 191 } 192 this._messageElement = this._format([consoleMessage.messageText]); 193 } 194 } else { 195 var args = consoleMessage.parameters || [consoleMessage.messageText]; 196 this._messageElement = this._format(args); 197 } 198 } 199 200 if (consoleMessage.source !== WebInspector.ConsoleMessage.MessageSource.Network || consoleMessage.request) { 201 if (consoleMessage.scriptId) { 202 this._anchorElement = this._linkifyScriptId(consoleMessage.scriptId, consoleMessage.url || "", consoleMessage.line, consoleMessage.column); 203 } else { 204 var useBlackboxing = (consoleMessage.source === WebInspector.ConsoleMessage.MessageSource.ConsoleAPI); 205 var callFrame = this._callFrameAnchorFromStackTrace(consoleMessage.stackTrace, useBlackboxing); 206 if (callFrame) 207 this._anchorElement = this._linkifyCallFrame(callFrame); 208 else if (consoleMessage.url && consoleMessage.url !== "undefined") 209 this._anchorElement = this._linkifyLocation(consoleMessage.url, consoleMessage.line, consoleMessage.column); 210 } 211 } 212 213 this._formattedMessage.appendChild(this._messageElement); 214 if (this._anchorElement) { 215 this._formattedMessage.insertBefore(document.createTextNode(" "), this._formattedMessage.firstChild); 216 this._formattedMessage.insertBefore(this._anchorElement, this._formattedMessage.firstChild); 217 } 218 219 var dumpStackTrace = !!consoleMessage.stackTrace && consoleMessage.stackTrace.length && (consoleMessage.source === WebInspector.ConsoleMessage.MessageSource.Network || consoleMessage.level === WebInspector.ConsoleMessage.MessageLevel.Error || consoleMessage.type === WebInspector.ConsoleMessage.MessageType.Trace); 220 if (dumpStackTrace) { 221 var ol = document.createElement("ol"); 222 ol.className = "outline-disclosure"; 223 var treeOutline = new TreeOutline(ol); 224 225 var content = this._formattedMessage; 226 var root = new TreeElement(content, null, true); 227 root.toggleOnClick = true; 228 root.selectable = false; 229 content.treeElementForTest = root; 230 treeOutline.appendChild(root); 231 if (consoleMessage.type === WebInspector.ConsoleMessage.MessageType.Trace) 232 root.expand(); 233 234 this._populateStackTraceTreeElement(root); 235 this._formattedMessage = ol; 236 } 237 }, 238 239 _formattedMessageText: function() 240 { 241 this.formattedMessage(); 242 return this._messageElement.textContent; 243 }, 244 245 /** 246 * @return {!Element} 247 */ 248 formattedMessage: function() 249 { 250 if (!this._formattedMessage) 251 this._formatMessage(); 252 return this._formattedMessage; 253 }, 254 255 /** 256 * @param {string} url 257 * @param {number} lineNumber 258 * @param {number} columnNumber 259 * @return {?Element} 260 */ 261 _linkifyLocation: function(url, lineNumber, columnNumber) 262 { 263 console.assert(this._linkifier); 264 var target = this._target(); 265 if (!this._linkifier || !target) 266 return null; 267 // FIXME(62725): stack trace line/column numbers are one-based. 268 lineNumber = lineNumber ? lineNumber - 1 : 0; 269 columnNumber = columnNumber ? columnNumber - 1 : 0; 270 if (this._message.source === WebInspector.ConsoleMessage.MessageSource.CSS) { 271 var headerIds = target.cssModel.styleSheetIdsForURL(url); 272 var cssLocation = new WebInspector.CSSLocation(target, headerIds[0] || null, url, lineNumber, columnNumber); 273 return this._linkifier.linkifyCSSLocation(cssLocation, "console-message-url"); 274 } 275 276 return this._linkifier.linkifyScriptLocation(target, null, url, lineNumber, columnNumber, "console-message-url"); 277 }, 278 279 /** 280 * @param {!ConsoleAgent.CallFrame} callFrame 281 * @return {?Element} 282 */ 283 _linkifyCallFrame: function(callFrame) 284 { 285 console.assert(this._linkifier); 286 var target = this._target(); 287 if (!this._linkifier) 288 return null; 289 290 return this._linkifier.linkifyConsoleCallFrame(target, callFrame, "console-message-url"); 291 }, 292 293 /** 294 * @param {string} scriptId 295 * @param {string} url 296 * @param {number} lineNumber 297 * @param {number} columnNumber 298 * @return {?Element} 299 */ 300 _linkifyScriptId: function(scriptId, url, lineNumber, columnNumber) 301 { 302 console.assert(this._linkifier); 303 var target = this._target(); 304 if (!this._linkifier || !target) 305 return null; 306 // FIXME(62725): stack trace line/column numbers are one-based. 307 lineNumber = lineNumber ? lineNumber - 1 : 0; 308 columnNumber = columnNumber ? columnNumber - 1 : 0; 309 return this._linkifier.linkifyScriptLocation(target, scriptId, url, lineNumber, columnNumber, "console-message-url"); 310 }, 311 312 /** 313 * @param {?Array.<!ConsoleAgent.CallFrame>|undefined} stackTrace 314 * @param {boolean} useBlackboxing 315 * @return {?ConsoleAgent.CallFrame} 316 */ 317 _callFrameAnchorFromStackTrace: function(stackTrace, useBlackboxing) 318 { 319 if (!stackTrace || !stackTrace.length) 320 return null; 321 var callFrame = stackTrace[0].scriptId ? stackTrace[0] : null; 322 if (!useBlackboxing) 323 return callFrame; 324 var target = this._target(); 325 for (var i = 0; i < stackTrace.length; ++i) { 326 var script = target && target.debuggerModel.scriptForId(stackTrace[i].scriptId); 327 var blackboxed = script ? 328 WebInspector.BlackboxSupport.isBlackboxed(script.sourceURL, script.isContentScript()) : 329 WebInspector.BlackboxSupport.isBlackboxedURL(stackTrace[i].url); 330 if (!blackboxed) 331 return stackTrace[i].scriptId ? stackTrace[i] : null; 332 } 333 return callFrame; 334 }, 335 336 /** 337 * @return {boolean} 338 */ 339 isErrorOrWarning: function() 340 { 341 return (this._message.level === WebInspector.ConsoleMessage.MessageLevel.Warning || this._message.level === WebInspector.ConsoleMessage.MessageLevel.Error); 342 }, 343 344 _format: function(parameters) 345 { 346 // This node is used like a Builder. Values are continually appended onto it. 347 var formattedResult = document.createElement("span"); 348 if (!parameters.length) 349 return formattedResult; 350 351 var target = this._target(); 352 353 // Formatting code below assumes that parameters are all wrappers whereas frontend console 354 // API allows passing arbitrary values as messages (strings, numbers, etc.). Wrap them here. 355 for (var i = 0; i < parameters.length; ++i) { 356 // FIXME: Only pass runtime wrappers here. 357 if (parameters[i] instanceof WebInspector.RemoteObject) 358 continue; 359 360 if (!target) { 361 parameters[i] = WebInspector.RemoteObject.fromLocalObject(parameters[i]); 362 continue; 363 } 364 365 if (typeof parameters[i] === "object") 366 parameters[i] = target.runtimeModel.createRemoteObject(parameters[i]); 367 else 368 parameters[i] = target.runtimeModel.createRemoteObjectFromPrimitiveValue(parameters[i]); 369 } 370 371 // There can be string log and string eval result. We distinguish between them based on message type. 372 var shouldFormatMessage = WebInspector.RemoteObject.type(parameters[0]) === "string" && (this._message.type !== WebInspector.ConsoleMessage.MessageType.Result || this._message.level === WebInspector.ConsoleMessage.MessageLevel.Error); 373 374 // Multiple parameters with the first being a format string. Save unused substitutions. 375 if (shouldFormatMessage) { 376 // Multiple parameters with the first being a format string. Save unused substitutions. 377 var result = this._formatWithSubstitutionString(parameters[0].description, parameters.slice(1), formattedResult); 378 parameters = result.unusedSubstitutions; 379 if (parameters.length) 380 formattedResult.createTextChild(" "); 381 } 382 383 if (this._message.type === WebInspector.ConsoleMessage.MessageType.Table) { 384 formattedResult.appendChild(this._formatParameterAsTable(parameters)); 385 return formattedResult; 386 } 387 388 // Single parameter, or unused substitutions from above. 389 for (var i = 0; i < parameters.length; ++i) { 390 // Inline strings when formatting. 391 if (shouldFormatMessage && parameters[i].type === "string") 392 formattedResult.appendChild(WebInspector.linkifyStringAsFragment(parameters[i].description)); 393 else 394 formattedResult.appendChild(this._formatParameter(parameters[i], false, true)); 395 if (i < parameters.length - 1) 396 formattedResult.createTextChild(" "); 397 } 398 return formattedResult; 399 }, 400 401 /** 402 * @param {!WebInspector.RemoteObject} output 403 * @param {boolean=} forceObjectFormat 404 * @param {boolean=} includePreview 405 * @return {!Element} 406 */ 407 _formatParameter: function(output, forceObjectFormat, includePreview) 408 { 409 var type = forceObjectFormat ? "object" : (output.subtype || output.type); 410 var formatter = this._customFormatters[type] || this._formatParameterAsValue; 411 var span = document.createElement("span"); 412 span.className = "console-formatted-" + type + " source-code"; 413 formatter.call(this, output, span, includePreview); 414 return span; 415 }, 416 417 /** 418 * @param {!WebInspector.RemoteObject} obj 419 * @param {!Element} elem 420 */ 421 _formatParameterAsValue: function(obj, elem) 422 { 423 elem.createTextChild(obj.description || ""); 424 if (obj.objectId) 425 elem.addEventListener("contextmenu", this._contextMenuEventFired.bind(this, obj), false); 426 }, 427 428 /** 429 * @param {!WebInspector.RemoteObject} obj 430 * @param {!Element} elem 431 * @param {boolean=} includePreview 432 */ 433 _formatParameterAsObject: function(obj, elem, includePreview) 434 { 435 this._formatParameterAsArrayOrObject(obj, elem, includePreview); 436 }, 437 438 /** 439 * @param {!WebInspector.RemoteObject} obj 440 * @param {!Element} elem 441 * @param {boolean=} includePreview 442 */ 443 _formatParameterAsArrayOrObject: function(obj, elem, includePreview) 444 { 445 var titleElement = document.createElement("span"); 446 if (includePreview && obj.preview) { 447 titleElement.classList.add("console-object-preview"); 448 var lossless = this._appendObjectPreview(titleElement, obj.preview, obj); 449 if (lossless) { 450 elem.appendChild(titleElement); 451 titleElement.addEventListener("contextmenu", this._contextMenuEventFired.bind(this, obj), false); 452 return; 453 } 454 } else { 455 titleElement.createTextChild(obj.description || ""); 456 } 457 var section = new WebInspector.ObjectPropertiesSection(obj, titleElement); 458 section.enableContextMenu(); 459 elem.appendChild(section.element); 460 461 var note = section.titleElement.createChild("span", "object-info-state-note"); 462 note.title = WebInspector.UIString("Object state below is captured upon first expansion"); 463 }, 464 465 /** 466 * @param {!WebInspector.RemoteObject} obj 467 * @param {!Event} event 468 */ 469 _contextMenuEventFired: function(obj, event) 470 { 471 var contextMenu = new WebInspector.ContextMenu(event); 472 contextMenu.appendApplicableItems(obj); 473 contextMenu.show(); 474 }, 475 476 /** 477 * @param {!Element} parentElement 478 * @param {!RuntimeAgent.ObjectPreview} preview 479 * @param {?WebInspector.RemoteObject} object 480 * @return {boolean} true iff preview captured all information. 481 */ 482 _appendObjectPreview: function(parentElement, preview, object) 483 { 484 var description = preview.description; 485 if (preview.type !== "object" || preview.subtype === "null") { 486 parentElement.appendChild(this._renderPropertyPreview(preview.type, preview.subtype, description)); 487 return true; 488 } 489 if (description && preview.subtype !== "array") 490 parentElement.createTextChildren(description, " "); 491 if (preview.entries) 492 return this._appendEntriesPreview(parentElement, preview); 493 return this._appendPropertiesPreview(parentElement, preview, object); 494 }, 495 496 /** 497 * @param {!Element} parentElement 498 * @param {!RuntimeAgent.ObjectPreview} preview 499 * @param {?WebInspector.RemoteObject} object 500 * @return {boolean} true iff preview captured all information. 501 */ 502 _appendPropertiesPreview: function(parentElement, preview, object) 503 { 504 var isArray = preview.subtype === "array"; 505 parentElement.createTextChild(isArray ? "[" : "{"); 506 for (var i = 0; i < preview.properties.length; ++i) { 507 if (i > 0) 508 parentElement.createTextChild(", "); 509 510 var property = preview.properties[i]; 511 var name = property.name; 512 if (!isArray || name != i) { 513 if (/^\s|\s$|^$|\n/.test(name)) 514 parentElement.createChild("span", "name").createTextChildren("\"", name.replace(/\n/g, "\u21B5"), "\""); 515 else 516 parentElement.createChild("span", "name").textContent = name; 517 parentElement.createTextChild(": "); 518 } 519 520 parentElement.appendChild(this._renderPropertyPreviewOrAccessor(object, [property])); 521 } 522 if (preview.overflow) 523 parentElement.createChild("span").textContent = "\u2026"; 524 parentElement.createTextChild(isArray ? "]" : "}"); 525 return preview.lossless; 526 }, 527 528 /** 529 * @param {!Element} parentElement 530 * @param {!RuntimeAgent.ObjectPreview} preview 531 * @return {boolean} true iff preview captured all information. 532 */ 533 _appendEntriesPreview: function(parentElement, preview) 534 { 535 var lossless = preview.lossless && !preview.properties.length; 536 parentElement.createTextChild("{"); 537 for (var i = 0; i < preview.entries.length; ++i) { 538 if (i > 0) 539 parentElement.createTextChild(", "); 540 541 var entry = preview.entries[i]; 542 if (entry.key) { 543 this._appendObjectPreview(parentElement, entry.key, null); 544 parentElement.createTextChild(" => "); 545 } 546 this._appendObjectPreview(parentElement, entry.value, null); 547 } 548 if (preview.overflow) 549 parentElement.createChild("span").textContent = "\u2026"; 550 parentElement.createTextChild("}"); 551 return lossless; 552 }, 553 554 /** 555 * @param {?WebInspector.RemoteObject} object 556 * @param {!Array.<!RuntimeAgent.PropertyPreview>} propertyPath 557 * @return {!Element} 558 */ 559 _renderPropertyPreviewOrAccessor: function(object, propertyPath) 560 { 561 var property = propertyPath.peekLast(); 562 if (property.type === "accessor") 563 return this._formatAsAccessorProperty(object, propertyPath.select("name"), false); 564 return this._renderPropertyPreview(property.type, /** @type {string} */ (property.subtype), property.value); 565 }, 566 567 /** 568 * @param {string} type 569 * @param {string=} subtype 570 * @param {string=} description 571 * @return {!Element} 572 */ 573 _renderPropertyPreview: function(type, subtype, description) 574 { 575 var span = document.createElementWithClass("span", "console-formatted-" + (subtype || type)); 576 description = description || ""; 577 578 if (type === "function") { 579 span.textContent = "function"; 580 return span; 581 } 582 583 if (type === "object" && subtype === "node" && description) { 584 span.classList.add("console-formatted-preview-node"); 585 WebInspector.DOMPresentationUtils.createSpansForNodeTitle(span, description); 586 return span; 587 } 588 589 if (type === "string") { 590 span.createTextChildren("\"", description.replace(/\n/g, "\u21B5"), "\""); 591 return span; 592 } 593 594 span.textContent = description; 595 return span; 596 }, 597 598 /** 599 * @param {!WebInspector.RemoteObject} object 600 * @param {!Element} elem 601 */ 602 _formatParameterAsNode: function(object, elem) 603 { 604 /** 605 * @param {!WebInspector.DOMNode} node 606 * @this {WebInspector.ConsoleViewMessage} 607 */ 608 function printNode(node) 609 { 610 if (!node) { 611 // Sometimes DOM is loaded after the sync message is being formatted, so we get no 612 // nodeId here. So we fall back to object formatting here. 613 this._formatParameterAsObject(object, elem, false); 614 return; 615 } 616 var renderer = self.runtime.instance(WebInspector.Renderer, node); 617 if (renderer) 618 elem.appendChild(renderer.render(node)); 619 else 620 console.error("No renderer for node found"); 621 } 622 object.pushNodeToFrontend(printNode.bind(this)); 623 }, 624 625 /** 626 * @param {!WebInspector.RemoteObject} array 627 * @return {boolean} 628 */ 629 useArrayPreviewInFormatter: function(array) 630 { 631 return this._message.type !== WebInspector.ConsoleMessage.MessageType.DirXML && !!array.preview; 632 }, 633 634 /** 635 * @param {!WebInspector.RemoteObject} array 636 * @param {!Element} elem 637 */ 638 _formatParameterAsArray: function(array, elem) 639 { 640 if (this.useArrayPreviewInFormatter(array)) { 641 this._formatParameterAsArrayOrObject(array, elem, true); 642 return; 643 } 644 645 const maxFlatArrayLength = 100; 646 if (this._message.isOutdated || array.arrayLength() > maxFlatArrayLength) 647 this._formatParameterAsObject(array, elem, false); 648 else 649 array.getOwnProperties(this._printArray.bind(this, array, elem)); 650 }, 651 652 /** 653 * @param {!Array.<!WebInspector.RemoteObject>} parameters 654 * @return {!Element} 655 */ 656 _formatParameterAsTable: function(parameters) 657 { 658 var element = document.createElement("span"); 659 var table = parameters[0]; 660 if (!table || !table.preview) 661 return element; 662 663 var columnNames = []; 664 var preview = table.preview; 665 var rows = []; 666 for (var i = 0; i < preview.properties.length; ++i) { 667 var rowProperty = preview.properties[i]; 668 var rowPreview = rowProperty.valuePreview; 669 if (!rowPreview) 670 continue; 671 672 var rowValue = {}; 673 const maxColumnsToRender = 20; 674 for (var j = 0; j < rowPreview.properties.length; ++j) { 675 var cellProperty = rowPreview.properties[j]; 676 var columnRendered = columnNames.indexOf(cellProperty.name) != -1; 677 if (!columnRendered) { 678 if (columnNames.length === maxColumnsToRender) 679 continue; 680 columnRendered = true; 681 columnNames.push(cellProperty.name); 682 } 683 684 if (columnRendered) { 685 var cellElement = this._renderPropertyPreviewOrAccessor(table, [rowProperty, cellProperty]); 686 cellElement.classList.add("nowrap-below"); 687 rowValue[cellProperty.name] = cellElement; 688 } 689 } 690 rows.push([rowProperty.name, rowValue]); 691 } 692 693 var flatValues = []; 694 for (var i = 0; i < rows.length; ++i) { 695 var rowName = rows[i][0]; 696 var rowValue = rows[i][1]; 697 flatValues.push(rowName); 698 for (var j = 0; j < columnNames.length; ++j) 699 flatValues.push(rowValue[columnNames[j]]); 700 } 701 702 var dataGridContainer = element.createChild("span"); 703 if (!preview.lossless || !flatValues.length) { 704 element.appendChild(this._formatParameter(table, true, false)); 705 if (!flatValues.length) 706 return element; 707 } 708 709 columnNames.unshift(WebInspector.UIString("(index)")); 710 var dataGrid = WebInspector.SortableDataGrid.create(columnNames, flatValues); 711 dataGrid.renderInline(); 712 this._dataGrids.push(dataGrid); 713 this._dataGridParents.set(dataGrid, dataGridContainer); 714 return element; 715 }, 716 717 /** 718 * @param {!WebInspector.RemoteObject} output 719 * @param {!Element} elem 720 */ 721 _formatParameterAsString: function(output, elem) 722 { 723 var span = document.createElement("span"); 724 span.className = "console-formatted-string source-code"; 725 span.appendChild(WebInspector.linkifyStringAsFragment(output.description || "")); 726 727 // Make black quotes. 728 elem.classList.remove("console-formatted-string"); 729 elem.createTextChild("\""); 730 elem.appendChild(span); 731 elem.createTextChild("\""); 732 }, 733 734 /** 735 * @param {!WebInspector.RemoteObject} array 736 * @param {!Element} elem 737 * @param {?Array.<!WebInspector.RemoteObjectProperty>} properties 738 */ 739 _printArray: function(array, elem, properties) 740 { 741 if (!properties) 742 return; 743 744 var elements = []; 745 for (var i = 0; i < properties.length; ++i) { 746 var property = properties[i]; 747 var name = property.name; 748 if (isNaN(name)) 749 continue; 750 if (property.getter) 751 elements[name] = this._formatAsAccessorProperty(array, [name], true); 752 else if (property.value) 753 elements[name] = this._formatAsArrayEntry(property.value); 754 } 755 756 elem.createTextChild("["); 757 var lastNonEmptyIndex = -1; 758 759 function appendUndefined(elem, index) 760 { 761 if (index - lastNonEmptyIndex <= 1) 762 return; 763 var span = elem.createChild("span", "console-formatted-undefined"); 764 span.textContent = WebInspector.UIString("undefined %d", index - lastNonEmptyIndex - 1); 765 } 766 767 var length = array.arrayLength(); 768 for (var i = 0; i < length; ++i) { 769 var element = elements[i]; 770 if (!element) 771 continue; 772 773 if (i - lastNonEmptyIndex > 1) { 774 appendUndefined(elem, i); 775 elem.createTextChild(", "); 776 } 777 778 elem.appendChild(element); 779 lastNonEmptyIndex = i; 780 if (i < length - 1) 781 elem.createTextChild(", "); 782 } 783 appendUndefined(elem, length); 784 785 elem.createTextChild("]"); 786 elem.addEventListener("contextmenu", this._contextMenuEventFired.bind(this, array), false); 787 }, 788 789 /** 790 * @param {!WebInspector.RemoteObject} output 791 * @return {!Element} 792 */ 793 _formatAsArrayEntry: function(output) 794 { 795 // Prevent infinite expansion of cross-referencing arrays. 796 return this._formatParameter(output, output.subtype === "array", false); 797 }, 798 799 /** 800 * @param {?WebInspector.RemoteObject} object 801 * @param {!Array.<string>} propertyPath 802 * @param {boolean} isArrayEntry 803 * @return {!Element} 804 */ 805 _formatAsAccessorProperty: function(object, propertyPath, isArrayEntry) 806 { 807 var rootElement = WebInspector.ObjectPropertyTreeElement.createRemoteObjectAccessorPropertySpan(object, propertyPath, onInvokeGetterClick.bind(this)); 808 809 /** 810 * @param {?WebInspector.RemoteObject} result 811 * @param {boolean=} wasThrown 812 * @this {WebInspector.ConsoleViewMessage} 813 */ 814 function onInvokeGetterClick(result, wasThrown) 815 { 816 if (!result) 817 return; 818 rootElement.removeChildren(); 819 if (wasThrown) { 820 var element = rootElement.createChild("span", "error-message"); 821 element.textContent = WebInspector.UIString("<exception>"); 822 element.title = /** @type {string} */ (result.description); 823 } else if (isArrayEntry) { 824 rootElement.appendChild(this._formatAsArrayEntry(result)); 825 } else { 826 // Make a PropertyPreview from the RemoteObject similar to the backend logic. 827 const maxLength = 100; 828 var type = result.type; 829 var subtype = result.subtype; 830 var description = ""; 831 if (type !== "function" && result.description) { 832 if (type === "string" || subtype === "regexp") 833 description = result.description.trimMiddle(maxLength); 834 else 835 description = result.description.trimEnd(maxLength); 836 } 837 rootElement.appendChild(this._renderPropertyPreview(type, subtype, description)); 838 } 839 } 840 841 return rootElement; 842 }, 843 844 /** 845 * @param {string} format 846 * @param {!Array.<string>} parameters 847 * @param {!Element} formattedResult 848 */ 849 _formatWithSubstitutionString: function(format, parameters, formattedResult) 850 { 851 var formatters = {}; 852 853 /** 854 * @param {boolean} force 855 * @param {!WebInspector.RemoteObject} obj 856 * @return {!Element} 857 * @this {WebInspector.ConsoleViewMessage} 858 */ 859 function parameterFormatter(force, obj) 860 { 861 return this._formatParameter(obj, force, false); 862 } 863 864 function stringFormatter(obj) 865 { 866 return obj.description; 867 } 868 869 function floatFormatter(obj) 870 { 871 if (typeof obj.value !== "number") 872 return "NaN"; 873 return obj.value; 874 } 875 876 function integerFormatter(obj) 877 { 878 if (typeof obj.value !== "number") 879 return "NaN"; 880 return Math.floor(obj.value); 881 } 882 883 function bypassFormatter(obj) 884 { 885 return (obj instanceof Node) ? obj : ""; 886 } 887 888 var currentStyle = null; 889 function styleFormatter(obj) 890 { 891 currentStyle = {}; 892 var buffer = document.createElement("span"); 893 buffer.setAttribute("style", obj.description); 894 for (var i = 0; i < buffer.style.length; i++) { 895 var property = buffer.style[i]; 896 if (isWhitelistedProperty(property)) 897 currentStyle[property] = buffer.style[property]; 898 } 899 } 900 901 function isWhitelistedProperty(property) 902 { 903 var prefixes = ["background", "border", "color", "font", "line", "margin", "padding", "text", "-webkit-background", "-webkit-border", "-webkit-font", "-webkit-margin", "-webkit-padding", "-webkit-text"]; 904 for (var i = 0; i < prefixes.length; i++) { 905 if (property.startsWith(prefixes[i])) 906 return true; 907 } 908 return false; 909 } 910 911 // Firebug uses %o for formatting objects. 912 formatters.o = parameterFormatter.bind(this, false); 913 formatters.s = stringFormatter; 914 formatters.f = floatFormatter; 915 // Firebug allows both %i and %d for formatting integers. 916 formatters.i = integerFormatter; 917 formatters.d = integerFormatter; 918 919 // Firebug uses %c for styling the message. 920 formatters.c = styleFormatter; 921 922 // Support %O to force object formatting, instead of the type-based %o formatting. 923 formatters.O = parameterFormatter.bind(this, true); 924 925 formatters._ = bypassFormatter; 926 927 function append(a, b) 928 { 929 if (b instanceof Node) 930 a.appendChild(b); 931 else if (typeof b !== "undefined") { 932 var toAppend = WebInspector.linkifyStringAsFragment(String(b)); 933 if (currentStyle) { 934 var wrapper = document.createElement('span'); 935 for (var key in currentStyle) 936 wrapper.style[key] = currentStyle[key]; 937 wrapper.appendChild(toAppend); 938 toAppend = wrapper; 939 } 940 a.appendChild(toAppend); 941 } 942 return a; 943 } 944 945 // String.format does treat formattedResult like a Builder, result is an object. 946 return String.format(format, parameters, formatters, formattedResult, append); 947 }, 948 949 clearHighlight: function() 950 { 951 if (!this._formattedMessage) 952 return; 953 954 var highlightedMessage = this._formattedMessage; 955 delete this._formattedMessage; 956 delete this._anchorElement; 957 delete this._messageElement; 958 this._formatMessage(); 959 this._element.replaceChild(this._formattedMessage, highlightedMessage); 960 }, 961 962 highlightSearchResults: function(regexObject) 963 { 964 if (!this._formattedMessage) 965 return; 966 967 this._highlightSearchResultsInElement(regexObject, this._messageElement); 968 if (this._anchorElement) 969 this._highlightSearchResultsInElement(regexObject, this._anchorElement); 970 }, 971 972 _highlightSearchResultsInElement: function(regexObject, element) 973 { 974 regexObject.lastIndex = 0; 975 var text = element.textContent; 976 var match = regexObject.exec(text); 977 var matchRanges = []; 978 while (match) { 979 matchRanges.push(new WebInspector.SourceRange(match.index, match[0].length)); 980 match = regexObject.exec(text); 981 } 982 WebInspector.highlightSearchResults(element, matchRanges); 983 }, 984 985 /** 986 * @return {boolean} 987 */ 988 matchesRegex: function(regexObject) 989 { 990 regexObject.lastIndex = 0; 991 return regexObject.test(this._formattedMessageText()) || (!!this._anchorElement && regexObject.test(this._anchorElement.textContent)); 992 }, 993 994 /** 995 * @param {boolean} show 996 */ 997 updateTimestamp: function(show) 998 { 999 if (!this._element) 1000 return; 1001 1002 if (show && !this.timestampElement) { 1003 this.timestampElement = this._element.createChild("span", "console-timestamp"); 1004 this.timestampElement.textContent = (new Date(this._message.timestamp)).toConsoleTime(); 1005 var afterRepeatCountChild = this._repeatCountElement && this._repeatCountElement.nextSibling; 1006 this._element.insertBefore(this.timestampElement, afterRepeatCountChild || this._element.firstChild); 1007 return; 1008 } 1009 1010 if (!show && this.timestampElement) { 1011 this.timestampElement.remove(); 1012 delete this.timestampElement; 1013 } 1014 }, 1015 1016 /** 1017 * @return {number} 1018 */ 1019 nestingLevel: function() 1020 { 1021 return this._nestingLevel; 1022 }, 1023 1024 resetCloseGroupDecorationCount: function() 1025 { 1026 this._closeGroupDecorationCount = 0; 1027 this._updateCloseGroupDecorations(); 1028 }, 1029 1030 incrementCloseGroupDecorationCount: function() 1031 { 1032 ++this._closeGroupDecorationCount; 1033 this._updateCloseGroupDecorations(); 1034 }, 1035 1036 _updateCloseGroupDecorations: function() 1037 { 1038 if (!this._nestingLevelMarkers) 1039 return; 1040 for (var i = 0, n = this._nestingLevelMarkers.length; i < n; ++i) { 1041 var marker = this._nestingLevelMarkers[i]; 1042 marker.classList.toggle("group-closed", n - i <= this._closeGroupDecorationCount); 1043 } 1044 }, 1045 1046 /** 1047 * @return {!Element} 1048 */ 1049 contentElement: function() 1050 { 1051 if (this._element) 1052 return this._element; 1053 1054 var element = document.createElementWithClass("div", "console-message"); 1055 this._element = element; 1056 1057 switch (this._message.level) { 1058 case WebInspector.ConsoleMessage.MessageLevel.Log: 1059 element.classList.add("console-log-level"); 1060 break; 1061 case WebInspector.ConsoleMessage.MessageLevel.Debug: 1062 element.classList.add("console-debug-level"); 1063 break; 1064 case WebInspector.ConsoleMessage.MessageLevel.Warning: 1065 element.classList.add("console-warning-level"); 1066 break; 1067 case WebInspector.ConsoleMessage.MessageLevel.Error: 1068 element.classList.add("console-error-level"); 1069 break; 1070 case WebInspector.ConsoleMessage.MessageLevel.Info: 1071 element.classList.add("console-info-level"); 1072 break; 1073 } 1074 1075 if (this._message.type === WebInspector.ConsoleMessage.MessageType.StartGroup || this._message.type === WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed) 1076 element.classList.add("console-group-title"); 1077 1078 element.appendChild(this.formattedMessage()); 1079 1080 if (this._repeatCount > 1) 1081 this._showRepeatCountElement(); 1082 1083 this.updateTimestamp(WebInspector.settings.consoleTimestampsEnabled.get()); 1084 1085 return this._element; 1086 }, 1087 1088 /** 1089 * @return {!Element} 1090 */ 1091 toMessageElement: function() 1092 { 1093 if (this._wrapperElement) 1094 return this._wrapperElement; 1095 1096 this._wrapperElement = document.createElementWithClass("div", "console-message-wrapper"); 1097 this._nestingLevelMarkers = []; 1098 for (var i = 0; i < this._nestingLevel; ++i) 1099 this._nestingLevelMarkers.push(this._wrapperElement.createChild("div", "nesting-level-marker")); 1100 this._updateCloseGroupDecorations(); 1101 this._wrapperElement.message = this; 1102 1103 this._wrapperElement.appendChild(this.contentElement()); 1104 return this._wrapperElement; 1105 }, 1106 1107 /** 1108 * @param {!TreeElement} parentTreeElement 1109 */ 1110 _populateStackTraceTreeElement: function(parentTreeElement) 1111 { 1112 /** 1113 * @param {!Array.<!ConsoleAgent.CallFrame>=} stackTrace 1114 * @this {WebInspector.ConsoleViewMessage} 1115 */ 1116 function appendStackTrace(stackTrace) 1117 { 1118 if (!stackTrace) 1119 return; 1120 1121 for (var i = 0; i < stackTrace.length; i++) { 1122 var frame = stackTrace[i]; 1123 1124 var content = document.createElementWithClass("div", "stacktrace-entry"); 1125 var functionName = frame.functionName || WebInspector.UIString("(anonymous function)"); 1126 if (frame.scriptId) { 1127 var urlElement = this._linkifyCallFrame(frame); 1128 if (!urlElement) 1129 continue; 1130 content.appendChild(urlElement); 1131 content.createTextChild(" "); 1132 } 1133 1134 content.createChild("span", "console-message-text source-code").textContent = functionName; 1135 parentTreeElement.appendChild(new TreeElement(content)); 1136 } 1137 } 1138 1139 appendStackTrace.call(this, this._message.stackTrace); 1140 1141 for (var asyncTrace = this._message.asyncStackTrace; asyncTrace; asyncTrace = asyncTrace.asyncStackTrace) { 1142 if (!asyncTrace.callFrames || !asyncTrace.callFrames.length) 1143 break; 1144 var content = document.createElementWithClass("div", "stacktrace-entry"); 1145 var description = WebInspector.asyncStackTraceLabel(asyncTrace.description); 1146 content.createChild("span", "console-message-text source-code console-async-trace-text").textContent = description; 1147 parentTreeElement.appendChild(new TreeElement(content)); 1148 appendStackTrace.call(this, asyncTrace.callFrames); 1149 } 1150 }, 1151 1152 resetIncrementRepeatCount: function() 1153 { 1154 this._repeatCount = 1; 1155 if (!this._repeatCountElement) 1156 return; 1157 1158 this._repeatCountElement.remove(); 1159 delete this._repeatCountElement; 1160 }, 1161 1162 incrementRepeatCount: function() 1163 { 1164 this._repeatCount++; 1165 this._showRepeatCountElement(); 1166 }, 1167 1168 _showRepeatCountElement: function() 1169 { 1170 if (!this._element) 1171 return; 1172 1173 if (!this._repeatCountElement) { 1174 this._repeatCountElement = document.createElement("span"); 1175 this._repeatCountElement.className = "bubble-repeat-count"; 1176 1177 this._element.insertBefore(this._repeatCountElement, this._element.firstChild); 1178 this._element.classList.add("repeated-message"); 1179 } 1180 this._repeatCountElement.textContent = this._repeatCount; 1181 }, 1182 1183 /** 1184 * @return {string} 1185 */ 1186 toString: function() 1187 { 1188 var sourceString; 1189 switch (this._message.source) { 1190 case WebInspector.ConsoleMessage.MessageSource.XML: 1191 sourceString = "XML"; 1192 break; 1193 case WebInspector.ConsoleMessage.MessageSource.JS: 1194 sourceString = "JavaScript"; 1195 break; 1196 case WebInspector.ConsoleMessage.MessageSource.Network: 1197 sourceString = "Network"; 1198 break; 1199 case WebInspector.ConsoleMessage.MessageSource.ConsoleAPI: 1200 sourceString = "ConsoleAPI"; 1201 break; 1202 case WebInspector.ConsoleMessage.MessageSource.Storage: 1203 sourceString = "Storage"; 1204 break; 1205 case WebInspector.ConsoleMessage.MessageSource.AppCache: 1206 sourceString = "AppCache"; 1207 break; 1208 case WebInspector.ConsoleMessage.MessageSource.Rendering: 1209 sourceString = "Rendering"; 1210 break; 1211 case WebInspector.ConsoleMessage.MessageSource.CSS: 1212 sourceString = "CSS"; 1213 break; 1214 case WebInspector.ConsoleMessage.MessageSource.Security: 1215 sourceString = "Security"; 1216 break; 1217 case WebInspector.ConsoleMessage.MessageSource.Other: 1218 sourceString = "Other"; 1219 break; 1220 } 1221 1222 var typeString; 1223 switch (this._message.type) { 1224 case WebInspector.ConsoleMessage.MessageType.Log: 1225 typeString = "Log"; 1226 break; 1227 case WebInspector.ConsoleMessage.MessageType.Dir: 1228 typeString = "Dir"; 1229 break; 1230 case WebInspector.ConsoleMessage.MessageType.DirXML: 1231 typeString = "Dir XML"; 1232 break; 1233 case WebInspector.ConsoleMessage.MessageType.Trace: 1234 typeString = "Trace"; 1235 break; 1236 case WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed: 1237 case WebInspector.ConsoleMessage.MessageType.StartGroup: 1238 typeString = "Start Group"; 1239 break; 1240 case WebInspector.ConsoleMessage.MessageType.EndGroup: 1241 typeString = "End Group"; 1242 break; 1243 case WebInspector.ConsoleMessage.MessageType.Assert: 1244 typeString = "Assert"; 1245 break; 1246 case WebInspector.ConsoleMessage.MessageType.Result: 1247 typeString = "Result"; 1248 break; 1249 case WebInspector.ConsoleMessage.MessageType.Profile: 1250 case WebInspector.ConsoleMessage.MessageType.ProfileEnd: 1251 typeString = "Profiling"; 1252 break; 1253 } 1254 1255 var levelString; 1256 switch (this._message.level) { 1257 case WebInspector.ConsoleMessage.MessageLevel.Log: 1258 levelString = "Log"; 1259 break; 1260 case WebInspector.ConsoleMessage.MessageLevel.Warning: 1261 levelString = "Warning"; 1262 break; 1263 case WebInspector.ConsoleMessage.MessageLevel.Debug: 1264 levelString = "Debug"; 1265 break; 1266 case WebInspector.ConsoleMessage.MessageLevel.Error: 1267 levelString = "Error"; 1268 break; 1269 case WebInspector.ConsoleMessage.MessageLevel.Info: 1270 levelString = "Info"; 1271 break; 1272 } 1273 1274 return sourceString + " " + typeString + " " + levelString + ": " + this.formattedMessage().textContent + "\n" + this._message.url + " line " + this._message.line; 1275 }, 1276 1277 get text() 1278 { 1279 return this._message.messageText; 1280 }, 1281 } 1282 1283 /** 1284 * @constructor 1285 * @extends {WebInspector.ConsoleViewMessage} 1286 * @param {!WebInspector.ConsoleMessage} consoleMessage 1287 * @param {?WebInspector.Linkifier} linkifier 1288 * @param {number} nestingLevel 1289 */ 1290 WebInspector.ConsoleGroupViewMessage = function(consoleMessage, linkifier, nestingLevel) 1291 { 1292 console.assert(consoleMessage.isGroupStartMessage()); 1293 WebInspector.ConsoleViewMessage.call(this, consoleMessage, linkifier, nestingLevel); 1294 this.setCollapsed(consoleMessage.type === WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed); 1295 } 1296 1297 WebInspector.ConsoleGroupViewMessage.prototype = { 1298 /** 1299 * @param {boolean} collapsed 1300 */ 1301 setCollapsed: function(collapsed) 1302 { 1303 this._collapsed = collapsed; 1304 if (this._wrapperElement) 1305 this._wrapperElement.classList.toggle("collapsed", this._collapsed); 1306 }, 1307 1308 /** 1309 * @return {boolean} 1310 */ 1311 collapsed: function() 1312 { 1313 return this._collapsed; 1314 }, 1315 1316 /** 1317 * @return {!Element} 1318 */ 1319 toMessageElement: function() 1320 { 1321 if (!this._wrapperElement) { 1322 WebInspector.ConsoleViewMessage.prototype.toMessageElement.call(this); 1323 this._wrapperElement.classList.toggle("collapsed", this._collapsed); 1324 } 1325 return this._wrapperElement; 1326 }, 1327 1328 __proto__: WebInspector.ConsoleViewMessage.prototype 1329 } 1330