Updates the elements DOM tree when nodes are added or removed from
authortimothy@apple.com <timothy@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 24 Jul 2008 02:48:36 +0000 (02:48 +0000)
committertimothy@apple.com <timothy@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 24 Jul 2008 02:48:36 +0000 (02:48 +0000)
        the inspected document.

        https://bugs.webkit.org/show_bug.cgi?id=6590
        <rdar://problem/5712921>

        Reviewed by Adam Roben.

        * loader/FrameLoader.cpp:
        (WebCore::FrameLoader::dispatchWindowObjectAvailable): Added a call to
        InspectorController::inspectedWindowScriptObjectCleared.
        * page/InspectorController.cpp:
        (WebCore::InspectorController::inspectedWindowScriptObjectCleared):
        Calls the WebInspector.inspectedWindowCleared script function.
        * page/InspectorController.h:
        * page/inspector/ElementsPanel.js:
        (WebInspector.ElementsPanel): Create the event listener callback wrappers.
        (WebInspector.ElementsPanel.prototype.show): Call _updateModifiedNodes if
        there are any recently modified nodes.
        (WebInspector.ElementsPanel.prototype.reset): Remove previous mutation event listeners.
        Adds a check for InspectorController.isWindowVisible to prevent adding
        event listeners when the window isn't visible.
        (WebInspector.ElementsPanel.prototype.inspectedWindowCleared):
        (WebInspector.ElementsPanel.prototype._addMutationEventListeners): Add DOMNodeInserted,
        DOMNodeRemoved and DOMContentLoaded event listeners to the passed in window or window's document.
        (WebInspector.ElementsPanel.prototype._removeMutationEventListeners): Removes the event listeners
        added in _addMutationEventListeners.
        (WebInspector.ElementsPanel.prototype.updateMutationEventListeners): Call _addMutationEventListeners
        again to reinstate the listners if the document changed or window cleared them.
        (WebInspector.ElementsPanel.prototype.registerMutationEventListeners): Append the window to
        _mutationMonitoredWindows and call _addMutationEventListeners.
        (WebInspector.ElementsPanel.prototype.unregisterMutationEventListeners): Remove the window from
        _mutationMonitoredWindows and call _removeMutationEventListeners.
        (WebInspector.ElementsPanel.prototype.unregisterAllMutationEventListeners): Call
        _removeMutationEventListeners for all windows in _mutationMonitoredWindows and
        clear _mutationMonitoredWindows.
        (WebInspector.ElementsPanel.prototype._contentLoaded): Append the node and parent
        to the recentlyModifiedNodes array. Call _updateModifiedNodesSoon if visible.
        (WebInspector.ElementsPanel.prototype._nodeInserted): Ditto.
        (WebInspector.ElementsPanel.prototype._nodeRemoved): Ditto.
        (WebInspector.ElementsPanel.prototype._updateModifiedNodesSoon): Call
        _updateModifiedNodes on a zero timeout.
        (WebInspector.ElementsPanel.prototype._updateModifiedNodes): Iterate over
        the recentlyModifiedNodes array and call updateChildren on all the parent
        elements that had changes. Only calls updateChildren once per parent element.
        (WebInspector.ElementsPanel.prototype._isAncestorIncludingParentFrames): Return
        false if the nodes are the same. Return true if the nodes are the same while
        looking at ancestor frame elements. THis use to return false, which was incorrect.
        (WebInspector.DOMNodeTreeElement.prototype.onpopulate): Call updateChildren.
        (WebInspector.DOMNodeTreeElement.prototype.updateChildren): Copied from
        onpopulate and changed to rebuild the children elements by adding new children,
        moving existing children and removed old children.
        (WebInspector.DOMNodeTreeElement.prototype.onexpand): If the node has a contentDocument
        call registerMutationEventListeners to track any mutations.
        * page/inspector/inspector.js:
        (WebInspector.inspectedWindowCleared): Call ElementsPanel.inspectedWindowCleared.
        * page/inspector/treeoutline.js:
        (TreeElement.prototype.get hasChildren): Return _hasChildren.
        (TreeElement.prototype.set hasChildren): Set _hasChildren and update the className.
        (TreeElement.prototype.hasAncestor): Return true if the element has the passed in ancestor.
        (TreeElement.prototype.expand): Fix an exception that can happen if expand is
        called before _attach.
        * WebCore/manual-tests/inspector/dom-mutation.html: Added.
        * WebCore/manual-tests/inspector/resources/mutate-frame-2.html: Added.
        * WebCore/manual-tests/inspector/resources/mutate-frame.html: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@35317 268f45cc-cd09-0410-ab3c-d52691b4dbfc

