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