[Trusted Types] Handle Trusted Types in xlink:href for SVG elements.

Bug: 933300
Change-Id: I58e72faa9f5cdbd0390c84842cc5d5641d0b7d21
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1634880
Reviewed-by: Fredrik Söderquist <fs@opera.com>
Commit-Queue: Daniel Vogelheim <vogelheim@chromium.org>
Cr-Commit-Position: refs/heads/master@{#670100}
diff --git a/third_party/blink/renderer/core/dom/element.cc b/third_party/blink/renderer/core/dom/element.cc
index 9ee33d8..0359576f 100644
--- a/third_party/blink/renderer/core/dom/element.cc
+++ b/third_party/blink/renderer/core/dom/element.cc
@@ -1589,6 +1589,24 @@
   return getAttribute(QualifiedName(g_null_atom, local_name, namespace_uri));
 }
 
+std::pair<wtf_size_t, const QualifiedName>
+Element::LookupAttributeQNameInternal(const AtomicString& local_name) const {
+  AtomicString case_adjusted_local_name = LowercaseIfNecessary(local_name);
+  if (!GetElementData()) {
+    return std::make_pair(
+        kNotFound,
+        QualifiedName(g_null_atom, case_adjusted_local_name, g_null_atom));
+  }
+
+  AttributeCollection attributes = GetElementData()->Attributes();
+  wtf_size_t index = attributes.FindIndex(case_adjusted_local_name);
+  return std::make_pair(
+      index,
+      index != kNotFound
+          ? attributes[index].GetName()
+          : QualifiedName(g_null_atom, case_adjusted_local_name, g_null_atom));
+}
+
 void Element::setAttribute(const AtomicString& local_name,
                            const AtomicString& value,
                            ExceptionState& exception_state) {
@@ -1600,22 +1618,9 @@
   }
 
   SynchronizeAttribute(local_name);
-  AtomicString case_adjusted_local_name = LowercaseIfNecessary(local_name);
-
-  if (!GetElementData()) {
-    SetAttributeInternal(
-        kNotFound,
-        QualifiedName(g_null_atom, case_adjusted_local_name, g_null_atom),
-        value, kNotInSynchronizationOfLazyAttribute);
-    return;
-  }
-
-  AttributeCollection attributes = GetElementData()->Attributes();
-  wtf_size_t index = attributes.FindIndex(case_adjusted_local_name);
-  const QualifiedName& q_name =
-      index != kNotFound
-          ? attributes[index].GetName()
-          : QualifiedName(g_null_atom, case_adjusted_local_name, g_null_atom);
+  wtf_size_t index;
+  QualifiedName q_name = QualifiedName::Null();
+  std::tie(index, q_name) = LookupAttributeQNameInternal(local_name);
   SetAttributeInternal(index, q_name, value,
                        kNotInSynchronizationOfLazyAttribute);
 }
