Detect system preview links
authordino@apple.com <dino@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 18 Apr 2018 22:13:50 +0000 (22:13 +0000)
committerdino@apple.com <dino@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 18 Apr 2018 22:13:50 +0000 (22:13 +0000)
https://bugs.webkit.org/show_bug.cgi?id=184753
<rdar://problem/39500514>

Reviewed by Antoine Quint.

Source/WebCore:

Detect a special type of link anchor, which we're calling System Previews.
Ultimately this will allow WebKit to present such links in a special
way.

A System Preview link is an <a> element, with a rel attribute that
includes "system-preview". It has a single element child, which is
either an <img> or a <picture>.

Test: system-preview/detection.html

* html/HTMLAnchorElement.cpp:
(WebCore::HTMLAnchorElement::relList): Add braces.
(WebCore::HTMLAnchorElement::isSystemPreviewLink const): New
function.
* html/HTMLAnchorElement.h:

* html/HTMLImageElement.cpp:
(WebCore::HTMLImageElement::isSystemPreviewImage const): Ask the
parent if it is a system preview link.
* html/HTMLImageElement.h:
* html/HTMLPictureElement.cpp:
(WebCore::HTMLPictureElement::isSystemPreviewImage const): Ditto.
* html/HTMLPictureElement.h:

* testing/Internals.cpp: Testing helpers.
(WebCore::Internals::systemPreviewRelType):
(WebCore::Internals::isSystemPreviewLink const):
(WebCore::Internals::isSystemPreviewImage const):
* testing/Internals.h:
* testing/Internals.idl:

LayoutTests:

* system-preview/detection-expected.txt: Added.
* system-preview/detection.html: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@230788 268f45cc-cd09-0410-ab3c-d52691b4dbfc

