blob: 172724b4648557f19010803eaf3e2f7410494570 [file] [log] [blame]
/*
* Copyright (C) 2015 Apple Inc. All rights reserved.
*
* 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.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. 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 INC. 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.
*/
WI.FormattedValue = {};
WI.FormattedValue.hasSimpleDisplay = function(object)
{
switch (object.type) {
case "boolean":
case "number":
case "string":
case "symbol":
case "bigint":
case "undefined":
return true;
case "function":
return false;
case "object":
var subtype = object.subtype;
return subtype === "null" || subtype === "regexp" || subtype === "date";
}
console.assert(false, "All RemoteObject types should be handled above");
return false;
};
WI.FormattedValue.classNameForTypes = function(type, subtype)
{
return "formatted-" + (subtype ? subtype : type);
};
WI.FormattedValue.classNameForObject = function(object)
{
return WI.FormattedValue.classNameForTypes(object.type, object.subtype);
};
WI.FormattedValue.createLinkifiedElementString = function(string)
{
var span = document.createElement("span");
span.className = "formatted-string";
span.append("\"", WI.linkifyStringAsFragment(string.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")), "\"");
return span;
};
WI.FormattedValue.createElementForNode = function(object)
{
var span = document.createElement("span");
span.className = "formatted-node";
object.pushNodeToFrontend(function(nodeId) {
if (!nodeId) {
span.textContent = object.description;
return;
}
var treeOutline = new WI.DOMTreeOutline;
treeOutline.setVisible(true);
treeOutline.rootDOMNode = WI.domManager.nodeForId(nodeId);
if (!treeOutline.children[0].hasChildren)
treeOutline.element.classList.add("single-node");
span.appendChild(treeOutline.element);
});
return span;
};
WI.FormattedValue.createElementForError = function(object)
{
var span = document.createElement("span");
span.classList.add("formatted-error");
span.textContent = object.description;
if (!object.preview)
return span;
function previewToObject(preview)
{
var result = {};
for (var property of preview.propertyPreviews)
result[property.name] = property.value;
return result;
}
var preview = previewToObject(object.preview);
if (!preview.sourceURL)
return span;
var sourceLinkWithPrefix = WI.ErrorObjectView.makeSourceLinkWithPrefix(preview.sourceURL, preview.line, preview.column);
span.append(sourceLinkWithPrefix);
return span;
};
WI.FormattedValue.createElementForNodePreview = function(preview, {remoteObjectAccessor} = {})
{
var value = preview.value || preview.description;
var span = document.createElement("span");
span.className = "formatted-node-preview syntax-highlighted";
if (remoteObjectAccessor) {
let domNode = null;
span.addEventListener("mouseenter", (event) => {
if (domNode) {
WI.domManager.highlightDOMNode(domNode.id, "all");
return;
}
remoteObjectAccessor((remoteObject) => {
remoteObject.pushNodeToFrontend((nodeId) => {
domNode = WI.domManager.nodeForId(nodeId);
if (domNode)
WI.domManager.highlightDOMNode(domNode.id, "all");
});
});
});
span.addEventListener("mouseleave", (event) => {
WI.domManager.hideDOMNodeHighlight();
});
span.addEventListener("contextmenu", (event) => {
if (!domNode)
return;
let contextMenu = WI.ContextMenu.createFromEvent(event);
WI.appendContextMenuItemsForDOMNode(contextMenu, domNode);
});
}
// Comment node preview.
if (value.startsWith("<!--")) {
var comment = span.appendChild(document.createElement("span"));
comment.className = "html-comment";
comment.textContent = value;
return span;
}
// Doctype node preview.
if (value.startsWith("<!DOCTYPE")) {
var doctype = span.appendChild(document.createElement("span"));
doctype.className = "html-doctype";
doctype.textContent = value;
return span;
}
// Element node previews have a very strict format, with at most a single attribute.
// We can style it up like a DOMNode without interactivity.
var matches = value.match(/^<(\S+?)(?: (\S+?)="(.*?)")?>$/);
// Remaining node types are often #text, #document, etc, with attribute nodes potentially being any string.
if (!matches) {
console.assert(!value.startsWith("<"), "Unexpected node preview format: " + value);
span.textContent = value;
return span;
}
var tag = document.createElement("span");
tag.className = "html-tag";
tag.append("<");
var tagName = tag.appendChild(document.createElement("span"));
tagName.className = "html-tag-name";
tagName.textContent = matches[1];
if (matches[2]) {
tag.append(" ");
var attribute = tag.appendChild(document.createElement("span"));
attribute.className = "html-attribute";
var attributeName = attribute.appendChild(document.createElement("span"));
attributeName.className = "html-attribute-name";
attributeName.textContent = matches[2];
attribute.append("=\"");
var attributeValue = attribute.appendChild(document.createElement("span"));
attributeValue.className = "html-attribute-value";
attributeValue.textContent = matches[3];
attribute.append("\"");
}
tag.append(">");
span.appendChild(tag);
return span;
};
WI.FormattedValue.createElementForFunctionWithName = function(description)
{
var span = document.createElement("span");
span.classList.add("formatted-function");
span.textContent = description.substring(0, description.indexOf("("));
return span;
};
WI.FormattedValue.createElementForTypesAndValue = function(type, subtype, displayString, size, isPreview, hadException)
{
var span = document.createElement("span");
span.classList.add(WI.FormattedValue.classNameForTypes(type, subtype));
// Exception.
if (hadException) {
span.textContent = "[Exception: " + displayString + "]";
return span;
}
// String: quoted and replace newlines as nice unicode symbols.
if (type === "string") {
displayString = displayString.truncate(WI.FormattedValue.MAX_PREVIEW_STRING_LENGTH);
span.textContent = doubleQuotedString(displayString.replace(/\n/g, "\u21B5"));
return span;
}
// Function: if class, show the description, otherwise elide in previews.
if (type === "function") {
if (subtype === "class")
span.textContent = displayString;
else
span.textContent = isPreview ? "function" : displayString;
return span;
}
// Everything else, the description/value string.
span.textContent = displayString;
// If there is a size, include it.
if (size !== undefined && (subtype === "array" || subtype === "set" || subtype === "map" || subtype === "weakmap" || subtype === "weakset")) {
var sizeElement = span.appendChild(document.createElement("span"));
sizeElement.className = "size";
sizeElement.textContent = " (" + size + ")";
}
return span;
};
WI.FormattedValue.createElementForRemoteObject = function(object, hadException)
{
return WI.FormattedValue.createElementForTypesAndValue(object.type, object.subtype, object.description, object.size, false, hadException);
};
WI.FormattedValue.createElementForObjectPreview = function(objectPreview)
{
return WI.FormattedValue.createElementForTypesAndValue(objectPreview.type, objectPreview.subtype, objectPreview.description, objectPreview.size, true, false);
};
WI.FormattedValue.createElementForPropertyPreview = function(propertyPreview)
{
return WI.FormattedValue.createElementForTypesAndValue(propertyPreview.type, propertyPreview.subtype, propertyPreview.value, undefined, true, false);
};
WI.FormattedValue.createObjectPreviewOrFormattedValueForObjectPreview = function(objectPreview, previewViewMode)
{
if (objectPreview.subtype === "node")
return WI.FormattedValue.createElementForNodePreview(objectPreview);
if (objectPreview.type === "function")
return WI.FormattedValue.createElementForFunctionWithName(objectPreview.description);
const object = null;
return new WI.ObjectPreviewView(object, objectPreview, previewViewMode).element;
};
WI.FormattedValue.createObjectPreviewOrFormattedValueForRemoteObject = function(object, previewViewMode)
{
if (object.subtype === "node")
return WI.FormattedValue.createElementForNode(object);
if (object.subtype === "error")
return WI.FormattedValue.createElementForError(object);
if (object.preview)
return new WI.ObjectPreviewView(object, object.preview, previewViewMode);
return WI.FormattedValue.createElementForRemoteObject(object);
};
WI.FormattedValue.createObjectTreeOrFormattedValueForRemoteObject = function(object, propertyPath, forceExpanding)
{
if (object.subtype === "node")
return WI.FormattedValue.createElementForNode(object);
if (object.subtype === "null")
return WI.FormattedValue.createElementForRemoteObject(object);
if (object.type === "object" || object.subtype === "class") {
var objectTree = new WI.ObjectTreeView(object, null, propertyPath, forceExpanding);
return objectTree.element;
}
return WI.FormattedValue.createElementForRemoteObject(object);
};
WI.FormattedValue.MAX_PREVIEW_STRING_LENGTH = 140;