Web Inspector: replace HTMLCanvasElement with CanvasRenderingContext for instrumentat...
[WebKit-https.git] / Source / WebCore / inspector / DOMEditor.cpp
index d0ce363..3493b01 100644 (file)
 #include "config.h"
 #include "DOMEditor.h"
 
-#if ENABLE(INSPECTOR)
-
-#include "Base64.h"
+#include "DOMException.h"
+#include "DOMPatchSupport.h"
 #include "Document.h"
-#include "HTMLDocument.h"
-#include "HTMLDocumentParser.h"
-#include "HTMLElement.h"
-#include "HTMLHeadElement.h"
+#include "Element.h"
+#include "InspectorHistory.h"
 #include "Node.h"
-
+#include "Text.h"
+#include "markup.h"
 #include <wtf/RefPtr.h>
-#include <wtf/SHA1.h>
-#include <wtf/text/CString.h>
-
-using namespace std;
 
 namespace WebCore {
 
-struct DOMEditor::NodeDigest {
-    NodeDigest(Node* node) : m_node(node) { }
+class DOMEditor::RemoveChildAction final : public InspectorHistory::Action {
+    WTF_MAKE_NONCOPYABLE(RemoveChildAction);
+public:
+    RemoveChildAction(Node& parentNode, Node& node)
+        : InspectorHistory::Action()
+        , m_parentNode(parentNode)
+        , m_node(node)
+    {
+    }
+
+    ExceptionOr<void> perform() final
+    {
+        m_anchorNode = m_node->nextSibling();
+        return redo();
+    }
+
+    ExceptionOr<void> undo() final
+    {
+        return m_parentNode->insertBefore(m_node, m_anchorNode.get());
+    }
 
-    String m_digest;
-    String m_attrsDigest;
-    Node* m_node;
-    Vector<OwnPtr<NodeDigest> > m_children;
+    ExceptionOr<void> redo() final
+    {
+        return m_parentNode->removeChild(m_node);
+    }
+
+private:
+    Ref<Node> m_parentNode;
+    Ref<Node> m_node;
+    RefPtr<Node> m_anchorNode;
 };
 
-DOMEditor::DOMEditor(Document* document) : m_document(document) { }
+class DOMEditor::InsertBeforeAction final : public InspectorHistory::Action {
+public:
+    InsertBeforeAction(Node& parentNode, Ref<Node>&& node, Node* anchorNode)
+        : InspectorHistory::Action()
+        , m_parentNode(parentNode)
+        , m_node(WTFMove(node))
+        , m_anchorNode(anchorNode)
+    {
+    }
 
-DOMEditor::~DOMEditor() { }
+private:
+    ExceptionOr<void> perform() final
+    {
+        if (m_node->parentNode()) {
+            m_removeChildAction = std::make_unique<RemoveChildAction>(*m_node->parentNode(), m_node);
+            auto result = m_removeChildAction->perform();
+            if (result.hasException())
+                return result.releaseException();
+        }
+        return m_parentNode->insertBefore(m_node, m_anchorNode.get());
+    }
 
-void DOMEditor::patch(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();
+    ExceptionOr<void> undo() final
+    {
+        auto result = m_parentNode->removeChild(m_node);
+        if (result.hasException())
+            return result.releaseException();
+        if (!m_removeChildAction)
+            return { };
+        return m_removeChildAction->undo();
+    }
 
-    if (!patchElement(m_document->head(), newDocument->head()) || !patchElement(m_document->body(), newDocument->body())) {
-        // Fall back to rewrite.
-        m_document->write(markup);
-        m_document->close();
+    ExceptionOr<void> redo() final
+    {
+        if (m_removeChildAction) {
+            auto result = m_removeChildAction->redo();
+            if (result.hasException())
+                return result.releaseException();
+        }
+        return m_parentNode->insertBefore(m_node, m_anchorNode.get());
     }
-}
 
-bool DOMEditor::patchElement(Element* oldElement, Element* newElement)
-{
-    if (oldElement)  {
-        if (newElement) {
-            OwnPtr<NodeDigest> oldInfo = createNodeDigest(oldElement);
-            OwnPtr<NodeDigest> newInfo = createNodeDigest(newElement);
-            return patchNode(oldInfo.get(), newInfo.get());
+    Ref<Node> m_parentNode;
+    Ref<Node> m_node;
+    RefPtr<Node> m_anchorNode;
+    std::unique_ptr<RemoveChildAction> m_removeChildAction;
+};
+
+class DOMEditor::RemoveAttributeAction final : public InspectorHistory::Action {
+    WTF_MAKE_NONCOPYABLE(RemoveAttributeAction);
+public:
+    RemoveAttributeAction(Element& element, const String& name)
+        : InspectorHistory::Action()
+        , m_element(element)
+        , m_name(name)
+    {
+    }
+
+private:
+    ExceptionOr<void> perform() final
+    {
+        m_value = m_element->getAttribute(m_name);
+        return redo();
+    }
+
+    ExceptionOr<void> undo() final
+    {
+        return m_element->setAttribute(m_name, m_value);
+    }
+
+    ExceptionOr<void> redo() final
+    {
+        m_element->removeAttribute(m_name);
+        return { };
+    }
+
+    Ref<Element> m_element;
+    String m_name;
+    String m_value;
+};
+
+class DOMEditor::SetAttributeAction final : public InspectorHistory::Action {
+    WTF_MAKE_NONCOPYABLE(SetAttributeAction);
+public:
+    SetAttributeAction(Element& element, const AtomicString& name, const AtomicString& value)
+        : InspectorHistory::Action()
+        , m_element(element)
+        , m_name(name)
+        , m_value(value)
+    {
+    }
+
+private:
+    ExceptionOr<void> perform() final
+    {
+        m_oldValue = m_element->getAttribute(m_name);
+        return redo();
+    }
+
+    ExceptionOr<void> undo() final
+    {
+        if (m_oldValue.isNull()) {
+            m_element->removeAttribute(m_name);
+            return { };
         }
-        oldElement->removeAllChildren();
-        return true;
+        return m_element->setAttribute(m_name, m_oldValue);
     }
-    if (newElement) {
-        ExceptionCode ec = 0;
-        m_document->documentElement()->appendChild(newElement, ec);
-        return !ec;
+
+    ExceptionOr<void> redo() final
+    {
+        return m_element->setAttribute(m_name, m_value);
     }
-    return true;
-}
 
-bool DOMEditor::patchNode(NodeDigest* oldDigest, NodeDigest* newDigest)
-{
-    if (oldDigest->m_digest == newDigest->m_digest)
-        return true;
+    Ref<Element> m_element;
+    AtomicString m_name;
+    AtomicString m_value;
+    AtomicString m_oldValue;
+};
 
-    Node* oldNode = oldDigest->m_node;
-    Node* newNode = newDigest->m_node;
+class DOMEditor::SetOuterHTMLAction final : public InspectorHistory::Action {
+public:
+    SetOuterHTMLAction(Node& node, const String& html)
+        : InspectorHistory::Action()
+        , m_node(node)
+        , m_nextSibling(node.nextSibling())
+        , m_html(html)
+    {
+    }
 
-    if (newNode->nodeType() != oldNode->nodeType() || newNode->nodeName() != oldNode->nodeName()) {
-        ExceptionCode ec = 0;
-        oldNode->parentNode()->replaceChild(newNode, oldNode, ec);
-        if (ec)
-            return false;
-        return true;
+    Node* newNode() const
+    {
+        return m_newNode.get();
     }
 
-    ExceptionCode ec = 0;
-    if (oldNode->nodeValue() != newNode->nodeValue())
-        oldNode->setNodeValue(newNode->nodeValue(), ec);
-    if (ec)
-        return false;
+private:
+    ExceptionOr<void> perform() final
+    {
+        m_oldHTML = createMarkup(m_node.get());
+        auto result = DOMPatchSupport { m_domEditor, m_node->document() }.patchNode(m_node, m_html);
+        if (result.hasException())
+            return result.releaseException();
+        m_newNode = result.releaseReturnValue();
+        return { };
+    }
 
-    if (oldNode->nodeType() == Node::ELEMENT_NODE && oldDigest->m_attrsDigest != newDigest->m_attrsDigest) {
-        Element* oldElement = static_cast<Element*>(oldNode);
-        Element* newElement = static_cast<Element*>(newNode);
-        oldElement->setAttributesFromElement(*newElement);
+    ExceptionOr<void> undo() final
+    {
+        return m_history.undo();
     }
-    if (oldDigest->m_node->nodeType() != Node::ELEMENT_NODE)
-        return true;
 
-    return patchChildren(static_cast<ContainerNode*>(oldNode), oldDigest->m_children, newDigest->m_children);
-}
+    ExceptionOr<void> redo() final
+    {
+        return m_history.redo();
+    }
 
-bool DOMEditor::patchChildren(ContainerNode* oldParent, Vector<OwnPtr<NodeDigest> >& oldList, Vector<OwnPtr<NodeDigest> >& newList)
-{
-    // Trim tail.
-    size_t offset = 0;
-    for (; offset < oldList.size() && offset < newList.size() && oldList[oldList.size() - offset - 1]->m_digest == newList[newList.size() - offset - 1]->m_digest; ++offset) { }
-    if (offset > 0) {
-        oldList.resize(oldList.size() - offset);
-        newList.resize(newList.size() - offset);
+    Ref<Node> m_node;
+    RefPtr<Node> m_nextSibling;
+    String m_html;
+    String m_oldHTML;
+    RefPtr<Node> m_newNode { nullptr };
+    InspectorHistory m_history;
+    DOMEditor m_domEditor { m_history };
+};
+
+class DOMEditor::InsertAdjacentHTMLAction final : public InspectorHistory::Action {
+    WTF_MAKE_NONCOPYABLE(InsertAdjacentHTMLAction);
+public:
+    InsertAdjacentHTMLAction(Element& element, const String& position, const String& html)
+        : InspectorHistory::Action()
+        , m_element(element)
+        , m_position(position)
+        , m_html(html)
+    {
     }
 
-    // Trim head.
-    for (offset = 0; offset < oldList.size() && offset < newList.size() && oldList[offset]->m_digest == newList[offset]->m_digest; ++offset) { }
-    if (offset > 0) {
-        oldList.remove(0, offset);
-        newList.remove(0, offset);
+private:
+    ExceptionOr<void> perform() final
+    {
+        return redo();
     }
 
-    // Diff the children lists.
-    typedef Vector<pair<NodeDigest*, size_t> > ResultMap;
-    ResultMap newMap(newList.size());
-    ResultMap oldMap(oldList.size());
+    ExceptionOr<void> undo() final
+    {
+        for (auto& addedNode : m_addedNodes)
+            addedNode->remove();
+        m_addedNodes.clear();
+        return { };
+    }
 
-    for (size_t i = 0; i < oldMap.size(); ++i)
-        oldMap[i].first = 0;
-    for (size_t i = 0; i < newMap.size(); ++i)
-        newMap[i].first = 0;
+    ExceptionOr<void> redo() final
+    {
+        auto result = m_element->insertAdjacentHTML(m_position, m_html, m_addedNodes);
+        if (result.hasException())
+            return result.releaseException();
+        return { };
+    }
 
-    typedef HashMap<String, Vector<size_t> > DiffTable;
-    DiffTable newTable;
-    DiffTable oldTable;
+    Ref<Element> m_element;
+    NodeVector m_addedNodes;
+    String m_position;
+    String m_html;
+};
 
-    for (size_t i = 0; i < newList.size(); ++i) {
-        DiffTable::iterator it = newTable.add(newList[i]->m_digest, Vector<size_t>()).first;
-        it->second.append(i);
+class DOMEditor::ReplaceWholeTextAction final : public InspectorHistory::Action {
+    WTF_MAKE_NONCOPYABLE(ReplaceWholeTextAction);
+public:
+    ReplaceWholeTextAction(Text& textNode, const String& text)
+        : InspectorHistory::Action()
+        , m_textNode(textNode)
+        , m_text(text)
+    {
     }
 
-    for (size_t i = 0; i < oldList.size(); ++i) {
-        DiffTable::iterator it = oldTable.add(oldList[i]->m_digest, Vector<size_t>()).first;
-        it->second.append(i);
+private:
+    ExceptionOr<void> perform() final
+    {
+        m_oldText = m_textNode->wholeText();
+        return redo();
     }
 
-    for (DiffTable::iterator newIt = newTable.begin(); newIt != newTable.end(); ++newIt) {
-        if (newIt->second.size() != 1)
-            continue;
+    ExceptionOr<void> undo() final
+    {
+        m_textNode->replaceWholeText(m_oldText);
+        return { };
+    }
 
-        DiffTable::iterator oldIt = oldTable.find(newIt->first);
-        if (oldIt == oldTable.end() || oldIt->second.size() != 1)
-            continue;
-        make_pair(newList[newIt->second[0]].get(), oldIt->second[0]);
-        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]);
+    ExceptionOr<void> redo() final
+    {
+        m_textNode->replaceWholeText(m_text);
+        return { };
     }
 
-    for (size_t i = 0; newList.size() > 0 && i < newList.size() - 1; ++i) {
-        if (!newMap[i].first || newMap[i + 1].first)
-            continue;
+    Ref<Text> m_textNode;
+    String m_text;
+    String m_oldText;
+};
 
-        size_t j = newMap[i].second + 1;
-        if (j < oldMap.size() && !oldMap[j].first && newList[i + 1]->m_digest == oldList[j]->m_digest) {
-            newMap[i + 1] = make_pair(newList[i + 1].get(), j);
-            oldMap[j] = make_pair(oldList[j].get(), i + 1);
-        }
+class DOMEditor::ReplaceChildNodeAction final: public InspectorHistory::Action {
+    WTF_MAKE_NONCOPYABLE(ReplaceChildNodeAction);
+public:
+    ReplaceChildNodeAction(Node& parentNode, Ref<Node>&& newNode, Node& oldNode)
+        : InspectorHistory::Action()
+        , m_parentNode(parentNode)
+        , m_newNode(WTFMove(newNode))
+        , m_oldNode(oldNode)
+    {
     }
 
-    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;
+private:
+    ExceptionOr<void> perform() final
+    {
+        return redo();
+    }
 
-        size_t j = newMap[i].second - 1;
-        if (!oldMap[j].first && newList[i - 1]->m_digest == oldList[j]->m_digest) {
-            newMap[i - 1] = make_pair(newList[i - 1].get(), j);
-            oldMap[j] = make_pair(oldList[j].get(), i - 1);
-        }
+    ExceptionOr<void> undo() final
+    {
+        return m_parentNode->replaceChild(m_oldNode, m_newNode);
     }
 
-    HashSet<NodeDigest*> merges;
-    for (size_t i = 0; i < oldList.size(); ++i) {
-        if (oldMap[i].first)
-            continue;
-
-        // Check if this change is between stable nodes. If it is, consider it as "modified".
-        if ((!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()) {
-                if (!patchNode(oldList[i].get(), newList[anchorCandidate].get()))
-                    return false;
-
-                merges.add(newList[anchorCandidate].get());
-            } else
-                oldList[i]->m_node->parentNode()->removeChild(oldList[i]->m_node);
-        } else {
-            ContainerNode* parentNode = static_cast<ContainerNode*>(oldList[i]->m_node->parentNode());
-            parentNode->removeChild(oldList[i]->m_node);
-        }
+    ExceptionOr<void> redo() final
+    {
+        return m_parentNode->replaceChild(m_newNode, m_oldNode);
     }
 
-    for (size_t i = 0; i < newMap.size(); ++i) {
-        if (newMap[i].first || merges.contains(newList[i].get()))
-            continue;
+    Ref<Node> m_parentNode;
+    Ref<Node> m_newNode;
+    Ref<Node> m_oldNode;
+};
 
-        ExceptionCode ec = 0;
-        oldParent->insertBefore(newList[i]->m_node, oldParent->childNode(i + offset), ec);
-        return !ec;
+class DOMEditor::SetNodeValueAction final : public InspectorHistory::Action {
+    WTF_MAKE_NONCOPYABLE(SetNodeValueAction);
+public:
+    SetNodeValueAction(Node& node, const String& value)
+        : InspectorHistory::Action()
+        , m_node(node)
+        , m_value(value)
+    {
     }
 
-    return true;
+private:
+    ExceptionOr<void> perform() final
+    {
+        m_oldValue = m_node->nodeValue();
+        return redo();
+    }
+
+    ExceptionOr<void> undo() final
+    {
+        return m_node->setNodeValue(m_oldValue);
+    }
+
+    ExceptionOr<void> redo() final
+    {
+        return m_node->setNodeValue(m_value);
+    }
+
+    Ref<Node> m_node;
+    String m_value;
+    String m_oldValue;
+};
+
+DOMEditor::DOMEditor(InspectorHistory& history)
+    : m_history(history)
+{
 }
 
-static void addStringToSHA1(SHA1& sha1, const String& string)
+DOMEditor::~DOMEditor() = default;
+
+ExceptionOr<void> DOMEditor::insertBefore(Node& parentNode, Ref<Node>&& node, Node* anchorNode)
 {
-    CString cString = string.utf8();
-    sha1.addBytes(reinterpret_cast<const uint8_t*>(cString.data()), cString.length());
+    return m_history.perform(std::make_unique<InsertBeforeAction>(parentNode, WTFMove(node), anchorNode));
 }
 
-PassOwnPtr<DOMEditor::NodeDigest> DOMEditor::createNodeDigest(Node* node)
+ExceptionOr<void> DOMEditor::removeChild(Node& parentNode, Node& node)
 {
-    NodeDigest* nodeDigest = new NodeDigest(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<NodeDigest> childInfo = createNodeDigest(child);
-            addStringToSHA1(sha1, childInfo->m_digest);
-            child = child->nextSibling();
-            nodeDigest->m_children.append(childInfo.release());
-        }
+    return m_history.perform(std::make_unique<RemoveChildAction>(parentNode, node));
+}
 
-        Element* element = static_cast<Element*>(node);
-        const NamedNodeMap* attrMap = element->attributes(true);
-        if (attrMap && attrMap->length()) {
-            unsigned numAttrs = attrMap->length();
-            SHA1 attrsSHA1;
-            for (unsigned i = 0; i < numAttrs; ++i) {
-                const Attribute* attribute = attrMap->attributeItem(i);
-                addStringToSHA1(attrsSHA1, attribute->name().toString());
-                addStringToSHA1(attrsSHA1, attribute->value());
-            }
-            Vector<uint8_t, 20> attrsHash;
-            attrsSHA1.computeHash(attrsHash);
-            nodeDigest->m_attrsDigest = base64Encode(reinterpret_cast<const char*>(attrsHash.data()), 10);
-            addStringToSHA1(sha1, nodeDigest->m_attrsDigest);
-        }
-    }
+ExceptionOr<void> DOMEditor::setAttribute(Element& element, const String& name, const String& value)
+{
+    return m_history.perform(std::make_unique<SetAttributeAction>(element, name, value));
+}
 
-    Vector<uint8_t, 20> hash;
-    sha1.computeHash(hash);
-    nodeDigest->m_digest = base64Encode(reinterpret_cast<const char*>(hash.data()), 10);
-    return adoptPtr(nodeDigest);
+ExceptionOr<void> DOMEditor::removeAttribute(Element& element, const String& name)
+{
+    return m_history.perform(std::make_unique<RemoveAttributeAction>(element, name));
 }
 
-} // namespace WebCore
+ExceptionOr<void> DOMEditor::setOuterHTML(Node& node, const String& html, Node*& newNode)
+{
+    auto action = std::make_unique<SetOuterHTMLAction>(node, html);
+    auto& rawAction = *action;
+    auto result = m_history.perform(WTFMove(action));
+    if (!result.hasException())
+        newNode = rawAction.newNode();
+    return result;
+}
+
+ExceptionOr<void> DOMEditor::insertAdjacentHTML(Element& element, const String& where, const String& html)
+{
+    return m_history.perform(std::make_unique<InsertAdjacentHTMLAction>(element, where, html));
+}
+
+ExceptionOr<void> DOMEditor::replaceWholeText(Text& textNode, const String& text)
+{
+    return m_history.perform(std::make_unique<ReplaceWholeTextAction>(textNode, text));
+}
+
+ExceptionOr<void> DOMEditor::replaceChild(Node& parentNode, Ref<Node>&& newNode, Node& oldNode)
+{
+    return m_history.perform(std::make_unique<ReplaceChildNodeAction>(parentNode, WTFMove(newNode), oldNode));
+}
 
-#endif // ENABLE(INSPECTOR)
+ExceptionOr<void> DOMEditor::setNodeValue(Node& node, const String& value)
+{
+    return m_history.perform(std::make_unique<SetNodeValueAction>(node, value));
+}
+
+static bool populateErrorString(ExceptionOr<void>&& result, ErrorString& errorString)
+{
+    if (!result.hasException())
+        return true;
+    errorString = DOMException::name(result.releaseException().code());
+    return false;
+}
+
+bool DOMEditor::insertBefore(Node& parentNode, Ref<Node>&& node, Node* anchorNode, ErrorString& errorString)
+{
+    return populateErrorString(insertBefore(parentNode, WTFMove(node), anchorNode), errorString);
+}
+
+bool DOMEditor::removeChild(Node& parentNode, Node& node, ErrorString& errorString)
+{
+    return populateErrorString(removeChild(parentNode, node), errorString);
+}
+
+bool DOMEditor::setAttribute(Element& element, const String& name, const String& value, ErrorString& errorString)
+{
+    return populateErrorString(setAttribute(element, name, value), errorString);
+}
+
+bool DOMEditor::removeAttribute(Element& element, const String& name, ErrorString& errorString)
+{
+    return populateErrorString(removeAttribute(element, name), errorString);
+}
+
+bool DOMEditor::setOuterHTML(Node& node, const String& html, Node*& newNode, ErrorString& errorString)
+{
+    return populateErrorString(setOuterHTML(node, html, newNode), errorString);
+}
+
+bool DOMEditor::insertAdjacentHTML(Element& element, const String& where, const String& html, ErrorString& errorString)
+{
+    return populateErrorString(insertAdjacentHTML(element, where, html), errorString);
+}
+
+bool DOMEditor::replaceWholeText(Text& textNode, const String& text, ErrorString& errorString)
+{
+    return populateErrorString(replaceWholeText(textNode, text), errorString);
+}
+
+} // namespace WebCore