Skip to content
Permalink
Browse files
Add support for editing DOM properties and scope variables by double
        clicking a property to enter edit mode.

        https://bugs.webkit.org/show_bug.cgi?id=20415

        Reviewed by Kevin McCullough.

        * page/inspector/ObjectPropertiesSection.js:
        (WebInspector.ObjectPropertiesSection): Set editable to true by default.
        (WebInspector.ObjectPropertiesSection.prototype.onpopulate):
        Factored out code into update, and calls update.
        (WebInspector.ObjectPropertiesSection.prototype.update): Moved from onpopulate.
        Call removeChildren since this method can be called multiple times now.
        (WebInspector.ObjectPropertyTreeElement): Pass an empty title, the title
        gets made later in onattach.
        (WebInspector.ObjectPropertyTreeElement.prototype.onpopulate): Don't return early
        if shouldRefreshChildren is true. Call removeChildren since this method can be
        called multiple times now.
        (WebInspector.ObjectPropertyTreeElement.prototype.ondblclick): Call startEditing.
        (WebInspector.ObjectPropertyTreeElement.prototype.onattach): Call update.
        (WebInspector.ObjectPropertyTreeElement.prototype.update): Update the title for
        this element (code moved from the constructor.)
        (WebInspector.ObjectPropertyTreeElement.prototype.updateSiblings): Recreate all
        sibling property elements.
        (WebInspector.ObjectPropertyTreeElement.prototype.startEditing): Call
        WebInspector.startEditing after rememebring some context.
        (WebInspector.ObjectPropertyTreeElement.prototype.editingEnded): Reset the scrollLeft
        for the list element, since it might have scrolled during editing.
        (WebInspector.ObjectPropertyTreeElement.prototype.editingCancelled): Call editingEnded
        then restore the state from the context. Then call update to restore the title.
        (WebInspector.ObjectPropertyTreeElement.prototype.editingCommitted): Call editingCancelled
        if the user input and the previous input are the same. Call editingEnded, then call applyExpression
        to commit the user input.
        (WebInspector.ObjectPropertyTreeElement.prototype.applyExpression): Evaluates the input expression
        and stores the result on the object for the property name of this element. If the expression is
        empty, delete the property and remove the tree element.
        * page/inspector/ScopeChainSidebarPane.js:
        (WebInspector.ScopeChainSidebarPane.prototype.update): Set the editInSelectedCallFrameWhenPaused
        property on each ObjectPropertiesSection.
        (WebInspector.ScopeVariableTreeElement.prototype.onattach): Call ObjectPropertyTreeElement's onattach
        since it is now implemented.
        * page/inspector/ScriptsPanel.js:
        (WebInspector.ScriptsPanel.prototype.evaluateInSelectedCallFrame): Added an updateInterface argument
        that defaults to true if omitted. It specifies whether to call update on the scope chain.
        * page/inspector/inspector.css: New styles.
        * page/inspector/treeoutline.js:
        (TreeElement.prototype._attach): Fixed an exception that fired when handling the shouldRefreshChildren
        change. The nextSibling would exist but have a _listItemNode that didn't match the new parent.

Canonical link: https://commits.webkit.org/28241@main
git-svn-id: https://svn.webkit.org/repository/webkit/trunk@35835 268f45cc-cd09-0410-ab3c-d52691b4dbfc
  • Loading branch information
xeenon committed Aug 19, 2008
1 parent 5722bb0 commit 36ad19a1a7db0575c619c504602ec2e9f8ad0d3b
@@ -1,5 +1,56 @@
2008-08-19 Timothy Hatcher <timothy@apple.com>

Add support for editing DOM properties and scope variables by double
clicking a property to enter edit mode.

https://bugs.webkit.org/show_bug.cgi?id=20415

Reviewed by Kevin McCullough.

