2 * Copyright (C) 2015 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.ObjectTreePropertyTreeElement = function(property, propertyPath, mode, prototypeName)
28 console.assert(property instanceof WebInspector.PropertyDescriptor);
29 console.assert(propertyPath instanceof WebInspector.PropertyPath);
31 this._property = property;
32 this._mode = mode || WebInspector.ObjectTreeView.Mode.Properties;
33 this._propertyPath = propertyPath;
34 this._prototypeName = prototypeName;
36 var classNames = ["object-tree-property"];
38 if (this._property.hasValue()) {
39 classNames.push(this._property.value.type);
40 if (this._property.value.subtype)
41 classNames.push(this._property.value.subtype);
43 classNames.push("accessor");
45 if (this._property.wasThrown)
46 classNames.push("had-error");
48 if (this._property.name === "__proto__")
49 classNames.push("prototype-property");
51 WebInspector.GeneralTreeElement.call(this, classNames, this._titleFragment(), null, this._property, false);
52 this._updateTooltips();
53 this._updateHasChildren();
56 this.toggleOnClick = true;
57 this.selectable = false;
58 this.tooltipHandledSeparately = true;
61 WebInspector.ObjectTreePropertyTreeElement.prototype = {
62 constructor: WebInspector.ObjectTreePropertyTreeElement,
63 __proto__: WebInspector.GeneralTreeElement.prototype,
69 return this._property;
74 onpopulate: function()
76 this._updateChildren();
81 if (this._previewView)
82 this._previewView.showTitle();
85 oncollapse: function()
87 if (this._previewView)
88 this._previewView.showPreview();
91 oncontextmenu: function(event)
93 this._contextMenuHandler(event);
98 _resolvedValue: function()
100 if (this._getterValue)
101 return this._getterValue;
102 if (this._property.hasValue())
103 return this._property.value;
107 _propertyPathType: function()
109 if (this._getterValue || this._property.hasValue())
110 return WebInspector.PropertyPath.Type.Value;
111 if (this._property.hasGetter())
112 return WebInspector.PropertyPath.Type.Getter;
113 if (this._property.hasSetter())
114 return WebInspector.PropertyPath.Type.Setter;
115 return WebInspector.PropertyPath.Type.Value;
118 _resolvedValuePropertyPath: function()
120 if (this._getterValue)
121 return this._propertyPath.appendPropertyDescriptor(this._getterValue, this._property, WebInspector.PropertyPath.Type.Value);
122 if (this._property.hasValue())
123 return this._propertyPath.appendPropertyDescriptor(this._property.value, this._property, WebInspector.PropertyPath.Type.Value);
127 _thisPropertyPath: function()
129 return this._propertyPath.appendPropertyDescriptor(null, this._property, this._propertyPathType());
132 _updateHasChildren: function()
134 var resolvedValue = this._resolvedValue();
135 var valueHasChildren = (resolvedValue && resolvedValue.hasChildren);
136 var wasThrown = this._property.wasThrown || this._getterHadError;
138 if (this._mode === WebInspector.ObjectTreeView.Mode.Properties)
139 this.hasChildren = !wasThrown && valueHasChildren;
141 this.hasChildren = !wasThrown && valueHasChildren && (this._property.name === "__proto__" || this._alwaysDisplayAsProperty());
144 _updateTooltips: function()
148 if (this._property.configurable)
149 attributes.push("configurable");
150 if (this._property.enumerable)
151 attributes.push("enumerable");
152 if (this._property.writable)
153 attributes.push("writable");
155 this.iconElement.title = attributes.join(" ");
158 _updateTitleAndIcon: function()
160 this.mainTitle = this._titleFragment();
162 if (this._getterValue) {
163 this.addClassName(this._getterValue.type);
164 if (this._getterValue.subtype)
165 this.addClassName(this._getterValue.subtype);
166 if (this._getterHadError)
167 this.addClassName("had-error");
168 this.removeClassName("accessor");
171 this._updateHasChildren();
174 _titleFragment: function()
176 if (this._property.name === "__proto__")
177 return this._createTitlePrototype();
179 if (this._mode === WebInspector.ObjectTreeView.Mode.Properties)
180 return this._createTitlePropertyStyle();
182 return this._createTitleAPIStyle();
185 _createTitlePrototype: function()
187 console.assert(this._property.hasValue());
188 console.assert(this._property.name === "__proto__");
190 var nameElement = document.createElement("span");
191 nameElement.className = "prototype-name";
192 nameElement.textContent = WebInspector.UIString("%s Prototype").format(this._sanitizedPrototypeString(this._property.value));
193 nameElement.title = this._propertyPathString(this._thisPropertyPath());
197 _createTitlePropertyStyle: function()
199 var container = document.createDocumentFragment();
202 var nameElement = document.createElement("span");
203 nameElement.className = "property-name";
204 nameElement.textContent = this._property.name + ": ";
205 nameElement.title = this._propertyPathString(this._thisPropertyPath());
207 // Property attributes.
208 if (this._mode === WebInspector.ObjectTreeView.Mode.Properties) {
209 if (!this._property.enumerable)
210 nameElement.classList.add("not-enumerable");
213 // Value / Getter Value / Getter.
214 var valueOrGetterElement;
215 var resolvedValue = this._resolvedValue();
217 if (resolvedValue.preview) {
218 this._previewView = new WebInspector.ObjectPreviewView(resolvedValue.preview);
219 valueOrGetterElement = this._previewView.element;
221 valueOrGetterElement = WebInspector.FormattedValue.createElementForRemoteObject(resolvedValue, this._property.wasThrown || this._getterHadError);
223 // Special case a function property string.
224 if (resolvedValue.type === "function")
225 valueOrGetterElement.textContent = this._functionPropertyString();
228 // FIXME: Option+Click for Value.
230 valueOrGetterElement = document.createElement("span");
231 if (this._property.hasGetter())
232 valueOrGetterElement.appendChild(this._createInteractiveGetterElement());
233 if (!this._property.hasSetter())
234 valueOrGetterElement.appendChild(this._createReadOnlyIconElement());
235 // FIXME: What if just a setter?
238 valueOrGetterElement.classList.add("value");
239 if (this._property.wasThrown || this._getterHadError)
240 valueOrGetterElement.classList.add("error");
242 container.appendChild(nameElement);
243 container.appendChild(valueOrGetterElement);
247 _createTitleAPIStyle: function()
249 // Fixed values and special properties display like a property.
250 if (this._alwaysDisplayAsProperty())
251 return this._createTitlePropertyStyle();
253 // Fetched getter values should already have been shown as properties.
254 console.assert(!this._getterValue);
256 // No API to display.
257 var isFunction = this._property.hasValue() && this._property.value.type === "function";
258 if (!isFunction && !this._property.hasGetter() && !this._property.hasSetter())
261 var container = document.createDocumentFragment();
263 // Function / Getter / Setter.
264 var nameElement = document.createElement("span");
265 nameElement.className = "property-name";
266 nameElement.textContent = this._property.name;
267 nameElement.title = this._propertyPathString(this._thisPropertyPath());
268 container.appendChild(nameElement);
271 var paramElement = document.createElement("span");
272 paramElement.className = "function-parameters";
273 paramElement.textContent = this._functionParameterString();
274 container.appendChild(paramElement);
276 if (this._property.hasGetter())
277 container.appendChild(this._createInteractiveGetterElement());
278 if (!this._property.hasSetter())
279 container.appendChild(this._createReadOnlyIconElement());
280 // FIXME: What if just a setter?
286 _createInteractiveGetterElement: function()
288 var getterElement = document.createElement("img");
289 getterElement.className = "getter";
290 getterElement.title = WebInspector.UIString("Invoke getter");
292 getterElement.addEventListener("click", function(event) {
293 event.stopPropagation();
294 var lastNonPrototypeObject = this._propertyPath.lastNonPrototypeObject;
295 var getterObject = this._property.get;
296 lastNonPrototypeObject.invokeGetter(getterObject, function(error, result, wasThrown) {
297 this._getterHadError = !!(error || wasThrown);
298 this._getterValue = result;
299 this._updateTitleAndIcon();
303 return getterElement;
306 _createReadOnlyIconElement: function()
308 var readOnlyElement = document.createElement("img");
309 readOnlyElement.className = "read-only";
310 readOnlyElement.title = WebInspector.UIString("Read only");
311 return readOnlyElement;
314 _alwaysDisplayAsProperty: function()
316 // Constructor, though a function, is often better treated as an expandable object.
317 if (this._property.name === "constructor")
320 // Non-function objects are often better treated as properties.
321 if (this._property.hasValue() && this._property.value.type !== "function")
324 // Fetched getter value.
325 if (this._getterValue)
331 _functionPropertyString: function()
333 return "function" + this._functionParameterString();
336 _functionParameterString: function()
338 var resolvedValue = this._resolvedValue();
339 console.assert(resolvedValue.type === "function");
341 // For Native methods, the toString is poor. We try to provide good function parameter strings.
342 if (isFunctionStringNativeCode(resolvedValue.description)) {
343 // Native function on a prototype, likely "Foo.prototype.method".
344 if (this._prototypeName) {
345 if (WebInspector.NativePrototypeFunctionParameters[this._prototypeName]) {
346 var params = WebInspector.NativePrototypeFunctionParameters[this._prototypeName][this._property.name];
347 return params ? "(" + params + ")" : "()";
351 // Native function property on a native function is likely a "Foo.method".
352 if (isFunctionStringNativeCode(this._propertyPath.object.description)) {
353 var match = this._propertyPath.object.description.match(/^function\s+([^)]+?)\(/);
356 if (WebInspector.NativeConstructorFunctionParameters[name]) {
357 var params = WebInspector.NativeConstructorFunctionParameters[name][this._property.name];
358 return params ? "(" + params + ")" : "()";
364 var match = resolvedValue.description.match(/^function.*?(\([^)]+?\))/);
365 return match ? match[1] : "()";
368 _sanitizedPrototypeString: function(value)
370 // FIXME: <https://webkit.org/b/141610> For many X, X.prototype is an X when it must be a plain object
371 if (value.type === "function")
373 if (value.subtype === "date")
375 if (value.subtype === "regexp")
378 return value.description.replace(/Prototype$/, "");
381 _propertyPathString: function(propertyPath)
383 if (propertyPath.isFullPathImpossible())
384 return WebInspector.UIString("Unable to determine path to property from root");
386 return propertyPath.displayPath(this._propertyPathType());
389 _updateChildren: function()
391 if (this.children.length && !this.shouldRefreshChildren)
394 var resolvedValue = this._resolvedValue();
395 if (resolvedValue.isCollectionType() && this._mode === WebInspector.ObjectTreeView.Mode.Properties)
396 resolvedValue.getCollectionEntries(0, 100, this._updateChildrenInternal.bind(this, this._updateEntries, this._mode));
397 else if (this._property.name === "__proto__")
398 resolvedValue.getOwnPropertyDescriptors(this._updateChildrenInternal.bind(this, this._updateProperties, WebInspector.ObjectTreeView.Mode.API));
400 resolvedValue.getDisplayablePropertyDescriptors(this._updateChildrenInternal.bind(this, this._updateProperties, this._mode));
403 _updateChildrenInternal: function(handler, mode, list)
405 this.removeChildren();
408 var errorMessageElement = WebInspector.ObjectTreeView.emptyMessageElement(WebInspector.UIString("Could not fetch properties. Object may no longer exist."));
409 this.appendChild(new TreeElement(errorMessageElement, null, false));
413 handler.call(this, list, this._resolvedValuePropertyPath(), mode);
416 _updateEntries: function(entries, propertyPath, mode)
418 for (var entry of entries) {
420 this.appendChild(new WebInspector.ObjectTreeMapKeyTreeElement(entry.key, propertyPath));
421 this.appendChild(new WebInspector.ObjectTreeMapValueTreeElement(entry.value, propertyPath, entry.key));
423 this.appendChild(new WebInspector.ObjectTreeSetIndexTreeElement(entry.value, propertyPath));
426 if (!this.children.length) {
427 var emptyMessageElement = WebInspector.ObjectTreeView.emptyMessageElement(WebInspector.UIString("No Entries."));
428 this.appendChild(new TreeElement(emptyMessageElement, null, false));
431 // Show the prototype so users can see the API.
432 var resolvedValue = this._resolvedValue();
433 resolvedValue.getOwnPropertyDescriptor("__proto__", function(propertyDescriptor) {
434 if (propertyDescriptor)
435 this.appendChild(new WebInspector.ObjectTreePropertyTreeElement(propertyDescriptor, propertyPath, mode));
439 _updateProperties: function(properties, propertyPath, mode)
441 properties.sort(WebInspector.ObjectTreeView.ComparePropertyDescriptors);
443 var resolvedValue = this._resolvedValue();
444 var isArray = resolvedValue.isArray();
445 var isPropertyMode = mode === WebInspector.ObjectTreeView.Mode.Properties || this._getterValue;
446 var isAPI = mode === WebInspector.ObjectTreeView.Mode.API;
448 var prototypeName = undefined;
449 if (this._property.name === "__proto__") {
450 if (resolvedValue.description)
451 prototypeName = this._sanitizedPrototypeString(resolvedValue);
454 for (var propertyDescriptor of properties) {
455 // FIXME: If this is a pure API ObjectTree, we should show the native getters.
456 // For now, just skip native binding getters in API mode, since we likely
457 // already showed them in the Properties section.
458 if (isAPI && propertyDescriptor.nativeGetter)
461 if (isArray && isPropertyMode) {
462 if (propertyDescriptor.isIndexProperty())
463 this.appendChild(new WebInspector.ObjectTreeArrayIndexTreeElement(propertyDescriptor, propertyPath));
464 else if (propertyDescriptor.name === "__proto__")
465 this.appendChild(new WebInspector.ObjectTreePropertyTreeElement(propertyDescriptor, propertyPath, mode, prototypeName));
467 this.appendChild(new WebInspector.ObjectTreePropertyTreeElement(propertyDescriptor, propertyPath, mode, prototypeName));
470 if (!this.children.length) {
471 var emptyMessageElement = WebInspector.ObjectTreeView.emptyMessageElement(WebInspector.UIString("No Properties."));
472 this.appendChild(new TreeElement(emptyMessageElement, null, false));
476 _logValue: function(value)
478 var resolvedValue = value || this._resolvedValue();
482 var propertyPath = this._resolvedValuePropertyPath();
483 var isImpossible = propertyPath.isFullPathImpossible();
484 var text = isImpossible ? WebInspector.UIString("Selected Value") : propertyPath.displayPath(this._propertyPathType());
487 WebInspector.quickConsole.prompt.pushHistoryItem(text);
489 WebInspector.consoleLogViewController.appendImmediateExecutionWithResult(text, resolvedValue);
492 _contextMenuHandler: function(event)
494 var resolvedValue = this._resolvedValue();
498 var contextMenu = new WebInspector.ContextMenu(event);
499 contextMenu.appendItem(WebInspector.UIString("Log Value"), this._logValue.bind(this));
501 var propertyPath = this._resolvedValuePropertyPath();
502 if (propertyPath && !propertyPath.isFullPathImpossible()) {
503 contextMenu.appendItem(WebInspector.UIString("Copy Path to Property"), function() {
504 InspectorFrontendHost.copyText(propertyPath.displayPath(WebInspector.PropertyPath.Type.Value));
508 contextMenu.appendSeparator();
510 this._appendMenusItemsForObject(contextMenu, resolvedValue);
512 if (!contextMenu.isEmpty())
516 _appendMenusItemsForObject: function(contextMenu, resolvedValue)
518 if (resolvedValue.type === "function") {
519 // FIXME: We should better handle bound functions.
520 if (!isFunctionStringNativeCode(resolvedValue.description)) {
521 contextMenu.appendItem(WebInspector.UIString("Jump to Definition"), function() {
522 DebuggerAgent.getFunctionDetails(resolvedValue.objectId, function(error, response) {
526 var location = response.location;
527 var sourceCode = WebInspector.debuggerManager.scriptForIdentifier(location.scriptId);
531 var sourceCodeLocation = sourceCode.createSourceCodeLocation(location.lineNumber, location.columnNumber || 0);
532 WebInspector.resourceSidebarPanel.showSourceCodeLocation(sourceCodeLocation);
539 if (resolvedValue.subtype === "node") {
540 contextMenu.appendItem(WebInspector.UIString("Reveal in DOM Tree"), function() {
541 resolvedValue.pushNodeToFrontend(function(nodeId) {
542 WebInspector.domTreeManager.inspectElement(nodeId);