Web Inspector: rename DOMEditor to DOMPatchSupport, move undoable actions from
authorpfeldman@chromium.org <pfeldman@chromium.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 9 Feb 2012 17:42:09 +0000 (17:42 +0000)
committerpfeldman@chromium.org <pfeldman@chromium.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 9 Feb 2012 17:42:09 +0000 (17:42 +0000)
InspectorDOMAgent to the new DOMEditor.
https://bugs.webkit.org/show_bug.cgi?id=78245

Reviewed by Yury Semikhatsky.

* CMakeLists.txt:
* GNUmakefile.list.am:
* Target.pri:
* WebCore.gypi:
* WebCore.vcproj/WebCore.vcproj:
* WebCore.xcodeproj/project.pbxproj:
* inspector/DOMEditor.cpp:
(DOMEditor::DOMAction):
(WebCore::DOMEditor::DOMAction::DOMAction):
(WebCore::DOMEditor::DOMAction::perform):
(WebCore::DOMEditor::DOMAction::undo):
(DOMEditor::RemoveChildAction):
(WebCore::DOMEditor::RemoveChildAction::RemoveChildAction):
(WebCore::DOMEditor::RemoveChildAction::perform):
(WebCore::DOMEditor::RemoveChildAction::undo):
(DOMEditor::InsertBeforeAction):
(WebCore::DOMEditor::InsertBeforeAction::InsertBeforeAction):
(WebCore::DOMEditor::InsertBeforeAction::perform):
(WebCore::DOMEditor::InsertBeforeAction::undo):
(DOMEditor::RemoveAttributeAction):
(WebCore::DOMEditor::RemoveAttributeAction::RemoveAttributeAction):
(WebCore::DOMEditor::RemoveAttributeAction::perform):
(WebCore::DOMEditor::RemoveAttributeAction::undo):
(DOMEditor::SetAttributeAction):
(WebCore::DOMEditor::SetAttributeAction::SetAttributeAction):
(WebCore::DOMEditor::SetAttributeAction::perform):
(WebCore::DOMEditor::SetAttributeAction::undo):
(DOMEditor::SetOuterHTMLAction):
(WebCore::DOMEditor::SetOuterHTMLAction::SetOuterHTMLAction):
(WebCore::DOMEditor::SetOuterHTMLAction::perform):
(WebCore::DOMEditor::SetOuterHTMLAction::undo):
(WebCore::DOMEditor::SetOuterHTMLAction::newNode):
(DOMEditor::ReplaceWholeTextAction):
(WebCore::DOMEditor::ReplaceWholeTextAction::ReplaceWholeTextAction):
(WebCore::DOMEditor::ReplaceWholeTextAction::perform):
(WebCore::DOMEditor::ReplaceWholeTextAction::undo):
(WebCore::DOMEditor::DOMEditor):
(WebCore):
(WebCore::DOMEditor::~DOMEditor):
(WebCore::DOMEditor::insertBefore):
(WebCore::DOMEditor::removeChild):
(WebCore::DOMEditor::setAttribute):
(WebCore::DOMEditor::removeAttribute):
(WebCore::DOMEditor::setOuterHTML):
(WebCore::DOMEditor::replaceWholeText):
* inspector/DOMEditor.h:
(WebCore):
(DOMEditor):
* inspector/DOMPatchSupport.cpp: Copied from Source/WebCore/inspector/DOMEditor.cpp.
(WebCore::DOMPatchSupport::DOMPatchSupport):
(WebCore::DOMPatchSupport::~DOMPatchSupport):
(WebCore::DOMPatchSupport::patchDocument):
(WebCore::DOMPatchSupport::patchNode):
(WebCore::DOMPatchSupport::innerPatchNode):
(WebCore):
(WebCore::DOMPatchSupport::diff):
(WebCore::DOMPatchSupport::innerPatchChildren):
(WebCore::DOMPatchSupport::createDigest):
(WebCore::DOMPatchSupport::insertBefore):
(WebCore::DOMPatchSupport::removeChild):
(WebCore::DOMPatchSupport::markNodeAsUsed):
(WebCore::DOMPatchSupport::dumpMap):
* inspector/DOMPatchSupport.h: Copied from Source/WebCore/inspector/DOMEditor.h.
(DOMPatchSupport):
* inspector/InspectorDOMAgent.cpp:
(WebCore::InspectorDOMAgent::InspectorDOMAgent):
(WebCore::InspectorDOMAgent::setFrontend):
(WebCore::InspectorDOMAgent::clearFrontend):
(WebCore::InspectorDOMAgent::reset):
(WebCore::InspectorDOMAgent::setAttributeValue):
(WebCore::InspectorDOMAgent::setAttributesAsText):
(WebCore::InspectorDOMAgent::removeAttribute):
(WebCore::InspectorDOMAgent::removeNode):
(WebCore::InspectorDOMAgent::setNodeName):
(WebCore::InspectorDOMAgent::setOuterHTML):
(WebCore::InspectorDOMAgent::setNodeValue):
(WebCore::InspectorDOMAgent::moveTo):
* inspector/InspectorDOMAgent.h:
(WebCore):
(InspectorDOMAgent):
* inspector/InspectorPageAgent.cpp:
(WebCore::InspectorPageAgent::setDocumentContent):

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

14 files changed:
Source/WebCore/CMakeLists.txt
Source/WebCore/ChangeLog
Source/WebCore/GNUmakefile.list.am
Source/WebCore/Target.pri
Source/WebCore/WebCore.gypi
Source/WebCore/WebCore.vcproj/WebCore.vcproj
Source/WebCore/WebCore.xcodeproj/project.pbxproj
Source/WebCore/inspector/DOMEditor.cpp
Source/WebCore/inspector/DOMEditor.h
Source/WebCore/inspector/DOMPatchSupport.cpp [new file with mode: 0644]
Source/WebCore/inspector/DOMPatchSupport.h [new file with mode: 0644]
Source/WebCore/inspector/InspectorDOMAgent.cpp
Source/WebCore/inspector/InspectorDOMAgent.h
Source/WebCore/inspector/InspectorPageAgent.cpp

index c583668..43a7f21 100644 (file)
@@ -878,6 +878,7 @@ SET(WebCore_SOURCES
     inspector/ContentSearchUtils.cpp
     inspector/DOMEditor.cpp
     inspector/DOMNodeHighlighter.cpp
+    inspector/DOMPatchSupport.cpp
     inspector/IdentifiersFactory.cpp
     inspector/InjectedScript.cpp
     inspector/InjectedScriptHost.cpp
index abe2fe2..9185467 100644 (file)
@@ -1,3 +1,94 @@
+2012-02-09  Pavel Feldman  <pfeldman@google.com>
+
+        Web Inspector: rename DOMEditor to DOMPatchSupport, move undoable actions from
+        InspectorDOMAgent to the new DOMEditor.
+        https://bugs.webkit.org/show_bug.cgi?id=78245
+
+        Reviewed by Yury Semikhatsky.
+
+        * CMakeLists.txt:
+        * GNUmakefile.list.am:
+        * Target.pri:
+        * WebCore.gypi:
+        * WebCore.vcproj/WebCore.vcproj:
+        * WebCore.xcodeproj/project.pbxproj:
+        * inspector/DOMEditor.cpp:
+        (DOMEditor::DOMAction):
+        (WebCore::DOMEditor::DOMAction::DOMAction):
+        (WebCore::DOMEditor::DOMAction::perform):
+        (WebCore::DOMEditor::DOMAction::undo):
+        (DOMEditor::RemoveChildAction):
+        (WebCore::DOMEditor::RemoveChildAction::RemoveChildAction):
+        (WebCore::DOMEditor::RemoveChildAction::perform):
+        (WebCore::DOMEditor::RemoveChildAction::undo):
+        (DOMEditor::InsertBeforeAction):
+        (WebCore::DOMEditor::InsertBeforeAction::InsertBeforeAction):
+        (WebCore::DOMEditor::InsertBeforeAction::perform):
+        (WebCore::DOMEditor::InsertBeforeAction::undo):
+        (DOMEditor::RemoveAttributeAction):
+        (WebCore::DOMEditor::RemoveAttributeAction::RemoveAttributeAction):
+        (WebCore::DOMEditor::RemoveAttributeAction::perform):
+        (WebCore::DOMEditor::RemoveAttributeAction::undo):
+        (DOMEditor::SetAttributeAction):
+        (WebCore::DOMEditor::SetAttributeAction::SetAttributeAction):
+        (WebCore::DOMEditor::SetAttributeAction::perform):
+        (WebCore::DOMEditor::SetAttributeAction::undo):
+        (DOMEditor::SetOuterHTMLAction):
+        (WebCore::DOMEditor::SetOuterHTMLAction::SetOuterHTMLAction):
+        (WebCore::DOMEditor::SetOuterHTMLAction::perform):
+        (WebCore::DOMEditor::SetOuterHTMLAction::undo):
+        (WebCore::DOMEditor::SetOuterHTMLAction::newNode):
+        (DOMEditor::ReplaceWholeTextAction):
+        (WebCore::DOMEditor::ReplaceWholeTextAction::ReplaceWholeTextAction):
+        (WebCore::DOMEditor::ReplaceWholeTextAction::perform):
+        (WebCore::DOMEditor::ReplaceWholeTextAction::undo):
+        (WebCore::DOMEditor::DOMEditor):
+        (WebCore):
+        (WebCore::DOMEditor::~DOMEditor):
+        (WebCore::DOMEditor::insertBefore):
+        (WebCore::DOMEditor::removeChild):
+        (WebCore::DOMEditor::setAttribute):
+        (WebCore::DOMEditor::removeAttribute):
+        (WebCore::DOMEditor::setOuterHTML):
+        (WebCore::DOMEditor::replaceWholeText):
+        * inspector/DOMEditor.h:
+        (WebCore):
+        (DOMEditor):
+        * inspector/DOMPatchSupport.cpp: Copied from Source/WebCore/inspector/DOMEditor.cpp.
+        (WebCore::DOMPatchSupport::DOMPatchSupport):
+        (WebCore::DOMPatchSupport::~DOMPatchSupport):
+        (WebCore::DOMPatchSupport::patchDocument):
+        (WebCore::DOMPatchSupport::patchNode):
+        (WebCore::DOMPatchSupport::innerPatchNode):
+        (WebCore):
+        (WebCore::DOMPatchSupport::diff):
+        (WebCore::DOMPatchSupport::innerPatchChildren):
+        (WebCore::DOMPatchSupport::createDigest):
+        (WebCore::DOMPatchSupport::insertBefore):
+        (WebCore::DOMPatchSupport::removeChild):
+        (WebCore::DOMPatchSupport::markNodeAsUsed):
+        (WebCore::DOMPatchSupport::dumpMap):
+        * inspector/DOMPatchSupport.h: Copied from Source/WebCore/inspector/DOMEditor.h.
+        (DOMPatchSupport):
+        * inspector/InspectorDOMAgent.cpp:
+        (WebCore::InspectorDOMAgent::InspectorDOMAgent):
+        (WebCore::InspectorDOMAgent::setFrontend):
+        (WebCore::InspectorDOMAgent::clearFrontend):
+        (WebCore::InspectorDOMAgent::reset):
+        (WebCore::InspectorDOMAgent::setAttributeValue):
+        (WebCore::InspectorDOMAgent::setAttributesAsText):
+        (WebCore::InspectorDOMAgent::removeAttribute):
+        (WebCore::InspectorDOMAgent::removeNode):
+        (WebCore::InspectorDOMAgent::setNodeName):
+        (WebCore::InspectorDOMAgent::setOuterHTML):
+        (WebCore::InspectorDOMAgent::setNodeValue):
+        (WebCore::InspectorDOMAgent::moveTo):
+        * inspector/InspectorDOMAgent.h:
+        (WebCore):
+        (InspectorDOMAgent):
+        * inspector/InspectorPageAgent.cpp:
+        (WebCore::InspectorPageAgent::setDocumentContent):
+
 2012-02-09  Mark Rowe  <mrowe@apple.com>
 
         REGRESSION (r104746): iframes load PDFs as media documents
index c08cb25..58d9e9a 100644 (file)
@@ -2344,6 +2344,8 @@ webcore_sources += \
        Source/WebCore/inspector/DOMEditor.h \
        Source/WebCore/inspector/DOMNodeHighlighter.cpp \
        Source/WebCore/inspector/DOMNodeHighlighter.h \
+       Source/WebCore/inspector/DOMPatchSupport.cpp \
+       Source/WebCore/inspector/DOMPatchSupport.h \
        Source/WebCore/inspector/DOMWrapperVisitor.h \
        Source/WebCore/inspector/IdentifiersFactory.cpp \
        Source/WebCore/inspector/IdentifiersFactory.h \
index 758dd4b..0ebe4e4 100644 (file)
@@ -855,6 +855,7 @@ SOURCES += \
     inspector/ContentSearchUtils.cpp \
     inspector/DOMEditor.cpp \
     inspector/DOMNodeHighlighter.cpp \
+    inspector/DOMPatchSupport.cpp \
     inspector/IdentifiersFactory.cpp \
     inspector/InjectedScript.cpp \
     inspector/InjectedScriptHost.cpp \
@@ -1920,6 +1921,7 @@ HEADERS += \
     inspector/ContentSearchUtils.h \
     inspector/DOMEditor.h \
     inspector/DOMNodeHighlighter.h \
+    inspector/DOMPatchSupport.h \
     inspector/DOMWrapperVisitor.h \
     inspector/IdentifiersFactory.h \
     inspector/InjectedScript.h \
index c59acdc..9fb39a4 100644 (file)
             'inspector/DOMEditor.h',
             'inspector/DOMNodeHighlighter.cpp',
             'inspector/DOMNodeHighlighter.h',
+            'inspector/DOMPatchSupport.cpp',
+            'inspector/DOMPatchSupport.h',
             'inspector/DOMWrapperVisitor.h',
             'inspector/IdentifiersFactory.cpp',
             'inspector/IdentifiersFactory.h',
index 796239e..57a1175 100755 (executable)
                                >
                        </File>
                        <File
+                               RelativePath="..\inspector\DOMPatchSupport.cpp"
+                               >
+                       </File>
+                       <File
+                               RelativePath="..\inspector\DOMPatchSupport.h"
+                               >
+                       </File>
+                       <File
                                RelativePath="..\inspector\DOMWrapperVisitor.h"
                                >
                        </File>
index 0d1c494..0204222 100644 (file)
                7A24587C1021EAF4000A00AA /* InspectorDOMAgent.h in Headers */ = {isa = PBXBuildFile; fileRef = 7A24587A1021EAF4000A00AA /* InspectorDOMAgent.h */; settings = {ATTRIBUTES = (Private, ); }; };
                7A54857F14E02D51006AE05A /* InspectorHistory.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7A54857D14E02D51006AE05A /* InspectorHistory.cpp */; };
                7A54858014E02D51006AE05A /* InspectorHistory.h in Headers */ = {isa = PBXBuildFile; fileRef = 7A54857E14E02D51006AE05A /* InspectorHistory.h */; };