* page/inspector/ObjectPropertiesSection.js:
(WebInspector.ObjectPropertiesSection): Set editable to true by default.
(WebInspector.ObjectPropertiesSection.prototype.onpopulate):
Factored out code into update, and calls update.
(WebInspector.ObjectPropertiesSection.prototype.update): Moved from onpopulate.
Call removeChildren since this method can be called multiple times now.
(WebInspector.ObjectPropertyTreeElement): Pass an empty title, the title
gets made later in onattach.
(WebInspector.ObjectPropertyTreeElement.prototype.onpopulate): Don't return early
if shouldRefreshChildren is true. Call removeChildren since this method can be
called multiple times now.
(WebInspector.ObjectPropertyTreeElement.prototype.ondblclick): Call startEditing.
(WebInspector.ObjectPropertyTreeElement.prototype.onattach): Call update.
(WebInspector.ObjectPropertyTreeElement.prototype.update): Update the title for
this element (code moved from the constructor.)
(WebInspector.ObjectPropertyTreeElement.prototype.updateSiblings): Recreate all
sibling property elements.
(WebInspector.ObjectPropertyTreeElement.prototype.startEditing): Call
WebInspector.startEditing after rememebring some context.
(WebInspector.ObjectPropertyTreeElement.prototype.editingEnded): Reset the scrollLeft
for the list element, since it might have scrolled during editing.
(WebInspector.ObjectPropertyTreeElement.prototype.editingCancelled): Call editingEnded
then restore the state from the context. Then call update to restore the title.
(WebInspector.ObjectPropertyTreeElement.prototype.editingCommitted): Call editingCancelled
if the user input and the previous input are the same. Call editingEnded, then call applyExpression
to commit the user input.
(WebInspector.ObjectPropertyTreeElement.prototype.applyExpression): Evaluates the input expression
and stores the result on the object for the property name of this element. If the expression is
empty, delete the property and remove the tree element.
* page/inspector/ScopeChainSidebarPane.js:
(WebInspector.ScopeChainSidebarPane.prototype.update): Set the editInSelectedCallFrameWhenPaused
property on each ObjectPropertiesSection.
(WebInspector.ScopeVariableTreeElement.prototype.onattach): Call ObjectPropertyTreeElement's onattach
since it is now implemented.
* page/inspector/ScriptsPanel.js:
(WebInspector.ScriptsPanel.prototype.evaluateInSelectedCallFrame): Added an updateInterface argument
that defaults to true if omitted. It specifies whether to call update on the scope chain.
* page/inspector/inspector.css: New styles.
* page/inspector/treeoutline.js:
(TreeElement.prototype._attach): Fixed an exception that fired when handling the shouldRefreshChildren
change. The nextSibling would exist but have a _listItemNode that didn't match the new parent.

2008-08-18 Timothy Hatcher <timothy@apple.com>

Surround the expression to be evaluated in parenthesis so the
result of the eval is the result of the whole expression not
the last potential sub-expression. So evaluating {x: 123}
@@ -39,12 +39,18 @@ WebInspector.ObjectPropertiesSection = function(object, title, subtitle, emptyPl
this.ignoreHasOwnProperty = ignoreHasOwnProperty;
this.extraProperties = extraProperties;
this.treeElementConstructor = treeElementConstructor || WebInspector.ObjectPropertyTreeElement;
this.editable = true;

WebInspector.PropertiesSection.call(this, title, subtitle);
}

