Upstream version 9.38.198.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/core/v8/ExceptionState.h"
35 #include "bindings/core/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/NodeTraversal.h"
42 #include "core/dom/XMLDocument.h"
43 #include "core/html/HTMLBodyElement.h"
44 #include "core/html/HTMLDocument.h"
45 #include "core/html/HTMLHeadElement.h"
46 #include "core/html/parser/HTMLDocumentParser.h"
47 #include "core/inspector/DOMEditor.h"
48 #include "core/inspector/InspectorHistory.h"
49 #include "core/xml/parser/XMLDocumentParser.h"
50 #include "platform/Crypto.h"
51 #include "public/platform/Platform.h"
52 #include "wtf/Deque.h"
53 #include "wtf/HashTraits.h"
54 #include "wtf/RefPtr.h"
55 #include "wtf/text/Base64.h"
56 #include "wtf/text/CString.h"
57
58 namespace blink {
59
60 struct DOMPatchSupport::Digest {
61     explicit Digest(Node* node) : m_node(node) { }
62
63     String m_sha1;
64     String m_attrsSHA1;
65     Node* m_node;
66     Vector<OwnPtr<Digest> > m_children;
67 };
68
69 void DOMPatchSupport::patchDocument(Document& document, const String& markup)
70 {
71     InspectorHistory history;
72     DOMEditor domEditor(&history);
73     DOMPatchSupport patchSupport(&domEditor, document);
74     patchSupport.patchDocument(markup);
75 }
76
77 DOMPatchSupport::DOMPatchSupport(DOMEditor* domEditor, Document& document)
78     : m_domEditor(domEditor)
79     , m_document(document)
80 {
81 }
82
83 void DOMPatchSupport::patchDocument(const String& markup)
84 {
85     RefPtrWillBeRawPtr<Document> newDocument = nullptr;
86     if (document().isHTMLDocument())
87         newDocument = HTMLDocument::create();
88     else if (document().isXHTMLDocument())
89         newDocument = XMLDocument::createXHTML();
90     else if (document().isXMLDocument())
91         newDocument = XMLDocument::create();
92
93     ASSERT(newDocument);
94     newDocument->setContextFeatures(document().contextFeatures());
95     RefPtrWillBeRawPtr<DocumentParser> parser = nullptr;
96     if (document().isHTMLDocument())
97         parser = HTMLDocumentParser::create(toHTMLDocument(*newDocument), false);
98     else
99         parser = XMLDocumentParser::create(*newDocument, 0);
100     parser->insert(markup); // Use insert() so that the parser will not yield.
101     parser->finish();
102     parser->detach();
103
104     OwnPtr<Digest> oldInfo = createDigest(document().documentElement(), 0);
105     OwnPtr<Digest> newInfo = createDigest(newDocument->documentElement(), &m_unusedNodesMap);
106
107     if (!innerPatchNode(oldInfo.get(), newInfo.get(), IGNORE_EXCEPTION)) {
108         // Fall back to rewrite.
109         document().write(markup);
110         document().close();
111     }
112 }
113
114 Node* DOMPatchSupport::patchNode(Node* node, const String& markup, ExceptionState& exceptionState)
115 {
116     // Don't parse <html> as a fragment.
117     if (node->isDocumentNode() || (node->parentNode() && node->parentNode()->isDocumentNode())) {
118         patchDocument(markup);
119         return 0;
120     }
121
122     Node* previousSibling = node->previousSibling();
123     RefPtrWillBeRawPtr<DocumentFragment> fragment = DocumentFragment::create(document());
124     Node* targetNode = node->parentElementOrShadowRoot() ? node->parentElementOrShadowRoot() : document().documentElement();
125
126     // Use the document BODY as the context element when editing immediate shadow root children,
127     // as it provides an equivalent parsing context.
128     if (targetNode->isShadowRoot())
129         targetNode = document().body();
130     Element* targetElement = toElement(targetNode);
131
132     // FIXME: This code should use one of createFragment* in markup.h
133     if (document().isHTMLDocument())
134         fragment->parseHTML(markup, targetElement);
135     else
136         fragment->parseXML(markup, targetElement);
137
138     // Compose the old list.
139     ContainerNode* parentNode = node->parentNode();
140     Vector<OwnPtr<Digest> > oldList;
141     for (Node* child = parentNode->firstChild(); child; child = child->nextSibling())
142         oldList.append(createDigest(child, 0));
143
144     // Compose the new list.
145     String markupCopy = markup.lower();
146     Vector<OwnPtr<Digest> > newList;
147     for (Node* child = parentNode->firstChild(); child != node; child = child->nextSibling())
148         newList.append(createDigest(child, 0));
149     for (Node* child = fragment->firstChild(); child; child = child->nextSibling()) {
150         if (isHTMLHeadElement(*child) && !child->hasChildren() && markupCopy.find("</head>") == kNotFound)
151             continue; // HTML5 parser inserts empty <head> tag whenever it parses <body>
152         if (isHTMLBodyElement(*child) && !child->hasChildren() && markupCopy.find("</body>") == kNotFound)
153             continue; // HTML5 parser inserts empty <body> tag whenever it parses </head>
154         newList.append(createDigest(child, &m_unusedNodesMap));
155     }
156     for (Node* child = node->nextSibling(); child; child = child->nextSibling())
157         newList.append(createDigest(child, 0));
158
159     if (!innerPatchChildren(parentNode, oldList, newList, exceptionState)) {
160         // Fall back to total replace.
161         if (!m_domEditor->replaceChild(parentNode, fragment.release(), node, exceptionState))
162             return 0;
163     }
164     return previousSibling ? previousSibling->nextSibling() : parentNode->firstChild();
165 }
166
167 bool DOMPatchSupport::innerPatchNode(Digest* oldDigest, Digest* newDigest, ExceptionState& exceptionState)
168 {
169     if (oldDigest->m_sha1 == newDigest->m_sha1)
170         return true;
171
172     Node* oldNode = oldDigest->m_node;
173     Node* newNode = newDigest->m_node;
174
175     if (newNode->nodeType() != oldNode->nodeType() || newNode->nodeName() != oldNode->nodeName())
176         return m_domEditor->replaceChild(oldNode->parentNode(), newNode, oldNode, exceptionState);
177
178     if (oldNode->nodeValue() != newNode->nodeValue()) {
179         if (!m_domEditor->setNodeValue(oldNode, newNode->nodeValue(), exceptionState))
180             return false;
181     }
182
183     if (!oldNode->isElementNode())
184         return true;
185
186     // Patch attributes
187     Element* oldElement = toElement(oldNode);
188     Element* newElement = toElement(newNode);
189     if (oldDigest->m_attrsSHA1 != newDigest->m_attrsSHA1) {
190         // FIXME: Create a function in Element for removing all properties. Take in account whether did/willModifyAttribute are important.
191         while (oldElement->attributesWithoutUpdate().size()) {
192             const Attribute& attribute = oldElement->attributesWithoutUpdate().at(0);
193             if (!m_domEditor->removeAttribute(oldElement, attribute.localName(), exceptionState))
194                 return false;
195         }
196
197         // FIXME: Create a function in Element for copying properties. cloneDataFromElement() is close but not enough for this case.
198         AttributeCollection attributes = newElement->attributesWithoutUpdate();
199         AttributeCollection::iterator end = attributes.end();
200         for (AttributeCollection::iterator it = attributes.begin(); it != end; ++it) {
201             if (!m_domEditor->setAttribute(oldElement, it->name().localName(), it->value(), exceptionState))
202                 return false;
203         }
204     }
205
206     bool result = innerPatchChildren(oldElement, oldDigest->m_children, newDigest->m_children, exceptionState);
207     m_unusedNodesMap.remove(newDigest->m_sha1);
208     return result;
209 }
210
211 pair<DOMPatchSupport::ResultMap, DOMPatchSupport::ResultMap>
212 DOMPatchSupport::diff(const Vector<OwnPtr<Digest> >& oldList, const Vector<OwnPtr<Digest> >& newList)
213 {
214     ResultMap newMap(newList.size());
215     ResultMap oldMap(oldList.size());
216
217     for (size_t i = 0; i < oldMap.size(); ++i) {
218         oldMap[i].first = 0;
219         oldMap[i].second = 0;
220     }
221
222     for (size_t i = 0; i < newMap.size(); ++i) {
223         newMap[i].first = 0;
224         newMap[i].second = 0;
225     }
226
227     // Trim head and tail.
228     for (size_t i = 0; i < oldList.size() && i < newList.size() && oldList[i]->m_sha1 == newList[i]->m_sha1; ++i) {
229         oldMap[i].first = oldList[i].get();
230         oldMap[i].second = i;
231         newMap[i].first = newList[i].get();
232         newMap[i].second = i;
233     }
234     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) {
235         size_t oldIndex = oldList.size() - i - 1;
236         size_t newIndex = newList.size() - i - 1;
237         oldMap[oldIndex].first = oldList[oldIndex].get();
238         oldMap[oldIndex].second = newIndex;
239         newMap[newIndex].first = newList[newIndex].get();
240         newMap[newIndex].second = oldIndex;
241     }
242
243     typedef HashMap<String, Vector<size_t> > DiffTable;
244     DiffTable newTable;
245     DiffTable oldTable;
246
247     for (size_t i = 0; i < newList.size(); ++i) {
248         newTable.add(newList[i]->m_sha1, Vector<size_t>()).storedValue->value.append(i);
249     }
250
251     for (size_t i = 0; i < oldList.size(); ++i) {
252         oldTable.add(oldList[i]->m_sha1, Vector<size_t>()).storedValue->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]] = std::make_pair(newList[newIt->value[0]].get(), oldIt->value[0]);
264         oldMap[oldIt->value[0]] = std::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] = std::make_pair(newList[i + 1].get(), j);
274             oldMap[j] = std::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] = std::make_pair(newList[i - 1].get(), j);
285             oldMap[j] = std::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 std::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 (isHTMLHeadElement(*oldList[i]->m_node)) {
320             oldHead = oldList[i].get();
321             continue;
322         }
323         if (isHTMLBodyElement(*oldList[i]->m_node)) {
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 && isHTMLHeadElement(*newList[i]->m_node))
364                 merges.set(newList[i].get(), oldHead);
365             if (oldBody && isHTMLBodyElement(*newList[i]->m_node))
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(), NodeTraversal::childAt(*parentNode, 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         RefPtrWillBeRawPtr<Node> node = oldMap[i].first->m_node;
389         Node* anchorNode = NodeTraversal::childAt(*parentNode, oldMap[i].second);
390         if (node == anchorNode)
391             continue;
392         if (isHTMLBodyElement(*node) || isHTMLHeadElement(*node))
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 addStringToDigestor(blink::WebCryptoDigestor* digestor, const String& string)
402 {
403     digestor->consume(reinterpret_cast<const unsigned char*>(string.utf8().data()), string.length());
404 }
405
406 PassOwnPtr<DOMPatchSupport::Digest> DOMPatchSupport::createDigest(Node* node, UnusedNodesMap* unusedNodesMap)
407 {
408     Digest* digest = new Digest(node);
409
410     OwnPtr<blink::WebCryptoDigestor> digestor = createDigestor(HashAlgorithmSha1);
411     DigestValue digestResult;
412
413     Node::NodeType nodeType = node->nodeType();
414     digestor->consume(reinterpret_cast<const unsigned char*>(&nodeType), sizeof(nodeType));
415     addStringToDigestor(digestor.get(), node->nodeName());
416     addStringToDigestor(digestor.get(), node->nodeValue());
417
418     if (node->isElementNode()) {
419         Element& element = toElement(*node);
420         Node* child = element.firstChild();
421         while (child) {
422             OwnPtr<Digest> childInfo = createDigest(child, unusedNodesMap);
423             addStringToDigestor(digestor.get(), childInfo->m_sha1);
424             child = child->nextSibling();
425             digest->m_children.append(childInfo.release());
426         }
427
428         AttributeCollection attributes = element.attributesWithoutUpdate();
429         if (!attributes.isEmpty()) {
430             OwnPtr<blink::WebCryptoDigestor> attrsDigestor = createDigestor(HashAlgorithmSha1);
431             AttributeCollection::iterator end = attributes.end();
432             for (AttributeCollection::iterator it = attributes.begin(); it != end; ++it) {
433                 addStringToDigestor(attrsDigestor.get(), it->name().toString());
434                 addStringToDigestor(attrsDigestor.get(), it->value().string());
435             }
436             finishDigestor(attrsDigestor.get(), digestResult);
437             digest->m_attrsSHA1 = base64Encode(reinterpret_cast<const char*>(digestResult.data()), 10);
438             addStringToDigestor(digestor.get(), digest->m_attrsSHA1);
439             digestResult.clear();
440         }
441     }
442     finishDigestor(digestor.get(), digestResult);
443     digest->m_sha1 = base64Encode(reinterpret_cast<const char*>(digestResult.data()), 10);
444
445     if (unusedNodesMap)
446         unusedNodesMap->add(digest->m_sha1, digest);
447     return adoptPtr(digest);
448 }
449
450 bool DOMPatchSupport::insertBeforeAndMarkAsUsed(ContainerNode* parentNode, Digest* digest, Node* anchor, ExceptionState& exceptionState)
451 {
452     bool result = m_domEditor->insertBefore(parentNode, digest->m_node, anchor, exceptionState);
453     markNodeAsUsed(digest);
454     return result;
455 }
456
457 bool DOMPatchSupport::removeChildAndMoveToNew(Digest* oldDigest, ExceptionState& exceptionState)
458 {
459     RefPtrWillBeRawPtr<Node> oldNode = oldDigest->m_node;
460     if (!m_domEditor->removeChild(oldNode->parentNode(), oldNode.get(), exceptionState))
461         return false;
462
463     // Diff works within levels. In order not to lose the node identity when user
464     // prepends his HTML with "<div>" (i.e. all nodes are shifted to the next nested level),
465     // prior to dropping the original node on the floor, check whether new DOM has a digest
466     // with matching sha1. If it does, replace it with the original DOM chunk. Chances are
467     // high that it will get merged back into the original DOM during the further patching.
468     UnusedNodesMap::iterator it = m_unusedNodesMap.find(oldDigest->m_sha1);
469     if (it != m_unusedNodesMap.end()) {
470         Digest* newDigest = it->value;
471         Node* newNode = newDigest->m_node;
472         if (!m_domEditor->replaceChild(newNode->parentNode(), oldNode, newNode, exceptionState))
473             return false;
474         newDigest->m_node = oldNode.get();
475         markNodeAsUsed(newDigest);
476         return true;
477     }
478
479     for (size_t i = 0; i < oldDigest->m_children.size(); ++i) {
480         if (!removeChildAndMoveToNew(oldDigest->m_children[i].get(), exceptionState))
481             return false;
482     }
483     return true;
484 }
485
486 void DOMPatchSupport::markNodeAsUsed(Digest* digest)
487 {
488     Deque<Digest*> queue;
489     queue.append(digest);
490     while (!queue.isEmpty()) {
491         Digest* first = queue.takeFirst();
492         m_unusedNodesMap.remove(first->m_sha1);
493         for (size_t i = 0; i < first->m_children.size(); ++i)
494             queue.append(first->m_children[i].get());
495     }
496 }
497
498 #ifdef DEBUG_DOM_PATCH_SUPPORT
499 static String nodeName(Node* node)
500 {
501     if (node->document().isXHTMLDocument())
502          return node->nodeName();
503     return node->nodeName().lower();
504 }
505
506 void DOMPatchSupport::dumpMap(const ResultMap& map, const String& name)
507 {
508     fprintf(stderr, "\n\n");
509     for (size_t i = 0; i < map.size(); ++i)
510         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);
511 }
512 #endif
513
514 } // namespace blink
515