+               7A54881714E432A1006AE05A /* DOMPatchSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 7A54881514E432A1006AE05A /* DOMPatchSupport.h */; };
+               7A54881814E432A1006AE05A /* DOMPatchSupport.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7A54881614E432A1006AE05A /* DOMPatchSupport.cpp */; };
                7A674BDB0F9EBF4E006CF099 /* PageGroupLoadDeferrer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7A674BD90F9EBF4E006CF099 /* PageGroupLoadDeferrer.cpp */; };
                7A674BDC0F9EBF4E006CF099 /* PageGroupLoadDeferrer.h in Headers */ = {isa = PBXBuildFile; fileRef = 7A674BDA0F9EBF4E006CF099 /* PageGroupLoadDeferrer.h */; };
                7A74ECBA101839A600BF939E /* InspectorDOMStorageAgent.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7A74ECB8101839A500BF939E /* InspectorDOMStorageAgent.cpp */; };
                7A24587A1021EAF4000A00AA /* InspectorDOMAgent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InspectorDOMAgent.h; sourceTree = "<group>"; };
                7A54857D14E02D51006AE05A /* InspectorHistory.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = InspectorHistory.cpp; sourceTree = "<group>"; };
                7A54857E14E02D51006AE05A /* InspectorHistory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InspectorHistory.h; sourceTree = "<group>"; };
+               7A54881514E432A1006AE05A /* DOMPatchSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DOMPatchSupport.h; sourceTree = "<group>"; };
+               7A54881614E432A1006AE05A /* DOMPatchSupport.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DOMPatchSupport.cpp; sourceTree = "<group>"; };
                7A563E5412DE32B000F4536D /* InjectedScriptSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InjectedScriptSource.h; sourceTree = "<group>"; };
                7A563F9512DF5C9100F4536D /* InjectedScriptSource.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = InjectedScriptSource.js; sourceTree = "<group>"; };
                7A674BD90F9EBF4E006CF099 /* PageGroupLoadDeferrer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PageGroupLoadDeferrer.cpp; sourceTree = "<group>"; };
                                7AABA25814BC613300AA9A11 /* DOMEditor.h */,
                                4F1442261339FD6200E0D6F8 /* DOMNodeHighlighter.cpp */,
                                4F1442271339FD6200E0D6F8 /* DOMNodeHighlighter.h */,
+                               7A54881514E432A1006AE05A /* DOMPatchSupport.h */,
+                               7A54881614E432A1006AE05A /* DOMPatchSupport.cpp */,
                                F35AE5AB14925F5B004D5776 /* DOMWrapperVisitor.h */,
                                5913A23F13D49EBA00F5B05C /* IdentifiersFactory.cpp */,
                                5913A24013D49EBA00F5B05C /* IdentifiersFactory.h */,
                                1AAADDE914DC8DF800AF64B3 /* ScrollingTreeNodeMac.h in Headers */,
                                10FB084B14E15C7E00A3DB98 /* PublicURLManager.h in Headers */,
                                7A54858014E02D51006AE05A /* InspectorHistory.h in Headers */,
+                               7A54881714E432A1006AE05A /* DOMPatchSupport.h in Headers */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
                                1AAADDE814DC8DF800AF64B3 /* ScrollingTreeNodeMac.mm in Sources */,
                                7A54857F14E02D51006AE05A /* InspectorHistory.cpp in Sources */,
                                CDAA8D0A14D71B2E0061EA60 /* PlatformClockCM.mm in Sources */,
+                               7A54881814E432A1006AE05A /* DOMPatchSupport.cpp in Sources */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
index f72a5ce..f153c90 100644 (file)
 
 #if ENABLE(INSPECTOR)
 
-#include "Attribute.h"
-#include "Base64.h"
+#include "DOMPatchSupport.h"
 #include "Document.h"
-#include "DocumentFragment.h"
-#include "HTMLDocument.h"
-#include "HTMLDocumentParser.h"
-#include "HTMLElement.h"
-#include "HTMLHeadElement.h"
-#include "HTMLNames.h"
+#include "Element.h"
+#include "ExceptionCode.h"
+#include "InspectorHistory.h"
 #include "Node.h"
+#include "Text.h"
+
+#include "markup.h"
 
-#include <wtf/Deque.h>
 #include <wtf/RefPtr.h>
