Consistently match default namespace declaration during serialization
During record-namespace-information for an Element, we match on the
attribute local name ('xmlns'), but while (later) performing the actual
serialization we check if the attribute namespace matches the XMLNS
namespace.
Use the same check in both of these cases, and allow matching both local
name and attribute namespace. This appears to match Gecko.
Guard behind flag "XMLSerializerConsistentDefaultNsDeclMatching".
Fixed: 40068829
Change-Id: Ie8d29f51502b5804ec89f90e259718342ffd5c1a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6429609
Reviewed-by: Kent Tamura <tkent@chromium.org>
Commit-Queue: Fredrik Söderquist <fs@opera.com>
Cr-Commit-Position: refs/heads/main@{#1452495}
diff --git a/third_party/blink/renderer/core/editing/serializers/markup_accumulator.cc b/third_party/blink/renderer/core/editing/serializers/markup_accumulator.cc
index 654991a..12ab0467 100644
--- a/third_party/blink/renderer/core/editing/serializers/markup_accumulator.cc
+++ b/third_party/blink/renderer/core/editing/serializers/markup_accumulator.cc
@@ -49,6 +49,41 @@
namespace blink {
+namespace {
+
+enum class DefaultNsDeclarationMatchType {
+ kLocalName,
+ kNamespaceUri,
+ kBoth,
+};
+
+// Check if the attribute matches a default namespace declaration (xmlns="...").
+//
+// We allow just matching on the local name here because xmlns attributes in
+// HTML documents don't a have namespace URI. Some web tests serialize HTML
+// documents with XMLSerializer, and Firefox has the same behavior.
+bool MatchesDefaultNsDeclaration(const Attribute& attribute,
+ DefaultNsDeclarationMatchType match_type) {
+ if (!attribute.Prefix().empty()) {
+ return false;
+ }
+ if (RuntimeEnabledFeatures::
+ XMLSerializerConsistentDefaultNsDeclMatchingEnabled()) {
+ match_type = DefaultNsDeclarationMatchType::kBoth;
+ }
+ if (match_type != DefaultNsDeclarationMatchType::kNamespaceUri &&
+ attribute.LocalName() == g_xmlns_atom) {
+ return true;
+ }
+ if (match_type != DefaultNsDeclarationMatchType::kLocalName &&
+ attribute.NamespaceURI() == xmlns_names::kNamespaceURI) {
+ return true;
+ }
+ return false;
+}
+
+} // namespace
+
class MarkupAccumulator::NamespaceContext final {
USING_FAST_MALLOC(MarkupAccumulator::NamespaceContext);
@@ -81,10 +116,8 @@
// 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().empty() && attr.LocalName() == g_xmlns_atom) {
+ if (MatchesDefaultNsDeclaration(
+ attr, DefaultNsDeclarationMatchType::kLocalName)) {
// 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
@@ -226,8 +259,8 @@
for (const auto& attribute : attributes) {
if (data.ignore_namespace_definition_attribute_ &&
- attribute.NamespaceURI() == xmlns_names::kNamespaceURI &&
- attribute.Prefix().empty()) {
+ MatchesDefaultNsDeclaration(
+ attribute, DefaultNsDeclarationMatchType::kNamespaceUri)) {
// 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()))
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index 650deb7..a5a6e519 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -5480,6 +5480,10 @@
status: "stable",
},
{
+ name: "XMLSerializerConsistentDefaultNsDeclMatching",
+ status: "stable",
+ },
+ {
// If enabled, the `getDisplayMedia()` family of APIs will ask for NV12
// frames, which should trigger a zero-copy path in the tab capture code.
name: "ZeroCopyTabCapture",
diff --git a/third_party/blink/web_tests/external/wpt/domparsing/XMLSerializer-serializeToString.html b/third_party/blink/web_tests/external/wpt/domparsing/XMLSerializer-serializeToString.html
index c3b704b..6c294e46 100644
--- a/third_party/blink/web_tests/external/wpt/domparsing/XMLSerializer-serializeToString.html
+++ b/third_party/blink/web_tests/external/wpt/domparsing/XMLSerializer-serializeToString.html
@@ -81,6 +81,25 @@
}, 'Check if inconsistent xmlns="..." is dropped.');
test(function() {
+ const root1 = parse('<package></package>');
+ root1.setAttribute('xmlns', 'http://www.idpf.org/2007/opf');
+ const manifest1 = root1.appendChild(root1.ownerDocument.createElement('manifest'));
+ manifest1.setAttribute('xmlns', 'http://www.idpf.org/2007/opf');
+ assert_equals(serialize(root1), '<package><manifest/></package>');
+
+ const root2 = parse('<package xmlns="http://www.idpf.org/2007/opf"></package>');
+ const manifest2 = root2.appendChild(root2.ownerDocument.createElement('manifest'));
+ manifest2.setAttribute('xmlns', 'http://www.idpf.org/2007/opf');
+ assert_equals(serialize(root2),
+ '<package xmlns="http://www.idpf.org/2007/opf"><manifest xmlns=""/></package>');
+
+ const root3 = parse('<package xmlns="http://www.idpf.org/2007/opf"></package>');
+ const manifest3 = root3.appendChild(root3.ownerDocument.createElement('manifest'));
+ assert_equals(serialize(root3),
+ '<package xmlns="http://www.idpf.org/2007/opf"><manifest xmlns=""/></package>');
+}, 'Drop inconsistent xmlns="..." by matching on local name');
+
+test(function() {
let root = parse('<r xmlns:xx="uri"></r>');
root.setAttributeNS('uri', 'name', 'v');
assert_equals(serialize(root), '<r xmlns:xx="uri" xx:name="v"/>');