diff options
Diffstat (limited to 'Source/WebInspectorUI/UserInterface/Views/ObjectTreePropertyTreeElement.js')
-rw-r--r-- | Source/WebInspectorUI/UserInterface/Views/ObjectTreePropertyTreeElement.js | 398 |
1 files changed, 398 insertions, 0 deletions
diff --git a/Source/WebInspectorUI/UserInterface/Views/ObjectTreePropertyTreeElement.js b/Source/WebInspectorUI/UserInterface/Views/ObjectTreePropertyTreeElement.js new file mode 100644 index 000000000..1288837a6 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Views/ObjectTreePropertyTreeElement.js @@ -0,0 +1,398 @@ +/* + * Copyright (C) 2015 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.ObjectTreePropertyTreeElement = class ObjectTreePropertyTreeElement extends WebInspector.ObjectTreeBaseTreeElement +{ + constructor(property, propertyPath, mode, prototypeName) + { + super(property, propertyPath, property); + + this._mode = mode || WebInspector.ObjectTreeView.Mode.Properties; + this._prototypeName = prototypeName; + + this.mainTitle = this._titleFragment(); + this.addClassName("object-tree-property"); + + if (this.property.hasValue()) { + this.addClassName(this.property.value.type); + if (this.property.value.subtype) + this.addClassName(this.property.value.subtype); + } else + this.addClassName("accessor"); + + if (this.property.wasThrown) + this.addClassName("had-error"); + if (this.property.name === "__proto__") + this.addClassName("prototype-property"); + + this._updateTooltips(); + this._updateHasChildren(); + } + + // Protected + + onpopulate() + { + this._updateChildren(); + } + + onexpand() + { + if (this._previewView) + this._previewView.showTitle(); + } + + oncollapse() + { + if (this._previewView) + this._previewView.showPreview(); + } + + invokedGetter() + { + this.mainTitle = this._titleFragment(); + + var resolvedValue = this.resolvedValue(); + this.addClassName(resolvedValue.type); + if (resolvedValue.subtype) + this.addClassName(resolvedValue.subtype); + if (this.hadError()) + this.addClassName("had-error"); + this.removeClassName("accessor"); + + this._updateHasChildren(); + } + + // Private + + _updateHasChildren() + { + var resolvedValue = this.resolvedValue(); + var valueHasChildren = (resolvedValue && resolvedValue.hasChildren); + var wasThrown = this.hadError(); + + if (this._mode === WebInspector.ObjectTreeView.Mode.Properties) + this.hasChildren = !wasThrown && valueHasChildren; + else + this.hasChildren = !wasThrown && valueHasChildren && (this.property.name === "__proto__" || this._alwaysDisplayAsProperty()); + } + + _updateTooltips() + { + var attributes = []; + + if (this.property.configurable) + attributes.push("configurable"); + if (this.property.enumerable) + attributes.push("enumerable"); + if (this.property.writable) + attributes.push("writable"); + + this.iconElement.title = attributes.join(" "); + } + + _titleFragment() + { + if (this.property.name === "__proto__") + return this._createTitlePrototype(); + + if (this._mode === WebInspector.ObjectTreeView.Mode.Properties) + return this._createTitlePropertyStyle(); + else + return this._createTitleAPIStyle(); + } + + _createTitlePrototype() + { + console.assert(this.property.hasValue()); + console.assert(this.property.name === "__proto__"); + + var nameElement = document.createElement("span"); + nameElement.className = "prototype-name"; + nameElement.textContent = WebInspector.UIString("%s Prototype").format(this._sanitizedPrototypeString(this.property.value)); + nameElement.title = this.propertyPathString(this.thisPropertyPath()); + return nameElement; + } + + _createTitlePropertyStyle() + { + var container = document.createDocumentFragment(); + + // Property name. + var nameElement = document.createElement("span"); + nameElement.className = "property-name"; + nameElement.textContent = this.property.name + ": "; + nameElement.title = this.propertyPathString(this.thisPropertyPath()); + + // Property attributes. + if (this._mode === WebInspector.ObjectTreeView.Mode.Properties) { + if (!this.property.enumerable) + nameElement.classList.add("not-enumerable"); + } + + // Value / Getter Value / Getter. + var valueOrGetterElement; + var resolvedValue = this.resolvedValue(); + if (resolvedValue) { + if (resolvedValue.preview) { + this._previewView = new WebInspector.ObjectPreviewView(resolvedValue.preview); + valueOrGetterElement = this._previewView.element; + } else { + valueOrGetterElement = WebInspector.FormattedValue.createElementForRemoteObject(resolvedValue, this.hadError()); + + // Special case a function property string. + if (resolvedValue.type === "function") + valueOrGetterElement.textContent = this._functionPropertyString(); + } + } else { + valueOrGetterElement = document.createElement("span"); + if (this.property.hasGetter()) + valueOrGetterElement.appendChild(this.createGetterElement(this._mode !== WebInspector.ObjectTreeView.Mode.ClassAPI)); + if (this.property.hasSetter()) + valueOrGetterElement.appendChild(this.createSetterElement()); + } + + valueOrGetterElement.classList.add("value"); + if (this.hadError()) + valueOrGetterElement.classList.add("error"); + + container.appendChild(nameElement); + container.appendChild(valueOrGetterElement); + return container; + } + + _createTitleAPIStyle() + { + // Fixed values and special properties display like a property. + if (this._alwaysDisplayAsProperty()) + return this._createTitlePropertyStyle(); + + // No API to display. + var isFunction = this.property.hasValue() && this.property.value.type === "function"; + if (!isFunction && !this.property.hasGetter() && !this.property.hasSetter()) + return null; + + var container = document.createDocumentFragment(); + + // Function / Getter / Setter. + var nameElement = document.createElement("span"); + nameElement.className = "property-name"; + nameElement.textContent = this.property.name; + nameElement.title = this.propertyPathString(this.thisPropertyPath()); + container.appendChild(nameElement); + + if (isFunction) { + var paramElement = document.createElement("span"); + paramElement.className = "function-parameters"; + paramElement.textContent = this._functionParameterString(); + container.appendChild(paramElement); + } else { + var spacer = container.appendChild(document.createElement("span")); + spacer.className = "spacer"; + if (this.property.hasGetter()) + container.appendChild(this.createGetterElement(this._mode !== WebInspector.ObjectTreeView.Mode.ClassAPI)); + if (this.property.hasSetter()) + container.appendChild(this.createSetterElement()); + } + + return container; + } + + _alwaysDisplayAsProperty() + { + // Constructor, though a function, is often better treated as an expandable object. + if (this.property.name === "constructor") + return true; + + // Non-function objects are often better treated as properties. + if (this.property.hasValue() && this.property.value.type !== "function") + return true; + + // Fetched getter value. + if (this._getterValue) + return true; + + return false; + } + + _functionPropertyString() + { + return "function" + this._functionParameterString(); + } + + _functionParameterString() + { + var resolvedValue = this.resolvedValue(); + console.assert(resolvedValue.type === "function"); + + // For Native methods, the toString is poor. We try to provide good function parameter strings. + if (isFunctionStringNativeCode(resolvedValue.description)) { + // Native function on a prototype, likely "Foo.prototype.method". + if (this._prototypeName) { + if (WebInspector.NativePrototypeFunctionParameters[this._prototypeName]) { + var params = WebInspector.NativePrototypeFunctionParameters[this._prototypeName][this._property.name]; + return params ? "(" + params + ")" : "()"; + } + } + + var parentDescription = this._propertyPath.object.description; + + // Native function property on a native function is likely a "Foo.method". + if (isFunctionStringNativeCode(parentDescription)) { + var match = parentDescription.match(/^function\s+([^)]+?)\(/); + if (match) { + var name = match[1]; + if (WebInspector.NativeConstructorFunctionParameters[name]) { + var params = WebInspector.NativeConstructorFunctionParameters[name][this._property.name]; + return params ? "(" + params + ")" : "()"; + } + } + } + + // Native DOM constructor or on native objects that are not functions. + if (parentDescription.endsWith("Constructor") || ["Math", "JSON", "Reflect", "Console"].includes(parentDescription)) { + var name = parentDescription; + if (WebInspector.NativeConstructorFunctionParameters[name]) { + var params = WebInspector.NativeConstructorFunctionParameters[name][this._property.name]; + return params ? "(" + params + ")" : "()"; + } + } + } + + var match = resolvedValue.functionDescription.match(/^function.*?(\([^)]*?\))/); + return match ? match[1] : "()"; + } + + _sanitizedPrototypeString(value) + { + // FIXME: <https://webkit.org/b/141610> For many X, X.prototype is an X when it must be a plain object + if (value.type === "function") + return "Function"; + if (value.subtype === "date") + return "Date"; + if (value.subtype === "regexp") + return "RegExp"; + + return value.description.replace(/Prototype$/, ""); + } + + _updateChildren() + { + if (this.children.length && !this.shouldRefreshChildren) + return; + + var resolvedValue = this.resolvedValue(); + if (resolvedValue.isCollectionType() && this._mode === WebInspector.ObjectTreeView.Mode.Properties) + resolvedValue.getCollectionEntries(0, 100, this._updateChildrenInternal.bind(this, this._updateEntries, this._mode)); + else if (this._mode === WebInspector.ObjectTreeView.Mode.ClassAPI) + resolvedValue.getOwnPropertyDescriptors(this._updateChildrenInternal.bind(this, this._updateProperties, WebInspector.ObjectTreeView.Mode.ClassAPI)); + else if (this.property.name === "__proto__") + resolvedValue.getOwnPropertyDescriptors(this._updateChildrenInternal.bind(this, this._updateProperties, WebInspector.ObjectTreeView.Mode.PrototypeAPI)); + else + resolvedValue.getDisplayablePropertyDescriptors(this._updateChildrenInternal.bind(this, this._updateProperties, this._mode)); + } + + _updateChildrenInternal(handler, mode, list) + { + this.removeChildren(); + + if (!list) { + var errorMessageElement = WebInspector.ObjectTreeView.createEmptyMessageElement(WebInspector.UIString("Could not fetch properties. Object may no longer exist.")); + this.appendChild(new WebInspector.TreeElement(errorMessageElement, null, false)); + return; + } + + handler.call(this, list, this.resolvedValuePropertyPath(), mode); + } + + _updateEntries(entries, propertyPath, mode) + { + for (var entry of entries) { + if (entry.key) { + this.appendChild(new WebInspector.ObjectTreeMapKeyTreeElement(entry.key, propertyPath)); + this.appendChild(new WebInspector.ObjectTreeMapValueTreeElement(entry.value, propertyPath, entry.key)); + } else + this.appendChild(new WebInspector.ObjectTreeSetIndexTreeElement(entry.value, propertyPath)); + } + + if (!this.children.length) { + var emptyMessageElement = WebInspector.ObjectTreeView.createEmptyMessageElement(WebInspector.UIString("No Entries")); + this.appendChild(new WebInspector.TreeElement(emptyMessageElement, null, false)); + } + + // Show the prototype so users can see the API. + var resolvedValue = this.resolvedValue(); + resolvedValue.getOwnPropertyDescriptor("__proto__", (propertyDescriptor) => { + if (propertyDescriptor) + this.appendChild(new WebInspector.ObjectTreePropertyTreeElement(propertyDescriptor, propertyPath, mode)); + }); + } + + _updateProperties(properties, propertyPath, mode) + { + properties.sort(WebInspector.ObjectTreeView.comparePropertyDescriptors); + + var resolvedValue = this.resolvedValue(); + var isArray = resolvedValue.isArray(); + var isPropertyMode = mode === WebInspector.ObjectTreeView.Mode.Properties || this._getterValue; + var isAPI = mode !== WebInspector.ObjectTreeView.Mode.Properties; + + var prototypeName; + if (this.property.name === "__proto__") { + if (resolvedValue.description) + prototypeName = this._sanitizedPrototypeString(resolvedValue); + } + + var hadProto = false; + for (var propertyDescriptor of properties) { + // FIXME: If this is a pure API ObjectTree, we should show the native getters. + // For now, just skip native binding getters in API mode, since we likely + // already showed them in the Properties section. + if (isAPI && propertyDescriptor.nativeGetter) + continue; + + // COMPATIBILITY (iOS 8): Sometimes __proto__ is not a value, but a get/set property. + // In those cases it is actually not useful to show. + if (propertyDescriptor.name === "__proto__" && !propertyDescriptor.hasValue()) + continue; + + if (isArray && isPropertyMode) { + if (propertyDescriptor.isIndexProperty()) + this.appendChild(new WebInspector.ObjectTreeArrayIndexTreeElement(propertyDescriptor, propertyPath)); + else if (propertyDescriptor.name === "__proto__") + this.appendChild(new WebInspector.ObjectTreePropertyTreeElement(propertyDescriptor, propertyPath, mode, prototypeName)); + } else + this.appendChild(new WebInspector.ObjectTreePropertyTreeElement(propertyDescriptor, propertyPath, mode, prototypeName)); + + if (propertyDescriptor.name === "__proto__") + hadProto = true; + } + + if (!this.children.length || (hadProto && this.children.length === 1)) { + var emptyMessageElement = WebInspector.ObjectTreeView.createEmptyMessageElement(WebInspector.UIString("No Properties")); + this.insertChild(new WebInspector.TreeElement(emptyMessageElement, null, false), 0); + } + } +}; |