Web Inspector: Create Separate Model and View Objects for RemoteObjects / ObjectPrevi...
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Views / ObjectTreeView.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.ObjectTreeView = function(object, mode, forceExpanding)
27 {
28     WebInspector.Object.call(this);
29
30     console.assert(object instanceof WebInspector.RemoteObject);
31
32     this._object = object;
33     this._mode = mode || WebInspector.ObjectTreeView.Mode.Properties;
34     this._expanded = false;
35     this._hasLosslessPreview = false;
36
37     this._element = document.createElement("div");
38     this._element.className = "object-tree";
39
40     if (this._object.preview) {
41         var previewView = new WebInspector.ObjectPreviewView(this._object.preview);
42         this._previewElement = previewView.element;
43         this._previewElement.addEventListener("click", this._handlePreviewOrTitleElementClick.bind(this));
44         this._element.appendChild(this._previewElement);
45
46         if (previewView.lossless && !forceExpanding) {
47             this._hasLosslessPreview = true;
48             this.element.classList.add("lossless-preview");
49         }
50     }
51
52     this._titleElement = document.createElement("span");
53     this._titleElement.className = "title";
54     this._titleElement.textContent = this._object.description || "";
55     this._titleElement.addEventListener("click", this._handlePreviewOrTitleElementClick.bind(this));
56     this._element.appendChild(this._titleElement);
57
58     if (this._object.preview)
59         this._titleElement.hidden = true;
60
61     this._outlineElement = document.createElement("ol");
62     this._outlineElement.className = "object-tree-outline";
63     this._outline = new TreeOutline(this._outlineElement);
64     this._element.appendChild(this._outlineElement);
65
66     // FIXME: Support editable ObjectTrees.
67 };
68
69 WebInspector.ObjectTreeView.Mode = {
70     Properties: Symbol("object-tree-properties"),
71     API: Symbol("object-tree-api"),
72 };
73
74 WebInspector.ObjectTreeView.classNameForObject = function(object)
75 {
76     return "formatted-" + (object.subtype ? object.subtype : object.type);
77 }
78
79 WebInspector.ObjectTreeView.ComparePropertyDescriptors = function(propertyA, propertyB)
80 {
81     var a = propertyA.name;
82     var b = propertyB.name;
83
84     // Put __proto__ at the bottom.
85     if (a === "__proto__")
86         return 1;
87     if (b === "__proto__")
88         return -1;
89
90     // Put internal properties at the top.
91     if (a.isInternalProperty && !b.isInternalProperty)
92         return -1;
93     if (b.isInternalProperty && !a.isInternalProperty)
94         return 1;
95
96     // if used elsewhere make sure to
97     //  - convert a and b to strings (not needed here, properties are all strings)
98     //  - check if a == b (not needed here, no two properties can be the same)
99
100     var diff = 0;
101     var chunk = /^\d+|^\D+/;
102     var chunka, chunkb, anum, bnum;
103     while (diff === 0) {
104         if (!a && b)
105             return -1;
106         if (!b && a)
107             return 1;
108         chunka = a.match(chunk)[0];
109         chunkb = b.match(chunk)[0];
110         anum = !isNaN(chunka);
111         bnum = !isNaN(chunkb);
112         if (anum && !bnum)
113             return -1;
114         if (bnum && !anum)
115             return 1;
116         if (anum && bnum) {
117             diff = chunka - chunkb;
118             if (diff === 0 && chunka.length !== chunkb.length) {
119                 if (!+chunka && !+chunkb) // chunks are strings of all 0s (special case)
120                     return chunka.length - chunkb.length;
121                 else
122                     return chunkb.length - chunka.length;
123             }
124         } else if (chunka !== chunkb)
125             return (chunka < chunkb) ? -1 : 1;
126         a = a.substring(chunka.length);
127         b = b.substring(chunkb.length);
128     }
129     return diff;
130 };
131
132 WebInspector.ObjectTreeView.prototype = {
133     constructor: WebInspector.ObjectTreeView,
134     __proto__: WebInspector.Object,
135
136     // Public
137
138     get object()
139     {
140         return this._object;
141     },
142
143     get element()
144     {
145         return this._element;
146     },
147
148     get expanded()
149     {
150         return this._expanded;
151     },
152
153     expand: function()
154     {
155         if (this._expanded)
156             return;
157
158         this._expanded = true;
159         this._element.classList.add("expanded");
160
161         if (this._previewElement) {
162             this._previewElement.hidden = true;
163             this._titleElement.hidden = false;
164         }
165
166         this.update();
167     },
168
169     collapse: function()
170     {
171         if (!this._expanded)
172             return;
173
174         this._expanded = false;
175         this._element.classList.remove("expanded");
176
177         if (this._previewElement) {
178             this._previewElement.hidden = false;
179             this._titleElement.hidden = true;
180         }
181     },
182
183     // Protected
184
185     update: function()
186     {
187         this._object.getOwnAndGetterPropertyDescriptors(this._updateProperties.bind(this));
188     },
189
190     // Private
191
192     _updateProperties: function(properties)
193     {
194         properties.sort(WebInspector.ObjectTreeView.ComparePropertyDescriptors);
195
196         this._outline.removeChildren();
197
198         for (var propertyDescriptor of properties)
199             this._outline.appendChild(new WebInspector.ObjectTreePropertyTreeElement(propertyDescriptor, this._mode));
200
201         if (this._mode === WebInspector.ObjectTreeView.Mode.Properties) {
202             if (this._object.isCollectionType())
203                 this._outline.appendChild(new WebInspector.ObjectTreeCollectionTreeElement(this._object));
204         }
205
206         if (!this._outline.children.length) {
207             var emptyMessageElement = document.createElement("div");
208             emptyMessageElement.className = "empty-message";
209             emptyMessageElement.textContent = WebInspector.UIString("No Properties");;
210             this._outline.appendChild(new TreeElement(emptyMessageElement, null, false));
211         }
212     },
213
214     _handlePreviewOrTitleElementClick: function(event)
215     {
216         if (this._hasLosslessPreview)
217             return;
218
219         if (!this._expanded)
220             this.expand();
221         else
222             this.collapse();
223
224         event.stopPropagation();
225     }
226 };