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 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 91 WebInspector.ObjectPropertiesSection.prototype.__proto__ = WebInspector.PropertiesSection.prototype; 92 93 WebInspector.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 138 WebInspector.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 146 WebInspector.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 314 WebInspector.ObjectPropertyTreeElement.prototype.__proto__ = TreeElement.prototype; 315