13 files changed:
LayoutTests/ChangeLog
LayoutTests/system-preview/detection-expected.txt [new file with mode: 0644]
LayoutTests/system-preview/detection.html [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/html/HTMLAnchorElement.cpp
Source/WebCore/html/HTMLAnchorElement.h
Source/WebCore/html/HTMLImageElement.cpp
Source/WebCore/html/HTMLImageElement.h
Source/WebCore/html/HTMLPictureElement.cpp
Source/WebCore/html/HTMLPictureElement.h
Source/WebCore/testing/Internals.cpp
Source/WebCore/testing/Internals.h
Source/WebCore/testing/Internals.idl

index 0b6c232..4ac80ac 100644 (file)
@@ -1,3 +1,14 @@
+2018-04-18  Dean Jackson  <dino@apple.com>
+
+        Detect system preview links
+        https://bugs.webkit.org/show_bug.cgi?id=184753
+        <rdar://problem/39500514>
+
+        Reviewed by Antoine Quint.
+
+        * system-preview/detection-expected.txt: Added.
+        * system-preview/detection.html: Added.
+
 2018-04-18  Ryan Haddad  <ryanhaddad@apple.com>
 
         Skip animations/missing-from-to.html on mac-wk1 debug.
diff --git a/LayoutTests/system-preview/detection-expected.txt b/LayoutTests/system-preview/detection-expected.txt
new file mode 100644 (file)
index 0000000..c187c17
--- /dev/null
@@ -0,0 +1,39 @@
+This test only works in WKTR or DRT.
+
+Valid system preview
+
+Result: Link element present. Identified as system preview. Child 0 is an image inside a system preview.
+
+Valid system preview (multiple rel values)
+
+Result: Link element present. Identified as system preview. Child 0 is an image inside a system preview.
+
+Valid system preview (picture child)
+
+Result: Link element present. Identified as system preview. Child 0 is an image inside a system preview.
+
+Invalid system preview (wrong rel value)
+
+Result: Link element present. Not identified as system preview. Child 0 is not an image inside a system preview.
+
+Invalid system preview (wrong children)
+
+hello
+Result: Link element present. Not identified as system preview. Child 0 is not an image inside a system preview.
+
+Invalid system preview (too many children)
+
+hello  
+Result: Link element present. Not identified as system preview. Child 0 is not an image inside a system preview. Child 1 is not an image inside a system preview.
+
+Invalid system preview (no children)
+
+Result: Link element present. Not identified as system preview.
+
+Invalid system preview (just an image)
+
+
+Result: No link. Child is not an image inside a system preview.
diff --git a/LayoutTests/system-preview/detection.html b/LayoutTests/system-preview/detection.html
new file mode 100644 (file)
index 0000000..d10db86
--- /dev/null
@@ -0,0 +1,126 @@
+<!DOCTYPE html>
+<script>
+if (window.testRunner)
+    window.testRunner.dumpAsText();
+
+function getSystemPreviewData(id)
+{
+    const container = document.getElementById(id);
+    const link = container.querySelector("a");
+    let children = [];
+    if (link)
+        children = link.children;
+    const result = container.querySelectorAll("p")[1];
+    return [link, children, result];
+}
+
+function addResult(resultElement, txt)
+{
+    resultElement.textContent += txt;
+}
+
+const TESTS = ["valid-1", "valid-2", "valid-3", "invalid-1", "invalid-2", "invalid-3", "invalid-4", "invalid-5"];
+
+window.addEventListener("load", () => {
+    if (!window.internals)
+        return;
+
+    const relValue = internals.systemPreviewRelType();
+    TESTS.forEach(id => {
+        const [link, children, result] = getSystemPreviewData(id);
+        if (link) {
+            addResult(result, "Link element present.");
+
+            if (id != "invalid-1")
+                link.rel += " " + relValue;
+
+            if (internals.isSystemPreviewLink(link))
+                addResult(result, " Identified as system preview.");
+            else
+                addResult(result, " Not identified as system preview.");
+
+        } else
+            addResult(result, "No link.");
+
+        if (children && children.length > 0) {
+            for (let i = 0; i < children.length; i++) {
+                if (internals.isSystemPreviewImage(children[i]))
+                    addResult(result, ` Child ${i} is an image inside a system preview.`);
+                else
+                    addResult(result, ` Child ${i} is not an image inside a system preview.`);
+            }
+        }
+
+        // Specific test for the case that is just an image.
+        if (id == "invalid-5") {
+            const child = document.getElementById(id).children[0];
+            if (internals.isSystemPreviewImage(child))
+                addResult(result, ` Child is an image inside a system preview.`);
+            else
+                addResult(result, ` Child is not an image inside a system preview.`);
+        }
+    });
+}, false);
+</script>
+<p>This test only works in WKTR or DRT.</p>
+<div id="valid-1">
+<p>Valid system preview</p>
+<a href="/" rel="">
+    <img>
+</a>
+<p>Result: </p>
+</div>
+
+<div id="valid-2">
+<p>Valid system preview (multiple rel values)</p>
+<a href="/" rel="foo">
+    <img>
+</a>
+<p>Result: </p>
+</div>
+
+<div id="valid-3">
+<p>Valid system preview (picture child)</p>
+<a href="/" rel="">
+    <picture></picture>
+</a>
+<p>Result: </p>
+</div>
+
+<div id="invalid-1">
+<p>Invalid system preview (wrong rel value)</p>
+<a href="/" rel="">
+    <img>
+</a>
+<p>Result: </p>
+</div>
+
+<div id="invalid-2">
+<p>Invalid system preview (wrong children)</p>
+<a href="/" rel="">
+    <span>hello</span>
+</a>
+<p>Result: </p>
+</div>
+
+<div id="invalid-3">
+<p>Invalid system preview (too many children)</p>
+<a href="/" rel="">
+    <span>hello</span>
+    <img>
+</a>
+<p>Result: </p>
+</div>
+
+<div id="invalid-4">
+<p>Invalid system preview (no children)</p>
+<a href="/" rel="">
+</a>
+<p>Result: </p>
+</div>
+
+<div id="invalid-5">
+<p>Invalid system preview (just an image)</p>
+<img>
+<p>Result: </p>
+</div>
index 68def10..308d238 100644 (file)
@@ -1,3 +1,42 @@
+2018-04-18  Dean Jackson  <dino@apple.com>
+
+        Detect system preview links
+        https://bugs.webkit.org/show_bug.cgi?id=184753
+        <rdar://problem/39500514>
+
+        Reviewed by Antoine Quint.
+
+        Detect a special type of link anchor, which we're calling System Previews.
+        Ultimately this will allow WebKit to present such links in a special
+        way.
+
+        A System Preview link is an <a> element, with a rel attribute that
+        includes "system-preview". It has a single element child, which is
+        either an <img> or a <picture>.
+
+        Test: system-preview/detection.html
+
+        * html/HTMLAnchorElement.cpp:
+        (WebCore::HTMLAnchorElement::relList): Add braces.
+        (WebCore::HTMLAnchorElement::isSystemPreviewLink const): New
+        function.
+        * html/HTMLAnchorElement.h:
+
+        * html/HTMLImageElement.cpp:
+        (WebCore::HTMLImageElement::isSystemPreviewImage const): Ask the
+        parent if it is a system preview link.
+        * html/HTMLImageElement.h:
+        * html/HTMLPictureElement.cpp:
+        (WebCore::HTMLPictureElement::isSystemPreviewImage const): Ditto.
+        * html/HTMLPictureElement.h:
+
+        * testing/Internals.cpp: Testing helpers.
+        (WebCore::Internals::systemPreviewRelType):
+        (WebCore::Internals::isSystemPreviewLink const):
+        (WebCore::Internals::isSystemPreviewImage const):
+        * testing/Internals.h:
+        * testing/Internals.idl:
+
 2018-04-18  Antti Koivisto  <antti@apple.com>
 
         :active pseudo class doesn't deactivate when using pressure sensitive trackpad
index 648664c..c5639e5 100644 (file)
@@ -36,6 +36,7 @@
 #include "HTMLCanvasElement.h"
 #include "HTMLImageElement.h"
 #include "HTMLParserIdioms.h"
+#include "HTMLPictureElement.h"
 #include "KeyboardEvent.h"
 #include "MouseEvent.h"
 #include "PingLoader.h"
 #include <wtf/IsoMallocInlines.h>
 #include <wtf/text/StringBuilder.h>
 
+#if USE(APPLE_INTERNAL_SDK)
+#import <WebKitAdditions/SystemPreviewDetection.cpp>
+#endif
+
 namespace WebCore {
 
 WTF_MAKE_ISO_ALLOCATED_IMPL(HTMLAnchorElement);
@@ -304,12 +309,13 @@ bool HTMLAnchorElement::hasRel(Relation relation) const
     return m_linkRelations.contains(relation);
 }
 
-DOMTokenList& HTMLAnchorElement::relList()
+DOMTokenList& HTMLAnchorElement::relList() const
 {
-    if (!m_relList) 
-        m_relList = std::make_unique<DOMTokenList>(*this, HTMLNames::relAttr, [](Document&, StringView token) {
+    if (!m_relList) {
+        m_relList = std::make_unique<DOMTokenList>(const_cast<HTMLAnchorElement&>(*this), HTMLNames::relAttr, [](Document&, StringView token) {
             return equalIgnoringASCIICase(token, "noreferrer") || equalIgnoringASCIICase(token, "noopener");
         });
+    }
     return *m_relList;
 }
 
@@ -362,6 +368,30 @@ void HTMLAnchorElement::sendPings(const URL& destinationURL)
         PingLoader::sendPing(*document().frame(), document().completeURL(pingURLs[i]), destinationURL);
 }
 
+bool HTMLAnchorElement::isSystemPreviewLink() const
+{
+#if USE(APPLE_INTERNAL_SDK)
+    auto systemPreviewRelValue = getSystemPreviewRelValue();
+#else
+    auto systemPreviewRelValue = String { ASCIILiteral("system-preview") };
+#endif
+
+    // The relList is created on demand, which means that calling relList()
+    // is only available in a non-const method.
+    if (!relList().contains(systemPreviewRelValue))
+        return false;
+
+    if (auto* child = firstElementChild()) {
+        if (is<HTMLImageElement>(child) || is<HTMLPictureElement>(child)) {
+            auto numChildren = childElementCount();
+            // FIXME: Should only be 1.
+            return numChildren == 1 || numChildren == 2;
+        }
+    }
+
+    return false;
+}
+
 void HTMLAnchorElement::handleClick(Event& event)
 {
     event.setDefaultHandled();
index 55818d1..f9cffc4 100644 (file)
@@ -66,7 +66,9 @@ public:
     SharedStringHash visitedLinkHash() const;
     void invalidateCachedVisitedLinkHash() { m_cachedVisitedLinkHash = 0; }
 
-    WEBCORE_EXPORT DOMTokenList& relList();
+    WEBCORE_EXPORT DOMTokenList& relList() const;
+
+    WEBCORE_EXPORT bool isSystemPreviewLink() const;
 
 protected:
     HTMLAnchorElement(const QualifiedName&, Document&);
@@ -107,7 +109,7 @@ private:
     OptionSet<Relation> m_linkRelations;
     mutable SharedStringHash m_cachedVisitedLinkHash;
 
-    std::unique_ptr<DOMTokenList> m_relList;
+    mutable std::unique_ptr<DOMTokenList> m_relList;
 };
 
 inline SharedStringHash HTMLAnchorElement::visitedLinkHash() const
index 65d940c..6ea882d 100644 (file)
@@ -27,6 +27,7 @@
 #include "CSSValueKeywords.h"
 #include "CachedImage.h"
 #include "FrameView.h"
+#include "HTMLAnchorElement.h"
 #include "HTMLDocument.h"
 #include "HTMLFormElement.h"
 #include "HTMLParserIdioms.h"
@@ -676,4 +677,12 @@ bool HTMLImageElement::willRespondToMouseClickEvents()
 }
 #endif
 
+bool HTMLImageElement::isSystemPreviewImage() const
+{
+    const auto* parent = parentElement();
+    if (!is<HTMLAnchorElement>(parent))
+        return false;
+    return downcast<HTMLAnchorElement>(parent)->isSystemPreviewLink();
+}
+
 }
