2009-08-01 Pavel Feldman <pfeldman@chromium.org>
authorabarth@webkit.org <abarth@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sun, 2 Aug 2009 05:18:35 +0000 (05:18 +0000)
committerabarth@webkit.org <abarth@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sun, 2 Aug 2009 05:18:35 +0000 (05:18 +0000)
        Reviewed by Timothy Hatcher.

        Web Inspector: Reimplement Elements Panel so that its
        interaction with DOM is serialized.

        This is a first cut wuth read support for DOM elements
        tree as well as some limited editing. No properties, no
        styles, no search capabilities are implemented yet.
        Set Preferences.useDOMAgent to true in order to try it
        out, otherwise disabled by default.

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

        * GNUmakefile.am:
        * WebCore.gypi:
        * WebCore.pro:
        * WebCore.vcproj/WebCore.vcproj:
        * WebCore.xcodeproj/project.pbxproj:
        * WebCoreSources.bkl:
        * inspector/InspectorBackend.cpp:
        (WebCore::InspectorBackend::getChildNodes):
        (WebCore::InspectorBackend::setAttribute):
        (WebCore::InspectorBackend::removeAttribute):
        (WebCore::InspectorBackend::setTextNodeValue):
        * inspector/InspectorBackend.h:
        * inspector/InspectorBackend.idl:
        * inspector/InspectorController.cpp:
        (WebCore::InspectorController::windowScriptObjectAvailable):
        (WebCore::InspectorController::setFrontendProxyObject):
        (WebCore::InspectorController::close):
        (WebCore::InspectorController::populateScriptObjects):
        (WebCore::InspectorController::didCommitLoad):
        * inspector/InspectorController.h:
        (WebCore::InspectorController::domAgent):
        * inspector/InspectorDOMAgent.cpp: Added.
        * inspector/InspectorDOMAgent.h: Added.
        * inspector/InspectorFrontend.cpp:
        * inspector/InspectorFrontend.h:
        * inspector/front-end/Callback.js: Added.
        * inspector/front-end/DOMAgent.js: Added.
        * inspector/front-end/ElementsPanel.js:
        * inspector/front-end/WebKit.qrc:
        * inspector/front-end/inspector.html:
        * inspector/front-end/inspector.js:
        (WebInspector.loaded):

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

22 files changed:
WebCore/ChangeLog
WebCore/GNUmakefile.am
WebCore/WebCore.gypi
WebCore/WebCore.pro
WebCore/WebCore.vcproj/WebCore.vcproj
WebCore/WebCore.xcodeproj/project.pbxproj
WebCore/WebCoreSources.bkl
WebCore/inspector/InspectorBackend.cpp
WebCore/inspector/InspectorBackend.h
WebCore/inspector/InspectorBackend.idl
WebCore/inspector/InspectorController.cpp
WebCore/inspector/InspectorController.h
WebCore/inspector/InspectorDOMAgent.cpp [new file with mode: 0644]
WebCore/inspector/InspectorDOMAgent.h [new file with mode: 0644]
WebCore/inspector/InspectorFrontend.cpp
WebCore/inspector/InspectorFrontend.h
WebCore/inspector/front-end/Callback.js [new file with mode: 0644]
WebCore/inspector/front-end/DOMAgent.js [new file with mode: 0644]
WebCore/inspector/front-end/ElementsPanel.js
WebCore/inspector/front-end/WebKit.qrc
WebCore/inspector/front-end/inspector.html
WebCore/inspector/front-end/inspector.js

index 63997f5..e02f5b2 100644 (file)
@@ -1,3 +1,51 @@
+2009-08-01  Pavel Feldman  <pfeldman@chromium.org>
+
+        Reviewed by Timothy Hatcher.
+
+        Web Inspector: Reimplement Elements Panel so that its
+        interaction with DOM is serialized.
+
+        This is a first cut wuth read support for DOM elements
+        tree as well as some limited editing. No properties, no
+        styles, no search capabilities are implemented yet.
+        Set Preferences.useDOMAgent to true in order to try it
+        out, otherwise disabled by default.
+
+        https://bugs.webkit.org/show_bug.cgi?id=27771
+
+        * GNUmakefile.am:
+        * WebCore.gypi:
+        * WebCore.pro:
+        * WebCore.vcproj/WebCore.vcproj:
+        * WebCore.xcodeproj/project.pbxproj:
+        * WebCoreSources.bkl:
+        * inspector/InspectorBackend.cpp:
+        (WebCore::InspectorBackend::getChildNodes):
+        (WebCore::InspectorBackend::setAttribute):
+        (WebCore::InspectorBackend::removeAttribute):
+        (WebCore::InspectorBackend::setTextNodeValue):
+        * inspector/InspectorBackend.h:
+        * inspector/InspectorBackend.idl:
+        * inspector/InspectorController.cpp:
+        (WebCore::InspectorController::windowScriptObjectAvailable):
+        (WebCore::InspectorController::setFrontendProxyObject):
+        (WebCore::InspectorController::close):
+        (WebCore::InspectorController::populateScriptObjects):
+        (WebCore::InspectorController::didCommitLoad):
+        * inspector/InspectorController.h:
+        (WebCore::InspectorController::domAgent):
+        * inspector/InspectorDOMAgent.cpp: Added.
+        * inspector/InspectorDOMAgent.h: Added.
+        * inspector/InspectorFrontend.cpp:
+        * inspector/InspectorFrontend.h:
+        * inspector/front-end/Callback.js: Added.
+        * inspector/front-end/DOMAgent.js: Added.
+        * inspector/front-end/ElementsPanel.js:
+        * inspector/front-end/WebKit.qrc:
+        * inspector/front-end/inspector.html:
+        * inspector/front-end/inspector.js:
+        (WebInspector.loaded):
+
 2009-08-01  Ryosuke Niwa  <rniwa@webkit.org>
 
         Reviewed by Adele Peterson.
index f3c4fa3..0b18dd5 100644 (file)
@@ -1125,6 +1125,8 @@ webcore_sources += \
        WebCore/inspector/InspectorBackend.h \
        WebCore/inspector/InspectorDatabaseResource.cpp \
        WebCore/inspector/InspectorDatabaseResource.h \
+       WebCore/inspector/InspectorDOMAgent.cpp \
+       WebCore/inspector/InspectorDOMAgent.h \
        WebCore/inspector/InspectorDOMStorageResource.cpp \
        WebCore/inspector/InspectorDOMStorageResource.h \
        WebCore/inspector/InspectorClient.h \
index 0dade31..73e246f 100644 (file)
             'inspector/InspectorController.h',
             'inspector/InspectorDatabaseResource.cpp',
             'inspector/InspectorDatabaseResource.h',
+            'inspector/InspectorDOMAgent.cpp',
+            'inspector/InspectorDOMAgent.h',
             'inspector/InspectorDOMStorageResource.cpp',
             'inspector/InspectorDOMStorageResource.h',
             'inspector/InspectorFrontend.cpp',
index 443eaad..e253896 100644 (file)
@@ -859,6 +859,7 @@ SOURCES += \
     inspector/ConsoleMessage.cpp \
     inspector/InspectorBackend.cpp \
     inspector/InspectorDatabaseResource.cpp \
+    inspector/InspectorDOMAgent.cpp \
     inspector/InspectorDOMStorageResource.cpp \
     inspector/InspectorController.cpp \
     inspector/InspectorFrontend.cpp \
index 129e68f..fe0083c 100644 (file)
                                >\r
                        </File>\r
                        <File\r
+                               RelativePath="..\inspector\InspectorDOMAgent.cpp"\r
+                               >\r
+                       </File>\r
+                       <File\r
+                               RelativePath="..\inspector\InspectorDOMAgent.h"\r
+                               >\r
+                       </File>\r
+                       <File\r
                                RelativePath="..\inspector\InspectorDOMStorageResource.cpp"\r
                                >\r
                        </File>\r
                                        >\r
                                </File>\r
                                <File\r
