• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
3 * Copyright (C) 2008 Matt Lilek <webkit@mattlilek.com>
4 * Copyright (C) 2009 Joseph Pecoraro
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
10 * 1.  Redistributions of source code must retain the above copyright
11 *     notice, this list of conditions and the following disclaimer.
12 * 2.  Redistributions in binary form must reproduce the above copyright
13 *     notice, this list of conditions and the following disclaimer in the
14 *     documentation and/or other materials provided with the distribution.
15 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
16 *     its contributors may be used to endorse or promote products derived
17 *     from this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
20 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
23 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31WebInspector.ElementsTreeOutline = function() {
32    this.element = document.createElement("ol");
33    this.element.addEventListener("mousedown", this._onmousedown.bind(this), false);
34    this.element.addEventListener("dblclick", this._ondblclick.bind(this), false);
35    this.element.addEventListener("mousemove", this._onmousemove.bind(this), false);
36    this.element.addEventListener("mouseout", this._onmouseout.bind(this), false);
37
38    TreeOutline.call(this, this.element);
39
40    this.includeRootDOMNode = true;
41    this.selectEnabled = false;
42    this.rootDOMNode = null;
43    this.focusedDOMNode = null;
44}
45
46WebInspector.ElementsTreeOutline.prototype = {
47    get rootDOMNode()
48    {
49        return this._rootDOMNode;
50    },
51
52    set rootDOMNode(x)
53    {
54        if (objectsAreSame(this._rootDOMNode, x))
55            return;
56
57        this._rootDOMNode = x;
58
59        this.update();
60    },
61
62    get focusedDOMNode()
63    {
64        return this._focusedDOMNode;
65    },
66
67    set focusedDOMNode(x)
68    {
69        if (objectsAreSame(this._focusedDOMNode, x)) {
70            this.revealAndSelectNode(x);
71            return;
72        }
73
74        this._focusedDOMNode = x;
75
76        this.revealAndSelectNode(x);
77
78        // The revealAndSelectNode() method might find a different element if there is inlined text,
79        // and the select() call would change the focusedDOMNode and reenter this setter. So to
80        // avoid calling focusedNodeChanged() twice, first check if _focusedDOMNode is the same
81        // node as the one passed in.
82        if (objectsAreSame(this._focusedDOMNode, x)) {
83            this.focusedNodeChanged();
84
85            if (x && !this.suppressSelectHighlight) {
86                InspectorController.highlightDOMNode(x);
87
88                if ("_restorePreviousHighlightNodeTimeout" in this)
89                    clearTimeout(this._restorePreviousHighlightNodeTimeout);
90
91                function restoreHighlightToHoveredNode()
92                {
93                    var hoveredNode = WebInspector.hoveredDOMNode;
94                    if (hoveredNode)
95                        InspectorController.highlightDOMNode(hoveredNode);
96                    else
97                        InspectorController.hideDOMNodeHighlight();
98                }
99
100                this._restorePreviousHighlightNodeTimeout = setTimeout(restoreHighlightToHoveredNode, 2000);
101            }
102        }
103    },
104
105    update: function()
106    {
107        this.removeChildren();
108
109        if (!this.rootDOMNode)
110            return;
111
112        var treeElement;
113        if (this.includeRootDOMNode) {
114            treeElement = new WebInspector.ElementsTreeElement(this.rootDOMNode);
115            treeElement.selectable = this.selectEnabled;
116            this.appendChild(treeElement);
117        } else {
118            // FIXME: this could use findTreeElement to reuse a tree element if it already exists
119            var node = (Preferences.ignoreWhitespace ? firstChildSkippingWhitespace.call(this.rootDOMNode) : this.rootDOMNode.firstChild);
120            while (node) {
121                treeElement = new WebInspector.ElementsTreeElement(node);
122                treeElement.selectable = this.selectEnabled;
123                this.appendChild(treeElement);
124                node = Preferences.ignoreWhitespace ? nextSiblingSkippingWhitespace.call(node) : node.nextSibling;
125            }
126        }
127
128        this.updateSelection();
129    },
130
131    updateSelection: function()
132    {
133        if (!this.selectedTreeElement)
134            return;
135        var element = this.treeOutline.selectedTreeElement;
136        element.updateSelection();
137    },
138
139    focusedNodeChanged: function(forceUpdate) {},
140
141    findTreeElement: function(node, isAncestor, getParent, equal)
142    {
143        if (typeof isAncestor === "undefined")
144            isAncestor = isAncestorIncludingParentFrames;
145        if (typeof getParent === "undefined")
146            getParent = parentNodeOrFrameElement;
147        if (typeof equal === "undefined")
148            equal = objectsAreSame;
149
150        var treeElement = TreeOutline.prototype.findTreeElement.call(this, node, isAncestor, getParent, equal);
151        if (!treeElement && node.nodeType === Node.TEXT_NODE) {
152            // The text node might have been inlined if it was short, so try to find the parent element.
153            treeElement = TreeOutline.prototype.findTreeElement.call(this, node.parentNode, isAncestor, getParent, equal);
154        }
155
156        return treeElement;
157    },
158
159    revealAndSelectNode: function(node)
160    {
161        if (!node)
162            return;
163
164        var treeElement = this.findTreeElement(node);
165        if (!treeElement)
166            return;
167
168        treeElement.reveal();
169        treeElement.select();
170    },
171
172    _treeElementFromEvent: function(event)
173    {
174        var root = this.element;
175
176        // We choose this X coordinate based on the knowledge that our list
177        // items extend nearly to the right edge of the outer <ol>.
178        var x = root.totalOffsetLeft + root.offsetWidth - 20;
179
180        var y = event.pageY;
181
182        // Our list items have 1-pixel cracks between them vertically. We avoid
183        // the cracks by checking slightly above and slightly below the mouse
184        // and seeing if we hit the same element each time.
185        var elementUnderMouse = this.treeElementFromPoint(x, y);
186        var elementAboveMouse = this.treeElementFromPoint(x, y - 2);
187        var element;
188        if (elementUnderMouse === elementAboveMouse)
189            element = elementUnderMouse;
190        else
191            element = this.treeElementFromPoint(x, y + 2);
192
193        return element;
194    },
195
196    _ondblclick: function(event)
197    {
198        var element = this._treeElementFromEvent(event);
199
200        if (!element || !element.ondblclick)
201            return;
202
203        element.ondblclick(element, event);
204    },
205
206    _onmousedown: function(event)
207    {
208        var element = this._treeElementFromEvent(event);
209
210        if (!element || element.isEventWithinDisclosureTriangle(event))
211            return;
212
213        element.select();
214    },
215
216    _onmousemove: function(event)
217    {
218        if (this._previousHoveredElement) {
219            this._previousHoveredElement.hovered = false;
220            delete this._previousHoveredElement;
221        }
222
223        var element = this._treeElementFromEvent(event);
224        if (element && !element.elementCloseTag) {
225            element.hovered = true;
226            this._previousHoveredElement = element;
227        }
228
229        WebInspector.hoveredDOMNode = (element && !element.elementCloseTag ? element.representedObject : null);
230    },
231
232    _onmouseout: function(event)
233    {
234        var nodeUnderMouse = document.elementFromPoint(event.pageX, event.pageY);
235        if (nodeUnderMouse.isDescendant(this.element))
236            return;
237
238        if (this._previousHoveredElement) {
239            this._previousHoveredElement.hovered = false;
240            delete this._previousHoveredElement;
241        }
242
243        WebInspector.hoveredDOMNode = null;
244    }
245}
246
247WebInspector.ElementsTreeOutline.prototype.__proto__ = TreeOutline.prototype;
248
249WebInspector.ElementsTreeElement = function(node)
250{
251    var hasChildren = node.contentDocument || (Preferences.ignoreWhitespace ? (firstChildSkippingWhitespace.call(node) ? true : false) : node.hasChildNodes());
252    var titleInfo = nodeTitleInfo.call(node, hasChildren, WebInspector.linkifyURL);
253
254    if (titleInfo.hasChildren)
255        this.whitespaceIgnored = Preferences.ignoreWhitespace;
256
257    // The title will be updated in onattach.
258    TreeElement.call(this, "", node, titleInfo.hasChildren);
259
260    if (this.representedObject.nodeType == Node.ELEMENT_NODE)
261        this._canAddAttributes = true;
262}
263
264WebInspector.ElementsTreeElement.prototype = {
265    get highlighted()
266    {
267        return this._highlighted;
268    },
269
270    set highlighted(x)
271    {
272        if (this._highlighted === x)
273            return;
274
275        this._highlighted = x;
276
277        if (this.listItemElement) {
278            if (x)
279                this.listItemElement.addStyleClass("highlighted");
280            else
281                this.listItemElement.removeStyleClass("highlighted");
282        }
283    },
284
285    get hovered()
286    {
287        return this._hovered;
288    },
289
290    set hovered(x)
291    {
292        if (this._hovered === x)
293            return;
294
295        this._hovered = x;
296
297        if (this.listItemElement) {
298            if (x) {
299                this.updateSelection();
300                this.listItemElement.addStyleClass("hovered");
301            } else
302                this.listItemElement.removeStyleClass("hovered");
303            if (this._canAddAttributes)
304                this.toggleNewAttributeButton();
305        }
306    },
307
308    toggleNewAttributeButton: function()
309    {
310        function removeWhenEditing(event)
311        {
312            if (this._addAttributeElement && this._addAttributeElement.parentNode)
313                this._addAttributeElement.parentNode.removeChild(this._addAttributeElement);
314            delete this._addAttributeElement;
315        }
316
317        if (!this._addAttributeElement && this._hovered && !this._editing) {
318            var span = document.createElement("span");
319            span.className = "add-attribute";
320            span.textContent = "\u2026";
321            span.addEventListener("dblclick", removeWhenEditing.bind(this), false);
322            this._addAttributeElement = span;
323
324            var tag = this.listItemElement.getElementsByClassName("webkit-html-tag")[0];
325            this._insertInLastAttributePosition(tag, span);
326        } else if (!this._hovered && this._addAttributeElement) {
327            if (this._addAttributeElement.parentNode)
328                this._addAttributeElement.parentNode.removeChild(this._addAttributeElement);
329            delete this._addAttributeElement;
330        }
331    },
332
333    updateSelection: function()
334    {
335        var listItemElement = this.listItemElement;
336        if (!listItemElement)
337            return;
338
339        if (document.body.offsetWidth <= 0) {
340            // The stylesheet hasn't loaded yet or the window is closed,
341            // so we can't calculate what is need. Return early.
342            return;
343        }
344
345        if (!this.selectionElement) {
346            this.selectionElement = document.createElement("div");
347            this.selectionElement.className = "selection selected";
348            listItemElement.insertBefore(this.selectionElement, listItemElement.firstChild);
349        }
350
351        this.selectionElement.style.height = listItemElement.offsetHeight + "px";
352    },
353
354    onattach: function()
355    {
356        this.listItemElement.addEventListener("mousedown", this.onmousedown.bind(this), false);
357
358        if (this._highlighted)
359            this.listItemElement.addStyleClass("highlighted");
360
361        if (this._hovered) {
362            this.updateSelection();
363            this.listItemElement.addStyleClass("hovered");
364        }
365
366        this._updateTitle();
367
368        this._preventFollowingLinksOnDoubleClick();
369    },
370
371    _preventFollowingLinksOnDoubleClick: function()
372    {
373        var links = this.listItemElement.querySelectorAll("li > .webkit-html-tag > .webkit-html-attribute > .webkit-html-external-link, li > .webkit-html-tag > .webkit-html-attribute > .webkit-html-resource-link");
374        if (!links)
375            return;
376
377        for (var i = 0; i < links.length; ++i)
378            links[i].preventFollowOnDoubleClick = true;
379    },
380
381    onpopulate: function()
382    {
383        if (this.children.length || this.whitespaceIgnored !== Preferences.ignoreWhitespace)
384            return;
385
386        this.whitespaceIgnored = Preferences.ignoreWhitespace;
387
388        this.updateChildren();
389    },
390
391    updateChildren: function(fullRefresh)
392    {
393        if (fullRefresh) {
394            var selectedTreeElement = this.treeOutline.selectedTreeElement;
395            if (selectedTreeElement && selectedTreeElement.hasAncestor(this))
396                this.select();
397            this.removeChildren();
398        }
399
400        var treeElement = this;
401        var treeChildIndex = 0;
402
403        function updateChildrenOfNode(node)
404        {
405            var treeOutline = treeElement.treeOutline;
406            var child = (Preferences.ignoreWhitespace ? firstChildSkippingWhitespace.call(node) : node.firstChild);
407            while (child) {
408                var currentTreeElement = treeElement.children[treeChildIndex];
409                if (!currentTreeElement || !objectsAreSame(currentTreeElement.representedObject, child)) {
410                    // Find any existing element that is later in the children list.
411                    var existingTreeElement = null;
412                    for (var i = (treeChildIndex + 1); i < treeElement.children.length; ++i) {
413                        if (objectsAreSame(treeElement.children[i].representedObject, child)) {
414                            existingTreeElement = treeElement.children[i];
415                            break;
416                        }
417                    }
418
419                    if (existingTreeElement && existingTreeElement.parent === treeElement) {
420                        // If an existing element was found and it has the same parent, just move it.
421                        var wasSelected = existingTreeElement.selected;
422                        treeElement.removeChild(existingTreeElement);
423                        treeElement.insertChild(existingTreeElement, treeChildIndex);
424                        if (wasSelected)
425                            existingTreeElement.select();
426                    } else {
427                        // No existing element found, insert a new element.
428                        var newElement = new WebInspector.ElementsTreeElement(child);
429                        newElement.selectable = treeOutline.selectEnabled;
430                        treeElement.insertChild(newElement, treeChildIndex);
431                    }
432                }
433
434                child = Preferences.ignoreWhitespace ? nextSiblingSkippingWhitespace.call(child) : child.nextSibling;
435                ++treeChildIndex;
436            }
437        }
438
439        // Remove any tree elements that no longer have this node (or this node's contentDocument) as their parent.
440        for (var i = (this.children.length - 1); i >= 0; --i) {
441            if ("elementCloseTag" in this.children[i])
442                continue;
443
444            var currentChild = this.children[i];
445            var currentNode = currentChild.representedObject;
446            var currentParentNode = currentNode.parentNode;
447
448            if (objectsAreSame(currentParentNode, this.representedObject))
449                continue;
450            if (this.representedObject.contentDocument && objectsAreSame(currentParentNode, this.representedObject.contentDocument))
451                continue;
452
453            var selectedTreeElement = this.treeOutline.selectedTreeElement;
454            if (selectedTreeElement && (selectedTreeElement === currentChild || selectedTreeElement.hasAncestor(currentChild)))
455                this.select();
456
457            this.removeChildAtIndex(i);
458
459            if (this.treeOutline.panel && currentNode.contentDocument)
460                this.treeOutline.panel.unregisterMutationEventListeners(currentNode.contentDocument.defaultView);
461        }
462
463        if (this.representedObject.contentDocument)
464            updateChildrenOfNode(this.representedObject.contentDocument);
465        updateChildrenOfNode(this.representedObject);
466
467        var lastChild = this.children[this.children.length - 1];
468        if (this.representedObject.nodeType == Node.ELEMENT_NODE && (!lastChild || !lastChild.elementCloseTag)) {
469            var title = "<span class=\"webkit-html-tag close\">&lt;/" + this.representedObject.nodeName.toLowerCase().escapeHTML() + "&gt;</span>";
470            var item = new TreeElement(title, null, false);
471            item.selectable = false;
472            item.elementCloseTag = true;
473            this.appendChild(item);
474        }
475    },
476
477    onexpand: function()
478    {
479        this.treeOutline.updateSelection();
480
481        if (this.treeOutline.panel && this.representedObject.contentDocument)
482            this.treeOutline.panel.registerMutationEventListeners(this.representedObject.contentDocument.defaultView);
483    },
484
485    oncollapse: function()
486    {
487        this.treeOutline.updateSelection();
488    },
489
490    onreveal: function()
491    {
492        if (this.listItemElement)
493            this.listItemElement.scrollIntoViewIfNeeded(false);
494    },
495
496    onselect: function()
497    {
498        this.treeOutline.focusedDOMNode = this.representedObject;
499        this.updateSelection();
500    },
501
502    onmousedown: function(event)
503    {
504        if (this._editing)
505            return;
506
507        // Prevent selecting the nearest word on double click.
508        if (event.detail >= 2)
509            event.preventDefault();
510    },
511
512    ondblclick: function(treeElement, event)
513    {
514        if (this._editing)
515            return;
516
517        if (this._startEditing(event, treeElement))
518            return;
519
520        if (this.treeOutline.panel) {
521            this.treeOutline.rootDOMNode = this.representedObject.parentNode;
522            this.treeOutline.focusedDOMNode = this.representedObject;
523        }
524
525        if (this.hasChildren && !this.expanded)
526            this.expand();
527    },
528
529    _insertInLastAttributePosition: function(tag, node)
530    {
531        if (tag.getElementsByClassName("webkit-html-attribute").length > 0)
532            tag.insertBefore(node, tag.lastChild);
533        else {
534            var nodeName = tag.textContent.match(/^<(.*?)>$/)[1];
535            tag.textContent = '';
536            tag.appendChild(document.createTextNode('<'+nodeName));
537            tag.appendChild(node);
538            tag.appendChild(document.createTextNode('>'));
539        }
540    },
541
542    _startEditing: function(event, treeElement)
543    {
544        if (this.treeOutline.focusedDOMNode != this.representedObject)
545            return;
546
547        if (this.representedObject.nodeType != Node.ELEMENT_NODE && this.representedObject.nodeType != Node.TEXT_NODE)
548            return false;
549
550        var textNode = event.target.enclosingNodeOrSelfWithClass("webkit-html-text-node");
551        if (textNode)
552            return this._startEditingTextNode(textNode);
553
554        var attribute = event.target.enclosingNodeOrSelfWithClass("webkit-html-attribute");
555        if (attribute)
556            return this._startEditingAttribute(attribute, event.target);
557
558        var newAttribute = event.target.enclosingNodeOrSelfWithClass("add-attribute");
559        if (newAttribute)
560            return this._addNewAttribute(treeElement.listItemElement);
561
562        return false;
563    },
564
565    _addNewAttribute: function(listItemElement)
566    {
567        var attr = document.createElement("span");
568        attr.className = "webkit-html-attribute";
569        attr.style.marginLeft = "2px"; // overrides the .editing margin rule
570        attr.style.marginRight = "2px"; // overrides the .editing margin rule
571        var name = document.createElement("span");
572        name.className = "webkit-html-attribute-name new-attribute";
573        name.textContent = " ";
574        var value = document.createElement("span");
575        value.className = "webkit-html-attribute-value";
576        attr.appendChild(name);
577        attr.appendChild(value);
578
579        var tag = listItemElement.getElementsByClassName("webkit-html-tag")[0];
580        this._insertInLastAttributePosition(tag, attr);
581        return this._startEditingAttribute(attr, attr);
582    },
583
584    _triggerEditAttribute: function(attributeName)
585    {
586        var attributeElements = this.listItemElement.getElementsByClassName("webkit-html-attribute-name");
587        for (var i = 0, len = attributeElements.length; i < len; ++i) {
588            if (attributeElements[i].textContent === attributeName) {
589                for (var elem = attributeElements[i].nextSibling; elem; elem = elem.nextSibling) {
590                    if (elem.nodeType !== Node.ELEMENT_NODE)
591                        continue;
592
593                    if (elem.hasStyleClass("webkit-html-attribute-value"))
594                        return this._startEditingAttribute(attributeElements[i].parentNode, elem);
595                }
596            }
597        }
598    },
599
600    _startEditingAttribute: function(attribute, elementForSelection)
601    {
602        if (WebInspector.isBeingEdited(attribute))
603            return true;
604
605        var attributeNameElement = attribute.getElementsByClassName("webkit-html-attribute-name")[0];
606        if (!attributeNameElement)
607            return false;
608
609        var attributeName = attributeNameElement.innerText;
610
611        function removeZeroWidthSpaceRecursive(node)
612        {
613            if (node.nodeType === Node.TEXT_NODE) {
614                node.nodeValue = node.nodeValue.replace(/\u200B/g, "");
615                return;
616            }
617
618            if (node.nodeType !== Node.ELEMENT_NODE)
619                return;
620
621            for (var child = node.firstChild; child; child = child.nextSibling)
622                removeZeroWidthSpaceRecursive(child);
623        }
624
625        // Remove zero-width spaces that were added by nodeTitleInfo.
626        removeZeroWidthSpaceRecursive(attribute);
627
628        this._editing = true;
629
630        WebInspector.startEditing(attribute, this._attributeEditingCommitted.bind(this), this._editingCancelled.bind(this), attributeName);
631        window.getSelection().setBaseAndExtent(elementForSelection, 0, elementForSelection, 1);
632
633        return true;
634    },
635
636    _startEditingTextNode: function(textNode)
637    {
638        if (WebInspector.isBeingEdited(textNode))
639            return true;
640
641        this._editing = true;
642
643        WebInspector.startEditing(textNode, this._textNodeEditingCommitted.bind(this), this._editingCancelled.bind(this));
644        window.getSelection().setBaseAndExtent(textNode, 0, textNode, 1);
645
646        return true;
647    },
648
649    _attributeEditingCommitted: function(element, newText, oldText, attributeName, moveDirection)
650    {
651        delete this._editing;
652
653        // Before we do anything, determine where we should move
654        // next based on the current element's settings
655        var moveToAttribute;
656        var newAttribute;
657        if (moveDirection) {
658            var found = false;
659            var attributes = this.representedObject.attributes;
660            for (var i = 0, len = attributes.length; i < len; ++i) {
661                if (attributes[i].name === attributeName) {
662                    found = true;
663                    if (moveDirection === "backward" && i > 0)
664                        moveToAttribute = attributes[i - 1].name;
665                    else if (moveDirection === "forward" && i < attributes.length - 1)
666                        moveToAttribute = attributes[i + 1].name;
667                    else if (moveDirection === "forward" && i === attributes.length - 1)
668                        newAttribute = true;
669                }
670            }
671
672            if (!found && moveDirection === "backward")
673                moveToAttribute = attributes[attributes.length - 1].name;
674            else if (!found && moveDirection === "forward" && !/^\s*$/.test(newText))
675                newAttribute = true;
676        }
677
678        function moveToNextAttributeIfNeeded() {
679            if (moveToAttribute)
680                this._triggerEditAttribute(moveToAttribute);
681            else if (newAttribute)
682                this._addNewAttribute(this.listItemElement);
683        }
684
685        var parseContainerElement = document.createElement("span");
686        parseContainerElement.innerHTML = "<span " + newText + "></span>";
687        var parseElement = parseContainerElement.firstChild;
688
689        if (!parseElement) {
690            this._editingCancelled(element, attributeName);
691            moveToNextAttributeIfNeeded.call(this);
692            return;
693        }
694
695        if (!parseElement.hasAttributes()) {
696            InspectorController.inspectedWindow().Element.prototype.removeAttribute.call(this.representedObject, attributeName);
697            this._updateTitle();
698            moveToNextAttributeIfNeeded.call(this);
699            return;
700        }
701
702        var foundOriginalAttribute = false;
703        for (var i = 0; i < parseElement.attributes.length; ++i) {
704            var attr = parseElement.attributes[i];
705            foundOriginalAttribute = foundOriginalAttribute || attr.name === attributeName;
706            try {
707                InspectorController.inspectedWindow().Element.prototype.setAttribute.call(this.representedObject, attr.name, attr.value);
708            } catch(e) {} // ignore invalid attribute (innerHTML doesn't throw errors, but this can)
709        }
710
711        if (!foundOriginalAttribute)
712            InspectorController.inspectedWindow().Element.prototype.removeAttribute.call(this.representedObject, attributeName);
713
714        this._updateTitle();
715
716        this.treeOutline.focusedNodeChanged(true);
717
718        moveToNextAttributeIfNeeded.call(this);
719    },
720
721    _textNodeEditingCommitted: function(element, newText)
722    {
723        delete this._editing;
724
725        var textNode;
726        if (this.representedObject.nodeType == Node.ELEMENT_NODE) {
727            // We only show text nodes inline in elements if the element only
728            // has a single child, and that child is a text node.
729            textNode = this.representedObject.firstChild;
730        } else if (this.representedObject.nodeType == Node.TEXT_NODE)
731            textNode = this.representedObject;
732
733        textNode.nodeValue = newText;
734        this._updateTitle();
735    },
736
737    _editingCancelled: function(element, context)
738    {
739        delete this._editing;
740
741        this._updateTitle();
742    },
743
744    _updateTitle: function()
745    {
746        var title = nodeTitleInfo.call(this.representedObject, this.hasChildren, WebInspector.linkifyURL).title;
747        this.title = "<span class=\"highlight\">" + title + "</span>";
748        delete this.selectionElement;
749        this.updateSelection();
750        this._preventFollowingLinksOnDoubleClick();
751    },
752}
753
754WebInspector.ElementsTreeElement.prototype.__proto__ = TreeElement.prototype;
755