2 * Copyright (C) 2013 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
26 WebInspector.ObjectPropertiesSection = function(object, title, subtitle, emptyPlaceholder, getAllProperties, extraProperties, treeElementConstructor)
28 this.emptyPlaceholder = (emptyPlaceholder || WebInspector.UIString("No Properties"));
30 this.getAllProperties = getAllProperties;
31 this.extraProperties = extraProperties;
32 this.treeElementConstructor = treeElementConstructor || WebInspector.ObjectPropertyTreeElement;
35 WebInspector.PropertiesSection.call(this, title, subtitle);
38 WebInspector.ObjectPropertiesSection.prototype = {
39 onpopulate: function()
46 function callback(properties)
51 this.updateProperties(properties);
54 if (this.getAllProperties)
55 this.object.deprecatedGetAllProperties(callback.bind(this));
57 this.object.deprecatedGetDisplayableProperties(callback.bind(this));
60 updateProperties: function(properties, rootTreeElementConstructor, rootPropertyComparer)
62 if (!rootTreeElementConstructor)
63 rootTreeElementConstructor = this.treeElementConstructor;
65 if (!rootPropertyComparer)
66 rootPropertyComparer = WebInspector.ObjectPropertiesSection.CompareProperties;
68 if (this.extraProperties)
69 for (var i = 0; i < this.extraProperties.length; ++i)
70 properties.push(this.extraProperties[i]);
72 properties.sort(rootPropertyComparer);
74 this.propertiesTreeOutline.removeChildren();
76 for (var i = 0; i < properties.length; ++i) {
77 properties[i].parentObject = this.object;
78 this.propertiesTreeOutline.appendChild(new rootTreeElementConstructor(properties[i]));
81 if (!this.propertiesTreeOutline.children.length) {
82 var title = document.createElement("div");
83 title.className = "info";
84 title.textContent = this.emptyPlaceholder;
85 var infoElement = new TreeElement(title, null, false);
86 this.propertiesTreeOutline.appendChild(infoElement);
88 this.propertiesForTest = properties;
90 if (this.object.isCollectionType())
91 this.propertiesTreeOutline.appendChild(new WebInspector.CollectionEntriesMainTreeElement(this.object));
93 this.dispatchEventToListeners(WebInspector.Section.Event.VisibleContentDidChange);
97 WebInspector.ObjectPropertiesSection.prototype.__proto__ = WebInspector.PropertiesSection.prototype;
99 WebInspector.ObjectPropertiesSection.CompareProperties = function(propertyA, propertyB)
101 var a = propertyA.name;
102 var b = propertyB.name;
103 if (a === "__proto__")
105 if (b === "__proto__")
108 // if used elsewhere make sure to
109 // - convert a and b to strings (not needed here, properties are all strings)
110 // - check if a == b (not needed here, no two properties can be the same)
113 var chunk = /^\d+|^\D+/;
114 var chunka, chunkb, anum, bnum;
120 chunka = a.match(chunk)[0];
121 chunkb = b.match(chunk)[0];
122 anum = !isNaN(chunka);
123 bnum = !isNaN(chunkb);
129 diff = chunka - chunkb;
130 if (diff === 0 && chunka.length !== chunkb.length) {
131 if (!+chunka && !+chunkb) // chunks are strings of all 0s (special case)
132 return chunka.length - chunkb.length;
134 return chunkb.length - chunka.length;
136 } else if (chunka !== chunkb)
137 return (chunka < chunkb) ? -1 : 1;
138 a = a.substring(chunka.length);
139 b = b.substring(chunkb.length);
144 WebInspector.ObjectPropertyTreeElement = function(property)
146 this.property = property;
148 // Pass an empty title, the title gets made later in onattach.
149 TreeElement.call(this, "", null, false);
150 this.toggleOnClick = true;
151 this.selectable = false;
154 WebInspector.ObjectPropertyTreeElement.prototype = {
155 onpopulate: function()
157 if (this.children.length && !this.shouldRefreshChildren)
160 function callback(properties) {
161 this.removeChildren();
165 properties.sort(WebInspector.ObjectPropertiesSection.CompareProperties);
166 for (var i = 0; i < properties.length; ++i)
167 this.appendChild(new this.treeOutline.section.treeElementConstructor(properties[i]));
169 if (this.property.value.isCollectionType())
170 this.appendChild(new WebInspector.CollectionEntriesMainTreeElement(this.property.value));
173 if (this.property.name === "__proto__")
174 this.property.value.deprecatedGetOwnProperties(callback.bind(this));
176 this.property.value.deprecatedGetDisplayableProperties(callback.bind(this));
179 ondblclick: function(event)
181 if (this.property.writable)
192 this.nameElement = document.createElement("span");
193 this.nameElement.className = "name";
194 this.nameElement.textContent = this.property.name;
195 if (!this.property.enumerable && (!this.parent.root || !this.treeOutline.section.dontHighlightNonEnumerablePropertiesAtTopLevel))
196 this.nameElement.classList.add("dimmed");
198 var separatorElement = document.createElement("span");
199 separatorElement.className = "separator";
200 separatorElement.textContent = ": ";
202 this.valueElement = document.createElement("span");
203 this.valueElement.className = "value";
205 var description = this.property.value.description;
206 // Render \n as a nice unicode cr symbol.
207 if (this.property.wasThrown)
208 this.valueElement.textContent = "[Exception: " + description + "]";
209 else if (this.property.value.type === "string" && typeof description === "string") {
210 this.valueElement.textContent = "\"" + description.replace(/\n/g, "\u21B5").replace(/"/g, "\\\"") + "\"";
211 this.valueElement._originalTextContent = "\"" + description + "\"";
212 } else if (this.property.value.type === "function" && typeof description === "string") {
213 this.valueElement.textContent = /.*/.exec(description)[0].replace(/ +$/g, "");
214 this.valueElement._originalTextContent = description;
216 this.valueElement.textContent = description;
218 if (this.property.value.type === "function")
219 this.valueElement.addEventListener("contextmenu", this._functionContextMenuEventFired.bind(this), false);
221 if (this.property.wasThrown)
222 this.valueElement.classList.add("error");
223 if (this.property.value.subtype)
224 this.valueElement.classList.add("formatted-" + this.property.value.subtype);
225 else if (this.property.value.type)
226 this.valueElement.classList.add("formatted-" + this.property.value.type);
227 if (this.property.value.subtype === "node")
228 this.valueElement.addEventListener("contextmenu", this._contextMenuEventFired.bind(this), false);
230 this.listItemElement.removeChildren();
232 this.listItemElement.appendChild(this.nameElement);
233 this.listItemElement.appendChild(separatorElement);
234 this.listItemElement.appendChild(this.valueElement);
235 this.hasChildren = this.property.value.hasChildren && !this.property.wasThrown;
238 _contextMenuEventFired: function(event)
240 function selectNode(nodeId)
243 WebInspector.domTreeManager.inspectElement(nodeId);
246 function revealElement()
248 this.property.value.pushNodeToFrontend(selectNode);
251 var contextMenu = new WebInspector.ContextMenu(event);
252 contextMenu.appendItem(WebInspector.UIString("Reveal in DOM Tree"), revealElement.bind(this));
256 _functionContextMenuEventFired: function(event)
258 function didGetLocation(error, response)
263 var location = response.location;
264 var sourceCode = WebInspector.debuggerManager.scriptForIdentifier(location.scriptId);
268 var sourceCodeLocation = sourceCode.createSourceCodeLocation(location.lineNumber, location.columnNumber || 0);
269 WebInspector.resourceSidebarPanel.showSourceCodeLocation(sourceCodeLocation);
272 function revealFunction()
274 DebuggerAgent.getFunctionDetails(this.property.value.objectId, didGetLocation);
277 var contextMenu = new WebInspector.ContextMenu(event);
278 contextMenu.appendItem(WebInspector.UIString("Jump to Definition"), revealFunction.bind(this));
282 updateSiblings: function()
284 if (this.parent.root)
285 this.treeOutline.section.update();
287 this.parent.shouldRefreshChildren = true;
290 startEditing: function()
292 if (WebInspector.isBeingEdited(this.valueElement) || !this.treeOutline.section.editable)
295 var context = { expanded: this.expanded };
297 // Lie about our children to prevent expanding on double click and to collapse subproperties.
298 this.hasChildren = false;
300 this.listItemElement.classList.add("editing-sub-part");
302 // Edit original source.
303 if (typeof this.valueElement._originalTextContent === "string")
304 this.valueElement.textContent = this.valueElement._originalTextContent;
306 var config = new WebInspector.EditingConfig(this.editingCommitted.bind(this), this.editingCancelled.bind(this), context);
307 WebInspector.startEditing(this.valueElement, config);
310 editingEnded: function(context)
312 this.listItemElement.scrollLeft = 0;
313 this.listItemElement.classList.remove("editing-sub-part");
314 if (context.expanded)
318 editingCancelled: function(element, context)
321 this.editingEnded(context);
324 editingCommitted: function(element, userInput, previousContent, context)
326 if (userInput === previousContent)
327 return this.editingCancelled(element, context); // nothing changed, so cancel
329 this.applyExpression(userInput, true);
331 this.editingEnded(context);
334 applyExpression: function(expression, updateInterface)
336 expression = expression.trim();
337 var expressionLength = expression.length;
338 function callback(error)
340 if (!updateInterface)
346 if (!expressionLength) {
347 // The property was deleted, so remove this tree element.
348 this.parent.removeChild(this);
350 // Call updateSiblings since their value might be based on the value that just changed.
351 this.updateSiblings();
354 this.property.parentObject.setPropertyValue(this.property.name, expression.trim(), callback.bind(this));
358 WebInspector.ObjectPropertyTreeElement.prototype.__proto__ = TreeElement.prototype;
360 WebInspector.CollectionEntriesMainTreeElement = function(remoteObject)
362 TreeElement.call(this, "<entries>", null, false);
364 console.assert(remoteObject);
366 this._remoteObject = remoteObject;
367 this._requestingEntries = false;
368 this._trackingEntries = false;
370 this.toggleOnClick = true;
371 this.selectable = false;
372 this.hasChildren = true;
375 // FIXME: When a parent TreeElement is collapsed, we do not get a chance
376 // to releaseWeakCollectionEntries. We should.
379 WebInspector.CollectionEntriesMainTreeElement.prototype = {
380 constructor: WebInspector.CollectionEntriesMainTreeElement,
381 __proto__: TreeElement.prototype,
385 if (this.children.length && !this.shouldRefreshChildren)
388 if (this._requestingEntries)
391 this._requestingEntries = true;
393 function callback(entries) {
394 this._requestingEntries = false;
396 this.removeChildren();
398 if (!entries || !entries.length) {
399 this.appendChild(new WebInspector.EmptyCollectionTreeElement);
403 this._trackWeakEntries();
405 for (var i = 0; i < entries.length; ++i) {
406 var entry = entries[i];
408 this.appendChild(new WebInspector.CollectionEntryTreeElement(entry, i));
410 this.appendChild(new WebInspector.ObjectPropertyTreeElement({
420 this._remoteObject.getCollectionEntries(0, 100, callback.bind(this));
423 oncollapse: function()
425 this._untrackWeakEntries();
430 this._untrackWeakEntries();
435 _trackWeakEntries: function()
437 if (!this._remoteObject.isWeakCollection())
440 if (this._trackingEntries)
443 this._trackingEntries = true;
445 WebInspector.logManager.addEventListener(WebInspector.LogManager.Event.Cleared, this._untrackWeakEntries, this);
446 WebInspector.logManager.addEventListener(WebInspector.LogManager.Event.ActiveLogCleared, this._untrackWeakEntries, this);
447 WebInspector.logManager.addEventListener(WebInspector.LogManager.Event.SessionStarted, this._untrackWeakEntries, this);
450 _untrackWeakEntries: function()
452 if (!this._remoteObject.isWeakCollection())
455 if (!this._trackingEntries)
458 this._trackingEntries = false;
460 this._remoteObject.releaseWeakCollectionEntries();
462 WebInspector.logManager.removeEventListener(WebInspector.LogManager.Event.Cleared, this._untrackWeakEntries, this);
463 WebInspector.logManager.removeEventListener(WebInspector.LogManager.Event.ActiveLogCleared, this._untrackWeakEntries, this);
464 WebInspector.logManager.removeEventListener(WebInspector.LogManager.Event.SessionStarted, this._untrackWeakEntries, this);
466 this.removeChildren();
473 WebInspector.CollectionEntryTreeElement = function(entry, index)
475 TreeElement.call(this, "", null, false);
477 console.assert(entry);
479 this._name = "" + index;
480 this._key = entry.key;
481 this._value = entry.value;
483 this.toggleOnClick = true;
484 this.selectable = false;
485 this.hasChildren = true;
488 WebInspector.CollectionEntryTreeElement.prototype = {
489 constructor: WebInspector.CollectionEntryTreeElement,
490 __proto__: TreeElement.prototype,
492 onpopulate: function()
494 if (this.children.length && !this.shouldRefreshChildren)
497 this.appendChild(new WebInspector.ObjectPropertyTreeElement({
504 this.appendChild(new WebInspector.ObjectPropertyTreeElement({
514 var nameElement = document.createElement("span");
515 nameElement.className = "name";
516 nameElement.textContent = "" + this._name;
518 var separatorElement = document.createElement("span");
519 separatorElement.className = "separator";
520 separatorElement.textContent = ": ";
522 var valueElement = document.createElement("span");
523 valueElement.className = "value";
524 valueElement.textContent = "{" + this._key.description + " => " + this._value.description + "}";
526 this.listItemElement.removeChildren();
527 this.listItemElement.appendChild(nameElement);
528 this.listItemElement.appendChild(separatorElement);
529 this.listItemElement.appendChild(valueElement);
533 WebInspector.EmptyCollectionTreeElement = function()
535 TreeElement.call(this, WebInspector.UIString("Empty Collection"), null, false);
537 this.selectable = false;
540 WebInspector.EmptyCollectionTreeElement.prototype = {
541 constructor: WebInspector.EmptyCollectionTreeElement,
542 __proto__: TreeElement.prototype