This source file includes following definitions.
- patchDocument
- m_document
- patchDocument
- patchNode
- innerPatchNode
- diff
- innerPatchChildren
- addStringToSHA1
- createDigest
- insertBeforeAndMarkAsUsed
- removeChildAndMoveToNew
- markNodeAsUsed
- nodeName
- dumpMap
#include "config.h"
#include "core/inspector/DOMPatchSupport.h"
#include "bindings/v8/ExceptionState.h"
#include "bindings/v8/ExceptionStatePlaceholder.h"
#include "core/dom/Attribute.h"
#include "core/dom/ContextFeatures.h"
#include "core/dom/Document.h"
#include "core/dom/DocumentFragment.h"
#include "core/dom/Node.h"
#include "core/dom/XMLDocument.h"
#include "core/html/HTMLBodyElement.h"
#include "core/html/HTMLDocument.h"
#include "core/html/HTMLHeadElement.h"
#include "core/html/parser/HTMLDocumentParser.h"
#include "core/inspector/DOMEditor.h"
#include "core/inspector/InspectorHistory.h"
#include "core/xml/parser/XMLDocumentParser.h"
#include "wtf/Deque.h"
#include "wtf/HashTraits.h"
#include "wtf/RefPtr.h"
#include "wtf/SHA1.h"
#include "wtf/text/Base64.h"
#include "wtf/text/CString.h"
using namespace std;
namespace WebCore {
struct DOMPatchSupport::Digest {
explicit Digest(Node* node) : m_node(node) { }
String m_sha1;
String m_attrsSHA1;
Node* m_node;
Vector<OwnPtr<Digest> > m_children;
};
void DOMPatchSupport::patchDocument(Document& document, const String& markup)
{
InspectorHistory history;
DOMEditor domEditor(&history);
DOMPatchSupport patchSupport(&domEditor, document);
patchSupport.patchDocument(markup);
}
DOMPatchSupport::DOMPatchSupport(DOMEditor* domEditor, Document& document)
: m_domEditor(domEditor)
, m_document(document)
{
}
void DOMPatchSupport::patchDocument(const String& markup)
{
RefPtr<Document> newDocument;
if (m_document.isHTMLDocument())
newDocument = HTMLDocument::create();
else if (m_document.isXHTMLDocument())
newDocument = XMLDocument::createXHTML();
else if (m_document.isXMLDocument())
newDocument = XMLDocument::create();
ASSERT(newDocument);
newDocument->setContextFeatures(m_document.contextFeatures());
RefPtr<DocumentParser> parser;
if (m_document.isHTMLDocument())
parser = HTMLDocumentParser::create(toHTMLDocument(newDocument.get()), false);
else
parser = XMLDocumentParser::create(newDocument.get(), 0);
parser->insert(markup);
parser->finish();
parser->detach();
OwnPtr<Digest> oldInfo = createDigest(m_document.documentElement(), 0);
OwnPtr<Digest> newInfo = createDigest(newDocument->documentElement(), &m_unusedNodesMap);
if (!innerPatchNode(oldInfo.get(), newInfo.get(), IGNORE_EXCEPTION)) {
m_document.write(markup);
m_document.close();
}
}
Node* DOMPatchSupport::patchNode(Node* node, const String& markup, ExceptionState& exceptionState)
{
if (node->isDocumentNode() || (node->parentNode() && node->parentNode()->isDocumentNode())) {
patchDocument(markup);
return 0;
}
Node* previousSibling = node->previousSibling();
RefPtr<DocumentFragment> fragment = DocumentFragment::create(m_document);
Node* targetNode = node->parentElementOrShadowRoot() ? node->parentElementOrShadowRoot() : m_document.documentElement();
if (targetNode->isShadowRoot())
targetNode = m_document.body();
Element* targetElement = toElement(targetNode);
if (m_document.isHTMLDocument())
fragment->parseHTML(markup, targetElement);
else
fragment->parseXML(markup, targetElement);
ContainerNode* parentNode = node->parentNode();
Vector<OwnPtr<Digest> > oldList;
for (Node* child = parentNode->firstChild(); child; child = child->nextSibling())
oldList.append(createDigest(child, 0));
String markupCopy = markup.lower();
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 (isHTMLHeadElement(*child) && !child->firstChild() && markupCopy.find("</head>") == kNotFound)
continue;
if (isHTMLBodyElement(*child) && !child->firstChild() && markupCopy.find("</body>") == kNotFound)
continue;
newList.append(createDigest(child, &m_unusedNodesMap));
}
for (Node* child = node->nextSibling(); child; child = child->nextSibling())
newList.append(createDigest(child, 0));
if (!innerPatchChildren(parentNode, oldList, newList, exceptionState)) {
if (!m_domEditor->replaceChild(parentNode, fragment.release(), node, exceptionState))
return 0;
}
return previousSibling ? previousSibling->nextSibling() : parentNode->firstChild();
}
bool DOMPatchSupport::innerPatchNode(Digest* oldDigest, Digest* newDigest, ExceptionState& exceptionState)
{
if (oldDigest->m_sha1 == newDigest->m_sha1)
return true;
Node* oldNode = oldDigest->m_node;
Node* newNode = newDigest->m_node;
if (newNode->nodeType() != oldNode->nodeType() || newNode->nodeName() != oldNode->nodeName())
return m_domEditor->replaceChild(oldNode->parentNode(), newNode, oldNode, exceptionState);
if (oldNode->nodeValue() != newNode->nodeValue()) {
if (!m_domEditor->setNodeValue(oldNode, newNode->nodeValue(), exceptionState))
return false;
}
if (oldNode->nodeType() != Node::ELEMENT_NODE)
return true;
Element* oldElement = toElement(oldNode);
Element* newElement = toElement(newNode);
if (oldDigest->m_attrsSHA1 != newDigest->m_attrsSHA1) {
if (oldElement->hasAttributesWithoutUpdate()) {
while (oldElement->attributeCount()) {
const Attribute& attribute = oldElement->attributeItem(0);
if (!m_domEditor->removeAttribute(oldElement, attribute.localName(), exceptionState))
return false;
}
}
if (newElement->hasAttributesWithoutUpdate()) {
size_t numAttrs = newElement->attributeCount();
for (size_t i = 0; i < numAttrs; ++i) {
const Attribute& attribute = newElement->attributeItem(i);
if (!m_domEditor->setAttribute(oldElement, attribute.name().localName(), attribute.value(), exceptionState))
return false;
}
}
}
bool result = innerPatchChildren(oldElement, oldDigest->m_children, newDigest->m_children, exceptionState);
m_unusedNodesMap.remove(newDigest->m_sha1);
return result;
}
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;
}
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) {
newTable.add(newList[i]->m_sha1, Vector<size_t>()).storedValue->value.append(i);
}
for (size_t i = 0; i < oldList.size(); ++i) {
oldTable.add(oldList[i]->m_sha1, Vector<size_t>()).storedValue->value.append(i);
}
for (DiffTable::iterator newIt = newTable.begin(); newIt != newTable.end(); ++newIt) {
if (newIt->value.size() != 1)
continue;
DiffTable::iterator oldIt = oldTable.find(newIt->key);
if (oldIt == oldTable.end() || oldIt->value.size() != 1)
continue;
newMap[newIt->value[0]] = make_pair(newList[newIt->value[0]].get(), oldIt->value[0]);
oldMap[oldIt->value[0]] = make_pair(oldList[oldIt->value[0]].get(), newIt->value[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);
}
bool DOMPatchSupport::innerPatchChildren(ContainerNode* parentNode, const Vector<OwnPtr<Digest> >& oldList, const Vector<OwnPtr<Digest> >& newList, ExceptionState& exceptionState)
{
pair<ResultMap, ResultMap> resultMaps = diff(oldList, newList);
ResultMap& oldMap = resultMaps.first;
ResultMap& newMap = resultMaps.second;
Digest* oldHead = 0;
Digest* oldBody = 0;
HashMap<Digest*, Digest*> merges;
HashSet<size_t, WTF::IntHash<size_t>, WTF::UnsignedWithZeroKeyHashTraits<size_t> > usedNewOrdinals;
for (size_t i = 0; i < oldList.size(); ++i) {
if (oldMap[i].first) {
if (usedNewOrdinals.add(oldMap[i].second).isNewEntry)
continue;
oldMap[i].first = 0;
oldMap[i].second = 0;
}
if (isHTMLHeadElement(*oldList[i]->m_node)) {
oldHead = oldList[i].get();
continue;
}
if (isHTMLBodyElement(*oldList[i]->m_node)) {
oldBody = oldList[i].get();
continue;
}
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 {
if (!removeChildAndMoveToNew(oldList[i].get(), exceptionState))
return false;
}
} else {
if (!removeChildAndMoveToNew(oldList[i].get(), exceptionState))
return false;
}
}
HashSet<size_t, WTF::IntHash<size_t>, WTF::UnsignedWithZeroKeyHashTraits<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)) {
newMap[i].first = 0;
newMap[i].second = 0;
continue;
}
usedOldOrdinals.add(oldOrdinal);
markNodeAsUsed(newMap[i].first);
}
if (oldHead || oldBody) {
for (size_t i = 0; i < newList.size(); ++i) {
if (oldHead && isHTMLHeadElement(*newList[i]->m_node))
merges.set(newList[i].get(), oldHead);
if (oldBody && isHTMLBodyElement(*newList[i]->m_node))
merges.set(newList[i].get(), oldBody);
}
}
for (HashMap<Digest*, Digest*>::iterator it = merges.begin(); it != merges.end(); ++it) {
if (!innerPatchNode(it->value, it->key, exceptionState))
return false;
}
for (size_t i = 0; i < newMap.size(); ++i) {
if (newMap[i].first || merges.contains(newList[i].get()))
continue;
if (!insertBeforeAndMarkAsUsed(parentNode, newList[i].get(), parentNode->traverseToChildAt(i), exceptionState))
return false;
}
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->traverseToChildAt(oldMap[i].second);
if (node == anchorNode)
continue;
if (isHTMLBodyElement(*node) || isHTMLHeadElement(*node))
continue;
if (!m_domEditor->insertBefore(parentNode, node.release(), anchorNode, exceptionState))
return false;
}
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<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 = toElement(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);
}
bool DOMPatchSupport::insertBeforeAndMarkAsUsed(ContainerNode* parentNode, Digest* digest, Node* anchor, ExceptionState& exceptionState)
{
bool result = m_domEditor->insertBefore(parentNode, digest->m_node, anchor, exceptionState);
markNodeAsUsed(digest);
return result;
}
bool DOMPatchSupport::removeChildAndMoveToNew(Digest* oldDigest, ExceptionState& exceptionState)
{
RefPtr<Node> oldNode = oldDigest->m_node;
if (!m_domEditor->removeChild(oldNode->parentNode(), oldNode.get(), exceptionState))
return false;
UnusedNodesMap::iterator it = m_unusedNodesMap.find(oldDigest->m_sha1);
if (it != m_unusedNodesMap.end()) {
Digest* newDigest = it->value;
Node* newNode = newDigest->m_node;
if (!m_domEditor->replaceChild(newNode->parentNode(), oldNode, newNode, exceptionState))
return false;
newDigest->m_node = oldNode.get();
markNodeAsUsed(newDigest);
return true;
}
for (size_t i = 0; i < oldDigest->m_children.size(); ++i) {
if (!removeChildAndMoveToNew(oldDigest->m_children[i].get(), exceptionState))
return false;
}
return true;
}
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
}