• 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        function callback(properties)
49        {
50            if (!properties)
51                return;
52            self.updateProperties(properties);
53        }
54        if (this.ignoreHasOwnProperty)
55            this.object.getAllProperties(callback);
56        else
57            this.object.getOwnProperties(callback);
58    },
59
60    updateProperties: function(properties, rootTreeElementConstructor, rootPropertyComparer)
61    {
62        if (!rootTreeElementConstructor)
63            rootTreeElementConstructor = this.treeElementConstructor;
64
65        if (!rootPropertyComparer)
66            rootPropertyComparer = WebInspector.ObjectPropertiesSection.CompareProperties;
67
68        if (this.extraProperties)
69            for (var i = 0; i < this.extraProperties.length; ++i)
70                properties.push(this.extraProperties[i]);
71
72        properties.sort(rootPropertyComparer);
73
74        this.propertiesTreeOutline.removeChildren();
75
76        for (var i = 0; i < properties.length; ++i) {
77            properties[i].parentObject = this.object;
78            this.propertiesTreeOutline.appendChild(new rootTreeElementConstructor(properties[i]));
79        }
80
81        if (!this.propertiesTreeOutline.children.length) {
82            var title = "<div class=\"info\">" + this.emptyPlaceholder + "</div>";
83            var infoElement = new TreeElement(null, null, false);
84            infoElement.titleHTML = title;
85            this.propertiesTreeOutline.appendChild(infoElement);
86        }
87        this.propertiesForTest = properties;
88    }
89}
90
91WebInspector.ObjectPropertiesSection.prototype.__proto__ = WebInspector.PropertiesSection.prototype;
92
93WebInspector.ObjectPropertiesSection.CompareProperties = function(propertyA, propertyB)
94{
95    var a = propertyA.name;
96    var b = propertyB.name;
97    if (a === "__proto__")
98        return 1;
99    if (b === "__proto__")
100        return -1;
101
102    // if used elsewhere make sure to
103    //  - convert a and b to strings (not needed here, properties are all strings)
104    //  - check if a == b (not needed here, no two properties can be the same)
105
106    var diff = 0;
107    var chunk = /^\d+|^\D+/;
108    var chunka, chunkb, anum, bnum;
109    while (diff === 0) {
110        if (!a && b)
111            return -1;
112        if (!b && a)
113            return 1;
114        chunka = a.match(chunk)[0];
115        chunkb = b.match(chunk)[0];
116        anum = !isNaN(chunka);
117        bnum = !isNaN(chunkb);
118        if (anum && !bnum)
119            return -1;
120        if (bnum && !anum)
121            return 1;
122        if (anum && bnum) {
123            diff = chunka - chunkb;
124            if (diff === 0 && chunka.length !== chunkb.length) {
125                if (!+chunka && !+chunkb) // chunks are strings of all 0s (special case)
126                    return chunka.length - chunkb.length;
127                else
128                    return chunkb.length - chunka.length;
129            }
130        } else if (chunka !== chunkb)
131            return (chunka < chunkb) ? -1 : 1;
132        a = a.substring(chunka.length);
133        b = b.substring(chunkb.length);
134    }
135    return diff;
136}
137
138WebInspector.ObjectPropertyTreeElement = function(property)
139{
140    this.property = property;
141
142    // Pass an empty title, the title gets made later in onattach.
143    TreeElement.call(this, "", null, false);
144}
145
146WebInspector.ObjectPropertyTreeElement.prototype = {
147    onpopulate: function()
148    {
149        if (this.children.length && !this.shouldRefreshChildren)
150            return;
151
152        var callback = function(properties) {
153            this.removeChildren();
154            if (!properties)
155                return;
156
157            properties.sort(WebInspector.ObjectPropertiesSection.CompareProperties);
158            for (var i = 0; i < properties.length; ++i) {
159                this.appendChild(new this.treeOutline.section.treeElementConstructor(properties[i]));
160            }
161        };
162        this.property.value.getOwnProperties(callback.bind(this));
163    },
164
165    ondblclick: function(event)
166    {
167        this.startEditing();
168    },
169
170    onattach: function()
171    {
172        this.update();
173    },
174
175    update: function()
176    {
177        this.nameElement = document.createElement("span");
178        this.nameElement.className = "name";
179        this.nameElement.textContent = this.property.name;
180
181        var separatorElement = document.createElement("span");
182        separatorElement.className = "separator";
183        separatorElement.textContent = ": ";
184
185        this.valueElement = document.createElement("span");
186        this.valueElement.className = "value";
187
188        var description = this.property.value.description;
189        // Render \n as a nice unicode cr symbol.
190        if (this.property.value.type === "string" && typeof description === "string") {
191            this.valueElement.textContent = "\"" + description.replace(/\n/g, "\u21B5") + "\"";
192            this.valueElement._originalTextContent = "\"" + description + "\"";
193        } else if (this.property.value.type === "function" && typeof description === "string") {
194            this.valueElement.textContent = /.*/.exec(description)[0].replace(/ +$/g, "");
195            this.valueElement._originalTextContent = description;
196        } else
197            this.valueElement.textContent = description;
198
199        if (this.property.isGetter)
200            this.valueElement.addStyleClass("dimmed");
201        if (this.property.value.isError())
202            this.valueElement.addStyleClass("error");
203        if (this.property.value.type)
204            this.valueElement.addStyleClass("console-formatted-" + this.property.value.type);
205        if (this.property.value.type === "node")
206            this.valueElement.addEventListener("contextmenu", this._contextMenuEventFired.bind(this), false);
207
208        this.listItemElement.removeChildren();
209
210        this.listItemElement.appendChild(this.nameElement);
211        this.listItemElement.appendChild(separatorElement);
212        this.listItemElement.appendChild(this.valueElement);
213        this.hasChildren = this.property.value.hasChildren;
214    },
215
216    _contextMenuEventFired: function()
217    {
218        function selectNode(nodeId)
219        {
220            if (nodeId) {
221                WebInspector.panels.elements.switchToAndFocus(WebInspector.domAgent.nodeForId(nodeId));
222            }
223        }
224
225        function revealElement()
226        {
227            this.property.value.pushNodeToFrontend(selectNode);
228        }
229
230        var contextMenu = new WebInspector.ContextMenu();
231        contextMenu.appendItem(WebInspector.UIString("Reveal in Elements Panel"), revealElement.bind(this));
232        contextMenu.show(event);
233    },
234
235    updateSiblings: function()
236    {
237        if (this.parent.root)
238            this.treeOutline.section.update();
239        else
240            this.parent.shouldRefreshChildren = true;
241    },
242
243    startEditing: function()
244    {
245        if (WebInspector.isBeingEdited(this.valueElement) || !this.treeOutline.section.editable)
246            return;
247
248        var context = { expanded: this.expanded };
249
250        // Lie about our children to prevent expanding on double click and to collapse subproperties.
251        this.hasChildren = false;
252
253        this.listItemElement.addStyleClass("editing-sub-part");
254
255        // Edit original source.
256        if (typeof this.valueElement._originalTextContent === "string")
257            this.valueElement.textContent = this.valueElement._originalTextContent;
258
259        WebInspector.startEditing(this.valueElement, {
260            context: context,
261            commitHandler: this.editingCommitted.bind(this),
262            cancelHandler: this.editingCancelled.bind(this)
263        });
264    },
265
266    editingEnded: function(context)
267    {
268        this.listItemElement.scrollLeft = 0;
269        this.listItemElement.removeStyleClass("editing-sub-part");
270        if (context.expanded)
271            this.expand();
272    },
273
274    editingCancelled: function(element, context)
275    {
276        this.update();
277        this.editingEnded(context);
278    },
279
280    editingCommitted: function(element, userInput, previousContent, context)
281    {
282        if (userInput === previousContent)
283            return this.editingCancelled(element, context); // nothing changed, so cancel
284
285        this.applyExpression(userInput, true);
286
287        this.editingEnded(context);
288    },
289
290    applyExpression: function(expression, updateInterface)
291    {
292        expression = expression.trim();
293        var expressionLength = expression.length;
294        function callback(error)
295        {
296            if (!updateInterface)
297                return;
298
299            if (error)
300                this.update();
301
302            if (!expressionLength) {
303                // The property was deleted, so remove this tree element.
304                this.parent.removeChild(this);
305            } else {
306                // Call updateSiblings since their value might be based on the value that just changed.
307                this.updateSiblings();
308            }
309        };
310        this.property.parentObject.setPropertyValue(this.property.name, expression.trim(), callback.bind(this));
311    }
312}
313
314WebInspector.ObjectPropertyTreeElement.prototype.__proto__ = TreeElement.prototype;
315