• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (C) 2008 Apple Inc. All Rights Reserved.
3 * Copyright (C) 2009 Joseph Pecoraro
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27WebInspector.ObjectPropertiesSection = function(object, title, subtitle, emptyPlaceholder, ignoreHasOwnProperty, extraProperties, treeElementConstructor)
28{
29    this.emptyPlaceholder = (emptyPlaceholder || WebInspector.UIString("No Properties"));
30    this.object = object;
31    this.ignoreHasOwnProperty = ignoreHasOwnProperty;
32    this.extraProperties = extraProperties;
33    this.treeElementConstructor = treeElementConstructor || WebInspector.ObjectPropertyTreeElement;
34    this.editable = true;
35
36    WebInspector.PropertiesSection.call(this, title, subtitle);
37}
38
39WebInspector.ObjectPropertiesSection.prototype = {
40    onpopulate: function()
41    {
42        this.update();
43    },
44
45    update: function()
46    {
47        var self = this;
48        var callback = function(properties) {
49            if (!properties)
50                return;
51            self.updateProperties(properties);
52        };
53        InjectedScriptAccess.get(this.object.injectedScriptId).getProperties(this.object, this.ignoreHasOwnProperty, true, callback);
54    },
55
56    updateProperties: function(properties, rootTreeElementConstructor, rootPropertyComparer)
57    {
58        if (!rootTreeElementConstructor)
59            rootTreeElementConstructor = this.treeElementConstructor;
60
61        if (!rootPropertyComparer)
62            rootPropertyComparer = WebInspector.ObjectPropertiesSection.CompareProperties;
63
64        if (this.extraProperties)
65            for (var i = 0; i < this.extraProperties.length; ++i)
66                properties.push(this.extraProperties[i]);
67
68        properties.sort(rootPropertyComparer);
69
70        this.propertiesTreeOutline.removeChildren();
71
72        for (var i = 0; i < properties.length; ++i)
73            this.propertiesTreeOutline.appendChild(new rootTreeElementConstructor(properties[i]));
74
75        if (!this.propertiesTreeOutline.children.length) {
76            var title = "<div class=\"info\">" + this.emptyPlaceholder + "</div>";
77            var infoElement = new TreeElement(title, null, false);
78            this.propertiesTreeOutline.appendChild(infoElement);
79        }
80    }
81}
82
83WebInspector.ObjectPropertiesSection.prototype.__proto__ = WebInspector.PropertiesSection.prototype;
84
85WebInspector.ObjectPropertiesSection.CompareProperties = function(propertyA, propertyB)
86{
87    var a = propertyA.name;
88    var b = propertyB.name;
89
90    // if used elsewhere make sure to
91    //  - convert a and b to strings (not needed here, properties are all strings)
92    //  - check if a == b (not needed here, no two properties can be the same)
93
94    var diff = 0;
95    var chunk = /^\d+|^\D+/;
96    var chunka, chunkb, anum, bnum;
97    while (diff === 0) {
98        if (!a && b)
99            return -1;
100        if (!b && a)
101            return 1;
102        chunka = a.match(chunk)[0];
103        chunkb = b.match(chunk)[0];
104        anum = !isNaN(chunka);
105        bnum = !isNaN(chunkb);
106        if (anum && !bnum)
107            return -1;
108        if (bnum && !anum)
109            return 1;
110        if (anum && bnum) {
111            diff = chunka - chunkb;
112            if (diff === 0 && chunka.length !== chunkb.length) {
113                if (!+chunka && !+chunkb) // chunks are strings of all 0s (special case)
114                    return chunka.length - chunkb.length;
115                else
116                    return chunkb.length - chunka.length;
117            }
118        } else if (chunka !== chunkb)
119            return (chunka < chunkb) ? -1 : 1;
120        a = a.substring(chunka.length);
121        b = b.substring(chunkb.length);
122    }
123    return diff;
124}
125
126WebInspector.ObjectPropertyTreeElement = function(property)
127{
128    this.property = property;
129
130    // Pass an empty title, the title gets made later in onattach.
131    TreeElement.call(this, "", null, false);
132}
133
134WebInspector.ObjectPropertyTreeElement.prototype = {
135    onpopulate: function()
136    {
137        if (this.children.length && !this.shouldRefreshChildren)
138            return;
139
140        var callback = function(properties) {
141            this.removeChildren();
142            if (!properties)
143                return;
144
145            properties.sort(WebInspector.ObjectPropertiesSection.CompareProperties);
146            for (var i = 0; i < properties.length; ++i) {
147                this.appendChild(new this.treeOutline.section.treeElementConstructor(properties[i]));
148            }
149        };
150        InjectedScriptAccess.get(this.property.value.injectedScriptId).getProperties(this.property.value, false, true, callback.bind(this));
151    },
152
153    ondblclick: function(event)
154    {
155        this.startEditing();
156    },
157
158    onattach: function()
159    {
160        this.update();
161    },
162
163    update: function()
164    {
165        this.nameElement = document.createElement("span");
166        this.nameElement.className = "name";
167        this.nameElement.textContent = this.property.name;
168
169        var separatorElement = document.createElement("span");
170        separatorElement.className = "separator";
171        separatorElement.textContent = ": ";
172
173        this.valueElement = document.createElement("span");
174        this.valueElement.className = "value";
175        this.valueElement.textContent = this.property.value.description;
176        if (this.property.isGetter)
177           this.valueElement.addStyleClass("dimmed");
178
179        this.listItemElement.removeChildren();
180
181        this.listItemElement.appendChild(this.nameElement);
182        this.listItemElement.appendChild(separatorElement);
183        this.listItemElement.appendChild(this.valueElement);
184        this.hasChildren = this.property.value.hasChildren;
185    },
186
187    updateSiblings: function()
188    {
189        if (this.parent.root)
190            this.treeOutline.section.update();
191        else
192            this.parent.shouldRefreshChildren = true;
193    },
194
195    startEditing: function()
196    {
197        if (WebInspector.isBeingEdited(this.valueElement) || !this.treeOutline.section.editable)
198            return;
199
200        var context = { expanded: this.expanded };
201
202        // Lie about our children to prevent expanding on double click and to collapse subproperties.
203        this.hasChildren = false;
204
205        this.listItemElement.addStyleClass("editing-sub-part");
206
207        WebInspector.startEditing(this.valueElement, this.editingCommitted.bind(this), this.editingCancelled.bind(this), context);
208    },
209
210    editingEnded: function(context)
211    {
212        this.listItemElement.scrollLeft = 0;
213        this.listItemElement.removeStyleClass("editing-sub-part");
214        if (context.expanded)
215            this.expand();
216    },
217
218    editingCancelled: function(element, context)
219    {
220        this.update();
221        this.editingEnded(context);
222    },
223
224    editingCommitted: function(element, userInput, previousContent, context)
225    {
226        if (userInput === previousContent)
227            return this.editingCancelled(element, context); // nothing changed, so cancel
228
229        this.applyExpression(userInput, true);
230
231        this.editingEnded(context);
232    },
233
234    applyExpression: function(expression, updateInterface)
235    {
236        expression = expression.trim();
237        var expressionLength = expression.length;
238        var self = this;
239        var callback = function(success) {
240            if (!updateInterface)
241                return;
242
243            if (!success)
244                self.update();
245
246            if (!expressionLength) {
247                // The property was deleted, so remove this tree element.
248                self.parent.removeChild(this);
249            } else {
250                // Call updateSiblings since their value might be based on the value that just changed.
251                self.updateSiblings();
252            }
253        };
254        InjectedScriptAccess.get(this.property.parentObjectProxy.injectedScriptId).setPropertyValue(this.property.parentObjectProxy, this.property.name, expression.trim(), callback);
255    }
256}
257
258WebInspector.ObjectPropertyTreeElement.prototype.__proto__ = TreeElement.prototype;
259