index b396bfd..f648fc6 100644 (file)
@@ -100,6 +100,8 @@ public:
     HTMLPictureElement* pictureElement() const;
     void setPictureElement(HTMLPictureElement*);
 
+    WEBCORE_EXPORT bool isSystemPreviewImage() const;
+
 protected:
     HTMLImageElement(const QualifiedName&, Document&, HTMLFormElement* = 0);
 
index 4c0aadb..cb9de7c 100644 (file)
@@ -27,6 +27,7 @@
 #include "HTMLPictureElement.h"
 
 #include "ElementChildIterator.h"
+#include "HTMLAnchorElement.h"
 #include "HTMLImageElement.h"
 #include "Logging.h"
 #include <wtf/IsoMallocInlines.h>
@@ -75,5 +76,13 @@ bool HTMLPictureElement::viewportChangeAffectedPicture() const
     return false;
 }
 
+bool HTMLPictureElement::isSystemPreviewImage() const
+{
+    const auto* parent = parentElement();
+    if (!is<HTMLAnchorElement>(parent))
+        return false;
+    return downcast<HTMLAnchorElement>(parent)->isSystemPreviewLink();
+}
+
 }
 
index 7ef6b91..0da4ce1 100644 (file)
@@ -46,6 +46,8 @@ public:
 
     WeakPtr<HTMLPictureElement> createWeakPtr() { return m_weakFactory.createWeakPtr(*this); }
 
