Parse and handle Ad Click Attribution attributes in HTMLAnchorElement::handleClick()
authorwilander@apple.com <wilander@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 4 Feb 2019 00:44:07 +0000 (00:44 +0000)
committerwilander@apple.com <wilander@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 4 Feb 2019 00:44:07 +0000 (00:44 +0000)
https://bugs.webkit.org/show_bug.cgi?id=194104
<rdar://problem/47649991>

Reviewed by Chris Dumez, Daniel Bates, and Darin Adler.

Source/WebCore:

Test: http/tests/adClickAttribution/anchor-tag-attributes-validation.html

This patch adds parsing and validation of the two new Ad Click Attribution
attributes in anchor elements: adcampaignid and addestination. The data is
not yet forwarded into the loader.

* html/HTMLAnchorElement.cpp:
(WebCore::HTMLAnchorElement::parseAdClickAttribution const):
(WebCore::HTMLAnchorElement::handleClick):
    Now calls HTMLAnchorElement::parseAdClickAttribution().
* html/HTMLAnchorElement.h:
* loader/AdClickAttribution.h:
    Made WebCore::AdClickAttribution copyable since it's needed to have it be
    WTF::Optional. Also made AdClickAttribution::MaxEntropy public. Changed
    numeric types from unsigned short to uint32_t.
(WebCore::AdClickAttribution::Campaign::isValid const):
(WebCore::AdClickAttribution::Conversion::isValid const):

Tools:

* TestWebKitAPI/Tests/WebCore/AdClickAttribution.cpp:
(TestWebKitAPI::TEST):
    Changed numeric types from unsigned short to uint32_t.

LayoutTests:

This test case makes sure invalid data triggers console warnings.

* http/tests/adClickAttribution/anchor-tag-attributes-validation-expected.txt: Added.
* http/tests/adClickAttribution/anchor-tag-attributes-validation.html: Added.
* platform/ios-wk2/http/tests/adClickAttribution/anchor-tag-attributes-validation-expected.txt: Added.
    Console line numbers are not emitted when running iOS tests so this -expected.txt file doesn't have them.

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

