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