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