@@ -1644,36 +1649,28 @@
 }
 
 void Element::setAttribute(
-    const AtomicString& name,
+    const AtomicString& local_name,
     const StringOrTrustedHTMLOrTrustedScriptOrTrustedScriptURLOrTrustedURL&
         string_or_TT,
     ExceptionState& exception_state) {
-  // TODO(vogelheim): Check whether this applies to non-HTML documents, too.
-  AtomicString name_lowercase = LowercaseIfNecessary(name);
-  const AttrNameToTrustedType* attribute_types = &GetCheckedAttributeTypes();
-  AttrNameToTrustedType::const_iterator it =
-      attribute_types->find(name_lowercase);
-  if (it != attribute_types->end()) {
-    String attr_value = GetStringFromSpecificTrustedType(
-        string_or_TT, it->value, &GetDocument(), exception_state);
-    if (!exception_state.HadException())
-      setAttribute(name_lowercase, AtomicString(attr_value), exception_state);
-    return;
-  } else if (name_lowercase.StartsWith("on")) {
-    // TODO(jakubvrana): This requires TrustedScript in all attributes starting
-    // with "on", including e.g. "one". We use this pattern elsewhere (e.g. in
-    // IsEventHandlerAttribute) but it's not ideal. Consider using the event
-    // attribute of the resulting AttributeTriggers.
-    String attr_value = GetStringFromSpecificTrustedType(
-        string_or_TT, SpecificTrustedType::kTrustedScript, &GetDocument(),
-        exception_state);
-    if (!exception_state.HadException())
-      setAttribute(name_lowercase, AtomicString(attr_value), exception_state);
+  if (!Document::IsValidName(local_name)) {
+    exception_state.ThrowDOMException(
+        DOMExceptionCode::kInvalidCharacterError,
+        "'" + local_name + "' is not a valid attribute name.");
     return;
   }
-  AtomicString value_string =
-      AtomicString(GetStringFromTrustedTypeWithoutCheck(string_or_TT));
-  setAttribute(name_lowercase, value_string, exception_state);
+
+  SynchronizeAttribute(local_name);
+  wtf_size_t index;
+  QualifiedName q_name = QualifiedName::Null();
+  std::tie(index, q_name) = LookupAttributeQNameInternal(local_name);
+  String value = GetStringFromSpecificTrustedType(
+      string_or_TT, ExpectedTrustedTypeForAttribute(q_name), &GetDocument(),
+      exception_state);
+  if (exception_state.HadException())
+    return;
+  SetAttributeInternal(index, q_name, AtomicString(value),
+                       kNotInSynchronizationOfLazyAttribute);
 }
 
 const AttrNameToTrustedType& Element::GetCheckedAttributeTypes() const {
@@ -1681,6 +1678,35 @@
   return attribute_map;
 }
 
+SpecificTrustedType Element::ExpectedTrustedTypeForAttribute(
+    const QualifiedName& q_name) const {
+  // There are only a handful of namespaced attributes we care about
+  // (xlink:href), and all of those have identical Trusted Types
+  // properties to their namespace-less counterpart. So we check whether this
+  // is one of SVG's 'known' attributes, and if so just check the local
+  // name part as usual.
+  if (!q_name.NamespaceURI().IsNull() &&
+      !SVGAnimatedHref::IsKnownAttribute(q_name)) {
+    return SpecificTrustedType::kNone;
+  }
+
+  const AttrNameToTrustedType* attribute_types = &GetCheckedAttributeTypes();
+  AttrNameToTrustedType::const_iterator iter =
+      attribute_types->find(q_name.LocalName());
+  if (iter != attribute_types->end())
+    return iter->value;
+
+  if (q_name.LocalName().StartsWith("on")) {
+    // TODO(jakubvrana): This requires TrustedScript in all attributes
+    // starting with "on", including e.g. "one". We use this pattern elsewhere
+    // (e.g. in IsEventHandlerAttribute) but it's not ideal. Consider using
+    // the event attribute of the resulting AttributeTriggers.
+    return SpecificTrustedType::kTrustedScript;
+  }
+
+  return SpecificTrustedType::kNone;
+}
+
 void Element::setAttribute(const QualifiedName& name,
                            const StringOrTrustedHTML& stringOrHTML,
                            ExceptionState& exception_state) {
@@ -3279,14 +3305,17 @@
     const StringOrTrustedHTMLOrTrustedScriptOrTrustedScriptURLOrTrustedURL&
         string_or_TT,
     ExceptionState& exception_state) {
-  String value =
-      GetStringFromTrustedType(string_or_TT, &GetDocument(), exception_state);
-  if (exception_state.HadException())
-    return;
   QualifiedName parsed_name = g_any_name;
   if (!ParseAttributeName(parsed_name, namespace_uri, qualified_name,
                           exception_state))
     return;
+
+  String value = GetStringFromSpecificTrustedType(
+      string_or_TT, ExpectedTrustedTypeForAttribute(parsed_name),
+      &GetDocument(), exception_state);
+  if (exception_state.HadException())
+    return;
+
   setAttribute(parsed_name, AtomicString(value));
 }
 
diff --git a/third_party/blink/renderer/core/dom/element.h b/third_party/blink/renderer/core/dom/element.h
index 73d2c81..d14474df 100644
--- a/third_party/blink/renderer/core/dom/element.h
+++ b/third_party/blink/renderer/core/dom/element.h
@@ -1078,6 +1078,10 @@
                                SynchronizationOfLazyAttribute);
   void RemoveAttributeInternal(wtf_size_t index,
                                SynchronizationOfLazyAttribute);
+  std::pair<wtf_size_t, const QualifiedName> LookupAttributeQNameInternal(
+      const AtomicString& local_name) const;
+  SpecificTrustedType ExpectedTrustedTypeForAttribute(
+      const QualifiedName&) const;
 
   void CancelFocusAppearanceUpdate();
 
diff --git a/third_party/blink/renderer/core/svg/svg_a_element.h b/third_party/blink/renderer/core/svg/svg_a_element.h
index 0302bca..19cfd753 100644
--- a/third_party/blink/renderer/core/svg/svg_a_element.h
+++ b/third_party/blink/renderer/core/svg/svg_a_element.h
@@ -38,6 +38,10 @@
 
   explicit SVGAElement(Document&);
 
