text: /*
   * Copyright (C) 2011 Google Inc.  All rights reserved.
   * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
   * Copyright (C) 2009 Joseph Pecoraro
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1.  Redistributions of source code must retain the above copyright
   *     notice, this list of conditions and the following disclaimer.
   * 2.  Redistributions in binary form must reproduce the above copyright
   *     notice, this list of conditions and the following disclaimer in the
   *     documentation and/or other materials provided with the distribution.
   * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
   *     its contributors may be used to endorse or promote products derived
   *     from this software without specific prior written permission.
   *
   * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
   * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
   * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
   * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   */
  
  /**
   * @constructor
   * @implements {WebInspector.ViewportElement}
   * @param {!WebInspector.ConsoleMessage} consoleMessage
   * @param {!WebInspector.Linkifier} linkifier
   * @param {number} nestingLevel
   */
  WebInspector.ConsoleViewMessage = function(consoleMessage, linkifier, nestingLevel)
  {
      this._message = consoleMessage;
      this._linkifier = linkifier;
      this._repeatCount = 1;
      this._closeGroupDecorationCount = 0;
      this._nestingLevel = nestingLevel;
  
      /** @type {!Array.<!WebInspector.DataGrid>} */
      this._dataGrids = [];
  
      /** @type {!Object.<string, function(!WebInspector.RemoteObject, !Element, boolean=)>} */
      this._customFormatters = {
          "array": this._formatParameterAsArray,
          "error": this._formatParameterAsError,
          "function": this._formatParameterAsFunction,
          "generator": this._formatParameterAsObject,
          "iterator": this._formatParameterAsObject,
          "map": this._formatParameterAsObject,
          "node": this._formatParameterAsNode,
          "object": this._formatParameterAsObject,
          "set": this._formatParameterAsObject,
          "string": this._formatParameterAsString
      };
      this._previewFormatter = new WebInspector.RemoteObjectPreviewFormatter();
      this._searchRegex = null;
  }
  
  WebInspector.ConsoleViewMessage.prototype = {
      /**
       * @return {?WebInspector.Target}
       */
      _target: function()
      {
          return this.consoleMessage().target();
      },
  
      /**
       * @override
       * @return {!Element}
       */
      element: function()
      {
          return this.toMessageElement();
      },
  
      /**
       * @override
       */
      wasShown: function()
      {
          for (var i = 0; this._dataGrids && i < this._dataGrids.length; ++i)
              this._dataGrids[i].updateWidths();
          this._isVisible = true;
      },
  
      onResize: function()
      {
          if (!this._isVisible)
              return;
          for (var i = 0; this._dataGrids && i < this._dataGrids.length; ++i)
              this._dataGrids[i].onResize();
      },
  
      /**
       * @override
       */
      willHide: function()
      {
          this._isVisible = false;
          this._cachedHeight = this.contentElement().offsetHeight;
      },
  
      /**
       * @return {number}
       */
      fastHeight: function()
      {
          if (this._cachedHeight)
              return this._cachedHeight;
          const defaultConsoleRowHeight = 18;  // Sync with consoleView.css
          if (this._message.type === WebInspector.ConsoleMessage.MessageType.Table) {
              var table = this._message.parameters[0];
              if (table && table.preview)
                  return defaultConsoleRowHeight * table.preview.properties.length;
          }
          return defaultConsoleRowHeight;
      },
  
      /**
       * @return {!WebInspector.ConsoleMessage}
       */
      consoleMessage: function()
      {
          return this._message;
      },
  
      _formatMessage: function()
      {
          this._formattedMessage = createElement("span");
          WebInspector.appendStyle(this._formattedMessage, "components/objectValue.css");
          this._formattedMessage.className = "console-message-text source-code";
  
          /**
           * @param {string} title
           * @return {!Element}
           * @this {WebInspector.ConsoleMessage}
           */
          function linkifyRequest(title)
          {
              return WebInspector.Linkifier.linkifyUsingRevealer(/** @type {!WebInspector.NetworkRequest} */ (this.request), title, this.request.url);
          }
  
          var consoleMessage = this._message;
          if (!this._messageElement) {
              if (consoleMessage.source === WebInspector.ConsoleMessage.MessageSource.ConsoleAPI) {
                  switch (consoleMessage.type) {
                      case WebInspector.ConsoleMessage.MessageType.Trace:
                          this._messageElement = this._format(consoleMessage.parameters || ["console.trace()"]);
                          break;
                      case WebInspector.ConsoleMessage.MessageType.Clear:
                          this._messageElement = createTextNode(WebInspector.UIString("Console was cleared"));
                          this._formattedMessage.classList.add("console-info");
                          break;
                      case WebInspector.ConsoleMessage.MessageType.Assert:
                          var args = [WebInspector.UIString("Assertion failed:")];
                          if (consoleMessage.parameters)
                              args = args.concat(consoleMessage.parameters);
                          this._messageElement = this._format(args);
                          break;
                      case WebInspector.ConsoleMessage.MessageType.Dir:
                          var obj = consoleMessage.parameters ? consoleMessage.parameters[0] : undefined;
                          var args = ["%O", obj];
                          this._messageElement = this._format(args);
                          break;
                      case WebInspector.ConsoleMessage.MessageType.Profile:
                      case WebInspector.ConsoleMessage.MessageType.ProfileEnd:
                          this._messageElement = this._format([consoleMessage.messageText]);
                          break;
                      default:
                          if (consoleMessage.parameters && consoleMessage.parameters.length === 1 && consoleMessage.parameters[0].type === "string")
                               this._messageElement = this._tryFormatAsError(/**@type {string} */(consoleMessage.parameters[0].value));
  
                          var args = consoleMessage.parameters || [consoleMessage.messageText];
                          this._messageElement = this._messageElement || this._format(args);
                  }
              } else if (consoleMessage.source === WebInspector.ConsoleMessage.MessageSource.Network) {
                  if (consoleMessage.request) {
                      this._messageElement = createElement("span");
                      if (consoleMessage.level === WebInspector.ConsoleMessage.MessageLevel.Error || consoleMessage.level === WebInspector.ConsoleMessage.MessageLevel.RevokedError) {
                          this._messageElement.createTextChildren(consoleMessage.request.requestMethod, " ");
                          this._messageElement.appendChild(WebInspector.Linkifier.linkifyUsingRevealer(consoleMessage.request, consoleMessage.request.url, consoleMessage.request.url));
                          if (consoleMessage.request.failed)
                              this._messageElement.createTextChildren(" ", consoleMessage.request.localizedFailDescription);
                          else
                              this._messageElement.createTextChildren(" ", String(consoleMessage.request.statusCode), " (", consoleMessage.request.statusText, ")");
                      } else {
                          var fragment = WebInspector.linkifyStringAsFragmentWithCustomLinkifier(consoleMessage.messageText, linkifyRequest.bind(consoleMessage));
                          this._messageElement.appendChild(fragment);
                      }
                  } else {
                      var url = consoleMessage.url;
                      if (url) {
                          var isExternal = !WebInspector.resourceForURL(url) && !WebInspector.networkMapping.uiSourceCodeForURLForAnyTarget(url);
                          this._anchorElement = WebInspector.linkifyURLAsNode(url, url, "console-message-url", isExternal);
                      }
                      this._messageElement = this._format([consoleMessage.messageText]);
                  }
              } else {
                  var args = consoleMessage.parameters || [consoleMessage.messageText];
                  this._messageElement = this._format(args);
              }
          }
  
          if (consoleMessage.source !== WebInspector.ConsoleMessage.MessageSource.Network || consoleMessage.request) {
              if (consoleMessage.scriptId) {
                  this._anchorElement = this._linkifyScriptId(consoleMessage.scriptId, consoleMessage.url || "", consoleMessage.line, consoleMessage.column);
              } else {
                  var showBlackboxed = (consoleMessage.source !== WebInspector.ConsoleMessage.MessageSource.ConsoleAPI);
                  var debuggerModel = WebInspector.DebuggerModel.fromTarget(this._target());
                  var callFrame = WebInspector.DebuggerPresentationUtils.callFrameAnchorFromStackTrace(debuggerModel, consoleMessage.stackTrace, consoleMessage.asyncStackTrace, showBlackboxed);
                  if (callFrame && callFrame.scriptId)
                      this._anchorElement = this._linkifyCallFrame(callFrame);
                  else if (consoleMessage.url && consoleMessage.url !== "undefined")
                      this._anchorElement = this._linkifyLocation(consoleMessage.url, consoleMessage.line, consoleMessage.column);
              }
          }
  
          this._formattedMessage.appendChild(this._messageElement);
          if (this._anchorElement) {
              // Append a space to prevent the anchor text from being glued to the console message when the user selects and copies the console messages.
              this._anchorElement.appendChild(createTextNode(" "));
              this._formattedMessage.insertBefore(this._anchorElement, this._formattedMessage.firstChild);
          }
  
          var dumpStackTrace = (!!consoleMessage.stackTrace || !!consoleMessage.asyncStackTrace) && (consoleMessage.source === WebInspector.ConsoleMessage.MessageSource.Network || consoleMessage.level === WebInspector.ConsoleMessage.MessageLevel.Error || consoleMessage.level === WebInspector.ConsoleMessage.MessageLevel.RevokedError || consoleMessage.type === WebInspector.ConsoleMessage.MessageType.Trace);
          if (dumpStackTrace) {
              var treeOutline = new TreeOutline();
              treeOutline.element.classList.add("outline-disclosure", "outline-disclosure-no-padding");
              var content = this._formattedMessage;
              var root = new TreeElement(content);
              root.toggleOnClick = true;
              root.selectable = false;
              content.treeElementForTest = root;
              treeOutline.appendChild(root);
              if (consoleMessage.type === WebInspector.ConsoleMessage.MessageType.Trace)
                  root.expand();
  
              this._populateStackTraceTreeElement(root);
              this._formattedMessage = treeOutline.element;
          }
      },
  
      /**
       * @return {!Element}
       */
      formattedMessage: function()
      {
          if (!this._formattedMessage)
              this._formatMessage();
          return this._formattedMessage;
      },
  
      /**
       * @param {string} url
       * @param {number} lineNumber
       * @param {number} columnNumber
       * @return {?Element}
       */
      _linkifyLocation: function(url, lineNumber, columnNumber)
      {
          var target = this._target();
          if (!target)
              return null;
          // FIXME(62725): stack trace line/column numbers are one-based.
          lineNumber = lineNumber ? lineNumber - 1 : 0;
          columnNumber = columnNumber ? columnNumber - 1 : 0;
          return this._linkifier.linkifyScriptLocation(target, null, url, lineNumber, columnNumber, "console-message-url");
      },
  
      /**
       * @param {!ConsoleAgent.CallFrame} callFrame
       * @return {?Element}
       */
      _linkifyCallFrame: function(callFrame)
      {
          var target = this._target();
          return this._linkifier.linkifyConsoleCallFrame(target, callFrame, "console-message-url");
      },
  
      /**
       * @param {string} scriptId
       * @param {string} url
       * @param {number} lineNumber
       * @param {number} columnNumber
       * @return {?Element}
       */
      _linkifyScriptId: function(scriptId, url, lineNumber, columnNumber)
      {
          var target = this._target();
          if (!target)
              return null;
          // FIXME(62725): stack trace line/column numbers are one-based.
          lineNumber = lineNumber ? lineNumber - 1 : 0;
          columnNumber = columnNumber ? columnNumber - 1 : 0;
          return this._linkifier.linkifyScriptLocation(target, scriptId, url, lineNumber, columnNumber, "console-message-url");
      },
  
      _format: function(parameters)
      {
          // This node is used like a Builder. Values are continually appended onto it.
          var formattedResult = createElement("span");
          if (!parameters.length)
              return formattedResult;
  
          var target = this._target();
  
          // Formatting code below assumes that parameters are all wrappers whereas frontend console
          // API allows passing arbitrary values as messages (strings, numbers, etc.). Wrap them here.
          for (var i = 0; i < parameters.length; ++i) {
              // FIXME: Only pass runtime wrappers here.
              if (parameters[i] instanceof WebInspector.RemoteObject)
                  continue;
  
              if (!target) {
                  parameters[i] = WebInspector.RemoteObject.fromLocalObject(parameters[i]);
                  continue;
              }
  
              if (typeof parameters[i] === "object")
                  parameters[i] = target.runtimeModel.createRemoteObject(parameters[i]);
              else
                  parameters[i] = target.runtimeModel.createRemoteObjectFromPrimitiveValue(parameters[i]);
          }
  
          // There can be string log and string eval result. We distinguish between them based on message type.
          var shouldFormatMessage = WebInspector.RemoteObject.type(parameters[0]) === "string" && (this._message.type !== WebInspector.ConsoleMessage.MessageType.Result || this._message.level === WebInspector.ConsoleMessage.MessageLevel.Error || this._message.level === WebInspector.ConsoleMessage.MessageLevel.RevokedError);
  
          // Multiple parameters with the first being a format string. Save unused substitutions.
          if (shouldFormatMessage) {
              // Multiple parameters with the first being a format string. Save unused substitutions.
              var result = this._formatWithSubstitutionString(parameters[0].description, parameters.slice(1), formattedResult);
              parameters = result.unusedSubstitutions;
              if (parameters.length)
                  formattedResult.createTextChild(" ");
          }
  
          if (this._message.type === WebInspector.ConsoleMessage.MessageType.Table) {
              formattedResult.appendChild(this._formatParameterAsTable(parameters));
              return formattedResult;
          }
  
          // Single parameter, or unused substitutions from above.
          for (var i = 0; i < parameters.length; ++i) {
              // Inline strings when formatting.
              if (shouldFormatMessage && parameters[i].type === "string")
                  formattedResult.appendChild(WebInspector.linkifyStringAsFragment(parameters[i].description));
              else
                  formattedResult.appendChild(this._formatParameter(parameters[i], false, true));
              if (i < parameters.length - 1)
                  formattedResult.createTextChild(" ");
          }
          return formattedResult;
      },
  
      /**
       * @param {!WebInspector.RemoteObject} output
       * @param {boolean=} forceObjectFormat
       * @param {boolean=} includePreview
       * @return {!Element}
       */
      _formatParameter: function(output, forceObjectFormat, includePreview)
      {
          if (output.customPreview()) {
              return (new WebInspector.CustomPreviewComponent(output)).element;
          }
  
          var type = forceObjectFormat ? "object" : (output.subtype || output.type);
          var formatter = this._customFormatters[type] || this._formatParameterAsValue;
          var span = createElement("span");
          span.className = "object-value-" + type + " source-code";
          formatter.call(this, output, span, includePreview);
          return span;
      },
  
      /**
       * @param {!WebInspector.RemoteObject} obj
       * @param {!Element} elem
       */
      _formatParameterAsValue: function(obj, elem)
      {
          elem.createTextChild(obj.description || "");
          if (obj.objectId)
              elem.addEventListener("contextmenu", this._contextMenuEventFired.bind(this, obj), false);
      },
  
      /**
       * @param {!WebInspector.RemoteObject} obj
       * @param {!Element} elem
       * @param {boolean=} includePreview
       */
      _formatParameterAsObject: function(obj, elem, includePreview)
      {
          this._formatParameterAsArrayOrObject(obj, elem, includePreview);
      },
  
      /**
       * @param {!WebInspector.RemoteObject} obj
       * @param {!Element} elem
       * @param {boolean=} includePreview
       */
      _formatParameterAsArrayOrObject: function(obj, elem, includePreview)
      {
          var titleElement = createElement("span");
          if (includePreview && obj.preview) {
              titleElement.classList.add("console-object-preview");
              var lossless = this._previewFormatter.appendObjectPreview(titleElement, obj.preview);
              if (lossless) {
                  elem.appendChild(titleElement);
                  titleElement.addEventListener("contextmenu", this._contextMenuEventFired.bind(this, obj), false);
                  return;
              }
          } else {
              if (obj.type === "function") {
                  WebInspector.ObjectPropertiesSection.formatObjectAsFunction(obj, titleElement, false);
                  titleElement.classList.add("object-value-function");
              } else {
                  titleElement.createTextChild(obj.description || "");
              }
          }
          var note = titleElement.createChild("span", "object-info-state-note");
          note.title = WebInspector.UIString("Object value at left was snapshotted when logged, value below was evaluated just now.");
          var section = new WebInspector.ObjectPropertiesSection(obj, titleElement);
          section.enableContextMenu();
          elem.appendChild(section.element);
          section.element.classList.add("console-view-object-properties-section");
      },
  
      /**
       * @param {!WebInspector.RemoteObject} func
       * @param {!Element} element
       * @param {boolean=} includePreview
       */
      _formatParameterAsFunction: function(func, element, includePreview)
      {
          WebInspector.RemoteFunction.objectAsFunction(func).targetFunction().then(formatTargetFunction.bind(this));
  
          /**
           * @param {!WebInspector.RemoteObject} targetFunction
           * @this {WebInspector.ConsoleViewMessage}
           */
          function formatTargetFunction(targetFunction)
          {
              var functionElement = createElement("span")
              WebInspector.ObjectPropertiesSection.formatObjectAsFunction(targetFunction, functionElement, true, includePreview);
              element.appendChild(functionElement);
              if (targetFunction !== func) {
                  var note = element.createChild("span", "object-info-state-note");
                  note.title = WebInspector.UIString("Function was resolved from bound function.");
              }
              element.addEventListener("contextmenu", this._contextMenuEventFired.bind(this, targetFunction), false);
          }
      },
  
      /**
       * @param {!WebInspector.RemoteObject} obj
       * @param {!Event} event
       */
      _contextMenuEventFired: function(obj, event)
      {
          var contextMenu = new WebInspector.ContextMenu(event);
          contextMenu.appendApplicableItems(obj);
          contextMenu.show();
      },
  
      /**
       * @param {?WebInspector.RemoteObject} object
       * @param {!Array.<!RuntimeAgent.PropertyPreview>} propertyPath
       * @return {!Element}
       */
      _renderPropertyPreviewOrAccessor: function(object, propertyPath)
      {
          var property = propertyPath.peekLast();
          if (property.type === "accessor")
              return this._formatAsAccessorProperty(object, propertyPath.select("name"), false);
          return this._previewFormatter.renderPropertyPreview(property.type, /** @type {string} */ (property.subtype), property.value);
      },
  
      /**
       * @param {!WebInspector.RemoteObject} object
       * @param {!Element} elem
       */
      _formatParameterAsNode: function(object, elem)
      {
          WebInspector.Renderer.renderPromise(object).then(appendRenderer, failedToRender.bind(this));
          /**
           * @param {!Element} rendererElement
           */
          function appendRenderer(rendererElement)
          {
              elem.appendChild(rendererElement);
          }
  
          /**
           * @this {WebInspector.ConsoleViewMessage}
           */
          function failedToRender()
          {
              this._formatParameterAsObject(object, elem, false);
          }
      },
  
      /**
       * @param {!WebInspector.RemoteObject} array
       * @return {boolean}
       */
      useArrayPreviewInFormatter: function(array)
      {
          return this._message.type !== WebInspector.ConsoleMessage.MessageType.DirXML;
      },
  
      /**
       * @param {!WebInspector.RemoteObject} array
       * @param {!Element} elem
       */
      _formatParameterAsArray: function(array, elem)
      {
          var maxFlatArrayLength = 100;
          if (this.useArrayPreviewInFormatter(array) || array.arrayLength() > maxFlatArrayLength)
              this._formatParameterAsArrayOrObject(array, elem, this.useArrayPreviewInFormatter(array) || array.arrayLength() <= maxFlatArrayLength);
          else
              array.getAllProperties(false, this._printArray.bind(this, array, elem));
      },
  
      /**
       * @param {!Array.<!WebInspector.RemoteObject>} parameters
       * @return {!Element}
       */
      _formatParameterAsTable: function(parameters)
      {
          var element = createElementWithClass("div", "console-message-formatted-table");
          var table = parameters[0];
          if (!table || !table.preview)
              return element;
  
          var columnNames = [];
          var preview = table.preview;
          var rows = [];
          for (var i = 0; i < preview.properties.length; ++i) {
              var rowProperty = preview.properties[i];
              var rowPreview = rowProperty.valuePreview;
              if (!rowPreview)
                  continue;
  
              var rowValue = {};
              const maxColumnsToRender = 20;
              for (var j = 0; j < rowPreview.properties.length; ++j) {
                  var cellProperty = rowPreview.properties[j];
                  var columnRendered = columnNames.indexOf(cellProperty.name) != -1;
                  if (!columnRendered) {
                      if (columnNames.length === maxColumnsToRender)
                          continue;
                      columnRendered = true;
                      columnNames.push(cellProperty.name);
                  }
  
                  if (columnRendered) {
                      var cellElement = this._renderPropertyPreviewOrAccessor(table, [rowProperty, cellProperty]);
                      cellElement.classList.add("console-message-nowrap-below");
                      rowValue[cellProperty.name] = cellElement;
                  }
              }
              rows.push([rowProperty.name, rowValue]);
          }
  
          var flatValues = [];
          for (var i = 0; i < rows.length; ++i) {
              var rowName = rows[i][0];
              var rowValue = rows[i][1];
              flatValues.push(rowName);
              for (var j = 0; j < columnNames.length; ++j)
                  flatValues.push(rowValue[columnNames[j]]);
          }
  
          var dataGridContainer = element.createChild("span");
          if (!preview.lossless || !flatValues.length) {
              element.appendChild(this._formatParameter(table, true, false));
              if (!flatValues.length)
                  return element;
          }
  
          columnNames.unshift(WebInspector.UIString("(index)"));
          var dataGrid = WebInspector.SortableDataGrid.create(columnNames, flatValues);
          dataGrid.renderInline();
          dataGridContainer.appendChild(dataGrid.element);
          this._dataGrids.push(dataGrid);
          return element;
      },
  
      /**
       * @param {!WebInspector.RemoteObject} output
       * @param {!Element} elem
       */
      _formatParameterAsString: function(output, elem)
      {
          var span = createElement("span");
          span.className = "object-value-string source-code";
          span.appendChild(WebInspector.linkifyStringAsFragment(output.description || ""));
  
          // Make black quotes.
          elem.classList.remove("object-value-string");
          elem.createTextChild("\"");
          elem.appendChild(span);
          elem.createTextChild("\"");
      },
  
      /**
       * @param {!WebInspector.RemoteObject} output
       * @param {!Element} elem
       */
      _formatParameterAsError: function(output, elem)
      {
          var span = elem.createChild("span", "object-value-error source-code");
          var text = output.description || "";
          var lines = text.split("\n", 2);
          span.appendChild(WebInspector.linkifyStringAsFragment(lines[0]));
          if (lines.length > 1) {
              var detailedLink = elem.createChild("a");
              detailedLink.textContent = "(\u2026)";
              function showDetailed(event)
              {
                  span.removeChildren();
                  detailedLink.remove();
                  span.appendChild(WebInspector.linkifyStringAsFragment(text));
                  event.consume(true);
              }
              detailedLink._showDetailedForTest = showDetailed.bind(null, new MouseEvent('click'));
              detailedLink.addEventListener("click", showDetailed, false);
          }
      },
  
      /**
       * @param {!WebInspector.RemoteObject} array
       * @param {!Element} elem
       * @param {?Array.<!WebInspector.RemoteObjectProperty>} properties
       */
      _printArray: function(array, elem, properties)
      {
          if (!properties) {
              this._formatParameterAsObject(array, elem, false);
              return;
          }
  
          var elements = [];
          for (var i = 0; i < properties.length; ++i) {
              var property = properties[i];
              var name = property.name;
              if (isNaN(name))
                  continue;
              if (property.getter)
                  elements[name] = this._formatAsAccessorProperty(array, [name], true);
              else if (property.value)
                  elements[name] = this._formatAsArrayEntry(property.value);
          }
  
          elem.createTextChild("[");
          var lastNonEmptyIndex = -1;
  
          function appendUndefined(elem, index)
          {
              if (index - lastNonEmptyIndex <= 1)
                  return;
              var span = elem.createChild("span", "object-value-undefined");
              span.textContent = WebInspector.UIString("undefined × %d", index - lastNonEmptyIndex - 1);
          }
  
          var length = array.arrayLength();
          for (var i = 0; i < length; ++i) {
              var element = elements[i];
              if (!element)
                  continue;
  
              if (i - lastNonEmptyIndex > 1) {
                  appendUndefined(elem, i);
                  elem.createTextChild(", ");
              }
  
              elem.appendChild(element);
              lastNonEmptyIndex = i;
              if (i < length - 1)
                  elem.createTextChild(", ");
          }
          appendUndefined(elem, length);
  
          elem.createTextChild("]");
          elem.addEventListener("contextmenu", this._contextMenuEventFired.bind(this, array), false);
      },
  
      /**
       * @param {!WebInspector.RemoteObject} output
       * @return {!Element}
       */
      _formatAsArrayEntry: function(output)
      {
          // Prevent infinite expansion of cross-referencing arrays.
          return this._formatParameter(output, output.subtype === "array", false);
      },
  
      /**
       * @param {?WebInspector.RemoteObject} object
       * @param {!Array.<string>} propertyPath
       * @param {boolean} isArrayEntry
       * @return {!Element}
       */
      _formatAsAccessorProperty: function(object, propertyPath, isArrayEntry)
      {
          var rootElement = WebInspector.ObjectPropertyTreeElement.createRemoteObjectAccessorPropertySpan(object, propertyPath, onInvokeGetterClick.bind(this));
  
          /**
           * @param {?WebInspector.RemoteObject} result
           * @param {boolean=} wasThrown
           * @this {WebInspector.ConsoleViewMessage}
           */
          function onInvokeGetterClick(result, wasThrown)
          {
              if (!result)
                  return;
              rootElement.removeChildren();
              if (wasThrown) {
                  var element = rootElement.createChild("span", "error-message");
                  element.textContent = WebInspector.UIString("<exception>");
                  element.title = /** @type {string} */ (result.description);
              } else if (isArrayEntry) {
                  rootElement.appendChild(this._formatAsArrayEntry(result));
              } else {
                  // Make a PropertyPreview from the RemoteObject similar to the backend logic.
                  const maxLength = 100;
                  var type = result.type;
                  var subtype = result.subtype;
                  var description = "";
                  if (type !== "function" && result.description) {
                      if (type === "string" || subtype === "regexp")
                          description = result.description.trimMiddle(maxLength);
                      else
                          description = result.description.trimEnd(maxLength);
                  }
                  rootElement.appendChild(this._previewFormatter.renderPropertyPreview(type, subtype, description));
              }
          }
  
          return rootElement;
      },
  
      /**
       * @param {string} format
       * @param {!Array.<string>} parameters
       * @param {!Element} formattedResult
       */
      _formatWithSubstitutionString: function(format, parameters, formattedResult)
      {
          var formatters = {};
  
          /**
           * @param {boolean} force
           * @param {!WebInspector.RemoteObject} obj
           * @return {!Element}
           * @this {WebInspector.ConsoleViewMessage}
           */
          function parameterFormatter(force, obj)
          {
              return this._formatParameter(obj, force, false);
          }
  
          function stringFormatter(obj)
          {
              return obj.description;
          }
  
          function floatFormatter(obj)
          {
              if (typeof obj.value !== "number")
                  return "NaN";
              return obj.value;
          }
  
          function integerFormatter(obj)
          {
              if (typeof obj.value !== "number")
                  return "NaN";
              return Math.floor(obj.value);
          }
  
          function bypassFormatter(obj)
          {
              return (obj instanceof Node) ? obj : "";
          }
  
          var currentStyle = null;
          function styleFormatter(obj)
          {
              currentStyle = {};
              var buffer = createElement("span");
              buffer.setAttribute("style", obj.description);
              for (var i = 0; i < buffer.style.length; i++) {
                  var property = buffer.style[i];
                  var value = buffer.style.getPropertyValue(property);
                  if (!value.startsWith("url(") && isWhitelistedProperty(property))
                      currentStyle[property] = buffer.style[property];
              }
          }
  
          function isWhitelistedProperty(property)
          {
              var prefixes = ["background", "border", "color", "font", "line", "margin", "padding", "text", "-webkit-background", "-webkit-border", "-webkit-font", "-webkit-margin", "-webkit-padding", "-webkit-text"];
              for (var i = 0; i < prefixes.length; i++) {
                  if (property.startsWith(prefixes[i]))
                      return true;
              }
              return false;
          }
  
          // Firebug uses %o for formatting objects.
          formatters.o = parameterFormatter.bind(this, false);
          formatters.s = stringFormatter;
          formatters.f = floatFormatter;
          // Firebug allows both %i and %d for formatting integers.
          formatters.i = integerFormatter;
          formatters.d = integerFormatter;
  
          // Firebug uses %c for styling the message.
          formatters.c = styleFormatter;
  
          // Support %O to force object formatting, instead of the type-based %o formatting.
          formatters.O = parameterFormatter.bind(this, true);
  
          formatters._ = bypassFormatter;
  
          function append(a, b)
          {
              if (b instanceof Node)
                  a.appendChild(b);
              else if (typeof b !== "undefined") {
                  var toAppend = WebInspector.linkifyStringAsFragment(String(b));
                  if (currentStyle) {
                      var wrapper = createElement('span');
                      wrapper.appendChild(toAppend);
                      applyCurrentStyle(wrapper);
                      for (var i = 0; i < wrapper.children.length; ++i)
                          applyCurrentStyle(wrapper.children[i]);
                      toAppend = wrapper;
                  }
                  a.appendChild(toAppend);
              }
              return a;
          }
  
          /**
           * @param {!Element} element
           */
          function applyCurrentStyle(element)
          {
              for (var key in currentStyle)
                  element.style[key] = currentStyle[key];
          }
  
          // String.format does treat formattedResult like a Builder, result is an object.
          return String.format(format, parameters, formatters, formattedResult, append);
      },
  
      /**
       * @return {boolean}
       */
      matchesFilterRegex: function(regexObject)
      {
          regexObject.lastIndex = 0;
          var text = this.searchableElement().deepTextContent();
          if (this._anchorElement)
              text += " " + this._anchorElement.textContent;
          return regexObject.test(text);
      },
  
      /**
       * @param {boolean} show
       */
      updateTimestamp: function(show)
      {
          if (!this._formattedMessage)
              return;
  
          if (show && !this.timestampElement) {
              this.timestampElement = createElementWithClass("span", "console-timestamp");
              this.timestampElement.textContent = (new Date(this._message.timestamp)).toConsoleTime() + " ";
              var afterRepeatCountChild = this._repeatCountElement && this._repeatCountElement.nextSibling;
              this._formattedMessage.insertBefore(this.timestampElement, this._formattedMessage.firstChild);
              return;
          }
  
          if (!show && this.timestampElement) {
              this.timestampElement.remove();
              delete this.timestampElement;
          }
      },
  
      /**
       * @return {number}
       */
      nestingLevel: function()
      {
          return this._nestingLevel;
      },
  
      resetCloseGroupDecorationCount: function()
      {
          if (!this._closeGroupDecorationCount)
              return;
          this._closeGroupDecorationCount = 0;
          this._updateCloseGroupDecorations();
      },
  
      incrementCloseGroupDecorationCount: function()
      {
          ++this._closeGroupDecorationCount;
          this._updateCloseGroupDecorations();
      },
  
      _updateCloseGroupDecorations: function()
      {
          if (!this._nestingLevelMarkers)
              return;
          for (var i = 0, n = this._nestingLevelMarkers.length; i < n; ++i) {
              var marker = this._nestingLevelMarkers[i];
              marker.classList.toggle("group-closed", n - i <= this._closeGroupDecorationCount);
          }
      },
  
      /**
       * @return {!Element}
       */
      contentElement: function()
      {
          if (this._element)
              return this._element;
  
          var element = createElementWithClass("div", "console-message");
          this._element = element;
  
          if (this._message.type === WebInspector.ConsoleMessage.MessageType.StartGroup || this._message.type === WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed)
              element.classList.add("console-group-title");
  
          element.appendChild(this.formattedMessage());
  
          if (this._repeatCount > 1)
              this._showRepeatCountElement();
  
          this.updateTimestamp(WebInspector.moduleSetting("consoleTimestampsEnabled").get());
  
          return this._element;
      },
  
      /**
       * @return {!Element}
       */
      toMessageElement: function()
      {
          if (this._wrapperElement)
              return this._wrapperElement;
  
          this._wrapperElement = createElement("div");
          this.updateMessageElement();
          return this._wrapperElement;
      },
  
      updateMessageElement: function()
      {
          if (!this._wrapperElement)
              return;
  
          this._wrapperElement.className = "console-message-wrapper";
          this._wrapperElement.removeChildren();
  
          this._nestingLevelMarkers = [];
          for (var i = 0; i < this._nestingLevel; ++i)
              this._nestingLevelMarkers.push(this._wrapperElement.createChild("div", "nesting-level-marker"));
          this._updateCloseGroupDecorations();
          this._wrapperElement.message = this;
  
          switch (this._message.level) {
          case WebInspector.ConsoleMessage.MessageLevel.Log:
              this._wrapperElement.classList.add("console-log-level");
              break;
          case WebInspector.ConsoleMessage.MessageLevel.Debug:
              this._wrapperElement.classList.add("console-debug-level");
              break;
          case WebInspector.ConsoleMessage.MessageLevel.Warning:
              this._wrapperElement.classList.add("console-warning-level");
              break;
          case WebInspector.ConsoleMessage.MessageLevel.Error:
              this._wrapperElement.classList.add("console-error-level");
              break;
          case WebInspector.ConsoleMessage.MessageLevel.RevokedError:
              this._wrapperElement.classList.add("console-revokedError-level");
              break;
          case WebInspector.ConsoleMessage.MessageLevel.Info:
              this._wrapperElement.classList.add("console-info-level");
              break;
          }
  
          this._wrapperElement.appendChild(this.contentElement());
      },
  
      /**
       * @param {!TreeElement} parentTreeElement
       */
      _populateStackTraceTreeElement: function(parentTreeElement)
      {
          var target = this._target();
          if (!target)
              return;
          var content = WebInspector.DOMPresentationUtils.buildStackTracePreviewContents(target,
              this._linkifier, this._message.stackTrace, this._message.asyncStackTrace);
          var treeElement = new TreeElement(content);
          treeElement.selectable = false;
          parentTreeElement.appendChild(treeElement);
      },
  
      resetIncrementRepeatCount: function()
      {
          this._repeatCount = 1;
          if (!this._repeatCountElement)
              return;
  
          this._repeatCountElement.remove();
          delete this._repeatCountElement;
      },
  
      incrementRepeatCount: function()
      {
          this._repeatCount++;
          this._showRepeatCountElement();
      },
  
      _showRepeatCountElement: function()
      {
          if (!this._element)
              return;
  
          if (!this._repeatCountElement) {
              this._repeatCountElement = createElement("span");
              this._repeatCountElement.className = "bubble-repeat-count";
  
              this._element.insertBefore(this._repeatCountElement, this._element.firstChild);
              this._element.classList.add("repeated-message");
          }
          this._repeatCountElement.textContent = this._repeatCount;
      },
  
      /**
       * @override
       * @return {string}
       */
      toString: function()
      {
          var sourceString;
          switch (this._message.source) {
              case WebInspector.ConsoleMessage.MessageSource.XML:
                  sourceString = "XML";
                  break;
              case WebInspector.ConsoleMessage.MessageSource.JS:
                  sourceString = "JavaScript";
                  break;
              case WebInspector.ConsoleMessage.MessageSource.Network:
                  sourceString = "Network";
                  break;
              case WebInspector.ConsoleMessage.MessageSource.ConsoleAPI:
                  sourceString = "ConsoleAPI";
                  break;
              case WebInspector.ConsoleMessage.MessageSource.Storage:
                  sourceString = "Storage";
                  break;
              case WebInspector.ConsoleMessage.MessageSource.AppCache:
                  sourceString = "AppCache";
                  break;
              case WebInspector.ConsoleMessage.MessageSource.Rendering:
                  sourceString = "Rendering";
                  break;
              case WebInspector.ConsoleMessage.MessageSource.CSS:
                  sourceString = "CSS";
                  break;
              case WebInspector.ConsoleMessage.MessageSource.Security:
                  sourceString = "Security";
                  break;
              case WebInspector.ConsoleMessage.MessageSource.Other:
                  sourceString = "Other";
                  break;
          }
  
          var typeString;
          switch (this._message.type) {
              case WebInspector.ConsoleMessage.MessageType.Log:
                  typeString = "Log";
                  break;
              case WebInspector.ConsoleMessage.MessageType.Dir:
                  typeString = "Dir";
                  break;
              case WebInspector.ConsoleMessage.MessageType.DirXML:
                  typeString = "Dir XML";
                  break;
              case WebInspector.ConsoleMessage.MessageType.Trace:
                  typeString = "Trace";
                  break;
              case WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed:
              case WebInspector.ConsoleMessage.MessageType.StartGroup:
                  typeString = "Start Group";
                  break;
              case WebInspector.ConsoleMessage.MessageType.EndGroup:
                  typeString = "End Group";
                  break;
              case WebInspector.ConsoleMessage.MessageType.Assert:
                  typeString = "Assert";
                  break;
              case WebInspector.ConsoleMessage.MessageType.Result:
                  typeString = "Result";
                  break;
              case WebInspector.ConsoleMessage.MessageType.Profile:
              case WebInspector.ConsoleMessage.MessageType.ProfileEnd:
                  typeString = "Profiling";
                  break;
          }
  
          var levelString;
          switch (this._message.level) {
              case WebInspector.ConsoleMessage.MessageLevel.Log:
                  levelString = "Log";
                  break;
              case WebInspector.ConsoleMessage.MessageLevel.Warning:
                  levelString = "Warning";
                  break;
              case WebInspector.ConsoleMessage.MessageLevel.Debug:
                  levelString = "Debug";
                  break;
              case WebInspector.ConsoleMessage.MessageLevel.Error:
                  levelString = "Error";
                  break;
              case WebInspector.ConsoleMessage.MessageLevel.RevokedError:
                  levelString = "RevokedError";
                  break;
              case WebInspector.ConsoleMessage.MessageLevel.Info:
                  levelString = "Info";
                  break;
          }
  
          return sourceString + " " + typeString + " " + levelString + ": " + this.formattedMessage().textContent + "\n" + this._message.url + " line " + this._message.line;
      },
  
      get text()
      {
          return this._message.messageText;
      },
  
      /**
       * @param {?RegExp} regex
       */
      setSearchRegex: function(regex)
      {
          if (this._searchHiglightNodeChanges && this._searchHiglightNodeChanges.length)
              WebInspector.revertDomChanges(this._searchHiglightNodeChanges);
          this._searchRegex = regex;
          this._searchHighlightNodes = [];
          this._searchHiglightNodeChanges = [];
          if (!this._searchRegex)
              return;
  
          var text = this.searchableElement().deepTextContent();
          var match;
          this._searchRegex.lastIndex = 0;
          var sourceRanges = [];
          while ((match = this._searchRegex.exec(text)) && match[0])
              sourceRanges.push(new WebInspector.SourceRange(match.index, match[0].length));
  
          if (sourceRanges.length && this.searchableElement())
              this._searchHighlightNodes = WebInspector.highlightSearchResults(this.searchableElement(), sourceRanges, this._searchHiglightNodeChanges);
      },
  
      /**
       * @return {?RegExp}
       */
      searchRegex: function()
      {
          return this._searchRegex;
      },
  
      /**
       * @return {number}
       */
      searchCount: function()
      {
          return this._searchHighlightNodes.length;
      },
  
      /**
       * @return {!Element}
       */
      searchHighlightNode: function(index)
      {
          return this._searchHighlightNodes[index];
      },
  
      /**
       * @return {!Element}
       */
      searchableElement: function()
      {
          this.formattedMessage();
          return this._messageElement;
      },
  
      /**
       * @param {string} string
       * @return {?Element}
       */
      _tryFormatAsError: function(string)
      {
          /**
           * @param {string} prefix
           */
          function startsWith(prefix)
          {
              return string.startsWith(prefix);
          }
  
          var errorPrefixes = ["EvalError", "ReferenceError", "SyntaxError", "TypeError", "RangeError", "Error", "URIError"];
          var target = this._target();
          if (!target || !errorPrefixes.some(startsWith))
              return null;
          var debuggerModel = WebInspector.DebuggerModel.fromTarget(target);
          if (!debuggerModel)
              return null;
  
          var lines = string.split("\n");
          var links = [];
          var position = 0;
          for (var i = 0; i < lines.length; ++i) {
              position += i > 0 ? lines[i - 1].length + 1 : 0;
              var isCallFrameLine = /^\s*at\s/.test(lines[i]);
              if (!isCallFrameLine && links.length)
                  return null;
  
              if (!isCallFrameLine)
                  continue;
  
              var openBracketIndex = -1;
              var closeBracketIndex = -1;
              var match = /\([^\)\(]+\)/.exec(lines[i]);
              if (match) {
                  openBracketIndex = match.index;
                  closeBracketIndex = match.index + match[0].length - 1;
              }
              var hasOpenBracket = openBracketIndex !== -1;
              var left = hasOpenBracket ? openBracketIndex + 1 : lines[i].indexOf("at") + 3;
              var right = hasOpenBracket ? closeBracketIndex : lines[i].length;
              var linkCandidate = lines[i].substring(left, right);
              var splitResult = WebInspector.ParsedURL.splitLineAndColumn(linkCandidate);
              if (!splitResult)
                  return null;
  
              var parsed = splitResult.url.asParsedURL();
              var url;
              if (parsed)
                  url = parsed.url;
              else if (debuggerModel.scriptsForSourceURL(splitResult.url).length)
                  url = splitResult.url;
              else if (splitResult.url === "<anonymous>")
                  continue;
              else
                  return null;
  
              links.push({url: url, positionLeft: position + left, positionRight: position + right, lineNumber: splitResult.lineNumber, columnNumber: splitResult.columnNumber});
          }
  
          if (!links.length)
              return null;
  
          var formattedResult = createElement("span");
          var start = 0;
          for (var i = 0; i < links.length; ++i) {
              formattedResult.appendChild(WebInspector.linkifyStringAsFragment(string.substring(start, links[i].positionLeft)));
              formattedResult.appendChild(this._linkifier.linkifyScriptLocation(target, null, links[i].url, links[i].lineNumber, links[i].columnNumber));
              start = links[i].positionRight;
          }
  
          if (start != string.length)
              formattedResult.appendChild(WebInspector.linkifyStringAsFragment(string.substring(start)));
  
          return formattedResult;
      }
  }
  
  /**
   * @constructor
   * @extends {WebInspector.ConsoleViewMessage}
   * @param {!WebInspector.ConsoleMessage} consoleMessage
   * @param {!WebInspector.Linkifier} linkifier
   * @param {number} nestingLevel
   */
  WebInspector.ConsoleGroupViewMessage = function(consoleMessage, linkifier, nestingLevel)
  {
      console.assert(consoleMessage.isGroupStartMessage());
      WebInspector.ConsoleViewMessage.call(this, consoleMessage, linkifier, nestingLevel);
      this.setCollapsed(consoleMessage.type === WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed);
  }
  
  WebInspector.ConsoleGroupViewMessage.prototype = {
      /**
       * @param {boolean} collapsed
       */
      setCollapsed: function(collapsed)
      {
          this._collapsed = collapsed;
          if (this._wrapperElement)
              this._wrapperElement.classList.toggle("collapsed", this._collapsed);
      },
  
      /**
       * @return {boolean}
       */
      collapsed: function()
      {
         return this._collapsed;
      },
  
      /**
       * @override
       * @return {!Element}
       */
      toMessageElement: function()
      {
          if (!this._wrapperElement) {
              WebInspector.ConsoleViewMessage.prototype.toMessageElement.call(this);
              this._wrapperElement.classList.toggle("collapsed", this._collapsed);
          }
          return this._wrapperElement;
      },
  
      __proto__: WebInspector.ConsoleViewMessage.prototype
  }
  