Pārlūkot izejas kodu

LibWeb: Improve relList feature detection support

`DOMTokenList.supports()` is now correct for all possible `rel`
attribute values for `link`, `a`, `area` and `form` elements.
Tim Ledbetter 6 mēneši atpakaļ
vecāks
revīzija
d946d94e2d

+ 9 - 9
Libraries/LibWeb/DOM/DOMTokenList.cpp

@@ -211,21 +211,21 @@ WebIDL::ExceptionOr<bool> DOMTokenList::replace(String const& token, String cons
 // https://dom.spec.whatwg.org/#concept-domtokenlist-validation
 // https://dom.spec.whatwg.org/#concept-domtokenlist-validation
 WebIDL::ExceptionOr<bool> DOMTokenList::supports(StringView token)
 WebIDL::ExceptionOr<bool> DOMTokenList::supports(StringView token)
 {
 {
-    static HashMap<FlyString, Vector<StringView>> supported_tokens_map = {
-        // NOTE: The supported values for rel were taken from HTMLLinkElement::Relationship
-        { HTML::AttributeNames::rel, { "alternate"sv, "stylesheet"sv, "preload"sv, "dns-prefetch"sv, "preconnect"sv, "icon"sv } },
-        { HTML::AttributeNames::sandbox, { "allow-downloads"sv, "allow-forms"sv, "allow-modals"sv, "allow-orientation-lock"sv, "allow-pointer-lock"sv, "allow-popups"sv, "allow-popups-to-escape-sandbox"sv, "allow-presentation"sv, "allow-same-origin"sv, "allow-scripts"sv, "allow-top-navigation"sv, "allow-top-navigation-by-user-activation"sv, "allow-top-navigation-to-custom-protocols"sv } },
+    // https://html.spec.whatwg.org/multipage/links.html#linkTypes
+    // https://html.spec.whatwg.org/multipage/iframe-embed-object.html#attr-iframe-sandbox
+    static HashMap<SupportedTokenKey, Vector<StringView>> supported_tokens_map = {
+        { { HTML::TagNames::link, HTML::AttributeNames::rel }, { "modulepreload"sv, "preload"sv, "preconnect"sv, "dns-prefetch"sv, "stylesheet"sv, "icon"sv, "alternate"sv, "prefetch"sv, "prerender"sv, "next"sv, "manifest"sv, "apple-touch-icon"sv, "apple-touch-icon-precomposed"sv, "canonical"sv } },
+        { { HTML::TagNames::a, HTML::AttributeNames::rel }, { "noreferrer"sv, "noopener"sv, "opener"sv } },
+        { { HTML::TagNames::area, HTML::AttributeNames::rel }, { "noreferrer"sv, "noopener"sv, "opener"sv } },
+        { { HTML::TagNames::form, HTML::AttributeNames::rel }, { "noreferrer"sv, "noopener"sv, "opener"sv } },
+        { { HTML::TagNames::iframe, HTML::AttributeNames::sandbox }, { "allow-downloads"sv, "allow-forms"sv, "allow-modals"sv, "allow-orientation-lock"sv, "allow-pointer-lock"sv, "allow-popups"sv, "allow-popups-to-escape-sandbox"sv, "allow-presentation"sv, "allow-same-origin"sv, "allow-scripts"sv, "allow-top-navigation"sv, "allow-top-navigation-by-user-activation"sv, "allow-top-navigation-to-custom-protocols"sv } },
     };
     };
 
 
     // 1. If the associated attribute’s local name does not define supported tokens, throw a TypeError.
     // 1. If the associated attribute’s local name does not define supported tokens, throw a TypeError.
-    auto supported_tokens = supported_tokens_map.get(m_associated_attribute);
+    auto supported_tokens = supported_tokens_map.get({ m_associated_element->local_name(), m_associated_attribute });
     if (!supported_tokens.has_value())
     if (!supported_tokens.has_value())
         return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, MUST(String::formatted("Attribute {} does not define any supported tokens", m_associated_attribute)) };
         return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, MUST(String::formatted("Attribute {} does not define any supported tokens", m_associated_attribute)) };
 
 
-    // AD-HOC: Other browsers return false for rel attributes on non-link elements for all attribute values we currently support.
-    if (m_associated_attribute == HTML::AttributeNames::rel && !is<HTML::HTMLLinkElement>(*m_associated_element))
-        return false;
-
     // 2. Let lowercase token be a copy of token, in ASCII lowercase.
     // 2. Let lowercase token be a copy of token, in ASCII lowercase.
     auto lowercase_token = token.to_lowercase_string();
     auto lowercase_token = token.to_lowercase_string();
 
 

+ 19 - 0
Libraries/LibWeb/DOM/DOMTokenList.h

@@ -62,3 +62,22 @@ private:
 };
 };
 
 
 }
 }
+
+struct SupportedTokenKey {
+    FlyString element_name;
+    FlyString attribute_name;
+
+    constexpr bool operator==(SupportedTokenKey const& other) const = default;
+};
+
+namespace AK {
+
+template<>
+struct Traits<SupportedTokenKey> : public DefaultTraits<SupportedTokenKey> {
+    static unsigned hash(SupportedTokenKey const& key)
+    {
+        return pair_int_hash(key.element_name.hash(), key.attribute_name.hash());
+    }
+};
+
+}

