Web Inspector: Array/Collection Sizes should be visible and distinct
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Views / ObjectPreviewView.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.ObjectPreviewView = function(preview, mode)
27 {
28     WebInspector.Object.call(this);
29
30     console.assert(preview instanceof WebInspector.ObjectPreview);
31
32     this._preview = preview;
33     this._mode = mode || WebInspector.ObjectPreviewView.Mode.Full;
34
35     this._element = document.createElement("span");
36     this._element.className = "object-preview";
37
38     this._previewElement = this._element.appendChild(document.createElement("span"));
39     this._previewElement.className = "preview";
40     this._lossless = this._appendPreview(this._previewElement, this._preview);
41
42     this._titleElement = this._element.appendChild(document.createElement("span"));
43     this._titleElement.className = "title";
44     this._titleElement.hidden = true;
45     this._initTitleElement();
46
47     if (this._preview.hasSize()) {
48         var sizeElement = this._element.appendChild(document.createElement("span"));
49         sizeElement.className = "size";
50         sizeElement.textContent = " (" + this._preview.size + ")";
51     }
52
53     if (this._lossless)
54         this._element.classList.add("lossless");
55 };
56
57 WebInspector.ObjectPreviewView.Mode = {
58     Brief: Symbol("object-preview-brief"),
59     Full: Symbol("object-preview-full"),
60 };
61
62 WebInspector.ObjectPreviewView.prototype = {
63     constructor: WebInspector.ObjectPreviewView,
64     __proto__: WebInspector.Object.prototype,
65
66     // Public
67
68     get preview()
69     {
70         return this._preview;
71     },
72
73     get element()
74     {
75         return this._element;
76     },
77
78     get mode()
79     {
80         return this._mode;
81     },
82
83     get lossless()
84     {
85         return this._lossless;
86     },
87
88     showTitle: function()
89     {
90         this._titleElement.hidden = false;
91         this._previewElement.hidden = true;
92     },
93
94     showPreview: function()
95     {
96         this._titleElement.hidden = true;
97         this._previewElement.hidden = false;
98     },
99
100     // Private
101
102     _initTitleElement: function()
103     {
104         // Display null / regexps as simple formatted values even in title.
105         if (this._preview.subtype === "regexp" || this._preview.subtype === "null")
106             this._titleElement.appendChild(WebInspector.FormattedValue.createElementForObjectPreview(this._preview));
107         else
108             this._titleElement.textContent = this._preview.description || "";
109     },
110
111     _numberOfPropertiesToShowInMode: function()
112     {
113         return this._mode === WebInspector.ObjectPreviewView.Mode.Brief ? 3 : Infinity;
114     },
115
116     _appendPreview: function(element, preview)
117     {
118         var displayObjectAsValue = false;
119         if (preview.type === "object") {
120             if (preview.subtype === "regexp" || preview.subtype === "null") {
121                 // Display null / regexps as simple formatted values.
122                 displayObjectAsValue = true;
123             }  else if (preview.subtype !== "array" && preview.description !== "Object") {
124                 // Class names for other non-array / non-basic-Object types.
125                 var nameElement = element.appendChild(document.createElement("span"));
126                 nameElement.className = "object-preview-name";
127                 nameElement.textContent = preview.description + " ";
128             }
129         }
130
131         // Content.
132         var bodyElement = element.appendChild(document.createElement("span"));
133         bodyElement.className = "object-preview-body";
134         if (!displayObjectAsValue) {
135             if (preview.collectionEntryPreviews)
136                 return this._appendEntryPreviews(bodyElement, preview);
137             if (preview.propertyPreviews)
138                 return this._appendPropertyPreviews(bodyElement, preview);
139         }
140         return this._appendValuePreview(bodyElement, preview);
141     },
142
143     _appendEntryPreviews: function(element, preview)
144     {
145         var lossless = preview.lossless && !preview.propertyPreviews.length;
146
147         element.appendChild(document.createTextNode("{"));
148
149         var limit = Math.min(preview.collectionEntryPreviews.length, this._numberOfPropertiesToShowInMode());
150         for (var i = 0; i < limit; ++i) {
151             if (i > 0)
152                 element.appendChild(document.createTextNode(", "));
153
154             var keyPreviewLossless = true;
155             var entry = preview.collectionEntryPreviews[i];
156             if (entry.keyPreview) {
157                 keyPreviewLossless = this._appendPreview(element, entry.keyPreview);
158                 element.appendChild(document.createTextNode(" => "));
159             }
160
161             var valuePreviewLossless = this._appendPreview(element, entry.valuePreview);
162
163             if (!keyPreviewLossless || !valuePreviewLossless)
164                 lossless = false;
165         }
166
167         if (preview.overflow)
168             element.appendChild(document.createTextNode("\u2026"));
169         element.appendChild(document.createTextNode("}"));
170
171         return lossless;
172     },
173
174     _appendPropertyPreviews: function(element, preview)
175     {
176         // Do not show Error properties in previews. They are more useful in full views.
177         if (preview.subtype === "error")
178             return false;
179
180         // Do not show Date properties in previews. If there are any properties, show them in full view.
181         if (preview.subtype === "date")
182             return !preview.propertyPreviews.length;
183
184         // FIXME: Array previews should have better sparse support: (undefined × 10).
185         var isArray = preview.subtype === "array";
186
187         element.appendChild(document.createTextNode(isArray ? "[" : "{"));
188
189         var numberAdded = 0;
190         var limit = this._numberOfPropertiesToShowInMode();
191         for (var i = 0; i < preview.propertyPreviews.length && numberAdded < limit; ++i) {
192             var property = preview.propertyPreviews[i];
193
194             // FIXME: Better handle getter/setter accessors. Should we show getters in previews?
195             if (property.type === "accessor")
196                 continue;
197
198             // Constructor name is often already visible, so don't show it as a property.
199             if (property.name === "constructor")
200                 continue;
201
202             if (numberAdded++ > 0)
203                 element.appendChild(document.createTextNode(", "));
204
205             if (!isArray || property.name != i) {
206                 var nameElement = element.appendChild(document.createElement("span"));
207                 nameElement.className = "name";
208                 nameElement.textContent = property.name;
209                 element.appendChild(document.createTextNode(": "));
210             }
211
212             element.appendChild(WebInspector.FormattedValue.createElementForPropertyPreview(property));
213         }
214
215         if (preview.overflow)
216             element.appendChild(document.createTextNode("\u2026"));
217
218         element.appendChild(document.createTextNode(isArray ? "]" : "}"));
219
220         return preview.lossless;
221     },
222
223     _appendValuePreview: function(element, preview)
224     {
225         element.appendChild(WebInspector.FormattedValue.createElementForObjectPreview(preview));
226         return true;
227     }
228 };