3516b6cc0cfc7e15d1a3211961335bfb31f5beda
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Views / ObjectTreePropertyTreeElement.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.ObjectTreePropertyTreeElement = function(property, propertyPath, mode, prototypeName)
27 {
28     this._mode = mode || WebInspector.ObjectTreeView.Mode.Properties;
29     this._prototypeName = prototypeName;
30
31     WebInspector.ObjectTreeBaseTreeElement.call(this, property, propertyPath, property);
32
33     this.mainTitle = this._titleFragment();
34     this.addClassName("object-tree-property");
35
36     if (this.property.hasValue()) {
37         this.addClassName(this.property.value.type);
38         if (this.property.value.subtype)
39             this.addClassName(this.property.value.subtype);
40     } else
41         this.addClassName("accessor");
42
43     if (this.property.wasThrown)
44         this.addClassName("had-error");
45     if (this.property.name === "__proto__")
46         this.addClassName("prototype-property");
47
48     this._updateTooltips();
49     this._updateHasChildren();
50 };
51
52 WebInspector.ObjectTreePropertyTreeElement.prototype = {
53     constructor: WebInspector.ObjectTreePropertyTreeElement,
54     __proto__: WebInspector.ObjectTreeBaseTreeElement.prototype,
55
56     // Protected
57
58     onpopulate()
59     {
60         this._updateChildren();
61     },
62
63     onexpand()
64     {
65         if (this._previewView)
66             this._previewView.showTitle();
67     },
68
69     oncollapse()
70     {
71         if (this._previewView)
72             this._previewView.showPreview();
73     },
74
75     invokedGetter()
76     {
77         this.mainTitle = this._titleFragment();
78
79         var resolvedValue = this.resolvedValue();
80         this.addClassName(resolvedValue.type);
81         if (resolvedValue.subtype)
82             this.addClassName(resolvedValue.subtype);
83         if (this.hadError())
84             this.addClassName("had-error");
85         this.removeClassName("accessor");
86
87         this._updateHasChildren();
88     },
89
90     // Private
91
92     _updateHasChildren()
93     {
94         var resolvedValue = this.resolvedValue();
95         var valueHasChildren = (resolvedValue && resolvedValue.hasChildren);
96         var wasThrown = this.hadError();
97
98         if (this._mode === WebInspector.ObjectTreeView.Mode.Properties)
99             this.hasChildren = !wasThrown && valueHasChildren;
100         else
101             this.hasChildren = !wasThrown && valueHasChildren && (this.property.name === "__proto__" || this._alwaysDisplayAsProperty());
102     },
103
104     _updateTooltips()
105     {
106         var attributes = [];
107
108         if (this.property.configurable)
109             attributes.push("configurable");
110         if (this.property.enumerable)
111             attributes.push("enumerable");
112         if (this.property.writable)
113             attributes.push("writable");
114
115         this.iconElement.title = attributes.join(" ");
116     },
117
118     _titleFragment()
119     {
120         if (this.property.name === "__proto__")
121             return this._createTitlePrototype();
122
123         if (this._mode === WebInspector.ObjectTreeView.Mode.Properties)
124             return this._createTitlePropertyStyle();
125         else
126             return this._createTitleAPIStyle();
127     },
128
129     _createTitlePrototype()
130     {
131         console.assert(this.property.hasValue());
132         console.assert(this.property.name === "__proto__");
133
134         var nameElement = document.createElement("span");
135         nameElement.className = "prototype-name";
136         nameElement.textContent = WebInspector.UIString("%s Prototype").format(this._sanitizedPrototypeString(this.property.value));
137         nameElement.title = this.propertyPathString(this.thisPropertyPath());
138         return nameElement;
139     },
140
141     _createTitlePropertyStyle()
142     {
143         var container = document.createDocumentFragment();
144
145         // Property name.
146         var nameElement = document.createElement("span");
147         nameElement.className = "property-name";
148         nameElement.textContent = this.property.name + ": ";
149         nameElement.title = this.propertyPathString(this.thisPropertyPath());
150
151         // Property attributes.
152         if (this._mode === WebInspector.ObjectTreeView.Mode.Properties) {
153             if (!this.property.enumerable)
154                 nameElement.classList.add("not-enumerable");
155         }
156
157         // Value / Getter Value / Getter.
158         var valueOrGetterElement;
159         var resolvedValue = this.resolvedValue();
160         if (resolvedValue) {
161             if (resolvedValue.preview) {
162                 this._previewView = new WebInspector.ObjectPreviewView(resolvedValue.preview);
163                 valueOrGetterElement = this._previewView.element;
164             } else {
165                 valueOrGetterElement = WebInspector.FormattedValue.createElementForRemoteObject(resolvedValue, this.hadError());
166
167                 // Special case a function property string.
168                 if (resolvedValue.type === "function")
169                     valueOrGetterElement.textContent = this._functionPropertyString();
170             }
171         } else {
172             valueOrGetterElement = document.createElement("span");
173             if (this.property.hasGetter())
174                 valueOrGetterElement.appendChild(this.createInteractiveGetterElement());
175             if (!this.property.hasSetter())
176                 valueOrGetterElement.appendChild(this.createReadOnlyIconElement());
177             // FIXME: What if just a setter?
178         }
179
180         valueOrGetterElement.classList.add("value");
181         if (this.hadError())
182             valueOrGetterElement.classList.add("error");
183
184         container.appendChild(nameElement);
185         container.appendChild(valueOrGetterElement);
186         return container;
187     },
188
189     _createTitleAPIStyle()
190     {
191         // Fixed values and special properties display like a property.
192         if (this._alwaysDisplayAsProperty())
193             return this._createTitlePropertyStyle();
194
195         // No API to display.
196         var isFunction = this.property.hasValue() && this.property.value.type === "function";
197         if (!isFunction && !this.property.hasGetter() && !this.property.hasSetter())
198             return null;
199
200         var container = document.createDocumentFragment();
201
202         // Function / Getter / Setter.
203         var nameElement = document.createElement("span");
204         nameElement.className = "property-name";
205         nameElement.textContent = this.property.name;
206         nameElement.title = this.propertyPathString(this.thisPropertyPath());
207         container.appendChild(nameElement);
208
209         if (isFunction) {
210             var paramElement = document.createElement("span");
211             paramElement.className = "function-parameters";
212             paramElement.textContent = this._functionParameterString();
213             container.appendChild(paramElement);
214         } else {
215             if (this.property.hasGetter())
216                 container.appendChild(this.createInteractiveGetterElement());
217             if (!this.property.hasSetter())
218                 container.appendChild(this.createReadOnlyIconElement());
219             // FIXME: What if just a setter?
220         }
221
222         return container;
223     },
224
225     _alwaysDisplayAsProperty()
226     {
227         // Constructor, though a function, is often better treated as an expandable object.
228         if (this.property.name === "constructor")
229             return true;
230
231         // Non-function objects are often better treated as properties.
232         if (this.property.hasValue() && this.property.value.type !== "function")
233             return true;
234
235         // Fetched getter value.
236         if (this._getterValue)
237             return true;
238
239         return false;
240     },
241
242     _functionPropertyString()
243     {
244         return "function" + this._functionParameterString();
245     },
246
247     _functionParameterString()
248     {
249         var resolvedValue = this.resolvedValue();
250         console.assert(resolvedValue.type === "function");
251
252         // For Native methods, the toString is poor. We try to provide good function parameter strings.
253         if (isFunctionStringNativeCode(resolvedValue.description)) {
254             // Native function on a prototype, likely "Foo.prototype.method".
255             if (this._prototypeName) {
256                 if (WebInspector.NativePrototypeFunctionParameters[this._prototypeName]) {
257                     var params = WebInspector.NativePrototypeFunctionParameters[this._prototypeName][this._property.name];
258                     return params ? "(" + params + ")" : "()";
259                 }
260             }
261
262             // Native function property on a native function is likely a "Foo.method".
263             if (isFunctionStringNativeCode(this._propertyPath.object.description)) {
264                 var match = this._propertyPath.object.description.match(/^function\s+([^)]+?)\(/);
265                 if (match) {
266                     var name = match[1];
267                     if (WebInspector.NativeConstructorFunctionParameters[name]) {
268                         var params = WebInspector.NativeConstructorFunctionParameters[name][this._property.name];
269                         return params ? "(" + params + ")" : "()";
270                     }
271                 }
272             }
273
274             // Native DOM constructor.
275             if (this._propertyPath.object.description.endsWith("Constructor")) {
276                 var name = this._propertyPath.object.description;
277                 if (WebInspector.NativeConstructorFunctionParameters[name]) {
278                     var params = WebInspector.NativeConstructorFunctionParameters[name][this._property.name];
279                     return params ? "(" + params + ")" : "()";
280                 }
281             }
282         }
283
284         var match = resolvedValue.description.match(/^function.*?(\([^)]+?\))/);
285         return match ? match[1] : "()";
286     },
287
288     _sanitizedPrototypeString(value)
289     {
290         // FIXME: <https://webkit.org/b/141610> For many X, X.prototype is an X when it must be a plain object
291         if (value.type === "function")
292             return "Function";
293         if (value.subtype === "date")
294             return "Date";
295         if (value.subtype === "regexp")
296             return "RegExp";
297
298         return value.description.replace(/Prototype$/, "");
299     },
300
301     _updateChildren()
302     {
303         if (this.children.length && !this.shouldRefreshChildren)
304             return;
305
306         var resolvedValue = this.resolvedValue();
307         if (resolvedValue.isCollectionType() && this._mode === WebInspector.ObjectTreeView.Mode.Properties)
308             resolvedValue.getCollectionEntries(0, 100, this._updateChildrenInternal.bind(this, this._updateEntries, this._mode));
309         else if (this.property.name === "__proto__")
310             resolvedValue.getOwnPropertyDescriptors(this._updateChildrenInternal.bind(this, this._updateProperties, WebInspector.ObjectTreeView.Mode.API));
311         else
312             resolvedValue.getDisplayablePropertyDescriptors(this._updateChildrenInternal.bind(this, this._updateProperties, this._mode));
313     },
314
315     _updateChildrenInternal(handler, mode, list)
316     {
317         this.removeChildren();
318
319         if (!list) {
320             var errorMessageElement = WebInspector.ObjectTreeView.emptyMessageElement(WebInspector.UIString("Could not fetch properties. Object may no longer exist."));
321             this.appendChild(new TreeElement(errorMessageElement, null, false));
322             return;
323         }
324
325         handler.call(this, list, this.resolvedValuePropertyPath(), mode);
326     },
327
328     _updateEntries(entries, propertyPath, mode)
329     {
330         for (var entry of entries) {
331             if (entry.key) {
332                 this.appendChild(new WebInspector.ObjectTreeMapKeyTreeElement(entry.key, propertyPath));
333                 this.appendChild(new WebInspector.ObjectTreeMapValueTreeElement(entry.value, propertyPath, entry.key));
334             } else
335                 this.appendChild(new WebInspector.ObjectTreeSetIndexTreeElement(entry.value, propertyPath));
336         }
337
338         if (!this.children.length) {
339             var emptyMessageElement = WebInspector.ObjectTreeView.emptyMessageElement(WebInspector.UIString("No Entries."));
340             this.appendChild(new TreeElement(emptyMessageElement, null, false));
341         }
342
343         // Show the prototype so users can see the API.
344         var resolvedValue = this.resolvedValue();
345         resolvedValue.getOwnPropertyDescriptor("__proto__", function(propertyDescriptor) {
346             if (propertyDescriptor)
347                 this.appendChild(new WebInspector.ObjectTreePropertyTreeElement(propertyDescriptor, propertyPath, mode));
348         }.bind(this));
349     },
350
351     _updateProperties(properties, propertyPath, mode)
352     {
353         properties.sort(WebInspector.ObjectTreeView.ComparePropertyDescriptors);
354
355         var resolvedValue = this.resolvedValue();
356         var isArray = resolvedValue.isArray();
357         var isPropertyMode = mode === WebInspector.ObjectTreeView.Mode.Properties || this._getterValue;
358         var isAPI = mode === WebInspector.ObjectTreeView.Mode.API;
359
360         var prototypeName = undefined;
361         if (this.property.name === "__proto__") {
362             if (resolvedValue.description)
363                 prototypeName = this._sanitizedPrototypeString(resolvedValue);
364         }
365
366         for (var propertyDescriptor of properties) {
367             // FIXME: If this is a pure API ObjectTree, we should show the native getters.
368             // For now, just skip native binding getters in API mode, since we likely
369             // already showed them in the Properties section.
370             if (isAPI && propertyDescriptor.nativeGetter)
371                 continue;
372             
373             if (isArray && isPropertyMode) {
374                 if (propertyDescriptor.isIndexProperty())
375                     this.appendChild(new WebInspector.ObjectTreeArrayIndexTreeElement(propertyDescriptor, propertyPath));
376                 else if (propertyDescriptor.name === "__proto__")
377                     this.appendChild(new WebInspector.ObjectTreePropertyTreeElement(propertyDescriptor, propertyPath, mode, prototypeName));
378             } else
379                 this.appendChild(new WebInspector.ObjectTreePropertyTreeElement(propertyDescriptor, propertyPath, mode, prototypeName));
380         }
381
382         if (!this.children.length) {
383             var emptyMessageElement = WebInspector.ObjectTreeView.emptyMessageElement(WebInspector.UIString("No Properties."));
384             this.appendChild(new TreeElement(emptyMessageElement, null, false));
385         }
386     }
387 };