-#include <wtf/SHA1.h>
-#include <wtf/text/CString.h>
 
 using namespace std;
 
 namespace WebCore {
 
-using HTMLNames::bodyTag;
-using HTMLNames::headTag;
-using HTMLNames::htmlTag;
+class DOMEditor::DOMAction : public InspectorHistory::Action {
+public:
+    DOMAction(const String& name) : InspectorHistory::Action(name) { }
 
-struct DOMEditor::Digest {
-    explicit Digest(Node* node) : m_node(node) { }
+    virtual bool perform(ErrorString* errorString)
+    {
+        ExceptionCode ec = 0;
+        bool result = perform(ec);
+        if (ec) {
+            ExceptionCodeDescription description(ec);
+            *errorString = description.name;
+        }
+        return result && !ec;
+    }
 
-    String m_sha1;
-    String m_attrsSHA1;
-    Node* m_node;
-    Vector<OwnPtr<Digest> > m_children;
-};
+    virtual bool undo(ErrorString* errorString)
+    {
+        ExceptionCode ec = 0;
+        bool result = undo(ec);
+        if (ec) {
+            ExceptionCodeDescription description(ec);
+            *errorString = description.name;
+        }
+        return result && !ec;
+    }
 
-DOMEditor::DOMEditor(Document* document) : m_document(document) { }
+    virtual bool perform(ExceptionCode&) = 0;
 
-DOMEditor::~DOMEditor() { }
+    virtual bool undo(ExceptionCode&) = 0;
 
-void DOMEditor::patchDocument(const String& markup)
-{
-    RefPtr<HTMLDocument> newDocument = HTMLDocument::create(0, KURL());
-    RefPtr<DocumentParser> parser = HTMLDocumentParser::create(newDocument.get(), false);
-    parser->insert(markup); // Use insert() so that the parser will not yield.
-    parser->finish();
-    parser->detach();
-
-    ExceptionCode ec = 0;
-    OwnPtr<Digest> oldInfo = createDigest(m_document->documentElement(), 0);
-    OwnPtr<Digest> newInfo = createDigest(newDocument->documentElement(), &m_unusedNodesMap);
-    innerPatchNode(oldInfo.get(), newInfo.get(), ec);
-
-    if (ec) {
-        // Fall back to rewrite.
-        m_document->write(markup);
-        m_document->close();
-    }
-}
+private:
+    RefPtr<Node> m_parentNode;
+    RefPtr<Node> m_node;
+    RefPtr<Node> m_anchorNode;
+};
 
-Node* DOMEditor::patchNode(Node* node, const String& markup, ExceptionCode& ec)
-{
-    // Don't parse <html> as a fragment.
-    if (node->isDocumentNode() || (node->parentNode() && node->parentNode()->isDocumentNode())) {
-        patchDocument(markup);
-        return 0;
+class DOMEditor::RemoveChildAction : public DOMEditor::DOMAction {
+    WTF_MAKE_NONCOPYABLE(RemoveChildAction);
+public:
+    RemoveChildAction(Node* parentNode, Node* node)
+        : DOMEditor::DOMAction("RemoveChild")
+        , m_parentNode(parentNode)
+        , m_node(node)
+    {
     }
 
-    Node* previousSibling = node->previousSibling();
-    RefPtr<DocumentFragment> fragment = DocumentFragment::create(m_document);
-    fragment->parseHTML(markup, node->parentElement() ? node->parentElement() : m_document->documentElement());
-
-    // Compose the old list.
-    ContainerNode* parentNode = node->parentNode();
-    Vector<OwnPtr<Digest> > oldList;
-    for (Node* child = parentNode->firstChild(); child; child = child->nextSibling())
-        oldList.append(createDigest(child, 0));
-
-    // Compose the new list.
-    String markupCopy = markup;
-    markupCopy.makeLower();
-    Vector<OwnPtr<Digest> > newList;
-    for (Node* child = parentNode->firstChild(); child != node; child = child->nextSibling())
-        newList.append(createDigest(child, 0));
-    for (Node* child = fragment->firstChild(); child; child = child->nextSibling()) {
-        if (child->hasTagName(headTag) && !child->firstChild() && markupCopy.find("</head>") == notFound)
-            continue; // HTML5 parser inserts empty <head> tag whenever it parses <body>
-        if (child->hasTagName(bodyTag) && !child->firstChild() && markupCopy.find("</body>") == notFound)
-            continue; // HTML5 parser inserts empty <body> tag whenever it parses </head>
-        newList.append(createDigest(child, &m_unusedNodesMap));
+    virtual bool perform(ExceptionCode& ec)
+    {
+        m_anchorNode = m_node->nextSibling();
+        return m_parentNode->removeChild(m_node.get(), ec);
     }
-    for (Node* child = node->nextSibling(); child; child = child->nextSibling())
-        newList.append(createDigest(child, 0));
-
-    innerPatchChildren(parentNode, oldList, newList, ec);
-    if (ec) {
-        // Fall back to total replace.
-        ec = 0;
-        parentNode->replaceChild(fragment.release(), node, ec);
-        if (ec)
-            return 0;
-    }
-    return previousSibling ? previousSibling->nextSibling() : parentNode->firstChild();
-}
 
-void DOMEditor::innerPatchNode(Digest* oldDigest, Digest* newDigest, ExceptionCode& ec)
-{
-    if (oldDigest->m_sha1 == newDigest->m_sha1)
-        return;
+    virtual bool undo(ExceptionCode& ec)
+    {
+        return m_parentNode->insertBefore(m_node.get(), m_anchorNode.get(), ec);
+    }
 
-    Node* oldNode = oldDigest->m_node;
-    Node* newNode = newDigest->m_node;
+private:
+    RefPtr<Node> m_parentNode;
+    RefPtr<Node> m_node;
+    RefPtr<Node> m_anchorNode;
+};
 
-    if (newNode->nodeType() != oldNode->nodeType() || newNode->nodeName() != oldNode->nodeName()) {
-        oldNode->parentNode()->replaceChild(newNode, oldNode, ec);
-        return;
+class DOMEditor::InsertBeforeAction : public DOMEditor::DOMAction {
+    WTF_MAKE_NONCOPYABLE(InsertBeforeAction);
+public:
+    InsertBeforeAction(Node* parentNode, Node* node, Node* anchorNode)
+        : DOMEditor::DOMAction("InsertBefore")
+        , m_parentNode(parentNode)
+        , m_node(node)
+        , m_anchorNode(anchorNode)
+    {
     }
 
-    if (oldNode->nodeValue() != newNode->nodeValue())
-        oldNode->setNodeValue(newNode->nodeValue(), ec);
-    if (ec)
-        return;
-
-    if (oldNode->nodeType() != Node::ELEMENT_NODE)
-        return;
-
-    // Patch attributes
-    Element* oldElement = static_cast<Element*>(oldNode);
-    Element* newElement = static_cast<Element*>(newNode);
-    if (oldDigest->m_attrsSHA1 != newDigest->m_attrsSHA1) {
-        // FIXME: Create a function in Element for removing all properties. Take in account whether did/willModifyAttribute are important.
-        if (oldElement->hasAttributesWithoutUpdate()) {
-            while (oldElement->attributeCount())
-                oldElement->removeAttribute(0);
-        }
-
-        // FIXME: Create a function in Element for copying properties. setAttributesFromElement() is close but not enough for this case.
-        if (newElement->hasAttributesWithoutUpdate()) {
-            size_t numAttrs = newElement->attributeCount();
-            for (size_t i = 0; i < numAttrs; ++i) {
-                const Attribute* attribute = newElement->attributeItem(i);
-                oldElement->setAttribute(attribute->name(), attribute->value());
-            }
+    virtual bool perform(ExceptionCode& ec)
+    {
+        if (m_node->parentNode()) {
+            m_removeChildAction = adoptPtr(new RemoveChildAction(m_node->parentNode(), m_node.get()));
+            if (!m_removeChildAction->perform(ec))
+                return false;
         }
+        return m_parentNode->insertBefore(m_node.get(), m_anchorNode.get(), ec);
     }
 
-    innerPatchChildren(oldElement, oldDigest->m_children, newDigest->m_children, ec);
-    m_unusedNodesMap.remove(newDigest->m_sha1);
-}
-
-pair<DOMEditor::ResultMap, DOMEditor::ResultMap>
-DOMEditor::diff(const Vector<OwnPtr<Digest> >& oldList, const Vector<OwnPtr<Digest> >& newList)
-{
-    ResultMap newMap(newList.size());
-    ResultMap oldMap(oldList.size());
+    virtual bool undo(ExceptionCode& ec)
+    {
+        if (m_removeChildAction)
+            return m_removeChildAction->undo(ec);
 
-    for (size_t i = 0; i < oldMap.size(); ++i) {
-        oldMap[i].first = 0;
-        oldMap[i].second = 0;
+        return m_parentNode->removeChild(m_node.get(), ec);
     }
 
-    for (size_t i = 0; i < newMap.size(); ++i) {
-        newMap[i].first = 0;
-        newMap[i].second = 0;
-    }
+private:
+    RefPtr<Node> m_parentNode;
+    RefPtr<Node> m_node;
+    RefPtr<Node> m_anchorNode;
+    OwnPtr<RemoveChildAction> m_removeChildAction;
+};
 
-    // Trim head and tail.
-    for (size_t i = 0; i < oldList.size() && i < newList.size() && oldList[i]->m_sha1 == newList[i]->m_sha1; ++i) {
-        oldMap[i].first = oldList[i].get();
-        oldMap[i].second = i;
-        newMap[i].first = newList[i].get();
-        newMap[i].second = i;
+class DOMEditor::RemoveAttributeAction : public DOMEditor::DOMAction {
+    WTF_MAKE_NONCOPYABLE(RemoveAttributeAction);
+public:
+    RemoveAttributeAction(Element* element, const String& name)
+        : DOMEditor::DOMAction("RemoveAttribute")
+        , m_element(element)
+        , m_name(name)
+    {
     }
-    for (size_t i = 0; i < oldList.size() && i < newList.size() && oldList[oldList.size() - i - 1]->m_sha1 == newList[newList.size() - i - 1]->m_sha1; ++i) {
-        size_t oldIndex = oldList.size() - i - 1;
-        size_t newIndex = newList.size() - i - 1;
-        oldMap[oldIndex].first = oldList[oldIndex].get();
-        oldMap[oldIndex].second = newIndex;
-        newMap[newIndex].first = newList[newIndex].get();
-        newMap[newIndex].second = oldIndex;
-    }
-
-    typedef HashMap<String, Vector<size_t> > DiffTable;
-    DiffTable newTable;
-    DiffTable oldTable;
 
-    for (size_t i = 0; i < newList.size(); ++i) {
-        DiffTable::iterator it = newTable.add(newList[i]->m_sha1, Vector<size_t>()).first;
-        it->second.append(i);
+    virtual bool perform(ExceptionCode&)
+    {
+        m_value = m_element->getAttribute(m_name);
+        m_element->removeAttribute(m_name);
+        return true;
     }
 
-    for (size_t i = 0; i < oldList.size(); ++i) {
-        DiffTable::iterator it = oldTable.add(oldList[i]->m_sha1, Vector<size_t>()).first;
-        it->second.append(i);
+    virtual bool undo(ExceptionCode& ec)
+    {
+        m_element->setAttribute(m_name, m_value, ec);
+        return true;
     }
 
-    for (DiffTable::iterator newIt = newTable.begin(); newIt != newTable.end(); ++newIt) {
-        if (newIt->second.size() != 1)
-            continue;
-
-        DiffTable::iterator oldIt = oldTable.find(newIt->first);
-        if (oldIt == oldTable.end() || oldIt->second.size() != 1)
-            continue;
+private:
+    RefPtr<Element> m_element;
+    String m_name;
+    String m_value;
+};
 
-        newMap[newIt->second[0]] = make_pair(newList[newIt->second[0]].get(), oldIt->second[0]);
-        oldMap[oldIt->second[0]] = make_pair(oldList[oldIt->second[0]].get(), newIt->second[0]);
+class DOMEditor::SetAttributeAction : public DOMEditor::DOMAction {
+    WTF_MAKE_NONCOPYABLE(SetAttributeAction);
+public:
+    SetAttributeAction(Element* element, const String& name, const String& value)
+        : DOMEditor::DOMAction("SetAttribute")
+        , m_element(element)
+        , m_name(name)
+        , m_value(value)
+        , m_hadAttribute(false)
+    {
     }
 
-    for (size_t i = 0; newList.size() > 0 && i < newList.size() - 1; ++i) {
-        if (!newMap[i].first || newMap[i + 1].first)
-            continue;
-
-        size_t j = newMap[i].second + 1;
-        if (j < oldMap.size() && !oldMap[j].first && newList[i + 1]->m_sha1 == oldList[j]->m_sha1) {
-            newMap[i + 1] = make_pair(newList[i + 1].get(), j);
-            oldMap[j] = make_pair(oldList[j].get(), i + 1);
-        }
+    virtual bool perform(ExceptionCode& ec)
+    {
+        m_hadAttribute = m_element->hasAttribute(m_name);
+        if (m_hadAttribute)
+            m_oldValue = m_element->getAttribute(m_name);
+        m_element->setAttribute(m_name, m_value, ec);
+        return !ec;
     }
 
-    for (size_t i = newList.size() - 1; newList.size() > 0 && i > 0; --i) {
-        if (!newMap[i].first || newMap[i - 1].first || newMap[i].second <= 0)
-            continue;
-
-        size_t j = newMap[i].second - 1;
-        if (!oldMap[j].first && newList[i - 1]->m_sha1 == oldList[j]->m_sha1) {
-            newMap[i - 1] = make_pair(newList[i - 1].get(), j);
-            oldMap[j] = make_pair(oldList[j].get(), i - 1);
-        }
+    virtual bool undo(ExceptionCode& ec)
+    {
+        if (m_hadAttribute)
+            m_element->setAttribute(m_name, m_oldValue, ec);
+        else
+            m_element->removeAttribute(m_name);
+        return true;
     }
 
-#ifdef DEBUG_DOM_EDITOR
-    dumpMap(oldMap, "OLD");
-    dumpMap(newMap, "NEW");
-#endif
-
-    return make_pair(oldMap, newMap);
-}
-
-void DOMEditor::innerPatchChildren(ContainerNode* parentNode, const Vector<OwnPtr<Digest> >& oldList, const Vector<OwnPtr<Digest> >& newList, ExceptionCode& ec)
-{
-    pair<ResultMap, ResultMap> resultMaps = diff(oldList, newList);
-    ResultMap& oldMap = resultMaps.first;
-    ResultMap& newMap = resultMaps.second;
-
-    Digest* oldHead = 0;
-    Digest* oldBody = 0;
-
-    // 1. First strip everything except for the nodes that retain. Collect pending merges.
-    HashMap<Digest*, Digest*> merges;
-    HashSet<size_t> usedNewOrdinals;
-    for (size_t i = 0; i < oldList.size(); ++i) {
-        if (oldMap[i].first) {
-            if (!usedNewOrdinals.contains(oldMap[i].second)) {
-                usedNewOrdinals.add(oldMap[i].second);
-                continue;
-            }
-            oldMap[i].first = 0;
-            oldMap[i].second = 0;
-        }
-
-        // Always match <head> and <body> tags with each other - we can't remove them from the DOM
-        // upon patching.
-        if (oldList[i]->m_node->hasTagName(headTag)) {
-            oldHead = oldList[i].get();
-            continue;
-        }
-        if (oldList[i]->m_node->hasTagName(bodyTag)) {
-            oldBody = oldList[i].get();
-            continue;
-        }
+private:
+    RefPtr<Element> m_element;
+    String m_name;
+    String m_value;
+    bool m_hadAttribute;
+    String m_oldValue;
+};
 
-        // Check if this change is between stable nodes. If it is, consider it as "modified".
-        if (!m_unusedNodesMap.contains(oldList[i]->m_sha1) && (!i || oldMap[i - 1].first) && (i == oldMap.size() - 1 || oldMap[i + 1].first)) {
-            size_t anchorCandidate = i ? oldMap[i - 1].second + 1 : 0;
-            size_t anchorAfter = i == oldMap.size() - 1 ? anchorCandidate + 1 : oldMap[i + 1].second;
-            if (anchorAfter - anchorCandidate == 1 && anchorCandidate < newList.size())
-                merges.set(newList[anchorCandidate].get(), oldList[i].get());
-            else {
-                removeChild(oldList[i].get(), ec);
-                if (ec)
-                    return;
-            }
-        } else {
-            removeChild(oldList[i].get(), ec);
-            if (ec)
-                return;
-        }
+class DOMEditor::SetOuterHTMLAction : public DOMEditor::DOMAction {
+    WTF_MAKE_NONCOPYABLE(SetOuterHTMLAction);
+public:
+    SetOuterHTMLAction(Node* node, const String& html)
+        : DOMEditor::DOMAction("SetOuterHTML")
+        , m_node(node)
+        , m_nextSibling(node->nextSibling())
+        , m_html(html)
+        , m_newNode(0)
+    {
     }
 
-    // Mark retained nodes as used, do not reuse node more than once.
-    HashSet<size_t> usedOldOrdinals;
-    for (size_t i = 0; i < newList.size(); ++i) {
-        if (!newMap[i].first)
-            continue;
-        size_t oldOrdinal = newMap[i].second;
-        if (usedOldOrdinals.contains(oldOrdinal)) {
-            // Do not map node more than once
-            newMap[i].first = 0;
-            newMap[i].second = 0;
-            continue;
-        }
-        usedOldOrdinals.add(oldOrdinal);
-        markNodeAsUsed(newMap[i].first);
+    virtual bool perform(ExceptionCode& ec)
+    {
+        m_oldHTML = createMarkup(m_node.get());
+        DOMPatchSupport domPatchSupport(m_node->ownerDocument());
+        m_newNode = domPatchSupport.patchNode(m_node.get(), m_html, ec);
+        return !ec;
     }
 
-    // Mark <head> and <body> nodes for merge.
-    if (oldHead || oldBody) {
-        for (size_t i = 0; i < newList.size(); ++i) {
-            if (oldHead && newList[i]->m_node->hasTagName(headTag))
-                merges.set(newList[i].get(), oldHead);
-            if (oldBody && newList[i]->m_node->hasTagName(bodyTag))
-                merges.set(newList[i].get(), oldBody);
+    virtual bool undo(ExceptionCode& ec)
+    {
+        DOMPatchSupport domPatchSupport(m_newNode->ownerDocument());
+        Node* node = domPatchSupport.patchNode(m_newNode, m_oldHTML, ec);
+        if (ec || !node)
+            return false;
+        // HTML editing could have produced extra nodes. Remove them if necessary.
+        node = node->nextSibling();
+
+        while (!ec && node && node != m_nextSibling.get()) {
+            Node* nodeToRemove = node;
+            node = node->nextSibling();
+            nodeToRemove->remove(ec);
         }
+        return !ec;
     }
 
-    // 2. Patch nodes marked for merge.
-    for (HashMap<Digest*, Digest*>::iterator it = merges.begin(); it != merges.end(); ++it) {
-        innerPatchNode(it->second, it->first, ec);
-        if (ec)
-            return;
+    Node* newNode()
+    {
+        return m_newNode;
     }
 
-    // 3. Insert missing nodes.
-    for (size_t i = 0; i < newMap.size(); ++i) {
-        if (newMap[i].first || merges.contains(newList[i].get()))
-            continue;
+private:
+    RefPtr<Node> m_node;
+    RefPtr<Node> m_nextSibling;
+    String m_html;
+    String m_oldHTML;
+    Node* m_newNode;
+};
 
-        ExceptionCode ec = 0;
-        insertBefore(parentNode, newList[i].get(), parentNode->childNode(i), ec);
-        if (ec)
-            return;
+class DOMEditor::ReplaceWholeTextAction : public DOMEditor::DOMAction {
+    WTF_MAKE_NONCOPYABLE(ReplaceWholeTextAction);
+public:
+    ReplaceWholeTextAction(Text* textNode, const String& text)
+        : DOMAction("ReplaceWholeText")
+        , m_textNode(textNode)
+        , m_text(text)
+    {
     }
 
-    // 4. Then put all nodes that retained into their slots (sort by new index).
-    for (size_t i = 0; i < oldMap.size(); ++i) {
-        if (!oldMap[i].first)
-            continue;
-        RefPtr<Node> node = oldMap[i].first->m_node;
-        Node* anchorNode = parentNode->childNode(oldMap[i].second);
-        if (node.get() == anchorNode)
-            continue;
-        if (node->hasTagName(bodyTag) || node->hasTagName(headTag))
-            continue; // Never move head or body, move the rest of the nodes around them.
-
-        parentNode->insertBefore(node, anchorNode, ec);
-        if (ec)
-            return;
+    virtual bool perform(ExceptionCode& ec)
+    {
+        m_oldText = m_textNode->wholeText();
+        m_textNode->replaceWholeText(m_text, ec);
+        return true;
     }
-}
 
-static void addStringToSHA1(SHA1& sha1, const String& string)
-{
-    CString cString = string.utf8();
-    sha1.addBytes(reinterpret_cast<const uint8_t*>(cString.data()), cString.length());
-}
-
-PassOwnPtr<DOMEditor::Digest> DOMEditor::createDigest(Node* node, UnusedNodesMap* unusedNodesMap)
-{
-    Digest* digest = new Digest(node);
-
-    SHA1 sha1;
-
-    Node::NodeType nodeType = node->nodeType();
-    sha1.addBytes(reinterpret_cast<const uint8_t*>(&nodeType), sizeof(nodeType));
-    addStringToSHA1(sha1, node->nodeName());
-    addStringToSHA1(sha1, node->nodeValue());
-
-    if (node->nodeType() == Node::ELEMENT_NODE) {
-        Node* child = node->firstChild();
-        while (child) {
-            OwnPtr<Digest> childInfo = createDigest(child, unusedNodesMap);
-            addStringToSHA1(sha1, childInfo->m_sha1);
-            child = child->nextSibling();
-            digest->m_children.append(childInfo.release());
-        }
-        Element* element = static_cast<Element*>(node);
-
-        if (element->hasAttributesWithoutUpdate()) {
-            size_t numAttrs = element->attributeCount();
-            SHA1 attrsSHA1;
-            for (size_t i = 0; i < numAttrs; ++i) {
-                const Attribute* attribute = element->attributeItem(i);
-                addStringToSHA1(attrsSHA1, attribute->name().toString());
-                addStringToSHA1(attrsSHA1, attribute->value());
-            }
-            Vector<uint8_t, 20> attrsHash;
-            attrsSHA1.computeHash(attrsHash);
-            digest->m_attrsSHA1 = base64Encode(reinterpret_cast<const char*>(attrsHash.data()), 10);
-            addStringToSHA1(sha1, digest->m_attrsSHA1);
-        }
+    virtual bool undo(ExceptionCode& ec)
+    {
+        m_textNode->replaceWholeText(m_oldText, ec);
+        return true;
     }
 
-    Vector<uint8_t, 20> hash;
-    sha1.computeHash(hash);
-    digest->m_sha1 = base64Encode(reinterpret_cast<const char*>(hash.data()), 10);
-    if (unusedNodesMap)
-        unusedNodesMap->add(digest->m_sha1, digest);
-    return adoptPtr(digest);
-}
+private:
+    RefPtr<Text> m_textNode;
+    String m_text;
+    String m_oldText;
+};
 
-void DOMEditor::insertBefore(ContainerNode* parentNode, Digest* digest, Node* anchor, ExceptionCode& ec)
+DOMEditor::DOMEditor(InspectorHistory* history) : m_history(history) { }
+
+DOMEditor::~DOMEditor() { }
+
+bool DOMEditor::insertBefore(Node* parentNode, Node* node, Node* anchorNode, ErrorString* errorString)
 {
-    parentNode->insertBefore(digest->m_node, anchor, ec);
-    markNodeAsUsed(digest);
+    return m_history->perform(adoptPtr(new InsertBeforeAction(parentNode, node, anchorNode)), errorString);
 }
 
-void DOMEditor::removeChild(Digest* oldDigest, ExceptionCode& ec)
+bool DOMEditor::removeChild(Node* parentNode, Node* node, ErrorString* errorString)
 {
-    RefPtr<Node> oldNode = oldDigest->m_node;
-    oldNode->parentNode()->removeChild(oldNode.get(), ec);
-
-    // Diff works within levels. In order not to lose the node identity when user
-    // prepends his HTML with "<div>" (i.e. all nodes are shifted to the next nested level),
-    // prior to dropping the original node on the floor, check whether new DOM has a digest
-    // with matching sha1. If it does, replace it with the original DOM chunk. Chances are
-    // high that it will get merged back into the original DOM during the further patching.
-
-    UnusedNodesMap::iterator it = m_unusedNodesMap.find(oldDigest->m_sha1);
-    if (it != m_unusedNodesMap.end()) {
-        Digest* newDigest = it->second;
-        Node* newNode = newDigest->m_node;
-        newNode->parentNode()->replaceChild(oldNode, newNode, ec);
-        newDigest->m_node = oldNode.get();
-        markNodeAsUsed(newDigest);
-        return;
-    }
+    return m_history->perform(adoptPtr(new RemoveChildAction(parentNode, node)), errorString);
+}
 
-    for (size_t i = 0; i < oldDigest->m_children.size(); ++i)
-        removeChild(oldDigest->m_children[i].get(), ec);
+bool DOMEditor::setAttribute(Element* element, const String& name, const String& value, ErrorString* errorString)
+{
+    return m_history->perform(adoptPtr(new SetAttributeAction(element, name, value)), errorString);
 }
 
-void DOMEditor::markNodeAsUsed(Digest* digest)
+bool DOMEditor::removeAttribute(Element* element, const String& name, ErrorString* errorString)
 {
-    Deque<Digest*> queue;
-    queue.append(digest);
-    while (!queue.isEmpty()) {
-        Digest* first = queue.takeFirst();
-        m_unusedNodesMap.remove(first->m_sha1);
-        for (size_t i = 0; i < first->m_children.size(); ++i)
-            queue.append(first->m_children[i].get());
-    }
+    return m_history->perform(adoptPtr(new RemoveAttributeAction(element, name)), errorString);
 }
 
-#ifdef DEBUG_DOM_EDITOR
-static String nodeName(Node* node)
+bool DOMEditor::setOuterHTML(Node* node, const String& html, Node** newNode, ErrorString* errorString)
 {
-    if (node->document()->isXHTMLDocument())
-         return node->nodeName();
-    return node->nodeName().lower();
+    OwnPtr<SetOuterHTMLAction> action = adoptPtr(new SetOuterHTMLAction(node, html));
+    SetOuterHTMLAction* rawAction = action.get();
+    bool result = m_history->perform(action.release(), errorString);
+    if (result)
+        *newNode = rawAction->newNode();
+    return result;
 }
 
-void DOMEditor::dumpMap(const ResultMap& map, const String& name)
+bool DOMEditor::replaceWholeText(Text* textNode, const String& text, ErrorString* errorString)
 {
-    fprintf(stderr, "\n\n");
-    for (size_t i = 0; i < map.size(); ++i)
-        fprintf(stderr, "%s[%lu]: %s (%p) - [%lu]\n", name.utf8().data(), i, map[i].first ? nodeName(map[i].first->m_node).utf8().data() : "", map[i].first, map[i].second);
+    return m_history->perform(adoptPtr(new ReplaceWholeTextAction(textNode, text)), errorString);
 }
-#endif
 
 } // namespace WebCore
 
index 1644f23..bd10b2a 100644 (file)
 #ifndef DOMEditor_h
 #define DOMEditor_h
 
-#include "ExceptionCode.h"
-
-#include <wtf/HashMap.h>
-#include <wtf/OwnPtr.h>
-#include <wtf/PassOwnPtr.h>
-#include <wtf/Vector.h>
 #include <wtf/text/WTFString.h>
 
 namespace WebCore {
 
-class ContainerNode;
-class Document;
-class NamedNodeMap;
+class Element;
+class InspectorHistory;
 class Node;
+class Text;
 
 #if ENABLE(INSPECTOR)
 
+typedef String ErrorString;
+
 class DOMEditor {
+    WTF_MAKE_NONCOPYABLE(DOMEditor);
 public:
-    explicit DOMEditor(Document*);
-    virtual ~DOMEditor();
+    explicit DOMEditor(InspectorHistory*);
+    ~DOMEditor();
 
-    void patchDocument(const String& markup);
-    Node* patchNode(Node*, const String& markup, ExceptionCode&);
+    bool insertBefore(Node* parentNode, Node*, Node* anchorNode, ErrorString*);
+    bool removeChild(Node* parentNode, Node*, ErrorString*);
+    bool setAttribute(Element*, const String& name, const String& value, ErrorString*);
+    bool removeAttribute(Element*, const String& name, ErrorString*);
+    bool setOuterHTML(Node*, const String& html, Node** newNode, ErrorString*);
+    bool replaceWholeText(Text*, const String& text, ErrorString*);
 
 private:
-    struct Digest;
-    typedef Vector<pair<Digest*, size_t> > ResultMap;
-    typedef HashMap<String, Digest*> UnusedNodesMap;
-
-    void innerPatchNode(Digest* oldNode, Digest* newNode, ExceptionCode&);
-    std::pair<ResultMap, ResultMap> diff(const Vector<OwnPtr<Digest> >& oldChildren, const Vector<OwnPtr<Digest> >& newChildren);
-    void innerPatchChildren(ContainerNode*, const Vector<OwnPtr<Digest> >& oldChildren, const Vector<OwnPtr<Digest> >& newChildren, ExceptionCode&);
-    PassOwnPtr<Digest> createDigest(Node*, UnusedNodesMap*);
-    void insertBefore(ContainerNode*, Digest*, Node* anchor, ExceptionCode&);
-    void removeChild(Digest*, ExceptionCode&);
-    void markNodeAsUsed(Digest*);
-#ifdef DEBUG_DOM_EDITOR
-    void dumpMap(const ResultMap&, const String& name);
-#endif
-
-    Document* m_document;
+    class DOMAction;
+    class RemoveChildAction;
+    class InsertBeforeAction;
+    class RemoveAttributeAction;
+    class SetAttributeAction;
+    class SetOuterHTMLAction;
+    class ReplaceWholeTextAction;
 
-    UnusedNodesMap m_unusedNodesMap;
+    InspectorHistory* m_history;
 };
 
 #endif // ENABLE(INSPECTOR)
diff --git a/Source/WebCore/inspector/DOMPatchSupport.cpp b/Source/WebCore/inspector/DOMPatchSupport.cpp
new file mode 100644 (file)
index 0000000..41206fd
--- /dev/null
@@ -0,0 +1,494 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+#include "config.h"
+#include "DOMPatchSupport.h"
+
+#if ENABLE(INSPECTOR)
+
+#include "Attribute.h"
+#include "Base64.h"
+#include "Document.h"
+#include "DocumentFragment.h"
+#include "HTMLDocument.h"
+#include "HTMLDocumentParser.h"
+#include "HTMLElement.h"
+#include "HTMLHeadElement.h"
+#include "HTMLNames.h"
+#include "Node.h"
+
+#include <wtf/Deque.h>
+#include <wtf/RefPtr.h>
+#include <wtf/SHA1.h>
+#include <wtf/text/CString.h>
+
+using namespace std;
+
+namespace WebCore {
+
+using HTMLNames::bodyTag;
+using HTMLNames::headTag;
+using HTMLNames::htmlTag;
+
+struct DOMPatchSupport::Digest {
+    explicit Digest(Node* node) : m_node(node) { }
+
+    String m_sha1;
+    String m_attrsSHA1;
+    Node* m_node;
+    Vector<OwnPtr<Digest> > m_children;
+};
+
+DOMPatchSupport::DOMPatchSupport(Document* document) : m_document(document) { }
+
+DOMPatchSupport::~DOMPatchSupport() { }
+
+void DOMPatchSupport::patchDocument(const String& markup)
+{
+    RefPtr<HTMLDocument> newDocument = HTMLDocument::create(0, KURL());
+    RefPtr<DocumentParser> parser = HTMLDocumentParser::create(newDocument.get(), false);
+    parser->insert(markup); // Use insert() so that the parser will not yield.
+    parser->finish();
+    parser->detach();
+
+    ExceptionCode ec = 0;
+    OwnPtr<Digest> oldInfo = createDigest(m_document->documentElement(), 0);
+    OwnPtr<Digest> newInfo = createDigest(newDocument->documentElement(), &m_unusedNodesMap);
+    innerPatchNode(oldInfo.get(), newInfo.get(), ec);
+
+    if (ec) {
+        // Fall back to rewrite.
+        m_document->write(markup);
+        m_document->close();
+    }
+}
+
+Node* DOMPatchSupport::patchNode(Node* node, const String& markup, ExceptionCode& ec)
+{
+    // Don't parse <html> as a fragment.
+    if (node->isDocumentNode() || (node->parentNode() && node->parentNode()->isDocumentNode())) {
+        patchDocument(markup);
+        return 0;
+    }
+
+    Node* previousSibling = node->previousSibling();
+    RefPtr<DocumentFragment> fragment = DocumentFragment::create(m_document);
+    fragment->parseHTML(markup, node->parentElement() ? node->parentElement() : m_document->documentElement());
+
+    // Compose the old list.
+    ContainerNode* parentNode = node->parentNode();
+    Vector<OwnPtr<Digest> > oldList;
+    for (Node* child = parentNode->firstChild(); child; child = child->nextSibling())
+        oldList.append(createDigest(child, 0));
+
+    // Compose the new list.
+    String markupCopy = markup;
+    markupCopy.makeLower();
+    Vector<OwnPtr<Digest> > newList;
+    for (Node* child = parentNode->firstChild(); child != node; child = child->nextSibling())
+        newList.append(createDigest(child, 0));
+    for (Node* child = fragment->firstChild(); child; child = child->nextSibling()) {
+        if (child->hasTagName(headTag) && !child->firstChild() && markupCopy.find("</head>") == notFound)
+            continue; // HTML5 parser inserts empty <head> tag whenever it parses <body>
+        if (child->hasTagName(bodyTag) && !child->firstChild() && markupCopy.find("</body>") == notFound)
+            continue; // HTML5 parser inserts empty <body> tag whenever it parses </head>
+        newList.append(createDigest(child, &m_unusedNodesMap));
+    }
+    for (Node* child = node->nextSibling(); child; child = child->nextSibling())
+        newList.append(createDigest(child, 0));
+
+    innerPatchChildren(parentNode, oldList, newList, ec);
+    if (ec) {
+        // Fall back to total replace.
+        ec = 0;
+        parentNode->replaceChild(fragment.release(), node, ec);
+        if (ec)
+            return 0;
+    }
+    return previousSibling ? previousSibling->nextSibling() : parentNode->firstChild();
+}
+
+void DOMPatchSupport::innerPatchNode(Digest* oldDigest, Digest* newDigest, ExceptionCode& ec)
+{
+    if (oldDigest->m_sha1 == newDigest->m_sha1)
+        return;
+
+    Node* oldNode = oldDigest->m_node;
+    Node* newNode = newDigest->m_node;
+
+    if (newNode->nodeType() != oldNode->nodeType() || newNode->nodeName() != oldNode->nodeName()) {
+        oldNode->parentNode()->replaceChild(newNode, oldNode, ec);
+        return;
+    }
+
+    if (oldNode->nodeValue() != newNode->nodeValue())
+        oldNode->setNodeValue(newNode->nodeValue(), ec);
+    if (ec)
+        return;
+
+    if (oldNode->nodeType() != Node::ELEMENT_NODE)
+        return;
+
+    // Patch attributes
+    Element* oldElement = static_cast<Element*>(oldNode);
+    Element* newElement = static_cast<Element*>(newNode);
+    if (oldDigest->m_attrsSHA1 != newDigest->m_attrsSHA1) {
+        // FIXME: Create a function in Element for removing all properties. Take in account whether did/willModifyAttribute are important.
+        if (oldElement->hasAttributesWithoutUpdate()) {
+            while (oldElement->attributeCount())
+                oldElement->removeAttribute(0);
+        }
+
+        // FIXME: Create a function in Element for copying properties. setAttributesFromElement() is close but not enough for this case.
+        if (newElement->hasAttributesWithoutUpdate()) {
+            size_t numAttrs = newElement->attributeCount();
+            for (size_t i = 0; i < numAttrs; ++i) {
+                const Attribute* attribute = newElement->attributeItem(i);
+                oldElement->setAttribute(attribute->name(), attribute->value());
+            }
+        }
+    }
+
+    innerPatchChildren(oldElement, oldDigest->m_children, newDigest->m_children, ec);
+    m_unusedNodesMap.remove(newDigest->m_sha1);
+}
+
+pair<DOMPatchSupport::ResultMap, DOMPatchSupport::ResultMap>
+DOMPatchSupport::diff(const Vector<OwnPtr<Digest> >& oldList, const Vector<OwnPtr<Digest> >& newList)
+{
+    ResultMap newMap(newList.size());
+    ResultMap oldMap(oldList.size());
+
+    for (size_t i = 0; i < oldMap.size(); ++i) {
+        oldMap[i].first = 0;
+        oldMap[i].second = 0;
+    }
+
+    for (size_t i = 0; i < newMap.size(); ++i) {
+        newMap[i].first = 0;
+        newMap[i].second = 0;
+    }
+
+    // Trim head and tail.
+    for (size_t i = 0; i < oldList.size() && i < newList.size() && oldList[i]->m_sha1 == newList[i]->m_sha1; ++i) {
+        oldMap[i].first = oldList[i].get();
+        oldMap[i].second = i;
+        newMap[i].first = newList[i].get();
+        newMap[i].second = i;
+    }
+    for (size_t i = 0; i < oldList.size() && i < newList.size() && oldList[oldList.size() - i - 1]->m_sha1 == newList[newList.size() - i - 1]->m_sha1; ++i) {
+        size_t oldIndex = oldList.size() - i - 1;
+        size_t newIndex = newList.size() - i - 1;
+        oldMap[oldIndex].first = oldList[oldIndex].get();
+        oldMap[oldIndex].second = newIndex;
+        newMap[newIndex].first = newList[newIndex].get();
+        newMap[newIndex].second = oldIndex;
+    }
+
+    typedef HashMap<String, Vector<size_t> > DiffTable;
+    DiffTable newTable;
+    DiffTable oldTable;
+
+    for (size_t i = 0; i < newList.size(); ++i) {
+        DiffTable::iterator it = newTable.add(newList[i]->m_sha1, Vector<size_t>()).first;
+        it->second.append(i);
+    }
+
+    for (size_t i = 0; i < oldList.size(); ++i) {
+        DiffTable::iterator it = oldTable.add(oldList[i]->m_sha1, Vector<size_t>()).first;
+        it->second.append(i);
+    }
+
+    for (DiffTable::iterator newIt = newTable.begin(); newIt != newTable.end(); ++newIt) {
+        if (newIt->second.size() != 1)
+            continue;
+
+        DiffTable::iterator oldIt = oldTable.find(newIt->first);
+        if (oldIt == oldTable.end() || oldIt->second.size() != 1)
+            continue;
+
+        newMap[newIt->second[0]] = make_pair(newList[newIt->second[0]].get(), oldIt->second[0]);
+        oldMap[oldIt->second[0]] = make_pair(oldList[oldIt->second[0]].get(), newIt->second[0]);
+    }
+
+    for (size_t i = 0; newList.size() > 0 && i < newList.size() - 1; ++i) {
+        if (!newMap[i].first || newMap[i + 1].first)
+            continue;
+
+        size_t j = newMap[i].second + 1;
+        if (j < oldMap.size() && !oldMap[j].first && newList[i + 1]->m_sha1 == oldList[j]->m_sha1) {
+            newMap[i + 1] = make_pair(newList[i + 1].get(), j);
+            oldMap[j] = make_pair(oldList[j].get(), i + 1);
+        }
+    }
+
+    for (size_t i = newList.size() - 1; newList.size() > 0 && i > 0; --i) {
+        if (!newMap[i].first || newMap[i - 1].first || newMap[i].second <= 0)
+            continue;
+
+        size_t j = newMap[i].second - 1;
+        if (!oldMap[j].first && newList[i - 1]->m_sha1 == oldList[j]->m_sha1) {
+            newMap[i - 1] = make_pair(newList[i - 1].get(), j);
+            oldMap[j] = make_pair(oldList[j].get(), i - 1);
+        }
+    }
+
+#ifdef DEBUG_DOM_PATCH_SUPPORT
+    dumpMap(oldMap, "OLD");
+    dumpMap(newMap, "NEW");
+#endif
+
+    return make_pair(oldMap, newMap);
+}
+
+void DOMPatchSupport::innerPatchChildren(ContainerNode* parentNode, const Vector<OwnPtr<Digest> >& oldList, const Vector<OwnPtr<Digest> >& newList, ExceptionCode& ec)
+{
+    pair<ResultMap, ResultMap> resultMaps = diff(oldList, newList);
+    ResultMap& oldMap = resultMaps.first;
+    ResultMap& newMap = resultMaps.second;
+
+    Digest* oldHead = 0;
+    Digest* oldBody = 0;
+
+    // 1. First strip everything except for the nodes that retain. Collect pending merges.
+    HashMap<Digest*, Digest*> merges;
+    HashSet<size_t> usedNewOrdinals;
+    for (size_t i = 0; i < oldList.size(); ++i) {
+        if (oldMap[i].first) {
+            if (!usedNewOrdinals.contains(oldMap[i].second)) {
+                usedNewOrdinals.add(oldMap[i].second);
+                continue;
+            }
+            oldMap[i].first = 0;
+            oldMap[i].second = 0;
+        }
+
+        // Always match <head> and <body> tags with each other - we can't remove them from the DOM
+        // upon patching.
+        if (oldList[i]->m_node->hasTagName(headTag)) {
+            oldHead = oldList[i].get();
+            continue;
+        }
+        if (oldList[i]->m_node->hasTagName(bodyTag)) {
+            oldBody = oldList[i].get();
+            continue;
+        }
+
+        // Check if this change is between stable nodes. If it is, consider it as "modified".
+        if (!m_unusedNodesMap.contains(oldList[i]->m_sha1) && (!i || oldMap[i - 1].first) && (i == oldMap.size() - 1 || oldMap[i + 1].first)) {
+            size_t anchorCandidate = i ? oldMap[i - 1].second + 1 : 0;
+            size_t anchorAfter = i == oldMap.size() - 1 ? anchorCandidate + 1 : oldMap[i + 1].second;
+            if (anchorAfter - anchorCandidate == 1 && anchorCandidate < newList.size())
+                merges.set(newList[anchorCandidate].get(), oldList[i].get());
+            else {
+                removeChild(oldList[i].get(), ec);
+                if (ec)
+                    return;
+            }
+        } else {
+            removeChild(oldList[i].get(), ec);
+            if (ec)
+                return;
+        }
+    }
+
+    // Mark retained nodes as used, do not reuse node more than once.
+    HashSet<size_t> usedOldOrdinals;
+    for (size_t i = 0; i < newList.size(); ++i) {
+        if (!newMap[i].first)
+            continue;
+        size_t oldOrdinal = newMap[i].second;
+        if (usedOldOrdinals.contains(oldOrdinal)) {
+            // Do not map node more than once
+            newMap[i].first = 0;
+            newMap[i].second = 0;
+            continue;
+        }
+        usedOldOrdinals.add(oldOrdinal);
+        markNodeAsUsed(newMap[i].first);
+    }
+
+    // Mark <head> and <body> nodes for merge.
+    if (oldHead || oldBody) {
+        for (size_t i = 0; i < newList.size(); ++i) {
+            if (oldHead && newList[i]->m_node->hasTagName(headTag))
+                merges.set(newList[i].get(), oldHead);
+            if (oldBody && newList[i]->m_node->hasTagName(bodyTag))
+                merges.set(newList[i].get(), oldBody);
+        }
+    }
+
+    // 2. Patch nodes marked for merge.
+    for (HashMap<Digest*, Digest*>::iterator it = merges.begin(); it != merges.end(); ++it) {
+        innerPatchNode(it->second, it->first, ec);
+        if (ec)
+            return;
+    }
+
+    // 3. Insert missing nodes.
+    for (size_t i = 0; i < newMap.size(); ++i) {
+        if (newMap[i].first || merges.contains(newList[i].get()))
+            continue;
+
+        ExceptionCode ec = 0;
+        insertBefore(parentNode, newList[i].get(), parentNode->childNode(i), ec);
+        if (ec)
+            return;
+    }
+
+    // 4. Then put all nodes that retained into their slots (sort by new index).
+    for (size_t i = 0; i < oldMap.size(); ++i) {
+        if (!oldMap[i].first)
+            continue;
+        RefPtr<Node> node = oldMap[i].first->m_node;
+        Node* anchorNode = parentNode->childNode(oldMap[i].second);
+        if (node.get() == anchorNode)
+            continue;
+        if (node->hasTagName(bodyTag) || node->hasTagName(headTag))
+            continue; // Never move head or body, move the rest of the nodes around them.
+
+        parentNode->insertBefore(node, anchorNode, ec);
+        if (ec)
+            return;
+    }
+}
+
+static void addStringToSHA1(SHA1& sha1, const String& string)
+{
+    CString cString = string.utf8();
+    sha1.addBytes(reinterpret_cast<const uint8_t*>(cString.data()), cString.length());
+}
+
+PassOwnPtr<DOMPatchSupport::Digest> DOMPatchSupport::createDigest(Node* node, UnusedNodesMap* unusedNodesMap)
+{
+    Digest* digest = new Digest(node);
+
+    SHA1 sha1;
+
+    Node::NodeType nodeType = node->nodeType();
+    sha1.addBytes(reinterpret_cast<const uint8_t*>(&nodeType), sizeof(nodeType));
+    addStringToSHA1(sha1, node->nodeName());
+    addStringToSHA1(sha1, node->nodeValue());
+
+    if (node->nodeType() == Node::ELEMENT_NODE) {
+        Node* child = node->firstChild();
+        while (child) {
+            OwnPtr<Digest> childInfo = createDigest(child, unusedNodesMap);
+            addStringToSHA1(sha1, childInfo->m_sha1);
+            child = child->nextSibling();
+            digest->m_children.append(childInfo.release());
+        }
+        Element* element = static_cast<Element*>(node);
+
+        if (element->hasAttributesWithoutUpdate()) {
+            size_t numAttrs = element->attributeCount();
+            SHA1 attrsSHA1;
+            for (size_t i = 0; i < numAttrs; ++i) {
+                const Attribute* attribute = element->attributeItem(i);
+                addStringToSHA1(attrsSHA1, attribute->name().toString());
+                addStringToSHA1(attrsSHA1, attribute->value());
+            }
+            Vector<uint8_t, 20> attrsHash;
+            attrsSHA1.computeHash(attrsHash);
+            digest->m_attrsSHA1 = base64Encode(reinterpret_cast<const char*>(attrsHash.data()), 10);
+            addStringToSHA1(sha1, digest->m_attrsSHA1);
+        }
+    }
+
+    Vector<uint8_t, 20> hash;
+    sha1.computeHash(hash);
+    digest->m_sha1 = base64Encode(reinterpret_cast<const char*>(hash.data()), 10);
+    if (unusedNodesMap)
+        unusedNodesMap->add(digest->m_sha1, digest);
+    return adoptPtr(digest);
+}
+
+void DOMPatchSupport::insertBefore(ContainerNode* parentNode, Digest* digest, Node* anchor, ExceptionCode& ec)
+{
+    parentNode->insertBefore(digest->m_node, anchor, ec);
+    markNodeAsUsed(digest);
+}
+
+void DOMPatchSupport::removeChild(Digest* oldDigest, ExceptionCode& ec)
+{
+    RefPtr<Node> oldNode = oldDigest->m_node;
+    oldNode->parentNode()->removeChild(oldNode.get(), ec);
+
+    // Diff works within levels. In order not to lose the node identity when user
+    // prepends his HTML with "<div>" (i.e. all nodes are shifted to the next nested level),
+    // prior to dropping the original node on the floor, check whether new DOM has a digest
+    // with matching sha1. If it does, replace it with the original DOM chunk. Chances are
+    // high that it will get merged back into the original DOM during the further patching.
+
+    UnusedNodesMap::iterator it = m_unusedNodesMap.find(oldDigest->m_sha1);
+    if (it != m_unusedNodesMap.end()) {
+        Digest* newDigest = it->second;
+        Node* newNode = newDigest->m_node;
+        newNode->parentNode()->replaceChild(oldNode, newNode, ec);
+        newDigest->m_node = oldNode.get();
+        markNodeAsUsed(newDigest);
+        return;
+    }
+
+    for (size_t i = 0; i < oldDigest->m_children.size(); ++i)
+        removeChild(oldDigest->m_children[i].get(), ec);
+}
+
+void DOMPatchSupport::markNodeAsUsed(Digest* digest)
+{
+    Deque<Digest*> queue;
+    queue.append(digest);
+    while (!queue.isEmpty()) {
+        Digest* first = queue.takeFirst();
+        m_unusedNodesMap.remove(first->m_sha1);
+        for (size_t i = 0; i < first->m_children.size(); ++i)
+            queue.append(first->m_children[i].get());
+    }
+}
+
+#ifdef DEBUG_DOM_PATCH_SUPPORT
+static String nodeName(Node* node)
+{
+    if (node->document()->isXHTMLDocument())
+         return node->nodeName();
+    return node->nodeName().lower();
+}
+
+void DOMPatchSupport::dumpMap(const ResultMap& map, const String& name)
+{
+    fprintf(stderr, "\n\n");
+    for (size_t i = 0; i < map.size(); ++i)
+        fprintf(stderr, "%s[%lu]: %s (%p) - [%lu]\n", name.utf8().data(), i, map[i].first ? nodeName(map[i].first->m_node).utf8().data() : "", map[i].first, map[i].second);
+}
+#endif
+
+} // namespace WebCore
+
+#endif // ENABLE(INSPECTOR)
diff --git a/Source/WebCore/inspector/DOMPatchSupport.h b/Source/WebCore/inspector/DOMPatchSupport.h
new file mode 100644 (file)
index 0000000..4a07192
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+#ifndef DOMPatchSupport_h
+#define DOMPatchSupport_h
+
+#include "ExceptionCode.h"
+
+#include <wtf/HashMap.h>
+#include <wtf/OwnPtr.h>
+#include <wtf/PassOwnPtr.h>
+#include <wtf/Vector.h>
+#include <wtf/text/WTFString.h>
+
+namespace WebCore {
+
+class ContainerNode;
+class Document;
+class NamedNodeMap;
+class Node;
+
+#if ENABLE(INSPECTOR)
+
+class DOMPatchSupport {
+    WTF_MAKE_NONCOPYABLE(DOMPatchSupport);
+public:
+    explicit DOMPatchSupport(Document*);
+    virtual ~DOMPatchSupport();
+
+    void patchDocument(const String& markup);
+    Node* patchNode(Node*, const String& markup, ExceptionCode&);
+
+private:
+    struct Digest;
+    typedef Vector<pair<Digest*, size_t> > ResultMap;
+    typedef HashMap<String, Digest*> UnusedNodesMap;
+
+    void innerPatchNode(Digest* oldNode, Digest* newNode, ExceptionCode&);
+    std::pair<ResultMap, ResultMap> diff(const Vector<OwnPtr<Digest> >& oldChildren, const Vector<OwnPtr<Digest> >& newChildren);
+    void innerPatchChildren(ContainerNode*, const Vector<OwnPtr<Digest> >& oldChildren, const Vector<OwnPtr<Digest> >& newChildren, ExceptionCode&);
+    PassOwnPtr<Digest> createDigest(Node*, UnusedNodesMap*);
+    void insertBefore(ContainerNode*, Digest*, Node* anchor, ExceptionCode&);
+    void removeChild(Digest*, ExceptionCode&);
+    void markNodeAsUsed(Digest*);
+#ifdef DEBUG_DOM_PATCH_SUPPORT
+    void dumpMap(const ResultMap&, const String& name);
+#endif
+
+    Document* m_document;
+
+    UnusedNodesMap m_unusedNodesMap;
+};
+
+#endif // ENABLE(INSPECTOR)
+
+} // namespace WebCore
+
+#endif // !defined(DOMPatchSupport_h)
index 98185c1..d253525 100644 (file)
@@ -48,6 +48,7 @@
 #include "CookieJar.h"
 #include "DOMEditor.h"
 #include "DOMNodeHighlighter.h"
+#include "DOMPatchSupport.h"
 #include "DOMWindow.h"
 #include "Document.h"
 #include "DocumentFragment.h"
@@ -66,6 +67,7 @@
 #include "InjectedScriptManager.h"
 #include "InspectorClient.h"
 #include "InspectorFrontend.h"
+#include "InspectorHistory.h"
 #include "InspectorPageAgent.h"
 #include "InspectorState.h"
 #include "InstrumentingAgents.h"
@@ -177,251 +179,6 @@ void RevalidateStyleAttributeTask::onTimer(Timer<RevalidateStyleAttributeTask>*)
     m_elements.clear();
 }
 
-class InspectorDOMAgent::DOMAction : public InspectorHistory::Action {
-public:
-    DOMAction(const String& name) : InspectorHistory::Action(name) { }
-
-    virtual bool perform(ErrorString* errorString)
-    {
-        ExceptionCode ec = 0;
-        bool result = perform(ec);
-        if (ec) {
-            ExceptionCodeDescription description(ec);
-            *errorString = description.name;
-        }
-        return result && !ec;
-    }
-
-    virtual bool undo(ErrorString* errorString)
-    {
-        ExceptionCode ec = 0;
-        bool result = undo(ec);
-        if (ec) {
-            ExceptionCodeDescription description(ec);
-            *errorString = description.name;
-        }
-        return result && !ec;
-    }
-
-    virtual bool perform(ExceptionCode&) = 0;
-
-    virtual bool undo(ExceptionCode&) = 0;
-
-private:
-    RefPtr<Node> m_parentNode;
-    RefPtr<Node> m_node;
-    RefPtr<Node> m_anchorNode;
-};
-
-class InspectorDOMAgent::RemoveChildAction : public InspectorDOMAgent::DOMAction {
-    WTF_MAKE_NONCOPYABLE(RemoveChildAction);
-public:
-    RemoveChildAction(Node* parentNode, Node* node)
-        : InspectorDOMAgent::DOMAction("RemoveChild")
-        , m_parentNode(parentNode)
-        , m_node(node)
-    {
-    }
-
-    virtual bool perform(ExceptionCode& ec)
-    {
-        m_anchorNode = m_node->nextSibling();
-        return m_parentNode->removeChild(m_node.get(), ec);
-    }
-
-    virtual bool undo(ExceptionCode& ec)
-    {
-        return m_parentNode->insertBefore(m_node.get(), m_anchorNode.get(), ec);
-    }
-
-private:
-    RefPtr<Node> m_parentNode;
-    RefPtr<Node> m_node;
-    RefPtr<Node> m_anchorNode;
-};
-
-class InspectorDOMAgent::InsertBeforeAction : public InspectorDOMAgent::DOMAction {
-    WTF_MAKE_NONCOPYABLE(InsertBeforeAction);
-public:
-    InsertBeforeAction(Node* parentNode, Node* node, Node* anchorNode)
-        : InspectorDOMAgent::DOMAction("InsertBefore")
-        , m_parentNode(parentNode)
-        , m_node(node)
-        , m_anchorNode(anchorNode)
-    {
-    }
-
-    virtual bool perform(ExceptionCode& ec)
-    {
-        if (m_node->parentNode()) {
-            m_removeChildAction = adoptPtr(new RemoveChildAction(m_node->parentNode(), m_node.get()));
-            if (!m_removeChildAction->perform(ec))
-                return false;
-        }
-        return m_parentNode->insertBefore(m_node.get(), m_anchorNode.get(), ec);
-    }
-
-    virtual bool undo(ExceptionCode& ec)
-    {
-        if (m_removeChildAction)
-            return m_removeChildAction->undo(ec);
-
-        return m_parentNode->removeChild(m_node.get(), ec);
-    }
-
-private:
-    RefPtr<Node> m_parentNode;
-    RefPtr<Node> m_node;
-    RefPtr<Node> m_anchorNode;
-    OwnPtr<RemoveChildAction> m_removeChildAction;
-};
-
-class InspectorDOMAgent::RemoveAttributeAction : public InspectorDOMAgent::DOMAction {
-    WTF_MAKE_NONCOPYABLE(RemoveAttributeAction);
-public:
-    RemoveAttributeAction(Element* element, const String& name)
-        : InspectorDOMAgent::DOMAction("RemoveAttribute")
-        , m_element(element)
-        , m_name(name)
-    {
-    }
-
-    virtual bool perform(ExceptionCode&)
-    {
-        m_value = m_element->getAttribute(m_name);
-        m_element->removeAttribute(m_name);
-        return true;
-    }
-
-    virtual bool undo(ExceptionCode& ec)
-    {
-        m_element->setAttribute(m_name, m_value, ec);
-        return true;
-    }
-
-private:
-    RefPtr<Element> m_element;
-    String m_name;
-    String m_value;
-};
-
-class InspectorDOMAgent::SetAttributeAction : public InspectorDOMAgent::DOMAction {
-    WTF_MAKE_NONCOPYABLE(SetAttributeAction);
-public:
-    SetAttributeAction(Element* element, const String& name, const String& value)
-        : InspectorDOMAgent::DOMAction("SetAttribute")
-        , m_element(element)
-        , m_name(name)
-        , m_value(value)
-        , m_hadAttribute(false)
-    {
-    }
-
-    virtual bool perform(ExceptionCode& ec)
-    {
-        m_hadAttribute = m_element->hasAttribute(m_name);
-        if (m_hadAttribute)
-            m_oldValue = m_element->getAttribute(m_name);
-        m_element->setAttribute(m_name, m_value, ec);
-        return !ec;
-    }
-
-    virtual bool undo(ExceptionCode& ec)
-    {
-        if (m_hadAttribute)
-            m_element->setAttribute(m_name, m_oldValue, ec);
-        else
-            m_element->removeAttribute(m_name);
-        return true;
-    }
-
-private:
-    RefPtr<Element> m_element;
-    String m_name;
-    String m_value;
-    bool m_hadAttribute;
-    String m_oldValue;
-};
-
-class InspectorDOMAgent::SetOuterHTMLAction : public InspectorDOMAgent::DOMAction {
-    WTF_MAKE_NONCOPYABLE(SetOuterHTMLAction);
-public:
-    SetOuterHTMLAction(Node* node, const String& html)
-        : InspectorDOMAgent::DOMAction("SetOuterHTML")
-        , m_node(node)
-        , m_nextSibling(node->nextSibling())
-        , m_html(html)
-        , m_newNode(0)
-    {
-    }
-
-    virtual bool perform(ExceptionCode& ec)
-    {
-        m_oldHTML = createMarkup(m_node.get());
-        DOMEditor domEditor(m_node->ownerDocument());
-        m_newNode = domEditor.patchNode(m_node.get(), m_html, ec);
-        return !ec;
-    }
-
-    virtual bool undo(ExceptionCode& ec)
-    {
-        DOMEditor domEditor(m_newNode->ownerDocument());
-        Node* node = domEditor.patchNode(m_newNode, m_oldHTML, ec);
-        if (ec || !node)
-            return false;
-        // HTML editing could have produced extra nodes. Remove them if necessary.
-        node = node->nextSibling();
-
-        while (!ec && node && node != m_nextSibling.get()) {
-            Node* nodeToRemove = node;
-            node = node->nextSibling();
-            nodeToRemove->remove(ec);
-        }
-        return !ec;
-    }
-
-    Node* newNode()
-    {
-        return m_newNode;
-    }
-
-private:
-    RefPtr<Node> m_node;
-    RefPtr<Node> m_nextSibling;
-    String m_html;
-    String m_oldHTML;
-    Node* m_newNode;
-};
-
-class InspectorDOMAgent::ReplaceWholeTextAction : public InspectorDOMAgent::DOMAction {
-    WTF_MAKE_NONCOPYABLE(ReplaceWholeTextAction);
-public:
-    ReplaceWholeTextAction(Text* textNode, const String& text)
-        : DOMAction("ReplaceWholeText")
-        , m_textNode(textNode)
-        , m_text(text)
-    {
-    }
-
-    virtual bool perform(ExceptionCode& ec)
-    {
-        m_oldText = m_textNode->wholeText();
-        m_textNode->replaceWholeText(m_text, ec);
-        return true;
-    }
-
-    virtual bool undo(ExceptionCode& ec)
-    {
-        m_textNode->replaceWholeText(m_oldText, ec);
-        return true;
-    }
-
-private:
-    RefPtr<Text> m_textNode;
-    String m_text;
-    String m_oldText;
-};
-
 InspectorDOMAgent::InspectorDOMAgent(InstrumentingAgents* instrumentingAgents, InspectorPageAgent* pageAgent, InspectorClient* client, InspectorState* inspectorState, InjectedScriptManager* injectedScriptManager)
     : InspectorBaseAgent<InspectorDOMAgent>("DOM", instrumentingAgents, inspectorState)
     , m_pageAgent(pageAgent)
@@ -431,7 +188,6 @@ InspectorDOMAgent::InspectorDOMAgent(InstrumentingAgents* instrumentingAgents, I
     , m_domListener(0)
     , m_lastNodeId(1)
     , m_searchingForNode(false)
-    , m_history(adoptPtr(new InspectorHistory()))
 {
 }
 
@@ -445,6 +201,9 @@ InspectorDOMAgent::~InspectorDOMAgent()
 void InspectorDOMAgent::setFrontend(InspectorFrontend* frontend)
 {
     ASSERT(!m_frontend);
+    m_history = adoptPtr(new InspectorHistory());
+    m_domEditor = adoptPtr(new DOMEditor(m_history.get()));
+
     m_frontend = frontend->dom();
     m_instrumentingAgents->setInspectorDOMAgent(this);
     m_document = m_pageAgent->mainFrame()->document();
@@ -456,6 +215,9 @@ void InspectorDOMAgent::setFrontend(InspectorFrontend* frontend)
 void InspectorDOMAgent::clearFrontend()
 {
     ASSERT(m_frontend);
+
+    m_history.clear();
+    m_domEditor.clear();
     setSearchingForNode(false, 0);
 
     ErrorString error;
@@ -499,7 +261,8 @@ Node* InspectorDOMAgent::highlightedNode() const
 
 void InspectorDOMAgent::reset()
 {
-    m_history->reset();
+    if (m_history)
+        m_history->reset();
     m_searchResults.clear();
     discardBindings();
     if (m_revalidateStyleAttrTask)
@@ -765,7 +528,7 @@ void InspectorDOMAgent::setAttributeValue(ErrorString* errorString, int elementI
     if (!element)
         return;
 
-    m_history->perform(adoptPtr(new SetAttributeAction(element, name, value)), errorString);
+    m_domEditor->setAttribute(element, name, value, errorString);
     m_history->markUndoableState();
 }
 
@@ -796,7 +559,8 @@ void InspectorDOMAgent::setAttributesAsText(ErrorString* errorString, int elemen
 
     Element* childElement = toElement(child);
     if (!childElement->hasAttributes() && name) {
-        m_history->perform(adoptPtr(new RemoveAttributeAction(element, *name)), errorString);
+        m_domEditor->removeAttribute(element, *name, errorString);
+        m_history->markUndoableState();
         return;
     }
 
@@ -806,12 +570,12 @@ void InspectorDOMAgent::setAttributesAsText(ErrorString* errorString, int elemen
         // Add attribute pair
         const Attribute* attribute = childElement->attributeItem(i);
         foundOriginalAttribute = foundOriginalAttribute || (name && attribute->name().toString() == *name);
-        if (!m_history->perform(adoptPtr(new SetAttributeAction(element, attribute->name().toString(), attribute->value())), errorString))
+        if (!m_domEditor->setAttribute(element, attribute->name().toString(), attribute->value(), errorString))
             return;
     }
 
     if (!foundOriginalAttribute && name && !name->stripWhiteSpace().isEmpty())
-        m_history->perform(adoptPtr(new RemoveAttributeAction(element, *name)), errorString);
+        m_domEditor->removeAttribute(element, *name, errorString);
 
     m_history->markUndoableState();
 }
@@ -822,7 +586,7 @@ void InspectorDOMAgent::removeAttribute(ErrorString* errorString, int elementId,
     if (!element)
         return;
 
-    m_history->perform(adoptPtr(new RemoveAttributeAction(element, name)), errorString);
+    m_domEditor->removeAttribute(element, name, errorString);
     m_history->markUndoableState();
 }
 
@@ -838,7 +602,7 @@ void InspectorDOMAgent::removeNode(ErrorString* errorString, int nodeId)
         return;
     }
 
-    m_history->perform(adoptPtr(new RemoveChildAction(parentNode, node)), errorString);
+    m_domEditor->removeChild(parentNode, node, errorString);
     m_history->markUndoableState();
 }
 
@@ -861,15 +625,15 @@ void InspectorDOMAgent::setNodeName(ErrorString* errorString, int nodeId, const
     // Copy over the original node's children.
     Node* child;
     while ((child = oldNode->firstChild())) {
-        if (!m_history->perform(adoptPtr(new InsertBeforeAction(newElem.get(), child, 0)), errorString))
+        if (!m_domEditor->insertBefore(newElem.get(), child, 0, errorString))
             return;
     }
 
     // Replace the old node with the new node
     ContainerNode* parent = oldNode->parentNode();
-    if (!m_history->perform(adoptPtr(new InsertBeforeAction(parent, newElem.get(), oldNode->nextSibling())), errorString))
+    if (!m_domEditor->insertBefore(parent, newElem.get(), oldNode->nextSibling(), errorString))
         return;
-    if (!m_history->perform(adoptPtr(new RemoveChildAction(parent, oldNode)), errorString))
+    if (!m_domEditor->removeChild(parent, oldNode, errorString))
         return;
     m_history->markUndoableState();
 
@@ -890,8 +654,8 @@ void InspectorDOMAgent::getOuterHTML(ErrorString* errorString, int nodeId, WTF::
 void InspectorDOMAgent::setOuterHTML(ErrorString* errorString, int nodeId, const String& outerHTML)
 {
     if (!nodeId) {
-        DOMEditor domEditor(m_document.get());
-        domEditor.patchDocument(outerHTML);
+        DOMPatchSupport domPatchSupport(m_document.get());
+        domPatchSupport.patchDocument(outerHTML);
         return;
     }
 
@@ -905,17 +669,11 @@ void InspectorDOMAgent::setOuterHTML(ErrorString* errorString, int nodeId, const
         return;
     }
 
-    DOMEditor domEditor(document);
-
-    OwnPtr<SetOuterHTMLAction> action = adoptPtr(new SetOuterHTMLAction(node, outerHTML));
-    SetOuterHTMLAction* rawAction = action.get();
     Node* newNode = 0;
-    if (!m_history->perform(action.release(), errorString))
+    if (!m_domEditor->setOuterHTML(node, outerHTML, &newNode, errorString))
         return;
     m_history->markUndoableState();
 
-    newNode = rawAction->newNode();
-
     if (!newNode) {
         // The only child node has been deleted.
         return;
@@ -939,7 +697,7 @@ void InspectorDOMAgent::setNodeValue(ErrorString* errorString, int nodeId, const
         return;
     }
 
-    m_history->perform(adoptPtr(new ReplaceWholeTextAction(static_cast<Text*>(node), value)), errorString);
+    m_domEditor->replaceWholeText(static_cast<Text*>(node), value, errorString);
 }
 
 void InspectorDOMAgent::getEventListenersForNode(ErrorString*, int nodeId, RefPtr<InspectorArray>& listenersArray)
@@ -1297,7 +1055,7 @@ void InspectorDOMAgent::moveTo(ErrorString* errorString, int nodeId, int targetE
         }
     }
 
-    if (!m_history->perform(adoptPtr(new InsertBeforeAction(targetElement, node, anchorNode)), errorString))
+    if (!m_domEditor->insertBefore(targetElement, node, anchorNode, errorString))
         return;
     m_history->markUndoableState();
 
index 40f0f81..0c8849f 100644 (file)
@@ -35,7 +35,6 @@
 #include "InjectedScriptManager.h"
 #include "InspectorBaseAgent.h"
 #include "InspectorFrontend.h"
-#include "InspectorHistory.h"
 #include "InspectorValues.h"
 #include "Timer.h"
 
 namespace WebCore {
 class ContainerNode;
 class CharacterData;
+class DOMEditor;
 class Document;
 class Element;
 class Event;
 class GraphicsContext;
 class InspectorClient;
-class InspectorDOMAgent;
 class InspectorFrontend;
+class InspectorHistory;
 class InspectorPageAgent;
 class IntRect;
 class HitTestResult;
@@ -193,14 +193,6 @@ public:
     Node* assertNode(ErrorString*, int nodeId);
 
 private:
-    class DOMAction;
-    class RemoveChildAction;
-    class InsertBeforeAction;
-    class RemoveAttributeAction;
-    class SetAttributeAction;
-    class SetOuterHTMLAction;
-    class ReplaceWholeTextAction;
-
     InspectorDOMAgent(InstrumentingAgents*, InspectorPageAgent*, InspectorClient*, InspectorState*, InjectedScriptManager*);
 
     void setSearchingForNode(bool enabled, InspectorObject* highlightConfig);
@@ -254,6 +246,7 @@ private:
     RefPtr<Node> m_nodeToFocus;
     bool m_searchingForNode;
     OwnPtr<InspectorHistory> m_history;
+    OwnPtr<DOMEditor> m_domEditor;
 };
 
 #endif // ENABLE(INSPECTOR)
index 44004bc..3bcb3ab 100644 (file)
@@ -42,7 +42,7 @@
 #include "ContentSearchUtils.h"
 #include "Cookie.h"
 #include "CookieJar.h"
-#include "DOMEditor.h"
+#include "DOMPatchSupport.h"
 #include "Document.h"
 #include "DocumentLoader.h"
 #include "Frame.h"
@@ -571,8 +571,8 @@ void InspectorPageAgent::setDocumentContent(ErrorString* errorString, const Stri
         *errorString = "No Document instance to set HTML for";
         return;
     }
-    DOMEditor editor(document);
-    editor.patchDocument(html);
+    DOMPatchSupport patcher(document);
+    patcher.patchDocument(html);
 }
 
 void InspectorPageAgent::setScreenSizeOverride(ErrorString* errorString, const int width, const int height)