OSDN Git Service

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