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