+  const AttrNameToTrustedType& GetCheckedAttributeTypes() const override {
+    return SVGURIReference::GetCheckedAttributeTypes();
+  }
+
   void Trace(blink::Visitor*) override;
 
  private:
diff --git a/third_party/blink/renderer/core/svg/svg_fe_image_element.h b/third_party/blink/renderer/core/svg/svg_fe_image_element.h
index af5dcd70..a21fb8e 100644
--- a/third_party/blink/renderer/core/svg/svg_fe_image_element.h
+++ b/third_party/blink/renderer/core/svg/svg_fe_image_element.h
@@ -46,6 +46,10 @@
     return preserve_aspect_ratio_.Get();
   }
 
+  const AttrNameToTrustedType& GetCheckedAttributeTypes() const override {
+    return SVGURIReference::GetCheckedAttributeTypes();
+  }
+
   // Promptly remove as a ImageResource client.
   EAGERLY_FINALIZE();
   void Trace(blink::Visitor*) override;
diff --git a/third_party/blink/renderer/core/svg/svg_filter_element.h b/third_party/blink/renderer/core/svg/svg_filter_element.h
index 02c1c25..46c123b 100644
--- a/third_party/blink/renderer/core/svg/svg_filter_element.h
+++ b/third_party/blink/renderer/core/svg/svg_filter_element.h
@@ -68,6 +68,10 @@
   // Get the associated SVGResource object, if any.
   LocalSVGResource* AssociatedResource() const;
 
+  const AttrNameToTrustedType& GetCheckedAttributeTypes() const override {
+    return SVGURIReference::GetCheckedAttributeTypes();
+  }
+
  private:
   void SvgAttributeChanged(const QualifiedName&) override;
   void ChildrenChanged(const ChildrenChange&) override;
diff --git a/third_party/blink/renderer/core/svg/svg_gradient_element.h b/third_party/blink/renderer/core/svg/svg_gradient_element.h
index 0376e5d..4fac3d3 100644
--- a/third_party/blink/renderer/core/svg/svg_gradient_element.h
+++ b/third_party/blink/renderer/core/svg/svg_gradient_element.h
@@ -64,6 +64,10 @@
   const SVGGradientElement* ReferencedElement() const;
   void CollectCommonAttributes(GradientAttributes&) const;
 
+  const AttrNameToTrustedType& GetCheckedAttributeTypes() const override {
+    return SVGURIReference::GetCheckedAttributeTypes();
+  }
+
   void Trace(blink::Visitor*) override;
 
  protected:
diff --git a/third_party/blink/renderer/core/svg/svg_image_element.h b/third_party/blink/renderer/core/svg/svg_image_element.h
index 7c7c3ee0..b5e3a36 100644
--- a/third_party/blink/renderer/core/svg/svg_image_element.h
+++ b/third_party/blink/renderer/core/svg/svg_image_element.h
@@ -78,6 +78,10 @@
     GetImageLoader().SetImageForTest(content);
   }
 
+  const AttrNameToTrustedType& GetCheckedAttributeTypes() const override {
+    return SVGURIReference::GetCheckedAttributeTypes();
+  }
+
  private:
   bool IsStructurallyExternal() const override {
     return !HrefString().IsNull();
diff --git a/third_party/blink/renderer/core/svg/svg_mpath_element.h b/third_party/blink/renderer/core/svg/svg_mpath_element.h
index ee6fa88..59e2871 100644
--- a/third_party/blink/renderer/core/svg/svg_mpath_element.h
+++ b/third_party/blink/renderer/core/svg/svg_mpath_element.h
@@ -39,6 +39,10 @@
 
   void TargetPathChanged();
 
+  const AttrNameToTrustedType& GetCheckedAttributeTypes() const override {
+    return SVGURIReference::GetCheckedAttributeTypes();
+  }
+
   void Trace(blink::Visitor*) override;
 
  private:
diff --git a/third_party/blink/renderer/core/svg/svg_pattern_element.h b/third_party/blink/renderer/core/svg/svg_pattern_element.h
index 45b7af1..1a4e2cbf 100644
--- a/third_party/blink/renderer/core/svg/svg_pattern_element.h
+++ b/third_party/blink/renderer/core/svg/svg_pattern_element.h
@@ -80,6 +80,10 @@
 
   const SVGPatternElement* ReferencedElement() const;
 
+  const AttrNameToTrustedType& GetCheckedAttributeTypes() const override {
+    return SVGURIReference::GetCheckedAttributeTypes();
+  }
+
   void Trace(blink::Visitor*) override;
 
  private:
diff --git a/third_party/blink/renderer/core/svg/svg_script_element.cc b/third_party/blink/renderer/core/svg/svg_script_element.cc
index 5d3ac543..0147e9df 100644
--- a/third_party/blink/renderer/core/svg/svg_script_element.cc
+++ b/third_party/blink/renderer/core/svg/svg_script_element.cc
@@ -173,6 +173,16 @@
 }
 #endif
 
