Changeset 73913 in webkit


Ignore:
Timestamp:
Dec 13, 2010 7:06:26 AM (13 years ago)
Author:
apavlov@chromium.org
Message:

2010-12-08 Alexander Pavlov <apavlov@chromium.org>

Reviewed by Joseph Pecoraro.

Web Inspector: Enable CSS property editing name/value-wise (like Firebug does)
https://bugs.webkit.org/show_bug.cgi?id=50565

For CSS property editing, the property name and value have become two fields separated
by a colon (rather than one field containing the full property text.) A user can tab
between the name and value fields forward and backward. A colon typed in the name field
and a semicolon in the value field (unless found inside a string) act as a Tab and focus
the next editable field (while applying the entire property value.)

Now a user can tab through all editable styles for an element, even across rule boundaries.

WebCore:

  • inspector/front-end/BreakpointsSidebarPane.js: (WebInspector.XHRBreakpointsSidebarPane.prototype._startEditingBreakpoint):
  • inspector/front-end/DataGrid.js: (WebInspector.DataGrid.prototype._startEditingColumnOfDataGridNode): (WebInspector.DataGrid.prototype._startEditing):
  • inspector/front-end/ElementsTreeOutline.js: (WebInspector.ElementsTreeElement.prototype._startEditingAttribute): (WebInspector.ElementsTreeElement.prototype._startEditingTextNode): (WebInspector.ElementsTreeElement.prototype._startEditingTagName): (WebInspector.ElementsTreeElement.prototype._startEditingAsHTML):
  • inspector/front-end/MetricsSidebarPane.js: (WebInspector.MetricsSidebarPane.prototype.startEditing):
  • inspector/front-end/ObjectPropertiesSection.js: (WebInspector.ObjectPropertyTreeElement.prototype.startEditing):
  • inspector/front-end/Section.js: (WebInspector.Section): (WebInspector.Section.prototype.get subtitleAsTextForTest): (WebInspector.Section.prototype.get nextSibling): (WebInspector.Section.prototype.get previousSibling):
  • inspector/front-end/SourceFrame.js: (WebInspector.SourceFrame.prototype._editBreakpointCondition):
  • inspector/front-end/StylesSidebarPane.js: (WebInspector.StylePropertiesSection.prototype.nextEditableSibling): (WebInspector.StylePropertiesSection.prototype.previousEditableSibling): (WebInspector.StylePropertiesSection.prototype.addNewBlankProperty): (WebInspector.StylePropertiesSection.prototype.startEditingSelector): (WebInspector.StylePropertiesSection.prototype.editingSelectorCommitted): (WebInspector.StylePropertyTreeElement.prototype.): (WebInspector.StylePropertyTreeElement.prototype):
  • inspector/front-end/TextViewer.js: (WebInspector.TextViewer.prototype._handleDoubleClick):
  • inspector/front-end/WatchExpressionsSidebarPane.js: (WebInspector.WatchExpressionTreeElement.prototype.startEditing):
  • inspector/front-end/inspector.css: (.child-editing):
  • inspector/front-end/inspector.js: (WebInspector.startEditing.defaultFinishHandler): (WebInspector.startEditing):
  • inspector/front-end/treeoutline.js: (TreeElement.prototype.select):

LayoutTests:

  • inspector/console-dir.html:
  • inspector/styles-add-blank-property.html:
Location:
trunk
Files:
17 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r73911 r73913  
     12010-12-08  Alexander Pavlov  <apavlov@chromium.org>
     2
     3        Reviewed by Joseph Pecoraro.
     4
     5        Web Inspector: Enable CSS property editing name/value-wise (like Firebug does)
     6        https://bugs.webkit.org/show_bug.cgi?id=50565
     7
     8        * inspector/console-dir.html:
     9        * inspector/styles-add-blank-property.html:
     10
    1112010-12-13  W. James MacLean  <wjmaclean@chromium.org>
    212
  • trunk/LayoutTests/inspector/console-dir.html

    r65248 r73913  
    3838        var node = element.traverseNextNode(element);
    3939        while (node) {
    40             if (node.sectionForTest) {
    41                 messages[i].section = node.sectionForTest;
    42                 node.sectionForTest.expanded = true;
     40            if (node._section) {
     41                messages[i].section = node._section;
     42                node._section.expanded = true;
    4343                break;
    4444            }
  • trunk/LayoutTests/inspector/styles-add-blank-property.html

    r70880 r73913  
    4040    var evt = document.createEvent("KeyboardEvent");
    4141    evt.initKeyboardEvent("keydown", true /* can bubble */, true /* can cancel */, null /* view */, "Enter", "");
    42     treeElement.listItemElement.textContent = "text-decoration: none";
    43     treeElement.listItemElement.dispatchEvent(evt);
     42    treeElement.nameElement.textContent = "text-decoration";
     43    treeElement.valueElement.textContent = "none";
     44    treeElement.nameElement.dispatchEvent(evt);
    4445
    4546    testController.runAfterPendingDispatches(frontend_dumpResults.bind(this, testController));
  • trunk/WebCore/ChangeLog

    r73912 r73913  
     12010-12-08  Alexander Pavlov  <apavlov@chromium.org>
     2
     3        Reviewed by Joseph Pecoraro.
     4
     5        Web Inspector: Enable CSS property editing name/value-wise (like Firebug does)
     6        https://bugs.webkit.org/show_bug.cgi?id=50565
     7
     8        For CSS property editing, the property name and value have become two fields separated
     9        by a colon (rather than one field containing the full property text.) A user can tab
     10        between the name and value fields forward and backward. A colon typed in the name field
     11        and a semicolon in the value field (unless found inside a string) act as a Tab and focus
     12        the next editable field (while applying the entire property value.)
     13
     14        Now a user can tab through all editable styles for an element, even across rule boundaries.
     15
     16        * inspector/front-end/BreakpointsSidebarPane.js:
     17        (WebInspector.XHRBreakpointsSidebarPane.prototype._startEditingBreakpoint):
     18        * inspector/front-end/DataGrid.js:
     19        (WebInspector.DataGrid.prototype._startEditingColumnOfDataGridNode):
     20        (WebInspector.DataGrid.prototype._startEditing):
     21        * inspector/front-end/ElementsTreeOutline.js:
     22        (WebInspector.ElementsTreeElement.prototype._startEditingAttribute):
     23        (WebInspector.ElementsTreeElement.prototype._startEditingTextNode):
     24        (WebInspector.ElementsTreeElement.prototype._startEditingTagName):
     25        (WebInspector.ElementsTreeElement.prototype._startEditingAsHTML):
     26        * inspector/front-end/MetricsSidebarPane.js:
     27        (WebInspector.MetricsSidebarPane.prototype.startEditing):
     28        * inspector/front-end/ObjectPropertiesSection.js:
     29        (WebInspector.ObjectPropertyTreeElement.prototype.startEditing):
     30        * inspector/front-end/Section.js:
     31        (WebInspector.Section):
     32        (WebInspector.Section.prototype.get subtitleAsTextForTest):
     33        (WebInspector.Section.prototype.get nextSibling):
     34        (WebInspector.Section.prototype.get previousSibling):
     35        * inspector/front-end/SourceFrame.js:
     36        (WebInspector.SourceFrame.prototype._editBreakpointCondition):
     37        * inspector/front-end/StylesSidebarPane.js:
     38        (WebInspector.StylePropertiesSection.prototype.nextEditableSibling):
     39        (WebInspector.StylePropertiesSection.prototype.previousEditableSibling):
     40        (WebInspector.StylePropertiesSection.prototype.addNewBlankProperty):
     41        (WebInspector.StylePropertiesSection.prototype.startEditingSelector):
     42        (WebInspector.StylePropertiesSection.prototype.editingSelectorCommitted):
     43        (WebInspector.StylePropertyTreeElement.prototype.):
     44        (WebInspector.StylePropertyTreeElement.prototype):
     45        * inspector/front-end/TextViewer.js:
     46        (WebInspector.TextViewer.prototype._handleDoubleClick):
     47        * inspector/front-end/WatchExpressionsSidebarPane.js:
     48        (WebInspector.WatchExpressionTreeElement.prototype.startEditing):
     49        * inspector/front-end/inspector.css:
     50        (.child-editing):
     51        * inspector/front-end/inspector.js:
     52        (WebInspector.startEditing.defaultFinishHandler):
     53        (WebInspector.startEditing):
     54        * inspector/front-end/treeoutline.js:
     55        (TreeElement.prototype.select):
     56
    1572010-12-13  Yael Aharon  <yael.aharon@nokia.com>
    258
  • trunk/WebCore/inspector/front-end/BreakpointsSidebarPane.js

    r72733 r73913  
    146146        var commitHandler = this._hideEditBreakpointDialog.bind(this, inputElement, true, breakpointItem);
    147147        var cancelHandler = this._hideEditBreakpointDialog.bind(this, inputElement, false, breakpointItem);
    148         WebInspector.startEditing(inputElement, commitHandler, cancelHandler);
     148        WebInspector.startEditing(inputElement, {
     149            commitHandler: commitHandler,
     150            cancelHandler: cancelHandler
     151        });
    149152    },
    150153
  • trunk/WebCore/inspector/front-end/DataGrid.js

    r71351 r73913  
    170170
    171171        var element = this._editingNode._element.children[column];
    172         WebInspector.startEditing(element, this._editingCommitted.bind(this), this._editingCancelled.bind(this), element.textContent);
     172        WebInspector.startEditing(element, {
     173            context: element.textContent,
     174            commitHandler: this._editingCommitted.bind(this),
     175            cancelHandler: this._editingCancelled.bind(this)
     176        });
    173177        window.getSelection().setBaseAndExtent(element, 0, element, 1);
    174178    },
     
    192196
    193197        this._editing = true;
    194         WebInspector.startEditing(element, this._editingCommitted.bind(this), this._editingCancelled.bind(this), element.textContent);
     198        WebInspector.startEditing(element, {
     199            context: element.textContent,
     200            commitHandler: this._editingCommitted.bind(this),
     201            cancelHandler: this._editingCancelled.bind(this)
     202        });
    195203        window.getSelection().setBaseAndExtent(element, 0, element, 1);
    196204    },
  • trunk/WebCore/inspector/front-end/ElementsTreeOutline.js

    r73285 r73913  
    873873        removeZeroWidthSpaceRecursive(attribute);
    874874
    875         this._editing = WebInspector.startEditing(attribute, this._attributeEditingCommitted.bind(this), this._editingCancelled.bind(this), attributeName);
     875        this._editing = WebInspector.startEditing(attribute, {
     876            context: attributeName,
     877            commitHandler: this._attributeEditingCommitted.bind(this),
     878            cancelHandler: this._editingCancelled.bind(this)
     879        });
    876880        window.getSelection().setBaseAndExtent(elementForSelection, 0, elementForSelection, 1);
    877881
     
    884888            return true;
    885889
    886         this._editing = WebInspector.startEditing(textNode, this._textNodeEditingCommitted.bind(this), this._editingCancelled.bind(this));
     890        this._editing = WebInspector.startEditing(textNode, {
     891            context: null,
     892            commitHandler: this._textNodeEditingCommitted.bind(this),
     893            cancelHandler: this._editingCancelled.bind(this)
     894        });
    887895        window.getSelection().setBaseAndExtent(textNode, 0, textNode, 1);
    888896
     
    927935        tagNameElement.addEventListener('keyup', keyupListener, false);
    928936
    929         this._editing = WebInspector.startEditing(tagNameElement, editingComitted.bind(this), editingCancelled.bind(this), tagName);
     937        this._editing = WebInspector.startEditing(tagNameElement, {
     938            context: tagName,
     939            commitHandler: editingComitted.bind(this),
     940            cancelHandler: editingCancelled.bind(this)
     941        });
    930942        window.getSelection().setBaseAndExtent(tagNameElement, 0, tagNameElement, 1);
    931943        return true;
     
    981993        }
    982994
    983         this._editing = WebInspector.startEditing(this._htmlEditElement, commit.bind(this), dispose.bind(this), null, true);
     995        this._editing = WebInspector.startEditing(this._htmlEditElement, {
     996            context: null,
     997            commitHandler: commit.bind(this),
     998            cancelHandler: dispose.bind(this),
     999            multiline: true
     1000        });
    9841001    },
    9851002
  • trunk/WebCore/inspector/front-end/MetricsSidebarPane.js

    r70772 r73913  
    176176        var context = { box: box, styleProperty: styleProperty };
    177177
    178         WebInspector.startEditing(targetElement, this.editingCommitted.bind(this), this.editingCancelled.bind(this), context);
     178        WebInspector.startEditing(targetElement, {
     179            context: context,
     180            commitHandler: this.editingCommitted.bind(this),
     181            cancelHandler: this.editingCancelled.bind(this)
     182        });
    179183    },
    180184
  • trunk/WebCore/inspector/front-end/ObjectPropertiesSection.js

    r73285 r73913  
    239239        this.listItemElement.addStyleClass("editing-sub-part");
    240240
    241         WebInspector.startEditing(this.valueElement, this.editingCommitted.bind(this), this.editingCancelled.bind(this), context);
     241        WebInspector.startEditing(this.valueElement, {
     242            context: context,
     243            commitHandler: this.editingCommitted.bind(this),
     244            cancelHandler: this.editingCancelled.bind(this)
     245        });
    242246    },
    243247
  • trunk/WebCore/inspector/front-end/Section.js

    r65905 r73913  
    3232    this.element = document.createElement("div");
    3333    this.element.className = "section";
    34     this.element.sectionForTest = this;
     34    this.element._section = this;
    3535
    3636    this.headerElement = document.createElement("div");
     
    125125    },
    126126
     127    get nextSibling()
     128    {
     129        var curElement = this.element;
     130        do {
     131            curElement = curElement.nextSibling;
     132        } while (curElement && !curElement._section);
     133
     134        return curElement ? curElement._section : null;
     135    },
     136
     137    get previousSibling()
     138    {
     139        var curElement = this.element;
     140        do {
     141            curElement = curElement.previousSibling;
     142        } while (curElement && !curElement._section);
     143
     144        return curElement ? curElement._section : null;
     145    },
     146
    127147    expand: function()
    128148    {
  • trunk/WebCore/inspector/front-end/SourceFrame.js

    r73730 r73913  
    706706        this._conditionEditorElement.addEventListener("blur", dismissedHandler, false);
    707707
    708         WebInspector.startEditing(this._conditionEditorElement, committed.bind(this), dismissedHandler);
     708        WebInspector.startEditing(this._conditionEditorElement, {
     709            context: null,
     710            commitHandler: committed.bind(this),
     711            cancelHandler: dismissedHandler
     712        });
    709713        this._conditionEditorElement.value = breakpoint.condition;
    710714        this._conditionEditorElement.select();
  • trunk/WebCore/inspector/front-end/StylesSidebarPane.js

    r73311 r73913  
    705705    },
    706706
     707    nextEditableSibling: function()
     708    {
     709        var curSection = this;
     710        do {
     711            curSection = curSection.nextSibling;
     712        } while (curSection && !curSection.editable);
     713
     714        return curSection;
     715    },
     716
     717    previousEditableSibling: function()
     718    {
     719        var curSection = this;
     720        do {
     721            curSection = curSection.previousSibling;
     722        } while (curSection && !curSection.editable);
     723
     724        return curSection;
     725    },
     726
    707727    update: function(full)
    708728    {
     
    796816        item.listItemElement.textContent = "";
    797817        item._newProperty = true;
     818        item.updateTitle();
    798819        return item;
    799820    },
     
    876897            return;
    877898
    878         WebInspector.startEditing(this._selectorElement, this.editingSelectorCommitted.bind(this), this.editingSelectorCancelled.bind(this), null);
     899        WebInspector.startEditing(this._selectorElement, {
     900            context: null,
     901            commitHandler: this.editingSelectorCommitted.bind(this),
     902            cancelHandler: this.editingSelectorCancelled.bind(this)
     903        });
    879904        window.getSelection().setBaseAndExtent(element, 0, element, 1);
    880905    },
     
    883908    {
    884909        function moveToNextIfNeeded() {
    885             if (!moveDirection || moveDirection !== "forward")
     910            if (!moveDirection)
    886911                return;
    887912
    888             this.expand();
    889             if (this.propertiesTreeOutline.children.length === 0)
    890                 this.addNewBlankProperty().startEditing();
    891             else {
    892                 var item = this.propertiesTreeOutline.children[0]
    893                 item.startEditing(item.valueElement);
     913            if (moveDirection === "forward") {
     914                this.expand();
     915                if (this.propertiesTreeOutline.children.length === 0)
     916                    this.addNewBlankProperty().startEditing();
     917                else {
     918                    var item = this.propertiesTreeOutline.children[0]
     919                    item.startEditing(item.nameElement);
     920                }
     921            } else {
     922                var previousSection = this.previousEditableSibling();
     923                if (!previousSection)
     924                    return;
     925
     926                previousSection.expand();
     927                previousSection.addNewBlankProperty().startEditing();
    894928            }
    895929        }
     
    13471381        this.listItemElement.appendChild(document.createTextNode(";"));
    13481382
    1349         if (!this.parsedOk)
     1383        if (!this.parsedOk) {
     1384            // Avoid having longhands under an invalid shorthand.
     1385            this.hasChildren = false;
    13501386            this.listItemElement.addStyleClass("not-parsed-ok");
     1387        }
    13511388        if (this.property.inactive)
    13521389            this.listItemElement.addStyleClass("inactive");
     
    14611498            return;
    14621499
    1463         if (WebInspector.isBeingEdited(this.listItemElement) || (this.treeOutline.section && !this.treeOutline.section.editable))
     1500        if (this.treeOutline.section && !this.treeOutline.section.editable)
     1501            return;
     1502
     1503        if (!selectElement)
     1504            selectElement = this.nameElement; // No arguments passed in - edit the name element by default.
     1505        else
     1506            selectElement = selectElement.enclosingNodeOrSelfWithClass("webkit-css-property") || selectElement.enclosingNodeOrSelfWithClass("value");
     1507
     1508        var isEditingName = selectElement === this.nameElement;
     1509        if (!isEditingName && selectElement !== this.valueElement) {
     1510            // Double-click in the LI - start editing value.
     1511            isEditingName = false;
     1512            selectElement = this.valueElement;
     1513        }
     1514
     1515        if (WebInspector.isBeingEdited(selectElement))
    14641516            return;
    14651517
     
    14671519            expanded: this.expanded,
    14681520            hasChildren: this.hasChildren,
    1469             keyDownListener: this.editingKeyDown.bind(this),
    1470             keyPressListener: this.editingKeyPress.bind(this)
     1521            keyDownListener: isEditingName ? this.editingNameKeyDown.bind(this) : this.editingValueKeyDown.bind(this),
     1522            keyPressListener: isEditingName ? this.editingNameKeyPress.bind(this) : this.editingValueKeyPress.bind(this),
     1523            isEditingName: isEditingName,
    14711524        };
    14721525
     
    14741527        this.hasChildren = false;
    14751528
    1476         if (!selectElement)
    1477             selectElement = this.listItemElement;
    1478 
    1479         this.listItemElement.addEventListener("keydown", context.keyDownListener, false);
    1480         this.listItemElement.addEventListener("keypress", context.keyPressListener, false);
    1481 
    1482         WebInspector.startEditing(this.listItemElement, this.editingCommitted.bind(this), this.editingCancelled.bind(this), context);
     1529        selectElement.addEventListener("keydown", context.keyDownListener, false);
     1530        selectElement.addEventListener("keypress", context.keyPressListener, false);
     1531        if (selectElement.parentElement)
     1532            selectElement.parentElement.addStyleClass("child-editing");
     1533        selectElement.textContent = selectElement.textContent; // remove color swatch and the like
     1534
     1535        function shouldCommitValueSemicolon(text, cursorPosition)
     1536        {
     1537            // FIXME: should this account for semicolons inside comments?
     1538            var openQuote = "";
     1539            for (var i = 0; i < cursorPosition; ++i) {
     1540                var ch = text[i];
     1541                if (ch === "\\" && openQuote !== "")
     1542                    ++i; // skip next character inside string
     1543                else if (!openQuote && (ch === "\"" || ch === "'"))
     1544                    openQuote = ch;
     1545                else if (openQuote === ch)
     1546                    openQuote = "";
     1547            }
     1548            return !openQuote;
     1549        }
     1550
     1551        function nameValueFinishHandler(context, isEditingName, event)
     1552        {
     1553            // FIXME: the ":"/";" detection does not work for non-US layouts due to the event being keydown rather than keypress.
     1554            var isFieldInputTerminated = (event.keyCode === WebInspector.KeyboardShortcut.Keys.Semicolon.code) &&
     1555                (isEditingName ? event.shiftKey : (!event.shiftKey && shouldCommitValueSemicolon(event.target.textContent, event.target.selectionLeftOffset)));
     1556            if (isEnterKey(event) || isFieldInputTerminated) {
     1557                // Enter or colon (for name)/semicolon outside of string (for value).
     1558                event.preventDefault();
     1559                return "move-forward";
     1560            } else if (event.keyCode === WebInspector.KeyboardShortcut.Keys.Esc.code)
     1561                return "cancel";
     1562            else if (event.keyIdentifier === "U+0009") // Tab key.
     1563                return "move-" + (event.shiftKey ? "backward" : "forward");
     1564        }
     1565
     1566        WebInspector.startEditing(selectElement, {
     1567            context: context,
     1568            commitHandler: this.editingCommitted.bind(this),
     1569            cancelHandler: this.editingCancelled.bind(this),
     1570            customFinishHandler: nameValueFinishHandler.bind(this, context, isEditingName)
     1571        });
    14831572        window.getSelection().setBaseAndExtent(selectElement, 0, selectElement, 1);
    14841573    },
    14851574
    1486     editingKeyPress: function(event)
    1487     {
     1575    editingNameKeyPress: function(event)
     1576    {
     1577        // Complete property names.
     1578        var character = event.data.toLowerCase();
     1579        if (character && /[a-z-]/.test(character)) {
     1580            var selection = window.getSelection();
     1581            var prefix = selection.anchorNode.textContent.substring(0, selection.anchorOffset);
     1582            var property = WebInspector.CSSCompletions.firstStartsWith(prefix + character);
     1583
     1584            if (!selection.isCollapsed)
     1585                selection.deleteFromDocument();
     1586
     1587            this.restoreNameElement();
     1588
     1589            if (property) {
     1590                if (property !== this.nameElement.textContent)
     1591                    this.nameElement.textContent = property;
     1592                this.nameElement.firstChild.select(prefix.length + 1);
     1593                event.preventDefault();
     1594            }
     1595        }
     1596    },
     1597
     1598    editingValueKeyPress: function(event)
     1599    {
     1600        // FIXME: This should complete property values.
     1601    },
     1602
     1603    editingNameKeyDown: function(event)
     1604    {
     1605        var showNext;
     1606        if (event.keyIdentifier === "Up")
     1607            showNext = false;
     1608        else if (event.keyIdentifier === "Down")
     1609            showNext = true;
     1610        else
     1611            return;
     1612
    14881613        var selection = window.getSelection();
    1489         var colonIndex = this.listItemElement.textContent.indexOf(":");
    1490         var selectionLeftOffset = event.target.selectionLeftOffset;
    1491 
    1492         if (colonIndex < 0 || selectionLeftOffset <= colonIndex) {
    1493             // Complete property names.
    1494             var character = event.data.toLowerCase();
    1495             if (character && /[a-z-]/.test(character)) {
    1496                 var prefix = selection.anchorNode.textContent.substring(0, selection.anchorOffset);
    1497                 var property = WebInspector.CSSCompletions.firstStartsWith(prefix + character);
    1498 
    1499                 if (!selection.isCollapsed)
    1500                     selection.deleteFromDocument();
    1501 
    1502                 this.restoreNameElement();
    1503 
    1504                 if (property) {
    1505                     if (property !== this.nameElement.textContent)
    1506                         this.nameElement.textContent = property;
    1507                     this.nameElement.firstChild.select(prefix.length + 1);
    1508                     event.preventDefault();
    1509                 }
    1510             }
    1511         } else {
    1512             // FIXME: This should complete property values.
    1513         }
    1514     },
    1515 
    1516     editingKeyDown: function(event)
     1614        if (!selection.rangeCount)
     1615            return;
     1616
     1617        var selectionRange = selection.getRangeAt(0);
     1618        if (selectionRange.commonAncestorContainer !== this.nameElement && !selectionRange.commonAncestorContainer.isDescendant(this.nameElement))
     1619            return;
     1620
     1621        const styleValueDelimeters = " \t\n\"':;,/()";
     1622        var wordRange = selectionRange.startContainer.rangeOfWord(selectionRange.startOffset, styleValueDelimeters, this.nameElement);
     1623        var wordString = wordRange.toString();
     1624        var prefix = selectionRange.startContainer.textContent.substring(0, selectionRange.startOffset);
     1625        var property;
     1626
     1627        if (showNext)
     1628            property = WebInspector.CSSCompletions.next(wordString, prefix);
     1629        else
     1630            property = WebInspector.CSSCompletions.previous(wordString, prefix);
     1631
     1632        if (property) {
     1633            this.nameElement.textContent = property;
     1634            this.nameElement.firstChild.select(selectionRange.startOffset);
     1635        }
     1636        event.preventDefault();
     1637    },
     1638
     1639    editingValueKeyDown: function(event)
    15171640    {
    15181641        var arrowKeyPressed = (event.keyIdentifier === "Up" || event.keyIdentifier === "Down");
     
    15261649
    15271650        var selectionRange = selection.getRangeAt(0);
    1528         if (selectionRange.commonAncestorContainer !== this.listItemElement && !selectionRange.commonAncestorContainer.isDescendant(this.listItemElement))
    1529             return;
    1530 
    1531         // If there are several properties in the text, do not handle increments/decrements.
    1532         var text = event.target.textContent.trim();
    1533         var openQuote;
    1534         var wasEscape = false;
    1535         // Exclude the last character from the check since it is allowed to be ";".
    1536         for (var i = 0; i < text.length - 1; ++i) {
    1537             var ch = text.charAt(i);
    1538             if (ch === "\\") {
    1539                 wasEscape = true;
    1540                 continue;
    1541             }
    1542             if (ch === ";" && !openQuote)
    1543                 return; // Do not handle name/value shifts if the property is compound.
    1544             if ((ch === "'" || ch === "\"") && !wasEscape) {
    1545                 if (!openQuote)
    1546                     openQuote = ch;
    1547                 else if (ch === openQuote)
    1548                     openQuote = null;
    1549             }
    1550             wasEscape = false;
    1551         }
     1651        if (selectionRange.commonAncestorContainer !== this.valueElement && !selectionRange.commonAncestorContainer.isDescendant(this.valueElement))
     1652            return;
    15521653
    15531654        const styleValueDelimeters = " \t\n\"':;,/()";
    1554         var wordRange = selectionRange.startContainer.rangeOfWord(selectionRange.startOffset, styleValueDelimeters, this.listItemElement);
     1655        var wordRange = selectionRange.startContainer.rangeOfWord(selectionRange.startOffset, styleValueDelimeters, this.valueElement);
    15551656        var wordString = wordRange.toString();
    15561657        var replacementString = wordString;
     
    15941695
    15951696            replacementString = prefix + number + suffix;
    1596         } else if (selection.containsNode(this.nameElement, true)) {
    1597             var prefix = selectionRange.startContainer.textContent.substring(0, selectionRange.startOffset);
    1598             var property;
    1599 
    1600             if (event.keyIdentifier === "Up")
    1601                 property = WebInspector.CSSCompletions.previous(wordString, prefix);
    1602             else if (event.keyIdentifier === "Down")
    1603                 property = WebInspector.CSSCompletions.next(wordString, prefix);
    1604 
    1605             var startOffset = selectionRange.startOffset;
    1606             if (property) {
    1607                 this.nameElement.textContent = property;
    1608                 this.nameElement.firstChild.select(startOffset);
    1609             }
    1610             event.preventDefault();
    1611             return;
    16121697        } else {
    16131698            // FIXME: this should cycle through known keywords for the current property value.
     
    16331718            this.originalPropertyText = this.property.propertyText;
    16341719        }
    1635         this.applyStyleText(this.listItemElement.textContent);
     1720
     1721        // Synthesize property text disregarding any comments, custom whitespace etc.
     1722        this.applyStyleText(this.nameElement.textContent + ": " + this.valueElement.textContent);
    16361723    },
    16371724
     
    16411728        if (context.expanded)
    16421729            this.expand();
    1643         this.listItemElement.removeEventListener("keydown", context.keyDownListener, false);
    1644         this.listItemElement.removeEventListener("keypress", context.keyPressListener, false);
     1730        var editedElement = context.isEditingName ? this.nameElement : this.valueElement;
     1731        editedElement.removeEventListener("keydown", context.keyDownListener, false);
     1732        editedElement.removeEventListener("keypress", context.keyPressListener, false);
     1733        if (editedElement.parentElement)
     1734            editedElement.parentElement.removeStyleClass("child-editing");
     1735
    16451736        delete this.originalPropertyText;
    16461737    },
     
    16621753    {
    16631754        this.editingEnded(context);
     1755        var isEditingName = context.isEditingName;
    16641756
    16651757        // Determine where to move to before making changes
    1666         var newProperty, moveToPropertyName, moveToSelector;
     1758        var createNewProperty, moveToPropertyName, moveToSelector;
    16671759        var moveTo = this;
    1668         do {
    1669             moveTo = (moveDirection === "forward" ? moveTo.nextSibling : moveTo.previousSibling);
    1670         } while(moveTo && !moveTo.selectable);
    1671 
    1672         if (moveTo)
    1673             moveToPropertyName = moveTo.name;
    1674         else if (moveDirection === "forward")
    1675             newProperty = true;
    1676         else if (moveDirection === "backward" && this.treeOutline.section.rule)
    1677             moveToSelector = true;
    1678 
    1679         // Make the Changes and trigger the moveToNextCallback after updating
     1760        var moveToOther = (isEditingName ^ (moveDirection === "forward"));
     1761        var abandonNewProperty = this._newProperty && !userInput && (moveToOther || isEditingName);
     1762        if (moveDirection === "forward" && !isEditingName || moveDirection === "backward" && isEditingName) {
     1763            do {
     1764                moveTo = (moveDirection === "forward" ? moveTo.nextSibling : moveTo.previousSibling);
     1765            } while(moveTo && !moveTo.selectable);
     1766
     1767           if (moveTo)
     1768                moveToPropertyName = moveTo.name;
     1769            else if (moveDirection === "forward" && (!this._newProperty || userInput))
     1770                createNewProperty = true;
     1771            else if (moveDirection === "backward" && this.treeOutline.section.rule)
     1772                moveToSelector = true;
     1773        }
     1774
     1775        // Make the Changes and trigger the moveToNextCallback after updating.
    16801776        var blankInput = /^\s*$/.test(userInput);
    1681         if (userInput !== previousContent || (this._newProperty && blankInput)) { // only if something changed, or adding a new style and it was blank
    1682             this.treeOutline.section._afterUpdate = moveToNextCallback.bind(this, this._newProperty, !blankInput);
    1683             this.applyStyleText(userInput, true);
    1684         } else
    1685             moveToNextCallback(this._newProperty, false, this.treeOutline.section, false);
    1686 
    1687         // The Callback to start editing the next property
     1777        var shouldCommitNewProperty = this._newProperty && (moveToOther || (!moveDirection && !isEditingName) || (isEditingName && blankInput));
     1778
     1779        if ((userInput !== previousContent && !this._newProperty) || shouldCommitNewProperty) {
     1780            this.treeOutline.section._afterUpdate = moveToNextCallback.bind(this, this._newProperty, !blankInput, this.treeOutline.section);
     1781            var propertyText;
     1782            if (blankInput || (this._newProperty && /^\s*$/.test(this.valueElement.textContent)))
     1783                propertyText = "";
     1784            else {
     1785                if (isEditingName)
     1786                    propertyText = userInput + ": " + this.valueElement.textContent;
     1787                else
     1788                    propertyText = this.nameElement.textContent + ": " + userInput;
     1789            }
     1790            this.applyStyleText(propertyText, true);
     1791        } else {
     1792            if (!this._newProperty)
     1793                this.updateTitle();
     1794            moveToNextCallback(this._newProperty, false, this.treeOutline.section);
     1795        }
     1796
     1797        var moveToIndex = moveTo && this.treeOutline ? this.treeOutline.children.indexOf(moveTo) : -1;
     1798
     1799        // The Callback to start editing the next/previous property/selector.
    16881800        function moveToNextCallback(alreadyNew, valueChanged, section)
    16891801        {
     
    16911803                return;
    16921804
    1693             // User just tabbed through without changes
     1805            // User just tabbed through without changes.
    16941806            if (moveTo && moveTo.parent) {
    1695                 moveTo.startEditing(moveTo.valueElement);
     1807                moveTo.startEditing(!isEditingName ? moveTo.nameElement : moveTo.valueElement);
    16961808                return;
    16971809            }
    16981810
    1699             // User has made a change then tabbed, wiping all the original treeElements,
    1700             // recalculate the new treeElement for the same property we were going to edit next
    1701             // FIXME(apavlov): this will not work for multiple same-named properties in a style
    1702             //                 (the first one will always be returned).
     1811            // User has made a change then tabbed, wiping all the original treeElements.
     1812            // Recalculate the new treeElement for the same property we were going to edit next.
    17031813            if (moveTo && !moveTo.parent) {
    1704                 var treeElement = section.findTreeElementWithName(moveToPropertyName);
    1705                 if (treeElement)
    1706                     treeElement.startEditing(treeElement.valueElement);
    1707                 return;
    1708             }
    1709 
    1710             // Create a new attribute in this section
    1711             if (newProperty) {
    1712                 if (alreadyNew && !valueChanged)
     1814                var propertyElements = section.propertiesTreeOutline.children;
     1815                if (moveDirection === "forward" && blankInput && !isEditingName)
     1816                    --moveToIndex;
     1817                if (moveToIndex >= propertyElements.length && !this._newProperty)
     1818                    createNewProperty = true;
     1819                else {
     1820                    var treeElement = moveToIndex >= 0 ? propertyElements[moveToIndex] : null;
     1821                    if (treeElement) {
     1822                        treeElement.startEditing(!isEditingName ? treeElement.nameElement : treeElement.valueElement);
     1823                        return;
     1824                    } else if (!alreadyNew)
     1825                        moveToSelector = true;
     1826                }
     1827            }
     1828
     1829            // Create a new attribute in this section (or move to next editable selector if possible).
     1830            if (createNewProperty) {
     1831                if (alreadyNew && !valueChanged && (isEditingName ^ (moveDirection === "backward")))
    17131832                    return;
    17141833
     
    17171836            }
    17181837
     1838            if (abandonNewProperty) {
     1839                var sectionToEdit = moveDirection === "backward" ? section : section.nextEditableSibling();
     1840                if (sectionToEdit && sectionToEdit.rule)
     1841                    sectionToEdit.startEditingSelector();
     1842                return;
     1843            }
     1844
    17191845            if (moveToSelector)
    17201846                section.startEditingSelector();
     
    17261852        var section = this.treeOutline.section;
    17271853        var elementsPanel = WebInspector.panels.elements;
    1728         styleText = styleText.replace(/\s/g, " ").trim(); // replace &nbsp; with whitespace.
     1854        styleText = styleText.replace(/\s/g, " ").trim(); // Replace &nbsp; with whitespace.
    17291855        var styleTextLength = styleText.length;
    1730         if (!styleTextLength && updateInterface) {
    1731             if (this._newProperty) {
    1732                 // The user deleted everything, so remove the tree element and update.
    1733                 this.parent.removeChild(this);
    1734                 section.afterUpdate();
    1735                 return;
    1736             } else
    1737                 delete section._afterUpdate;
     1856        if (!styleTextLength && updateInterface && this._newProperty) {
     1857            // The user deleted everything, so remove the tree element and update.
     1858            this.parent.removeChild(this);
     1859            section.afterUpdate();
     1860            return;
    17381861        }
    17391862
  • trunk/WebCore/inspector/front-end/TextViewer.js

    r69276 r73913  
    269269        var cancelEditingCallback = this._cancelEditingLine.bind(this, lineRow.lastChild, oldContent);
    270270        var commitEditingCallback = this._commitEditingLine.bind(this, lineRow.lineNumber, lineRow.lastChild, cancelEditingCallback);
    271         this._editingLine = WebInspector.startEditing(lineRow.lastChild, commitEditingCallback, cancelEditingCallback, null, true);
     271        this._editingLine = WebInspector.startEditing(lineRow.lastChild, {
     272            context: null,
     273            commitHandler: commitEditingCallback,
     274            cancelHandler: cancelEditingCallback,
     275            multiline: true
     276        });
    272277    },
    273278
  • trunk/WebCore/inspector/front-end/WatchExpressionsSidebarPane.js

    r71109 r73913  
    241241        this.listItemElement.addStyleClass("editing-sub-part");
    242242
    243         WebInspector.startEditing(this.nameElement, this.editingCommitted.bind(this), this.editingCancelled.bind(this), context);
     243        WebInspector.startEditing(this.nameElement, {
     244            context: context,
     245            commitHandler: this.editingCommitted.bind(this),
     246            cancelHandler: this.editingCancelled.bind(this)
     247        });
    244248    },
    245249
  • trunk/WebCore/inspector/front-end/inspector.css

    r73311 r73913  
    14251425}
    14261426
     1427.child-editing {
     1428    color: black !important;
     1429    text-decoration: none !important;
     1430    overflow: visible !important;
     1431}
     1432
    14271433.editing br {
    14281434    display: none;
  • trunk/WebCore/inspector/front-end/inspector.js

    r73909 r73913  
    19161916}
    19171917
    1918 WebInspector.startEditing = function(element, committedCallback, cancelledCallback, context, multiline)
     1918// Available config fields (all optional):
     1919// context: Object - an arbitrary context object to be passed to the commit and cancel handlers
     1920// commitHandler: Function - handles editing "commit" outcome
     1921// cancelHandler: Function - handles editing "cancel" outcome
     1922// customFinishHandler: Function - custom finish handler for the editing session (invoked on keydown)
     1923// multiline: Boolean - whether the edited element is multiline
     1924WebInspector.startEditing = function(element, config)
    19191925{
    19201926    if (element.__editing)
     
    19231929    WebInspector.__editing = true;
    19241930
     1931    config = config || {};
     1932    var committedCallback = config.commitHandler;
     1933    var cancelledCallback = config.cancelHandler;
     1934    var context = config.context;
    19251935    var oldText = getContent(element);
    19261936    var moveDirection = "";
     
    19781988    }
    19791989
    1980     function keyDownEventListener(event) {
     1990    function defaultFinishHandler(event)
     1991    {
    19811992        var isMetaOrCtrl = WebInspector.isMac() ?
    19821993            event.metaKey && !event.shiftKey && !event.ctrlKey && !event.altKey :
    19831994            event.ctrlKey && !event.shiftKey && !event.metaKey && !event.altKey;
    1984         if (isEnterKey(event) && (!multiline || isMetaOrCtrl)) {
     1995        if (isEnterKey(event) && (!config.multiline || isMetaOrCtrl))
     1996            return "commit";
     1997        else if (event.keyCode === WebInspector.KeyboardShortcut.Keys.Esc.code)
     1998            return "cancel";
     1999        else if (event.keyIdentifier === "U+0009") // Tab key
     2000            return "move-" + (event.shiftKey ? "backward" : "forward");
     2001    }
     2002
     2003    function keyDownEventListener(event)
     2004    {
     2005        var handler = config.customFinishHandler || defaultFinishHandler;
     2006        var result = handler(event);
     2007        if (result === "commit") {
    19852008            editingCommitted.call(element);
    19862009            event.preventDefault();
    19872010            event.stopPropagation();
    1988         } else if (event.keyCode === WebInspector.KeyboardShortcut.Keys.Esc.code) {
     2011        } else if (result === "cancel") {
    19892012            editingCancelled.call(element);
    19902013            event.preventDefault();
    19912014            event.stopPropagation();
    1992         } else if (event.keyIdentifier === "U+0009") // Tab key
    1993             moveDirection = (event.shiftKey ? "backward" : "forward");
     2015        } else if (result && result.indexOf("move-") === 0) {
     2016            moveDirection = result.substring(5);
     2017            if (event.keyIdentifier !== "U+0009")
     2018                blurEventListener();
     2019        }
    19942020    }
    19952021
  • trunk/WebCore/inspector/front-end/treeoutline.js

    r72072 r73913  
    822822    this.selected = true;
    823823    this.treeOutline._childrenListNode.focus();
     824
     825    // Focusing on another node may detach "this" from tree.
     826    if (!this.treeOutline)
     827        return;
    824828    this.treeOutline.selectedTreeElement = this;
    825829    if (this._listItemNode)
Note: See TracChangeset for help on using the changeset viewer.