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