+                                       RelativePath="..\inspector\front-end\Callback.js"\r
+                                       >\r
+                               </File>\r
+                               <File\r
                                        RelativePath="..\inspector\front-end\CallStackSidebarPane.js"\r
                                        >\r
                                </File>\r
                                        >\r
                                </File>\r
                                <File\r
+                                       RelativePath="..\inspector\front-end\DOMAgent.js"\r
+                                       >\r
+                               </File>\r
+                               <File\r
                                        RelativePath="..\inspector\front-end\DOMStorage.js"\r
                                        >\r
                                </File>\r
index ab096fd..e63a21a 100644 (file)
                75793ED50D0CE85B007FC0AC /* DOMMessageEventInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 75793ED20D0CE85B007FC0AC /* DOMMessageEventInternal.h */; };
                7A1E88F5101CC384000C4DF5 /* ScriptArray.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7A1E88F3101CC384000C4DF5 /* ScriptArray.cpp */; };
                7A1E88F6101CC384000C4DF5 /* ScriptArray.h in Headers */ = {isa = PBXBuildFile; fileRef = 7A1E88F4101CC384000C4DF5 /* ScriptArray.h */; };
+               7A24587B1021EAF4000A00AA /* InspectorDOMAgent.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7A2458791021EAF4000A00AA /* InspectorDOMAgent.cpp */; };
+               7A24587C1021EAF4000A00AA /* InspectorDOMAgent.h in Headers */ = {isa = PBXBuildFile; fileRef = 7A24587A1021EAF4000A00AA /* InspectorDOMAgent.h */; };
                7A674BDB0F9EBF4E006CF099 /* PageGroupLoadDeferrer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7A674BD90F9EBF4E006CF099 /* PageGroupLoadDeferrer.cpp */; };
                7A674BDC0F9EBF4E006CF099 /* PageGroupLoadDeferrer.h in Headers */ = {isa = PBXBuildFile; fileRef = 7A674BDA0F9EBF4E006CF099 /* PageGroupLoadDeferrer.h */; };
                7A74ECB71018399A00BF939E /* InspectorBackend.idl in Resources */ = {isa = PBXBuildFile; fileRef = 7A74ECB61018399A00BF939E /* InspectorBackend.idl */; };
                75793ED20D0CE85B007FC0AC /* DOMMessageEventInternal.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = DOMMessageEventInternal.h; sourceTree = "<group>"; };
                7A1E88F3101CC384000C4DF5 /* ScriptArray.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ScriptArray.cpp; sourceTree = "<group>"; };
                7A1E88F4101CC384000C4DF5 /* ScriptArray.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ScriptArray.h; sourceTree = "<group>"; };
+               7A2458791021EAF4000A00AA /* InspectorDOMAgent.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = InspectorDOMAgent.cpp; sourceTree = "<group>"; };
+               7A24587A1021EAF4000A00AA /* InspectorDOMAgent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InspectorDOMAgent.h; sourceTree = "<group>"; };
                7A674BD90F9EBF4E006CF099 /* PageGroupLoadDeferrer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PageGroupLoadDeferrer.cpp; sourceTree = "<group>"; };
                7A674BDA0F9EBF4E006CF099 /* PageGroupLoadDeferrer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PageGroupLoadDeferrer.h; sourceTree = "<group>"; };
                7A74ECB61018399A00BF939E /* InspectorBackend.idl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = InspectorBackend.idl; sourceTree = "<group>"; };
                                1C81B9560E97330800266E07 /* InspectorController.h */,
                                41F062130F5F192600A07EAC /* InspectorDatabaseResource.cpp */,
                                41F062120F5F192600A07EAC /* InspectorDatabaseResource.h */,
+                               7A2458791021EAF4000A00AA /* InspectorDOMAgent.cpp */,
+                               7A24587A1021EAF4000A00AA /* InspectorDOMAgent.h */,
                                41F061730F5F00AC00A07EAC /* InspectorDOMStorageResource.cpp */,
                                41F061720F5F00AC00A07EAC /* InspectorDOMStorageResource.h */,
                                7AED3E030FBB1EAA00D2B03C /* InspectorFrontend.cpp */,
                                7A1E88F6101CC384000C4DF5 /* ScriptArray.h in Headers */,
                                24F54EAD101FE914000AE741 /* ApplicationCacheHost.h in Headers */,
                                41A3D58F101C152D00316D07 /* DedicatedWorkerThread.h in Headers */,
+                               7A24587C1021EAF4000A00AA /* InspectorDOMAgent.h in Headers */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
                                7A1E88F5101CC384000C4DF5 /* ScriptArray.cpp in Sources */,
                                24F54EAC101FE914000AE741 /* ApplicationCacheHost.cpp in Sources */,
                                41A3D58E101C152D00316D07 /* DedicatedWorkerThread.cpp in Sources */,
+                               7A24587B1021EAF4000A00AA /* InspectorDOMAgent.cpp in Sources */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
index bdbb8b9..0705c54 100644 (file)
@@ -663,6 +663,7 @@ This file contains the list of files needed to build WebCore.
         inspector/ConsoleMessage.cpp
         inspector/InspectorBackend.cpp
         inspector/InspectorDatabaseResource.cpp
+        inspector/InspectorDOMAgent.cpp
         inspector/InspectorDOMStorageResource.cpp
         inspector/InspectorController.cpp
         inspector/InspectorFrontend.cpp
index d50efd3..1eb10e8 100644 (file)
@@ -36,6 +36,7 @@
 #include "HTMLFrameOwnerElement.h"
 #include "InspectorClient.h"
 #include "InspectorController.h"
+#include "InspectorDOMAgent.h"
 #include "InspectorResource.h"
 
 #if ENABLE(JAVASCRIPT_DEBUGGER)
@@ -181,10 +182,10 @@ bool InspectorBackend::searchingForNode()
     return false;
 }
 
-void InspectorBackend::loaded()
+void InspectorBackend::loaded(bool enableDOMAgent)
 {
     if (m_inspectorController)
-        m_inspectorController->scriptObjectReady();
+        m_inspectorController->scriptObjectReady(enableDOMAgent);
 }
 
 void InspectorBackend::enableResourceTracking(bool always)
@@ -354,6 +355,30 @@ void InspectorBackend::stepOutOfFunctionInDebugger()
 
 #endif
 
+void InspectorBackend::getChildNodes(long callId, long elementId)
+{
+    if (m_inspectorController)
+        m_inspectorController->domAgent()->getChildNodes(callId, elementId);
+}
+
+void InspectorBackend::setAttribute(long callId, long elementId, const String& name, const String& value)
+{
+    if (m_inspectorController)
+        m_inspectorController->domAgent()->setAttribute(callId, elementId, name, value);
+}
+
+void InspectorBackend::removeAttribute(long callId, long elementId, const String& name)
+{
+    if (m_inspectorController)
+        m_inspectorController->domAgent()->removeAttribute(callId, elementId, name);
+}
+
+void InspectorBackend::setTextNodeValue(long callId, long elementId, const String& value)
+{
+    if (m_inspectorController)
+        m_inspectorController->domAgent()->setTextNodeValue(callId, elementId, value);
+}
+
 void InspectorBackend::highlight(Node* node)
 {
     if (m_inspectorController)
index bb891c2..daa16b4 100644 (file)
@@ -39,6 +39,7 @@ namespace WebCore {
 
 class CachedResource;
 class InspectorClient;
+class InspectorDOMAgent;
 class JavaScriptCallFrame;
 class Node;
 
@@ -81,7 +82,7 @@ public:
 
     bool searchingForNode();
 
-    void loaded();
+    void loaded(bool enableDOMAgent);
 
     void enableResourceTracking(bool always);
     void disableResourceTracking(bool always);
@@ -122,6 +123,11 @@ public:
     void stepOutOfFunctionInDebugger();
 #endif
 
+    void getChildNodes(long callId, long elementId);
+    void setAttribute(long callId, long elementId, const String& name, const String& value);
+    void removeAttribute(long callId, long elementId, const String& name);
+    void setTextNodeValue(long callId, long elementId, const String& value);
+
     // Generic code called from custom implementations.
     void highlight(Node* node);
 
index 21e6d99..bae1f26 100644 (file)
@@ -36,7 +36,7 @@ module core {
     ] InspectorBackend {
         void hideDOMNodeHighlight();
         [Custom] void highlightDOMNode(in Node node);
-        void loaded();
+        void loaded(in boolean enableDOMAgent);
         void windowUnloading();
         void attach();
         void detach();
@@ -97,5 +97,9 @@ module core {
 
         [Custom] Array profiles();
 #endif
-     };
+        void getChildNodes(in long callId, in long elementId);
+        void setAttribute(in long callId, in long elementId, in DOMString name, in DOMString value);
+        void removeAttribute(in long callId, in long elementId, in DOMString name);
+        void setTextNodeValue(in long callId, in long elementId, in DOMString value);
+    };
  }
index dd9d156..c3351e5 100644 (file)
@@ -51,6 +51,7 @@
 #include "InspectorClient.h"
 #include "InspectorFrontend.h"
 #include "InspectorDatabaseResource.h"
+#include "InspectorDOMAgent.h"
 #include "InspectorDOMStorageResource.h"
 #include "InspectorResource.h"
 #include "JavaScriptProfile.h"
@@ -506,12 +507,11 @@ void InspectorController::windowScriptObjectAvailable()
 
     // Grant the inspector the ability to script the inspected page.
     m_page->mainFrame()->document()->securityOrigin()->grantUniversalAccess();
-
     m_scriptState = scriptStateFromPage(m_page);
     ScriptGlobalObject::set(m_scriptState, "InspectorController", m_inspectorBackend.get());
 }
 
