• 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._update(properties);
52        };
53        InspectorController.getProperties(this.object, this.ignoreHasOwnProperty, callback);
54    },
55
56    _update: function(properties)
57    {
58        if (this.extraProperties)
59            for (var prop in this.extraProperties)
60                properties.push(new WebInspector.ObjectPropertyProxy(prop, this.extraProperties[prop]));
61        properties.sort(this._displaySort);
62
63        this.propertiesTreeOutline.removeChildren();
64
65        for (var i = 0; i < properties.length; ++i)
66            this.propertiesTreeOutline.appendChild(new this.treeElementConstructor(properties[i]));
67
68        if (!this.propertiesTreeOutline.children.length) {
69            var title = "<div class=\"info\">" + this.emptyPlaceholder + "</div>";
70            var infoElement = new TreeElement(title, null, false);
71            this.propertiesTreeOutline.appendChild(infoElement);
72        }
73    },
74
75    _displaySort: function(propertyA, propertyB) {
76        var a = propertyA.name;
77        var b = propertyB.name;
78
79        // if used elsewhere make sure to
80        //  - convert a and b to strings (not needed here, properties are all strings)
81        //  - check if a == b (not needed here, no two properties can be the same)
82
83        var diff = 0;
84        var chunk = /^\d+|^\D+/;
85        var chunka, chunkb, anum, bnum;
86        while (diff === 0) {
87            if (!a && b)
88                return -1;
89            if (!b && a)
90                return 1;
91            chunka = a.match(chunk)[0];
92            chunkb = b.match(chunk)[0];
93            anum = !isNaN(chunka);
94            bnum = !isNaN(chunkb);
95            if (anum && !bnum)
96                return -1;
97            if (bnum && !anum)
98                return 1;
99            if (anum && bnum) {
100                diff = chunka - chunkb;
101                if (diff === 0 && chunka.length !== chunkb.length) {
102                    if (!+chunka && !+chunkb) // chunks are strings of all 0s (special case)
103                        return chunka.length - chunkb.length;
104                    else
105                        return chunkb.length - chunka.length;
106                }
107            } else if (chunka !== chunkb)
108                return (chunka < chunkb) ? -1 : 1;
109            a = a.substring(chunka.length);
110            b = b.substring(chunkb.length);
111        }
112        return diff;
113    }
114}
115
116WebInspector.ObjectPropertiesSection.prototype.__proto__ = WebInspector.PropertiesSection.prototype;
117
118WebInspector.ObjectPropertyTreeElement = function(property)
119{
120    this.property = property;
121
122    // Pass an empty title, the title gets made later in onattach.
123    TreeElement.call(this, "", null, false);
124}
125
126WebInspector.ObjectPropertyTreeElement.prototype = {
127    safePropertyValue: function(object, propertyName)
128    {
129        if (object["__lookupGetter__"] && object.__lookupGetter__(propertyName))
130            return;
131        return object[propertyName];
132    },
133
134    onpopulate: function()
135    {
136        if (this.children.length && !this.shouldRefreshChildren)
137            return;
138
139        var self = this;
140        var callback = function(properties) {
141            self.removeChildren();
142            if (!properties)
143                return;
144
145            properties.sort(self._displaySort);
146            for (var i = 0; i < properties.length; ++i) {
147                self.appendChild(new self.treeOutline.section.treeElementConstructor(properties[i]));
148            }
149        };
150        InspectorController.getProperties(this.property.childObjectProxy, false, callback);
151    },
152
153    ondblclick: function(element, event)
154    {
155        this.startEditing();
156    },
157
158    onattach: function()
159    {
160        this.update();
161    },
162
163    update: function()
164    {
165        var nameElement = document.createElement("span");
166        nameElement.className = "name";
167        nameElement.textContent = this.property.name;
168
169        this.valueElement = document.createElement("span");
170        this.valueElement.className = "value";
171        this.valueElement.textContent = this.property.textContent;
172        if (this.property.isGetter)
173           this.valueElement.addStyleClass("dimmed");
174
175        this.listItemElement.removeChildren();
176
177        this.listItemElement.appendChild(nameElement);
178        this.listItemElement.appendChild(document.createTextNode(": "));
179        this.listItemElement.appendChild(this.valueElement);
180        this.hasChildren = this.property.hasChildren;
181    },
182
183    updateSiblings: function()
184    {
185        if (this.parent.root)
186            this.treeOutline.section.update();
187        else
188            this.parent.shouldRefreshChildren = true;
189    },
190
191    startEditing: function()
192    {
193        if (WebInspector.isBeingEdited(this.valueElement) || !this.treeOutline.section.editable)
194            return;
195
196        var context = { expanded: this.expanded };
197
198        // Lie about our children to prevent expanding on double click and to collapse subproperties.
199        this.hasChildren = false;
200
201        this.listItemElement.addStyleClass("editing-sub-part");
202
203        WebInspector.startEditing(this.valueElement, this.editingCommitted.bind(this), this.editingCancelled.bind(this), context);
204    },
205
206    editingEnded: function(context)
207    {
208        this.listItemElement.scrollLeft = 0;
209        this.listItemElement.removeStyleClass("editing-sub-part");
210        if (context.expanded)
211            this.expand();
212    },
213
214    editingCancelled: function(element, context)
215    {
216        this.update();
217        this.editingEnded(context);
218    },
219
220    editingCommitted: function(element, userInput, previousContent, context)
221    {
222        if (userInput === previousContent)
223            return this.editingCancelled(element, context); // nothing changed, so cancel
224
225        this.applyExpression(userInput, true);
226
227        this.editingEnded(context);
228    },
229
230    applyExpression: function(expression, updateInterface)
231    {
232        expression = expression.trimWhitespace();
233        var expressionLength = expression.length;
234        var self = this;
235        var callback = function(success) {
236            if (!updateInterface)
237                return;
238
239            if (!success)
240                self.update();
241
242            if (!expressionLength) {
243                // The property was deleted, so remove this tree element.
244                self.parent.removeChild(this);
245            } else {
246                // Call updateSiblings since their value might be based on the value that just changed.
247                self.updateSiblings();
248            }
249        };
250        InspectorController.setPropertyValue(this.property.parentObjectProxy, this.property.name, expression.trimWhitespace(), callback);
251    }
252}
253
254WebInspector.ObjectPropertyTreeElement.prototype.__proto__ = TreeElement.prototype;
255