WebCore/ChangeLog
WebCore/loader/FrameLoader.cpp
WebCore/manual-tests/inspector/dom-mutation.html [new file with mode: 0644]
WebCore/manual-tests/inspector/resources/mutate-frame-2.html [new file with mode: 0644]
WebCore/manual-tests/inspector/resources/mutate-frame.html [new file with mode: 0644]
WebCore/page/InspectorController.cpp
WebCore/page/InspectorController.h
WebCore/page/inspector/ElementsPanel.js
WebCore/page/inspector/inspector.js
WebCore/page/inspector/treeoutline.js

index e54a25d..4bd2571 100644 (file)
@@ -1,3 +1,71 @@
+2008-07-23  Timothy Hatcher  <timothy@apple.com>
+
+        Updates the elements DOM tree when nodes are added or removed from
+        the inspected document.
+
+        https://bugs.webkit.org/show_bug.cgi?id=6590
+        <rdar://problem/5712921>
+
+        Reviewed by Adam Roben.
+
+        * loader/FrameLoader.cpp:
+        (WebCore::FrameLoader::dispatchWindowObjectAvailable): Added a call to
+        InspectorController::inspectedWindowScriptObjectCleared.
+        * page/InspectorController.cpp:
+        (WebCore::InspectorController::inspectedWindowScriptObjectCleared):
+        Calls the WebInspector.inspectedWindowCleared script function.
+        * page/InspectorController.h:
+        * page/inspector/ElementsPanel.js:
+        (WebInspector.ElementsPanel): Create the event listener callback wrappers.
+        (WebInspector.ElementsPanel.prototype.show): Call _updateModifiedNodes if
+        there are any recently modified nodes.
+        (WebInspector.ElementsPanel.prototype.reset): Remove previous mutation event listeners.
+        Adds a check for InspectorController.isWindowVisible to prevent adding
+        event listeners when the window isn't visible.
+        (WebInspector.ElementsPanel.prototype.inspectedWindowCleared): 
+        (WebInspector.ElementsPanel.prototype._addMutationEventListeners): Add DOMNodeInserted,
+        DOMNodeRemoved and DOMContentLoaded event listeners to the passed in window or window's document.
+        (WebInspector.ElementsPanel.prototype._removeMutationEventListeners): Removes the event listeners
+        added in _addMutationEventListeners.
+        (WebInspector.ElementsPanel.prototype.updateMutationEventListeners): Call _addMutationEventListeners
+        again to reinstate the listners if the document changed or window cleared them.
+        (WebInspector.ElementsPanel.prototype.registerMutationEventListeners): Append the window to
+        _mutationMonitoredWindows and call _addMutationEventListeners.
+        (WebInspector.ElementsPanel.prototype.unregisterMutationEventListeners): Remove the window from
+        _mutationMonitoredWindows and call _removeMutationEventListeners.
+        (WebInspector.ElementsPanel.prototype.unregisterAllMutationEventListeners): Call
+        _removeMutationEventListeners for all windows in _mutationMonitoredWindows and
+        clear _mutationMonitoredWindows.
+        (WebInspector.ElementsPanel.prototype._contentLoaded): Append the node and parent
+        to the recentlyModifiedNodes array. Call _updateModifiedNodesSoon if visible.
+        (WebInspector.ElementsPanel.prototype._nodeInserted): Ditto.
+        (WebInspector.ElementsPanel.prototype._nodeRemoved): Ditto.
+        (WebInspector.ElementsPanel.prototype._updateModifiedNodesSoon): Call
+        _updateModifiedNodes on a zero timeout.
+        (WebInspector.ElementsPanel.prototype._updateModifiedNodes): Iterate over
+        the recentlyModifiedNodes array and call updateChildren on all the parent
+        elements that had changes. Only calls updateChildren once per parent element.
+        (WebInspector.ElementsPanel.prototype._isAncestorIncludingParentFrames): Return
+        false if the nodes are the same. Return true if the nodes are the same while
+        looking at ancestor frame elements. THis use to return false, which was incorrect.
+        (WebInspector.DOMNodeTreeElement.prototype.onpopulate): Call updateChildren.
+        (WebInspector.DOMNodeTreeElement.prototype.updateChildren): Copied from
+        onpopulate and changed to rebuild the children elements by adding new children,
+        moving existing children and removed old children.
+        (WebInspector.DOMNodeTreeElement.prototype.onexpand): If the node has a contentDocument
+        call registerMutationEventListeners to track any mutations.
+        * page/inspector/inspector.js:
+        (WebInspector.inspectedWindowCleared): Call ElementsPanel.inspectedWindowCleared.
+        * page/inspector/treeoutline.js:
+        (TreeElement.prototype.get hasChildren): Return _hasChildren.
+        (TreeElement.prototype.set hasChildren): Set _hasChildren and update the className.
+        (TreeElement.prototype.hasAncestor): Return true if the element has the passed in ancestor.
+        (TreeElement.prototype.expand): Fix an exception that can happen if expand is
+        called before _attach.
+        * WebCore/manual-tests/inspector/dom-mutation.html: Added.
+        * WebCore/manual-tests/inspector/resources/mutate-frame-2.html: Added.
+        * WebCore/manual-tests/inspector/resources/mutate-frame.html: Added.
+
 2008-07-22  Timothy Hatcher  <timothy@apple.com>
 
         Fix an exception that occurred when double clicking the closing tag
