blob: 9eb487198422ffa2b422c4590097f7c4498860f0 [file] [log] [blame]
/*
* Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2012 Apple Inc. All rights
* reserved.
* Copyright (C) 2009, 2010 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "third_party/blink/renderer/core/editing/serializers/markup_accumulator.h"
#include "third_party/blink/renderer/core/dom/attr.h"
#include "third_party/blink/renderer/core/dom/cdata_section.h"
#include "third_party/blink/renderer/core/dom/comment.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/document_fragment.h"
#include "third_party/blink/renderer/core/dom/document_type.h"
#include "third_party/blink/renderer/core/dom/processing_instruction.h"
#include "third_party/blink/renderer/core/editing/editing_utilities.h"
#include "third_party/blink/renderer/core/editing/editor.h"
#include "third_party/blink/renderer/core/html/html_element.h"
#include "third_party/blink/renderer/core/html/html_template_element.h"
#include "third_party/blink/renderer/core/xml_names.h"
#include "third_party/blink/renderer/core/xmlns_names.h"
#include "third_party/blink/renderer/platform/weborigin/kurl.h"
#include "third_party/blink/renderer/platform/wtf/text/character_names.h"
namespace blink {
class MarkupAccumulator::NamespaceContext final {
USING_FAST_MALLOC(MarkupAccumulator::NamespaceContext);
public:
// https://w3c.github.io/DOM-Parsing/#dfn-add
//
// This function doesn't accept empty prefix and empty namespace URI.
// - The default namespace is managed separately.
// - Namespace URI never be empty if the prefix is not empty.
void Add(const AtomicString& prefix, const AtomicString& namespace_uri) {
DCHECK(!prefix.IsEmpty())
<< " prefix=" << prefix << " namespace_uri=" << namespace_uri;
DCHECK(!namespace_uri.IsEmpty())
<< " prefix=" << prefix << " namespace_uri=" << namespace_uri;
prefix_ns_map_.Set(prefix, namespace_uri);
auto result =
ns_prefixes_map_.insert(namespace_uri, Vector<AtomicString>());
result.stored_value->value.push_back(prefix);
}
// https://w3c.github.io/DOM-Parsing/#dfn-recording-the-namespace-information
AtomicString RecordNamespaceInformation(const Element& element) {
AtomicString local_default_namespace;
// 2. For each attribute attr in element's attributes, in the order they are
// specified in the element's attribute list:
for (const auto& attr : element.Attributes()) {
// We don't check xmlns namespace of attr here because xmlns attributes in
// HTML documents don't have namespace URI. Some web tests serialize
// HTML documents with XMLSerializer, and Firefox has the same behavior.
if (attr.Prefix().IsEmpty() && attr.LocalName() == g_xmlns_atom) {
// 3.1. If attribute prefix is null, then attr is a default namespace
// declaration. Set the default namespace attr value to attr's value
// and stop running these steps, returning to Main to visit the next
// attribute.
local_default_namespace = attr.Value();
} else if (attr.Prefix() == g_xmlns_atom) {
Add(attr.Prefix() ? attr.LocalName() : g_empty_atom, attr.Value());
}
}
// 3. Return the value of default namespace attr value.
return local_default_namespace;
}
AtomicString LookupNamespaceURI(const AtomicString& prefix) const {
return prefix_ns_map_.at(prefix ? prefix : g_empty_atom);
}
const AtomicString& ContextNamespace() const { return context_namespace_; }
void SetContextNamespace(const AtomicString& context_ns) {
context_namespace_ = context_ns;
}
void InheritLocalDefaultNamespace(
const AtomicString& local_default_namespace) {
if (!local_default_namespace)
return;
SetContextNamespace(local_default_namespace.IsEmpty()
? g_null_atom
: local_default_namespace);
}
const Vector<AtomicString> PrefixList(const AtomicString& ns) const {
return ns_prefixes_map_.at(ns ? ns : g_empty_atom);
}
private:
using PrefixToNamespaceMap = HashMap<AtomicString, AtomicString>;
PrefixToNamespaceMap prefix_ns_map_;
// Map a namespace URI to a list of prefixes.
// https://w3c.github.io/DOM-Parsing/#the-namespace-prefix-map
using NamespaceToPrefixesMap = HashMap<AtomicString, Vector<AtomicString>>;
NamespaceToPrefixesMap ns_prefixes_map_;
// https://w3c.github.io/DOM-Parsing/#dfn-context-namespace
AtomicString context_namespace_;
};
// This stores values used to serialize an element. The values are not
// inherited to child node serialization.
class MarkupAccumulator::ElementSerializationData final {
STACK_ALLOCATED();
public:
// https://w3c.github.io/DOM-Parsing/#dfn-ignore-namespace-definition-attribute
bool ignore_namespace_definition_attribute_ = false;
AtomicString serialized_prefix_;
};
MarkupAccumulator::MarkupAccumulator(AbsoluteURLs resolve_urls_method,
SerializationType serialization_type,
IncludeShadowRoots include_shadow_roots,
ClosedRootsSet include_closed_roots)
: formatter_(resolve_urls_method, serialization_type),
include_shadow_roots_(include_shadow_roots),
include_closed_roots_(include_closed_roots) {}
MarkupAccumulator::~MarkupAccumulator() = default;
void MarkupAccumulator::AppendString(const String& string) {
markup_.Append(string);
}
void MarkupAccumulator::AppendEndTag(const Element& element,
const AtomicString& prefix) {
formatter_.AppendEndMarkup(markup_, element, prefix, element.localName());
}
void MarkupAccumulator::AppendStartMarkup(const Node& node) {
switch (node.getNodeType()) {
case Node::kTextNode:
formatter_.AppendText(markup_, To<Text>(node));
break;
case Node::kElementNode:
NOTREACHED();
break;
case Node::kAttributeNode:
// Only XMLSerializer can pass an Attr. So, |documentIsHTML| flag is
// false.
formatter_.AppendAttributeValue(markup_, To<Attr>(node).value(), false);
break;
default:
formatter_.AppendStartMarkup(markup_, node);
break;
}
}
void MarkupAccumulator::AppendCustomAttributes(const Element&) {}
bool MarkupAccumulator::ShouldIgnoreAttribute(
const Element& element,
const Attribute& attribute) const {
return false;
}
bool MarkupAccumulator::ShouldIgnoreElement(const Element& element) const {
return false;
}
AtomicString MarkupAccumulator::AppendElement(const Element& element) {
const ElementSerializationData data = AppendStartTagOpen(element);
if (SerializeAsHTML()) {
// https://html.spec.whatwg.org/C/#html-fragment-serialisation-algorithm
AttributeCollection attributes = element.Attributes();
// 3.2. Element: If current node's is value is not null, and the
// element does not have an is attribute in its attribute list, ...
const AtomicString& is_value = element.IsValue();
if (!is_value.IsNull() && !attributes.Find(html_names::kIsAttr)) {
AppendAttribute(element, Attribute(html_names::kIsAttr, is_value));
}
for (const auto& attribute : attributes) {
if (!ShouldIgnoreAttribute(element, attribute))
AppendAttribute(element, attribute);
}
} else {
// https://w3c.github.io/DOM-Parsing/#xml-serializing-an-element-node
for (const auto& attribute : element.Attributes()) {
if (data.ignore_namespace_definition_attribute_ &&
attribute.NamespaceURI() == xmlns_names::kNamespaceURI &&
attribute.Prefix().IsEmpty()) {
// Drop xmlns= only if it's inconsistent with element's namespace.
// https://github.com/w3c/DOM-Parsing/issues/47
if (!EqualIgnoringNullity(attribute.Value(), element.namespaceURI()))
continue;
}
if (!ShouldIgnoreAttribute(element, attribute))
AppendAttribute(element, attribute);
}
}
// Give an opportunity to subclasses to add their own attributes.
AppendCustomAttributes(element);
AppendStartTagClose(element);
return data.serialized_prefix_;
}
MarkupAccumulator::ElementSerializationData
MarkupAccumulator::AppendStartTagOpen(const Element& element) {
ElementSerializationData data;
data.serialized_prefix_ = element.prefix();
if (SerializeAsHTML()) {
formatter_.AppendStartTagOpen(markup_, element);
return data;
}
// https://w3c.github.io/DOM-Parsing/#xml-serializing-an-element-node
NamespaceContext& namespace_context = namespace_stack_.back();
// 5. Let ignore namespace definition attribute be a boolean flag with value
// false.
data.ignore_namespace_definition_attribute_ = false;
// 8. Let local default namespace be the result of recording the namespace
// information for node given map and local prefixes map.
AtomicString local_default_namespace =
namespace_context.RecordNamespaceInformation(element);
// 9. Let inherited ns be a copy of namespace.
AtomicString inherited_ns = namespace_context.ContextNamespace();
// 10. Let ns be the value of node's namespaceURI attribute.
AtomicString ns = element.namespaceURI();
// 11. If inherited ns is equal to ns, then:
if (inherited_ns == ns) {
// 11.1. If local default namespace is not null, then set ignore namespace
// definition attribute to true.
data.ignore_namespace_definition_attribute_ =
!local_default_namespace.IsNull();
// 11.3. Otherwise, append to qualified name the value of node's
// localName. The node's prefix if it exists, is dropped.
// 11.4. Append the value of qualified name to markup.
formatter_.AppendStartTagOpen(markup_, g_null_atom, element.localName());
data.serialized_prefix_ = g_null_atom;
return data;
}
// 12. Otherwise, inherited ns is not equal to ns (the node's own namespace is
// different from the context namespace of its parent). Run these sub-steps:
// 12.1. Let prefix be the value of node's prefix attribute.
AtomicString prefix = element.prefix();
// 12.2. Let candidate prefix be the result of retrieving a preferred prefix
// string prefix from map given namespace ns.
AtomicString candidate_prefix;
if (!ns.IsEmpty() && (!prefix.IsEmpty() || ns != local_default_namespace)) {
candidate_prefix = RetrievePreferredPrefixString(ns, prefix);
}
// 12.4. if candidate prefix is not null (a namespace prefix is defined which
// maps to ns), then:
if (!candidate_prefix.IsNull() && LookupNamespaceURI(candidate_prefix)) {
// 12.4.1. Append to qualified name the concatenation of candidate prefix,
// ":" (U+003A COLON), and node's localName.
// 12.4.3. Append the value of qualified name to markup.
formatter_.AppendStartTagOpen(markup_, candidate_prefix,
element.localName());
data.serialized_prefix_ = candidate_prefix;
// 12.4.2. If the local default namespace is not null (there exists a
// locally-defined default namespace declaration attribute) and its value is
// not the XML namespace, then let inherited ns get the value of local
// default namespace unless the local default namespace is the empty string
// in which case let it get null (the context namespace is changed to the
// declared default, rather than this node's own namespace).
if (local_default_namespace != xml_names::kNamespaceURI)
namespace_context.InheritLocalDefaultNamespace(local_default_namespace);
return data;
}
// 12.5. Otherwise, if prefix is not null, then:
if (!prefix.IsEmpty()) {
// 12.5.1. If the local prefixes map contains a key matching prefix, then
// let prefix be the result of generating a prefix providing as input map,
// ns, and prefix index
if (element.hasAttribute(
AtomicString(String(WTF::g_xmlns_with_colon + prefix)))) {
prefix = GeneratePrefix(ns);
} else {
// 12.5.2. Add prefix to map given namespace ns.
AddPrefix(prefix, ns);
}
// 12.5.3. Append to qualified name the concatenation of prefix, ":" (U+003A
// COLON), and node's localName.
// 12.5.4. Append the value of qualified name to markup.
formatter_.AppendStartTagOpen(markup_, prefix, element.localName());
data.serialized_prefix_ = prefix;
// 12.5.5. Append the following to markup, in the order listed:
MarkupFormatter::AppendAttribute(markup_, g_xmlns_atom, prefix, ns, false);
// 12.5.5.7. If local default namespace is not null (there exists a
// locally-defined default namespace declaration attribute), then let
// inherited ns get the value of local default namespace unless the local
// default namespace is the empty string in which case let it get null.
namespace_context.InheritLocalDefaultNamespace(local_default_namespace);
return data;
}
// 12.6. Otherwise, if local default namespace is null, or local default
// namespace is not null and its value is not equal to ns, then:
if (local_default_namespace.IsNull() ||
!EqualIgnoringNullity(local_default_namespace, ns)) {
// 12.6.1. Set the ignore namespace definition attribute flag to true.
data.ignore_namespace_definition_attribute_ = true;
// 12.6.3. Let the value of inherited ns be ns.
namespace_context.SetContextNamespace(ns);
// 12.6.4. Append the value of qualified name to markup.
formatter_.AppendStartTagOpen(markup_, element);
// 12.6.5. Append the following to markup, in the order listed:
MarkupFormatter::AppendAttribute(markup_, g_null_atom, g_xmlns_atom, ns,
false);
return data;
}
// 12.7. Otherwise, the node has a local default namespace that matches
// ns. Append to qualified name the value of node's localName, let the value
// of inherited ns be ns, and append the value of qualified name to markup.
DCHECK(EqualIgnoringNullity(local_default_namespace, ns));
namespace_context.SetContextNamespace(ns);
formatter_.AppendStartTagOpen(markup_, element);
return data;
}
void MarkupAccumulator::AppendStartTagClose(const Element& element) {
formatter_.AppendStartTagClose(markup_, element);
}
void MarkupAccumulator::AppendAttribute(const Element& element,
const Attribute& attribute) {
String value = formatter_.ResolveURLIfNeeded(element, attribute);
if (SerializeAsHTML()) {
MarkupFormatter::AppendAttributeAsHTML(markup_, attribute, value);
} else {
AppendAttributeAsXMLWithNamespace(element, attribute, value);
}
}
void MarkupAccumulator::AppendAttributeAsXMLWithNamespace(
const Element& element,
const Attribute& attribute,
const String& value) {
// https://w3c.github.io/DOM-Parsing/#serializing-an-element-s-attributes
// 3.3. Let attribute namespace be the value of attr's namespaceURI value.
const AtomicString& attribute_namespace = attribute.NamespaceURI();
// 3.4. Let candidate prefix be null.
AtomicString candidate_prefix;
if (attribute_namespace.IsNull()) {
MarkupFormatter::AppendAttribute(markup_, candidate_prefix,
attribute.LocalName(), value, false);
return;
}
// 3.5. If attribute namespace is not null, then run these sub-steps:
// 3.5.1. Let candidate prefix be the result of retrieving a preferred
// prefix string from map given namespace attribute namespace with preferred
// prefix being attr's prefix value.
candidate_prefix =
RetrievePreferredPrefixString(attribute_namespace, attribute.Prefix());
// 3.5.2. If the value of attribute namespace is the XMLNS namespace, then
// run these steps:
if (attribute_namespace == xmlns_names::kNamespaceURI) {
if (!attribute.Prefix() && attribute.LocalName() != g_xmlns_atom)
candidate_prefix = g_xmlns_atom;
} else {
// 3.5.3. Otherwise, the attribute namespace in not the XMLNS namespace.
// Run these steps:
if (ShouldAddNamespaceAttribute(attribute, candidate_prefix)) {
if (!candidate_prefix || LookupNamespaceURI(candidate_prefix)) {
// 3.5.3.1. Let candidate prefix be the result of generating a prefix
// providing map, attribute namespace, and prefix index as input.
candidate_prefix = GeneratePrefix(attribute_namespace);
// 3.5.3.2. Append the following to result, in the order listed:
MarkupFormatter::AppendAttribute(markup_, g_xmlns_atom,
candidate_prefix, attribute_namespace,
false);
} else {
DCHECK(candidate_prefix);
AppendNamespace(candidate_prefix, attribute_namespace);
}
}
}
MarkupFormatter::AppendAttribute(markup_, candidate_prefix,
attribute.LocalName(), value, false);
}
bool MarkupAccumulator::ShouldAddNamespaceAttribute(
const Attribute& attribute,
const AtomicString& candidate_prefix) {
// xmlns and xmlns:prefix attributes should be handled by another branch in
// AppendAttributeAsXMLWithNamespace().
DCHECK_NE(attribute.NamespaceURI(), xmlns_names::kNamespaceURI);
// Null namespace is checked earlier in AppendAttributeAsXMLWithNamespace().
DCHECK(attribute.NamespaceURI());
// Attributes without a prefix will need one generated for them, and an xmlns
// attribute for that prefix.
if (!candidate_prefix)
return true;
return !EqualIgnoringNullity(LookupNamespaceURI(candidate_prefix),
attribute.NamespaceURI());
}
void MarkupAccumulator::AppendNamespace(const AtomicString& prefix,
const AtomicString& namespace_uri) {
AtomicString found_uri = LookupNamespaceURI(prefix);
if (!EqualIgnoringNullity(found_uri, namespace_uri)) {
AddPrefix(prefix, namespace_uri);
if (prefix.IsEmpty()) {
MarkupFormatter::AppendAttribute(markup_, g_null_atom, g_xmlns_atom,
namespace_uri, false);
} else {
MarkupFormatter::AppendAttribute(markup_, g_xmlns_atom, prefix,
namespace_uri, false);
}
}
}
EntityMask MarkupAccumulator::EntityMaskForText(const Text& text) const {
return formatter_.EntityMaskForText(text);
}
void MarkupAccumulator::PushNamespaces(const Element& element) {
if (SerializeAsHTML())
return;
DCHECK_GT(namespace_stack_.size(), 0u);
// TODO(tkent): Avoid to copy the whole map.
// We can't do |namespace_stack_.emplace_back(namespace_stack_.back())|
// because back() returns a reference in the vector backing, and
// emplace_back() can reallocate it.
namespace_stack_.push_back(NamespaceContext(namespace_stack_.back()));
}
void MarkupAccumulator::PopNamespaces(const Element& element) {
if (SerializeAsHTML())
return;
namespace_stack_.pop_back();
}
// https://w3c.github.io/DOM-Parsing/#dfn-retrieving-a-preferred-prefix-string
AtomicString MarkupAccumulator::RetrievePreferredPrefixString(
const AtomicString& ns,
const AtomicString& preferred_prefix) {
DCHECK(!ns.IsEmpty()) << ns;
AtomicString ns_for_preferred = LookupNamespaceURI(preferred_prefix);
// Preserve the prefix if the prefix is used in the scope and the namespace
// for it is matches to the node's one.
// This is equivalent to the following step in the specification:
// 2.1. If prefix matches preferred prefix, then stop running these steps and
// return prefix.
if (!preferred_prefix.IsEmpty() && !ns_for_preferred.IsNull() &&
EqualIgnoringNullity(ns_for_preferred, ns))
return preferred_prefix;
const Vector<AtomicString>& candidate_list =
namespace_stack_.back().PrefixList(ns);
// Get the last effective prefix.
//
// <el1 xmlns:p="U1" xmlns:q="U1">
// <el2 xmlns:q="U2">
// el2.setAttributeNS(U1, 'n', 'v');
// We should get 'p'.
//
// <el1 xmlns="U1">
// el1.setAttributeNS(U1, 'n', 'v');
// We should not get '' for attributes.
for (auto it = candidate_list.rbegin(); it != candidate_list.rend(); ++it) {
AtomicString candidate_prefix = *it;
DCHECK(!candidate_prefix.IsEmpty());
AtomicString ns_for_candaite = LookupNamespaceURI(candidate_prefix);
if (EqualIgnoringNullity(ns_for_candaite, ns))
return candidate_prefix;
}
// No prefixes for |ns|.
// Preserve the prefix if the prefix is not used in the current scope.
if (!preferred_prefix.IsEmpty() && ns_for_preferred.IsNull())
return preferred_prefix;
// If a prefix is not specified, or the prefix is mapped to a
// different namespace, we should generate new prefix.
return g_null_atom;
}
void MarkupAccumulator::AddPrefix(const AtomicString& prefix,
const AtomicString& namespace_uri) {
namespace_stack_.back().Add(prefix, namespace_uri);
}
AtomicString MarkupAccumulator::LookupNamespaceURI(const AtomicString& prefix) {
return namespace_stack_.back().LookupNamespaceURI(prefix);
}
// https://w3c.github.io/DOM-Parsing/#dfn-generating-a-prefix
AtomicString MarkupAccumulator::GeneratePrefix(
const AtomicString& new_namespace) {
AtomicString generated_prefix;
do {
// 1. Let generated prefix be the concatenation of the string "ns" and the
// current numerical value of prefix index.
generated_prefix = "ns" + String::Number(prefix_index_);
// 2. Let the value of prefix index be incremented by one.
++prefix_index_;
} while (LookupNamespaceURI(generated_prefix));
// 3. Add to map the generated prefix given the new namespace namespace.
AddPrefix(generated_prefix, new_namespace);
// 4. Return the value of generated prefix.
return generated_prefix;
}
bool MarkupAccumulator::SerializeAsHTML() const {
return formatter_.SerializeAsHTML();
}
std::pair<Node*, Element*> MarkupAccumulator::GetAuxiliaryDOMTree(
const Element& element) const {
ShadowRoot* shadow_root = element.GetShadowRoot();
if (!shadow_root || include_shadow_roots_ != kIncludeShadowRoots)
return std::pair<Node*, Element*>();
DCHECK(RuntimeEnabledFeatures::DeclarativeShadowDOMEnabled(
element.GetExecutionContext()));
AtomicString shadowroot_type;
switch (shadow_root->GetType()) {
case ShadowRootType::kUserAgent:
// Don't serialize user agent shadow roots, only explicit shadow roots.
return std::pair<Node*, Element*>();
case ShadowRootType::kOpen:
shadowroot_type = "open";
break;
case ShadowRootType::kClosed:
shadowroot_type = "closed";
break;
}
if (shadow_root->GetType() == ShadowRootType::kClosed &&
!include_closed_roots_.Contains(shadow_root)) {
return std::pair<Node*, Element*>();
}
// Wrap the shadowroot into a declarative Shadow DOM <template shadowroot>
// element.
auto* template_element = MakeGarbageCollected<Element>(
html_names::kTemplateTag, &(element.GetDocument()));
template_element->setAttribute(html_names::kShadowrootAttr, shadowroot_type);
if (shadow_root->delegatesFocus()) {
template_element->SetBooleanAttribute(
html_names::kShadowrootdelegatesfocusAttr, true);
}
return std::pair<Node*, Element*>(shadow_root, template_element);
}
template <typename Strategy>
void MarkupAccumulator::SerializeNodesWithNamespaces(
const Node& target_node,
ChildrenOnly children_only) {
if (!target_node.IsElementNode()) {
if (!children_only)
AppendStartMarkup(target_node);
for (const Node& child : Strategy::ChildrenOf(target_node))
SerializeNodesWithNamespaces<Strategy>(child, kIncludeNode);
return;
}
const auto& target_element = To<Element>(target_node);
if (ShouldIgnoreElement(target_element))
return;
PushNamespaces(target_element);
AtomicString prefix_override;
if (!children_only)
prefix_override = AppendElement(target_element);
bool has_end_tag =
!(SerializeAsHTML() && ElementCannotHaveEndTag(target_element));
if (has_end_tag) {
const Node* parent = &target_element;
if (auto* template_element =
DynamicTo<HTMLTemplateElement>(target_element)) {
// Declarative shadow roots that are currently being parsed will have a
// null content() - don't serialize contents in this case.
parent = template_element->content();
}
if (parent) {
for (const Node& child : Strategy::ChildrenOf(*parent))
SerializeNodesWithNamespaces<Strategy>(child, kIncludeNode);
}
// Traverses other DOM tree, i.e., shadow tree.
std::pair<Node*, Element*> auxiliary_pair =
GetAuxiliaryDOMTree(target_element);
if (Node* auxiliary_tree = auxiliary_pair.first) {
Element* enclosing_element = auxiliary_pair.second;
AtomicString enclosing_element_prefix;
if (enclosing_element)
enclosing_element_prefix = AppendElement(*enclosing_element);
for (const Node& child : Strategy::ChildrenOf(*auxiliary_tree))
SerializeNodesWithNamespaces<Strategy>(child, kIncludeNode);
if (enclosing_element)
AppendEndTag(*enclosing_element, enclosing_element_prefix);
}
if (!children_only)
AppendEndTag(target_element, prefix_override);
}
PopNamespaces(target_element);
}
template <typename Strategy>
String MarkupAccumulator::SerializeNodes(const Node& target_node,
ChildrenOnly children_only) {
if (!SerializeAsHTML()) {
// https://w3c.github.io/DOM-Parsing/#dfn-xml-serialization
DCHECK_EQ(namespace_stack_.size(), 0u);
// 2. Let prefix map be a new namespace prefix map.
namespace_stack_.emplace_back();
// 3. Add the XML namespace with prefix value "xml" to prefix map.
AddPrefix(g_xml_atom, xml_names::kNamespaceURI);
// 4. Let prefix index be a generated namespace prefix index with value 1.
prefix_index_ = 1;
}
SerializeNodesWithNamespaces<Strategy>(target_node, children_only);
return ToString();
}
template String MarkupAccumulator::SerializeNodes<EditingStrategy>(
const Node&,
ChildrenOnly);
} // namespace blink