String WebPageSerializerImpl::postActionAfterSerializeOpenTag( const Element* element, SerializeDomParam* param) { StringBuilder result; param->haveAddedContentsBeforeEnd = false; if (!param->isHTMLDocument) return result.toString(); // Check after processing the open tag of HEAD element if (!param->haveAddedCharsetDeclaration && isHTMLHeadElement(*element)) { param->haveAddedCharsetDeclaration = true; // Check meta element. WebKit only pre-parse the first 512 bytes // of the document. If the whole <HEAD> is larger and meta is the // end of head part, then this kind of pages aren't decoded correctly // because of this issue. So when we serialize the DOM, we need to // make sure the meta will in first child of head tag. // See http://bugs.webkit.org/show_bug.cgi?id=16621. // First we generate new content for writing correct META element. result.append(WebPageSerializer::generateMetaCharsetDeclaration( String(param->textEncoding.name()))); param->haveAddedContentsBeforeEnd = true; // Will search each META which has charset declaration, and skip them all // in PreActionBeforeSerializeOpenTag. } else if (isHTMLScriptElement(*element) || isHTMLScriptElement(*element)) { param->isInScriptOrStyleTag = true; } return result.toString(); }
Node* DOMPatchSupport::patchNode(Node* node, const String& markup, ExceptionState& exceptionState) { // Don't parse <html> as a fragment. if (node->isDocumentNode() || (node->parentNode() && node->parentNode()->isDocumentNode())) { patchDocument(markup); return 0; } Node* previousSibling = node->previousSibling(); RefPtrWillBeRawPtr<DocumentFragment> fragment = DocumentFragment::create(m_document); Node* targetNode = node->parentElementOrShadowRoot() ? node->parentElementOrShadowRoot() : m_document.documentElement(); // Use the document BODY as the context element when editing immediate shadow root children, // as it provides an equivalent parsing context. if (targetNode->isShadowRoot()) targetNode = m_document.body(); Element* targetElement = toElement(targetNode); // FIXME: This code should use one of createFragment* in markup.h if (m_document.isHTMLDocument()) fragment->parseHTML(markup, targetElement); else fragment->parseXML(markup, targetElement); // Compose the old list. ContainerNode* parentNode = node->parentNode(); Vector<OwnPtr<Digest> > oldList; for (Node* child = parentNode->firstChild(); child; child = child->nextSibling()) oldList.append(createDigest(child, 0)); // Compose the new list. String markupCopy = markup.lower(); Vector<OwnPtr<Digest> > newList; for (Node* child = parentNode->firstChild(); child != node; child = child->nextSibling()) newList.append(createDigest(child, 0)); for (Node* child = fragment->firstChild(); child; child = child->nextSibling()) { if (isHTMLHeadElement(*child) && !child->firstChild() && markupCopy.find("</head>") == kNotFound) continue; // HTML5 parser inserts empty <head> tag whenever it parses <body> if (isHTMLBodyElement(*child) && !child->firstChild() && markupCopy.find("</body>") == kNotFound) continue; // HTML5 parser inserts empty <body> tag whenever it parses </head> newList.append(createDigest(child, &m_unusedNodesMap)); } for (Node* child = node->nextSibling(); child; child = child->nextSibling()) newList.append(createDigest(child, 0)); if (!innerPatchChildren(parentNode, oldList, newList, exceptionState)) { // Fall back to total replace. if (!m_domEditor->replaceChild(parentNode, fragment.release(), node, exceptionState)) return 0; } return previousSibling ? previousSibling->nextSibling() : parentNode->firstChild(); }
DocumentFragment* createContextualFragment( const String& markup, Element* element, ParserContentPolicy parserContentPolicy, ExceptionState& exceptionState) { DCHECK(element); if (!isSupportedContainer(element)) { exceptionState.throwDOMException( NotSupportedError, "The range's container is '" + element->localName() + "', which is not supported."); return nullptr; } DocumentFragment* fragment = createFragmentForInnerOuterHTML( markup, element, parserContentPolicy, "createContextualFragment", exceptionState); if (!fragment) return nullptr; // We need to pop <html> and <body> elements and remove <head> to // accommodate folks passing complete HTML documents to make the // child of an element. Node* nextNode = nullptr; for (Node* node = fragment->firstChild(); node; node = nextNode) { nextNode = node->nextSibling(); if (isHTMLHtmlElement(*node) || isHTMLHeadElement(*node) || isHTMLBodyElement(*node)) { HTMLElement* element = toHTMLElement(node); if (Node* firstChild = element->firstChild()) nextNode = firstChild; removeElementPreservingChildren(fragment, element); } } return fragment; }
bool DOMPatchSupport::innerPatchChildren(ContainerNode* parentNode, const Vector<OwnPtr<Digest> >& oldList, const Vector<OwnPtr<Digest> >& newList, ExceptionState& exceptionState) { pair<ResultMap, ResultMap> resultMaps = diff(oldList, newList); ResultMap& oldMap = resultMaps.first; ResultMap& newMap = resultMaps.second; Digest* oldHead = 0; Digest* oldBody = 0; // 1. First strip everything except for the nodes that retain. Collect pending merges. HashMap<Digest*, Digest*> merges; HashSet<size_t, WTF::IntHash<size_t>, WTF::UnsignedWithZeroKeyHashTraits<size_t> > usedNewOrdinals; for (size_t i = 0; i < oldList.size(); ++i) { if (oldMap[i].first) { if (usedNewOrdinals.add(oldMap[i].second).isNewEntry) continue; oldMap[i].first = 0; oldMap[i].second = 0; } // Always match <head> and <body> tags with each other - we can't remove them from the DOM // upon patching. if (isHTMLHeadElement(*oldList[i]->m_node)) { oldHead = oldList[i].get(); continue; } if (isHTMLBodyElement(*oldList[i]->m_node)) { oldBody = oldList[i].get(); continue; } // Check if this change is between stable nodes. If it is, consider it as "modified". if (!m_unusedNodesMap.contains(oldList[i]->m_sha1) && (!i || oldMap[i - 1].first) && (i == oldMap.size() - 1 || oldMap[i + 1].first)) { size_t anchorCandidate = i ? oldMap[i - 1].second + 1 : 0; size_t anchorAfter = (i == oldMap.size() - 1) ? anchorCandidate + 1 : oldMap[i + 1].second; if (anchorAfter - anchorCandidate == 1 && anchorCandidate < newList.size()) merges.set(newList[anchorCandidate].get(), oldList[i].get()); else { if (!removeChildAndMoveToNew(oldList[i].get(), exceptionState)) return false; } } else { if (!removeChildAndMoveToNew(oldList[i].get(), exceptionState)) return false; } } // Mark retained nodes as used, do not reuse node more than once. HashSet<size_t, WTF::IntHash<size_t>, WTF::UnsignedWithZeroKeyHashTraits<size_t> > usedOldOrdinals; for (size_t i = 0; i < newList.size(); ++i) { if (!newMap[i].first) continue; size_t oldOrdinal = newMap[i].second; if (usedOldOrdinals.contains(oldOrdinal)) { // Do not map node more than once newMap[i].first = 0; newMap[i].second = 0; continue; } usedOldOrdinals.add(oldOrdinal); markNodeAsUsed(newMap[i].first); } // Mark <head> and <body> nodes for merge. if (oldHead || oldBody) { for (size_t i = 0; i < newList.size(); ++i) { if (oldHead && isHTMLHeadElement(*newList[i]->m_node)) merges.set(newList[i].get(), oldHead); if (oldBody && isHTMLBodyElement(*newList[i]->m_node)) merges.set(newList[i].get(), oldBody); } } // 2. Patch nodes marked for merge. for (HashMap<Digest*, Digest*>::iterator it = merges.begin(); it != merges.end(); ++it) { if (!innerPatchNode(it->value, it->key, exceptionState)) return false; } // 3. Insert missing nodes. for (size_t i = 0; i < newMap.size(); ++i) { if (newMap[i].first || merges.contains(newList[i].get())) continue; if (!insertBeforeAndMarkAsUsed(parentNode, newList[i].get(), parentNode->traverseToChildAt(i), exceptionState)) return false; } // 4. Then put all nodes that retained into their slots (sort by new index). for (size_t i = 0; i < oldMap.size(); ++i) { if (!oldMap[i].first) continue; RefPtrWillBeRawPtr<Node> node = oldMap[i].first->m_node; Node* anchorNode = parentNode->traverseToChildAt(oldMap[i].second); if (node == anchorNode) continue; if (isHTMLBodyElement(*node) || isHTMLHeadElement(*node)) continue; // Never move head or body, move the rest of the nodes around them. if (!m_domEditor->insertBefore(parentNode, node.release(), anchorNode, exceptionState)) return false; } return true; }