+    WEBCORE_EXPORT bool isSystemPreviewImage() const;
+
 private:
     HTMLPictureElement(const QualifiedName&, Document&);
 
index b583ac8..e3105b8 100644 (file)
 #include "FrameView.h"
 #include "GCObservation.h"
 #include "GridPosition.h"
+#include "HTMLAnchorElement.h"
 #include "HTMLCanvasElement.h"
 #include "HTMLIFrameElement.h"
 #include "HTMLImageElement.h"
 #include "HTMLInputElement.h"
 #include "HTMLLinkElement.h"
 #include "HTMLNames.h"
+#include "HTMLPictureElement.h"
 #include "HTMLPlugInElement.h"
 #include "HTMLPreloadScanner.h"
 #include "HTMLSelectElement.h"
 #include "MockCredentialsMessenger.h"
 #endif
 
+#if USE(APPLE_INTERNAL_SDK)
+#import <WebKitAdditions/SystemPreviewDetection.cpp>
+#endif
+
 using JSC::CallData;
 using JSC::CallType;
 using JSC::CodeBlock;
@@ -4464,4 +4470,27 @@ MockCredentialsMessenger& Internals::mockCredentialsMessenger() const
 }
 #endif
 
+String Internals::systemPreviewRelType()
+{
+#if USE(APPLE_INTERNAL_SDK)
+    return getSystemPreviewRelValue();
+#else
+    return ASCIILiteral("system-preview");
+#endif
+}
+
+bool Internals::isSystemPreviewLink(Element& element) const
+{
+    return is<HTMLAnchorElement>(element) && downcast<HTMLAnchorElement>(element).isSystemPreviewLink();
+}
+
+bool Internals::isSystemPreviewImage(Element& element) const
+{
+    if (is<HTMLImageElement>(element))
+        return downcast<HTMLImageElement>(element).isSystemPreviewImage();
+    if (is<HTMLPictureElement>(element))
+        return downcast<HTMLPictureElement>(element).isSystemPreviewImage();
+    return false;
+}
+
 } // namespace WebCore
index 00b21b9..c06ac90 100644 (file)
@@ -56,10 +56,12 @@ class FetchResponse;
 class File;
 class Frame;
 class GCObservation;
+class HTMLAnchorElement;
 class HTMLImageElement;
 class HTMLInputElement;
 class HTMLLinkElement;
 class HTMLMediaElement;
+class HTMLPictureElement;
 class HTMLSelectElement;
 class ImageData;
 class InspectorStubFrontend;
@@ -655,6 +657,10 @@ public:
     MockCredentialsMessenger& mockCredentialsMessenger() const;
 #endif
 
+    String systemPreviewRelType();
+    bool isSystemPreviewLink(Element&) const;
+    bool isSystemPreviewImage(Element&) const;
+
 private:
     explicit Internals(Document&);
     Document* contextDocument() const;
index 9771e80..85ecf30 100644 (file)
@@ -588,4 +588,8 @@ enum EventThrottlingBehavior {
     [EnabledAtRuntime=WebAnimations] void setTimelineCurrentTime(AnimationTimeline timeline, double currentTime);
     [Conditional=APPLE_PAY] readonly attribute MockPaymentCoordinator mockPaymentCoordinator;
     [Conditional=WEB_AUTHN] readonly attribute MockCredentialsMessenger mockCredentialsMessenger;
+
+    DOMString systemPreviewRelType();
+    boolean isSystemPreviewLink(Element element);
+    boolean isSystemPreviewImage(Element element);
 };