-void InspectorController::scriptObjectReady()
+void InspectorController::scriptObjectReady(bool enableDOMAgent)
 {
     ASSERT(m_scriptState);
     if (!m_scriptState)
@@ -521,6 +521,8 @@ void InspectorController::scriptObjectReady()
     if (!ScriptGlobalObject::get(m_scriptState, "WebInspector", webInspectorObj))
         return;
     setFrontendProxyObject(m_scriptState, webInspectorObj);
+    if (enableDOMAgent)
+        m_domAgent = new InspectorDOMAgent(m_frontend.get());
 
 #if ENABLE(JAVASCRIPT_DEBUGGER)
     Setting debuggerEnabled = setting(debuggerEnabledSettingName);
@@ -593,6 +595,10 @@ void InspectorController::close()
     closeWindow();
 
     m_frontend.set(0);
+    if (m_domAgent) {
+        m_domAgent->setDocument(0);
+        m_domAgent = 0;
+    }
     m_scriptState = 0;
 }
 
@@ -643,6 +649,8 @@ void InspectorController::populateScriptObjects()
         (*it)->bind(m_frontend.get());
 #endif
 
+    if (m_domAgent)
+        m_domAgent->setDocument(m_inspectedPage->mainFrame()->document());
     m_frontend->populateInterface();
 }
 
@@ -731,6 +739,9 @@ void InspectorController::didCommitLoad(DocumentLoader* loader)
                 m_mainResource = 0;
             }
         }
+
+        if (m_domAgent)
+            m_domAgent->setDocument(m_inspectedPage->mainFrame()->document());
     }
 
     for (Frame* frame = loader->frame(); frame; frame = frame->tree()->traverseNext(loader->frame()))