LayoutTests/ChangeLog
LayoutTests/http/tests/adClickAttribution/anchor-tag-attributes-validation-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/adClickAttribution/anchor-tag-attributes-validation.html [new file with mode: 0644]
LayoutTests/platform/ios-wk2/http/tests/adClickAttribution/anchor-tag-attributes-validation-expected.txt [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/html/HTMLAnchorElement.cpp
Source/WebCore/html/HTMLAnchorElement.h
Source/WebCore/loader/AdClickAttribution.h
Tools/ChangeLog
Tools/TestWebKitAPI/Tests/WebCore/AdClickAttribution.cpp

index 3c0ced3..b231bbc 100644 (file)
@@ -1,3 +1,18 @@
+2019-02-03  John Wilander  <wilander@apple.com>
+
+        Parse and handle Ad Click Attribution attributes in HTMLAnchorElement::handleClick()
+        https://bugs.webkit.org/show_bug.cgi?id=194104
+        <rdar://problem/47649991>
+
+        Reviewed by Chris Dumez, Daniel Bates, and Darin Adler.
+
+        This test case makes sure invalid data triggers console warnings.
+
+        * http/tests/adClickAttribution/anchor-tag-attributes-validation-expected.txt: Added.
+        * http/tests/adClickAttribution/anchor-tag-attributes-validation.html: Added.
+        * platform/ios-wk2/http/tests/adClickAttribution/anchor-tag-attributes-validation-expected.txt: Added.
+            Console line numbers are not emitted when running iOS tests so this -expected.txt file doesn't have them.
+
 2019-02-03  Wenson Hsieh  <wenson_hsieh@apple.com>
 
         Unable to move selection into editable roots with 0 height
diff --git a/LayoutTests/http/tests/adClickAttribution/anchor-tag-attributes-validation-expected.txt b/LayoutTests/http/tests/adClickAttribution/anchor-tag-attributes-validation-expected.txt
new file mode 100644 (file)
index 0000000..311084e
--- /dev/null
@@ -0,0 +1,33 @@
+CONSOLE MESSAGE: line 107: adcampaignid must have a non-negative value less than 64 for Ad Click Attribution.
+CONSOLE MESSAGE: line 107: adcampaignid must have a non-negative value less than 64 for Ad Click Attribution.
+CONSOLE MESSAGE: line 107: adcampaignid can not be converted to a non-negative integer which is required for Ad Click Attribution.
+CONSOLE MESSAGE: line 107: adcampaignid can not be converted to a non-negative integer which is required for Ad Click Attribution.
+CONSOLE MESSAGE: line 107: adcampaignid can not be converted to a non-negative integer which is required for Ad Click Attribution.
+CONSOLE MESSAGE: line 107: adddestination could not be converted to a valid HTTP-family URL.
+CONSOLE MESSAGE: line 107: adddestination could not be converted to a valid HTTP-family URL.
+CONSOLE MESSAGE: line 107: adddestination could not be converted to a valid HTTP-family URL.
+CONSOLE MESSAGE: line 107: Both adcampaignid and addestination need to be set for Ad Click Attribution to work.
+CONSOLE MESSAGE: line 107: Both adcampaignid and addestination need to be set for Ad Click Attribution to work.
+Test for validity of ad click attribution attributes on anchor tags.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+Link1
+Link2
+Link3
+Link4
+Link5
+Link6
+Link7
+Link8
+Link9
+Link10
+Link11
+Link12
+Link13
+Link14
+
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/http/tests/adClickAttribution/anchor-tag-attributes-validation.html b/LayoutTests/http/tests/adClickAttribution/anchor-tag-attributes-validation.html
new file mode 100644 (file)
index 0000000..1da4578
--- /dev/null
@@ -0,0 +1,79 @@
+<!DOCTYPE html> <!-- webkit-test-runner [ useFlexibleViewport=true, internal:AdClickAttributionEnabled=true ] -->
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
+    <script src="/js-test-resources/js-test.js"></script>
+    <script src="/js-test-resources/ui-helper.js"></script>
+</head>
+<body onload="runAllTests()">
+<div id="description"></div>
+<div id="output"></div><br>
+<div id="console"></div>
+<script>
+    description("Test for validity of ad click attribution attributes on anchor tags.");
+    jsTestIsAsync = true;
+
+    function createAdClickAttributionAnchorElement(elementID, adCampaignID, adDestination) {
+        let anchorElement = document.createElement("a");
+        anchorElement.id = elementID;
+        anchorElement.adcampaignid = adCampaignID;
+        anchorElement.addestination = adDestination;
+        anchorElement.href = "#";
+        anchorElement.innerText = "Link" + currentTest;
+        return anchorElement;
+    }
+
+    function activateElement(elementID, callback) {
+        var element = document.getElementById(elementID);
+        var centerX = element.offsetLeft + element.offsetWidth / 2;
+        var centerY = element.offsetTop + element.offsetHeight / 2;
+        UIHelper.activateAt(centerX, centerY).then(
+            function () {
+                callback();
+            },
+            function () {
+                testFailed("Promise rejected.");
+                finishJSTest();
+            }
+        );
+    }
+
+    let currentTest = 0;
+    function runOneTest(adCampaignID, adDestination, callback) {
+        const currentElementID = "test" + currentTest++;
+        const anchorElement = createAdClickAttributionAnchorElement(currentElementID, adCampaignID, adDestination);
+        output.appendChild(anchorElement);
+        const brElement = document.createElement("br");
+        output.appendChild(brElement);
+        activateElement(currentElementID, callback);
+    }
+
+    const validAdCampaignID = "03";
+    const validAdDestination = "http://webkit.org";
+    const testCases = [
+        [ validAdCampaignID, validAdDestination ],
+        [ "100", validAdDestination ],           // Too many characters.
+        [ "1", validAdDestination ],             // Too few characters.
+        [ "98", validAdDestination ],            // Too high value.
+        [ "-1", validAdDestination ],            // Negative value.
+        [ "ab", validAdDestination ],            // Non-digits.
+        [ "1", validAdDestination ],            // Non-ASCII.
+        [ " 1", validAdDestination ],            // Leading space.
+        [ "1 ", validAdDestination ],            // Trailing space.
+        [ validAdCampaignID, "webkit.org" ],     // Missing protocol.
+        [ validAdCampaignID, "://webkit.org" ],  // Partially missing protocol.
+        [ validAdCampaignID, "" ],           // Non-ASCII characters as destination.
+        [ "", validAdDestination ],              // Empty campaign ID.
+        [ validAdCampaignID, "" ]                // Empty destination.
+    ];
+
+    function runAllTests() {
+        if (currentTest < testCases.length)
+            runOneTest(testCases[currentTest][0], testCases[currentTest][1], runAllTests);
+        else
+            finishJSTest();
+    }
+</script>
+</body>
+</html>
diff --git a/LayoutTests/platform/ios-wk2/http/tests/adClickAttribution/anchor-tag-attributes-validation-expected.txt b/LayoutTests/platform/ios-wk2/http/tests/adClickAttribution/anchor-tag-attributes-validation-expected.txt
new file mode 100644 (file)
index 0000000..2ca10c5
--- /dev/null
@@ -0,0 +1,33 @@
+CONSOLE MESSAGE: adcampaignid must have a non-negative value less than 64 for Ad Click Attribution.
+CONSOLE MESSAGE: adcampaignid must have a non-negative value less than 64 for Ad Click Attribution.
+CONSOLE MESSAGE: adcampaignid can not be converted to a non-negative integer which is required for Ad Click Attribution.
+CONSOLE MESSAGE: adcampaignid can not be converted to a non-negative integer which is required for Ad Click Attribution.
+CONSOLE MESSAGE: adcampaignid can not be converted to a non-negative integer which is required for Ad Click Attribution.
+CONSOLE MESSAGE: adddestination could not be converted to a valid HTTP-family URL.
+CONSOLE MESSAGE: adddestination could not be converted to a valid HTTP-family URL.
+CONSOLE MESSAGE: adddestination could not be converted to a valid HTTP-family URL.
+CONSOLE MESSAGE: Both adcampaignid and addestination need to be set for Ad Click Attribution to work.
+CONSOLE MESSAGE: Both adcampaignid and addestination need to be set for Ad Click Attribution to work.
+Test for validity of ad click attribution attributes on anchor tags.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+Link1
+Link2
+Link3
+Link4
+Link5
+Link6
+Link7
+Link8
+Link9
+Link10
+Link11
+Link12
+Link13
+Link14
+
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
index 5ce2e7c..ad90d9b 100644 (file)
@@ -1,3 +1,29 @@
+2019-02-03  John Wilander  <wilander@apple.com>
+
+        Parse and handle Ad Click Attribution attributes in HTMLAnchorElement::handleClick()
+        https://bugs.webkit.org/show_bug.cgi?id=194104
+        <rdar://problem/47649991>
+
+        Reviewed by Chris Dumez, Daniel Bates, and Darin Adler.
+
+        Test: http/tests/adClickAttribution/anchor-tag-attributes-validation.html
+
+        This patch adds parsing and validation of the two new Ad Click Attribution
+        attributes in anchor elements: adcampaignid and addestination. The data is
+        not yet forwarded into the loader.
+
+        * html/HTMLAnchorElement.cpp:
+        (WebCore::HTMLAnchorElement::parseAdClickAttribution const):
+        (WebCore::HTMLAnchorElement::handleClick):
+            Now calls HTMLAnchorElement::parseAdClickAttribution().
+        * html/HTMLAnchorElement.h:
+        * loader/AdClickAttribution.h:
+            Made WebCore::AdClickAttribution copyable since it's needed to have it be
+            WTF::Optional. Also made AdClickAttribution::MaxEntropy public. Changed
+            numeric types from unsigned short to uint32_t.
+        (WebCore::AdClickAttribution::Campaign::isValid const):
+        (WebCore::AdClickAttribution::Conversion::isValid const):
+
 2019-02-03  Ryosuke Niwa  <rniwa@webkit.org>
 
         Validate navigation policy decisions to avoid crashes in continueLoadAfterNavigationPolicy
index e7e167c..071870c 100644 (file)
@@ -24,6 +24,7 @@
 #include "config.h"
 #include "HTMLAnchorElement.h"
 
+#include "AdClickAttribution.h"
 #include "DOMTokenList.h"
 #include "ElementIterator.h"
 #include "EventHandler.h"
 #include "SecurityPolicy.h"
 #include "Settings.h"
 #include "URLUtils.h"
+#include "UserGestureIndicator.h"
 #include <wtf/IsoMallocInlines.h>
 #include <wtf/text/StringBuilder.h>
+#include <wtf/text/StringConcatenateNumbers.h>
 
 namespace WebCore {
 
@@ -394,6 +397,52 @@ bool HTMLAnchorElement::isSystemPreviewLink() const
 }
 #endif
 
+Optional<AdClickAttribution> HTMLAnchorElement::parseAdClickAttribution() const
+{
+    using Campaign = AdClickAttribution::Campaign;
+    using Source = AdClickAttribution::Source;
+    using Destination = AdClickAttribution::Destination;
+
+    if (!RuntimeEnabledFeatures::sharedFeatures().adClickAttributionEnabled() || !UserGestureIndicator::processingUserGesture())
+        return WTF::nullopt;
+
+    if (!hasAttributeWithoutSynchronization(adcampaignidAttr) && !hasAttributeWithoutSynchronization(addestinationAttr))
+        return WTF::nullopt;
+    
+    auto adCampaignIDAttr = attributeWithoutSynchronization(adcampaignidAttr);
+    auto adDestinationAttr = attributeWithoutSynchronization(addestinationAttr);
+    
+    if (adCampaignIDAttr.isEmpty() || adDestinationAttr.isEmpty()) {
+        document().addConsoleMessage(MessageSource::Other, MessageLevel::Warning, "Both adcampaignid and addestination need to be set for Ad Click Attribution to work."_s);
+        return WTF::nullopt;
+    }
+
+    RefPtr<Frame> frame = document().frame();
+    if (!frame || !frame->isMainFrame()) {
+        document().addConsoleMessage(MessageSource::Other, MessageLevel::Warning, "Ad Click Attribution is only supported in the main frame."_s);
+        return WTF::nullopt;
+    }
+    
+    auto adCampaignID = parseHTMLNonNegativeInteger(adCampaignIDAttr);
+    if (!adCampaignID) {
+        document().addConsoleMessage(MessageSource::Other, MessageLevel::Warning, "adcampaignid can not be converted to a non-negative integer which is required for Ad Click Attribution."_s);
+        return WTF::nullopt;
+    }
+    
+    if (adCampaignID.value() >= AdClickAttribution::MaxEntropy) {
+        document().addConsoleMessage(MessageSource::Other, MessageLevel::Warning, makeString("adcampaignid must have a non-negative value less than ", AdClickAttribution::MaxEntropy, " for Ad Click Attribution."));
+        return WTF::nullopt;
+    }
+
+    URL adDestinationURL { URL(), adDestinationAttr };
+    if (!adDestinationURL.isValid() || !adDestinationURL.protocolIsInHTTPFamily()) {
+        document().addConsoleMessage(MessageSource::Other, MessageLevel::Warning, "adddestination could not be converted to a valid HTTP-family URL."_s);
+        return WTF::nullopt;
+    }
+
+    return AdClickAttribution { Campaign(adCampaignID.value()), Source(document().domain()), Destination(adDestinationURL.host().toString()) };
+}
+
 void HTMLAnchorElement::handleClick(Event& event)
 {
     event.setDefaultHandled();
@@ -438,6 +487,13 @@ void HTMLAnchorElement::handleClick(Event& event)
     else if (hasRel(Relation::NoOpener) || (RuntimeEnabledFeatures::sharedFeatures().blankAnchorTargetImpliesNoOpenerEnabled() && equalIgnoringASCIICase(effectiveTarget, "_blank")))
         newFrameOpenerPolicy = NewFrameOpenerPolicy::Suppress;
 
+    auto adClickAttribution = parseAdClickAttribution();
+    // FIXME: The adClickAttribution should be forwarded to the loader and handled down the pipe. See
+    // rdar://problem/47650118
+    // A matching conversion event needs to happen before the complete ad click attributionURL can be
+    // created. Thus, it should be empty for now.
+    ASSERT_UNUSED(adClickAttribution, !adClickAttribution || adClickAttribution->url().isNull());
+    
     frame->loader().urlSelected(completedURL, effectiveTarget, &event, LockHistory::No, LockBackForwardList::No, shouldSendReferrer, document().shouldOpenExternalURLsPolicyToPropagate(), newFrameOpenerPolicy, downloadAttribute, systemPreviewInfo);
 
     sendPings(completedURL);
index 6b02c9a..61315ce 100644 (file)
 #include "SharedStringHash.h"
 #include "URLUtils.h"
 #include <wtf/OptionSet.h>
+#include <wtf/Optional.h>
 
 namespace WebCore {
 
+class AdClickAttribution;
 class DOMTokenList;
 
 // Link relation bitmask values.
@@ -95,6 +97,8 @@ private:
 
     void sendPings(const URL& destinationURL);
 
+    Optional<AdClickAttribution> parseAdClickAttribution() const;
+
     void handleClick(Event&);
 
     enum EventType {
index 7df05ee..fe06a1b 100644 (file)
@@ -37,14 +37,13 @@ class URL;
 
 namespace WebCore {
 
-constexpr unsigned short maxEntropy = 64;
-
 class AdClickAttribution {
-    WTF_MAKE_NONCOPYABLE(AdClickAttribution);
 public:
-    using CampaignId = unsigned short;
-    using ConversionData = unsigned short;
-    using PriorityValue = unsigned short;
+    using CampaignId = uint32_t;
+    using ConversionData = uint32_t;
+    using PriorityValue = uint32_t;
+
+    static constexpr uint32_t MaxEntropy = 64;
 
     struct Campaign {
         explicit Campaign(CampaignId id)
@@ -54,7 +53,7 @@ public:
         
         bool isValid() const
         {
-            return id < maxEntropy;
+            return id < MaxEntropy;
         }
         
         CampaignId id;
@@ -104,7 +103,7 @@ public:
 
         bool isValid() const
         {
-            return data < maxEntropy && priority < maxEntropy;
+            return data < MaxEntropy && priority < MaxEntropy;
         }
         
         ConversionData data;
index c0ae8eb..1039278 100644 (file)
@@ -1,3 +1,15 @@
+2019-02-03  John Wilander  <wilander@apple.com>
+
+        Parse and handle Ad Click Attribution attributes in HTMLAnchorElement::handleClick()
+        https://bugs.webkit.org/show_bug.cgi?id=194104
+        <rdar://problem/47649991>
+
+        Reviewed by Chris Dumez, Daniel Bates, and Darin Adler.
+
+        * TestWebKitAPI/Tests/WebCore/AdClickAttribution.cpp:
+        (TestWebKitAPI::TEST):
+            Changed numeric types from unsigned short to uint32_t.
+
 2019-02-02  David Kilzer  <ddkilzer@apple.com>
 
         Leak of NSArray (4.25 Kbytes) in com.apple.WebKit.WebContent running WebKit layout tests on iOS Simulator
index 57ce281..a9ca75d 100644 (file)
@@ -33,8 +33,8 @@ using namespace WebCore;
 
 namespace TestWebKitAPI {
 
-constexpr unsigned short min6BitValue { 0 };
-constexpr unsigned short max6BitValue { 63 };
+constexpr uint32_t min6BitValue { 0 };
+constexpr uint32_t max6BitValue { 63 };
 
 // Positive test cases.
 
@@ -52,8 +52,8 @@ TEST(AdClickAttribution, ValidMinValues)
 
 TEST(AdClickAttribution, ValidMidValues)
 {
-    AdClickAttribution attribution(AdClickAttribution::Campaign((unsigned short)12), AdClickAttribution::Source("webkit.org"), AdClickAttribution::Destination("example.com"));
-    attribution.setConversion(AdClickAttribution::Conversion((unsigned short)44, AdClickAttribution::Priority((unsigned short)22)));
+    AdClickAttribution attribution(AdClickAttribution::Campaign((uint32_t)12), AdClickAttribution::Source("webkit.org"), AdClickAttribution::Destination("example.com"));
+    attribution.setConversion(AdClickAttribution::Conversion((uint32_t)44, AdClickAttribution::Priority((uint32_t)22)));
 
     auto attributionURL = attribution.url();
     auto referrerURL = attribution.referrer();