Web Inspector: Inline multiple console log values if they are simple
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Views / FormattedValue.js
1 /*
2  * Copyright (C) 2015 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 WebInspector.FormattedValue = {};
27
28 WebInspector.FormattedValue.hasSimpleDisplay = function(object)
29 {
30     switch (object.type) {
31     case "boolean":
32     case "number":
33     case "string":
34     case "symbol":
35     case "undefined":
36         return true;
37
38     case "function":
39         return false;
40
41     case "object":
42         var subtype = object.subtype;
43         return subtype === "null" || subtype === "regexp" || subtype === "date";
44     }
45
46     console.assert(false, "All RemoteObject types should be handled above");
47     return false;
48 };
49
50 WebInspector.FormattedValue.classNameForTypes = function(type, subtype)
51 {
52     return "formatted-" + (subtype ? subtype : type);
53 };
54
55 WebInspector.FormattedValue.classNameForObject = function(object)
56 {
57     return WebInspector.FormattedValue.classNameForTypes(object.type, object.subtype);
58 };
59
60 WebInspector.FormattedValue.createLinkifiedElementString = function(string)
61 {
62     var span = document.createElement("span");
63     span.className = "formatted-string";
64     span.append("\"", WebInspector.linkifyStringAsFragment(string.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")), "\"");
65     return span;
66 };
67
68 WebInspector.FormattedValue.createElementForNode = function(object)
69 {
70     var span = document.createElement("span");
71     span.className = "formatted-node";
72
73     object.pushNodeToFrontend(function(nodeId) {
74         if (!nodeId) {
75             span.textContent = object.description;
76             return;
77         }
78
79         var treeOutline = new WebInspector.DOMTreeOutline;
80         treeOutline.setVisible(true);
81         treeOutline.rootDOMNode = WebInspector.domTreeManager.nodeForId(nodeId);
82         if (!treeOutline.children[0].hasChildren)
83             treeOutline.element.classList.add("single-node");
84         span.appendChild(treeOutline.element);
85     });
86
87     return span;
88 };
89
90 WebInspector.FormattedValue.createElementForError = function(object)
91 {
92     var span = document.createElement("span");
93     span.classList.add("formatted-error");
94     span.textContent = object.description;
95
96     if (!object.preview)
97         return span;
98
99     function previewToObject(preview)
100     {
101         var result = {};
102         for (var property of preview.propertyPreviews)
103             result[property.name] = property.value;
104
105         return result;
106     }
107
108     var preview = previewToObject(object.preview);
109     if (!preview.sourceURL)
110         return span;
111
112     var sourceLinkWithPrefix = WebInspector.ErrorObjectView.makeSourceLinkWithPrefix(preview.sourceURL, preview.line, preview.column);
113     span.append(sourceLinkWithPrefix);
114     return span;
115 };
116
117 WebInspector.FormattedValue.createElementForNodePreview = function(preview)
118 {
119     var value = preview.value || preview.description;
120     var span = document.createElement("span");
121     span.className = "formatted-node-preview syntax-highlighted";
122
123     // Comment node preview.
124     if (value.startsWith("<!--")) {
125         var comment = span.appendChild(document.createElement("span"));
126         comment.className = "html-comment";
127         comment.textContent = value;
128         return span;
129     }
130
131     // Doctype node preview.
132     if (value.startsWith("<!DOCTYPE")) {
133         var doctype = span.appendChild(document.createElement("span"));
134         doctype.className = "html-doctype";
135         doctype.textContent = value;
136         return span;
137     }
138
139     // Element node previews have a very strict format, with at most a single attribute.
140     // We can style it up like a DOMNode without interactivity.
141     var matches = value.match(/^<(\S+?)(?: (\S+?)="(.*?)")?>$/);
142
143     // Remaining node types are often #text, #document, etc, with attribute nodes potentially being any string.
144     if (!matches) {
145         console.assert(!value.startsWith("<"), "Unexpected node preview format: " + value);
146         span.textContent = value;
147         return span;
148     }
149
150     var tag = document.createElement("span");
151     tag.className = "html-tag";
152     tag.append("<");
153
154     var tagName = tag.appendChild(document.createElement("span"));
155     tagName.className = "html-tag-name";
156     tagName.textContent = matches[1];
157
158     if (matches[2]) {
159         tag.append(" ");
160         var attribute = tag.appendChild(document.createElement("span"));
161         attribute.className = "html-attribute";
162         var attributeName = attribute.appendChild(document.createElement("span"));
163         attributeName.className = "html-attribute-name";
164         attributeName.textContent = matches[2];
165         attribute.append("=\"");
166         var attributeValue = attribute.appendChild(document.createElement("span"));
167         attributeValue.className = "html-attribute-value";
168         attributeValue.textContent = matches[3];
169         attribute.append("\"");
170     }
171
172     tag.append(">");
173     span.appendChild(tag);
174
175     return span;
176 };
177
178 WebInspector.FormattedValue.createElementForFunctionWithName = function(description)
179 {
180     var span = document.createElement("span");
181     span.classList.add("formatted-function");
182     span.textContent = description.substring(0, description.indexOf("("));
183     return span;
184 };
185
186 WebInspector.FormattedValue.createElementForTypesAndValue = function(type, subtype, displayString, size, isPreview, hadException)
187 {
188     var span = document.createElement("span");
189     span.classList.add(WebInspector.FormattedValue.classNameForTypes(type, subtype));
190
191     // Exception.
192     if (hadException) {
193         span.textContent = "[Exception: " + displayString + "]";
194         return span;
195     }
196
197     // String: quoted and replace newlines as nice unicode symbols.
198     if (type === "string") {
199         displayString = displayString.truncate(WebInspector.FormattedValue.MAX_PREVIEW_STRING_LENGTH);
200         span.textContent = doubleQuotedString(displayString.replace(/\n/g, "\u21B5"));
201         return span;
202     }
203
204     // Function: if class, show the description, otherwise elide in previews.
205     if (type === "function") {
206         if (subtype === "class")
207             span.textContent = displayString;
208         else
209             span.textContent = isPreview ? "function" : displayString;
210         return span;
211     }
212
213     // Everything else, the description/value string.
214     span.textContent = displayString;
215
216     // If there is a size, include it.
217     if (size !== undefined && (subtype === "array" || subtype === "set" || subtype === "map" || subtype === "weakmap" || subtype === "weakset")) {
218         var sizeElement = span.appendChild(document.createElement("span"));
219         sizeElement.className = "size";
220         sizeElement.textContent = " (" + size + ")";
221     }
222
223     return span;
224 };
225
226 WebInspector.FormattedValue.createElementForRemoteObject = function(object, hadException)
227 {
228     return WebInspector.FormattedValue.createElementForTypesAndValue(object.type, object.subtype, object.description, object.size, false, hadException);
229 };
230
231 WebInspector.FormattedValue.createElementForObjectPreview = function(objectPreview)
232 {
233     return WebInspector.FormattedValue.createElementForTypesAndValue(objectPreview.type, objectPreview.subtype, objectPreview.description, objectPreview.size, true, false);
234 };
235
236 WebInspector.FormattedValue.createElementForPropertyPreview = function(propertyPreview)
237 {
238     return WebInspector.FormattedValue.createElementForTypesAndValue(propertyPreview.type, propertyPreview.subtype, propertyPreview.value, undefined, true, false);
239 };
240
241 WebInspector.FormattedValue.createObjectPreviewOrFormattedValueForObjectPreview = function(objectPreview, previewViewMode)
242 {
243     if (objectPreview.subtype === "node")
244         return WebInspector.FormattedValue.createElementForNodePreview(objectPreview);
245
246     if (objectPreview.type === "function")
247         return WebInspector.FormattedValue.createElementForFunctionWithName(objectPreview.description);
248
249     return new WebInspector.ObjectPreviewView(objectPreview, previewViewMode).element;
250 };
251
252 WebInspector.FormattedValue.createObjectPreviewOrFormattedValueForRemoteObject = function(object, previewViewMode)
253 {
254     if (object.subtype === "node")
255         return WebInspector.FormattedValue.createElementForNode(object);
256
257     if (object.subtype === "error")
258         return WebInspector.FormattedValue.createElementForError(object);
259
260     if (object.preview)
261         return new WebInspector.ObjectPreviewView(object.preview, previewViewMode);
262
263     return WebInspector.FormattedValue.createElementForRemoteObject(object);
264 };
265
266 WebInspector.FormattedValue.createObjectTreeOrFormattedValueForRemoteObject = function(object, propertyPath, forceExpanding)
267 {
268     if (object.subtype === "node")
269         return WebInspector.FormattedValue.createElementForNode(object);
270
271     if (object.subtype === "null")
272         return WebInspector.FormattedValue.createElementForRemoteObject(object);
273
274     if (object.type === "object" || object.subtype === "class") {
275         var objectTree = new WebInspector.ObjectTreeView(object, null, propertyPath, forceExpanding);
276         return objectTree.element;
277     }
278
279     return WebInspector.FormattedValue.createElementForRemoteObject(object);
280 };
281
282 WebInspector.FormattedValue.MAX_PREVIEW_STRING_LENGTH = 140;