index 5743155..5ab5845 100644 (file)
@@ -4784,9 +4784,13 @@ void FrameLoader::dispatchWindowObjectAvailable()
         return;
 
     m_client->windowObjectCleared();
-    if (Page* page = m_frame->page())
+
+    if (Page* page = m_frame->page()) {
+        if (InspectorController* inspector = page->inspectorController())
+            inspector->inspectedWindowScriptObjectCleared(m_frame);
         if (InspectorController* inspector = page->parentInspectorController())
             inspector->windowScriptObjectAvailable();
+    }
 }
 
 Widget* FrameLoader::createJavaAppletWidget(const IntSize& size, Element* element, const HashMap<String, String>& args)
diff --git a/WebCore/manual-tests/inspector/dom-mutation.html b/WebCore/manual-tests/inspector/dom-mutation.html
new file mode 100644 (file)
index 0000000..33e8333
--- /dev/null
@@ -0,0 +1,31 @@
+<script>
+function test1() {
+    document.getElementById("test").src = "resources/mutate-frame.html";
+}
+
+function test2() {
+    document.getElementById("test").src = "resources/mutate-frame-2.html";
+}
+
+var count = 1;
+function run() {
+    var container = document.getElementById("test2");
+    var div = document.createElement("div");
+    div.textContent = "Testing " + (count++);
+    container.appendChild(div);
+    if (count > 10)
+        container.removeChild(container.firstChild);
+}
+
+setInterval(run, 1000);
+</script>
+<p>Test for <a href="https://bugs.webkit.org/show_bug.cgi?id=6590">Bug 6590: Web Inspector shows stale DOM tree if the DOM changes after the inspector has loaded</a>.</p>
+<p>To test, open the Inspector and watch the DOM change to match the page. Clicking the buttons will navigate the subframe, and the all the subframe child nodes should change.</p>
+<div style="clear: both">
+<button onclick="test1()">Test Frame 1</button>
+<button onclick="test2()">Test Frame 2</button>
+</div>
+<div style="float: left">
+<iframe id="test" src="resources/mutate-frame.html" width="200" height="300"></iframe>
+</div>
+<div style="float: left; margin-left: 10px;" id="test2"></div>
diff --git a/WebCore/manual-tests/inspector/resources/mutate-frame-2.html b/WebCore/manual-tests/inspector/resources/mutate-frame-2.html
new file mode 100644 (file)
index 0000000..9d413b9
--- /dev/null
@@ -0,0 +1,12 @@
+<script>
+var count = 1;
+function run() {
+    var div = document.createElement("div");
+    div.textContent = "Testing " + (count++);
+    document.body.appendChild(div);
+    if (count > 10)
+        document.body.removeChild(document.body.firstChild);
+}
+
+setInterval(run, 1000);
+</script>
diff --git a/WebCore/manual-tests/inspector/resources/mutate-frame.html b/WebCore/manual-tests/inspector/resources/mutate-frame.html
new file mode 100644 (file)
index 0000000..08fc656
--- /dev/null
@@ -0,0 +1,10 @@
+<script>
+var count = 1;
+function run() {
+    var div = document.createElement("div");
+    div.textContent = "Testing " + (count++);
+    document.body.appendChild(div);
+}
+
+setInterval(run, 1000);
+</script>
index 47b908a..a88d43c 100644 (file)
@@ -1243,6 +1243,25 @@ void InspectorController::detachWindow()
     m_client->detachWindow();
 }
 