+const AttrNameToTrustedType& SVGScriptElement::GetCheckedAttributeTypes()
+    const {
+  DEFINE_STATIC_LOCAL(AttrNameToTrustedType, attribute_map,
+                      ({
+                          {svg_names::kHrefAttr.LocalName(),
+                           SpecificTrustedType::kTrustedScriptURL},
+                      }));
+  return attribute_map;
+}
+
 void SVGScriptElement::Trace(blink::Visitor* visitor) {
   visitor->Trace(loader_);
   SVGElement::Trace(visitor);
diff --git a/third_party/blink/renderer/core/svg/svg_script_element.h b/third_party/blink/renderer/core/svg/svg_script_element.h
index c1222ee2..cb4b1763 100644
--- a/third_party/blink/renderer/core/svg/svg_script_element.h
+++ b/third_party/blink/renderer/core/svg/svg_script_element.h
@@ -49,6 +49,8 @@
 
   bool IsScriptElement() const override { return true; }
 
+  const AttrNameToTrustedType& GetCheckedAttributeTypes() const override;
+
   void Trace(blink::Visitor*) override;
 
  private:
diff --git a/third_party/blink/renderer/core/svg/svg_text_path_element.h b/third_party/blink/renderer/core/svg/svg_text_path_element.h
index 0576602..17c7f3c8 100644
--- a/third_party/blink/renderer/core/svg/svg_text_path_element.h
+++ b/third_party/blink/renderer/core/svg/svg_text_path_element.h
@@ -66,6 +66,10 @@
     return spacing_.Get();
   }
 
+  const AttrNameToTrustedType& GetCheckedAttributeTypes() const override {
+    return SVGURIReference::GetCheckedAttributeTypes();
+  }
+
   void Trace(blink::Visitor*) override;
 
  private:
diff --git a/third_party/blink/renderer/core/svg/svg_uri_reference.cc b/third_party/blink/renderer/core/svg/svg_uri_reference.cc
index 99471e10..3153f223 100644
--- a/third_party/blink/renderer/core/svg/svg_uri_reference.cc
+++ b/third_party/blink/renderer/core/svg/svg_uri_reference.cc
@@ -151,4 +151,13 @@
   observer = nullptr;
 }
 
