1 /* 2 * Copyright (C) 2012 Google Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are 6 * met: 7 * 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above 11 * copyright notice, this list of conditions and the following disclaimer 12 * in the documentation and/or other materials provided with the 13 * distribution. 14 * * Neither the name of Google Inc. nor the names of its 15 * contributors may be used to endorse or promote products derived from 16 * this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31 #include "config.h" 32 #include "core/inspector/DOMPatchSupport.h" 33 34 #include "HTMLNames.h" 35 #include "bindings/v8/ExceptionState.h" 36 #include "bindings/v8/ExceptionStatePlaceholder.h" 37 #include "core/dom/Attribute.h" 38 #include "core/dom/ContextFeatures.h" 39 #include "core/dom/Document.h" 40 #include "core/dom/DocumentFragment.h" 41 #include "core/dom/Node.h" 42 #include "core/html/HTMLDocument.h" 43 #include "core/html/parser/HTMLDocumentParser.h" 44 #include "core/inspector/DOMEditor.h" 45 #include "core/inspector/InspectorHistory.h" 46 #include "core/xml/parser/XMLDocumentParser.h" 47 #include "wtf/Deque.h" 48 #include "wtf/HashTraits.h" 49 #include "wtf/RefPtr.h" 50 #include "wtf/SHA1.h" 51 #include "wtf/text/Base64.h" 52 #include "wtf/text/CString.h" 53 54 using namespace std; 55 56 namespace WebCore { 57 58 using HTMLNames::bodyTag; 59 using HTMLNames::headTag; 60 using HTMLNames::htmlTag; 61 62 struct DOMPatchSupport::Digest { 63 explicit Digest(Node* node) : m_node(node) { } 64 65 String m_sha1; 66 String m_attrsSHA1; 67 Node* m_node; 68 Vector<OwnPtr<Digest> > m_children; 69 }; 70 71 void DOMPatchSupport::patchDocument(Document& document, const String& markup) 72 { 73 InspectorHistory history; 74 DOMEditor domEditor(&history); 75 DOMPatchSupport patchSupport(&domEditor, document); 76 patchSupport.patchDocument(markup); 77 } 78 79 DOMPatchSupport::DOMPatchSupport(DOMEditor* domEditor, Document& document) 80 : m_domEditor(domEditor) 81 , m_document(document) 82 { 83 } 84 85 void DOMPatchSupport::patchDocument(const String& markup) 86 { 87 RefPtr<Document> newDocument; 88 if (m_document.isHTMLDocument()) 89 newDocument = HTMLDocument::create(); 90 else if (m_document.isXHTMLDocument()) 91 newDocument = HTMLDocument::createXHTML(); 92 else if (m_document.isSVGDocument()) 93 newDocument = Document::create(); 94 95 ASSERT(newDocument); 96 newDocument->setContextFeatures(m_document.contextFeatures()); 97 RefPtr<DocumentParser> parser; 98 if (m_document.isHTMLDocument()) 99 parser = HTMLDocumentParser::create(toHTMLDocument(newDocument.get()), false); 100 else 101 parser = XMLDocumentParser::create(newDocument.get(), 0); 102 parser->insert(markup); // Use insert() so that the parser will not yield. 103 parser->finish(); 104 parser->detach(); 105 106 OwnPtr<Digest> oldInfo = createDigest(m_document.documentElement(), 0); 107 OwnPtr<Digest> newInfo = createDigest(newDocument->documentElement(), &m_unusedNodesMap); 108 109 if (!innerPatchNode(oldInfo.get(), newInfo.get(), IGNORE_EXCEPTION)) { 110 // Fall back to rewrite. 111 m_document.write(markup); 112 m_document.close(); 113 } 114 } 115 116 Node* DOMPatchSupport::patchNode(Node* node, const String& markup, ExceptionState& exceptionState) 117 { 118 // Don't parse <html> as a fragment. 119 if (node->isDocumentNode() || (node->parentNode() && node->parentNode()->isDocumentNode())) { 120 patchDocument(markup); 121 return 0; 122 } 123 124 Node* previousSibling = node->previousSibling(); 125 // FIXME: This code should use one of createFragment* in markup.h 126 RefPtr<DocumentFragment> fragment = DocumentFragment::create(m_document); 127 if (m_document.isHTMLDocument()) 128 fragment->parseHTML(markup, node->parentElement() ? node->parentElement() : m_document.documentElement()); 129 else 130 fragment->parseXML(markup, node->parentElement() ? node->parentElement() : m_document.documentElement()); 131 132 // Compose the old list. 133 ContainerNode* parentNode = node->parentNode(); 134 Vector<OwnPtr<Digest> > oldList; 135 for (Node* child = parentNode->firstChild(); child; child = child->nextSibling()) 136 oldList.append(createDigest(child, 0)); 137 138 // Compose the new list. 139 String markupCopy = markup.lower(); 140 Vector<OwnPtr<Digest> > newList; 141 for (Node* child = parentNode->firstChild(); child != node; child = child->nextSibling()) 142 newList.append(createDigest(child, 0)); 143 for (Node* child = fragment->firstChild(); child; child = child->nextSibling()) { 144 if (child->hasTagName(headTag) && !child->firstChild() && markupCopy.find("</head>") == kNotFound) 145 continue; // HTML5 parser inserts empty <head> tag whenever it parses <body> 146 if (child->hasTagName(bodyTag) && !child->firstChild() && markupCopy.find("</body>") == kNotFound) 147 continue; // HTML5 parser inserts empty <body> tag whenever it parses </head> 148 newList.append(createDigest(child, &m_unusedNodesMap)); 149 } 150 for (Node* child = node->nextSibling(); child; child = child->nextSibling()) 151 newList.append(createDigest(child, 0)); 152 153 if (!innerPatchChildren(parentNode, oldList, newList, exceptionState)) { 154 // Fall back to total replace. 155 if (!m_domEditor->replaceChild(parentNode, fragment.release(), node, exceptionState)) 156 return 0; 157 } 158 return previousSibling ? previousSibling->nextSibling() : parentNode->firstChild(); 159 } 160 161 bool DOMPatchSupport::innerPatchNode(Digest* oldDigest, Digest* newDigest, ExceptionState& exceptionState) 162 { 163 if (oldDigest->m_sha1 == newDigest->m_sha1) 164 return true; 165 166 Node* oldNode = oldDigest->m_node; 167 Node* newNode = newDigest->m_node; 168 169 if (newNode->nodeType() != oldNode->nodeType() || newNode->nodeName() != oldNode->nodeName()) 170 return m_domEditor->replaceChild(oldNode->parentNode(), newNode, oldNode, exceptionState); 171 172 if (oldNode->nodeValue() != newNode->nodeValue()) { 173 if (!m_domEditor->setNodeValue(oldNode, newNode->nodeValue(), exceptionState)) 174 return false; 175 } 176 177 if (oldNode->nodeType() != Node::ELEMENT_NODE) 178 return true; 179 180 // Patch attributes 181 Element* oldElement = toElement(oldNode); 182 Element* newElement = toElement(newNode); 183 if (oldDigest->m_attrsSHA1 != newDigest->m_attrsSHA1) { 184 // FIXME: Create a function in Element for removing all properties. Take in account whether did/willModifyAttribute are important. 185 if (oldElement->hasAttributesWithoutUpdate()) { 186 while (oldElement->attributeCount()) { 187 const Attribute* attribute = oldElement->attributeItem(0); 188 if (!m_domEditor->removeAttribute(oldElement, attribute->localName(), exceptionState)) 189 return false; 190 } 191 } 192 193 // FIXME: Create a function in Element for copying properties. cloneDataFromElement() is close but not enough for this case. 194 if (newElement->hasAttributesWithoutUpdate()) { 195 size_t numAttrs = newElement->attributeCount(); 196 for (size_t i = 0; i < numAttrs; ++i) { 197 const Attribute* attribute = newElement->attributeItem(i); 198 if (!m_domEditor->setAttribute(oldElement, attribute->name().localName(), attribute->value(), exceptionState)) 199 return false; 200 } 201 } 202 } 203 204 bool result = innerPatchChildren(oldElement, oldDigest->m_children, newDigest->m_children, exceptionState); 205 m_unusedNodesMap.remove(newDigest->m_sha1); 206 return result; 207 } 208 209 pair<DOMPatchSupport::ResultMap, DOMPatchSupport::ResultMap> 210 DOMPatchSupport::diff(const Vector<OwnPtr<Digest> >& oldList, const Vector<OwnPtr<Digest> >& newList) 211 { 212 ResultMap newMap(newList.size()); 213 ResultMap oldMap(oldList.size()); 214 215 for (size_t i = 0; i < oldMap.size(); ++i) { 216 oldMap[i].first = 0; 217 oldMap[i].second = 0; 218 } 219 220 for (size_t i = 0; i < newMap.size(); ++i) { 221 newMap[i].first = 0; 222 newMap[i].second = 0; 223 } 224 225 // Trim head and tail. 226 for (size_t i = 0; i < oldList.size() && i < newList.size() && oldList[i]->m_sha1 == newList[i]->m_sha1; ++i) { 227 oldMap[i].first = oldList[i].get(); 228 oldMap[i].second = i; 229 newMap[i].first = newList[i].get(); 230 newMap[i].second = i; 231 } 232 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) { 233 size_t oldIndex = oldList.size() - i - 1; 234 size_t newIndex = newList.size() - i - 1; 235 oldMap[oldIndex].first = oldList[oldIndex].get(); 236 oldMap[oldIndex].second = newIndex; 237 newMap[newIndex].first = newList[newIndex].get(); 238 newMap[newIndex].second = oldIndex; 239 } 240 241 typedef HashMap<String, Vector<size_t> > DiffTable; 242 DiffTable newTable; 243 DiffTable oldTable; 244 245 for (size_t i = 0; i < newList.size(); ++i) { 246 DiffTable::iterator it = newTable.add(newList[i]->m_sha1, Vector<size_t>()).iterator; 247 it->value.append(i); 248 } 249 250 for (size_t i = 0; i < oldList.size(); ++i) { 251 DiffTable::iterator it = oldTable.add(oldList[i]->m_sha1, Vector<size_t>()).iterator; 252 it->value.append(i); 253 } 254 255 for (DiffTable::iterator newIt = newTable.begin(); newIt != newTable.end(); ++newIt) { 256 if (newIt->value.size() != 1) 257 continue; 258 259 DiffTable::iterator oldIt = oldTable.find(newIt->key); 260 if (oldIt == oldTable.end() || oldIt->value.size() != 1) 261 continue; 262 263 newMap[newIt->value[0]] = make_pair(newList[newIt->value[0]].get(), oldIt->value[0]); 264 oldMap[oldIt->value[0]] = make_pair(oldList[oldIt->value[0]].get(), newIt->value[0]); 265 } 266 267 for (size_t i = 0; newList.size() > 0 && i < newList.size() - 1; ++i) { 268 if (!newMap[i].first || newMap[i + 1].first) 269 continue; 270 271 size_t j = newMap[i].second + 1; 272 if (j < oldMap.size() && !oldMap[j].first && newList[i + 1]->m_sha1 == oldList[j]->m_sha1) { 273 newMap[i + 1] = make_pair(newList[i + 1].get(), j); 274 oldMap[j] = make_pair(oldList[j].get(), i + 1); 275 } 276 } 277 278 for (size_t i = newList.size() - 1; newList.size() > 0 && i > 0; --i) { 279 if (!newMap[i].first || newMap[i - 1].first || newMap[i].second <= 0) 280 continue; 281 282 size_t j = newMap[i].second - 1; 283 if (!oldMap[j].first && newList[i - 1]->m_sha1 == oldList[j]->m_sha1) { 284 newMap[i - 1] = make_pair(newList[i - 1].get(), j); 285 oldMap[j] = make_pair(oldList[j].get(), i - 1); 286 } 287 } 288 289 #ifdef DEBUG_DOM_PATCH_SUPPORT 290 dumpMap(oldMap, "OLD"); 291 dumpMap(newMap, "NEW"); 292 #endif 293 294 return make_pair(oldMap, newMap); 295 } 296 297 bool DOMPatchSupport::innerPatchChildren(ContainerNode* parentNode, const Vector<OwnPtr<Digest> >& oldList, const Vector<OwnPtr<Digest> >& newList, ExceptionState& exceptionState) 298 { 299 pair<ResultMap, ResultMap> resultMaps = diff(oldList, newList); 300 ResultMap& oldMap = resultMaps.first; 301 ResultMap& newMap = resultMaps.second; 302 303 Digest* oldHead = 0; 304 Digest* oldBody = 0; 305 306 // 1. First strip everything except for the nodes that retain. Collect pending merges. 307 HashMap<Digest*, Digest*> merges; 308 HashSet<size_t, WTF::IntHash<size_t>, WTF::UnsignedWithZeroKeyHashTraits<size_t> > usedNewOrdinals; 309 for (size_t i = 0; i < oldList.size(); ++i) { 310 if (oldMap[i].first) { 311 if (usedNewOrdinals.add(oldMap[i].second).isNewEntry) 312 continue; 313 oldMap[i].first = 0; 314 oldMap[i].second = 0; 315 } 316 317 // Always match <head> and <body> tags with each other - we can't remove them from the DOM 318 // upon patching. 319 if (oldList[i]->m_node->hasTagName(headTag)) { 320 oldHead = oldList[i].get(); 321 continue; 322 } 323 if (oldList[i]->m_node->hasTagName(bodyTag)) { 324 oldBody = oldList[i].get(); 325 continue; 326 } 327 328 // Check if this change is between stable nodes. If it is, consider it as "modified". 329 if (!m_unusedNodesMap.contains(oldList[i]->m_sha1) && (!i || oldMap[i - 1].first) && (i == oldMap.size() - 1 || oldMap[i + 1].first)) { 330 size_t anchorCandidate = i ? oldMap[i - 1].second + 1 : 0; 331 size_t anchorAfter = (i == oldMap.size() - 1) ? anchorCandidate + 1 : oldMap[i + 1].second; 332 if (anchorAfter - anchorCandidate == 1 && anchorCandidate < newList.size()) 333 merges.set(newList[anchorCandidate].get(), oldList[i].get()); 334 else { 335 if (!removeChildAndMoveToNew(oldList[i].get(), exceptionState)) 336 return false; 337 } 338 } else { 339 if (!removeChildAndMoveToNew(oldList[i].get(), exceptionState)) 340 return false; 341 } 342 } 343 344 // Mark retained nodes as used, do not reuse node more than once. 345 HashSet<size_t, WTF::IntHash<size_t>, WTF::UnsignedWithZeroKeyHashTraits<size_t> > usedOldOrdinals; 346 for (size_t i = 0; i < newList.size(); ++i) { 347 if (!newMap[i].first) 348 continue; 349 size_t oldOrdinal = newMap[i].second; 350 if (usedOldOrdinals.contains(oldOrdinal)) { 351 // Do not map node more than once 352 newMap[i].first = 0; 353 newMap[i].second = 0; 354 continue; 355 } 356 usedOldOrdinals.add(oldOrdinal); 357 markNodeAsUsed(newMap[i].first); 358 } 359 360 // Mark <head> and <body> nodes for merge. 361 if (oldHead || oldBody) { 362 for (size_t i = 0; i < newList.size(); ++i) { 363 if (oldHead && newList[i]->m_node->hasTagName(headTag)) 364 merges.set(newList[i].get(), oldHead); 365 if (oldBody && newList[i]->m_node->hasTagName(bodyTag)) 366 merges.set(newList[i].get(), oldBody); 367 } 368 } 369 370 // 2. Patch nodes marked for merge. 371 for (HashMap<Digest*, Digest*>::iterator it = merges.begin(); it != merges.end(); ++it) { 372 if (!innerPatchNode(it->value, it->key, exceptionState)) 373 return false; 374 } 375 376 // 3. Insert missing nodes. 377 for (size_t i = 0; i < newMap.size(); ++i) { 378 if (newMap[i].first || merges.contains(newList[i].get())) 379 continue; 380 if (!insertBeforeAndMarkAsUsed(parentNode, newList[i].get(), parentNode->childNode(i), exceptionState)) 381 return false; 382 } 383 384 // 4. Then put all nodes that retained into their slots (sort by new index). 385 for (size_t i = 0; i < oldMap.size(); ++i) { 386 if (!oldMap[i].first) 387 continue; 388 RefPtr<Node> node = oldMap[i].first->m_node; 389 Node* anchorNode = parentNode->childNode(oldMap[i].second); 390 if (node.get() == anchorNode) 391 continue; 392 if (node->hasTagName(bodyTag) || node->hasTagName(headTag)) 393 continue; // Never move head or body, move the rest of the nodes around them. 394 395 if (!m_domEditor->insertBefore(parentNode, node.release(), anchorNode, exceptionState)) 396 return false; 397 } 398 return true; 399 } 400 401 static void addStringToSHA1(SHA1& sha1, const String& string) 402 { 403 CString cString = string.utf8(); 404 sha1.addBytes(reinterpret_cast<const uint8_t*>(cString.data()), cString.length()); 405 } 406 407 PassOwnPtr<DOMPatchSupport::Digest> DOMPatchSupport::createDigest(Node* node, UnusedNodesMap* unusedNodesMap) 408 { 409 Digest* digest = new Digest(node); 410 411 SHA1 sha1; 412 413 Node::NodeType nodeType = node->nodeType(); 414 sha1.addBytes(reinterpret_cast<const uint8_t*>(&nodeType), sizeof(nodeType)); 415 addStringToSHA1(sha1, node->nodeName()); 416 addStringToSHA1(sha1, node->nodeValue()); 417 418 if (node->nodeType() == Node::ELEMENT_NODE) { 419 Node* child = node->firstChild(); 420 while (child) { 421 OwnPtr<Digest> childInfo = createDigest(child, unusedNodesMap); 422 addStringToSHA1(sha1, childInfo->m_sha1); 423 child = child->nextSibling(); 424 digest->m_children.append(childInfo.release()); 425 } 426 Element* element = toElement(node); 427 428 if (element->hasAttributesWithoutUpdate()) { 429 size_t numAttrs = element->attributeCount(); 430 SHA1 attrsSHA1; 431 for (size_t i = 0; i < numAttrs; ++i) { 432 const Attribute* attribute = element->attributeItem(i); 433 addStringToSHA1(attrsSHA1, attribute->name().toString()); 434 addStringToSHA1(attrsSHA1, attribute->value()); 435 } 436 Vector<uint8_t, 20> attrsHash; 437 attrsSHA1.computeHash(attrsHash); 438 digest->m_attrsSHA1 = base64Encode(reinterpret_cast<const char*>(attrsHash.data()), 10); 439 addStringToSHA1(sha1, digest->m_attrsSHA1); 440 } 441 } 442 443 Vector<uint8_t, 20> hash; 444 sha1.computeHash(hash); 445 digest->m_sha1 = base64Encode(reinterpret_cast<const char*>(hash.data()), 10); 446 if (unusedNodesMap) 447 unusedNodesMap->add(digest->m_sha1, digest); 448 return adoptPtr(digest); 449 } 450 451 bool DOMPatchSupport::insertBeforeAndMarkAsUsed(ContainerNode* parentNode, Digest* digest, Node* anchor, ExceptionState& exceptionState) 452 { 453 bool result = m_domEditor->insertBefore(parentNode, digest->m_node, anchor, exceptionState); 454 markNodeAsUsed(digest); 455 return result; 456 } 457 458 bool DOMPatchSupport::removeChildAndMoveToNew(Digest* oldDigest, ExceptionState& exceptionState) 459 { 460 RefPtr<Node> oldNode = oldDigest->m_node; 461 if (!m_domEditor->removeChild(oldNode->parentNode(), oldNode.get(), exceptionState)) 462 return false; 463 464 // Diff works within levels. In order not to lose the node identity when user 465 // prepends his HTML with "<div>" (i.e. all nodes are shifted to the next nested level), 466 // prior to dropping the original node on the floor, check whether new DOM has a digest 467 // with matching sha1. If it does, replace it with the original DOM chunk. Chances are 468 // high that it will get merged back into the original DOM during the further patching. 469 UnusedNodesMap::iterator it = m_unusedNodesMap.find(oldDigest->m_sha1); 470 if (it != m_unusedNodesMap.end()) { 471 Digest* newDigest = it->value; 472 Node* newNode = newDigest->m_node; 473 if (!m_domEditor->replaceChild(newNode->parentNode(), oldNode, newNode, exceptionState)) 474 return false; 475 newDigest->m_node = oldNode.get(); 476 markNodeAsUsed(newDigest); 477 return true; 478 } 479 480 for (size_t i = 0; i < oldDigest->m_children.size(); ++i) { 481 if (!removeChildAndMoveToNew(oldDigest->m_children[i].get(), exceptionState)) 482 return false; 483 } 484 return true; 485 } 486 487 void DOMPatchSupport::markNodeAsUsed(Digest* digest) 488 { 489 Deque<Digest*> queue; 490 queue.append(digest); 491 while (!queue.isEmpty()) { 492 Digest* first = queue.takeFirst(); 493 m_unusedNodesMap.remove(first->m_sha1); 494 for (size_t i = 0; i < first->m_children.size(); ++i) 495 queue.append(first->m_children[i].get()); 496 } 497 } 498 499 #ifdef DEBUG_DOM_PATCH_SUPPORT 500 static String nodeName(Node* node) 501 { 502 if (node->document().isXHTMLDocument()) 503 return node->nodeName(); 504 return node->nodeName().lower(); 505 } 506 507 void DOMPatchSupport::dumpMap(const ResultMap& map, const String& name) 508 { 509 fprintf(stderr, "\n\n"); 510 for (size_t i = 0; i < map.size(); ++i) 511 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); 512 } 513 #endif 514 515 } // namespace WebCore 516 517