WebInspector.ObjectPropertiesSection.prototype = {
onpopulate: function()
{
this.update();
},

update: function()
{
var properties = [];
for (var prop in this.object)
@@ -54,6 +60,8 @@ WebInspector.ObjectPropertiesSection.prototype = {
properties.push(prop);
properties.sort();

this.propertiesTreeOutline.removeChildren();

for (var i = 0; i < properties.length; ++i) {
var object = this.object;
var propertyName = properties[i];
@@ -81,28 +89,8 @@ WebInspector.ObjectPropertyTreeElement = function(parentObject, propertyName)
this.parentObject = parentObject;
this.propertyName = propertyName;

var childObject = this.safePropertyValue(parentObject, propertyName);
var isGetter = ("__lookupGetter__" in parentObject && parentObject.__lookupGetter__(propertyName));

var title = "<span class=\"name\">" + propertyName.escapeHTML() + "</span>: ";
if (!isGetter)
title += "<span class=\"value\">" + Object.describe(childObject, true).escapeHTML() + "</span>";
else
// FIXME: this should show something like "getter" once we can change localization (bug 16734).
title += "<span class=\"value dimmed\">&mdash;</span>";

var hasSubProperties = false;
var type = typeof childObject;
if (childObject && (type === "object" || type === "function")) {
for (subPropertyName in childObject) {
if (subPropertyName === "__treeElementIdentifier")
continue;
hasSubProperties = true;
break;
}
}

TreeElement.call(this, title, null, hasSubProperties);
// Pass an empty title, the title gets made later in onattach.
TreeElement.call(this, "", null, false);
}

WebInspector.ObjectPropertyTreeElement.prototype = {
@@ -115,9 +103,11 @@ WebInspector.ObjectPropertyTreeElement.prototype = {

onpopulate: function()
{
if (this.children.length)
if (this.children.length && !this.shouldRefreshChildren)
return;

this.removeChildren();

var childObject = this.safePropertyValue(this.parentObject, this.propertyName);
var properties = Object.sortedProperties(childObject);
for (var i = 0; i < properties.length; ++i) {
@@ -126,6 +116,150 @@ WebInspector.ObjectPropertyTreeElement.prototype = {
continue;
this.appendChild(new this.treeOutline.section.treeElementConstructor(childObject, propertyName));
}
},

ondblclick: function(element, event)
{
this.startEditing();
},

onattach: function()
{
this.update();
},

update: function()
{
var childObject = this.safePropertyValue(this.parentObject, this.propertyName);
var isGetter = ("__lookupGetter__" in this.parentObject && this.parentObject.__lookupGetter__(this.propertyName));

var nameElement = document.createElement("span");
nameElement.className = "name";
nameElement.textContent = this.propertyName;

this.valueElement = document.createElement("span");
this.valueElement.className = "value";
if (!isGetter) {
this.valueElement.textContent = Object.describe(childObject, true);
} else {
// FIXME: this should show something like "getter" (bug 16734).
this.valueElement.textContent = "\u2014"; // em dash
this.valueElement.addStyleClass("dimmed");
}

this.listItemElement.removeChildren();

this.listItemElement.appendChild(nameElement);
this.listItemElement.appendChild(document.createTextNode(": "));
this.listItemElement.appendChild(this.valueElement);

var hasSubProperties = false;
var type = typeof childObject;
if (childObject && (type === "object" || type === "function")) {
for (subPropertyName in childObject) {
if (subPropertyName === "__treeElementIdentifier")
continue;
hasSubProperties = true;
break;
}
}

this.hasChildren = hasSubProperties;
},

updateSiblings: function()
{
if (this.parent.root)
this.treeOutline.section.update();
else
this.parent.shouldRefreshChildren = true;
},

startEditing: function()
{
if (WebInspector.isBeingEdited(this.valueElement) || !this.treeOutline.section.editable)
return;

var context = { expanded: this.expanded };

// Lie about our children to prevent expanding on double click and to collapse subproperties.
this.hasChildren = false;

this.listItemElement.addStyleClass("editing-sub-part");

WebInspector.startEditing(this.valueElement, this.editingCommitted.bind(this), this.editingCancelled.bind(this), context);
},

editingEnded: function(context)
{
this.listItemElement.scrollLeft = 0;
this.listItemElement.removeStyleClass("editing-sub-part");
if (context.expanded)
this.expand();
},

editingCancelled: function(element, context)
{
this.update();
this.editingEnded(context);
},

editingCommitted: function(element, userInput, previousContent, context)
{
if (userInput === previousContent)
return this.editingCancelled(element, context); // nothing changed, so cancel

this.applyExpression(userInput, true);

this.editingEnded(context);
},

applyExpression: function(expression, updateInterface)
{
var expressionLength = expression.trimWhitespace().length;

if (!expressionLength) {
// The user deleted everything, so try to delete the property.
delete this.parentObject[this.propertyName];

if (updateInterface) {
if (this.propertyName in this.parentObject) {
// The property was not deleted, so update.
this.update();
} else {
// The property was deleted, so remove this tree element.
this.parent.removeChild(this);
}
}

return;
}

// Surround the expression in parenthesis so the result of the eval is the result
// of the whole expression not the last potential sub-expression.
expression = "(" + expression + ")";

try {
// Evaluate in the currently selected call frame if the debugger is paused.
// Otherwise evaluate in against the inspected window.
if (WebInspector.panels.scripts.paused && this.treeOutline.section.editInSelectedCallFrameWhenPaused)
var result = WebInspector.panels.scripts.evaluateInSelectedCallFrame(expression, false);
else
var result = InspectorController.inspectedWindow().eval(expression);

// Store the result in the property.
this.parentObject[this.propertyName] = result;
} catch(e) {
// The expression failed so don't change the value. So just update and return.
if (updateInterface)
this.update();
return;
}

if (updateInterface) {
// Call updateSiblings since their value might be based on the value that just changed.
this.updateSiblings();
}
}
}

@@ -86,6 +86,7 @@ WebInspector.ScopeChainSidebarPane.prototype = {
subtitle = null;

var section = new WebInspector.ObjectPropertiesSection(scopeObject, title, subtitle, emptyPlaceholder, true, extraProperties, WebInspector.ScopeVariableTreeElement);
section.editInSelectedCallFrameWhenPaused = true;
section.pane = this;

if (!foundLocalScope || localScope)
@@ -107,6 +108,7 @@ WebInspector.ScopeVariableTreeElement = function(parentObject, propertyName)
WebInspector.ScopeVariableTreeElement.prototype = {
onattach: function()
{
WebInspector.ObjectPropertyTreeElement.prototype.onattach.call(this);
if (this.hasChildren && this.propertyIdentifier in this.treeOutline.section.pane.callFrame._expandedProperties)
this.expand();
},
@@ -283,13 +283,16 @@ WebInspector.ScriptsPanel.prototype = {
sourceFrame.removeBreakpoint(breakpoint);
},

evaluateInSelectedCallFrame: function(code)
evaluateInSelectedCallFrame: function(code, updateInterface)
{
var selectedCallFrame = this.sidebarPanes.callstack.selectedCallFrame;
if (!this._paused || !selectedCallFrame)
return;
if (typeof updateInterface === "undefined")
updateInterface = true;
var result = selectedCallFrame.evaluate(code);
this.sidebarPanes.scopechain.update(selectedCallFrame);
if (updateInterface)
this.sidebarPanes.scopechain.update(selectedCallFrame);
return result;
},

@@ -1208,6 +1208,9 @@ body.inactive .placard.selected {
-webkit-user-modify: read-write-plaintext-only;
text-overflow: clip;
padding-left: 2px;
margin-left: -2px;
padding-right: 2px;
margin-right: -2px;
margin-bottom: -1px;
padding-bottom: 1px;
opacity: 1.0 !important;
@@ -1227,6 +1230,12 @@ li.editing .swatch, li.editing .enabled-button {
display: none !important;
}

.section .properties li.editing-sub-part {
padding: 3px 6px 8px 18px;
margin: -3px -6px -8px -6px;
text-overflow: clip;
}

.section .properties .overloaded, .section .properties .disabled {
text-decoration: line-through;
}
@@ -556,7 +556,10 @@ TreeElement.prototype._attach = function()
this.onattach(this);
}

this.parent._childrenListNode.insertBefore(this._listItemNode, (this.nextSibling ? this.nextSibling._listItemNode : null));
var nextSibling = null;
if (this.nextSibling && this.nextSibling._listItemNode && this.nextSibling._listItemNode.parentNode === this.parent._childrenListNode)
nextSibling = this.nextSibling._listItemNode;
this.parent._childrenListNode.insertBefore(this._listItemNode, nextSibling);
if (this._childrenListNode)
this.parent._childrenListNode.insertBefore(this._childrenListNode, this._listItemNode.nextSibling);
if (this.selected)

0 comments on commit 36ad19a

Please sign in to comment.