+const AttrNameToTrustedType& SVGURIReference::GetCheckedAttributeTypes() const {
+  DEFINE_STATIC_LOCAL(
+      AttrNameToTrustedType, attribute_map,
+      ({
+          {svg_names::kHrefAttr.LocalName(), SpecificTrustedType::kTrustedURL},
+      }));
+  return attribute_map;
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/svg/svg_uri_reference.h b/third_party/blink/renderer/core/svg/svg_uri_reference.h
index 9b0d505..b15cb9d5 100644
--- a/third_party/blink/renderer/core/svg/svg_uri_reference.h
+++ b/third_party/blink/renderer/core/svg/svg_uri_reference.h
@@ -24,6 +24,7 @@
 #include <memory>
 #include "base/callback.h"
 #include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/core/dom/element.h"
 #include "third_party/blink/renderer/core/svg/svg_animated_href.h"
 #include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/weborigin/kurl.h"
@@ -31,7 +32,6 @@
 namespace blink {
 
 class Document;
-class Element;
 class IdTargetObserver;
 class SVGElement;
 class TreeScope;
@@ -81,6 +81,8 @@
   // JS API
   SVGAnimatedHref* href() const { return href_.Get(); }
 
+  const AttrNameToTrustedType& GetCheckedAttributeTypes() const;
+
   void Trace(blink::Visitor*) override;
 
  protected:
diff --git a/third_party/blink/renderer/core/svg/svg_use_element.h b/third_party/blink/renderer/core/svg/svg_use_element.h
index 7c40606..b1ce459 100644
--- a/third_party/blink/renderer/core/svg/svg_use_element.h
+++ b/third_party/blink/renderer/core/svg/svg_use_element.h
@@ -59,6 +59,10 @@
   void DispatchPendingEvent();
   Path ToClipPath() const;
 
+  const AttrNameToTrustedType& GetCheckedAttributeTypes() const override {
+    return SVGURIReference::GetCheckedAttributeTypes();
+  }
+
   void Trace(blink::Visitor*) override;
 
  private:
diff --git a/third_party/blink/renderer/core/trustedtypes/trusted_types_util.cc b/third_party/blink/renderer/core/trustedtypes/trusted_types_util.cc
index ea8ed8d0..695d0dd2 100644
--- a/third_party/blink/renderer/core/trustedtypes/trusted_types_util.cc
+++ b/third_party/blink/renderer/core/trustedtypes/trusted_types_util.cc
@@ -171,6 +171,8 @@
     const ExecutionContext* execution_context,
     ExceptionState& exception_state) {
   switch (specific_trusted_type) {
+    case SpecificTrustedType::kNone:
+      return GetStringFromTrustedTypeWithoutCheck(string_or_trusted_type);
     case SpecificTrustedType::kTrustedHTML: {
       StringOrTrustedHTML string_or_trusted_html =
           string_or_trusted_type.IsTrustedHTML()
diff --git a/third_party/blink/renderer/core/trustedtypes/trusted_types_util.h b/third_party/blink/renderer/core/trustedtypes/trusted_types_util.h
index 4dac44bb..31c9f6ce 100644
--- a/third_party/blink/renderer/core/trustedtypes/trusted_types_util.h
+++ b/third_party/blink/renderer/core/trustedtypes/trusted_types_util.h
@@ -21,6 +21,7 @@
 class USVStringOrTrustedURL;
 
 enum class SpecificTrustedType {
+  kNone,
   kTrustedHTML,
   kTrustedScript,
   kTrustedScriptURL,
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/Element-setAttributeNS.tentative.html b/third_party/blink/web_tests/external/wpt/trusted-types/Element-setAttributeNS.tentative.html
index 80128cf..374e6a9 100644
--- a/third_party/blink/web_tests/external/wpt/trusted-types/Element-setAttributeNS.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/Element-setAttributeNS.tentative.html
@@ -21,4 +21,15 @@
     test(t => {
       assert_element_accepts_trusted_url_set_ns(window, '3', t, 'a', 'b', RESULTS.URL);
     }, "Element.setAttributeNS assigned via policy (successful URL transformation)");
+
+    test(t => {
+      let p = createURL_policy(window, '5');
+      let url = p.createURL(INPUTS.URL);
+
+      let elem = document.createElementNS("http://www.w3.org/2000/svg", "image");
+      elem.setAttributeNS("http://www.w3.org/1999/xlink", "href", url);
+      let attr_node = elem.getAttributeNodeNS("http://www.w3.org/1999/xlink", "href");
+      assert_equals(attr_node.value + "", RESULTS.URL);
+    }, "Element.setAttributeNS accepts a URL on <svg:image xlink:href/>");
+
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/block-string-assignment-to-Element-setAttributeNS.tentative.html b/third_party/blink/web_tests/external/wpt/trusted-types/block-string-assignment-to-Element-setAttributeNS.tentative.html
index 3ad27e2..4bb9569 100644
--- a/third_party/blink/web_tests/external/wpt/trusted-types/block-string-assignment-to-Element-setAttributeNS.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/block-string-assignment-to-Element-setAttributeNS.tentative.html
@@ -25,11 +25,110 @@
       assert_element_accepts_trusted_url_set_ns(window, '3', t, 'a', 'b', RESULTS.URL);
     }, "Element.setAttributeNS assigned via policy (successful URL transformation)");
 
+    // Unknown, namespaced attributes should not be TT checked:
     test(t => {
-      assert_throws_no_trusted_type_set_ns('a', 'b', 'A string');
-    }, "`Element.setAttributeNS = string` throws");
+      assert_element_accepts_non_trusted_type_set_ns('a', 'b', 'A string', 'A string');
+    }, "Element.setAttributeNS accepts untrusted string for non-specced accessor");
 
     test(t => {
-      assert_throws_no_trusted_type_set_ns('a', 'b', null);
-    }, "`Element.setAttributeNS = null` throws");
+      assert_element_accepts_non_trusted_type_set_ns('a', 'b', null, 'null');
+    }, "Element.setAttributeNS accepts null for non-specced accessor");
+
+    // Setup trusted values for use in subsequent tests.
+    const url = createURL_policy(window, '4').createURL(INPUTS.URL);
+    const script_url = createScriptURL_policy(window, '5').createScriptURL(INPUTS.ScriptURL);
+    const html = createHTML_policy(window, '6').createHTML(INPUTS.HTML);
+    const script = createScript_policy(window, '7').createScript(INPUTS.Script);
+
+    // SVG elements that use xlink:href (SVGURIReference) and that expect
+    // TrustedURL.
+    // There a number of affected elements, and there are several ways to set
+    // a namespaced attribute. Let's iterate over all combinations.
+    // TODO(vogelheim): Also SMIL timed elements.
+    const xlink = "http://www.w3.org/1999/xlink";
+    const svg = "http://www.w3.org/2000/svg";
+    const elems = [ "a", "feImage", "filter", "image", "linearGradient",
+                    "mpath", "pattern", "radialGradient", "textPath", "use" ];
+
+    // There are multiple ways to set a namespaced attribute. Let's encapsulate
+    // each in a function.
+    const variants = {
+      "setAttributeNS with prefix": (element_name, value) => {
+        let elem = document.createElementNS(svg, element_name);
+        elem.setAttributeNS(xlink, "xlink:href", value);
+        return elem;
+      },
+      "setAttributeNS without prefix": (element_name, value) => {
+        let elem = document.createElementNS(svg, element_name);
+        elem.setAttributeNS(xlink, "href", value);
+        return elem;
+      },
+      "setAttribute with prefix": (element_name, value) => {
+        let elem = document.createElementNS(svg, element_name);
+        // Create the namespaced attribute with setAttributeNS. Then refer
+        // to it with the prefix in setAttribute. This test will break
+        // if either setAttributeNS or setAttribtue functionality it broken.
+        elem.setAttributeNS(xlink, "xlink:href", url);
+        elem.setAttribute("xlink:href", value);
+        return elem;
+      }
+    };
+    for (const e of elems) {
+      for (const variant in variants) {
+        // Assigning a TrustedURL works.
+        test(t => {
+          let elem = variants[variant](e, url);
+          assert_equals("" + RESULTS.URL,
+                        elem.getAttributeNodeNS(xlink, "href").value);
+        }, "Assigning TrustedURL to <svg:" + e + "> works via " + variant);
+
+        // Assigning things that ought to not work.
+        const values = ["abc", null, script_url, html, script];
+        values.forEach((value, index) => {
+          test(t => {
+            assert_throws(new TypeError(), _ => { variants[variant](e, value); });
+          }, "Blocking non-TrustedURL assignment to <svg:" + e + "> via " +
+             variant + " value no " + index);
+        });
+      }
+    }
+
+    // Test 'synchronization' of 'xlink:href'.
+    test(t => {
+      // ..setAttribute("xlink:href") will behave differently, depending on
+      // whether the element already has an attribute by that name. Make sure
+      // that Trusted Type handling respects that difference.
+
+      // Case 1: "xlink:href" on an empty element: This is an unknown attribute
+      // not processed by SVG, and string assignment should work.
+      let elem1 = document.createElementNS(svg, "a");
+      elem1.setAttribute("xlink:href", "abc");
+
+      // Case 2: "xlink:href", after a namespaced attribute has been set: Now
+      // this mirrors the SVG attribute, and string assignment should fail.
+      let elem2 = document.createElementNS(svg, "a");
+      elem2.setAttributeNS(xlink, "xlink:href", url);
+      assert_throws(new TypeError(), _ => {
+        elem2.setAttribute("xlink:href", "abc");
+      });
+    }, "Test synchronized, namespaced attributes.");
+
+    // svg:script xlink:href=... expects a TrustedScriptURL.
+    let elem = document.createElementNS(svg, "script");
+    // Assigning a TrustedScriptURL works.
+    test(t => {
+      elem.setAttributeNS(xlink, "href", script_url);
+      assert_equals("" + RESULTS.ScriptURL,
+                    elem.getAttributeNodeNS(xlink, "href").value);
+    }, "Assigning TrustedScriptURL to <svg:script xlink:href=...> works");
+
+    // Assigning things that ought to not work.
+    test(t => {
+      const values = [ "abc", null, url, html, script ];
+      for (const v of values) {
+        assert_throws(new TypeError(), _ => {
+          elem.setAttributeNS(xlink, "href", v);
+        });
+      }
+    }, "Blocking non-TrustedScriptURL assignment to <svg:script xlink:href=...> works");
 </script>