Home | History | Annotate | Download | only in inspector
      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 DOMPatchSupport::~DOMPatchSupport() { }
     86 
     87 void DOMPatchSupport::patchDocument(const String& markup)
     88 {
     89     RefPtr<Document> newDocument;
     90     if (m_document->isHTMLDocument())
     91         newDocument = HTMLDocument::create();
     92     else if (m_document->isXHTMLDocument())
     93         newDocument = HTMLDocument::createXHTML();
     94     else if (m_document->isSVGDocument())
     95         newDocument = Document::create();
     96 
     97     ASSERT(newDocument);
     98     newDocument->setContextFeatures(m_document->contextFeatures());
     99     RefPtr<DocumentParser> parser;
    100     if (m_document->isHTMLDocument())
    101         parser = HTMLDocumentParser::create(static_cast<HTMLDocument*>(newDocument.get()), false);
    102     else
    103         parser = XMLDocumentParser::create(newDocument.get(), 0);
    104     parser->insert(markup); // Use insert() so that the parser will not yield.
    105     parser->finish();
    106     parser->detach();
    107 
    108     OwnPtr<Digest> oldInfo = createDigest(m_document->documentElement(), 0);
    109     OwnPtr<Digest> newInfo = createDigest(newDocument->documentElement(), &m_unusedNodesMap);
    110 
    111     if (!innerPatchNode(oldInfo.get(), newInfo.get(), IGNORE_EXCEPTION)) {
    112         // Fall back to rewrite.
    113         m_document->write(markup);
    114         m_document->close();
    115     }
    116 }
    117 
    118 Node* DOMPatchSupport::patchNode(Node* node, const String& markup, ExceptionState& es)
    119 {
    120     // Don't parse <html> as a fragment.
    121     if (node->isDocumentNode() || (node->parentNode() && node->parentNode()->isDocumentNode())) {
    122         patchDocument(markup);
    123         return 0;
    124     }
    125 
    126     Node* previousSibling = node->previousSibling();
    127     // FIXME: This code should use one of createFragment* in markup.h
    128     RefPtr<DocumentFragment> fragment = DocumentFragment::create(m_document);
    129     if (m_document->isHTMLDocument())
    130         fragment->parseHTML(markup, node->parentElement() ? node->parentElement() : m_document->documentElement());
    131     else
    132         fragment->parseXML(markup, node->parentElement() ? node->parentElement() : m_document->documentElement());
    133 
    134     // Compose the old list.
    135     ContainerNode* parentNode = node->parentNode();
    136     Vector<OwnPtr<Digest> > oldList;
    137     for (Node* child = parentNode->firstChild(); child; child = child->nextSibling())
    138         oldList.append(createDigest(child, 0));
    139 
    140     // Compose the new list.
    141     String markupCopy = markup;
    142     markupCopy.makeLower();
    143     Vector<OwnPtr<Digest> > newList;
    144     for (Node* child = parentNode->firstChild(); child != node; child = child->nextSibling())
    145         newList.append(createDigest(child, 0));
    146     for (Node* child = fragment->firstChild(); child; child = child->nextSibling()) {
    147         if (child->hasTagName(headTag) && !child->firstChild() && markupCopy.find("</head>") == notFound)
    148             continue; // HTML5 parser inserts empty <head> tag whenever it parses <body>
    149         if (child->hasTagName(bodyTag) && !child->firstChild() && markupCopy.find("</body>") == notFound)
    150             continue; // HTML5 parser inserts empty <body> tag whenever it parses </head>
    151         newList.append(createDigest(child, &m_unusedNodesMap));
    152     }
    153     for (Node* child = node->nextSibling(); child; child = child->nextSibling())
    154         newList.append(createDigest(child, 0));
    155 
    156     if (!innerPatchChildren(parentNode, oldList, newList, es)) {
    157         // Fall back to total replace.
    158         if (!m_domEditor->replaceChild(parentNode, fragment.release(), node, es))
    159             return 0;
    160     }
    161     return previousSibling ? previousSibling->nextSibling() : parentNode->firstChild();
    162 }
    163 
    164 bool DOMPatchSupport::innerPatchNode(Digest* oldDigest, Digest* newDigest, ExceptionState& es)
    165 {
    166     if (oldDigest->m_sha1 == newDigest->m_sha1)
    167         return true;
    168 
    169     Node* oldNode = oldDigest->m_node;
    170     Node* newNode = newDigest->m_node;
    171 
    172     if (newNode->nodeType() != oldNode->nodeType() || newNode->nodeName() != oldNode->nodeName())
    173         return m_domEditor->replaceChild(oldNode->parentNode(), newNode, oldNode, es);
    174 
    175     if (oldNode->nodeValue() != newNode->nodeValue()) {
    176         if (!m_domEditor->setNodeValue(oldNode, newNode->nodeValue(), es))
    177             return false;
    178     }
    179 
    180     if (oldNode->nodeType() != Node::ELEMENT_NODE)
    181         return true;
    182 
    183     // Patch attributes
    184     Element* oldElement = toElement(oldNode);
    185     Element* newElement = toElement(newNode);
    186     if (oldDigest->m_attrsSHA1 != newDigest->m_attrsSHA1) {
    187         // FIXME: Create a function in Element for removing all properties. Take in account whether did/willModifyAttribute are important.
    188         if (oldElement->hasAttributesWithoutUpdate()) {
    189             while (oldElement->attributeCount()) {
    190                 const Attribute* attribute = oldElement->attributeItem(0);
    191                 if (!m_domEditor->removeAttribute(oldElement, attribute->localName(), es))
    192                     return false;
    193             }
    194         }
    195 
    196         // FIXME: Create a function in Element for copying properties. cloneDataFromElement() is close but not enough for this case.
    197         if (newElement->hasAttributesWithoutUpdate()) {
    198             size_t numAttrs = newElement->attributeCount();
    199             for (size_t i = 0; i < numAttrs; ++i) {
    200                 const Attribute* attribute = newElement->attributeItem(i);
    201                 if (!m_domEditor->setAttribute(oldElement, attribute->name().localName(), attribute->value(), es))
    202                     return false;
    203             }
    204         }
    205     }
    206 
    207     bool result = innerPatchChildren(oldElement, oldDigest->m_children, newDigest->m_children, es);
    208     m_unusedNodesMap.remove(newDigest->m_sha1);
    209     return result;
    210 }
    211 
    212 pair<DOMPatchSupport::ResultMap, DOMPatchSupport::ResultMap>
    213 DOMPatchSupport::diff(const Vector<OwnPtr<Digest> >& oldList, const Vector<OwnPtr<Digest> >& newList)
    214 {
    215     ResultMap newMap(newList.size());
    216     ResultMap oldMap(oldList.size());
    217 
    218     for (size_t i = 0; i < oldMap.size(); ++i) {
    219         oldMap[i].first = 0;
    220         oldMap[i].second = 0;
    221     }
    222 
    223     for (size_t i = 0; i < newMap.size(); ++i) {
    224         newMap[i].first = 0;
    225         newMap[i].second = 0;
    226     }
    227 
    228     // Trim head and tail.
    229     for (size_t i = 0; i < oldList.size() && i < newList.size() && oldList[i]->m_sha1 == newList[i]->m_sha1; ++i) {
    230         oldMap[i].first = oldList[i].get();
    231         oldMap[i].second = i;
    232         newMap[i].first = newList[i].get();
    233         newMap[i].second = i;
    234     }
    235     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) {
    236         size_t oldIndex = oldList.size() - i - 1;
    237         size_t newIndex = newList.size() - i - 1;
    238         oldMap[oldIndex].first = oldList[oldIndex].get();
    239         oldMap[oldIndex].second = newIndex;
    240         newMap[newIndex].first = newList[newIndex].get();
    241         newMap[newIndex].second = oldIndex;
    242     }
    243 
    244     typedef HashMap<String, Vector<size_t> > DiffTable;
    245     DiffTable newTable;
    246     DiffTable oldTable;
    247 
    248     for (size_t i = 0; i < newList.size(); ++i) {
    249         DiffTable::iterator it = newTable.add(newList[i]->m_sha1, Vector<size_t>()).iterator;
    250         it->value.append(i);
    251     }
    252 
    253     for (size_t i = 0; i < oldList.size(); ++i) {
    254         DiffTable::iterator it = oldTable.add(oldList[i]->m_sha1, Vector<size_t>()).iterator;
    255         it->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]] = make_pair(newList[newIt->value[0]].get(), oldIt->value[0]);
    267         oldMap[oldIt->value[0]] = 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] = make_pair(newList[i + 1].get(), j);
    277             oldMap[j] = 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] = make_pair(newList[i - 1].get(), j);
    288             oldMap[j] = 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 make_pair(oldMap, newMap);
    298 }
    299 
    300 bool DOMPatchSupport::innerPatchChildren(ContainerNode* parentNode, const Vector<OwnPtr<Digest> >& oldList, const Vector<OwnPtr<Digest> >& newList, ExceptionState& es)
    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 (oldList[i]->m_node->hasTagName(headTag)) {
    323             oldHead = oldList[i].get();
    324             continue;
    325         }
    326         if (oldList[i]->m_node->hasTagName(bodyTag)) {
    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(), es))
    339                     return false;
    340             }
    341         } else {
    342             if (!removeChildAndMoveToNew(oldList[i].get(), es))
    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 && newList[i]->m_node->hasTagName(headTag))
    367                 merges.set(newList[i].get(), oldHead);
    368             if (oldBody && newList[i]->m_node->hasTagName(bodyTag))
    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, es))
    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->childNode(i), es))
    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         RefPtr<Node> node = oldMap[i].first->m_node;
    392         Node* anchorNode = parentNode->childNode(oldMap[i].second);
    393         if (node.get() == anchorNode)
    394             continue;
    395         if (node->hasTagName(bodyTag) || node->hasTagName(headTag))
    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, es))
    399             return false;
    400     }
    401     return true;
    402 }
    403 
    404 static void addStringToSHA1(SHA1& sha1, const String& string)
    405 {
    406     CString cString = string.utf8();
    407     sha1.addBytes(reinterpret_cast<const uint8_t*>(cString.data()), cString.length());
    408 }
    409 
    410 PassOwnPtr<DOMPatchSupport::Digest> DOMPatchSupport::createDigest(Node* node, UnusedNodesMap* unusedNodesMap)
    411 {
    412     Digest* digest = new Digest(node);
    413 
    414     SHA1 sha1;
    415 
    416     Node::NodeType nodeType = node->nodeType();
    417     sha1.addBytes(reinterpret_cast<const uint8_t*>(&nodeType), sizeof(nodeType));
    418     addStringToSHA1(sha1, node->nodeName());
    419     addStringToSHA1(sha1, node->nodeValue());
    420 
    421     if (node->nodeType() == Node::ELEMENT_NODE) {
    422         Node* child = node->firstChild();
    423         while (child) {
    424             OwnPtr<Digest> childInfo = createDigest(child, unusedNodesMap);
    425             addStringToSHA1(sha1, childInfo->m_sha1);
    426             child = child->nextSibling();
    427             digest->m_children.append(childInfo.release());
    428         }
    429         Element* element = toElement(node);
    430 
    431         if (element->hasAttributesWithoutUpdate()) {
    432             size_t numAttrs = element->attributeCount();
    433             SHA1 attrsSHA1;
    434             for (size_t i = 0; i < numAttrs; ++i) {
    435                 const Attribute* attribute = element->attributeItem(i);
    436                 addStringToSHA1(attrsSHA1, attribute->name().toString());
    437                 addStringToSHA1(attrsSHA1, attribute->value());
    438             }
    439             Vector<uint8_t, 20> attrsHash;
    440             attrsSHA1.computeHash(attrsHash);
    441             digest->m_attrsSHA1 = base64Encode(reinterpret_cast<const char*>(attrsHash.data()), 10);
    442             addStringToSHA1(sha1, digest->m_attrsSHA1);
    443         }
    444     }
    445 
    446     Vector<uint8_t, 20> hash;
    447     sha1.computeHash(hash);
    448     digest->m_sha1 = base64Encode(reinterpret_cast<const char*>(hash.data()), 10);
    449     if (unusedNodesMap)
    450         unusedNodesMap->add(digest->m_sha1, digest);
    451     return adoptPtr(digest);
    452 }
    453 
    454 bool DOMPatchSupport::insertBeforeAndMarkAsUsed(ContainerNode* parentNode, Digest* digest, Node* anchor, ExceptionState& es)
    455 {
    456     bool result = m_domEditor->insertBefore(parentNode, digest->m_node, anchor, es);
    457     markNodeAsUsed(digest);
    458     return result;
    459 }
    460 
    461 bool DOMPatchSupport::removeChildAndMoveToNew(Digest* oldDigest, ExceptionState& es)
    462 {
    463     RefPtr<Node> oldNode = oldDigest->m_node;
    464     if (!m_domEditor->removeChild(oldNode->parentNode(), oldNode.get(), es))
    465         return false;
    466 
    467     // Diff works within levels. In order not to lose the node identity when user
    468     // prepends his HTML with "<div>" (i.e. all nodes are shifted to the next nested level),
    469     // prior to dropping the original node on the floor, check whether new DOM has a digest
    470     // with matching sha1. If it does, replace it with the original DOM chunk. Chances are
    471     // high that it will get merged back into the original DOM during the further patching.
    472     UnusedNodesMap::iterator it = m_unusedNodesMap.find(oldDigest->m_sha1);
    473     if (it != m_unusedNodesMap.end()) {
    474         Digest* newDigest = it->value;
    475         Node* newNode = newDigest->m_node;
    476         if (!m_domEditor->replaceChild(newNode->parentNode(), oldNode, newNode, es))
    477             return false;
    478         newDigest->m_node = oldNode.get();
    479         markNodeAsUsed(newDigest);
    480         return true;
    481     }
    482 
    483     for (size_t i = 0; i < oldDigest->m_children.size(); ++i) {
    484         if (!removeChildAndMoveToNew(oldDigest->m_children[i].get(), es))
    485             return false;
    486     }
    487     return true;
    488 }
    489 
    490 void DOMPatchSupport::markNodeAsUsed(Digest* digest)
    491 {
    492     Deque<Digest*> queue;
    493     queue.append(digest);
    494     while (!queue.isEmpty()) {
    495         Digest* first = queue.takeFirst();
    496         m_unusedNodesMap.remove(first->m_sha1);
    497         for (size_t i = 0; i < first->m_children.size(); ++i)
    498             queue.append(first->m_children[i].get());
    499     }
    500 }
    501 
    502 #ifdef DEBUG_DOM_PATCH_SUPPORT
    503 static String nodeName(Node* node)
    504 {
    505     if (node->document()->isXHTMLDocument())
    506          return node->nodeName();
    507     return node->nodeName().lower();
    508 }
    509 
    510 void DOMPatchSupport::dumpMap(const ResultMap& map, const String& name)
    511 {
    512     fprintf(stderr, "\n\n");
    513     for (size_t i = 0; i < map.size(); ++i)
    514         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);
    515 }
    516 #endif
    517 
    518 } // namespace WebCore
    519 
    520