index 771f741..d9e7176 100644 (file)
@@ -53,11 +53,13 @@ namespace WebCore {
 
 class CachedResource;
 class Database;
+class Document;
 class DocumentLoader;
 class GraphicsContext;
 class HitTestResult;
 class InspectorBackend;
 class InspectorClient;
+class InspectorDOMAgent;
 class InspectorFrontend;
 class JavaScriptCallFrame;
 class StorageArea;
@@ -269,12 +271,14 @@ private:
     friend class InspectorBackend;
  
     // Following are used from InspectorBackend and internally.
-    void scriptObjectReady();
+    void scriptObjectReady(bool enableDOMAgent);
     void moveWindowBy(float x, float y) const;
     void setAttachedWindow(bool);
     void setAttachedWindowHeight(unsigned height);
     void storeLastActivePanel(const String& panelName);
     void closeWindow();
+    InspectorDOMAgent* domAgent() { return m_domAgent.get(); }
+
 #if ENABLE(JAVASCRIPT_DEBUGGER)
     void startUserInitiatedProfilingSoon();
     void toggleRecordButton(bool);
@@ -301,6 +305,7 @@ private:
     Page* m_inspectedPage;
     InspectorClient* m_client;
     OwnPtr<InspectorFrontend> m_frontend;
+    RefPtr<InspectorDOMAgent> m_domAgent;
     Page* m_page;
     RefPtr<Node> m_nodeToFocus;
     RefPtr<InspectorResource> m_mainResource;
diff --git a/WebCore/inspector/InspectorDOMAgent.cpp b/WebCore/inspector/InspectorDOMAgent.cpp
new file mode 100644 (file)
index 0000000..f222239
--- /dev/null
@@ -0,0 +1,471 @@
+/*
+ * Copyright (C) 2009 Apple Inc. All rights reserved.
+ * Copyright (C) 2009 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1.  Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ * 2.  Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
+ *     its contributors may be used to endorse or promote products derived
+ *     from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "InspectorDOMAgent.h"
+
+#include "AtomicString.h"
+#include "DOMWindow.h"
+#include "Document.h"
+#include "Event.h"
+#include "EventListener.h"
+#include "EventNames.h"
+#include "EventTarget.h"
+#include "HTMLFrameOwnerElement.h"
+#include "InspectorFrontend.h"
+#include "markup.h"
+#include "MutationEvent.h"
+#include "Node.h"
+#include "NodeList.h"
+#include "PlatformString.h"
+#include "ScriptObject.h"
+#include "Text.h"
+
+#include <wtf/OwnPtr.h>
+#include <wtf/Vector.h>
+
+namespace WebCore {
+
+InspectorDOMAgent::InspectorDOMAgent(InspectorFrontend* frontend)
+    : m_frontend(frontend)
+    , m_lastNodeId(1)
+{
+}
+
+InspectorDOMAgent::~InspectorDOMAgent()
+{
+    setDocument(0);
+}
+
+void InspectorDOMAgent::setDocument(Document* doc)
+{
+    if (doc == mainFrameDocument())
+        return;
+
+    ListHashSet<RefPtr<Document> > copy = m_documents;
+    for (ListHashSet<RefPtr<Document> >::iterator it = copy.begin(); it != copy.end(); ++it)
+        stopListening((*it).get());
+
+    ASSERT(!m_documents.size());
+
+    if (doc) {
+        startListening(doc);
+        if (doc->documentElement()) {
+            pushDocumentElementToFrontend();
+        }
+    } else {
+        discardBindings();
+    }
+}
+
+void InspectorDOMAgent::startListening(Document* doc)
+{
+    if (m_documents.contains(doc))
+        return;
+
+    doc->addEventListener(eventNames().DOMContentLoadedEvent, this, false);
+    doc->addEventListener(eventNames().DOMNodeInsertedEvent, this, false);
+    doc->addEventListener(eventNames().DOMNodeRemovedEvent, this, false);
+    doc->addEventListener(eventNames().DOMNodeRemovedFromDocumentEvent, this, true);
+    doc->addEventListener(eventNames().DOMAttrModifiedEvent, this, false);
+    m_documents.add(doc);
+}
+
+void InspectorDOMAgent::stopListening(Document* doc)
+{
+    if (!m_documents.contains(doc))
+        return;
+
+    doc->removeEventListener(eventNames().DOMContentLoadedEvent, this, false);
+    doc->removeEventListener(eventNames().DOMNodeInsertedEvent, this, false);
+    doc->removeEventListener(eventNames().DOMNodeRemovedEvent, this, false);
+    doc->removeEventListener(eventNames().DOMNodeRemovedFromDocumentEvent, this, true);
+    doc->removeEventListener(eventNames().DOMAttrModifiedEvent, this, false);
+    m_documents.remove(doc);
+}
+
+void InspectorDOMAgent::handleEvent(Event* event, bool)
+{
+    AtomicString type = event->type();
+    Node* node = event->target()->toNode();
+
+    // Remove mapping entry if necessary.
+    if (type == eventNames().DOMNodeRemovedFromDocumentEvent) {
+        unbind(node);
+        return;
+    }
+
+    if (type == eventNames().DOMAttrModifiedEvent) {
+        long id = idForNode(node);
+        // If node is not mapped yet -> ignore the event.
+        if (!id)
+            return;
+
+        Element* element = static_cast<Element*>(node);
+        m_frontend->attributesUpdated(id, buildArrayForElementAttributes(element));
+    } else if (type == eventNames().DOMNodeInsertedEvent) {
+        if (isWhitespace(node))
+            return;
+
+        Node* parent = static_cast<MutationEvent*>(event)->relatedNode();
+        long parentId = idForNode(parent);
+        // Return if parent is not mapped yet.
+        if (!parentId)
+            return;
+
+        if (!m_childrenRequested.contains(parentId)) {
+            // No children are mapped yet -> only notify on changes of hasChildren.
+            m_frontend->hasChildrenUpdated(parentId, true);
+        } else {
+            // Children have been requested -> return value of a new child.
+            long prevId = idForNode(innerPreviousSibling(node));
+
+            ScriptObject value = buildObjectForNode(node, 0);
+            m_frontend->childNodeInserted(parentId, prevId, value);
+        }
+    } else if (type == eventNames().DOMNodeRemovedEvent) {
+        if (isWhitespace(node))
+            return;
+
+        Node* parent = static_cast<MutationEvent*>(event)->relatedNode();
+        long parentId = idForNode(parent);
+        // If parent is not mapped yet -> ignore the event.
+        if (!parentId)
+            return;
+
+        if (!m_childrenRequested.contains(parentId)) {
+            // No children are mapped yet -> only notify on changes of hasChildren.
+            if (innerChildNodeCount(parent) == 1)
+                m_frontend->hasChildrenUpdated(parentId, false);
+        } else {
+            m_frontend->childNodeRemoved(parentId, idForNode(node));
+        }
+    } else if (type == eventNames().DOMContentLoadedEvent) {
+        // Re-push document once it is loaded.
+        discardBindings();
+        pushDocumentElementToFrontend();
+    }
+}
+
+long InspectorDOMAgent::bind(Node* node)
+{
+    HashMap<Node*, long>::iterator it = m_nodeToId.find(node);
+    if (it != m_nodeToId.end())
+        return it->second;
+    long id = m_lastNodeId++;
+    m_nodeToId.set(node, id);
+    m_idToNode.set(id, node);
+    return id;
+}
+
+void InspectorDOMAgent::unbind(Node* node)
+{
+    if (node->isFrameOwnerElement()) {
+        const HTMLFrameOwnerElement* frameOwner = static_cast<const HTMLFrameOwnerElement*>(node);
+        stopListening(frameOwner->contentDocument());
+    }
+
+    HashMap<Node*, long>::iterator it = m_nodeToId.find(node);
+    if (it != m_nodeToId.end()) {
+        m_idToNode.remove(m_idToNode.find(it->second));
+        m_childrenRequested.remove(m_childrenRequested.find(it->second));
+        m_nodeToId.remove(it);
+    }
+}
+
+void InspectorDOMAgent::pushDocumentElementToFrontend()
+{
+    Element* docElem = mainFrameDocument()->documentElement();
+    if (!m_nodeToId.contains(docElem))
+        m_frontend->setDocumentElement(buildObjectForNode(docElem, 0));
+}
+
+void InspectorDOMAgent::pushChildNodesToFrontend(long elementId)
+{
+    Node* node = nodeForId(elementId);
+    if (!node || (node->nodeType() != Node::ELEMENT_NODE))
+        return;
+    if (m_childrenRequested.contains(elementId))
+        return;
+
+    Element* element = static_cast<Element*>(node);
+    ScriptArray children = buildArrayForElementChildren(element, 1);
+    m_childrenRequested.add(elementId);
+    m_frontend->setChildNodes(elementId, children);
+}
+
+void InspectorDOMAgent::discardBindings()
+{
+    m_nodeToId.clear();
+    m_idToNode.clear();
+    m_childrenRequested.clear();
+}
+
+Node* InspectorDOMAgent::nodeForId(long id)
+{
+    HashMap<long, Node*>::iterator it = m_idToNode.find(id);
+    if (it != m_idToNode.end())
+        return it->second;
+    return 0;
+}
+
+long InspectorDOMAgent::idForNode(Node* node)
+{
+    if (!node)
+        return 0;
+    HashMap<Node*, long>::iterator it = m_nodeToId.find(node);
+    if (it != m_nodeToId.end())
+        return it->second;
+    return 0;
+}
+
+void InspectorDOMAgent::getChildNodes(long callId, long elementId)
+{
+    pushChildNodesToFrontend(elementId);
+    m_frontend->didGetChildNodes(callId);
+}
+
+long InspectorDOMAgent::pushNodePathToFrontend(Node* nodeToPush)
+{
+    ASSERT(nodeToPush);  // Invalid input
+
+    // If we are sending information to the client that is currently being created. Send root node first.
+    pushDocumentElementToFrontend();
+
+    // Return id in case the node is known.
+    long result = idForNode(nodeToPush);
+    if (result)
+        return result;
+
+    Element* element = innerParentElement(nodeToPush);
+    ASSERT(element);  // Node is detached or is a document itself
+
+    Vector<Element*> path;
+    while (element && !idForNode(element)) {
+        path.append(element);
+        element = innerParentElement(element);
+    }
+
+    // element is known to the client
+    ASSERT(element);
+    path.append(element);
+    for (int i = path.size() - 1; i >= 0; --i) {
+        long nodeId = idForNode(path.at(i));
+        ASSERT(nodeId);
+        pushChildNodesToFrontend(nodeId);
+    }
+    return idForNode(nodeToPush);
+}
+
+void InspectorDOMAgent::setAttribute(long callId, long elementId, const String& name, const String& value)
+{
+    Node* node = nodeForId(elementId);
+    if (node && (node->nodeType() == Node::ELEMENT_NODE)) {
+        Element* element = static_cast<Element*>(node);
+        ExceptionCode ec = 0;
+        element->setAttribute(name, value, ec);
+        m_frontend->didApplyDomChange(callId, ec == 0);
+    } else {
+        m_frontend->didApplyDomChange(callId, false);
+    }
+}
+
+void InspectorDOMAgent::removeAttribute(long callId, long elementId, const String& name)
+{
+    Node* node = nodeForId(elementId);
+    if (node && (node->nodeType() == Node::ELEMENT_NODE)) {
+        Element* element = static_cast<Element*>(node);
+        ExceptionCode ec = 0;
+        element->removeAttribute(name, ec);
+        m_frontend->didApplyDomChange(callId, ec == 0);
+    } else {
+        m_frontend->didApplyDomChange(callId, false);
+    }
+}
+
+void InspectorDOMAgent::setTextNodeValue(long callId, long elementId, const String& value)
+{
+    Node* node = nodeForId(elementId);
+    if (node && (node->nodeType() == Node::TEXT_NODE)) {
+        Text* text_node = static_cast<Text*>(node);
+        ExceptionCode ec = 0;
+        text_node->replaceWholeText(value, ec);
+        m_frontend->didApplyDomChange(callId, ec == 0);
+    } else {
+        m_frontend->didApplyDomChange(callId, false);
+    }
+}
+
+ScriptObject InspectorDOMAgent::buildObjectForNode(Node* node, int depth)
+{
+    ScriptObject value = m_frontend->newScriptObject();
+
+    long id = bind(node);
+    String nodeName;
+    String nodeValue;
+
+    switch (node->nodeType()) {
+        case Node::TEXT_NODE:
+        case Node::COMMENT_NODE:
+            nodeValue = node->nodeValue();
+            break;
+        case Node::ATTRIBUTE_NODE:
+        case Node::DOCUMENT_NODE:
+        case Node::DOCUMENT_FRAGMENT_NODE:
+            break;
+        case Node::ELEMENT_NODE:
+        default:
+            nodeName = node->nodeName();
+            break;
+    }
+
+    value.set("id", static_cast<int>(id));
+    value.set("nodeType", node->nodeType());
+    value.set("nodeName", nodeName);
+    value.set("nodeValue", nodeValue);
+
+    if (node->nodeType() == Node::ELEMENT_NODE) {
+        Element* element = static_cast<Element*>(node);
+        value.set("attributes", buildArrayForElementAttributes(element));
+        int nodeCount = innerChildNodeCount(element);
+        value.set("childNodeCount", nodeCount);
+
+        ScriptArray children = buildArrayForElementChildren(element, depth);
+        if (children.length() > 0)
+            value.set("children", children);
+    }
+    return value;
+}
+
+ScriptArray InspectorDOMAgent::buildArrayForElementAttributes(Element* element)
+{
+    ScriptArray attributesValue = m_frontend->newScriptArray();
+    // Go through all attributes and serialize them.
+    const NamedNodeMap* attrMap = element->attributes(true);
+    if (!attrMap)
+        return attributesValue;
+    unsigned numAttrs = attrMap->length();
+    int index = 0;
+    for (unsigned i = 0; i < numAttrs; ++i) {
+        // Add attribute pair
+        const Attribute *attribute = attrMap->attributeItem(i);
+        attributesValue.set(index++, attribute->name().toString());
+        attributesValue.set(index++, attribute->value());
+    }
+    return attributesValue;
+}
+
+ScriptArray InspectorDOMAgent::buildArrayForElementChildren(Element* element, int depth)
+{
+    ScriptArray children = m_frontend->newScriptArray();
+    if (depth == 0) {
+        int index = 0;
+        // Special case the_only text child.
+        if (innerChildNodeCount(element) == 1) {
+            Node *child = innerFirstChild(element);
+            if (child->nodeType() == Node::TEXT_NODE)
+                children.set(index++, buildObjectForNode(child, 0));
+        }
+        return children;
+    } else if (depth > 0) {
+        depth--;
+    }
+
+    int index = 0;
+    for (Node *child = innerFirstChild(element); child; child = innerNextSibling(child))
+        children.set(index++, buildObjectForNode(child, depth));
+    return children;
+}
+
+Node* InspectorDOMAgent::innerFirstChild(Node* node)
+{
+    if (node->isFrameOwnerElement()) {
+        HTMLFrameOwnerElement* frameOwner = static_cast<HTMLFrameOwnerElement*>(node);
+        Document* doc = frameOwner->contentDocument();
+        if (doc) {
+            startListening(doc);
+            return doc->firstChild();
+        }
+    }
+    node = node->firstChild();
+    while (isWhitespace(node))
+        node = node->nextSibling();
+    return node;
+}
+
+Node* InspectorDOMAgent::innerNextSibling(Node* node)
+{
+    do {
+        node = node->nextSibling();
+    } while (isWhitespace(node));
+    return node;
+}
+
+Node* InspectorDOMAgent::innerPreviousSibling(Node* node)
+{
+    do {
+        node = node->previousSibling();
+    } while (isWhitespace(node));
+    return node;
+}
+
+int InspectorDOMAgent::innerChildNodeCount(Node* node)
+{
+    int count = 0;
+    Node* child = innerFirstChild(node);
+    while (child) {
+        count++;
+        child = innerNextSibling(child);
+    }
+    return count;
+}
+
+Element* InspectorDOMAgent::innerParentElement(Node* node)
+{
+    Element* element = node->parentElement();
+    if (!element)
+        return node->ownerDocument()->ownerElement();
+    return element;
+}
+
+bool InspectorDOMAgent::isWhitespace(Node* node)
+{
+    //TODO: pull ignoreWhitespace setting from the frontend and use here.
+    return node && node->nodeType() == Node::TEXT_NODE && node->nodeValue().stripWhiteSpace().length() == 0;
+}
+
+Document* InspectorDOMAgent::mainFrameDocument()
+{
+    ListHashSet<RefPtr<Document> >::iterator it = m_documents.begin();
+    if (it != m_documents.end())
+        return it->get();
+    return 0;
+}
+
+} // namespace WebCore
diff --git a/WebCore/inspector/InspectorDOMAgent.h b/WebCore/inspector/InspectorDOMAgent.h
new file mode 100644 (file)
index 0000000..28c3a22
--- /dev/null
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2009 Apple Inc. All rights reserved.
+ * Copyright (C) 2009 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1.  Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ * 2.  Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
+ *     its contributors may be used to endorse or promote products derived
+ *     from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef InspectorDOMAgent_h
+#define InspectorDOMAgent_h
+
+#include "EventListener.h"
+#include "ScriptArray.h"
+#include "ScriptObject.h"
+#include "ScriptState.h"
+
+#include <wtf/ListHashSet.h>
+#include <wtf/HashMap.h>
+#include <wtf/HashSet.h>
+#include <wtf/PassRefPtr.h>
+#include <wtf/RefPtr.h>
+
+namespace WebCore {
+    class Element;
+    class Event;
+    class Document;
+    class InspectorFrontend;
+    class NameNodeMap;
+    class Node;
+    class Page;
+
+    class InspectorDOMAgent : public EventListener {
+    public:
+        InspectorDOMAgent(InspectorFrontend* frontend);
+        ~InspectorDOMAgent();
+
+        // Methods called from the frontend.
+        void getChildNodes(long callId, long elementId);
+        void setAttribute(long callId, long elementId, const String& name, const String& value);
+        void removeAttribute(long callId, long elementId, const String& name);
+        void setTextNodeValue(long callId, long elementId, const String& value);
+
+        // Methods called from the InspectorController.
+        void setDocument(Document* document);
+
+        Node* nodeForId(long nodeId);
+        long idForNode(Node* node);
+        long pushNodePathToFrontend(Node* node);
+
+   private:
+        void startListening(Document* document);
+        void stopListening(Document* document);
+
+        virtual void handleEvent(Event* event, bool isWindowEvent);
+
+        long bind(Node* node);
+        void unbind(Node* node);
+
+        void pushDocumentElementToFrontend();
+        void pushChildNodesToFrontend(long elementId);
+
+        ScriptObject buildObjectForNode(Node* node, int depth);
+        ScriptArray buildArrayForElementAttributes(Element* elemen);
+        ScriptArray buildArrayForElementChildren(Element* element, int depth);
+
+        // We represent embedded doms as a part of the same hierarchy. Hence we treat children of frame owners differently.
+        // We also skip whitespace text nodes conditionally. Following methods encapsulate these specifics.
+        Node* innerFirstChild(Node* node);
+        Node* innerNextSibling(Node* node);
+        Node* innerPreviousSibling(Node* node);
+        int innerChildNodeCount(Node* node);
+        Element* innerParentElement(Node* node);
+        bool isWhitespace(Node* node);
+
+        Document* mainFrameDocument();
+        void discardBindings();
+
+        InspectorFrontend* m_frontend;
+        HashMap<Node*, long> m_nodeToId;
+        HashMap<long, Node*> m_idToNode;
+        HashSet<long> m_childrenRequested;
+        long m_lastNodeId;
+        ListHashSet<RefPtr<Document> > m_documents;
+        RefPtr<EventListener> m_eventListener;
+    };
+
+
+} // namespace WebCore
+
+#endif // !defined(InspectorDOMAgent_h)
index ef2e8bd..af65fb4 100644 (file)
@@ -286,6 +286,69 @@ bool InspectorFrontend::addDOMStorage(const ScriptObject& domStorageObj)
 }
 #endif
 
+void InspectorFrontend::setDocumentElement(const ScriptObject& root)
+{
+    OwnPtr<ScriptFunctionCall> function(newFunctionCall("setDocumentElement"));
+    function->appendArgument(root);
+    function->call();
+}
+
+void InspectorFrontend::setChildNodes(int parentId, const ScriptArray& nodes)
+{
+    OwnPtr<ScriptFunctionCall> function(newFunctionCall("setChildNodes"));
+    function->appendArgument(parentId);
+    function->appendArgument(nodes);
+    function->call();
+}
+
+void InspectorFrontend::hasChildrenUpdated(int id, bool newValue)
+{
+    OwnPtr<ScriptFunctionCall> function(newFunctionCall("hasChildrenUpdated"));
+    function->appendArgument(id);
+    function->appendArgument(newValue);
+    function->call();
+}
+
+void InspectorFrontend::childNodeInserted(int parentId, int prevId, const ScriptObject& node)
+{
+    OwnPtr<ScriptFunctionCall> function(newFunctionCall("childNodeInserted"));
+    function->appendArgument(parentId);
+    function->appendArgument(prevId);
+    function->appendArgument(node);
+    function->call();
+}
+
+void InspectorFrontend::childNodeRemoved(int parentId, int id)
+{
+    OwnPtr<ScriptFunctionCall> function(newFunctionCall("childNodeRemoved"));
+    function->appendArgument(parentId);
+    function->appendArgument(id);
+    function->call();
+}
+
+void InspectorFrontend::attributesUpdated(int id, const ScriptArray& attributes)
+{
+    OwnPtr<ScriptFunctionCall> function(newFunctionCall("attributesUpdated"));
+    function->appendArgument(id);
+    function->appendArgument(attributes);
+    function->call();
+}
+
+void InspectorFrontend::didGetChildNodes(int callId)
+{
+    OwnPtr<ScriptFunctionCall> function(newFunctionCall("didGetChildNodes"));
+    function->appendArgument(callId);
+    function->call();
+}
+
+void InspectorFrontend::didApplyDomChange(int callId, bool success)
+{
+    OwnPtr<ScriptFunctionCall> function(newFunctionCall("didApplyDomChange"));
+    function->appendArgument(callId);
+    function->appendArgument(success);
+    function->call();
+}
+
 PassOwnPtr<ScriptFunctionCall> InspectorFrontend::newFunctionCall(const String& functionName)
 {
     ScriptFunctionCall* function = new ScriptFunctionCall(m_scriptState, m_webInspector, "dispatch");
index b868c10..7a48b62 100644 (file)
@@ -96,6 +96,15 @@ namespace WebCore {
         bool addDOMStorage(const ScriptObject& domStorageObj);
 #endif
 
+        void setDocumentElement(const ScriptObject& root);
+        void setChildNodes(int parentId, const ScriptArray& nodes);
+        void hasChildrenUpdated(int id, bool newValue);
+        void childNodeInserted(int parentId, int prevId, const ScriptObject& node);
+        void childNodeRemoved(int parentId, int id);
+        void attributesUpdated(int id, const ScriptArray& attributes);
+        void didGetChildNodes(int callId);
+        void didApplyDomChange(int callId, bool success);
+
     private:
         PassOwnPtr<ScriptFunctionCall> newFunctionCall(const String& functionName);
         void callSimpleFunction(const String& functionName);
diff --git a/WebCore/inspector/front-end/Callback.js b/WebCore/inspector/front-end/Callback.js
new file mode 100644 (file)
index 0000000..8ae7f95
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2009 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+WebInspector.Callback = function()
+{
+    this._lastCallbackId = 1;
+    this._callbacks = {};
+}
+
+WebInspector.Callback.prototype = {
+    wrap: function(callback)
+    {
+        var callbackId = this._lastCallbackId++;
+        this._callbacks[callbackId] = callback || function() {};
+        return callbackId;
+    },
+
+    processCallback: function(callbackId, opt_vararg)
+    {
+        var args = Array.prototype.slice.call(arguments, 1);
+        var callback = this._callbacks[callbackId];
+        callback.apply(null, args);
+        delete this._callbacks[callbackId];
+    }
+}
+
+WebInspector.Callback._INSTANCE = new WebInspector.Callback();
+WebInspector.Callback.wrap = WebInspector.Callback._INSTANCE.wrap.bind(WebInspector.Callback._INSTANCE);
+WebInspector.Callback.processCallback = WebInspector.Callback._INSTANCE.processCallback.bind(WebInspector.Callback._INSTANCE);
diff --git a/WebCore/inspector/front-end/DOMAgent.js b/WebCore/inspector/front-end/DOMAgent.js
new file mode 100644 (file)
index 0000000..680ae5a
--- /dev/null
@@ -0,0 +1,557 @@
+/*
+ * Copyright (C) 2009 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+WebInspector.DOMNode = function(doc, payload) {
+    this.ownerDocument = doc;
+
+    this._id = payload.id;
+    this.nodeType = payload.nodeType;
+    this.nodeName = payload.nodeName;
+    this._nodeValue = payload.nodeValue;
+    this.textContent = this.nodeValue;
+
+    this.attributes = [];
+    this._attributesMap = {};
+    if (payload.attributes)
+        this._setAttributesPayload(payload.attributes);
+
+    this._childNodeCount = payload.childNodeCount;
+    this.children = null;
+
+    this.nextSibling = null;
+    this.prevSibling = null;
+    this.firstChild = null;
+    this.parentNode = null;
+
+    if (payload.childNodes)
+        this._setChildrenPayload(payload.childNodes);
+
+    this._computedStyle = null;
+    this.style = null;
+    this._matchedCSSRules = [];
+}
+
+WebInspector.DOMNode.prototype = {
+    hasAttributes: function()
+    {
+        return this.attributes.length > 0;
+    },
+
+    hasChildNodes: function()  {
+        return this._childNodeCount > 0;
+    },
+
+    get nodeValue() {
+        return this._nodeValue;
+    },
+
+    set nodeValue(value) {
+        if (this.nodeType != Node.TEXT_NODE)
+            return;
+        var self = this;
+        var callback = function()
+        {
+            self._nodeValue = value;
+            self.textContent = value;
+        };
+        this.ownerDocument._domAgent.setTextNodeValueAsync(this, value, callback);
+    },
+
+    getAttribute: function(name)
+    {
+        var attr = this._attributesMap[name];
+        return attr ? attr.value : undefined;
+    },
+
+    setAttribute: function(name, value)
+    {
+        var self = this;
+        var callback = function()
+        {
+            var attr = self._attributesMap[name];
+            if (attr)
+                attr.value = value;
+            else
+                attr = self._addAttribute(name, value);
+        };
+        this.ownerDocument._domAgent.setAttributeAsync(this, name, value, callback);
+    },
+
+    removeAttribute: function(name)
+    {
+        var self = this;
+        var callback = function()
+        {
+            delete self._attributesMap[name];
+            for (var i = 0;  i < self.attributes.length; ++i) {
+                if (self.attributes[i].name == name) {
+                    self.attributes.splice(i, 1);
+                    break;
+                }
+            }
+        };
+        this.ownerDocument._domAgent.removeAttributeAsync(this, name, callback);
+    },
+
+    _setAttributesPayload: function(attrs)
+    {
+        for (var i = 0; i < attrs.length; i += 2)
+            this._addAttribute(attrs[i], attrs[i + 1]);
+    },
+
+    _insertChild: function(prev, payload)
+    {
+        var node = new WebInspector.DOMNode(this.ownerDocument, payload);
+        if (!prev)
+            // First node
+            this.children = [ node ];
+        else
+            this.children.splice(this.children.indexOf(prev) + 1, 0, node);
+        this._renumber();
+        return node;
+    },
+
+    removeChild_: function(node)
+    {
+        this.children.splice(this.children.indexOf(node), 1);
+        node.parentNode = null;
+        this._renumber();
+    },
+
+    _setChildrenPayload: function(payloads)
+    {
+        this.children = [];
+        for (var i = 0; i < payloads.length; ++i) {
+            var payload = payloads[i];
+            var node = new WebInspector.DOMNode(this.ownerDocument, payload);
+            this.children.push(node);
+        }
+        this._renumber();
+    },
+
+    _renumber: function()
+    {
+        this._childNodeCount = this.children.length;
+        if (this._childNodeCount == 0) {
+            this.firstChild = null;
+            return;
+        }
+        this.firstChild = this.children[0];
+        for (var i = 0; i < this._childNodeCount; ++i) {
+            var child = this.children[i];
+            child.nextSibling = i + 1 < this._childNodeCount ? this.children[i + 1] : null;
+            child.prevSibling = i - 1 >= 0 ? this.children[i - 1] : null;
+            child.parentNode = this;
+        }
+    },
+
+    _addAttribute: function(name, value)
+    {
+        var attr = {
+            "name": name,
+            "value": value,
+            "_node": this
+        };
+        this._attributesMap[name] = attr;
+        this.attributes.push(attr);
+    },
+
+    _setStyles: function(computedStyle, inlineStyle, styleAttributes, matchedCSSRules)
+    {
+        this._computedStyle = this._makeStyle(computedStyle);
+        this.style = this._makeStyle(inlineStyle);
+
+        for (var name in styleAttributes) {
+            if (this._attributesMap[name])
+                this._attributesMap[name].style = this._makeStyle(styleAttributes[name]);
+        }
+
+        this._matchedCSSRules = [];
+        for (var i = 0; i < matchedCSSRules.length; i++) {
+            var description = matchedCSSRules[i];
+
+            var rule = {};
+            rule.selectorText = description.selectorText;
+            rule.style = this._makeStyle(description.style);
+
+            if (description.parentStyleSheet) {
+                var parentStyleMock = {};
+                parentStyleMock.href = description.parentStyleSheet.href;
+                var nodeName = description.parentStyleSheet.ownerNodeName;
+                if (nodeName) {
+                    parentStyleMock.ownerNode = {
+                        "nodeName": nodeName
+                    };
+                }
+                rule.parentStyleSheet = parentStyleMock;
+            }
+            this._matchedCSSRules.push(rule);
+        }
+    },
+
+    _makeStyle: function(payload)
+    {
+        var style = new WebInspector.CSSStyleDeclaration(payload);
+        style._nodeId = this._id;
+        return style;
+    },
+
+    _clearStyles: function()
+    {
+        this.computedStyle = null;
+        this.style = null;
+        for (var name in this._attributesMap)
+            this._attributesMap[name].style = null;
+        this._matchedCSSRules = null;
+    }
+}
+
+WebInspector.DOMDocument = function(domAgent, defaultView)
+{
+    WebInspector.DOMNode.call(this, null,
+        {
+            id: 0,
+            nodeType: Node.DOCUMENT_NODE,
+            nodeName: "",
+            nodeValue: "",
+            attributes: [],
+            childNodeCount: 0
+        });
+    this._listeners = {};
+    this._domAgent = domAgent;
+    this.defaultView = defaultView;
+}
+
+WebInspector.DOMDocument.prototype = {
+
+    addEventListener: function(name, callback, useCapture)
+    {
+        var listeners = this._listeners[name];
+        if (!listeners) {
+            listeners = [];
+            this._listeners[name] = listeners;
+        }
+        listeners.push(callback);
+    },
+
+    removeEventListener: function(name, callback, useCapture)
+    {
+        var listeners = this._listeners[name];
+        if (!listeners)
+            return;
+
+        var index = listeners.indexOf(callback);
+        if (index != -1)
+            listeners.splice(index, 1);
+    },
+
+    _fireDomEvent: function(name, event)
+    {
+        var listeners = this._listeners[name];
+        if (!listeners)
+          return;
+
+        for (var i = 0; i < listeners.length; ++i)
+          listeners[i](event);
+    }
+}
+
+WebInspector.DOMDocument.prototype.__proto__ = WebInspector.DOMNode.prototype;
+
+
+WebInspector.DOMWindow = function(domAgent)
+{
+    this._domAgent = domAgent;
+}
+
+WebInspector.DOMWindow.prototype = {
+    get document()
+    {
+        return this._domAgent.document;
+    },
+    get Node()
+    {
+        return WebInspector.DOMNode;
+    },
+
+    get Element()
+    {
+        return WebInspector.DOMNode;
+    },
+
+    Object: function()
+    {
+    },
+
+    getComputedStyle: function(node)
+    {
+        return node._computedStyle;
+    },
+
+    getMatchedCSSRules: function(node, pseudoElement, authorOnly)
+    {
+        return node._matchedCSSRules;
+    }
+}
+
+WebInspector.DOMAgent = function() {
+    this._window = new WebInspector.DOMWindow(this);
+    this._idToDOMNode = null;
+    this.document = null;
+
+    // Install onpopulate handler. This is a temporary measure.
+    // TODO: add this code into the original updateChildren once domAgent
+    // becomes primary source of DOM information.
+    // TODO2: update ElementsPanel to not track embedded iframes - it is already being handled
+    // in the agent backend.
+    var domAgent = this;
+    var originalUpdateChildren = WebInspector.ElementsTreeElement.prototype.updateChildren;
+    WebInspector.ElementsTreeElement.prototype.updateChildren = function()
+    {
+        domAgent.getChildNodesAsync(this.representedObject, originalUpdateChildren.bind(this));
+    };
+
+    // Mute console handle to avoid crash on selection change.
+    // TODO: Re-implement inspectorConsoleAPI to work in a serialized way and remove this workaround.
+    WebInspector.Console.prototype.addInspectedNode = function()
+    {
+    };
+
+    // Whitespace is ignored in InspectorDOMAgent already -> no need to filter.
+    // TODO: Either remove all of its usages or push value into the agent backend.
+    Preferences.ignoreWhitespace = false;
+}
+
+WebInspector.DOMAgent.prototype = {
+    get inspectedWindow()
+    {
+        return this._window;
+    },
+
+    getChildNodesAsync: function(parent, opt_callback)
+    {
+        var children = parent.children;
+        if (children && opt_callback) {
+          opt_callback(children);
+          return;
+        }
+        var mycallback = function() {
+            if (opt_callback) {
+                opt_callback(parent.children);
+            }
+        };
+        var callId = WebInspector.Callback.wrap(mycallback);
+        InspectorController.getChildNodes(callId, parent._id);
+    },
+
+    setAttributeAsync: function(node, name, value, callback)
+    {
+        var mycallback = this._didApplyDomChange.bind(this, node, callback);
+        InspectorController.setAttribute(WebInspector.Callback.wrap(mycallback), node._id, name, value);
+    },
+
+    removeAttributeAsync: function(node, name, callback)
+    {
+        var mycallback = this._didApplyDomChange.bind(this, node, callback);
+        InspectorController.removeAttribute(WebInspector.Callback.wrap(mycallback), node._id, name);
+    },
+
+    setTextNodeValueAsync: function(node, text, callback)
+    {
+        var mycallback = this._didApplyDomChange.bind(this, node, callback);
+        InspectorController.setTextNodeValue(WebInspector.Callback.wrap(mycallback), node._id, text);
+    },
+
+    _didApplyDomChange: function(node, callback, success)
+    {
+        if (!success)
+            return;
+        callback();
+        // TODO(pfeldman): Fix this hack.
+        var elem = WebInspector.panels.elements.treeOutline.findTreeElement(node);
+        if (elem) {
+            elem._updateTitle();
+        }
+    },
+
+    _attributesUpdated: function(nodeId, attrsArray)
+    {
+        var node = this._idToDOMNode[nodeId];
+        node._setAttributesPayload(attrsArray);
+    },
+
+    getNodeForId: function(nodeId) {
+        return this._idToDOMNode[nodeId];
+    },
+
+    _setDocumentElement: function(payload)
+    {
+        this.document = new WebInspector.DOMDocument(this, this._window);
+        this._idToDOMNode = { 0 : this.document };
+        this._setChildNodes(0, [payload]);
+        this.document.documentElement = this.document.firstChild;
+        this.document.documentElement.ownerDocument = this.document;
+        WebInspector.panels.elements.reset();
+    },
+
+    _setChildNodes: function(parentId, payloads)
+    {
+        var parent = this._idToDOMNode[parentId];
+        if (parent.children) {
+          return;
+        }
+        parent._setChildrenPayload(payloads);
+        this._bindNodes(parent.children);
+    },
+
+    _bindNodes: function(children)
+    {
+        for (var i = 0; i < children.length; ++i) {
+            var child = children[i];
+            this._idToDOMNode[child._id] = child;
+            if (child.children)
+                this._bindNodes(child.children);
+        }
+    },
+
+    _hasChildrenUpdated: function(nodeId, newValue)
+    {
+        var node = this._idToDOMNode[nodeId];
+        var outline = WebInspector.panels.elements.treeOutline;
+        var treeElement = outline.findTreeElement(node);
+        if (treeElement) {
+            treeElement.hasChildren = newValue;
+            treeElement.whitespaceIgnored = Preferences.ignoreWhitespace;
+        }
+    },
+
+    _childNodeInserted: function(parentId, prevId, payload)
+    {
+        var parent = this._idToDOMNode[parentId];
+        var prev = this._idToDOMNode[prevId];
+        var node = parent._insertChild(prev, payload);
+        this._idToDOMNode[node._id] = node;
+        var event = { target : node, relatedNode : parent };
+        this.document._fireDomEvent("DOMNodeInserted", event);
+    },
+
+    _childNodeRemoved: function(parentId, nodeId)
+    {
+        var parent = this._idToDOMNode[parentId];
+        var node = this._idToDOMNode[nodeId];
+        parent.removeChild_(node);
+        var event = { target : node, relatedNode : parent };
+        this.document._fireDomEvent("DOMNodeRemoved", event);
+        delete this._idToDOMNode[nodeId];
+    }
+}
+
+WebInspector.CSSStyleDeclaration = function(payload) {
+    this._id = payload.id;
+    this.width = payload.width;
+    this.height = payload.height;
+    this.__disabledProperties = payload.disabledProperties;
+    this.__disabledPropertyValues = payload.disabledPropertyValues;
+    this.__disabledPropertyPriorities = payload.disabledPropertyPriorities;
+
+    this._propertyMap = {};
+    this.length = this._properties.length;
+
+    for (var i = 0; i < this.length; ++i) {
+        var property = this._properties[i];
+        var name = property.name;
+        this[i] = name;
+        this._propertyMap[name] = property;
+    }
+}
+
+WebInspector.CSSStyleDeclaration.prototype = {
+    getPropertyValue: function(name)
+    {
+        var property = this._propertyMap[name];
+        return property ? property.value : "";
+    },
+
+    getPropertyPriority: function(name)
+    {
+        var property = this._propertyMap[name];
+        return property ? property.priority : "";
+    },
+
+    getPropertyShorthand: function(name)
+    {
+        var property = this._propertyMap[name];
+        return property ? property.shorthand : "";
+    },
+
+    isPropertyImplicit: function(name)
+    {
+        var property = this._propertyMap[name];
+        return property ? property.implicit : "";
+    }
+}
+
+WebInspector.attributesUpdated = function()
+{
+    this.domAgent._attributesUpdated.apply(this.domAgent, arguments);
+}
+
+WebInspector.setDocumentElement = function()
+{
+    this.domAgent._setDocumentElement.apply(this.domAgent, arguments);
+}
+
+WebInspector.setChildNodes = function()
+{
+    this.domAgent._setChildNodes.apply(this.domAgent, arguments);
+}
+
+WebInspector.hasChildrenUpdated = function()
+{
+    this.domAgent._hasChildrenUpdated.apply(this.domAgent, arguments);
+}
+
+WebInspector.childNodeInserted = function()
+{
+    this.domAgent._childNodeInserted.apply(this.domAgent, arguments);
+    this._childNodeInserted.bind(this);
+}
+
+WebInspector.childNodeRemoved = function()
+{
+    this.domAgent._childNodeRemoved.apply(this.domAgent, arguments);
+    this._childNodeRemoved.bind(this);
+}
+
+WebInspector.didGetChildNodes = WebInspector.Callback.processCallback;
+WebInspector.didPerformSearch = WebInspector.Callback.processCallback;
+WebInspector.didApplyDomChange = WebInspector.Callback.processCallback;
+WebInspector.didRemoveAttribute = WebInspector.Callback.processCallback;
+WebInspector.didSetTextNodeValue = WebInspector.Callback.processCallback;
index 76d9746..583afcc 100644 (file)
@@ -60,7 +60,6 @@ WebInspector.ElementsPanel = function()
             InspectorController.toggleNodeSearch();
             this.panel.nodeSearchButton.removeStyleClass("toggled-on");
         }
-
         WebInspector.console.addInspectedNode(this._focusedDOMNode);
     };
 
@@ -181,7 +180,7 @@ WebInspector.ElementsPanel.prototype = {
         delete this.currentQuery;
         this.searchCanceled();
 
-        var inspectedWindow = InspectorController.inspectedWindow();
+        var inspectedWindow = Preferences.useDOMAgent ? WebInspector.domAgent.inspectedWindow : InspectorController.inspectedWindow();
         if (!inspectedWindow || !inspectedWindow.document)
             return;
 
index 6619499..511a1bc 100644 (file)
@@ -4,6 +4,7 @@
     <file>BottomUpProfileDataGridTree.js</file>
     <file>Breakpoint.js</file>
     <file>BreakpointsSidebarPane.js</file>
+    <file>Callback.js</file>
     <file>CallStackSidebarPane.js</file>
     <file>Console.js</file>
     <file>Database.js</file>
@@ -11,6 +12,7 @@
     <file>DatabasesPanel.js</file>
     <file>DatabaseTableView.js</file>
     <file>DataGrid.js</file>
+    <file>DOMAgent.js</file>
     <file>DOMStorage.js</file>
     <file>DOMStorageDataGrid.js</file>
     <file>DOMStorageItemsView.js</file>
index 762074e..dc76c48 100644 (file)
@@ -39,6 +39,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     <script type="text/javascript" src="Placard.js"></script>
     <script type="text/javascript" src="View.js"></script>
     <script type="text/javascript" src="Console.js"></script>
+    <script type="text/javascript" src="Callback.js"></script>
     <script type="text/javascript" src="Resource.js"></script>
     <script type="text/javascript" src="ResourceCategory.js"></script>
     <script type="text/javascript" src="Database.js"></script>
@@ -78,6 +79,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     <script type="text/javascript" src="BottomUpProfileDataGridTree.js"></script>
     <script type="text/javascript" src="TopDownProfileDataGridTree.js"></script>
     <script type="text/javascript" src="ProfileView.js"></script>
+    <script type="text/javascript" src="DOMAgent.js"></script>
 </head>
 <body class="detached">
     <div id="toolbar">
index 7e23692..db5b6d0 100644 (file)
@@ -37,7 +37,8 @@ var Preferences = {
     minScriptsSidebarWidth: 200,
     showInheritedComputedStyleProperties: false,
     styleRulesExpandedState: {},
-    showMissingLocalizedStrings: false
+    showMissingLocalizedStrings: false,
+    useDOMAgent: false
 }
 
 var WebInspector = {
@@ -299,6 +300,8 @@ WebInspector.loaded = function()
     document.body.addStyleClass("platform-" + platform);
 
     this.console = new WebInspector.Console();
+    if (Preferences.useDOMAgent)
+        this.domAgent = new WebInspector.DOMAgent();
 
     this.panels = {};
     this._createPanels();
@@ -378,7 +381,7 @@ WebInspector.loaded = function()
     document.getElementById("toolbar").addEventListener("mousedown", this.toolbarDragStart, true);
     document.getElementById("close-button").addEventListener("click", this.close, true);
 
-    InspectorController.loaded();
+    InspectorController.loaded(Preferences.useDOMAgent);
 }
 
 var windowLoaded = function()