+void InspectorController::inspectedWindowScriptObjectCleared(Frame* frame)
+{
+    if (!enabled() || !m_scriptContext || !m_scriptObject)
+        return;
+
+    JSDOMWindow* win = toJSDOMWindow(frame);
+    ExecState* exec = win->globalExec();
+
+    JSValueRef arg0;
+
+    {
+        KJS::JSLock lock(false);
+        arg0 = toRef(JSInspectedObjectWrapper::wrap(exec, win));
+    }
+
+    JSValueRef exception = 0;
+    callFunction(m_scriptContext, m_scriptObject, "inspectedWindowCleared", 1, &arg0, exception);
+}
+
 void InspectorController::windowScriptObjectAvailable()
 {
     if (!m_page || !enabled())
index 3eac0be..428d9b5 100644 (file)
@@ -120,6 +120,7 @@ public:
     JSContextRef scriptContext() const { return m_scriptContext; };
     void setScriptContext(JSContextRef context) { m_scriptContext = context; };
 
+    void inspectedWindowScriptObjectCleared(Frame*);
     void windowScriptObjectAvailable();
 
     void scriptObjectReady();
index f97322c..afce0ee 100644 (file)
@@ -76,6 +76,11 @@ WebInspector.ElementsPanel = function()
     this.element.appendChild(this.sidebarElement);
     this.element.appendChild(this.sidebarResizeElement);
 
+    this._mutationMonitoredWindows = [];
+    this._nodeInsertedEventListener = InspectorController.wrapCallback(this._nodeInserted.bind(this));
+    this._nodeRemovedEventListener = InspectorController.wrapCallback(this._nodeRemoved.bind(this));
+    this._contentLoadedEventListener = InspectorController.wrapCallback(this._contentLoaded.bind(this));
+
     this.reset();
 }
 
@@ -103,6 +108,8 @@ WebInspector.ElementsPanel.prototype = {
         this.sidebarResizeElement.style.right = (this.sidebarElement.offsetWidth - 3) + "px";
         this.updateBreadcrumb();
         this.updateTreeSelection();
+        if (this.recentlyModifiedNodes.length)
+            this._updateModifiedNodes();
     },
 
     hide: function()
@@ -128,6 +135,9 @@ WebInspector.ElementsPanel.prototype = {
         this.hoveredDOMNode = null;
         this.forceHoverHighlight = false;
 
+        this.recentlyModifiedNodes = [];
+        this.unregisterAllMutationEventListeners();
+
         var inspectedWindow = InspectorController.inspectedWindow();
         if (!inspectedWindow || !inspectedWindow.document)
             return;
@@ -141,11 +151,17 @@ WebInspector.ElementsPanel.prototype = {
             }
 
             var contentLoadedCallback = InspectorController.wrapCallback(contentLoaded.bind(this));
-
             inspectedWindow.document.addEventListener("DOMContentLoaded", contentLoadedCallback, false);
             return;
         }
 
+        // If the window isn't visible, return early so the DOM tree isn't built
+        // and mutation event listeners are not added.
+        if (!InspectorController.isWindowVisible())
+            return;
+
+        this.registerMutationEventListeners(inspectedWindow);
+
         var inspectedRootDocument = inspectedWindow.document;
         this.rootDOMNode = inspectedRootDocument;
 
@@ -157,6 +173,59 @@ WebInspector.ElementsPanel.prototype = {
         }
     },
 