+ 9 - 0
Tests/LibWeb/Text/expected/wpt-import/html/semantics/rellist-feature-detection.txt

@@ -0,0 +1,9 @@
+Harness status: OK
+
+Found 4 tests
+
+4 Pass
+Pass	Make sure that relList based feature detection is working for <link>
+Pass	Make sure that relList based feature detection is working for <a>
+Pass	Make sure that relList based feature detection is working for <area>
+Pass	Make sure that relList based feature detection is working for <form>

+ 84 - 0
Tests/LibWeb/Text/input/wpt-import/html/semantics/rellist-feature-detection.html

@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<title>Test relList attribute</title>
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<script>
+let link_support_table = {};
+// https://html.spec.whatwg.org/multipage/links.html#linkTypes
+link_support_table['link'] = {
+  supported : ['modulepreload', 'preload', 'preconnect', 'dns-prefetch',
+               'stylesheet', 'icon', 'alternate', 'prefetch',
+               'prerender', 'next', 'manifest', 'apple-touch-icon',
+               'apple-touch-icon-precomposed', 'canonical'],
+  unsupported : ['author', 'bookmark', 'external', 'help', 'import',
+                 'license', 'nofollow', 'pingback', 'prev', 'search',
+                 'tag', 'noreferrer', 'noopener']
+};
+link_support_table['a'] =  {
+  supported : ['noreferrer', 'noopener', 'opener'],
+  unsupported : ['author', 'bookmark', 'external', 'help', 'license',
+                 'nofollow', 'pingback', 'prev', 'search', 'tag',
+                 'modulepreload', 'preload', 'preconnect', 'dns-prefetch',
+                 'stylesheet', 'import', 'icon', 'alternate', 'prefetch',
+                 'prerender', 'next', 'manifest', 'apple-touch-icon',
+                 'apple-touch-icon-precomposed', 'canonical']
+};
+link_support_table['area'] = link_support_table['a'];
+link_support_table['form'] = link_support_table['a'];
+
+function test_rellist(tag_name) {
+  const rel_table = link_support_table[tag_name];
+  const element = document.createElement(tag_name);
+  let tag = element.tagName;
+  // Test that setting rel is also setting relList, for both
+  // valid and invalid values.
+  element.rel = 'whatever';
+  assert_true(element.relList.contains('whatever'), 'tag = ' + tag + ', setting rel must work');
+  element.rel = 'prefetch';
+  assert_true(element.relList.contains('prefetch'), 'tag = ' + tag + ', setting rel must work');
+  // Test that add() works.
+  element.relList.add('preloadwhatever');
+  assert_equals(element.rel, 'prefetch preloadwhatever', 'tag = ' + tag + ', add must work');
+  assert_true(element.relList.contains('preloadwhatever'), 'tag = ' + tag + ', add must work');
+  // Test that remove() works.
+  element.relList.remove('preloadwhatever');
+  assert_equals(element.rel, 'prefetch', 'tag = ' + tag + ', remove must work');
+  assert_false(element.relList.contains('preloadwhatever'), 'tag = ' + tag + ', remove must work');
+  // Test that toggle() works.
+  element.relList.toggle('prefetch', false);
+  assert_equals(element.rel, '', 'tag = ' + tag + ', toggle must work');
+  element.relList.toggle('prefetch', true);
+  assert_equals(element.rel, 'prefetch', 'tag = ' + tag + ', toggle must work');
+  // Test that replace() works.
+  element.relList.replace('prefetch', 'first');
+  assert_equals(element.rel, 'first', 'tag = ' + tag + ', replace must work');
+  // Test that indexed item getter works.
+  element.relList.add('second');
+  assert_equals(element.relList.length, 2, 'tag = ' + tag + ', relList length must be correct');
+  assert_equals(element.relList[0], 'first', 'tag = ' + tag + ', relList indexed item must work');
+  assert_equals(element.relList[1], 'second', 'tag = ' + tag + ', relList indexed item must work');
+  // Test that relList is  [SameObject].
+  let savedRelList = element.relList;
+  element.rel = 'something';
+  assert_equals(element.relList, savedRelList, 'tag = ' + tag + ', SameObject must work');
+
+  // Test that supports() is returning true for valid values
+  // and false for invalid ones.
+  let supported = rel_table['supported'];
+  for (let link_type in supported) {
+    assert_true(element.relList.supports(supported[link_type]), 'tag = ' + tag + ', link type = ' + supported[link_type] + ' must be supported');
+    assert_true(element.relList.supports(supported[link_type].toUpperCase()), 'tag = ' + tag + ', link type = ' + supported[link_type].toUpperCase() + ' must be supported');
+  }
+  let unsupported = rel_table['unsupported'];
+  for (let link_type in unsupported) {
+    assert_false(element.relList.supports(unsupported[link_type]), 'tag = ' + tag + ', link type = ' + unsupported[link_type] + ' must be unsupported');
+  }
+}
+
+['link', 'a', 'area', 'form'].forEach(tag_name => {
+  test(
+    () => test_rellist(tag_name),
+    `Make sure that relList based feature detection is working for <${tag_name}>`
+  );
+});
+</script>