+    inspectedWindowCleared: function(window)
+    {
+        if (InspectorController.isWindowVisible())
+            this.updateMutationEventListeners(window);
+    },
+
+    _addMutationEventListeners: function(monitoredWindow)
+    {
+        monitoredWindow.document.addEventListener("DOMNodeInserted", this._nodeInsertedEventListener, true);
+        monitoredWindow.document.addEventListener("DOMNodeRemoved", this._nodeRemovedEventListener, true);
+        if (monitoredWindow.frameElement)
+            monitoredWindow.addEventListener("DOMContentLoaded", this._contentLoadedEventListener, true);
+    },
+
+    _removeMutationEventListeners: function(monitoredWindow)
+    {
+        if (monitoredWindow.frameElement)
+            monitoredWindow.removeEventListener("DOMContentLoaded", this._contentLoadedEventListener, true);
+        if (!monitoredWindow.document)
+            return;
+        monitoredWindow.document.removeEventListener("DOMNodeInserted", this._nodeInsertedEventListener, true);
+        monitoredWindow.document.removeEventListener("DOMNodeRemoved", this._nodeRemovedEventListener, true);
+    },
+
+    updateMutationEventListeners: function(monitoredWindow)
+    {
+        this._addMutationEventListeners(monitoredWindow);
+    },
+
+    registerMutationEventListeners: function(monitoredWindow)
+    {
+        if (!monitoredWindow || this._mutationMonitoredWindows.indexOf(monitoredWindow) !== -1)
+            return;
+        this._mutationMonitoredWindows.push(monitoredWindow);
+        if (InspectorController.isWindowVisible())
+            this._addMutationEventListeners(monitoredWindow);
+    },
+
+    unregisterMutationEventListeners: function(monitoredWindow)
+    {
+        if (!monitoredWindow || this._mutationMonitoredWindows.indexOf(monitoredWindow) === -1)
+            return;
+        this._mutationMonitoredWindows.remove(monitoredWindow);
+        this._removeMutationEventListeners(monitoredWindow);
+    },
+
+    unregisterAllMutationEventListeners: function()
+    {
+        for (var i = 0; i < this._mutationMonitoredWindows.length; ++i)
+            this._removeMutationEventListeners(this._mutationMonitoredWindows[i]);
+        this._mutationMonitoredWindows = [];
+    },
+
     updateTreeSelection: function()
     {
         if (!this.treeOutline || !this.treeOutline.selectedTreeElement)
@@ -258,6 +327,70 @@ WebInspector.ElementsPanel.prototype = {
             this._updateHoverHighlight();
     },
 
+    _contentLoaded: function(event)
+    {
+        this.recentlyModifiedNodes.push({node: event.target, parent: event.target.defaultView.frameElement, replaced: true});
+        if (this.visible)
+            this._updateModifiedNodesSoon();
+    },
+
+    _nodeInserted: function(event)
+    {
+        this.recentlyModifiedNodes.push({node: event.target, parent: event.relatedNode, inserted: true});
+        if (this.visible)
+            this._updateModifiedNodesSoon();
+    },
+
+    _nodeRemoved: function(event)
+    {
+        this.recentlyModifiedNodes.push({node: event.target, parent: event.relatedNode, removed: true});
+        if (this.visible)
+            this._updateModifiedNodesSoon();
+    },
+
+    _updateModifiedNodesSoon: function()
+    {
+        if ("_updateModifiedNodesTimeout" in this)
+            return;
+        this._updateModifiedNodesTimeout = setTimeout(this._updateModifiedNodes.bind(this), 0);
+    },
+
+    _updateModifiedNodes: function()
+    {
+        if ("_updateModifiedNodesTimeout" in this) {
+            clearTimeout(this._updateModifiedNodesTimeout);
+            delete this._updateModifiedNodesTimeout;
+        }
+
+        var updatedParentTreeElements = [];
+        var updateBreadcrumbs = false;
+
+        for (var i = 0; i < this.recentlyModifiedNodes.length; ++i) {
+            var replaced = this.recentlyModifiedNodes[i].replaced;
+            var parent = this.recentlyModifiedNodes[i].parent;
+            if (!parent)
+                continue;
+
+            var parentNodeItem = this.treeOutline.findTreeElement(parent, null, null, objectsAreSame);
+            if (parentNodeItem && !parentNodeItem.alreadyUpdatedChildren) {
+                parentNodeItem.updateChildren(replaced);
+                parentNodeItem.alreadyUpdatedChildren = true;
+                updatedParentTreeElements.push(parentNodeItem);
+            }
+
+            if (!updateBreadcrumbs && (objectsAreSame(this.focusedDOMNode, parent) || this._isAncestorIncludingParentFrames(this.focusedDOMNode, parent)))
+                updateBreadcrumbs = true;
+        }
+
+        for (var i = 0; i < updatedParentTreeElements.length; ++i)
+            delete updatedParentTreeElements[i].alreadyUpdatedChildren;
+
+        this.recentlyModifiedNodes = [];
+
+        if (updateBreadcrumbs)
+            this.updateBreadcrumb(true);
+    },
+
     _updateHoverHighlightSoon: function()
     {
         if ("_updateHoverHighlightTimeout" in this)
@@ -888,8 +1021,10 @@ WebInspector.ElementsPanel.prototype = {
 
     _isAncestorIncludingParentFrames: function(a, b)
     {
+        if (objectsAreSame(a, b))
+            return false;
         for (var node = b; node; node = this._getDocumentForNode(node).defaultView.frameElement)
-            if (isAncestorNode.call(a, node))
+            if (objectsAreSame(a, node) || isAncestorNode.call(a, node))
                 return true;
         return false;
     },
@@ -1037,28 +1172,91 @@ WebInspector.DOMNodeTreeElement.prototype = {
         if (this.children.length || this.whitespaceIgnored !== Preferences.ignoreWhitespace)
             return;
 
-        this.removeChildren();
         this.whitespaceIgnored = Preferences.ignoreWhitespace;
 
+        this.updateChildren();
+    },
+
+    updateChildren: function(fullRefresh)
+    {
+        if (fullRefresh) {
+            var selectedTreeElement = this.treeOutline.selectedTreeElement;
+            if (selectedTreeElement && selectedTreeElement.hasAncestor(this))
+                this.select();
+            this.removeChildren();
+        }
+
         var treeElement = this;
-        function appendChildrenOfNode(node)
+        var treeChildIndex = 0;
+
+        function updateChildrenOfNode(node)
         {
+            var treeOutline = treeElement.treeOutline;
             var child = (Preferences.ignoreWhitespace ? firstChildSkippingWhitespace.call(node) : node.firstChild);
             while (child) {
-                treeElement.appendChild(new WebInspector.DOMNodeTreeElement(child));
+                var currentTreeElement = treeElement.children[treeChildIndex];
+                if (!currentTreeElement || !objectsAreSame(currentTreeElement.representedObject, child)) {
+                    // Find any existing element that is later in the children list.
+                    var existingTreeElement = null;
+                    for (var i = (treeChildIndex + 1); i < treeElement.children.length; ++i) {
+                        if (objectsAreSame(treeElement.children[i].representedObject, child)) {
+                            existingTreeElement = treeElement.children[i];
+                            break;
+                        }
+                    }
+
+                    if (existingTreeElement && existingTreeElement.parent === treeElement) {
+                        // If an existing element was found and it has the same parent, just move it.
+                        var wasSelected = existingTreeElement.selected;
+                        treeElement.removeChild(existingTreeElement);
+                        treeElement.insertChild(existingTreeElement, treeChildIndex);
+                        if (wasSelected)
+                            existingTreeElement.select();
+                    } else {
+                        // No existing element found, insert a new element.
+                        treeElement.insertChild(new WebInspector.DOMNodeTreeElement(child), treeChildIndex);
+                    }
+                }
+
                 child = Preferences.ignoreWhitespace ? nextSiblingSkippingWhitespace.call(child) : child.nextSibling;
+                ++treeChildIndex;
             }
         }
 
-        if (this.representedObject.contentDocument)
-            appendChildrenOfNode(this.representedObject.contentDocument);
+        // Remove any tree elements that no longer have this node (or this node's contentDocument) as their parent.
+        for (var i = (this.children.length - 1); i >= 0; --i) {
+            if ("elementCloseTag" in this.children[i])
+                continue;
 
-        appendChildrenOfNode(this.representedObject);
+            var currentChild = this.children[i];
+            var currentNode = currentChild.representedObject;
+            var currentParentNode = currentNode.parentNode;
 
-        if (this.representedObject.nodeType == Node.ELEMENT_NODE) {
+            if (objectsAreSame(currentParentNode, this.representedObject))
+                continue;
+            if (this.representedObject.contentDocument && objectsAreSame(currentParentNode, this.representedObject.contentDocument))
+                continue;
+
+            var selectedTreeElement = this.treeOutline.selectedTreeElement;
+            if (selectedTreeElement && (selectedTreeElement === currentChild || selectedTreeElement.hasAncestor(currentChild)))
+                this.select();
+
+            this.removeChildAtIndex(i);
+
+            if (currentNode.contentDocument)
+                this.treeOutline.panel.unregisterMutationEventListeners(currentNode.contentDocument.defaultView);
+        }
+
+        if (this.representedObject.contentDocument)
+            updateChildrenOfNode(this.representedObject.contentDocument);
+        updateChildrenOfNode(this.representedObject);
+
+        var lastChild = this.children[this.children.length - 1];
+        if (this.representedObject.nodeType == Node.ELEMENT_NODE && (!lastChild || !lastChild.elementCloseTag)) {
             var title = "<span class=\"webkit-html-tag close\">&lt;/" + this.representedObject.nodeName.toLowerCase().escapeHTML() + "&gt;</span>";
-            var item = new TreeElement(title, this.representedObject, false);
+            var item = new TreeElement(title, null, false);
             item.selectable = false;
+            item.elementCloseTag = true;
             this.appendChild(item);
         }
     },
@@ -1066,6 +1264,9 @@ WebInspector.DOMNodeTreeElement.prototype = {
     onexpand: function()
     {
         this.treeOutline.panel.updateTreeSelection();
+
+        if (this.representedObject.contentDocument)
+            this.treeOutline.panel.registerMutationEventListeners(this.representedObject.contentDocument.defaultView);
     },
 
     oncollapse: function()
index aca81de..1dd06d5 100644 (file)
@@ -786,6 +786,11 @@ WebInspector.reset = function()
     this.console.clearMessages();
 }
 
+WebInspector.inspectedWindowCleared = function(inspectedWindow)
+{
+    this.panels.elements.inspectedWindowCleared(inspectedWindow);
+}
+
 WebInspector.resourceURLChanged = function(resource, oldURL)
 {
     delete this.resourceURLMap[oldURL];
index 5c10cf1..053d51e 100644 (file)
@@ -466,6 +466,27 @@ TreeElement.prototype = {
             this._listItemNode.title = x ? x : "";
     },
 
+    get hasChildren() {
+        return this._hasChildren;
+    },
+
+    set hasChildren(x) {
+        if (this._hasChildren === x)
+            return;
+
+        this._hasChildren = x;
+
+        if (!this._listItemNode)
+            return;
+
+        if (x)
+            this._listItemNode.addStyleClass("parent");
+        else {
+            this._listItemNode.removeStyleClass("parent");
+            this.collapse();
+        }
+    },
+
     get hidden() {
         return this._hidden;
     },
@@ -628,7 +649,7 @@ TreeElement.prototype.expand = function()
     if (!this.hasChildren || (this.expanded && !this._shouldRefreshChildren && this._childrenListNode))
         return;
 
-    if (!this._childrenListNode || this._shouldRefreshChildren) {
+    if (this.treeOutline && (!this._childrenListNode || this._shouldRefreshChildren)) {
         if (this._childrenListNode && this._childrenListNode.parentNode)
             this._childrenListNode.parentNode.removeChild(this._childrenListNode);
 
@@ -650,7 +671,7 @@ TreeElement.prototype.expand = function()
 
     if (this._listItemNode) {
         this._listItemNode.addStyleClass("expanded");
-        if (this._childrenListNode.parentNode != this._listItemNode.parentNode)
+        if (this._childrenListNode && this._childrenListNode.parentNode != this._listItemNode.parentNode)
             this.parent._childrenListNode.insertBefore(this._childrenListNode, this._listItemNode.nextSibling);
     }
 
@@ -685,6 +706,20 @@ TreeElement.prototype.expandRecursively = function(maxDepth)
     }
 }
 
+TreeElement.prototype.hasAncestor = function(ancestor) {
+    if (!ancestor)
+        return false;
+
+    var currentNode = this.parent;
+    while (currentNode) {
+        if (ancestor === currentNode)
+            return true;
+        currentNode = currentNode.parent;
+    }
+
+    return false;
+}
+
 TreeElement.prototype.reveal = function()
 {
     var currentAncestor = this.parent;