Changeset 270136 in webkit


Ignore:
Timestamp:
Nov 20, 2020 4:38:46 PM (20 months ago)
Author:
Kate Cheney
Message:

PCM: Persist pending ad clicks and attributions so they can survive browser restart
https://bugs.webkit.org/show_bug.cgi?id=219134
<rdar://problem/70470129>

Reviewed by John Wilander.
Source/WebCore:

This patch migrates PCM data to be stored on disk and
updates naming of various PCM data to match naming agreed
upon in standards bodies:

  • source -> sourceSite
  • campaign/campaignID -> sourceID
  • destination -> attributeOnSite
  • conversion/conversionValue -> attributionTriggerData
  • unconverted -> unattributed
  • convert(ed) -> attribute(d)

Tests: http/tests/privateClickMeasurement/expired-ad-click-gets-removed-on-session-start.html

http/tests/privateClickMeasurement/expired-attribution-report-gets-sent-on-session-start.html

  • html/HTMLAnchorElement.cpp:

(WebCore::HTMLAnchorElement::parsePrivateClickMeasurement const):

  • loader/PrivateClickMeasurement.cpp:

(WebCore::PrivateClickMeasurement::maxAge):
(WebCore::PrivateClickMeasurement::isValid const):
(WebCore::PrivateClickMeasurement::parseAttributionRequest):
(WebCore::PrivateClickMeasurement::attributeAndGetEarliestTimeToSend):
(WebCore::PrivateClickMeasurement::hasHigherPriorityThan const):
(WebCore::PrivateClickMeasurement::reportURL const):
(WebCore::PrivateClickMeasurement::json const):
(WebCore::PrivateClickMeasurement::parseConversionRequest): Deleted.
(WebCore::PrivateClickMeasurement::convertAndGetEarliestTimeToSend): Deleted.
Renaming.

(WebCore::PrivateClickMeasurement::markAsExpired): Deleted.
(WebCore::PrivateClickMeasurement::hasExpired const): Deleted.
(WebCore::PrivateClickMeasurement::markConversionAsSent): Deleted.
(WebCore::PrivateClickMeasurement::wasConversionSent const): Deleted.
We can remove the *Expired() functions as they were only indicators
for the HashMap storage to know what attributions to delete. This is
now handled by SQLite. Similarly, we can remove the markConversionAsSent
and wasConversionSent functions because a sent attribution will not
be stored in the database, so this value will always be false.

(WebCore::PrivateClickMeasurement::toString const): Deleted.

  • loader/PrivateClickMeasurement.h:

(WebCore::PrivateClickMeasurement::SourceID::SourceID):
(WebCore::PrivateClickMeasurement::SourceSite::SourceSite):
(WebCore::PrivateClickMeasurement::SourceSite::operator== const):
(WebCore::PrivateClickMeasurement::SourceSite::deletedValue):
(WebCore::PrivateClickMeasurement::SourceSite::constructDeletedValue):
(WebCore::PrivateClickMeasurement::SourceSiteHash::hash):
(WebCore::PrivateClickMeasurement::SourceSiteHash::equal):
(WebCore::PrivateClickMeasurement::AttributeOnSite::AttributeOnSite):
(WebCore::PrivateClickMeasurement::AttributeOnSite::operator== const):
(WebCore::PrivateClickMeasurement::AttributeOnSite::deletedValue):
(WebCore::PrivateClickMeasurement::AttributeOnSite::constructDeletedValue):
(WebCore::PrivateClickMeasurement::AttributeOnSiteHash::hash):
(WebCore::PrivateClickMeasurement::AttributeOnSiteHash::equal):
(WebCore::PrivateClickMeasurement::AttributionTriggerData::AttributionTriggerData):
Renaming.

(WebCore::PrivateClickMeasurement::PrivateClickMeasurement):
(WebCore::PrivateClickMeasurement::sourceSite const):
(WebCore::PrivateClickMeasurement::attributeOnSite const):
(WebCore::PrivateClickMeasurement::timeOfAdClick const):
(WebCore::PrivateClickMeasurement::setEarliestTimeToSend):
(WebCore::PrivateClickMeasurement::sourceID):
(WebCore::PrivateClickMeasurement::attributionTriggerData):
(WebCore::PrivateClickMeasurement::setAttribution):
Now that we store data on disk, we need a more flexible constructor
and more functions to rebuild PCM objects from the database.

(WebCore::PrivateClickMeasurement::encode const):
(WebCore::PrivateClickMeasurement::decode):
(WebCore::PrivateClickMeasurement::AttributionTriggerData::encode const):
(WebCore::PrivateClickMeasurement::AttributionTriggerData::decode):
(WTF::HashTraits<WebCore::PrivateClickMeasurement::SourceSite>::emptyValue):
(WTF::HashTraits<WebCore::PrivateClickMeasurement::SourceSite>::constructDeletedValue):
(WTF::HashTraits<WebCore::PrivateClickMeasurement::SourceSite>::isDeletedValue):
(WTF::HashTraits<WebCore::PrivateClickMeasurement::AttributeOnSite>::emptyValue):
(WTF::HashTraits<WebCore::PrivateClickMeasurement::AttributeOnSite>::constructDeletedValue):
(WTF::HashTraits<WebCore::PrivateClickMeasurement::AttributeOnSite>::isDeletedValue):
(WebCore::PrivateClickMeasurement::Campaign::Campaign): Deleted.
(WebCore::PrivateClickMeasurement::Campaign::isValid const): Deleted.
(WebCore::PrivateClickMeasurement::Source::Source): Deleted.
(WebCore::PrivateClickMeasurement::Source::operator== const): Deleted.
(WebCore::PrivateClickMeasurement::Source::matches const): Deleted.
(WebCore::PrivateClickMeasurement::Source::isHashTableDeletedValue const): Deleted.
(WebCore::PrivateClickMeasurement::Source::deletedValue): Deleted.
(WebCore::PrivateClickMeasurement::Source::constructDeletedValue): Deleted.
(WebCore::PrivateClickMeasurement::Source::deleteValue): Deleted.
(WebCore::PrivateClickMeasurement::Source::isDeletedValue const): Deleted.
(WebCore::PrivateClickMeasurement::SourceHash::hash): Deleted.
(WebCore::PrivateClickMeasurement::SourceHash::equal): Deleted.
(WebCore::PrivateClickMeasurement::Destination::Destination): Deleted.
(WebCore::PrivateClickMeasurement::Destination::operator== const): Deleted.
(WebCore::PrivateClickMeasurement::Destination::matches const): Deleted.
(WebCore::PrivateClickMeasurement::Destination::isHashTableDeletedValue const): Deleted.
(WebCore::PrivateClickMeasurement::Destination::deletedValue): Deleted.
(WebCore::PrivateClickMeasurement::Destination::constructDeletedValue): Deleted.
(WebCore::PrivateClickMeasurement::Destination::deleteValue): Deleted.
(WebCore::PrivateClickMeasurement::Destination::isDeletedValue const): Deleted.
(WebCore::PrivateClickMeasurement::DestinationHash::hash): Deleted.
(WebCore::PrivateClickMeasurement::DestinationHash::equal): Deleted.
(WebCore::PrivateClickMeasurement::Conversion::Conversion): Deleted.
(WebCore::PrivateClickMeasurement::Conversion::isValid const): Deleted.
(WebCore::PrivateClickMeasurement::source const): Deleted.
(WebCore::PrivateClickMeasurement::destination const): Deleted.
Renaming.

(WebCore::PrivateClickMeasurement::isEmpty const): Deleted.
Not needed anymore, the database uses Optionals to indicate an empty result.

(WebCore::PrivateClickMeasurement::Conversion::encode const): Deleted.
(WebCore::PrivateClickMeasurement::Conversion::decode): Deleted.
(WTF::HashTraits<WebCore::PrivateClickMeasurement::Source>::emptyValue): Deleted.
(WTF::HashTraits<WebCore::PrivateClickMeasurement::Source>::constructDeletedValue): Deleted.
(WTF::HashTraits<WebCore::PrivateClickMeasurement::Source>::isDeletedValue): Deleted.
(WTF::HashTraits<WebCore::PrivateClickMeasurement::Destination>::emptyValue): Deleted.
(WTF::HashTraits<WebCore::PrivateClickMeasurement::Destination>::constructDeletedValue): Deleted.
(WTF::HashTraits<WebCore::PrivateClickMeasurement::Destination>::isDeletedValue): Deleted.
Renaming.

Source/WebKit:

This patch migrates Private Click Measurement to use SQLite,
which is beneficial because it requires less in-memory storage and
persists PCM data across browser sessions. It also updates naming
to match naming agreed upon in standards bodies:

  • source -> sourceSite
  • campaign/campaignID -> sourceID
  • destination -> attributeOnSite
  • conversion/conversionValue -> attributionTriggerData
  • unconverted -> unattributed
  • convert(ed) -> attribute(d)

This adds 3 SQLite tables: one for clicks that haven't been
attributed, one for attributions that haven't been sent, and one to
store the last time the reports were sent to make sure reports get
sent as soon as possible if needed after a browser restart.

Behavior is identical to existing PCM implementation with the addition
of persistence. Existing PCM tests confirm no regressions.

Reviewed by John Wilander.

  • NetworkProcess/Classifier/ResourceLoadStatisticsDatabaseStore.cpp:

(WebKit::createTableQueries):
(WebKit::ResourceLoadStatisticsDatabaseStore::ResourceLoadStatisticsDatabaseStore):
(WebKit::ResourceLoadStatisticsDatabaseStore::createUniqueIndices):
(WebKit::ResourceLoadStatisticsDatabaseStore::createSchema):
(WebKit::ResourceLoadStatisticsDatabaseStore::destroyStatements):
New queries to interact with PCM data.

(WebKit::ResourceLoadStatisticsDatabaseStore::updateTimerLastFired):
(WebKit::ResourceLoadStatisticsDatabaseStore::timerLastFired):
(WebKit::ResourceLoadStatisticsDatabaseStore::updatePrivateClickMeasurementAttributionTimes):
Set earliestTimeToSend to be the original value minus the time passed since the last timer fire
for each entry. If the result is less than 0, set to 0 so the report gets sent immediately.

(WebKit::ResourceLoadStatisticsDatabaseStore::buildPrivateClickMeasurementFromDatabase):
Creates a PCM object from data in the database.

(WebKit::ResourceLoadStatisticsDatabaseStore::findPrivateClickMeasurement):
(WebKit::ResourceLoadStatisticsDatabaseStore::insertPrivateClickMeasurement):
(WebKit::ResourceLoadStatisticsDatabaseStore::markAllUnattributedPrivateClickMeasurementAsExpiredForTesting):
(WebKit::ResourceLoadStatisticsDatabaseStore::removeUnattributed):
(WebKit::ResourceLoadStatisticsDatabaseStore::attributePrivateClickMeasurement):
(WebKit::ResourceLoadStatisticsDatabaseStore::allAttributedPrivateClickMeasurement):
(WebKit::ResourceLoadStatisticsDatabaseStore::clearPrivateClickMeasurement):
(WebKit::ResourceLoadStatisticsDatabaseStore::clearExpiredPrivateClickMeasurement):
(WebKit::ResourceLoadStatisticsDatabaseStore::attributionToString):
(WebKit::ResourceLoadStatisticsDatabaseStore::privateClickMeasurementToString):
(WebKit::ResourceLoadStatisticsDatabaseStore::clearSentAttributions):
These functions use database queries to implement PCM functionality with exactly the same
behavior as the in-memory PCM implementation.

(WebKit::ResourceLoadStatisticsDatabaseStore::markAttributedPrivateClickMeasurementsAsExpiredForTesting):

  • NetworkProcess/Classifier/ResourceLoadStatisticsDatabaseStore.h:
  • NetworkProcess/Classifier/ResourceLoadStatisticsMemoryStore.h:
  • NetworkProcess/Classifier/ResourceLoadStatisticsStore.h:
  • NetworkProcess/Classifier/WebResourceLoadStatisticsStore.cpp:

(WebKit::WebResourceLoadStatisticsStore::updateTimerLastFired):
(WebKit::WebResourceLoadStatisticsStore::insertPrivateClickMeasurement):
(WebKit::WebResourceLoadStatisticsStore::markAllUnattributedPrivateClickMeasurementAsExpiredForTesting):
(WebKit::WebResourceLoadStatisticsStore::attributePrivateClickMeasurement):
(WebKit::WebResourceLoadStatisticsStore::allAttributedPrivateClickMeasurement):
(WebKit::WebResourceLoadStatisticsStore::clearPrivateClickMeasurement):
(WebKit::WebResourceLoadStatisticsStore::clearPrivateClickMeasurementForRegistrableDomain):
(WebKit::WebResourceLoadStatisticsStore::clearExpiredPrivateClickMeasurement):
(WebKit::WebResourceLoadStatisticsStore::privateClickMeasurementToString):
(WebKit::WebResourceLoadStatisticsStore::clearSentAttributions):
(WebKit::WebResourceLoadStatisticsStore::markAttributedPrivateClickMeasurementsAsExpiredForTesting):

  • NetworkProcess/Classifier/WebResourceLoadStatisticsStore.h:
  • NetworkProcess/NetworkProcess.cpp:

(WebKit::NetworkProcess::firePrivateClickMeasurementTimerImmediately):
(WebKit::NetworkProcess::simulateResourceLoadStatisticsSessionRestart):
(WebKit::NetworkProcess::markAttributedPrivateClickMeasurementsAsExpiredForTesting):
Test functions to help simulate a browser restart after PCM data has expired during
a session close. This is the only behavior change from the existing PCM implementation.

  • NetworkProcess/NetworkProcess.h:
  • NetworkProcess/NetworkProcess.messages.in:
  • NetworkProcess/NetworkResourceLoader.cpp:

(WebKit::NetworkResourceLoader::willSendRedirectedRequest):
(WebKit::NetworkResourceLoader::continueWillSendRedirectedRequest):

  • NetworkProcess/NetworkResourceLoader.h:
  • NetworkProcess/NetworkSession.cpp:

(WebKit::NetworkSession::NetworkSession):
(WebKit::NetworkSession::firePrivateClickMeasurementTimerImmediately):
(WebKit::NetworkSession::storePrivateClickMeasurement):
(WebKit::NetworkSession::handlePrivateClickMeasurementConversion):
(WebKit::NetworkSession::markAttributedPrivateClickMeasurementsAsExpiredForTesting):
(WebKit::NetworkSession::markPrivateClickMeasurementsAsExpiredForTesting):

  • NetworkProcess/NetworkSession.h:
  • NetworkProcess/PrivateClickMeasurementManager.cpp:

(WebKit::PrivateClickMeasurementManager::PrivateClickMeasurementManager):
Move constructor to cpp file to call startTimer(5_s) which will kick
off sending any reports that have expired in the database. We should
wait 5 seconds so we are sure ITP is up and running.

(WebKit::PrivateClickMeasurementManager::storeUnattributed):
(WebKit::PrivateClickMeasurementManager::handleAttribution):
(WebKit::PrivateClickMeasurementManager::startTimer):
(WebKit::PrivateClickMeasurementManager::attribute):
(WebKit::PrivateClickMeasurementManager::fireConversionRequest):
(WebKit::PrivateClickMeasurementManager::clearSentAttributions):
(WebKit::PrivateClickMeasurementManager::updateTimerLastFired):
(WebKit::PrivateClickMeasurementManager::firePendingAttributionRequests):
(WebKit::PrivateClickMeasurementManager::clear):
(WebKit::PrivateClickMeasurementManager::clearForRegistrableDomain):
(WebKit::PrivateClickMeasurementManager::clearExpired):
(WebKit::PrivateClickMeasurementManager::toString const):
(WebKit::PrivateClickMeasurementManager::setConversionURLForTesting):
(WebKit::PrivateClickMeasurementManager::markAllUnattributedAsExpiredForTesting):
(WebKit::PrivateClickMeasurementManager::markAttributedPrivateClickMeasurementsAsExpiredForTesting):
(WebKit::PrivateClickMeasurementManager::storeUnconverted): Deleted.
(WebKit::PrivateClickMeasurementManager::handleConversion): Deleted.
(WebKit::PrivateClickMeasurementManager::convert): Deleted.
(WebKit::PrivateClickMeasurementManager::firePendingConversionRequests): Deleted.
(WebKit::PrivateClickMeasurementManager::markAllUnconvertedAsExpiredForTesting): Deleted.
Implementation moved to ResourceLoadStatisticsDatabaseStore.

  • NetworkProcess/PrivateClickMeasurementManager.h:

(WebKit::PrivateClickMeasurementManager::PrivateClickMeasurementManager): Deleted.
Moved to cpp file.

(WebKit::PrivateClickMeasurementManager::m_sessionID): Deleted.

  • UIProcess/API/C/WKPage.cpp:

(WKPageMarkAttributedPrivateClickMeasurementsAsExpiredForTesting):
(WKPageSimulateResourceLoadStatisticsSessionRestart):

  • UIProcess/API/C/WKPagePrivate.h:
  • UIProcess/WebPageProxy.cpp:

(WebKit::WebPageProxy::didCommitLoadForFrame):
(WebKit::WebPageProxy::markAttributedPrivateClickMeasurementsAsExpiredForTesting):
(WebKit::WebPageProxy::simulateResourceLoadStatisticsSessionRestart):

  • UIProcess/WebPageProxy.h:

Testing support.

Tools:

Add support for testing of expired ad-clicks and attributions.
Update names after Private Click Measurement standards discussions.

  • TestWebKitAPI/Tests/WebCore/PrivateClickMeasurement.cpp:

(TestWebKitAPI::TEST):

  • WebKitTestRunner/InjectedBundle/Bindings/TestRunner.idl:
  • WebKitTestRunner/InjectedBundle/TestRunner.cpp:

(WTR::TestRunner::markAttributedPrivateClickMeasurementsAsExpiredForTesting):
(WTR::TestRunner::simulateResourceLoadStatisticsSessionRestart):

  • WebKitTestRunner/InjectedBundle/TestRunner.h:
  • WebKitTestRunner/TestController.cpp:

(WTR::TestController::markAttributedPrivateClickMeasurementsAsExpiredForTesting):
(WTR::TestController::simulateResourceLoadStatisticsSessionRestart):

  • WebKitTestRunner/TestController.h:
  • WebKitTestRunner/TestInvocation.cpp:

(WTR::TestInvocation::didReceiveSynchronousMessageFromInjectedBundle):

LayoutTests:

Added layout test coverage. Removed 'Conversion request sent:'
category in the toString() method because it will always be false.
Sent conversions are purged from the database, so it is not a useful
piece of information to report. Updated naming based on standards
coversations.

  • http/tests/privateClickMeasurement/attribution-conversion-through-cross-site-image-redirect-expected.txt:
  • http/tests/privateClickMeasurement/attribution-conversion-through-cross-site-image-redirect.html:
  • http/tests/privateClickMeasurement/attribution-conversion-through-image-redirect-in-new-window-expected.txt:
  • http/tests/privateClickMeasurement/attribution-conversion-through-image-redirect-in-new-window.html:
  • http/tests/privateClickMeasurement/attribution-conversion-through-image-redirect-with-priority-expected.txt:
  • http/tests/privateClickMeasurement/attribution-conversion-through-image-redirect-with-priority.html:
  • http/tests/privateClickMeasurement/attribution-conversion-through-image-redirect-without-priority-expected.txt:
  • http/tests/privateClickMeasurement/attribution-conversion-through-image-redirect-without-priority.html:
  • http/tests/privateClickMeasurement/conversion-disabled-in-ephemeral-session-expected.txt:
  • http/tests/privateClickMeasurement/expired-ad-click-gets-removed-on-session-start-expected.txt: Added.
  • http/tests/privateClickMeasurement/expired-ad-click-gets-removed-on-session-start.html: Copied from LayoutTests/http/tests/privateClickMeasurement/second-conversion-with-higher-priority.html.
  • http/tests/privateClickMeasurement/expired-attribution-report-gets-sent-on-session-start-expected.txt: Added.
  • http/tests/privateClickMeasurement/expired-attribution-report-gets-sent-on-session-start.html: Copied from LayoutTests/http/tests/privateClickMeasurement/send-attribution-conversion-request.html.
  • http/tests/privateClickMeasurement/resources/conversionReport.php:
  • http/tests/privateClickMeasurement/resources/getConversionData.php:
  • http/tests/privateClickMeasurement/second-attribution-converted-with-higher-priority-expected.txt:
  • http/tests/privateClickMeasurement/second-attribution-converted-with-higher-priority.html:
  • http/tests/privateClickMeasurement/second-attribution-converted-with-lower-priority-expected.txt:
  • http/tests/privateClickMeasurement/second-attribution-converted-with-lower-priority.html:
  • http/tests/privateClickMeasurement/second-conversion-with-higher-priority-expected.txt:
  • http/tests/privateClickMeasurement/second-conversion-with-higher-priority.html:
  • http/tests/privateClickMeasurement/second-conversion-with-lower-priority-expected.txt:
  • http/tests/privateClickMeasurement/second-conversion-with-lower-priority.html:
  • http/tests/privateClickMeasurement/send-attribution-conversion-request-expected.txt:
  • http/tests/privateClickMeasurement/send-attribution-conversion-request.html:
  • http/tests/privateClickMeasurement/store-private-click-measurement-expected.txt:
Location:
trunk
Files:
2 added
56 edited
2 copied

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r270133 r270136  
     12020-11-20  Kate Cheney  <katherine_cheney@apple.com>
     2
     3        PCM: Persist pending ad clicks and attributions so they can survive browser restart
     4        https://bugs.webkit.org/show_bug.cgi?id=219134
     5        <rdar://problem/70470129>
     6
     7        Reviewed by John Wilander.
     8       
     9        Added layout test coverage. Removed 'Conversion request sent:'
     10        category in the toString() method because it will always be false.
     11        Sent conversions are purged from the database, so it is not a useful
     12        piece of information to report. Updated naming based on standards
     13        coversations.
     14
     15        * http/tests/privateClickMeasurement/attribution-conversion-through-cross-site-image-redirect-expected.txt:
     16        * http/tests/privateClickMeasurement/attribution-conversion-through-cross-site-image-redirect.html:
     17        * http/tests/privateClickMeasurement/attribution-conversion-through-image-redirect-in-new-window-expected.txt:
     18        * http/tests/privateClickMeasurement/attribution-conversion-through-image-redirect-in-new-window.html:
     19        * http/tests/privateClickMeasurement/attribution-conversion-through-image-redirect-with-priority-expected.txt:
     20        * http/tests/privateClickMeasurement/attribution-conversion-through-image-redirect-with-priority.html:
     21        * http/tests/privateClickMeasurement/attribution-conversion-through-image-redirect-without-priority-expected.txt:
     22        * http/tests/privateClickMeasurement/attribution-conversion-through-image-redirect-without-priority.html:
     23        * http/tests/privateClickMeasurement/conversion-disabled-in-ephemeral-session-expected.txt:
     24        * http/tests/privateClickMeasurement/expired-ad-click-gets-removed-on-session-start-expected.txt: Added.
     25        * http/tests/privateClickMeasurement/expired-ad-click-gets-removed-on-session-start.html: Copied from LayoutTests/http/tests/privateClickMeasurement/second-conversion-with-higher-priority.html.
     26        * http/tests/privateClickMeasurement/expired-attribution-report-gets-sent-on-session-start-expected.txt: Added.
     27        * http/tests/privateClickMeasurement/expired-attribution-report-gets-sent-on-session-start.html: Copied from LayoutTests/http/tests/privateClickMeasurement/send-attribution-conversion-request.html.
     28        * http/tests/privateClickMeasurement/resources/conversionReport.php:
     29        * http/tests/privateClickMeasurement/resources/getConversionData.php:
     30        * http/tests/privateClickMeasurement/second-attribution-converted-with-higher-priority-expected.txt:
     31        * http/tests/privateClickMeasurement/second-attribution-converted-with-higher-priority.html:
     32        * http/tests/privateClickMeasurement/second-attribution-converted-with-lower-priority-expected.txt:
     33        * http/tests/privateClickMeasurement/second-attribution-converted-with-lower-priority.html:
     34        * http/tests/privateClickMeasurement/second-conversion-with-higher-priority-expected.txt:
     35        * http/tests/privateClickMeasurement/second-conversion-with-higher-priority.html:
     36        * http/tests/privateClickMeasurement/second-conversion-with-lower-priority-expected.txt:
     37        * http/tests/privateClickMeasurement/second-conversion-with-lower-priority.html:
     38        * http/tests/privateClickMeasurement/send-attribution-conversion-request-expected.txt:
     39        * http/tests/privateClickMeasurement/send-attribution-conversion-request.html:
     40        * http/tests/privateClickMeasurement/store-private-click-measurement-expected.txt:
     41
    1422020-11-20  Lauro Moura  <lmoura@igalia.com>
    243
  • trunk/LayoutTests/http/tests/contentextensions/block-private-click-measurement-expected.txt

    r269712 r270136  
    88Frame: '<!--frame1-->'
    99--------
    10 Conversion not received - timed out.
     10Attribution not received - timed out.
    1111
    12 Unconverted Private Click Measurements:
     12Unattributed Private Click Measurements:
    1313WebCore::PrivateClickMeasurement 1
    14 Source: 127.0.0.1
    15 Destination: localhost
    16 Campaign ID: 3
    17 No conversion data.
     14Source site: 127.0.0.1
     15Attribute on site: localhost
     16Source ID: 3
     17No attribution trigger data.
  • trunk/LayoutTests/http/tests/privateClickMeasurement/attribution-conversion-through-cross-site-image-redirect-expected.txt

    r269712 r270136  
    1 Tests that triggering of private click measurement conversions through cross-site redirects do not work.
     1Tests that triggering of private click measurement attributions through cross-site redirects do not work.
    22
    33
    4 Unconverted Private Click Measurements:
     4Unattributed Private Click Measurements:
    55WebCore::PrivateClickMeasurement 1
    6 Source: 127.0.0.1
    7 Destination: localhost
    8 Campaign ID: 3
    9 No conversion data.
     6Source site: 127.0.0.1
     7Attribute on site: localhost
     8Source ID: 3
     9No attribution trigger data.
  • trunk/LayoutTests/http/tests/privateClickMeasurement/attribution-conversion-through-cross-site-image-redirect.html

    r269886 r270136  
    88</head>
    99<body onload="setTimeout(runTest, 0)">
    10 <div id="description">Tests that triggering of private click measurement conversions through cross-site redirects do not work.</div>
     10<div id="description">Tests that triggering of private click measurement attributions through cross-site redirects do not work.</div>
    1111<a id="targetLink" href="http://localhost:8000/privateClickMeasurement/attribution-conversion-through-cross-site-image-redirect.html?stepTwo" attributionsourceid="3" attributeon="http://localhost:8000">Link</a><br>
    1212<div id="output"></div>
  • trunk/LayoutTests/http/tests/privateClickMeasurement/attribution-conversion-through-image-redirect-in-new-window-expected.txt

    r269712 r270136  
    1 Tests triggering of private click measurement conversions in a new window.
     1Tests triggering of private click measurement attributions in a new window.
    22
    33
    4 Converted Private Click Measurements:
     4Attributed Private Click Measurements:
    55WebCore::PrivateClickMeasurement 1
    6 Source: 127.0.0.1
    7 Destination: localhost
    8 Campaign ID: 3
    9 Conversion data: 12
    10 Conversion priority: 3
    11 Conversion earliest time to send: Within 24-48 hours
    12 Conversion request sent: false
     6Source site: 127.0.0.1
     7Attribute on site: localhost
     8Source ID: 3
     9Attribution trigger data: 12
     10Attribution priority: 3
     11Attribution earliest time to send: Within 24-48 hours
  • trunk/LayoutTests/http/tests/privateClickMeasurement/attribution-conversion-through-image-redirect-in-new-window.html

    r269886 r270136  
    88</head>
    99<body onload="setTimeout(runTest, 0)">
    10 <div id="description">Tests triggering of private click measurement conversions in a new window.</div>
     10<div id="description">Tests triggering of private click measurement attributions in a new window.</div>
    1111<a target="_blank" rel="opener" id="targetLink" href="http://localhost:8000/privateClickMeasurement/resources/convertAndPostMessageBack.html" attributionsourceid="3" attributeon="http://localhost:8000">Link</a><br>
    1212<div id="output"></div>
  • trunk/LayoutTests/http/tests/privateClickMeasurement/attribution-conversion-through-image-redirect-with-priority-expected.txt

    r269712 r270136  
    1 Tests triggering of private click measurement conversions with priority.
     1Tests triggering of private click measurement attributions with priority.
    22
    33
    4 Converted Private Click Measurements:
     4Attributed Private Click Measurements:
    55WebCore::PrivateClickMeasurement 1
    6 Source: 127.0.0.1
    7 Destination: localhost
    8 Campaign ID: 3
    9 Conversion data: 12
    10 Conversion priority: 3
    11 Conversion earliest time to send: Within 24-48 hours
    12 Conversion request sent: false
     6Source site: 127.0.0.1
     7Attribute on site: localhost
     8Source ID: 3
     9Attribution trigger data: 12
     10Attribution priority: 3
     11Attribution earliest time to send: Within 24-48 hours
  • trunk/LayoutTests/http/tests/privateClickMeasurement/attribution-conversion-through-image-redirect-with-priority.html

    r269886 r270136  
    88</head>
    99<body onload="setTimeout(runTest, 0)">
    10 <div id="description">Tests triggering of private click measurement conversions with priority.</div>
     10<div id="description">Tests triggering of private click measurement attributions with priority.</div>
    1111<a id="targetLink" href="http://localhost:8000/privateClickMeasurement/attribution-conversion-through-image-redirect-with-priority.html?stepTwo" attributionsourceid="3" attributeon="http://localhost:8000">Link</a><br>
    1212<div id="output"></div>
  • trunk/LayoutTests/http/tests/privateClickMeasurement/attribution-conversion-through-image-redirect-without-priority-expected.txt

    r269712 r270136  
    1 Tests triggering of private click measurement conversions without priority.
     1Tests triggering of private click measurement attributions without priority.
    22
    33
    4 Converted Private Click Measurements:
     4Attributed Private Click Measurements:
    55WebCore::PrivateClickMeasurement 1
    6 Source: 127.0.0.1
    7 Destination: localhost
    8 Campaign ID: 3
    9 Conversion data: 12
    10 Conversion priority: 0
    11 Conversion earliest time to send: Within 24-48 hours
    12 Conversion request sent: false
     6Source site: 127.0.0.1
     7Attribute on site: localhost
     8Source ID: 3
     9Attribution trigger data: 12
     10Attribution priority: 0
     11Attribution earliest time to send: Within 24-48 hours
  • trunk/LayoutTests/http/tests/privateClickMeasurement/attribution-conversion-through-image-redirect-without-priority.html

    r269886 r270136  
    88</head>
    99<body onload="setTimeout(runTest, 0)">
    10 <div id="description">Tests triggering of private click measurement conversions without priority.</div>
     10<div id="description">Tests triggering of private click measurement attributions without priority.</div>
    1111<a id="targetLink" href="http://localhost:8000/privateClickMeasurement/attribution-conversion-through-image-redirect-without-priority.html?stepTwo" attributionsourceid="3" attributeon="http://localhost:8000">Link</a><br>
    1212<div id="output"></div>
  • trunk/LayoutTests/http/tests/privateClickMeasurement/conversion-disabled-in-ephemeral-session-expected.txt

    r269712 r270136  
    66Frame: '<!--frame1-->'
    77--------
    8 Conversion not received - timed out.
     8Attribution not received - timed out.
    99
    10 Unconverted Private Click Measurements:
     10Unattributed Private Click Measurements:
    1111WebCore::PrivateClickMeasurement 1
    12 Source: 127.0.0.1
    13 Destination: localhost
    14 Campaign ID: 3
    15 No conversion data.
     12Source site: 127.0.0.1
     13Attribute on site: localhost
     14Source ID: 3
     15No attribution trigger data.
  • trunk/LayoutTests/http/tests/privateClickMeasurement/expired-ad-click-gets-removed-on-session-start.html

    r270135 r270136  
    88</head>
    99<body onload="setTimeout(runTest, 0)">
    10 <div id="description">Tests that the attribution is updated if it gets a second conversion with higher priority.</div>
     10<div id="description">Tests that an expired ad click does not get converted on session start.</div>
    1111<a id="targetLink">Link</a><br>
    1212<div id="output"></div>
    1313<script>
    14     const path = "/privateClickMeasurement/second-conversion-with-higher-priority.html";
     14    const path = "/privateClickMeasurement/expired-ad-click-gets-removed-on-session-start.html";
    1515    const configuration = [
    1616        {
     
    4444    }
    4545
    46     function convert(priority, callback) {
     46    function convert(callback) {
    4747        let pixelElement = document.getElementById("pixel");
    4848        if (pixelElement)
     
    5050
    5151        let imageElement = document.createElement("img");
    52         imageElement.src = "https://127.0.0.1:8443/privateClickMeasurement/resources/redirectToConversion.php?conversionData=12&priority=" + priority;
     52        imageElement.src = "https://127.0.0.1:8443/privateClickMeasurement/resources/redirectToConversion.php?conversionData=12";
    5353        imageElement.id = "pixel";
    5454        imageElement.onerror = callback;
     
    5959        if (window.testRunner) {
    6060            if (window.location.search === "") {
    61                 // Ad click 127.0.0.1 –> localhost.
    6261                configureLink(0);
    6362                activateElement("targetLink");
    6463            } else if (window.location.search === "?stepTwo") {
    65                 // Convert the ad click with priority 3.
    66                 convert("03", function() {
    67                     // Convert the ad click with priority 4.
    68                     convert("04", function() {
    69                         testRunner.dumpPrivateClickMeasurement();
    70                         document.body.removeChild(document.getElementById("targetLink"));
    71                         document.body.removeChild(document.getElementById("pixel"));
    72                         tearDownAndFinish();
    73                     });
     64                window.testRunner.markPrivateClickMeasurementsAsExpiredForTesting();
     65                window.testRunner.simulateResourceLoadStatisticsSessionRestart();
     66                convert(function() {
     67                    testRunner.dumpPrivateClickMeasurement();
     68                    document.body.removeChild(document.getElementById("targetLink"));
     69                    document.body.removeChild(document.getElementById("pixel"));
     70                    tearDownAndFinish();
    7471                });
    75             } else {
    76                 document.getElementById("output").innerText = "FAIL Unknown window.location.search == " + window.location.search + ".";
    77                 tearDownAndFinish();
    7872            }
    7973        } else {
  • trunk/LayoutTests/http/tests/privateClickMeasurement/expired-attribution-report-gets-sent-on-session-start.html

    r270135 r270136  
    88</head>
    99<body onload="setTimeout(runTest, 0)">
    10 <div id="description">Tests sending of private click measurement requests after a conversion. Also tests that cookies are not sent in those requests and cookies are not accepted in the responses.</div>
    11 <a id="targetLink" href="http://localhost:8000/privateClickMeasurement/send-attribution-conversion-request.html?stepTwo" attributionsourceid="3" attributeon="http://localhost:8000">Link</a><br>
     10<div id="description">Tests sending of private click measurement requests after an attribution happens immediately for reports which have expired when a session is closed.</div>
     11<a id="targetLink" href="http://localhost:8000/privateClickMeasurement/expired-attribution-report-gets-sent-on-session-start.html?stepTwo" attributionsourceid="3" attributeon="http://localhost:8000">Link</a><br>
    1212<div id="output"></div>
    1313<script>
     
    1919
    2020    if (window.testRunner) {
    21         testRunner.setPrivateClickMeasurementOverrideTimerForTesting(true);
    2221        testRunner.setPrivateClickMeasurementConversionURLForTesting("http://127.0.0.1:8000/privateClickMeasurement/resources/conversionReport.php?nonce=" + nonce);
    2322    }
     
    4645
    4746    function appendConversionDataIframeAndFinish() {
     47        // Mark this attribution as past-due for reporting, and simulate a session restart to
     48        // make sure the attribution gets sent immediately.
     49        window.testRunner.markAttributedPrivateClickMeasurementsAsExpiredForTesting();
     50        testRunner.simulateResourceLoadStatisticsSessionRestart();
    4851        testRunner.dumpPrivateClickMeasurement();
    4952        document.body.removeChild(document.getElementById("targetLink"));
    5053        document.body.removeChild(document.getElementById("pixel"));
    5154
    52         appendIframe("http://127.0.0.1:8000/cookies/resources/echo-cookies.php");
    5355        appendIframe("http://127.0.0.1:8000/privateClickMeasurement/resources/getConversionData.php?timeout_ms=2000&nonce=" + nonce, function() {
    54             appendIframe("http://127.0.0.1:8000/cookies/resources/echo-cookies.php", function() {
    55                 tearDownAndFinish();
    56             });
     56            tearDownAndFinish();
    5757        });
    5858    }
     
    6969                document.body.appendChild(imageElement);
    7070            } else {
    71                 document.cookie = "cookieSetAsFirstParty=1; path=/";
    7271                activateElement("targetLink");
    7372            }
  • trunk/LayoutTests/http/tests/privateClickMeasurement/resources/conversionReport.php

    r269712 r270136  
    1616        fwrite($conversionFile, "$name: $outputURL\n");
    1717    } else if ($name === "HTTP_COOKIE") {
    18         fwrite($conversionFile, "Cookies in conversion request: $value\n");
     18        fwrite($conversionFile, "Cookies in attribution request: $value\n");
    1919        $cookiesFound = true;
    2020    } else if ($name === "CONTENT_TYPE") {
     
    2323}
    2424if (!$cookiesFound) {
    25     fwrite($conversionFile, "No cookies in conversion request.\n");
     25    fwrite($conversionFile, "No cookies in attribution request.\n");
    2626}
    2727
  • trunk/LayoutTests/http/tests/privateClickMeasurement/resources/getConversionData.php

    r269712 r270136  
    2828
    2929if ($conversionFileFound) {
    30     echo "Conversion received.";
     30    echo "Attribution received.";
    3131    $conversionFile = fopen($conversionFilePath, 'r');
    3232    while ($line = fgets($conversionFile)) {
     
    3838    unlink($conversionFilePath);
    3939} else {
    40     echo "Conversion not received - timed out.<br>";
     40    echo "Attribution not received - timed out.<br>";
    4141}
    4242
  • trunk/LayoutTests/http/tests/privateClickMeasurement/second-attribution-converted-with-higher-priority-expected.txt

    r269712 r270136  
    1 Tests that a second attribution conversion with higher priority replaces an older with lower priority.
     1Tests that a second attribution with higher priority replaces an older with lower priority.
    22
    33
    4 Converted Private Click Measurements:
     4Attributed Private Click Measurements:
    55WebCore::PrivateClickMeasurement 1
    6 Source: 127.0.0.1
    7 Destination: localhost
    8 Campaign ID: 4
    9 Conversion data: 12
    10 Conversion priority: 4
    11 Conversion earliest time to send: Within 24-48 hours
    12 Conversion request sent: false
     6Source site: 127.0.0.1
     7Attribute on site: localhost
     8Source ID: 4
     9Attribution trigger data: 12
     10Attribution priority: 4
     11Attribution earliest time to send: Within 24-48 hours
  • trunk/LayoutTests/http/tests/privateClickMeasurement/second-attribution-converted-with-higher-priority.html

    r269886 r270136  
    88</head>
    99<body onload="setTimeout(runTest, 0)">
    10 <div id="description">Tests that a second attribution conversion with higher priority replaces an older with lower priority.</div>
     10<div id="description">Tests that a second attribution with higher priority replaces an older with lower priority.</div>
    1111<a id="targetLink">Link</a><br>
    1212<div id="output"></div>
  • trunk/LayoutTests/http/tests/privateClickMeasurement/second-attribution-converted-with-lower-priority-expected.txt

    r269712 r270136  
    1 Tests that a second attribution conversion with lower priority does not replace an older with higher priority.
     1Tests that a second attribution with lower priority does not replace an older with higher priority.
    22
    33
    4 Converted Private Click Measurements:
     4Attributed Private Click Measurements:
    55WebCore::PrivateClickMeasurement 1
    6 Source: 127.0.0.1
    7 Destination: localhost
    8 Campaign ID: 3
    9 Conversion data: 12
    10 Conversion priority: 4
    11 Conversion earliest time to send: Within 24-48 hours
    12 Conversion request sent: false
     6Source site: 127.0.0.1
     7Attribute on site: localhost
     8Source ID: 3
     9Attribution trigger data: 12
     10Attribution priority: 4
     11Attribution earliest time to send: Within 24-48 hours
  • trunk/LayoutTests/http/tests/privateClickMeasurement/second-attribution-converted-with-lower-priority.html

    r269886 r270136  
    88</head>
    99<body onload="setTimeout(runTest, 0)">
    10 <div id="description">Tests that a second attribution conversion with lower priority does not replace an older with higher priority.</div>
     10<div id="description">Tests that a second attribution with lower priority does not replace an older with higher priority.</div>
    1111<a id="targetLink">Link</a><br>
    1212<div id="output"></div>
  • trunk/LayoutTests/http/tests/privateClickMeasurement/second-conversion-with-higher-priority-expected.txt

    r269712 r270136  
    1 Tests that the attribution is updated if it gets a second conversion with higher priority.
     1Tests that the attribution is updated if it gets a second attribution with higher priority.
    22
    33
    4 Converted Private Click Measurements:
     4Attributed Private Click Measurements:
    55WebCore::PrivateClickMeasurement 1
    6 Source: 127.0.0.1
    7 Destination: localhost
    8 Campaign ID: 3
    9 Conversion data: 12
    10 Conversion priority: 4
    11 Conversion earliest time to send: Within 24-48 hours
    12 Conversion request sent: false
     6Source site: 127.0.0.1
     7Attribute on site: localhost
     8Source ID: 3
     9Attribution trigger data: 12
     10Attribution priority: 4
     11Attribution earliest time to send: Within 24-48 hours
  • trunk/LayoutTests/http/tests/privateClickMeasurement/second-conversion-with-higher-priority.html

    r269886 r270136  
    88</head>
    99<body onload="setTimeout(runTest, 0)">
    10 <div id="description">Tests that the attribution is updated if it gets a second conversion with higher priority.</div>
     10<div id="description">Tests that the attribution is updated if it gets a second attribution with higher priority.</div>
    1111<a id="targetLink">Link</a><br>
    1212<div id="output"></div>
  • trunk/LayoutTests/http/tests/privateClickMeasurement/second-conversion-with-lower-priority-expected.txt

    r269712 r270136  
    1 Tests that the attribution is not updated if it gets a second conversion with lower priority.
     1Tests that the attribution is not updated if it gets a second attribution with lower priority.
    22
    33
    4 Converted Private Click Measurements:
     4Attributed Private Click Measurements:
    55WebCore::PrivateClickMeasurement 1
    6 Source: 127.0.0.1
    7 Destination: localhost
    8 Campaign ID: 3
    9 Conversion data: 12
    10 Conversion priority: 4
    11 Conversion earliest time to send: Within 24-48 hours
    12 Conversion request sent: false
     6Source site: 127.0.0.1
     7Attribute on site: localhost
     8Source ID: 3
     9Attribution trigger data: 12
     10Attribution priority: 4
     11Attribution earliest time to send: Within 24-48 hours
  • trunk/LayoutTests/http/tests/privateClickMeasurement/second-conversion-with-lower-priority.html

    r269886 r270136  
    88</head>
    99<body onload="setTimeout(runTest, 0)">
    10 <div id="description">Tests that the attribution is not updated if it gets a second conversion with lower priority.</div>
     10<div id="description">Tests that the attribution is not updated if it gets a second attribution with lower priority.</div>
    1111<a id="targetLink">Link</a><br>
    1212<div id="output"></div>
  • trunk/LayoutTests/http/tests/privateClickMeasurement/send-attribution-conversion-request-expected.txt

    r269886 r270136  
    1 Tests sending of private click measurement requests after a conversion. Also tests that cookies are not sent in those requests and cookies are not accepted in the responses.
     1Tests sending of private click measurement requests after an attribution. Also tests that cookies are not sent in those requests and cookies are not accepted in the responses.
    22
    33
     
    1111Frame: '<!--frame2-->'
    1212--------
    13 Conversion received.
     13Attribution received.
    1414HTTP_HOST: 127.0.0.1:8000
    1515Content type: application/json
    1616REQUEST_URI: /privateClickMeasurement/resources/conversionReport.php
    17 No cookies in conversion request.
     17No cookies in attribution request.
    1818Request body:
    1919{"source-engagement-type":"click","source-site":"127.0.0.1","source-id":3,"attributed-on-site":"localhost","trigger-data":12,"report-version":1}
  • trunk/LayoutTests/http/tests/privateClickMeasurement/send-attribution-conversion-request.html

    r269886 r270136  
    88</head>
    99<body onload="setTimeout(runTest, 0)">
    10 <div id="description">Tests sending of private click measurement requests after a conversion. Also tests that cookies are not sent in those requests and cookies are not accepted in the responses.</div>
     10<div id="description">Tests sending of private click measurement requests after an attribution. Also tests that cookies are not sent in those requests and cookies are not accepted in the responses.</div>
    1111<a id="targetLink" href="http://localhost:8000/privateClickMeasurement/send-attribution-conversion-request.html?stepTwo" attributionsourceid="3" attributeon="http://localhost:8000">Link</a><br>
    1212<div id="output"></div>
  • trunk/LayoutTests/http/tests/privateClickMeasurement/store-private-click-measurement-expected.txt

    r269712 r270136  
    22
    33
    4 Unconverted Private Click Measurements:
     4Unattributed Private Click Measurements:
    55WebCore::PrivateClickMeasurement 1
    6 Source: 127.0.0.1
    7 Destination: localhost
    8 Campaign ID: 3
    9 No conversion data.
     6Source site: 127.0.0.1
     7Attribute on site: localhost
     8Source ID: 3
     9No attribution trigger data.
  • trunk/Source/WebCore/ChangeLog

    r270135 r270136  
     12020-11-20  Kate Cheney  <katherine_cheney@apple.com>
     2
     3        PCM: Persist pending ad clicks and attributions so they can survive browser restart
     4        https://bugs.webkit.org/show_bug.cgi?id=219134
     5        <rdar://problem/70470129>
     6
     7        Reviewed by John Wilander.
     8
     9        This patch migrates PCM data to be stored on disk and
     10        updates naming of various PCM data to match naming agreed
     11        upon in standards bodies:
     12
     13        - source -> sourceSite
     14        - campaign/campaignID -> sourceID
     15        - destination -> attributeOnSite
     16        - conversion/conversionValue -> attributionTriggerData
     17        - unconverted -> unattributed
     18        - convert(ed) -> attribute(d)
     19
     20        Tests: http/tests/privateClickMeasurement/expired-ad-click-gets-removed-on-session-start.html
     21               http/tests/privateClickMeasurement/expired-attribution-report-gets-sent-on-session-start.html
     22
     23        * html/HTMLAnchorElement.cpp:
     24        (WebCore::HTMLAnchorElement::parsePrivateClickMeasurement const):
     25        * loader/PrivateClickMeasurement.cpp:
     26        (WebCore::PrivateClickMeasurement::maxAge):
     27        (WebCore::PrivateClickMeasurement::isValid const):
     28        (WebCore::PrivateClickMeasurement::parseAttributionRequest):
     29        (WebCore::PrivateClickMeasurement::attributeAndGetEarliestTimeToSend):
     30        (WebCore::PrivateClickMeasurement::hasHigherPriorityThan const):
     31        (WebCore::PrivateClickMeasurement::reportURL const):
     32        (WebCore::PrivateClickMeasurement::json const):
     33        (WebCore::PrivateClickMeasurement::parseConversionRequest): Deleted.
     34        (WebCore::PrivateClickMeasurement::convertAndGetEarliestTimeToSend): Deleted.
     35        Renaming.
     36       
     37        (WebCore::PrivateClickMeasurement::markAsExpired): Deleted.
     38        (WebCore::PrivateClickMeasurement::hasExpired const): Deleted.
     39        (WebCore::PrivateClickMeasurement::markConversionAsSent): Deleted.
     40        (WebCore::PrivateClickMeasurement::wasConversionSent const): Deleted.
     41        We can remove the *Expired() functions as they were only indicators
     42        for the HashMap storage to know what attributions to delete. This is
     43        now handled by SQLite. Similarly, we can remove the markConversionAsSent
     44        and wasConversionSent functions because a sent attribution will not
     45        be stored in the database, so this value will always be false.
     46
     47        (WebCore::PrivateClickMeasurement::toString const): Deleted.
     48        * loader/PrivateClickMeasurement.h:
     49        (WebCore::PrivateClickMeasurement::SourceID::SourceID):
     50        (WebCore::PrivateClickMeasurement::SourceSite::SourceSite):
     51        (WebCore::PrivateClickMeasurement::SourceSite::operator== const):
     52        (WebCore::PrivateClickMeasurement::SourceSite::deletedValue):
     53        (WebCore::PrivateClickMeasurement::SourceSite::constructDeletedValue):
     54        (WebCore::PrivateClickMeasurement::SourceSiteHash::hash):
     55        (WebCore::PrivateClickMeasurement::SourceSiteHash::equal):
     56        (WebCore::PrivateClickMeasurement::AttributeOnSite::AttributeOnSite):
     57        (WebCore::PrivateClickMeasurement::AttributeOnSite::operator== const):
     58        (WebCore::PrivateClickMeasurement::AttributeOnSite::deletedValue):
     59        (WebCore::PrivateClickMeasurement::AttributeOnSite::constructDeletedValue):
     60        (WebCore::PrivateClickMeasurement::AttributeOnSiteHash::hash):
     61        (WebCore::PrivateClickMeasurement::AttributeOnSiteHash::equal):
     62        (WebCore::PrivateClickMeasurement::AttributionTriggerData::AttributionTriggerData):
     63        Renaming.
     64
     65        (WebCore::PrivateClickMeasurement::PrivateClickMeasurement):
     66        (WebCore::PrivateClickMeasurement::sourceSite const):
     67        (WebCore::PrivateClickMeasurement::attributeOnSite const):
     68        (WebCore::PrivateClickMeasurement::timeOfAdClick const):
     69        (WebCore::PrivateClickMeasurement::setEarliestTimeToSend):
     70        (WebCore::PrivateClickMeasurement::sourceID):
     71        (WebCore::PrivateClickMeasurement::attributionTriggerData):
     72        (WebCore::PrivateClickMeasurement::setAttribution):
     73        Now that we store data on disk, we need a more flexible constructor
     74        and more functions to rebuild PCM objects from the database.
     75
     76        (WebCore::PrivateClickMeasurement::encode const):
     77        (WebCore::PrivateClickMeasurement::decode):
     78        (WebCore::PrivateClickMeasurement::AttributionTriggerData::encode const):
     79        (WebCore::PrivateClickMeasurement::AttributionTriggerData::decode):
     80        (WTF::HashTraits<WebCore::PrivateClickMeasurement::SourceSite>::emptyValue):
     81        (WTF::HashTraits<WebCore::PrivateClickMeasurement::SourceSite>::constructDeletedValue):
     82        (WTF::HashTraits<WebCore::PrivateClickMeasurement::SourceSite>::isDeletedValue):
     83        (WTF::HashTraits<WebCore::PrivateClickMeasurement::AttributeOnSite>::emptyValue):
     84        (WTF::HashTraits<WebCore::PrivateClickMeasurement::AttributeOnSite>::constructDeletedValue):
     85        (WTF::HashTraits<WebCore::PrivateClickMeasurement::AttributeOnSite>::isDeletedValue):
     86        (WebCore::PrivateClickMeasurement::Campaign::Campaign): Deleted.
     87        (WebCore::PrivateClickMeasurement::Campaign::isValid const): Deleted.
     88        (WebCore::PrivateClickMeasurement::Source::Source): Deleted.
     89        (WebCore::PrivateClickMeasurement::Source::operator== const): Deleted.
     90        (WebCore::PrivateClickMeasurement::Source::matches const): Deleted.
     91        (WebCore::PrivateClickMeasurement::Source::isHashTableDeletedValue const): Deleted.
     92        (WebCore::PrivateClickMeasurement::Source::deletedValue): Deleted.
     93        (WebCore::PrivateClickMeasurement::Source::constructDeletedValue): Deleted.
     94        (WebCore::PrivateClickMeasurement::Source::deleteValue): Deleted.
     95        (WebCore::PrivateClickMeasurement::Source::isDeletedValue const): Deleted.
     96        (WebCore::PrivateClickMeasurement::SourceHash::hash): Deleted.
     97        (WebCore::PrivateClickMeasurement::SourceHash::equal): Deleted.
     98        (WebCore::PrivateClickMeasurement::Destination::Destination): Deleted.
     99        (WebCore::PrivateClickMeasurement::Destination::operator== const): Deleted.
     100        (WebCore::PrivateClickMeasurement::Destination::matches const): Deleted.
     101        (WebCore::PrivateClickMeasurement::Destination::isHashTableDeletedValue const): Deleted.
     102        (WebCore::PrivateClickMeasurement::Destination::deletedValue): Deleted.
     103        (WebCore::PrivateClickMeasurement::Destination::constructDeletedValue): Deleted.
     104        (WebCore::PrivateClickMeasurement::Destination::deleteValue): Deleted.
     105        (WebCore::PrivateClickMeasurement::Destination::isDeletedValue const): Deleted.
     106        (WebCore::PrivateClickMeasurement::DestinationHash::hash): Deleted.
     107        (WebCore::PrivateClickMeasurement::DestinationHash::equal): Deleted.
     108        (WebCore::PrivateClickMeasurement::Conversion::Conversion): Deleted.
     109        (WebCore::PrivateClickMeasurement::Conversion::isValid const): Deleted.
     110        (WebCore::PrivateClickMeasurement::source const): Deleted.
     111        (WebCore::PrivateClickMeasurement::destination const): Deleted.
     112        Renaming.
     113
     114        (WebCore::PrivateClickMeasurement::isEmpty const): Deleted.
     115        Not needed anymore, the database uses Optionals to indicate an empty result.
     116
     117        (WebCore::PrivateClickMeasurement::Conversion::encode const): Deleted.
     118        (WebCore::PrivateClickMeasurement::Conversion::decode): Deleted.
     119        (WTF::HashTraits<WebCore::PrivateClickMeasurement::Source>::emptyValue): Deleted.
     120        (WTF::HashTraits<WebCore::PrivateClickMeasurement::Source>::constructDeletedValue): Deleted.
     121        (WTF::HashTraits<WebCore::PrivateClickMeasurement::Source>::isDeletedValue): Deleted.
     122        (WTF::HashTraits<WebCore::PrivateClickMeasurement::Destination>::emptyValue): Deleted.
     123        (WTF::HashTraits<WebCore::PrivateClickMeasurement::Destination>::constructDeletedValue): Deleted.
     124        (WTF::HashTraits<WebCore::PrivateClickMeasurement::Destination>::isDeletedValue): Deleted.
     125        Renaming.
     126
    11272020-11-20  Zalan Bujtas  <zalan@apple.com>
    2128
  • trunk/Source/WebCore/html/HTMLAnchorElement.cpp

    r269953 r270136  
    401401Optional<PrivateClickMeasurement> HTMLAnchorElement::parsePrivateClickMeasurement() const
    402402{
    403     using Campaign = PrivateClickMeasurement::Campaign;
    404     using Source = PrivateClickMeasurement::Source;
    405     using Destination = PrivateClickMeasurement::Destination;
     403    using SourceID = PrivateClickMeasurement::SourceID;
     404    using SourceSite = PrivateClickMeasurement::SourceSite;
     405    using AttributeOnSite = PrivateClickMeasurement::AttributeOnSite;
    406406
    407407    auto* page = document().page();
     
    451451    }
    452452
    453     return PrivateClickMeasurement { Campaign(attributionSourceID.value()), Source(documentRegistrableDomain), Destination(attributeOnURL) };
     453    return PrivateClickMeasurement { SourceID(attributionSourceID.value()), SourceSite(documentRegistrableDomain), AttributeOnSite(attributeOnURL) };
    454454}
    455455
  • trunk/Source/WebCore/loader/PrivateClickMeasurement.cpp

    r269886 r270136  
    3838
    3939static const char privateClickMeasurementPathPrefix[] = "/.well-known/private-click-measurement/";
    40 const size_t privateClickMeasurementConversionDataPathSegmentSize = 2;
     40const size_t privateClickMeasurementAttributionTriggerDataPathSegmentSize = 2;
    4141const size_t privateClickMeasurementPriorityPathSegmentSize = 2;
    42 const Seconds maxAge { 24_h * 7 };
     42
     43const Seconds PrivateClickMeasurement::maxAge()
     44{
     45    return 24_h * 7;
     46};
    4347
    4448bool PrivateClickMeasurement::isValid() const
    4549{
    46     return m_conversion
    47         && m_conversion.value().isValid()
    48         && m_campaign.isValid()
    49         && !m_source.registrableDomain.isEmpty()
    50         && !m_destination.registrableDomain.isEmpty()
     50    return m_attributionTriggerData
     51        && m_attributionTriggerData.value().isValid()
     52        && m_sourceID.isValid()
     53        && !m_sourceSite.registrableDomain.isEmpty()
     54        && !m_attributeOnSite.registrableDomain.isEmpty()
    5155        && m_earliestTimeToSend;
    5256}
    5357
    54 Expected<PrivateClickMeasurement::Conversion, String> PrivateClickMeasurement::parseConversionRequest(const URL& redirectURL)
     58Expected<PrivateClickMeasurement::AttributionTriggerData, String> PrivateClickMeasurement::parseAttributionRequest(const URL& redirectURL)
    5559{
    5660    if (!redirectURL.protocolIs("https") || redirectURL.hasCredentials() || redirectURL.hasQuery() || redirectURL.hasFragmentIdentifier()) {
     
    7276
    7377    auto prefixLength = sizeof(privateClickMeasurementPathPrefix) - 1;
    74     if (path.length() == prefixLength + privateClickMeasurementConversionDataPathSegmentSize) {
    75         auto conversionDataUInt64 = path.substring(prefixLength, privateClickMeasurementConversionDataPathSegmentSize).toUInt64Strict();
    76         if (!conversionDataUInt64 || *conversionDataUInt64 > MaxEntropy) {
     78    if (path.length() == prefixLength + privateClickMeasurementAttributionTriggerDataPathSegmentSize) {
     79        auto attributionTriggerDataUInt64 = path.substring(prefixLength, privateClickMeasurementAttributionTriggerDataPathSegmentSize).toUInt64Strict();
     80        if (!attributionTriggerDataUInt64 || *attributionTriggerDataUInt64 > MaxEntropy) {
    7781            if (UNLIKELY(debugModeEnabled())) {
    7882                RELEASE_LOG_INFO(PrivateClickMeasurement, "Conversion was not accepted because the conversion data could not be parsed or was higher than the allowed maximum of %{public}u.", MaxEntropy);
     
    8286        }
    8387
    84         return Conversion { static_cast<uint32_t>(*conversionDataUInt64), Priority { 0 } };
     88        return AttributionTriggerData { static_cast<uint32_t>(*attributionTriggerDataUInt64), Priority { 0 } };
    8589    }
    8690   
    87     if (path.length() == prefixLength + privateClickMeasurementConversionDataPathSegmentSize + 1 + privateClickMeasurementPriorityPathSegmentSize) {
    88         auto conversionDataUInt64 = path.substring(prefixLength, privateClickMeasurementConversionDataPathSegmentSize).toUInt64Strict();
    89         if (!conversionDataUInt64 || *conversionDataUInt64 > MaxEntropy) {
     91    if (path.length() == prefixLength + privateClickMeasurementAttributionTriggerDataPathSegmentSize + 1 + privateClickMeasurementPriorityPathSegmentSize) {
     92        auto attributionTriggerDataUInt64 = path.substring(prefixLength, privateClickMeasurementAttributionTriggerDataPathSegmentSize).toUInt64Strict();
     93        if (!attributionTriggerDataUInt64 || *attributionTriggerDataUInt64 > MaxEntropy) {
    9094            if (UNLIKELY(debugModeEnabled())) {
    9195                RELEASE_LOG_INFO(PrivateClickMeasurement, "Conversion was not accepted because the conversion data could not be parsed or was higher than the allowed maximum of %{public}u.", MaxEntropy);
     
    9599        }
    96100
    97         auto conversionPriorityUInt64 = path.substring(prefixLength + privateClickMeasurementConversionDataPathSegmentSize + 1, privateClickMeasurementPriorityPathSegmentSize).toUInt64Strict();
    98         if (!conversionPriorityUInt64 || *conversionPriorityUInt64 > MaxEntropy) {
     101        auto attributionPriorityUInt64 = path.substring(prefixLength + privateClickMeasurementAttributionTriggerDataPathSegmentSize + 1, privateClickMeasurementPriorityPathSegmentSize).toUInt64Strict();
     102        if (!attributionPriorityUInt64 || *attributionPriorityUInt64 > MaxEntropy) {
    99103            if (UNLIKELY(debugModeEnabled())) {
    100104                RELEASE_LOG_INFO(PrivateClickMeasurement, "Conversion was not accepted because the priority could not be parsed or was higher than the allowed maximum of %{public}u.", MaxEntropy);
     
    104108        }
    105109
    106         return Conversion { static_cast<uint32_t>(*conversionDataUInt64), Priority { static_cast<uint32_t>(*conversionPriorityUInt64) } };
     110        return AttributionTriggerData { static_cast<uint32_t>(*attributionTriggerDataUInt64), Priority { static_cast<uint32_t>(*attributionPriorityUInt64) } };
    107111    }
    108112
     
    114118}
    115119
    116 Optional<Seconds> PrivateClickMeasurement::convertAndGetEarliestTimeToSend(Conversion&& conversion)
     120Optional<Seconds> PrivateClickMeasurement::attributeAndGetEarliestTimeToSend(AttributionTriggerData&& attributionTriggerData)
    117121{
    118     if (!conversion.isValid() || (m_conversion && m_conversion->priority >= conversion.priority))
     122    if (!attributionTriggerData.isValid() || (m_attributionTriggerData && m_attributionTriggerData->priority >= attributionTriggerData.priority))
    119123        return { };
    120124
    121     m_conversion = WTFMove(conversion);
     125    m_attributionTriggerData = WTFMove(attributionTriggerData);
    122126    // 24-48 hour delay before sending. This helps privacy since the conversion and the attribution
    123127    // requests are detached and the time of the attribution does not reveal the time of the conversion.
     
    127131}
    128132
    129 void PrivateClickMeasurement::markAsExpired()
    130 {
    131     m_timeOfAdClick = { };
    132 }
    133 
    134 bool PrivateClickMeasurement::hasExpired() const
    135 {
    136     return WallTime::now() > m_timeOfAdClick + maxAge;
    137 }
    138 
    139133bool PrivateClickMeasurement::hasHigherPriorityThan(const PrivateClickMeasurement& other) const
    140134{
    141     if (!other.m_conversion)
     135    if (!other.m_attributionTriggerData)
    142136        return true;
    143137   
    144     if (!m_conversion)
     138    if (!m_attributionTriggerData)
    145139        return false;
    146140
    147     return m_conversion->priority > other.m_conversion->priority;
     141    return m_attributionTriggerData->priority > other.m_attributionTriggerData->priority;
    148142}
    149143
     
    155149    StringBuilder builder;
    156150    builder.appendLiteral("https://");
    157     builder.append(m_source.registrableDomain.string());
     151    builder.append(m_sourceSite.registrableDomain.string());
    158152    builder.appendLiteral(privateClickMeasurementPathPrefix);
    159153
     
    168162{
    169163    auto reportDetails = JSON::Object::create();
    170     if (!m_conversion)
     164    if (!m_attributionTriggerData)
    171165        return reportDetails;
    172166
    173167    reportDetails->setString("source-engagement-type"_s, "click"_s);
    174     reportDetails->setString("source-site"_s, m_source.registrableDomain.string());
    175     reportDetails->setInteger("source-id"_s, m_campaign.id);
    176     reportDetails->setString("attributed-on-site"_s, m_destination.registrableDomain.string());
    177     reportDetails->setInteger("trigger-data"_s, m_conversion->data);
     168    reportDetails->setString("source-site"_s, m_sourceSite.registrableDomain.string());
     169    reportDetails->setInteger("source-id"_s, m_sourceID.id);
     170    reportDetails->setString("attributed-on-site"_s, m_attributeOnSite.registrableDomain.string());
     171    reportDetails->setInteger("trigger-data"_s, m_attributionTriggerData->data);
    178172    reportDetails->setInteger("report-version"_s, 1);
    179173    return reportDetails;
    180 }
    181 
    182 void PrivateClickMeasurement::markConversionAsSent()
    183 {
    184     ASSERT(m_conversion);
    185     if (m_conversion)
    186         m_conversion->wasSent = Conversion::WasSent::Yes;
    187 }
    188 
    189 bool PrivateClickMeasurement::wasConversionSent() const
    190 {
    191     return m_conversion && m_conversion->wasSent == Conversion::WasSent::Yes;
    192 }
    193 
    194 String PrivateClickMeasurement::toString() const
    195 {
    196     StringBuilder builder;
    197     builder.appendLiteral("Source: ");
    198     builder.append(m_source.registrableDomain.string());
    199     builder.appendLiteral("\nDestination: ");
    200     builder.append(m_destination.registrableDomain.string());
    201     builder.appendLiteral("\nCampaign ID: ");
    202     builder.appendNumber(m_campaign.id);
    203     if (m_conversion) {
    204         builder.appendLiteral("\nConversion data: ");
    205         builder.appendNumber(m_conversion.value().data);
    206         builder.appendLiteral("\nConversion priority: ");
    207         builder.appendNumber(m_conversion.value().priority);
    208         builder.appendLiteral("\nConversion earliest time to send: ");
    209         if (!m_earliestTimeToSend)
    210             builder.appendLiteral("Not set");
    211         else {
    212             auto secondsUntilSend = *m_earliestTimeToSend - WallTime::now();
    213             builder.append((secondsUntilSend >= 24_h && secondsUntilSend <= 48_h) ? "Within 24-48 hours" : "Outside 24-48 hours");
    214         }
    215         builder.appendLiteral("\nConversion request sent: ");
    216         builder.append((wasConversionSent() ? "true" : "false"));
    217     } else
    218         builder.appendLiteral("\nNo conversion data.");
    219     builder.append('\n');
    220 
    221     return builder.toString();
    222174}
    223175
  • trunk/Source/WebCore/loader/PrivateClickMeasurement.h

    r269712 r270136  
    4040class PrivateClickMeasurement {
    4141public:
    42     using CampaignId = uint32_t;
    43     using ConversionData = uint32_t;
    4442    using PriorityValue = uint32_t;
    4543
    4644    static constexpr uint32_t MaxEntropy = 63;
    4745
    48     struct Campaign {
    49         Campaign() = default;
    50         explicit Campaign(CampaignId id)
     46    struct SourceID {
     47        SourceID() = default;
     48        explicit SourceID(uint32_t id)
    5149            : id { id }
    5250        {
     
    5856        }
    5957       
    60         CampaignId id { 0 };
    61     };
    62 
    63     struct Source {
    64         Source() = default;
    65         explicit Source(const URL& url)
     58        uint32_t id { 0 };
     59    };
     60
     61    struct SourceSite {
     62        SourceSite() = default;
     63        explicit SourceSite(const URL& url)
    6664            : registrableDomain { url }
    6765        {
    6866        }
    6967
    70         explicit Source(const RegistrableDomain& domain)
     68        explicit SourceSite(const RegistrableDomain& domain)
    7169            : registrableDomain { domain }
    7270        {
    7371        }
    7472
    75         explicit Source(WTF::HashTableDeletedValueType)
     73        explicit SourceSite(WTF::HashTableDeletedValueType)
    7674            : registrableDomain(WTF::HashTableDeletedValue)
    7775        {
    7876        }
    7977
    80         bool operator==(const Source& other) const
     78        bool operator==(const SourceSite& other) const
    8179        {
    8280            return registrableDomain == other.registrableDomain;
     
    9391        }
    9492
    95         static Source deletedValue()
    96         {
    97             return Source { WTF::HashTableDeletedValue };
    98         }
    99 
    100         static void constructDeletedValue(Source& source)
    101         {
    102             new (&source) Source;
    103             source = Source::deletedValue();
     93        static SourceSite deletedValue()
     94        {
     95            return SourceSite { WTF::HashTableDeletedValue };
     96        }
     97
     98        static void constructDeletedValue(SourceSite& sourceSite)
     99        {
     100            new (&sourceSite) SourceSite;
     101            sourceSite = SourceSite::deletedValue();
    104102        }
    105103
     
    117115    };
    118116
    119     struct SourceHash {
    120         static unsigned hash(const Source& source)
    121         {
    122             return source.registrableDomain.hash();
    123         }
    124        
    125         static bool equal(const Source& a, const Source& b)
     117    struct SourceSiteHash {
     118        static unsigned hash(const SourceSite& sourceSite)
     119        {
     120            return sourceSite.registrableDomain.hash();
     121        }
     122       
     123        static bool equal(const SourceSite& a, const SourceSite& b)
    126124        {
    127125            return a == b;
     
    131129    };
    132130
    133     struct Destination {
    134         Destination() = default;
    135         explicit Destination(const URL& url)
     131    struct AttributeOnSite {
     132        AttributeOnSite() = default;
     133        explicit AttributeOnSite(const URL& url)
    136134            : registrableDomain { RegistrableDomain { url } }
    137135        {
    138136        }
    139137
    140         explicit Destination(WTF::HashTableDeletedValueType)
     138        explicit AttributeOnSite(WTF::HashTableDeletedValueType)
    141139            : registrableDomain { WTF::HashTableDeletedValue }
    142140        {
    143141        }
    144142
    145         explicit Destination(RegistrableDomain&& domain)
     143        explicit AttributeOnSite(RegistrableDomain&& domain)
    146144            : registrableDomain { WTFMove(domain) }
    147145        {
    148146        }
    149147       
    150         bool operator==(const Destination& other) const
     148        bool operator==(const AttributeOnSite& other) const
    151149        {
    152150            return registrableDomain == other.registrableDomain;
     
    163161        }
    164162
    165         static Destination deletedValue()
    166         {
    167             return Destination { WTF::HashTableDeletedValue };
    168         }
    169 
    170         static void constructDeletedValue(Destination& destination)
    171         {
    172             new (&destination) Destination;
    173             destination = Destination::deletedValue();
     163        static AttributeOnSite deletedValue()
     164        {
     165            return AttributeOnSite { WTF::HashTableDeletedValue };
     166        }
     167
     168        static void constructDeletedValue(AttributeOnSite& attributeOnSite)
     169        {
     170            new (&attributeOnSite) AttributeOnSite;
     171            attributeOnSite = AttributeOnSite::deletedValue();
    174172        }
    175173
     
    187185    };
    188186
    189     struct DestinationHash {
    190         static unsigned hash(const Destination& destination)
    191         {
    192             return destination.registrableDomain.hash();
    193         }
    194        
    195         static bool equal(const Destination& a, const Destination& b)
     187    struct AttributeOnSiteHash {
     188        static unsigned hash(const AttributeOnSite& attributeOnSite)
     189        {
     190            return attributeOnSite.registrableDomain.hash();
     191        }
     192       
     193        static bool equal(const AttributeOnSite& a, const AttributeOnSite& b)
    196194        {
    197195            return a == b;
     
    210208    };
    211209   
    212     struct Conversion {
     210    struct AttributionTriggerData {
    213211        enum class WasSent : bool { No, Yes };
    214212       
    215         Conversion(ConversionData data, Priority priority, WasSent wasSent = WasSent::No)
     213        AttributionTriggerData(uint32_t data, Priority priority, WasSent wasSent = WasSent::No)
    216214            : data { data }
    217215            , priority { priority.value }
     
    225223        }
    226224       
    227         ConversionData data;
     225        uint32_t data;
    228226        PriorityValue priority;
    229227        WasSent wasSent = WasSent::No;
    230228
    231229        template<class Encoder> void encode(Encoder&) const;
    232         template<class Decoder> static Optional<Conversion> decode(Decoder&);
     230        template<class Decoder> static Optional<AttributionTriggerData> decode(Decoder&);
    233231    };
    234232
    235233    PrivateClickMeasurement() = default;
    236     PrivateClickMeasurement(Campaign campaign, const Source& source, const Destination& destination)
    237         : m_campaign { campaign }
    238         , m_source { source }
    239         , m_destination { destination }
    240         , m_timeOfAdClick { WallTime::now() }
     234    PrivateClickMeasurement(SourceID sourceID, const SourceSite& sourceSite, const AttributeOnSite& attributeOnSite, WallTime timeOfAdClick = WallTime::now())
     235        : m_sourceID { sourceID }
     236        , m_sourceSite { sourceSite }
     237        , m_attributeOnSite { attributeOnSite }
     238        , m_timeOfAdClick { timeOfAdClick }
    241239    {
    242240    }
    243241
    244     WEBCORE_EXPORT static Expected<Conversion, String> parseConversionRequest(const URL& redirectURL);
    245     WEBCORE_EXPORT Optional<Seconds> convertAndGetEarliestTimeToSend(Conversion&&);
     242    WEBCORE_EXPORT static const Seconds maxAge();
     243    WEBCORE_EXPORT static Expected<AttributionTriggerData, String> parseAttributionRequest(const URL& redirectURL);
     244    WEBCORE_EXPORT Optional<Seconds> attributeAndGetEarliestTimeToSend(AttributionTriggerData&&);
    246245    WEBCORE_EXPORT bool hasHigherPriorityThan(const PrivateClickMeasurement&) const;
    247246    WEBCORE_EXPORT URL reportURL() const;
    248247    WEBCORE_EXPORT Ref<JSON::Object> json() const;
    249     const Source& source() const { return m_source; };
    250     const Destination& destination() const { return m_destination; };
     248    const SourceSite& sourceSite() const { return m_sourceSite; };
     249    const AttributeOnSite& attributeOnSite() const { return m_attributeOnSite; };
     250    WallTime timeOfAdClick() const { return m_timeOfAdClick; }
    251251    Optional<WallTime> earliestTimeToSend() const { return m_earliestTimeToSend; };
    252     WEBCORE_EXPORT void markAsExpired();
    253     WEBCORE_EXPORT bool hasExpired() const;
    254     WEBCORE_EXPORT void markConversionAsSent();
    255     WEBCORE_EXPORT bool wasConversionSent() const;
    256 
    257     bool isEmpty() const { return m_source.registrableDomain.isEmpty(); };
    258 
    259     WEBCORE_EXPORT String toString() const;
     252    void setEarliestTimeToSend(WallTime time) { m_earliestTimeToSend = time; }
     253    SourceID sourceID() { return m_sourceID; }
     254    Optional<AttributionTriggerData> attributionTriggerData() { return m_attributionTriggerData; }
     255    void setAttribution(AttributionTriggerData&& attributionTriggerData) { m_attributionTriggerData = WTFMove(attributionTriggerData); }
    260256
    261257    template<class Encoder> void encode(Encoder&) const;
     
    266262    static bool debugModeEnabled();
    267263
    268     Campaign m_campaign;
    269     Source m_source;
    270     Destination m_destination;
     264    SourceID m_sourceID;
     265    SourceSite m_sourceSite;
     266    AttributeOnSite m_attributeOnSite;
    271267    WallTime m_timeOfAdClick;
    272268
    273     Optional<Conversion> m_conversion;
     269    Optional<AttributionTriggerData> m_attributionTriggerData;
    274270    Optional<WallTime> m_earliestTimeToSend;
    275271};
     
    278274void PrivateClickMeasurement::encode(Encoder& encoder) const
    279275{
    280     encoder << m_campaign.id << m_source.registrableDomain << m_destination.registrableDomain << m_timeOfAdClick << m_conversion << m_earliestTimeToSend;
     276    encoder << m_sourceID.id << m_sourceSite.registrableDomain << m_attributeOnSite.registrableDomain << m_timeOfAdClick << m_attributionTriggerData << m_earliestTimeToSend;
    281277}
    282278
     
    284280Optional<PrivateClickMeasurement> PrivateClickMeasurement::decode(Decoder& decoder)
    285281{
    286     Optional<CampaignId> campaignId;
    287     decoder >> campaignId;
    288     if (!campaignId)
     282    Optional<uint32_t> sourceID;
     283    decoder >> sourceID;
     284    if (!sourceID)
    289285        return WTF::nullopt;
    290286   
     
    294290        return WTF::nullopt;
    295291   
    296     Optional<RegistrableDomain> destinationRegistrableDomain;
    297     decoder >> destinationRegistrableDomain;
    298     if (!destinationRegistrableDomain)
     292    Optional<RegistrableDomain> attributeOnRegistrableDomain;
     293    decoder >> attributeOnRegistrableDomain;
     294    if (!attributeOnRegistrableDomain)
    299295        return WTF::nullopt;
    300296   
     
    304300        return WTF::nullopt;
    305301   
    306     Optional<Optional<Conversion>> conversion;
    307     decoder >> conversion;
    308     if (!conversion)
     302    Optional<Optional<AttributionTriggerData>> attributionTriggerData;
     303    decoder >> attributionTriggerData;
     304    if (!attributionTriggerData)
    309305        return WTF::nullopt;
    310306   
     
    314310        return WTF::nullopt;
    315311   
    316     PrivateClickMeasurement attribution { Campaign { WTFMove(*campaignId) }, Source { WTFMove(*sourceRegistrableDomain) }, Destination { WTFMove(*destinationRegistrableDomain) } };
    317     attribution.m_conversion = WTFMove(*conversion);
     312    PrivateClickMeasurement attribution { SourceID { WTFMove(*sourceID) }, SourceSite { WTFMove(*sourceRegistrableDomain) }, AttributeOnSite { WTFMove(*attributeOnRegistrableDomain) } };
     313    attribution.m_attributionTriggerData = WTFMove(*attributionTriggerData);
    318314    attribution.m_earliestTimeToSend = WTFMove(*earliestTimeToSend);
    319315   
     
    322318
    323319template<class Encoder>
    324 void PrivateClickMeasurement::Conversion::encode(Encoder& encoder) const
     320void PrivateClickMeasurement::AttributionTriggerData::encode(Encoder& encoder) const
    325321{
    326322    encoder << data << priority << wasSent;
     
    328324
    329325template<class Decoder>
    330 Optional<PrivateClickMeasurement::Conversion> PrivateClickMeasurement::Conversion::decode(Decoder& decoder)
     326Optional<PrivateClickMeasurement::AttributionTriggerData> PrivateClickMeasurement::AttributionTriggerData::decode(Decoder& decoder)
    331327{
    332     Optional<ConversionData> data;
     328    Optional<uint32_t> data;
    333329    decoder >> data;
    334330    if (!data)
     
    345341        return WTF::nullopt;
    346342   
    347     return Conversion { WTFMove(*data), Priority { *priority }, *wasSent };
     343    return AttributionTriggerData { WTFMove(*data), Priority { *priority }, *wasSent };
    348344}
    349345
     
    353349template<typename T> struct DefaultHash;
    354350
    355 template<> struct DefaultHash<WebCore::PrivateClickMeasurement::Source> : WebCore::PrivateClickMeasurement::SourceHash { };
    356 template<> struct HashTraits<WebCore::PrivateClickMeasurement::Source> : GenericHashTraits<WebCore::PrivateClickMeasurement::Source> {
    357     static WebCore::PrivateClickMeasurement::Source emptyValue() { return { }; }
    358     static void constructDeletedValue(WebCore::PrivateClickMeasurement::Source& slot) { WebCore::PrivateClickMeasurement::Source::constructDeletedValue(slot); }
    359     static bool isDeletedValue(const WebCore::PrivateClickMeasurement::Source& slot) { return slot.isDeletedValue(); }
     351template<> struct DefaultHash<WebCore::PrivateClickMeasurement::SourceSite> : WebCore::PrivateClickMeasurement::SourceSiteHash { };
     352template<> struct HashTraits<WebCore::PrivateClickMeasurement::SourceSite> : GenericHashTraits<WebCore::PrivateClickMeasurement::SourceSite> {
     353    static WebCore::PrivateClickMeasurement::SourceSite emptyValue() { return { }; }
     354    static void constructDeletedValue(WebCore::PrivateClickMeasurement::SourceSite& slot) { WebCore::PrivateClickMeasurement::SourceSite::constructDeletedValue(slot); }
     355    static bool isDeletedValue(const WebCore::PrivateClickMeasurement::SourceSite& slot) { return slot.isDeletedValue(); }
    360356};
    361357
    362 template<> struct DefaultHash<WebCore::PrivateClickMeasurement::Destination> : WebCore::PrivateClickMeasurement::DestinationHash { };
    363 template<> struct HashTraits<WebCore::PrivateClickMeasurement::Destination> : GenericHashTraits<WebCore::PrivateClickMeasurement::Destination> {
    364     static WebCore::PrivateClickMeasurement::Destination emptyValue() { return { }; }
    365     static void constructDeletedValue(WebCore::PrivateClickMeasurement::Destination& slot) { WebCore::PrivateClickMeasurement::Destination::constructDeletedValue(slot); }
    366     static bool isDeletedValue(const WebCore::PrivateClickMeasurement::Destination& slot) { return slot.isDeletedValue(); }
     358template<> struct DefaultHash<WebCore::PrivateClickMeasurement::AttributeOnSite> : WebCore::PrivateClickMeasurement::AttributeOnSiteHash { };
     359template<> struct HashTraits<WebCore::PrivateClickMeasurement::AttributeOnSite> : GenericHashTraits<WebCore::PrivateClickMeasurement::AttributeOnSite> {
     360    static WebCore::PrivateClickMeasurement::AttributeOnSite emptyValue() { return { }; }
     361    static void constructDeletedValue(WebCore::PrivateClickMeasurement::AttributeOnSite& slot) { WebCore::PrivateClickMeasurement::AttributeOnSite::constructDeletedValue(slot); }
     362    static bool isDeletedValue(const WebCore::PrivateClickMeasurement::AttributeOnSite& slot) { return slot.isDeletedValue(); }
    367363};
    368364}
  • trunk/Source/WebKit/ChangeLog

    r270130 r270136  
     12020-11-20  Kate Cheney  <katherine_cheney@apple.com>
     2
     3        PCM: Persist pending ad clicks and attributions so they can survive browser restart
     4        https://bugs.webkit.org/show_bug.cgi?id=219134
     5        <rdar://problem/70470129>
     6
     7        Reviewed by John Wilander.
     8
     9        This patch migrates Private Click Measurement to use SQLite,
     10        which is beneficial because it requires less in-memory storage and
     11        persists PCM data across browser sessions. It also updates naming
     12        to match naming agreed upon in standards bodies:
     13
     14        - source -> sourceSite
     15        - campaign/campaignID -> sourceID
     16        - destination -> attributeOnSite
     17        - conversion/conversionValue -> attributionTriggerData
     18        - unconverted -> unattributed
     19        - convert(ed) -> attribute(d)
     20
     21        This adds 3 SQLite tables: one for clicks that haven't been
     22        attributed, one for attributions that haven't been sent, and one to
     23        store the last time the reports were sent to make sure reports get
     24        sent as soon as possible if needed after a browser restart.
     25
     26        Behavior is identical to existing PCM implementation with the addition
     27        of persistence. Existing PCM tests confirm no regressions.
     28
     29        Reviewed by John Wilander.
     30
     31        * NetworkProcess/Classifier/ResourceLoadStatisticsDatabaseStore.cpp:
     32        (WebKit::createTableQueries):
     33        (WebKit::ResourceLoadStatisticsDatabaseStore::ResourceLoadStatisticsDatabaseStore):
     34        (WebKit::ResourceLoadStatisticsDatabaseStore::createUniqueIndices):
     35        (WebKit::ResourceLoadStatisticsDatabaseStore::createSchema):
     36        (WebKit::ResourceLoadStatisticsDatabaseStore::destroyStatements):
     37        New queries to interact with PCM data.
     38
     39        (WebKit::ResourceLoadStatisticsDatabaseStore::updateTimerLastFired):
     40        (WebKit::ResourceLoadStatisticsDatabaseStore::timerLastFired):
     41        (WebKit::ResourceLoadStatisticsDatabaseStore::updatePrivateClickMeasurementAttributionTimes):
     42        Set earliestTimeToSend to be the original value minus the time passed since the last timer fire
     43        for each entry. If the result is less than 0, set to 0 so the report gets sent immediately.
     44
     45        (WebKit::ResourceLoadStatisticsDatabaseStore::buildPrivateClickMeasurementFromDatabase):
     46        Creates a PCM object from data in the database.
     47
     48        (WebKit::ResourceLoadStatisticsDatabaseStore::findPrivateClickMeasurement):
     49        (WebKit::ResourceLoadStatisticsDatabaseStore::insertPrivateClickMeasurement):
     50        (WebKit::ResourceLoadStatisticsDatabaseStore::markAllUnattributedPrivateClickMeasurementAsExpiredForTesting):
     51        (WebKit::ResourceLoadStatisticsDatabaseStore::removeUnattributed):
     52        (WebKit::ResourceLoadStatisticsDatabaseStore::attributePrivateClickMeasurement):
     53        (WebKit::ResourceLoadStatisticsDatabaseStore::allAttributedPrivateClickMeasurement):
     54        (WebKit::ResourceLoadStatisticsDatabaseStore::clearPrivateClickMeasurement):
     55        (WebKit::ResourceLoadStatisticsDatabaseStore::clearExpiredPrivateClickMeasurement):
     56        (WebKit::ResourceLoadStatisticsDatabaseStore::attributionToString):
     57        (WebKit::ResourceLoadStatisticsDatabaseStore::privateClickMeasurementToString):
     58        (WebKit::ResourceLoadStatisticsDatabaseStore::clearSentAttributions):
     59        These functions use database queries to implement PCM functionality with exactly the same
     60        behavior as the in-memory PCM implementation.
     61
     62        (WebKit::ResourceLoadStatisticsDatabaseStore::markAttributedPrivateClickMeasurementsAsExpiredForTesting):
     63        * NetworkProcess/Classifier/ResourceLoadStatisticsDatabaseStore.h:
     64        * NetworkProcess/Classifier/ResourceLoadStatisticsMemoryStore.h:
     65        * NetworkProcess/Classifier/ResourceLoadStatisticsStore.h:
     66        * NetworkProcess/Classifier/WebResourceLoadStatisticsStore.cpp:
     67        (WebKit::WebResourceLoadStatisticsStore::updateTimerLastFired):
     68        (WebKit::WebResourceLoadStatisticsStore::insertPrivateClickMeasurement):
     69        (WebKit::WebResourceLoadStatisticsStore::markAllUnattributedPrivateClickMeasurementAsExpiredForTesting):
     70        (WebKit::WebResourceLoadStatisticsStore::attributePrivateClickMeasurement):
     71        (WebKit::WebResourceLoadStatisticsStore::allAttributedPrivateClickMeasurement):
     72        (WebKit::WebResourceLoadStatisticsStore::clearPrivateClickMeasurement):
     73        (WebKit::WebResourceLoadStatisticsStore::clearPrivateClickMeasurementForRegistrableDomain):
     74        (WebKit::WebResourceLoadStatisticsStore::clearExpiredPrivateClickMeasurement):
     75        (WebKit::WebResourceLoadStatisticsStore::privateClickMeasurementToString):
     76        (WebKit::WebResourceLoadStatisticsStore::clearSentAttributions):
     77        (WebKit::WebResourceLoadStatisticsStore::markAttributedPrivateClickMeasurementsAsExpiredForTesting):
     78        * NetworkProcess/Classifier/WebResourceLoadStatisticsStore.h:
     79
     80        * NetworkProcess/NetworkProcess.cpp:
     81        (WebKit::NetworkProcess::firePrivateClickMeasurementTimerImmediately):
     82        (WebKit::NetworkProcess::simulateResourceLoadStatisticsSessionRestart):
     83        (WebKit::NetworkProcess::markAttributedPrivateClickMeasurementsAsExpiredForTesting):
     84        Test functions to help simulate a browser restart after PCM data has expired during
     85        a session close. This is the only behavior change from the existing PCM implementation.
     86
     87        * NetworkProcess/NetworkProcess.h:
     88        * NetworkProcess/NetworkProcess.messages.in:
     89        * NetworkProcess/NetworkResourceLoader.cpp:
     90        (WebKit::NetworkResourceLoader::willSendRedirectedRequest):
     91        (WebKit::NetworkResourceLoader::continueWillSendRedirectedRequest):
     92        * NetworkProcess/NetworkResourceLoader.h:
     93        * NetworkProcess/NetworkSession.cpp:
     94        (WebKit::NetworkSession::NetworkSession):
     95        (WebKit::NetworkSession::firePrivateClickMeasurementTimerImmediately):
     96        (WebKit::NetworkSession::storePrivateClickMeasurement):
     97        (WebKit::NetworkSession::handlePrivateClickMeasurementConversion):
     98        (WebKit::NetworkSession::markAttributedPrivateClickMeasurementsAsExpiredForTesting):
     99        (WebKit::NetworkSession::markPrivateClickMeasurementsAsExpiredForTesting):
     100        * NetworkProcess/NetworkSession.h:
     101
     102        * NetworkProcess/PrivateClickMeasurementManager.cpp:
     103        (WebKit::PrivateClickMeasurementManager::PrivateClickMeasurementManager):
     104        Move constructor to cpp file to call startTimer(5_s) which will kick
     105        off sending any reports that have expired in the database. We should
     106        wait 5 seconds so we are sure ITP is up and running.
     107
     108        (WebKit::PrivateClickMeasurementManager::storeUnattributed):
     109        (WebKit::PrivateClickMeasurementManager::handleAttribution):
     110        (WebKit::PrivateClickMeasurementManager::startTimer):
     111        (WebKit::PrivateClickMeasurementManager::attribute):
     112        (WebKit::PrivateClickMeasurementManager::fireConversionRequest):
     113        (WebKit::PrivateClickMeasurementManager::clearSentAttributions):
     114        (WebKit::PrivateClickMeasurementManager::updateTimerLastFired):
     115        (WebKit::PrivateClickMeasurementManager::firePendingAttributionRequests):
     116        (WebKit::PrivateClickMeasurementManager::clear):
     117        (WebKit::PrivateClickMeasurementManager::clearForRegistrableDomain):
     118        (WebKit::PrivateClickMeasurementManager::clearExpired):
     119        (WebKit::PrivateClickMeasurementManager::toString const):
     120        (WebKit::PrivateClickMeasurementManager::setConversionURLForTesting):
     121        (WebKit::PrivateClickMeasurementManager::markAllUnattributedAsExpiredForTesting):
     122        (WebKit::PrivateClickMeasurementManager::markAttributedPrivateClickMeasurementsAsExpiredForTesting):
     123        (WebKit::PrivateClickMeasurementManager::storeUnconverted): Deleted.
     124        (WebKit::PrivateClickMeasurementManager::handleConversion): Deleted.
     125        (WebKit::PrivateClickMeasurementManager::convert): Deleted.
     126        (WebKit::PrivateClickMeasurementManager::firePendingConversionRequests): Deleted.
     127        (WebKit::PrivateClickMeasurementManager::markAllUnconvertedAsExpiredForTesting): Deleted.
     128        Implementation moved to ResourceLoadStatisticsDatabaseStore.
     129
     130        * NetworkProcess/PrivateClickMeasurementManager.h:
     131        (WebKit::PrivateClickMeasurementManager::PrivateClickMeasurementManager): Deleted.
     132        Moved to cpp file.
     133
     134        (WebKit::PrivateClickMeasurementManager::m_sessionID): Deleted.
     135        * UIProcess/API/C/WKPage.cpp:
     136        (WKPageMarkAttributedPrivateClickMeasurementsAsExpiredForTesting):
     137        (WKPageSimulateResourceLoadStatisticsSessionRestart):
     138        * UIProcess/API/C/WKPagePrivate.h:
     139        * UIProcess/WebPageProxy.cpp:
     140        (WebKit::WebPageProxy::didCommitLoadForFrame):
     141        (WebKit::WebPageProxy::markAttributedPrivateClickMeasurementsAsExpiredForTesting):
     142        (WebKit::WebPageProxy::simulateResourceLoadStatisticsSessionRestart):
     143        * UIProcess/WebPageProxy.h:
     144        Testing support.
     145
    11462020-11-20  Simon Fraser  <simon.fraser@apple.com>
    2147
  • trunk/Source/WebKit/NetworkProcess/Classifier/ResourceLoadStatisticsDatabaseStore.cpp

    r269807 r270136  
    8181constexpr auto topFrameLoadedThirdPartyScriptsQuery = "INSERT OR IGNORE into TopFrameLoadedThirdPartyScripts (topFrameDomainID, subresourceDomainID) SELECT ?, domainID FROM ObservedDomains where registrableDomain in ( "_s;
    8282constexpr auto subresourceUniqueRedirectsFromQuery = "INSERT OR IGNORE INTO SubresourceUniqueRedirectsFrom (subresourceDomainID, fromDomainID) SELECT ?, domainID FROM ObservedDomains WHERE registrableDomain in ( "_s;
     83constexpr auto insertUnattributedPrivateClickMeasurementQuery = "INSERT OR REPLACE INTO UnattributedPrivateClickMeasurement (sourceSiteDomainID, attributeOnSiteDomainID, "
     84    "sourceID, timeOfAdClick) VALUES (?, ?, ?, ?)"_s;
     85constexpr auto insertAttributedPrivateClickMeasurementQuery = "INSERT OR REPLACE INTO AttributedPrivateClickMeasurement (sourceSiteDomainID, attributeOnSiteDomainID, "
     86    "sourceID, attributionTriggerData, priority, timeOfAdClick, earliestTimeToSend) VALUES (?, ?, ?, ?, ?, ?, ?)"_s;
     87constexpr auto updateTimerLastFiredQuery = "INSERT OR REPLACE INTO TimerLastFired (key, timeLastFiredSeconds) VALUES (1, ?)"_s;
    8388
    8489// INSERT OR REPLACE Queries
     
    111116constexpr auto updateGrandfatheredQuery = "UPDATE ObservedDomains SET grandfathered = ? WHERE registrableDomain = ?"_s;
    112117constexpr auto updateIsScheduledForAllButCookieDataRemovalQuery = "UPDATE ObservedDomains SET isScheduledForAllButCookieDataRemoval = ? WHERE registrableDomain = ?"_s;
     118constexpr auto setUnattributedPrivateClickMeasurementAsExpiredQuery = "UPDATE UnattributedPrivateClickMeasurement SET timeOfAdClick = -1.0"_s;
     119constexpr auto updateAttributionsEarliestTimeToSendQuery = "UPDATE AttributedPrivateClickMeasurement as c SET "
     120    "earliestTimeToSend = (SELECT MAX(0.0, newTime) FROM (SELECT (earliestTimeToSend - ?) as newTime FROM "
     121    "AttributedPrivateClickMeasurement as d WHERE c.sourceSiteDomainID = d.sourceSiteDomainID AND c.attributeOnSiteDomainID = "
     122    "d.attributeOnSiteDomainID))"_s;
    113123
    114124// SELECT Queries
     
    129139    "UNION ALL SELECT topFrameDomainID FROM SubresourceUnderTopFrameDomains WHERE subresourceDomainID = ?"
    130140    "UNION ALL SELECT toDomainID FROM SubresourceUniqueRedirectsTo WHERE subresourceDomainID = ?"_s;
     141constexpr auto allUnattributedPrivateClickMeasurementAttributionsQuery = "SELECT * FROM UnattributedPrivateClickMeasurement"_s;
     142constexpr auto allAttributedPrivateClickMeasurementQuery = "SELECT * FROM AttributedPrivateClickMeasurement"_s;
     143constexpr auto findUnattributedQuery = "SELECT * FROM UnattributedPrivateClickMeasurement WHERE sourceSiteDomainID = ? AND attributeOnSiteDomainID = ?"_s;
     144constexpr auto findAttributedQuery = "SELECT * FROM AttributedPrivateClickMeasurement WHERE sourceSiteDomainID = ? AND attributeOnSiteDomainID = ?"_s;
     145constexpr auto timerLastFiredQuery = "SELECT timeLastFiredSeconds FROM TimerLastFired WHERE key = 1"_s;
    131146
    132147// EXISTS for testing queries
     
    140155// DELETE Queries
    141156constexpr auto removeAllDataQuery = "DELETE FROM ObservedDomains WHERE domainID = ?"_s;
     157constexpr auto clearUnattributedPrivateClickMeasurementQuery = "DELETE FROM UnattributedPrivateClickMeasurement WHERE sourceSiteDomainID LIKE ? OR attributeOnSiteDomainID LIKE ?"_s;
     158constexpr auto clearAttributedPrivateClickMeasurementQuery = "DELETE FROM AttributedPrivateClickMeasurement WHERE sourceSiteDomainID LIKE ? OR attributeOnSiteDomainID LIKE ?"_s;
     159constexpr auto clearExpiredPrivateClickMeasurementQuery = "DELETE FROM UnattributedPrivateClickMeasurement WHERE ? > timeOfAdClick"_s;
     160constexpr auto removeUnattributedQuery = "DELETE FROM UnattributedPrivateClickMeasurement WHERE sourceSiteDomainID = ? AND attributeOnSiteDomainID = ?"_s;
    142161
    143162constexpr auto createObservedDomain = "CREATE TABLE ObservedDomains ("
     
    220239    "year INTEGER NOT NULL, month INTEGER NOT NULL, monthDay INTEGER NOT NULL);"_s;
    221240
    222 
    223 // CREATE UNIQUE INDEX Queries
     241constexpr auto createUnattributedPrivateClickMeasurement = "CREATE TABLE UnattributedPrivateClickMeasurement ("
     242    "sourceSiteDomainID INTEGER NOT NULL, attributeOnSiteDomainID INTEGER NOT NULL, sourceID INTEGER NOT NULL, "
     243    "timeOfAdClick REAL NOT NULL, FOREIGN KEY(sourceSiteDomainID) REFERENCES ObservedDomains(domainID) ON DELETE CASCADE, "
     244    "FOREIGN KEY(attributeOnSiteDomainID) REFERENCES ObservedDomains(domainID) ON DELETE CASCADE);"_s;
     245
     246constexpr auto createAttributedPrivateClickMeasurement = "CREATE TABLE AttributedPrivateClickMeasurement ("
     247    "sourceSiteDomainID INTEGER NOT NULL, attributeOnSiteDomainID INTEGER NOT NULL, sourceID INTEGER NOT NULL, "
     248    "attributionTriggerData INTEGER NOT NULL, priority INTEGER NOT NULL, timeOfAdClick REAL NOT NULL, earliestTimeToSend REAL NOT NULL, "
     249    "FOREIGN KEY(sourceSiteDomainID) REFERENCES ObservedDomains(domainID) ON DELETE CASCADE, "
     250    "FOREIGN KEY(attributeOnSiteDomainID) REFERENCES ObservedDomains(domainID) ON DELETE CASCADE);"_s;
     251
     252constexpr auto createTimerLastFired = "CREATE TABLE TimerLastFired ( key INTEGER PRIMARY KEY, "
     253    " timeLastFiredSeconds REAL NOT NULL )"_s;
     254
     255// CREATE UNIQUE INDEX Queries.
    224256constexpr auto createUniqueIndexStorageAccessUnderTopFrameDomains = "CREATE UNIQUE INDEX IF NOT EXISTS StorageAccessUnderTopFrameDomains_domainID_topLevelDomainID on StorageAccessUnderTopFrameDomains ( domainID, topLevelDomainID );"_s;
    225257constexpr auto createUniqueIndexTopFrameUniqueRedirectsTo = "CREATE UNIQUE INDEX IF NOT EXISTS TopFrameUniqueRedirectsTo_sourceDomainID_toDomainID on TopFrameUniqueRedirectsTo ( sourceDomainID, toDomainID );"_s;
     
    233265constexpr auto createUniqueIndexSubresourceUniqueRedirectsFrom = "CREATE UNIQUE INDEX IF NOT EXISTS SubresourceUniqueRedirectsFrom_subresourceDomainID_fromDomainID on SubresourceUnderTopFrameDomains ( subresourceDomainID, fromDomainID );"_s;
    234266constexpr auto createUniqueIndexOperatingDates = "CREATE UNIQUE INDEX IF NOT EXISTS OperatingDates_year_month_monthDay on OperatingDates ( year, month, monthDay );"_s;
     267constexpr auto createUniqueIndexUnattributedPrivateClickMeasurement = "CREATE UNIQUE INDEX IF NOT EXISTS UnattributedPrivateClickMeasurement_sourceSiteDomainID_attributeOnSiteDomainID on UnattributedPrivateClickMeasurement ( sourceSiteDomainID, attributeOnSiteDomainID );"_s;
     268constexpr auto createUniqueIndexAttributedPrivateClickMeasurement = "CREATE UNIQUE INDEX IF NOT EXISTS AttributedPrivateClickMeasurement_sourceSiteDomainID_attributeOnSiteDomainID on AttributedPrivateClickMeasurement ( sourceSiteDomainID, attributeOnSiteDomainID );"_s;
    235269
    236270static const String ObservedDomainsTableSchemaV1()
     
    264298        { "SubresourceUniqueRedirectsTo"_s, createSubresourceUniqueRedirectsTo},
    265299        { "SubresourceUniqueRedirectsFrom"_s, createSubresourceUniqueRedirectsFrom},
    266         { "OperatingDates"_s, createOperatingDates}
     300        { "OperatingDates"_s, createOperatingDates},
     301        { "UnattributedPrivateClickMeasurement"_s, createUnattributedPrivateClickMeasurement},
     302        { "AttributedPrivateClickMeasurement"_s, createAttributedPrivateClickMeasurement},
     303        { "TimerLastFired"_s, createTimerLastFired}
    267304    });
    268305   
     
    295332
    296333    includeTodayAsOperatingDateIfNecessary();
     334    updatePrivateClickMeasurementAttributionTimes();
    297335    allStores().add(this);
    298336}
     
    556594        || !m_database.executeCommand(createUniqueIndexSubresourceUniqueRedirectsTo)
    557595        || !m_database.executeCommand(createUniqueIndexSubresourceUnderTopFrameDomains)
    558         || !m_database.executeCommand(createUniqueIndexOperatingDates)) {
     596        || !m_database.executeCommand(createUniqueIndexOperatingDates)
     597        || !m_database.executeCommand(createUniqueIndexUnattributedPrivateClickMeasurement)
     598        || !m_database.executeCommand(createUniqueIndexAttributedPrivateClickMeasurement)) {
    559599        RELEASE_LOG_ERROR(Network, "%p - ResourceLoadStatisticsDatabaseStore::createUniqueIndices failed to execute, error message: %" PUBLIC_LOG_STRING, this, m_database.lastErrorMsg());
    560600        return false;
     
    631671        return false;
    632672    }
    633    
     673
     674    if (!m_database.executeCommand(createUnattributedPrivateClickMeasurement)) {
     675        LOG_ERROR("Could not create UnattributedPrivateClickMeasurement table in database (%i) - %s", m_database.lastError(), m_database.lastErrorMsg());
     676        return false;
     677    }
     678
     679    if (!m_database.executeCommand(createAttributedPrivateClickMeasurement)) {
     680        LOG_ERROR("Could not create AttributedPrivateClickMeasurement table in database (%i) - %s", m_database.lastError(), m_database.lastErrorMsg());
     681        return false;
     682    }
     683   
     684    if (!m_database.executeCommand(createTimerLastFired)) {
     685        LOG_ERROR("Could not create TimerLastFired table in database (%i) - %s", m_database.lastError(), m_database.lastErrorMsg());
     686        return false;
     687    }
     688
    634689    if (!createUniqueIndices())
    635690        return false;
     
    679734    m_observedDomainsExistsStatement = nullptr;
    680735    m_removeAllDataStatement = nullptr;
     736    m_insertUnattributedPrivateClickMeasurementStatement = nullptr;
     737    m_insertAttributedPrivateClickMeasurementStatement = nullptr;
     738    m_setUnattributedPrivateClickMeasurementAsExpiredStatement = nullptr;
     739    m_clearUnattributedPrivateClickMeasurementStatement = nullptr;
     740    m_clearAttributedPrivateClickMeasurementStatement = nullptr;
     741    m_clearExpiredPrivateClickMeasurementStatement = nullptr;
     742    m_allUnattributedPrivateClickMeasurementAttributionsStatement = nullptr;
     743    m_allAttributedPrivateClickMeasurementStatement = nullptr;
     744    m_findUnattributedStatement = nullptr;
     745    m_findAttributedStatement = nullptr;
     746    m_updateTimerLastFiredStatement = nullptr;
     747    m_timerLastFiredStatement = nullptr;
     748    m_updateAttributionsEarliestTimeToSendStatement = nullptr;
     749    m_removeUnattributedStatement = nullptr;
    681750}
    682751
     
    28152884}
    28162885
     2886void ResourceLoadStatisticsDatabaseStore::updateTimerLastFired()
     2887{
     2888    auto scopedStatement = this->scopedStatement(m_updateTimerLastFiredStatement, updateTimerLastFiredQuery, "updateTimerLastFired"_s);
     2889
     2890    auto now = WallTime::now().secondsSinceEpoch().value();
     2891
     2892    if (!scopedStatement
     2893        || scopedStatement->bindDouble(1, now) != SQLITE_OK
     2894        || scopedStatement->step() != SQLITE_DONE) {
     2895        RELEASE_LOG_ERROR(Network, "%p - ResourceLoadStatisticsDatabaseStore::updateTimerLastFired failed, error message: %{private}s", this, m_database.lastErrorMsg());
     2896        ASSERT_NOT_REACHED();
     2897    }
     2898}
     2899
     2900WallTime ResourceLoadStatisticsDatabaseStore::timerLastFired()
     2901{
     2902    auto scopedStatement = this->scopedStatement(m_timerLastFiredStatement, timerLastFiredQuery, "timerLastFired"_s);
     2903
     2904    if (!scopedStatement) {
     2905        RELEASE_LOG_ERROR_IF_ALLOWED(m_sessionID, "%p - ResourceLoadStatisticsDatabaseStore::timerLastFired, error message: %{private}s", this, m_database.lastErrorMsg());
     2906        ASSERT_NOT_REACHED();
     2907    }
     2908
     2909    if (scopedStatement->step() == SQLITE_ROW)
     2910        return WallTime::fromRawSeconds(scopedStatement->getColumnDouble(0));
     2911   
     2912    return WallTime::now();
     2913}
     2914
     2915void ResourceLoadStatisticsDatabaseStore::updatePrivateClickMeasurementAttributionTimes()
     2916{
     2917    // We should update attributions on session-start to account for the time
     2918    // that passed while the session was closed. If the amount of time since the last
     2919    // timer fire is greater than the earliestTimeToSend, we should set earliestTimeToSend
     2920    // to be immediately.
     2921    auto lastFired = timerLastFired();
     2922    auto timePassed = WallTime::now() - lastFired;
     2923
     2924    auto scopedStatement = this->scopedStatement(m_updateAttributionsEarliestTimeToSendStatement, updateAttributionsEarliestTimeToSendQuery, "updatePrivateClickMeasurementAttributionTimes"_s);
     2925
     2926    if (!scopedStatement
     2927        || scopedStatement->bindDouble(1, timePassed.value()) != SQLITE_OK
     2928        || scopedStatement->step() != SQLITE_DONE) {
     2929        RELEASE_LOG_ERROR_IF_ALLOWED(m_sessionID, "%p - ResourceLoadStatisticsDatabaseStore::updatePrivateClickMeasurementAttributionTimes, error message: %{private}s", this, m_database.lastErrorMsg());
     2930        ASSERT_NOT_REACHED();
     2931    }
     2932}
     2933
     2934PrivateClickMeasurement ResourceLoadStatisticsDatabaseStore::buildPrivateClickMeasurementFromDatabase(WebCore::SQLiteStatement* statement, PrivateClickMeasurementAttributionType attributionType)
     2935{
     2936    auto sourceSiteDomain = getDomainStringFromDomainID(statement->getColumnInt(0));
     2937    auto attributeOnSiteDomain = getDomainStringFromDomainID(statement->getColumnInt(1));
     2938    auto sourceID = statement->getColumnInt(2);
     2939    auto timeOfAdClick = attributionType == PrivateClickMeasurementAttributionType::Attributed ? statement->getColumnDouble(5) : statement->getColumnDouble(3);
     2940
     2941    PrivateClickMeasurement attribution(WebCore::PrivateClickMeasurement::SourceID(sourceID), WebCore::PrivateClickMeasurement::SourceSite(RegistrableDomain::uncheckedCreateFromRegistrableDomainString(sourceSiteDomain)), WebCore::PrivateClickMeasurement::AttributeOnSite(RegistrableDomain::uncheckedCreateFromRegistrableDomainString(attributeOnSiteDomain)), WallTime::fromRawSeconds(timeOfAdClick));
     2942   
     2943    if (attributionType == PrivateClickMeasurementAttributionType::Attributed) {
     2944        auto attributionTriggerData = statement->getColumnInt(3);
     2945        auto priority = statement->getColumnInt(4);
     2946        auto earliestTimeToSend = statement->getColumnDouble(6);
     2947       
     2948        if (attributionTriggerData != -1)
     2949            attribution.setAttribution(WebCore::PrivateClickMeasurement::AttributionTriggerData { static_cast<uint32_t>(attributionTriggerData), WebCore::PrivateClickMeasurement::Priority(priority) });
     2950
     2951        attribution.setEarliestTimeToSend(WallTime::fromRawSeconds(earliestTimeToSend));
     2952    }
     2953
     2954    return attribution;
     2955}
     2956
     2957std::pair<Optional<UnattributedPrivateClickMeasurement>, Optional<AttributedPrivateClickMeasurement>> ResourceLoadStatisticsDatabaseStore::findPrivateClickMeasurement(const WebCore::PrivateClickMeasurement::SourceSite& sourceSite, const WebCore::PrivateClickMeasurement::AttributeOnSite& attributeOnSite)
     2958{
     2959    auto sourceSiteDomainID = domainID(sourceSite.registrableDomain);
     2960    auto attributeOnSiteDomainID = domainID(attributeOnSite.registrableDomain);
     2961
     2962    if (!sourceSiteDomainID || !attributeOnSiteDomainID)
     2963        return std::make_pair(WTF::nullopt, WTF::nullopt);
     2964
     2965    auto findUnattributedScopedStatement = this->scopedStatement(m_findUnattributedStatement, findUnattributedQuery, "findPrivateClickMeasurement"_s);
     2966    if (!findUnattributedScopedStatement
     2967        || findUnattributedScopedStatement->bindInt(1, *sourceSiteDomainID) != SQLITE_OK
     2968        || findUnattributedScopedStatement->bindInt(2, *attributeOnSiteDomainID) != SQLITE_OK) {
     2969        RELEASE_LOG_ERROR_IF_ALLOWED(m_sessionID, "%p - ResourceLoadStatisticsDatabaseStore::findPrivateClickMeasurement findUnattributedQuery, error message: %{private}s", this, m_database.lastErrorMsg());
     2970        ASSERT_NOT_REACHED();
     2971    }
     2972
     2973    auto findAttributedScopedStatement = this->scopedStatement(m_findAttributedStatement, findAttributedQuery, "findPrivateClickMeasurement"_s);
     2974    if (!findAttributedScopedStatement
     2975        || findAttributedScopedStatement->bindInt(1, *sourceSiteDomainID) != SQLITE_OK
     2976        || findAttributedScopedStatement->bindInt(2, *attributeOnSiteDomainID) != SQLITE_OK) {
     2977        RELEASE_LOG_ERROR_IF_ALLOWED(m_sessionID, "%p - ResourceLoadStatisticsDatabaseStore::findPrivateClickMeasurement findAttributedQuery, error message: %{private}s", this, m_database.lastErrorMsg());
     2978        ASSERT_NOT_REACHED();
     2979    }
     2980
     2981    Optional<UnattributedPrivateClickMeasurement> unattributedPrivateClickMeasurement;
     2982    if (findUnattributedScopedStatement->step() == SQLITE_ROW)
     2983        unattributedPrivateClickMeasurement = buildPrivateClickMeasurementFromDatabase(findUnattributedScopedStatement.get(), PrivateClickMeasurementAttributionType::Unattributed);
     2984
     2985    Optional<AttributedPrivateClickMeasurement> attributedPrivateClickMeasurement;
     2986    if (findAttributedScopedStatement->step() == SQLITE_ROW)
     2987        attributedPrivateClickMeasurement = buildPrivateClickMeasurementFromDatabase(findAttributedScopedStatement.get(), PrivateClickMeasurementAttributionType::Attributed);
     2988
     2989    return std::make_pair(unattributedPrivateClickMeasurement, attributedPrivateClickMeasurement);
     2990}
     2991
     2992void ResourceLoadStatisticsDatabaseStore::insertPrivateClickMeasurement(PrivateClickMeasurement&& attribution, PrivateClickMeasurementAttributionType attributionType)
     2993{
     2994    auto sourceData = ensureResourceStatisticsForRegistrableDomain(attribution.sourceSite().registrableDomain);
     2995    auto attributeOnData = ensureResourceStatisticsForRegistrableDomain(attribution.attributeOnSite().registrableDomain);
     2996
     2997    if (!sourceData.second || !attributeOnData.second)
     2998        return;
     2999
     3000    if (attributionType == PrivateClickMeasurementAttributionType::Attributed) {
     3001        auto attributionTriggerData = attribution.attributionTriggerData() ? attribution.attributionTriggerData().value().data : -1;
     3002        auto priority = attribution.attributionTriggerData() ? attribution.attributionTriggerData().value().priority : -1;
     3003        auto earliestTimeToSend = attribution.earliestTimeToSend() ? attribution.earliestTimeToSend().value().secondsSinceEpoch().value() : -1;
     3004
     3005        auto statement = SQLiteStatement(m_database, insertAttributedPrivateClickMeasurementQuery);
     3006        if (statement.prepare() != SQLITE_OK
     3007            || statement.bindInt(1, *sourceData.second) != SQLITE_OK
     3008            || statement.bindInt(2, *attributeOnData.second) != SQLITE_OK
     3009            || statement.bindInt(3, attribution.sourceID().id) != SQLITE_OK
     3010            || statement.bindInt(4, attributionTriggerData) != SQLITE_OK
     3011            || statement.bindInt(5, priority) != SQLITE_OK
     3012            || statement.bindDouble(6, attribution.timeOfAdClick().secondsSinceEpoch().value()) != SQLITE_OK
     3013            || statement.bindDouble(7, earliestTimeToSend) != SQLITE_OK
     3014            || statement.step() != SQLITE_DONE) {
     3015            RELEASE_LOG_ERROR_IF_ALLOWED(m_sessionID, "%p - ResourceLoadStatisticsDatabaseStore::insertPrivateClickMeasurement insertAttributedPrivateClickMeasurementQuery, error message: %{private}s", this, m_database.lastErrorMsg());
     3016            ASSERT_NOT_REACHED();
     3017        }
     3018        return;
     3019    }
     3020
     3021    auto statement = SQLiteStatement(m_database, insertUnattributedPrivateClickMeasurementQuery);
     3022    if (statement.prepare() != SQLITE_OK
     3023        || statement.bindInt(1, *sourceData.second) != SQLITE_OK
     3024        || statement.bindInt(2, *attributeOnData.second) != SQLITE_OK
     3025        || statement.bindInt(3, attribution.sourceID().id) != SQLITE_OK
     3026        || statement.bindDouble(4, attribution.timeOfAdClick().secondsSinceEpoch().value()) != SQLITE_OK
     3027        || statement.step() != SQLITE_DONE) {
     3028        RELEASE_LOG_ERROR_IF_ALLOWED(m_sessionID, "%p - ResourceLoadStatisticsDatabaseStore::insertPrivateClickMeasurement insertUnattributedPrivateClickMeasurementQuery, error message: %{private}s", this, m_database.lastErrorMsg());
     3029        ASSERT_NOT_REACHED();
     3030    }
     3031}
     3032
     3033void ResourceLoadStatisticsDatabaseStore::markAllUnattributedPrivateClickMeasurementAsExpiredForTesting()
     3034{
     3035    auto scopedStatement = this->scopedStatement(m_setUnattributedPrivateClickMeasurementAsExpiredStatement, setUnattributedPrivateClickMeasurementAsExpiredQuery, "markAllUnattributedPrivateClickMeasurementAsExpiredForTesting"_s);
     3036
     3037    if (!scopedStatement || scopedStatement->step() != SQLITE_DONE) {
     3038        RELEASE_LOG_ERROR_IF_ALLOWED(m_sessionID, "%p - ResourceLoadStatisticsDatabaseStore::markAllUnattributedPrivateClickMeasurementAsExpiredForTesting, error message: %{private}s", this, m_database.lastErrorMsg());
     3039        ASSERT_NOT_REACHED();
     3040    }
     3041}
     3042
     3043void ResourceLoadStatisticsDatabaseStore::removeUnattributed(PrivateClickMeasurement& attribution)
     3044{
     3045    auto sourceSiteDomainID = domainID(attribution.sourceSite().registrableDomain);
     3046    auto attributeOnSiteDomainID = domainID(attribution.attributeOnSite().registrableDomain);
     3047
     3048    if (!sourceSiteDomainID || !attributeOnSiteDomainID)
     3049        return;
     3050   
     3051    auto scopedStatement = this->scopedStatement(m_removeUnattributedStatement, removeUnattributedQuery, "removeUnattributed"_s);
     3052
     3053    if (!scopedStatement
     3054        || scopedStatement->bindInt(1, *sourceSiteDomainID) != SQLITE_OK
     3055        || scopedStatement->bindInt(2, *attributeOnSiteDomainID) != SQLITE_OK
     3056        || scopedStatement->step() != SQLITE_DONE) {
     3057        RELEASE_LOG_ERROR_IF_ALLOWED(m_sessionID, "%p - ResourceLoadStatisticsDatabaseStore::removeUnattributed, error message: %{private}s", this, m_database.lastErrorMsg());
     3058        ASSERT_NOT_REACHED();
     3059    }
     3060}
     3061
     3062Optional<Seconds> ResourceLoadStatisticsDatabaseStore::attributePrivateClickMeasurement(const SourceSite& sourceSite, const AttributeOnSite& attributeOnSite, AttributionTriggerData&& attributionTriggerData)
     3063{
     3064    // We should always clear expired clicks from the database before scheduling an attribution.
     3065    clearExpiredPrivateClickMeasurement();
     3066
     3067    if (!attributionTriggerData.isValid()) {
     3068        if (UNLIKELY(debugModeEnabled())) {
     3069            RELEASE_LOG_INFO(PrivateClickMeasurement, "Got an invalid attribution.");
     3070            debugBroadcastConsoleMessage(MessageSource::PrivateClickMeasurement, MessageLevel::Error, "[Private Click Measurement] Got an invalid attribution."_s);
     3071        }
     3072        return WTF::nullopt;
     3073    }
     3074
     3075    auto data = attributionTriggerData.data;
     3076    auto priority = attributionTriggerData.priority;
     3077
     3078    if (UNLIKELY(debugModeEnabled())) {
     3079        RELEASE_LOG_INFO(PrivateClickMeasurement, "Got an attribution with attribution trigger data: %{public}u and priority: %{public}u.", data, priority);
     3080        debugBroadcastConsoleMessage(MessageSource::PrivateClickMeasurement, MessageLevel::Info, makeString("[Private Click Measurement] Got an attribution with attribution trigger data: '"_s, data, "' and priority: '"_s, priority, "'."_s));
     3081    }
     3082
     3083    auto secondsUntilSend = Seconds::infinity();
     3084
     3085    auto attribution = findPrivateClickMeasurement(sourceSite, attributeOnSite);
     3086    auto& previouslyUnattributed = attribution.first;
     3087    auto& previouslyAttributed = attribution.second;
     3088
     3089    if (previouslyUnattributed) {
     3090        // Always convert the pending attribution and remove it from the unattributed map.
     3091        removeUnattributed(*previouslyUnattributed);
     3092        if (auto optionalSecondsUntilSend = previouslyUnattributed.value().attributeAndGetEarliestTimeToSend(WTFMove(attributionTriggerData))) {
     3093            secondsUntilSend = *optionalSecondsUntilSend;
     3094            ASSERT(secondsUntilSend != Seconds::infinity());
     3095            if (UNLIKELY(debugModeEnabled())) {
     3096                RELEASE_LOG_INFO(PrivateClickMeasurement, "Converted a stored ad click with attribution trigger data: %{public}u and priority: %{public}u.", data, priority);
     3097                debugBroadcastConsoleMessage(MessageSource::PrivateClickMeasurement, MessageLevel::Info, makeString("[Private Click Measurement] Converted a stored ad click with attribution trigger data: '"_s, data, "' and priority: '"_s, priority, "'."_s));
     3098            }
     3099        }
     3100
     3101        // If there is no previous attribution, or the new attribution has higher priority, insert/update the database.
     3102        if (!previouslyAttributed || previouslyUnattributed.value().hasHigherPriorityThan(*previouslyAttributed)) {
     3103            insertPrivateClickMeasurement(WTFMove(*previouslyUnattributed), PrivateClickMeasurementAttributionType::Attributed);
     3104
     3105            if (UNLIKELY(debugModeEnabled())) {
     3106                RELEASE_LOG_INFO(PrivateClickMeasurement, "Replaced a previously converted ad click with a new one with attribution data: %{public}u and priority: %{public}u because it had higher priority.", data, priority);
     3107                debugBroadcastConsoleMessage(MessageSource::PrivateClickMeasurement, MessageLevel::Info, makeString("[Private Click Measurement] Replaced a previously converted ad click with a new one with attribution trigger data: '"_s, data, "' and priority: '"_s, priority, "' because it had higher priority."_s));
     3108            }
     3109        }
     3110    } else if (previouslyAttributed) {
     3111        // If we have no new attribution, re-attribute the old one to respect the new priority.
     3112        if (auto optionalSecondsUntilSend = previouslyAttributed.value().attributeAndGetEarliestTimeToSend(WTFMove(attributionTriggerData))) {
     3113            insertPrivateClickMeasurement(WTFMove(*previouslyAttributed), PrivateClickMeasurementAttributionType::Attributed);
     3114
     3115            secondsUntilSend = *optionalSecondsUntilSend;
     3116            ASSERT(secondsUntilSend != Seconds::infinity());
     3117
     3118            if (UNLIKELY(debugModeEnabled())) {
     3119                RELEASE_LOG_INFO(PrivateClickMeasurement, "Re-converted an ad click with a new one with attribution trigger data: %{public}u and priority: %{public}u because it had higher priority.", data, priority);
     3120                debugBroadcastConsoleMessage(MessageSource::PrivateClickMeasurement, MessageLevel::Info, makeString("[Private Click Measurement] Re-converted an ad click with a new one with attribution trigger data: '"_s, data, "' and priority: '"_s, priority, "'' because it had higher priority."_s));
     3121            }
     3122        }
     3123    }
     3124
     3125    if (secondsUntilSend == Seconds::infinity())
     3126        return WTF::nullopt;
     3127
     3128    return secondsUntilSend;
     3129}
     3130
     3131Vector<WebCore::PrivateClickMeasurement> ResourceLoadStatisticsDatabaseStore::allAttributedPrivateClickMeasurement()
     3132{
     3133    auto attributedScopedStatement = this->scopedStatement(m_allAttributedPrivateClickMeasurementStatement, allAttributedPrivateClickMeasurementQuery, "privateClickMeasurementToString"_s);
     3134
     3135    if (!attributedScopedStatement) {
     3136        RELEASE_LOG_ERROR_IF_ALLOWED(m_sessionID, "%p - ResourceLoadStatisticsDatabaseStore::privateClickMeasurementToString, error message: %{private}s", this, m_database.lastErrorMsg());
     3137        ASSERT_NOT_REACHED();
     3138    }
     3139
     3140    Vector<WebCore::PrivateClickMeasurement> attributions;
     3141    while (attributedScopedStatement->step() == SQLITE_ROW)
     3142        attributions.append(buildPrivateClickMeasurementFromDatabase(attributedScopedStatement.get(), PrivateClickMeasurementAttributionType::Attributed));
     3143
     3144    return attributions;
     3145}
     3146
     3147void ResourceLoadStatisticsDatabaseStore::clearPrivateClickMeasurement(Optional<RegistrableDomain> domain)
     3148{
     3149    // Default to clear all entries if no domain is specified.
     3150    String bindParameter = "%";
     3151    if (domain) {
     3152        auto domainIDToMatch = domainID(*domain);
     3153        if (!domainIDToMatch)
     3154            return;
     3155
     3156        bindParameter = String::number(*domainIDToMatch);
     3157    }
     3158
     3159    auto clearUnattributedScopedStatement = this->scopedStatement(m_clearUnattributedPrivateClickMeasurementStatement, clearUnattributedPrivateClickMeasurementQuery, "clearPrivateClickMeasurement"_s);
     3160
     3161    if (!clearUnattributedScopedStatement
     3162        || clearUnattributedScopedStatement->bindText(1, bindParameter) != SQLITE_OK
     3163        || clearUnattributedScopedStatement->bindText(2, bindParameter) != SQLITE_OK
     3164        || clearUnattributedScopedStatement->step() != SQLITE_DONE) {
     3165        RELEASE_LOG_ERROR_IF_ALLOWED(m_sessionID, "%p - ResourceLoadStatisticsDatabaseStore::clearPrivateClickMeasurement clearUnattributedScopedStatement, error message: %{private}s", this, m_database.lastErrorMsg());
     3166        ASSERT_NOT_REACHED();
     3167    }
     3168
     3169    auto clearAttributedScopedStatement = this->scopedStatement(m_clearAttributedPrivateClickMeasurementStatement, clearAttributedPrivateClickMeasurementQuery, "clearPrivateClickMeasurement"_s);
     3170
     3171    if (!clearAttributedScopedStatement
     3172        || clearAttributedScopedStatement->bindText(1, bindParameter) != SQLITE_OK
     3173        || clearAttributedScopedStatement->bindText(2, bindParameter) != SQLITE_OK
     3174        || clearAttributedScopedStatement->step() != SQLITE_DONE) {
     3175        RELEASE_LOG_ERROR_IF_ALLOWED(m_sessionID, "%p - ResourceLoadStatisticsDatabaseStore::clearPrivateClickMeasurement clearAttributedScopedStatement, error message: %{private}s", this, m_database.lastErrorMsg());
     3176        ASSERT_NOT_REACHED();
     3177    }
     3178}
     3179
     3180void ResourceLoadStatisticsDatabaseStore::clearExpiredPrivateClickMeasurement()
     3181{
     3182    auto expirationTimeFrame = WallTime::now() - WebCore::PrivateClickMeasurement::maxAge();
     3183    auto scopedStatement = this->scopedStatement(m_clearExpiredPrivateClickMeasurementStatement, clearExpiredPrivateClickMeasurementQuery, "clearExpiredPrivateClickMeasurement"_s);
     3184
     3185    if (!scopedStatement
     3186        || scopedStatement->bindDouble(1, expirationTimeFrame.secondsSinceEpoch().value()) != SQLITE_OK
     3187        || scopedStatement->step() != SQLITE_DONE) {
     3188        RELEASE_LOG_ERROR_IF_ALLOWED(m_sessionID, "%p - ResourceLoadStatisticsDatabaseStore::clearExpiredPrivateClickMeasurement, error message: %{private}s", this, m_database.lastErrorMsg());
     3189        ASSERT_NOT_REACHED();
     3190    }
     3191}
     3192
     3193String ResourceLoadStatisticsDatabaseStore::attributionToString(WebCore::SQLiteStatement* statement, PrivateClickMeasurementAttributionType attributionType)
     3194{
     3195    auto sourceSiteDomain = getDomainStringFromDomainID(statement->getColumnInt(0));
     3196    auto attributeOnSiteDomain = getDomainStringFromDomainID(statement->getColumnInt(1));
     3197    auto sourceID = statement->getColumnInt(2);
     3198
     3199    StringBuilder builder;
     3200    builder.appendLiteral("Source site: ");
     3201    builder.append(sourceSiteDomain);
     3202    builder.appendLiteral("\nAttribute on site: ");
     3203    builder.append(attributeOnSiteDomain);
     3204    builder.appendLiteral("\nSource ID: ");
     3205    builder.appendNumber(sourceID);
     3206
     3207    if (attributionType == PrivateClickMeasurementAttributionType::Attributed) {
     3208        auto attributionTriggerData = statement->getColumnInt(3);
     3209        auto priority = statement->getColumnInt(4);
     3210        auto earliestTimeToSend = statement->getColumnInt(6);
     3211
     3212        if (attributionTriggerData != -1) {
     3213            builder.appendLiteral("\nAttribution trigger data: ");
     3214            builder.appendNumber(attributionTriggerData);
     3215            builder.appendLiteral("\nAttribution priority: ");
     3216            builder.appendNumber(priority);
     3217            builder.appendLiteral("\nAttribution earliest time to send: ");
     3218            if (earliestTimeToSend == -1)
     3219                builder.appendLiteral("Not set");
     3220            else {
     3221                auto secondsUntilSend = WallTime::fromRawSeconds(earliestTimeToSend) - WallTime::now();
     3222                builder.append((secondsUntilSend >= 24_h && secondsUntilSend <= 48_h) ? "Within 24-48 hours" : "Outside 24-48 hours");
     3223            }
     3224        } else
     3225            builder.appendLiteral("\nNo attribution trigger data.");
     3226    } else
     3227        builder.appendLiteral("\nNo attribution trigger data.");
     3228    builder.append('\n');
     3229
     3230    return builder.toString();
     3231}
     3232
     3233String ResourceLoadStatisticsDatabaseStore::privateClickMeasurementToString()
     3234{
     3235    SQLiteStatement privateClickMeasurementDataExists(m_database, "SELECT (SELECT COUNT(*) FROM UnattributedPrivateClickMeasurement) as cnt1, (SELECT COUNT(*) FROM AttributedPrivateClickMeasurement) as cnt2"_s);
     3236    if (privateClickMeasurementDataExists.prepare() != SQLITE_OK || privateClickMeasurementDataExists.step() != SQLITE_ROW) {
     3237        RELEASE_LOG_ERROR(Network, "%p - ResourceLoadStatisticsDatabaseStore::privateClickMeasurementToString failed, error message: %{private}s", this, m_database.lastErrorMsg());
     3238        ASSERT_NOT_REACHED();
     3239        return { };
     3240    }
     3241
     3242    if (!privateClickMeasurementDataExists.getColumnInt(0) && !privateClickMeasurementDataExists.getColumnInt(1))
     3243        return "\nNo stored Private Click Measurement data.\n"_s;
     3244
     3245    auto unattributedScopedStatement = this->scopedStatement(m_allUnattributedPrivateClickMeasurementAttributionsStatement, allUnattributedPrivateClickMeasurementAttributionsQuery, "privateClickMeasurementToString"_s);
     3246
     3247    if (!unattributedScopedStatement) {
     3248        RELEASE_LOG_ERROR_IF_ALLOWED(m_sessionID, "%p - ResourceLoadStatisticsDatabaseStore::privateClickMeasurementToString, error message: %{private}s", this, m_database.lastErrorMsg());
     3249        ASSERT_NOT_REACHED();
     3250    }
     3251
     3252    unsigned unattributedNumber = 0;
     3253    StringBuilder builder;
     3254    while (unattributedScopedStatement->step() == SQLITE_ROW) {
     3255        if (!unattributedNumber)
     3256            builder.appendLiteral("Unattributed Private Click Measurements:\n");
     3257        else
     3258            builder.append('\n');
     3259        builder.appendLiteral("WebCore::PrivateClickMeasurement ");
     3260        builder.appendNumber(++unattributedNumber);
     3261        builder.append('\n');
     3262        builder.append(attributionToString(unattributedScopedStatement.get(), PrivateClickMeasurementAttributionType::Unattributed));
     3263    }
     3264
     3265    auto attributedScopedStatement = this->scopedStatement(m_allAttributedPrivateClickMeasurementStatement, allAttributedPrivateClickMeasurementQuery, "privateClickMeasurementToString"_s);
     3266
     3267    if (!attributedScopedStatement) {
     3268        RELEASE_LOG_ERROR_IF_ALLOWED(m_sessionID, "%p - ResourceLoadStatisticsDatabaseStore::privateClickMeasurementToString, error message: %{private}s", this, m_database.lastErrorMsg());
     3269        ASSERT_NOT_REACHED();
     3270    }
     3271
     3272    unsigned attributedNumber = 0;
     3273    while (attributedScopedStatement->step() == SQLITE_ROW) {
     3274        if (unattributedNumber)
     3275            builder.append('\n');
     3276        if (!attributedNumber)
     3277            builder.appendLiteral("Attributed Private Click Measurements:\n");
     3278        else
     3279            builder.append('\n');
     3280        builder.appendLiteral("WebCore::PrivateClickMeasurement ");
     3281        builder.appendNumber(++attributedNumber + unattributedNumber);
     3282        builder.append('\n');
     3283        builder.append(attributionToString(attributedScopedStatement.get(), PrivateClickMeasurementAttributionType::Attributed));
     3284    }
     3285    return builder.toString();
     3286}
     3287
     3288void ResourceLoadStatisticsDatabaseStore::clearSentAttributions(Vector<WebCore::PrivateClickMeasurement>&& attributions)
     3289{
     3290    for (auto& attribution : attributions) {
     3291        auto sourceSiteDomainID = domainID(attribution.sourceSite().registrableDomain);
     3292        auto attributeOnSiteDomainID = domainID(attribution.attributeOnSite().registrableDomain);
     3293
     3294        if (!sourceSiteDomainID || !attributeOnSiteDomainID)
     3295            return;
     3296
     3297        SQLiteStatement clearAttributedStatement(m_database, "DELETE FROM AttributedPrivateClickMeasurement WHERE sourceSiteDomainID = ? AND attributeOnSiteDomainID = ?"_s);
     3298        if (clearAttributedStatement.prepare() != SQLITE_OK
     3299            || clearAttributedStatement.bindInt(1, *sourceSiteDomainID) != SQLITE_OK
     3300            || clearAttributedStatement.bindInt(2, *attributeOnSiteDomainID) != SQLITE_OK
     3301            || clearAttributedStatement.step() != SQLITE_DONE) {
     3302            RELEASE_LOG_ERROR_IF_ALLOWED(m_sessionID, "%p - ResourceLoadStatisticsDatabaseStore::clearSentAttributions failed to step, error message: %{private}s", this, m_database.lastErrorMsg());
     3303            ASSERT_NOT_REACHED();
     3304        }
     3305        clearAttributedStatement.reset();
     3306    }
     3307}
     3308
     3309void ResourceLoadStatisticsDatabaseStore::markAttributedPrivateClickMeasurementsAsExpiredForTesting()
     3310{
     3311    // Update the last timer fired time to be one day ago.
     3312    auto yesterday = (WallTime::now() - 24_h).secondsSinceEpoch().value();
     3313    auto scopedStatement = this->scopedStatement(m_updateTimerLastFiredStatement, updateTimerLastFiredQuery, "insertExpiredAttributionTesting"_s);
     3314    if (!scopedStatement
     3315        || scopedStatement->bindDouble(1, yesterday) != SQLITE_OK
     3316        || scopedStatement->step() != SQLITE_DONE) {
     3317        RELEASE_LOG_ERROR(Network, "%p - ResourceLoadStatisticsDatabaseStore::insertExpiredAttributionTesting failed, error message: %{private}s", this, m_database.lastErrorMsg());
     3318        ASSERT_NOT_REACHED();
     3319    }
     3320
     3321    auto expiredTimeToSend = 1_h; // Must be less than 1 day.
     3322   
     3323    auto statement = SQLiteStatement(m_database, "UPDATE AttributedPrivateClickMeasurement SET earliestTimeToSend = ?");
     3324    if (statement.prepare() != SQLITE_OK
     3325        || statement.bindInt(1, expiredTimeToSend.value()) != SQLITE_OK
     3326        || statement.step() != SQLITE_DONE) {
     3327        RELEASE_LOG_ERROR_IF_ALLOWED(m_sessionID, "%p - ResourceLoadStatisticsDatabaseStore::insertPrivateClickMeasurement, error message: %{private}s", this, m_database.lastErrorMsg());
     3328        ASSERT_NOT_REACHED();
     3329    }
     3330    return;
     3331}
     3332
    28173333} // namespace WebKit
    28183334
  • trunk/Source/WebKit/NetworkProcess/Classifier/ResourceLoadStatisticsDatabaseStore.h

    r269807 r270136  
    5454
    5555class ResourceLoadStatisticsMemoryStore;
     56class PrivateClickMeasurementManager;
     57
     58using AttributedPrivateClickMeasurement = WebCore::PrivateClickMeasurement;
     59using UnattributedPrivateClickMeasurement = WebCore::PrivateClickMeasurement;
     60using SourceSite = WebCore::PrivateClickMeasurement::SourceSite;
     61using AttributeOnSite = WebCore::PrivateClickMeasurement::AttributeOnSite;
     62using AttributionTriggerData = WebCore::PrivateClickMeasurement::AttributionTriggerData;
    5663
    5764// This is always constructed / used / destroyed on the WebResourceLoadStatisticsStore's statistics queue.
     
    124131    void insertExpiredStatisticForTesting(const RegistrableDomain&, bool hasUserInteraction, bool isScheduledForAllButCookieDataRemoval, bool isPrevalent) override;
    125132    void interrupt();
     133
     134    // Private Click Measurement.
     135    void insertPrivateClickMeasurement(WebCore::PrivateClickMeasurement&&, PrivateClickMeasurementAttributionType) override;
     136    void markAllUnattributedPrivateClickMeasurementAsExpiredForTesting() override;
     137    Optional<Seconds> attributePrivateClickMeasurement(const WebCore::PrivateClickMeasurement::SourceSite&, const WebCore::PrivateClickMeasurement::AttributeOnSite&, WebCore::PrivateClickMeasurement::AttributionTriggerData&&) override;
     138    Vector<WebCore::PrivateClickMeasurement> allAttributedPrivateClickMeasurement() override;
     139    void clearPrivateClickMeasurement(Optional<RegistrableDomain>) override;
     140    void clearExpiredPrivateClickMeasurement() override;
     141    String privateClickMeasurementToString() override;
     142    void clearSentAttributions(Vector<WebCore::PrivateClickMeasurement>&&) override;
     143    void updateTimerLastFired() override;
     144    void markAttributedPrivateClickMeasurementsAsExpiredForTesting() override;
     145    void updatePrivateClickMeasurementAttributionTimes();
    126146
    127147private:
     
    214234    Optional<WallTime> mostRecentUserInteractionTime(const DomainData&);
    215235   
     236    void removeUnattributed(WebCore::PrivateClickMeasurement&);
     237    WebCore::PrivateClickMeasurement buildPrivateClickMeasurementFromDatabase(WebCore::SQLiteStatement*, PrivateClickMeasurementAttributionType);
     238    String attributionToString(WebCore::SQLiteStatement*, PrivateClickMeasurementAttributionType);
     239    std::pair<Optional<UnattributedPrivateClickMeasurement>, Optional<AttributedPrivateClickMeasurement>> findPrivateClickMeasurement(const WebCore::PrivateClickMeasurement::SourceSite&, const WebCore::PrivateClickMeasurement::AttributeOnSite&);
     240    WallTime timerLastFired();
     241
    216242    const String m_storageDirectoryPath;
    217243    mutable WebCore::SQLiteDatabase m_database;
     
    253279    mutable std::unique_ptr<WebCore::SQLiteStatement> m_observedDomainsExistsStatement;
    254280    mutable std::unique_ptr<WebCore::SQLiteStatement> m_removeAllDataStatement;
     281    std::unique_ptr<WebCore::SQLiteStatement> m_insertUnattributedPrivateClickMeasurementStatement;
     282    std::unique_ptr<WebCore::SQLiteStatement> m_insertAttributedPrivateClickMeasurementStatement;
     283    std::unique_ptr<WebCore::SQLiteStatement> m_setUnattributedPrivateClickMeasurementAsExpiredStatement;
     284    std::unique_ptr<WebCore::SQLiteStatement> m_clearUnattributedPrivateClickMeasurementStatement;
     285    std::unique_ptr<WebCore::SQLiteStatement> m_clearAttributedPrivateClickMeasurementStatement;
     286    std::unique_ptr<WebCore::SQLiteStatement> m_clearExpiredPrivateClickMeasurementStatement;
     287    std::unique_ptr<WebCore::SQLiteStatement> m_allUnattributedPrivateClickMeasurementAttributionsStatement;
     288    std::unique_ptr<WebCore::SQLiteStatement> m_allAttributedPrivateClickMeasurementStatement;
     289    std::unique_ptr<WebCore::SQLiteStatement> m_findUnattributedStatement;
     290    std::unique_ptr<WebCore::SQLiteStatement> m_findAttributedStatement;
     291    std::unique_ptr<WebCore::SQLiteStatement> m_updateTimerLastFiredStatement;
     292    std::unique_ptr<WebCore::SQLiteStatement> m_timerLastFiredStatement;
     293    std::unique_ptr<WebCore::SQLiteStatement> m_updateAttributionsEarliestTimeToSendStatement;
     294    std::unique_ptr<WebCore::SQLiteStatement> m_removeUnattributedStatement;
     295   
    255296    PAL::SessionID m_sessionID;
    256297    bool m_isNewResourceLoadStatisticsDatabaseFile { false };
  • trunk/Source/WebKit/NetworkProcess/Classifier/ResourceLoadStatisticsMemoryStore.h

    r268458 r270136  
    109109    void insertExpiredStatisticForTesting(const RegistrableDomain&, bool hasUserInteraction, bool isScheduledForAllButCookieDataRemoval, bool isPrevalent) override;
    110110
     111    // Private Click Measurement is not implemented in the ITP memory store.
     112    void insertPrivateClickMeasurement(WebCore::PrivateClickMeasurement&&, PrivateClickMeasurementAttributionType) override { };
     113    void markAllUnattributedPrivateClickMeasurementAsExpiredForTesting() override { };
     114    Optional<Seconds> attributePrivateClickMeasurement(const WebCore::PrivateClickMeasurement::SourceSite&, const WebCore::PrivateClickMeasurement::AttributeOnSite&, WebCore::PrivateClickMeasurement::AttributionTriggerData&&) override { return { }; };
     115    Vector<WebCore::PrivateClickMeasurement> allAttributedPrivateClickMeasurement() override { return { }; };
     116    void clearPrivateClickMeasurement(Optional<RegistrableDomain>) override { };
     117    void clearExpiredPrivateClickMeasurement() override { };
     118    String privateClickMeasurementToString() override { return String(); };
     119    void clearSentAttributions(Vector<WebCore::PrivateClickMeasurement>&&) override { };
     120    void markAttributedPrivateClickMeasurementsAsExpiredForTesting() override { };
     121    void updateTimerLastFired() override { };
     122
    111123private:
    112124    void includeTodayAsOperatingDateIfNecessary() override;
  • trunk/Source/WebKit/NetworkProcess/Classifier/ResourceLoadStatisticsStore.h

    r268458 r270136  
    203203    virtual bool hasStatisticsExpired(WallTime mostRecentUserInteractionTime, OperatingDatesWindow) const = 0;
    204204    virtual void insertExpiredStatisticForTesting(const RegistrableDomain&, bool hasUserInteraction, bool isScheduledForAllButCookieDataRemoval, bool) = 0;
     205   
     206    // Private Click Measurement.
     207    virtual void insertPrivateClickMeasurement(WebCore::PrivateClickMeasurement&&, PrivateClickMeasurementAttributionType) = 0;
     208    virtual void markAllUnattributedPrivateClickMeasurementAsExpiredForTesting() = 0;
     209    virtual Optional<Seconds> attributePrivateClickMeasurement(const WebCore::PrivateClickMeasurement::SourceSite&, const WebCore::PrivateClickMeasurement::AttributeOnSite&, WebCore::PrivateClickMeasurement::AttributionTriggerData&&) = 0;
     210    virtual Vector<WebCore::PrivateClickMeasurement> allAttributedPrivateClickMeasurement() = 0;
     211    virtual void clearPrivateClickMeasurement(Optional<RegistrableDomain>) = 0;
     212    virtual void clearExpiredPrivateClickMeasurement() = 0;
     213    virtual String privateClickMeasurementToString() = 0;
     214    virtual void clearSentAttributions(Vector<WebCore::PrivateClickMeasurement>&&) = 0;
     215    virtual void markAttributedPrivateClickMeasurementsAsExpiredForTesting() = 0;
     216    virtual void updateTimerLastFired() = 0;
    205217
    206218protected:
  • trunk/Source/WebKit/NetworkProcess/Classifier/WebResourceLoadStatisticsStore.cpp

    r269807 r270136  
    14761476}
    14771477
     1478void WebResourceLoadStatisticsStore::updateTimerLastFired()
     1479{
     1480    ASSERT(RunLoop::isMain());
     1481
     1482    if (isEphemeral())
     1483        return;
     1484
     1485    postTask([this]() mutable {
     1486        if (!m_statisticsStore)
     1487            return;
     1488
     1489        m_statisticsStore->updateTimerLastFired();
     1490    });
     1491}
     1492
     1493void WebResourceLoadStatisticsStore::insertPrivateClickMeasurement(PrivateClickMeasurement&& attribution, PrivateClickMeasurementAttributionType attributionType)
     1494{
     1495    ASSERT(RunLoop::isMain());
     1496
     1497    if (isEphemeral())
     1498        return;
     1499
     1500    postTask([this, attribution = WTFMove(attribution), attributionType]() mutable {
     1501        if (!m_statisticsStore)
     1502            return;
     1503
     1504        m_statisticsStore->insertPrivateClickMeasurement(WTFMove(attribution), attributionType);
     1505    });
     1506}
     1507
     1508void WebResourceLoadStatisticsStore::markAllUnattributedPrivateClickMeasurementAsExpiredForTesting()
     1509{
     1510    ASSERT(RunLoop::isMain());
     1511
     1512    if (isEphemeral())
     1513        return;
     1514
     1515    postTask([this]() {
     1516        if (!m_statisticsStore)
     1517            return;
     1518
     1519        m_statisticsStore->markAllUnattributedPrivateClickMeasurementAsExpiredForTesting();
     1520    });
     1521}
     1522
     1523void WebResourceLoadStatisticsStore::attributePrivateClickMeasurement(const PrivateClickMeasurement::SourceSite& sourceSite, const PrivateClickMeasurement::AttributeOnSite& attributeOnSite, PrivateClickMeasurement::AttributionTriggerData&& attributionTriggerData, CompletionHandler<void(Optional<Seconds>)>&& completionHandler)
     1524{
     1525    ASSERT(RunLoop::isMain());
     1526
     1527    if (isEphemeral()) {
     1528        completionHandler({ });
     1529        return;
     1530    }
     1531
     1532    postTask([this, sourceSite, attributeOnSite, attributionTriggerData = WTFMove(attributionTriggerData), completionHandler = WTFMove(completionHandler)]() mutable {
     1533        if (!m_statisticsStore) {
     1534            postTaskReply([completionHandler = WTFMove(completionHandler)]() mutable {
     1535                completionHandler(WTF::nullopt);
     1536            });
     1537            return;
     1538        }
     1539
     1540        auto seconds = m_statisticsStore->attributePrivateClickMeasurement(sourceSite, attributeOnSite, WTFMove(attributionTriggerData));
     1541        postTaskReply([seconds, completionHandler = WTFMove(completionHandler)]() mutable {
     1542            completionHandler(seconds);
     1543        });
     1544    });
     1545}
     1546
     1547void WebResourceLoadStatisticsStore::allAttributedPrivateClickMeasurement(CompletionHandler<void(Vector<PrivateClickMeasurement>&&)>&& completionHandler)
     1548{
     1549    ASSERT(RunLoop::isMain());
     1550
     1551    if (isEphemeral()) {
     1552        completionHandler({ });
     1553        return;
     1554    }
     1555
     1556    postTask([this, completionHandler = WTFMove(completionHandler)]() mutable {
     1557        if (!m_statisticsStore) {
     1558            postTaskReply([completionHandler = WTFMove(completionHandler)]() mutable {
     1559                completionHandler({ });
     1560            });
     1561            return;
     1562        }
     1563
     1564        auto convertedAttributions = m_statisticsStore->allAttributedPrivateClickMeasurement();
     1565        postTaskReply([convertedAttributions = WTFMove(convertedAttributions), completionHandler = WTFMove(completionHandler)]() mutable {
     1566            completionHandler(WTFMove(convertedAttributions));
     1567        });
     1568    });
     1569}
     1570
     1571void WebResourceLoadStatisticsStore::clearPrivateClickMeasurement()
     1572{
     1573    ASSERT(RunLoop::isMain());
     1574
     1575    if (isEphemeral())
     1576        return;
     1577
     1578    postTask([this]() {
     1579        if (!m_statisticsStore)
     1580            return;
     1581
     1582        m_statisticsStore->clearPrivateClickMeasurement(WTF::nullopt);
     1583    });
     1584}
     1585   
     1586void WebResourceLoadStatisticsStore::clearPrivateClickMeasurementForRegistrableDomain(const RegistrableDomain& domain)
     1587{
     1588    ASSERT(RunLoop::isMain());
     1589
     1590    if (isEphemeral())
     1591        return;
     1592
     1593    postTask([this, domain = domain.isolatedCopy()]() mutable {
     1594        if (!m_statisticsStore)
     1595            return;
     1596
     1597        m_statisticsStore->clearPrivateClickMeasurement(domain);
     1598    });
     1599}
     1600
     1601void WebResourceLoadStatisticsStore::clearExpiredPrivateClickMeasurement()
     1602{
     1603    ASSERT(RunLoop::isMain());
     1604
     1605    if (isEphemeral())
     1606        return;
     1607
     1608    postTask([this]() {
     1609        if (!m_statisticsStore)
     1610            return;
     1611
     1612        m_statisticsStore->clearExpiredPrivateClickMeasurement();
     1613    });
     1614}
     1615
     1616void WebResourceLoadStatisticsStore::privateClickMeasurementToString(CompletionHandler<void(String)>&& completionHandler)
     1617{
     1618    ASSERT(RunLoop::isMain());
     1619
     1620    if (isEphemeral()) {
     1621        completionHandler("\nNo stored Private Click Measurement data.\n"_s);
     1622        return;
     1623    }
     1624
     1625    postTask([this, completionHandler = WTFMove(completionHandler)]() mutable {
     1626        if (!m_statisticsStore) {
     1627            postTaskReply([completionHandler = WTFMove(completionHandler)]() mutable {
     1628                completionHandler({ });
     1629            });
     1630            return;
     1631        }
     1632
     1633        auto result = m_statisticsStore->privateClickMeasurementToString();
     1634        postTaskReply([result, completionHandler = WTFMove(completionHandler)]() mutable {
     1635            completionHandler(result);
     1636        });
     1637    });
     1638}
     1639
     1640void WebResourceLoadStatisticsStore::clearSentAttributions(Vector<WebCore::PrivateClickMeasurement>&& attributionsToClear)
     1641{
     1642    ASSERT(RunLoop::isMain());
     1643
     1644    if (isEphemeral())
     1645        return;
     1646
     1647    postTask([this, attributionsToClear = WTFMove(attributionsToClear)]() mutable {
     1648        if (!m_statisticsStore)
     1649            return;
     1650
     1651        m_statisticsStore->clearSentAttributions(WTFMove(attributionsToClear));
     1652    });
     1653}
     1654
     1655void WebResourceLoadStatisticsStore::markAttributedPrivateClickMeasurementsAsExpiredForTesting(CompletionHandler<void()>&& completionHandler)
     1656{
     1657    ASSERT(RunLoop::isMain());
     1658
     1659    if (isEphemeral()) {
     1660        completionHandler();
     1661        return;
     1662    }
     1663
     1664    postTask([this, completionHandler = WTFMove(completionHandler)]() mutable {
     1665        if (m_statisticsStore)
     1666            m_statisticsStore->markAttributedPrivateClickMeasurementsAsExpiredForTesting();
     1667
     1668        postTaskReply(WTFMove(completionHandler));
     1669    });
     1670}
     1671
    14781672} // namespace WebKit
    14791673
  • trunk/Source/WebKit/NetworkProcess/Classifier/WebResourceLoadStatisticsStore.h

    r269807 r270136  
    3838#include <WebCore/NetworkStorageSession.h>
    3939#include <WebCore/PageIdentifier.h>
     40#include <WebCore/PrivateClickMeasurement.h>
    4041#include <WebCore/RegistrableDomain.h>
    4142#include <WebCore/ResourceLoadObserver.h>
     
    6667class WebFrameProxy;
    6768class WebProcessProxy;
     69enum class PrivateClickMeasurementAttributionType : bool;
    6870enum class ShouldGrandfatherStatistics : bool;
    6971enum class ShouldIncludeLocalhost : bool { No, Yes };
     
    305307    void insertExpiredStatisticForTesting(const RegistrableDomain&, bool hadUserInteraction, bool isScheduledForAllButCookieDataRemoval, bool isPrevalent, CompletionHandler<void()>&&);
    306308
     309    // Private Click Measurement.
     310    void insertPrivateClickMeasurement(WebCore::PrivateClickMeasurement&&, PrivateClickMeasurementAttributionType);
     311    void markAllUnattributedPrivateClickMeasurementAsExpiredForTesting();
     312    void attributePrivateClickMeasurement(const WebCore::PrivateClickMeasurement::SourceSite&, const WebCore::PrivateClickMeasurement::AttributeOnSite&, WebCore::PrivateClickMeasurement::AttributionTriggerData&&, CompletionHandler<void(Optional<Seconds>)>&&);
     313    void allAttributedPrivateClickMeasurement(CompletionHandler<void(Vector<WebCore::PrivateClickMeasurement>&&)>&&);
     314    void clearPrivateClickMeasurement();
     315    void clearPrivateClickMeasurementForRegistrableDomain(const WebCore::RegistrableDomain&);
     316    void clearExpiredPrivateClickMeasurement();
     317    void privateClickMeasurementToString(CompletionHandler<void(String)>&&);
     318    void clearSentAttributions(Vector<WebCore::PrivateClickMeasurement>&&);
     319    void updateTimerLastFired();
     320    void markAttributedPrivateClickMeasurementsAsExpiredForTesting(CompletionHandler<void()>&&);
     321
    307322private:
    308323    explicit WebResourceLoadStatisticsStore(NetworkSession&, const String&, ShouldIncludeLocalhost, WebCore::ResourceLoadStatistics::IsEphemeral);
  • trunk/Source/WebKit/NetworkProcess/NetworkProcess.cpp

    r269712 r270136  
    25632563}
    25642564
     2565void NetworkProcess::firePrivateClickMeasurementTimerImmediately(PAL::SessionID sessionID)
     2566{
     2567    if (auto* session = networkSession(sessionID))
     2568        session->firePrivateClickMeasurementTimerImmediately();
     2569}
     2570
     2571void NetworkProcess::simulateResourceLoadStatisticsSessionRestart(PAL::SessionID sessionID, CompletionHandler<void()>&& completionHandler)
     2572{
     2573    if (auto* session = networkSession(sessionID)) {
     2574        session->recreateResourceLoadStatisticStore([this, sessionID, completionHandler = WTFMove(completionHandler)] () mutable {
     2575            firePrivateClickMeasurementTimerImmediately(sessionID);
     2576            completionHandler();
     2577        });
     2578        return;
     2579    }
     2580    completionHandler();
     2581}
     2582
     2583void NetworkProcess::markAttributedPrivateClickMeasurementsAsExpiredForTesting(PAL::SessionID sessionID, CompletionHandler<void()>&& completionHandler)
     2584{
     2585    if (auto* session = networkSession(sessionID)) {
     2586        session->markAttributedPrivateClickMeasurementsAsExpiredForTesting(WTFMove(completionHandler));
     2587        return;
     2588    }
     2589    completionHandler();
     2590}
     2591
    25652592void NetworkProcess::setPrivateClickMeasurementConversionURLForTesting(PAL::SessionID sessionID, URL&& url, CompletionHandler<void()>&& completionHandler)
    25662593{
  • trunk/Source/WebKit/NetworkProcess/NetworkProcess.h

    r269712 r270136  
    337337    void clearPrivateClickMeasurement(PAL::SessionID, CompletionHandler<void()>&&);
    338338    void setPrivateClickMeasurementOverrideTimerForTesting(PAL::SessionID, bool value, CompletionHandler<void()>&&);
     339    void markAttributedPrivateClickMeasurementsAsExpiredForTesting(PAL::SessionID, CompletionHandler<void()>&&);
     340    void simulateResourceLoadStatisticsSessionRestart(PAL::SessionID, CompletionHandler<void()>&&);
    339341    void setPrivateClickMeasurementConversionURLForTesting(PAL::SessionID, URL&&, CompletionHandler<void()>&&);
    340342    void markPrivateClickMeasurementsAsExpiredForTesting(PAL::SessionID, CompletionHandler<void()>&&);
     
    479481    void addServiceWorkerSession(PAL::SessionID, bool processTerminationDelayEnabled, String&& serviceWorkerRegistrationDirectory, const SandboxExtension::Handle&);
    480482#endif
     483
     484    void firePrivateClickMeasurementTimerImmediately(PAL::SessionID);
    481485
    482486    class SessionStorageQuotaManager {
  • trunk/Source/WebKit/NetworkProcess/NetworkProcess.messages.in

    r269712 r270136  
    166166    ClearPrivateClickMeasurement(PAL::SessionID sessionID) -> () Async
    167167    SetPrivateClickMeasurementOverrideTimerForTesting(PAL::SessionID sessionID, bool value) -> () Async
     168    MarkAttributedPrivateClickMeasurementsAsExpiredForTesting(PAL::SessionID sessionID) -> () Async
     169    SimulateResourceLoadStatisticsSessionRestart(PAL::SessionID sessionID) -> () Async
    168170    SetPrivateClickMeasurementConversionURLForTesting(PAL::SessionID sessionID, URL url) -> () Async
    169171    MarkPrivateClickMeasurementsAsExpiredForTesting(PAL::SessionID sessionID) -> () Async
  • trunk/Source/WebKit/NetworkProcess/NetworkResourceLoader.cpp

    r269712 r270136  
    815815    m_redirectResponse = redirectResponse;
    816816
    817     Optional<PrivateClickMeasurement::Conversion> privateClickMeasurementConversion;
     817    Optional<PrivateClickMeasurement::AttributionTriggerData> privateClickMeasurementAttributionTriggerData;
    818818    if (!sessionID().isEphemeral()) {
    819         if (auto result = PrivateClickMeasurement::parseConversionRequest(redirectRequest.url()))
    820             privateClickMeasurementConversion = result.value();
     819        if (auto result = PrivateClickMeasurement::parseAttributionRequest(redirectRequest.url()))
     820            privateClickMeasurementAttributionTriggerData = result.value();
    821821        else if (!result.error().isEmpty())
    822822            addConsoleMessage(MessageSource::PrivateClickMeasurement, MessageLevel::Error, result.error());
     
    828828
    829829    if (m_networkLoadChecker) {
    830         if (privateClickMeasurementConversion)
     830        if (privateClickMeasurementAttributionTriggerData)
    831831            m_networkLoadChecker->enableContentExtensionsCheck();
    832832        m_networkLoadChecker->storeRedirectionIfNeeded(request, redirectResponse);
    833833
    834834        RELEASE_LOG_IF_ALLOWED("willSendRedirectedRequest: Checking redirect using NetworkLoadChecker");
    835         m_networkLoadChecker->checkRedirection(WTFMove(request), WTFMove(redirectRequest), WTFMove(redirectResponse), this, [protectedThis = makeRef(*this), this, storedCredentialsPolicy = m_networkLoadChecker->storedCredentialsPolicy(), privateClickMeasurementConversion = WTFMove(privateClickMeasurementConversion)](auto&& result) mutable {
     835        m_networkLoadChecker->checkRedirection(WTFMove(request), WTFMove(redirectRequest), WTFMove(redirectResponse), this, [protectedThis = makeRef(*this), this, storedCredentialsPolicy = m_networkLoadChecker->storedCredentialsPolicy(), privateClickMeasurementAttributionTriggerData = WTFMove(privateClickMeasurementAttributionTriggerData)](auto&& result) mutable {
    836836            if (!result.has_value()) {
    837837                if (result.error().isCancellation()) {
     
    866866
    867867            m_shouldRestartLoad = storedCredentialsPolicy != m_networkLoadChecker->storedCredentialsPolicy();
    868             this->continueWillSendRedirectedRequest(WTFMove(result->request), WTFMove(result->redirectRequest), WTFMove(result->redirectResponse), WTFMove(privateClickMeasurementConversion));
     868            this->continueWillSendRedirectedRequest(WTFMove(result->request), WTFMove(result->redirectRequest), WTFMove(result->redirectResponse), WTFMove(privateClickMeasurementAttributionTriggerData));
    869869        });
    870870        return;
    871871    }
    872     continueWillSendRedirectedRequest(WTFMove(request), WTFMove(redirectRequest), WTFMove(redirectResponse), WTFMove(privateClickMeasurementConversion));
    873 }
    874 
    875 void NetworkResourceLoader::continueWillSendRedirectedRequest(ResourceRequest&& request, ResourceRequest&& redirectRequest, ResourceResponse&& redirectResponse, Optional<PrivateClickMeasurement::Conversion>&& privateClickMeasurementConversion)
    876 {
    877     RELEASE_LOG_IF_ALLOWED("continueWillSendRedirectedRequest: (m_isKeptAlive=%d, hasAdClickConversion=%d)", m_isKeptAlive, !!privateClickMeasurementConversion);
     872    continueWillSendRedirectedRequest(WTFMove(request), WTFMove(redirectRequest), WTFMove(redirectResponse), WTFMove(privateClickMeasurementAttributionTriggerData));
     873}
     874
     875void NetworkResourceLoader::continueWillSendRedirectedRequest(ResourceRequest&& request, ResourceRequest&& redirectRequest, ResourceResponse&& redirectResponse, Optional<PrivateClickMeasurement::AttributionTriggerData>&& privateClickMeasurementAttributionTriggerData)
     876{
     877    RELEASE_LOG_IF_ALLOWED("continueWillSendRedirectedRequest: (m_isKeptAlive=%d, hasAdClickConversion=%d)", m_isKeptAlive, !!privateClickMeasurementAttributionTriggerData);
    878878    ASSERT(!isSynchronous());
    879879
     
    884884
    885885    NetworkSession* networkSession = nullptr;
    886     if (privateClickMeasurementConversion && (networkSession = m_connection->networkProcess().networkSession(sessionID())))
    887         networkSession->handlePrivateClickMeasurementConversion(WTFMove(*privateClickMeasurementConversion), request.url(), redirectRequest);
     886    if (privateClickMeasurementAttributionTriggerData && (networkSession = m_connection->networkProcess().networkSession(sessionID())))
     887        networkSession->handlePrivateClickMeasurementConversion(WTFMove(*privateClickMeasurementAttributionTriggerData), request.url(), redirectRequest);
    888888
    889889    // We send the request body separately because the ResourceRequest body normally does not get encoded when sent over IPC, as an optimization.
  • trunk/Source/WebKit/NetworkProcess/NetworkResourceLoader.h

    r269712 r270136  
    192192#endif
    193193
    194     void continueWillSendRedirectedRequest(WebCore::ResourceRequest&&, WebCore::ResourceRequest&& redirectRequest, WebCore::ResourceResponse&&, Optional<WebCore::PrivateClickMeasurement::Conversion>&&);
     194    void continueWillSendRedirectedRequest(WebCore::ResourceRequest&&, WebCore::ResourceRequest&& redirectRequest, WebCore::ResourceResponse&&, Optional<WebCore::PrivateClickMeasurement::AttributionTriggerData>&&);
    195195    void didFinishWithRedirectResponse(WebCore::ResourceRequest&&, WebCore::ResourceRequest&& redirectRequest, WebCore::ResourceResponse&&);
    196196    WebCore::ResourceResponse sanitizeResponseIfPossible(WebCore::ResourceResponse&&, WebCore::ResourceResponse::SanitizationType);
     
    203203    void logSlowCacheRetrieveIfNeeded(const NetworkCache::Cache::RetrieveInfo&);
    204204
    205     void handlePrivateClickMeasurementConversion(WebCore::PrivateClickMeasurement::Conversion&&, const URL&, const WebCore::ResourceRequest&);
     205    void handlePrivateClickMeasurementConversion(WebCore::PrivateClickMeasurement::AttributionTriggerData&&, const URL&, const WebCore::ResourceRequest&);
    206206
    207207    Optional<Seconds> validateCacheEntryForMaxAgeCapValidation(const WebCore::ResourceRequest&, const WebCore::ResourceRequest& redirectRequest, const WebCore::ResourceResponse&);
  • trunk/Source/WebKit/NetworkProcess/NetworkSession.cpp

    r269712 r270136  
    9191    , m_standaloneApplicationDomain(parameters.resourceLoadStatisticsParameters.standaloneApplicationDomain)
    9292#endif
    93     , m_privateClickMeasurement(makeUniqueRef<PrivateClickMeasurementManager>(networkProcess, parameters.sessionID))
     93    , m_privateClickMeasurement(makeUniqueRef<PrivateClickMeasurementManager>(*this, networkProcess, parameters.sessionID))
    9494    , m_testSpeedMultiplier(parameters.testSpeedMultiplier)
    9595    , m_allowsServerPreconnect(parameters.allowsServerPreconnect)
     
    187187        m_resourceLoadStatistics->setPrevalentResourceForDebugMode(m_resourceLoadStatisticsManualPrevalentResource, [] { });
    188188    forwardResourceLoadStatisticsSettings();
     189}
     190
     191void NetworkSession::firePrivateClickMeasurementTimerImmediately()
     192{
     193    m_privateClickMeasurement->startTimer(0_s);
    189194}
    190195
     
    306311void NetworkSession::storePrivateClickMeasurement(WebCore::PrivateClickMeasurement&& privateClickMeasurement)
    307312{
    308     m_privateClickMeasurement->storeUnconverted(WTFMove(privateClickMeasurement));
    309 }
    310 
    311 void NetworkSession::handlePrivateClickMeasurementConversion(PrivateClickMeasurement::Conversion&& conversion, const URL& requestURL, const WebCore::ResourceRequest& redirectRequest)
    312 {
    313     m_privateClickMeasurement->handleConversion(WTFMove(conversion), requestURL, redirectRequest);
     313    m_privateClickMeasurement->storeUnattributed(WTFMove(privateClickMeasurement));
     314}
     315
     316void NetworkSession::handlePrivateClickMeasurementConversion(PrivateClickMeasurement::AttributionTriggerData&& attributionTriggerData, const URL& requestURL, const WebCore::ResourceRequest& redirectRequest)
     317{
     318    m_privateClickMeasurement->handleAttribution(WTFMove(attributionTriggerData), requestURL, redirectRequest);
    314319}
    315320
     
    334339}
    335340
     341void NetworkSession::markAttributedPrivateClickMeasurementsAsExpiredForTesting(CompletionHandler<void()>&& completionHandler)
     342{
     343    m_privateClickMeasurement->markAttributedPrivateClickMeasurementsAsExpiredForTesting(WTFMove(completionHandler));
     344}
     345
    336346void NetworkSession::setPrivateClickMeasurementConversionURLForTesting(URL&& url)
    337347{
     
    341351void NetworkSession::markPrivateClickMeasurementsAsExpiredForTesting()
    342352{
    343     m_privateClickMeasurement->markAllUnconvertedAsExpiredForTesting();
     353    m_privateClickMeasurement->markAllUnattributedAsExpiredForTesting();
    344354}
    345355
  • trunk/Source/WebKit/NetworkProcess/NetworkSession.h

    r269712 r270136  
    118118#endif
    119119    void storePrivateClickMeasurement(WebCore::PrivateClickMeasurement&&);
    120     void handlePrivateClickMeasurementConversion(WebCore::PrivateClickMeasurement::Conversion&&, const URL& requestURL, const WebCore::ResourceRequest& redirectRequest);
     120    void handlePrivateClickMeasurementConversion(WebCore::PrivateClickMeasurement::AttributionTriggerData&&, const URL& requestURL, const WebCore::ResourceRequest& redirectRequest);
    121121    void dumpPrivateClickMeasurement(CompletionHandler<void(String)>&&);
    122122    void clearPrivateClickMeasurement();
    123123    void clearPrivateClickMeasurementForRegistrableDomain(WebCore::RegistrableDomain&&);
    124124    void setPrivateClickMeasurementOverrideTimerForTesting(bool value);
     125    void markAttributedPrivateClickMeasurementsAsExpiredForTesting(CompletionHandler<void()>&&);
    125126    void setPrivateClickMeasurementConversionURLForTesting(URL&&);
    126127    void markPrivateClickMeasurementsAsExpiredForTesting();
     128    void firePrivateClickMeasurementTimerImmediately();
    127129
    128130    void addKeptAliveLoad(Ref<NetworkResourceLoader>&&);
  • trunk/Source/WebKit/NetworkProcess/PrivateClickMeasurementManager.cpp

    r269712 r270136  
    4343using namespace WebCore;
    4444
    45 using Source = PrivateClickMeasurement::Source;
    46 using Destination = PrivateClickMeasurement::Destination;
    47 using DestinationMap = HashMap<Destination, PrivateClickMeasurement>;
    48 using Conversion = PrivateClickMeasurement::Conversion;
     45using SourceSite = PrivateClickMeasurement::SourceSite;
     46using AttributeOnSite = PrivateClickMeasurement::AttributeOnSite;
     47using AttributionTriggerData = PrivateClickMeasurement::AttributionTriggerData;
    4948
    5049constexpr Seconds debugModeSecondsUntilSend { 60_s };
    5150
    52 void PrivateClickMeasurementManager::storeUnconverted(PrivateClickMeasurement&& attribution)
     51PrivateClickMeasurementManager::PrivateClickMeasurementManager(NetworkSession& networkSession, NetworkProcess& networkProcess, PAL::SessionID sessionID)
     52    : m_firePendingAttributionRequestsTimer(*this, &PrivateClickMeasurementManager::firePendingAttributionRequests)
     53    , m_networkSession(makeWeakPtr(networkSession))
     54    , m_networkProcess(networkProcess)
     55    , m_sessionID(sessionID)
     56    , m_pingLoadFunction([](NetworkResourceLoadParameters&& params, CompletionHandler<void(const WebCore::ResourceError&, const WebCore::ResourceResponse&)>&& completionHandler) {
     57        UNUSED_PARAM(params);
     58        completionHandler(WebCore::ResourceError(), WebCore::ResourceResponse());
     59    })
     60{
     61    // We should send any pending attributions on session-start in case their
     62    // send delay has expired while the session was closed. Waiting 5 seconds accounts for the
     63    // delay in database startup.
     64    startTimer(5_s);
     65}
     66
     67void PrivateClickMeasurementManager::storeUnattributed(PrivateClickMeasurement&& attribution)
    5368{
    5469    clearExpired();
     
    5974    }
    6075
    61     m_unconvertedPrivateClickMeasurementMap.set(std::make_pair(attribution.source(), attribution.destination()), WTFMove(attribution));
    62 }
    63 
    64 void PrivateClickMeasurementManager::handleConversion(Conversion&& conversion, const URL& requestURL, const WebCore::ResourceRequest& redirectRequest)
     76    if (auto* resourceLoadStatistics = m_networkSession->resourceLoadStatistics())
     77        resourceLoadStatistics->insertPrivateClickMeasurement(WTFMove(attribution), PrivateClickMeasurementAttributionType::Unattributed);
     78}
     79
     80void PrivateClickMeasurementManager::handleAttribution(AttributionTriggerData&& attributionTriggerData, const URL& requestURL, const WebCore::ResourceRequest& redirectRequest)
    6581{
    6682    if (m_sessionID.isEphemeral())
     
    7288    if (!redirectDomain.matches(requestURL)) {
    7389        if (UNLIKELY(debugModeEnabled())) {
    74             RELEASE_LOG_INFO(PrivateClickMeasurement, "Conversion was not accepted because the HTTP redirect was not same-site.");
    75             m_networkProcess->broadcastConsoleMessage(m_sessionID, MessageSource::PrivateClickMeasurement, MessageLevel::Error, "[Private Click Measurement] Conversion was not accepted because the HTTP redirect was not same-site."_s);
     90            RELEASE_LOG_INFO(PrivateClickMeasurement, "Attribution was not accepted because the HTTP redirect was not same-site.");
     91            m_networkProcess->broadcastConsoleMessage(m_sessionID, MessageSource::PrivateClickMeasurement, MessageLevel::Error, "[Private Click Measurement] Attribution was not accepted because the HTTP redirect was not same-site."_s);
    7692        }
    7793        return;
     
    8096    if (redirectDomain.matches(firstPartyURL)) {
    8197        if (UNLIKELY(debugModeEnabled())) {
    82             RELEASE_LOG_INFO(PrivateClickMeasurement, "Conversion was not accepted because it was requested in an HTTP redirect that is same-site as the first-party.");
    83             m_networkProcess->broadcastConsoleMessage(m_sessionID, MessageSource::PrivateClickMeasurement, MessageLevel::Error, "[Private Click Measurement] Conversion was not accepted because it was requested in an HTTP redirect that is same-site as the first-party."_s);
    84         }
    85         return;
    86     }
    87 
    88     convert(PrivateClickMeasurement::Source { WTFMove(redirectDomain) }, PrivateClickMeasurement::Destination { firstPartyURL }, WTFMove(conversion));
     98            RELEASE_LOG_INFO(PrivateClickMeasurement, "Attribution was not accepted because it was requested in an HTTP redirect that is same-site as the first-party.");
     99            m_networkProcess->broadcastConsoleMessage(m_sessionID, MessageSource::PrivateClickMeasurement, MessageLevel::Error, "[Private Click Measurement] Attribution was not accepted because it was requested in an HTTP redirect that is same-site as the first-party."_s);
     100        }
     101        return;
     102    }
     103
     104    attribute(SourceSite { WTFMove(redirectDomain) }, AttributeOnSite { firstPartyURL }, WTFMove(attributionTriggerData));
    89105}
    90106
    91107void PrivateClickMeasurementManager::startTimer(Seconds seconds)
    92108{
    93     m_firePendingConversionRequestsTimer.startOneShot(m_isRunningTest ? 0_s : seconds);
    94 }
    95 
    96 void PrivateClickMeasurementManager::convert(const Source& source, const Destination& destination, Conversion&& conversion)
    97 {
    98     clearExpired();
    99 
    100     if (!conversion.isValid()) {
    101         if (UNLIKELY(debugModeEnabled())) {
    102             RELEASE_LOG_INFO(PrivateClickMeasurement, "Got an invalid conversion.");
    103             m_networkProcess->broadcastConsoleMessage(m_sessionID, MessageSource::PrivateClickMeasurement, MessageLevel::Error, "[Private Click Measurement] Got an invalid conversion."_s);
    104         }
    105         return;
    106     }
    107 
    108     auto conversionData = conversion.data;
    109     auto conversionPriority = conversion.priority;
    110 
    111     if (UNLIKELY(debugModeEnabled())) {
    112         RELEASE_LOG_INFO(PrivateClickMeasurement, "Got a conversion with conversion data: %{public}u and priority: %{public}u.", conversionData, conversionPriority);
    113         m_networkProcess->broadcastConsoleMessage(m_sessionID, MessageSource::PrivateClickMeasurement, MessageLevel::Info, makeString("[Private Click Measurement] Got a conversion with conversion data: '"_s, conversionData, "' and priority: '"_s, conversionPriority, "'."_s));
    114     }
    115 
    116     auto secondsUntilSend = Seconds::infinity();
    117 
    118     auto pair = std::make_pair(source, destination);
    119     auto previouslyUnconvertedAttribution = m_unconvertedPrivateClickMeasurementMap.take(pair);
    120     auto previouslyConvertedAttributionIter = m_convertedPrivateClickMeasurementMap.find(pair);
    121 
    122     if (!previouslyUnconvertedAttribution.isEmpty()) {
    123         // Always convert the pending attribution and remove it from the unconverted map.
    124         if (auto optionalSecondsUntilSend = previouslyUnconvertedAttribution.convertAndGetEarliestTimeToSend(WTFMove(conversion))) {
    125             secondsUntilSend = *optionalSecondsUntilSend;
    126             ASSERT(secondsUntilSend != Seconds::infinity());
    127 
    128             if (UNLIKELY(debugModeEnabled())) {
    129                 RELEASE_LOG_INFO(PrivateClickMeasurement, "Converted a stored ad click with conversion data: %{public}u and priority: %{public}u.", conversionData, conversionPriority);
    130                 m_networkProcess->broadcastConsoleMessage(m_sessionID, MessageSource::PrivateClickMeasurement, MessageLevel::Info, makeString("[Private Click Measurement] Converted a stored ad click with conversion data: '"_s, conversionData, "' and priority: '"_s, conversionPriority, "'."_s));
    131             }
    132         }
    133 
    134         if (previouslyConvertedAttributionIter == m_convertedPrivateClickMeasurementMap.end())
    135             m_convertedPrivateClickMeasurementMap.add(pair, WTFMove(previouslyUnconvertedAttribution));
    136         else if (previouslyUnconvertedAttribution.hasHigherPriorityThan(previouslyConvertedAttributionIter->value)) {
    137             // If the newly converted attribution has higher priority, replace the old one.
    138             m_convertedPrivateClickMeasurementMap.set(pair, WTFMove(previouslyUnconvertedAttribution));
    139 
    140             if (UNLIKELY(debugModeEnabled())) {
    141                 RELEASE_LOG_INFO(PrivateClickMeasurement, "Replaced a previously converted ad click with a new one with conversion data: %{public}u and priority: %{public}u because it had higher priority.", conversionData, conversionPriority);
    142                 m_networkProcess->broadcastConsoleMessage(m_sessionID, MessageSource::PrivateClickMeasurement, MessageLevel::Info, makeString("[Private Click Measurement] Replaced a previously converted ad click with a new one with conversion data: '"_s, conversionData, "' and priority: '"_s, conversionPriority, "' because it had higher priority."_s));
    143             }
    144         }
    145     } else if (previouslyConvertedAttributionIter != m_convertedPrivateClickMeasurementMap.end()) {
    146         // If we have no newly converted attribution, re-convert the old one to respect the new priority.
    147         if (auto optionalSecondsUntilSend = previouslyConvertedAttributionIter->value.convertAndGetEarliestTimeToSend(WTFMove(conversion))) {
    148             secondsUntilSend = *optionalSecondsUntilSend;
    149             ASSERT(secondsUntilSend != Seconds::infinity());
    150 
    151             if (UNLIKELY(debugModeEnabled())) {
    152                 RELEASE_LOG_INFO(PrivateClickMeasurement, "Re-converted an ad click with a new one with conversion data: %{public}u and priority: %{public}u because it had higher priority.", conversionData, conversionPriority);
    153                 m_networkProcess->broadcastConsoleMessage(m_sessionID, MessageSource::PrivateClickMeasurement, MessageLevel::Info, makeString("[Private Click Measurement] Re-converted an ad click with a new one with conversion data: '"_s, conversionData, "' and priority: '"_s, conversionPriority, "'' because it had higher priority."_s));
    154             }
    155         }
    156     }
    157 
    158     if (secondsUntilSend == Seconds::infinity())
    159         return;
    160    
    161     if (m_firePendingConversionRequestsTimer.isActive() && m_firePendingConversionRequestsTimer.nextFireInterval() < secondsUntilSend)
    162         return;
    163 
    164     if (UNLIKELY(debugModeEnabled())) {
    165         RELEASE_LOG_INFO(PrivateClickMeasurement, "Setting timer for firing conversion requests to the debug mode timeout of %{public}f seconds where the regular timeout would have been %{public}f seconds.", debugModeSecondsUntilSend.seconds(), secondsUntilSend.seconds());
    166         m_networkProcess->broadcastConsoleMessage(m_sessionID, MessageSource::PrivateClickMeasurement, MessageLevel::Info, makeString("[Private Click Measurement] Setting timer for firing conversion requests to the debug mode timeout of "_s, debugModeSecondsUntilSend.seconds(), " seconds where the regular timeout would have been "_s, secondsUntilSend.seconds(), " seconds."_s));
    167         secondsUntilSend = debugModeSecondsUntilSend;
    168     }
    169 
    170     startTimer(secondsUntilSend);
     109    m_firePendingAttributionRequestsTimer.startOneShot(m_isRunningTest ? 0_s : seconds);
     110}
     111
     112void PrivateClickMeasurementManager::attribute(const SourceSite& sourceSite, const AttributeOnSite& attributeOnSite, AttributionTriggerData&& attributionTriggerData)
     113{
     114    if (auto* resourceLoadStatistics = m_networkSession->resourceLoadStatistics()) {
     115        resourceLoadStatistics->attributePrivateClickMeasurement(sourceSite, attributeOnSite, WTFMove(attributionTriggerData), [this] (auto optionalSecondsUntilSend) {
     116            if (optionalSecondsUntilSend) {
     117                auto secondsUntilSend = *optionalSecondsUntilSend;
     118                if (m_firePendingAttributionRequestsTimer.isActive() && m_firePendingAttributionRequestsTimer.nextFireInterval() < secondsUntilSend)
     119                    return;
     120
     121                if (UNLIKELY(debugModeEnabled())) {
     122                    RELEASE_LOG_INFO(PrivateClickMeasurement, "Setting timer for firing attribution requests to the debug mode timeout of %{public}f seconds where the regular timeout would have been %{public}f seconds.", debugModeSecondsUntilSend.seconds(), secondsUntilSend.seconds());
     123                    m_networkProcess->broadcastConsoleMessage(m_sessionID, MessageSource::PrivateClickMeasurement, MessageLevel::Info, makeString("[Private Click Measurement] Setting timer for firing attribution requests to the debug mode timeout of "_s, debugModeSecondsUntilSend.seconds(), " seconds where the regular timeout would have been "_s, secondsUntilSend.seconds(), " seconds."_s));
     124                        secondsUntilSend = debugModeSecondsUntilSend;
     125                }
     126                startTimer(secondsUntilSend);
     127            }
     128        });
     129    }
    171130}
    172131
    173132void PrivateClickMeasurementManager::fireConversionRequest(const PrivateClickMeasurement& attribution)
    174133{
    175     auto conversionURL = m_conversionBaseURLForTesting ? *m_conversionBaseURLForTesting : attribution.reportURL();
    176     if (conversionURL.isEmpty() || !conversionURL.isValid())
    177         return;
    178 
    179     ResourceRequest request { WTFMove(conversionURL) };
     134    auto attributionURL = m_attributionBaseURLForTesting ? *m_attributionBaseURLForTesting : attribution.reportURL();
     135    if (attributionURL.isEmpty() || !attributionURL.isValid())
     136        return;
     137
     138    ResourceRequest request { WTFMove(attributionURL) };
    180139   
    181140    request.setHTTPMethod("POST"_s);
     
    201160
    202161    if (UNLIKELY(debugModeEnabled())) {
    203         RELEASE_LOG_INFO(PrivateClickMeasurement, "About to fire an attribution request for a conversion.");
    204         m_networkProcess->broadcastConsoleMessage(m_sessionID, MessageSource::PrivateClickMeasurement, MessageLevel::Error, "[Private Click Measurement] About to fire an attribution request for a conversion."_s);
     162        RELEASE_LOG_INFO(PrivateClickMeasurement, "About to fire an attribution request.");
     163        m_networkProcess->broadcastConsoleMessage(m_sessionID, MessageSource::PrivateClickMeasurement, MessageLevel::Error, "[Private Click Measurement] About to fire an attribution request."_s);
    205164    }
    206165
     
    222181}
    223182
    224 void PrivateClickMeasurementManager::firePendingConversionRequests()
    225 {
    226     auto nextTimeToFire = Seconds::infinity();
    227     for (auto& attribution : m_convertedPrivateClickMeasurementMap.values()) {
    228         if (attribution.wasConversionSent()) {
    229             ASSERT_NOT_REACHED();
    230             continue;
    231         }
    232         auto earliestTimeToSend = attribution.earliestTimeToSend();
    233         if (!earliestTimeToSend) {
    234             ASSERT_NOT_REACHED();
    235             continue;
    236         }
    237 
    238         auto now = WallTime::now();
    239         if (*earliestTimeToSend <= now || m_isRunningTest || debugModeEnabled()) {
    240             fireConversionRequest(attribution);
    241             attribution.markConversionAsSent();
    242             continue;
    243         }
    244 
    245         auto seconds = *earliestTimeToSend - now;
    246         nextTimeToFire = std::min(nextTimeToFire, seconds);
    247     }
    248 
    249     m_convertedPrivateClickMeasurementMap.removeIf([](auto& keyAndValue) {
    250         return keyAndValue.value.wasConversionSent();
     183void PrivateClickMeasurementManager::clearSentAttributions(Vector<PrivateClickMeasurement>&& sentConversions)
     184{
     185    if (auto* resourceLoadStatistics = m_networkSession->resourceLoadStatistics())
     186        resourceLoadStatistics->clearSentAttributions(WTFMove(sentConversions));
     187}
     188
     189void PrivateClickMeasurementManager::updateTimerLastFired()
     190{
     191    if (auto* resourceLoadStatistics = m_networkSession->resourceLoadStatistics())
     192        resourceLoadStatistics->updateTimerLastFired();
     193}
     194
     195void PrivateClickMeasurementManager::firePendingAttributionRequests()
     196{
     197    auto* resourceLoadStatistics = m_networkSession->resourceLoadStatistics();
     198    if (!resourceLoadStatistics)
     199        return;
     200
     201    resourceLoadStatistics->allAttributedPrivateClickMeasurement([this] (auto&& attributions) {
     202        auto nextTimeToFire = Seconds::infinity();
     203        Vector<PrivateClickMeasurement> sentAttributions;
     204       
     205        for (auto& attribution : attributions) {
     206            auto earliestTimeToSend = attribution.earliestTimeToSend();
     207            if (!earliestTimeToSend) {
     208                ASSERT_NOT_REACHED();
     209                continue;
     210            }
     211
     212            auto now = WallTime::now();
     213            if (*earliestTimeToSend <= now || m_isRunningTest || debugModeEnabled()) {
     214                fireConversionRequest(attribution);
     215                sentAttributions.append(WTFMove(attribution));
     216                continue;
     217            }
     218
     219            auto seconds = *earliestTimeToSend - now;
     220            nextTimeToFire = std::min(nextTimeToFire, seconds);
     221        }
     222       
     223        clearSentAttributions(WTFMove(sentAttributions));
     224        updateTimerLastFired();
     225
     226        if (nextTimeToFire < Seconds::infinity())
     227            startTimer(nextTimeToFire);
    251228    });
    252 
    253     if (nextTimeToFire < Seconds::infinity())
    254         startTimer(nextTimeToFire);
    255229}
    256230
    257231void PrivateClickMeasurementManager::clear()
    258232{
    259     m_firePendingConversionRequestsTimer.stop();
    260     m_unconvertedPrivateClickMeasurementMap.clear();
    261     m_convertedPrivateClickMeasurementMap.clear();
     233    m_firePendingAttributionRequestsTimer.stop();
     234
     235    if (auto* resourceLoadStatistics = m_networkSession->resourceLoadStatistics())
     236        resourceLoadStatistics->clearPrivateClickMeasurement();
    262237}
    263238
    264239void PrivateClickMeasurementManager::clearForRegistrableDomain(const RegistrableDomain& domain)
    265240{
    266     m_unconvertedPrivateClickMeasurementMap.removeIf([&domain](auto& keyAndValue) {
    267         return keyAndValue.key.first.registrableDomain == domain || keyAndValue.key.second.registrableDomain == domain;
    268     });
    269 
    270     m_convertedPrivateClickMeasurementMap.removeIf([&domain](auto& keyAndValue) {
    271         return keyAndValue.key.first.registrableDomain == domain || keyAndValue.key.second.registrableDomain == domain;
    272     });
     241    if (auto* resourceLoadStatistics = m_networkSession->resourceLoadStatistics())
     242        resourceLoadStatistics->clearPrivateClickMeasurementForRegistrableDomain(domain);
    273243}
    274244
    275245void PrivateClickMeasurementManager::clearExpired()
    276246{
    277     m_unconvertedPrivateClickMeasurementMap.removeIf([](auto& keyAndValue) {
    278         return keyAndValue.value.hasExpired();
    279     });
     247    if (auto* resourceLoadStatistics = m_networkSession->resourceLoadStatistics())
     248        resourceLoadStatistics->clearExpiredPrivateClickMeasurement();
    280249}
    281250
    282251void PrivateClickMeasurementManager::toString(CompletionHandler<void(String)>&& completionHandler) const
    283252{
    284     if (m_unconvertedPrivateClickMeasurementMap.isEmpty() && m_convertedPrivateClickMeasurementMap.isEmpty())
    285         return completionHandler("\nNo stored Private Click Measurement data.\n"_s);
    286 
    287     unsigned unconvertedAttributionNumber = 0;
    288     StringBuilder builder;
    289     for (auto& attribution : m_unconvertedPrivateClickMeasurementMap.values()) {
    290         if (!unconvertedAttributionNumber)
    291             builder.appendLiteral("Unconverted Private Click Measurements:\n");
    292         else
    293             builder.append('\n');
    294         builder.appendLiteral("WebCore::PrivateClickMeasurement ");
    295         builder.appendNumber(++unconvertedAttributionNumber);
    296         builder.append('\n');
    297         builder.append(attribution.toString());
    298 }
    299 
    300     unsigned convertedAttributionNumber = 0;
    301     for (auto& attribution : m_convertedPrivateClickMeasurementMap.values()) {
    302         if (unconvertedAttributionNumber)
    303             builder.append('\n');
    304         if (!convertedAttributionNumber)
    305             builder.appendLiteral("Converted Private Click Measurements:\n");
    306         else
    307             builder.append('\n');
    308         builder.appendLiteral("WebCore::PrivateClickMeasurement ");
    309         builder.appendNumber(++convertedAttributionNumber + unconvertedAttributionNumber);
    310         builder.append('\n');
    311         builder.append(attribution.toString());
    312     }
    313 
    314     completionHandler(builder.toString());
     253    if (auto* resourceLoadStatistics = m_networkSession->resourceLoadStatistics())
     254        resourceLoadStatistics->privateClickMeasurementToString(WTFMove(completionHandler));
    315255}
    316256
     
    318258{
    319259    if (testURL.isEmpty())
    320         m_conversionBaseURLForTesting = { };
     260        m_attributionBaseURLForTesting = { };
    321261    else
    322         m_conversionBaseURLForTesting = WTFMove(testURL);
    323 }
    324 
    325 void PrivateClickMeasurementManager::markAllUnconvertedAsExpiredForTesting()
    326 {
    327     for (auto& attribution : m_unconvertedPrivateClickMeasurementMap.values())
    328         attribution.markAsExpired();
     262        m_attributionBaseURLForTesting = WTFMove(testURL);
     263}
     264
     265void PrivateClickMeasurementManager::markAllUnattributedAsExpiredForTesting()
     266{
     267    if (auto* resourceLoadStatistics = m_networkSession->resourceLoadStatistics())
     268        resourceLoadStatistics->markAllUnattributedPrivateClickMeasurementAsExpiredForTesting();
    329269}
    330270
     
    334274}
    335275
     276void PrivateClickMeasurementManager::markAttributedPrivateClickMeasurementsAsExpiredForTesting(CompletionHandler<void()>&& completionHandler)
     277{
     278    if (auto* resourceLoadStatistics = m_networkSession->resourceLoadStatistics()) {
     279        resourceLoadStatistics->markAttributedPrivateClickMeasurementsAsExpiredForTesting(WTFMove(completionHandler));
     280        return;
     281    }
     282    completionHandler();
     283}
     284
    336285} // namespace WebKit
  • trunk/Source/WebKit/NetworkProcess/PrivateClickMeasurementManager.h

    r269712 r270136  
    4141namespace WebKit {
    4242
     43enum class PrivateClickMeasurementAttributionType : bool { Unattributed, Attributed };
     44
    4345class PrivateClickMeasurementManager : public CanMakeWeakPtr<PrivateClickMeasurementManager> {
    4446    WTF_MAKE_FAST_ALLOCATED;
     
    4749    using RegistrableDomain = WebCore::RegistrableDomain;
    4850    using PrivateClickMeasurement = WebCore::PrivateClickMeasurement;
    49     using Source = WebCore::PrivateClickMeasurement::Source;
    50     using Destination = WebCore::PrivateClickMeasurement::Destination;
    51     using Conversion = WebCore::PrivateClickMeasurement::Conversion;
     51    using SourceSite = WebCore::PrivateClickMeasurement::SourceSite;
     52    using AttributeOnSite = WebCore::PrivateClickMeasurement::AttributeOnSite;
     53    using AttributionTriggerData = WebCore::PrivateClickMeasurement::AttributionTriggerData;
    5254
    53     explicit PrivateClickMeasurementManager(NetworkProcess& networkProcess, PAL::SessionID sessionID)
    54         : m_firePendingConversionRequestsTimer(*this, &PrivateClickMeasurementManager::firePendingConversionRequests)
    55         , m_pingLoadFunction([](NetworkResourceLoadParameters&& params, CompletionHandler<void(const WebCore::ResourceError&, const WebCore::ResourceResponse&)>&& completionHandler) {
    56             UNUSED_PARAM(params);
    57             completionHandler(WebCore::ResourceError(), WebCore::ResourceResponse());
    58         })
    59         , m_networkProcess(networkProcess)
    60         , m_sessionID(sessionID)
    61     {
    62     }
     55    explicit PrivateClickMeasurementManager(NetworkSession&, NetworkProcess&, PAL::SessionID);
    6356
    64     void storeUnconverted(PrivateClickMeasurement&&);
    65     void handleConversion(Conversion&&, const URL& requestURL, const WebCore::ResourceRequest& redirectRequest);
     57    void storeUnattributed(PrivateClickMeasurement&&);
     58    void handleAttribution(AttributionTriggerData&&, const URL& requestURL, const WebCore::ResourceRequest& redirectRequest);
    6659    void clear();
    6760    void clearForRegistrableDomain(const RegistrableDomain&);
     
    7063    void setOverrideTimerForTesting(bool value) { m_isRunningTest = value; }
    7164    void setConversionURLForTesting(URL&&);
    72     void markAllUnconvertedAsExpiredForTesting();
     65    void markAllUnattributedAsExpiredForTesting();
     66    void markAttributedPrivateClickMeasurementsAsExpiredForTesting(CompletionHandler<void()>&&);
     67    void startTimer(Seconds);
    7368
    7469private:
    75     void startTimer(Seconds);
    76     void convert(const Source&, const Destination&, Conversion&&);
     70    void clearSentAttributions(Vector<PrivateClickMeasurement>&&);
     71    void attribute(const SourceSite&, const AttributeOnSite&, AttributionTriggerData&&);
    7772    void fireConversionRequest(const PrivateClickMeasurement&);
    78     void firePendingConversionRequests();
     73    void firePendingAttributionRequests();
    7974    void clearExpired();
    8075    bool debugModeEnabled() const;
     76    void updateTimerLastFired();
    8177
    82     HashMap<std::pair<Source, Destination>, PrivateClickMeasurement> m_unconvertedPrivateClickMeasurementMap;
    83     HashMap<std::pair<Source, Destination>, PrivateClickMeasurement> m_convertedPrivateClickMeasurementMap;
    84     WebCore::Timer m_firePendingConversionRequestsTimer;
    85     Function<void(NetworkResourceLoadParameters&&, CompletionHandler<void(const WebCore::ResourceError&, const WebCore::ResourceResponse&)>&&)> m_pingLoadFunction;
     78    WebCore::Timer m_firePendingAttributionRequestsTimer;
    8679    bool m_isRunningTest { false };
    87     Optional<URL> m_conversionBaseURLForTesting;
     80    Optional<URL> m_attributionBaseURLForTesting;
     81    WeakPtr<NetworkSession> m_networkSession;
    8882    Ref<NetworkProcess> m_networkProcess;
    8983    PAL::SessionID m_sessionID;
     84    Function<void(NetworkResourceLoadParameters&&, CompletionHandler<void(const WebCore::ResourceError&, const WebCore::ResourceResponse&)>&&)> m_pingLoadFunction;
    9085};
    9186   
  • trunk/Source/WebKit/UIProcess/API/C/WKPage.cpp

    r269810 r270136  
    28922892}
    28932893
     2894void WKPageMarkAttributedPrivateClickMeasurementsAsExpiredForTesting(WKPageRef page, WKPageMarkAttributedPrivateClickMeasurementsAsExpiredForTestingFunction callback, void* callbackContext)
     2895{
     2896    toImpl(page)->markAttributedPrivateClickMeasurementsAsExpiredForTesting([callbackContext, callback] () {
     2897        callback(callbackContext);
     2898    });
     2899}
     2900
     2901void WKPageSimulateResourceLoadStatisticsSessionRestart(WKPageRef page, WKPageSimulateResourceLoadStatisticsSessionRestartFunction callback, void* callbackContext)
     2902{
     2903    toImpl(page)->simulateResourceLoadStatisticsSessionRestart([callbackContext, callback] () {
     2904        callback(callbackContext);
     2905    });
     2906}
     2907
    28942908void WKPageSetPrivateClickMeasurementConversionURLForTesting(WKPageRef page, WKURLRef URLRef, WKPageSetPrivateClickMeasurementConversionURLForTestingFunction callback, void* callbackContext)
    28952909{
  • trunk/Source/WebKit/UIProcess/API/C/WKPagePrivate.h

    r269712 r270136  
    176176typedef void (*WKPageSetPrivateClickMeasurementOverrideTimerForTestingFunction)(void* functionContext);
    177177WK_EXPORT void WKPageSetPrivateClickMeasurementOverrideTimerForTesting(WKPageRef page, bool value, WKPageSetPrivateClickMeasurementOverrideTimerForTestingFunction callback, void* callbackContext);
     178typedef void (*WKPageMarkAttributedPrivateClickMeasurementsAsExpiredForTestingFunction)(void* functionContext);
     179WK_EXPORT void WKPageMarkAttributedPrivateClickMeasurementsAsExpiredForTesting(WKPageRef page, WKPageMarkAttributedPrivateClickMeasurementsAsExpiredForTestingFunction callback, void* callbackContext);
     180typedef void (*WKPageSimulateResourceLoadStatisticsSessionRestartFunction)(void* functionContext);
     181WK_EXPORT void WKPageSimulateResourceLoadStatisticsSessionRestart(WKPageRef page, WKPageSimulateResourceLoadStatisticsSessionRestartFunction callback, void* callbackContext);
    178182typedef void (*WKPageSetPrivateClickMeasurementConversionURLForTestingFunction)(void* functionContext);
    179183WK_EXPORT void WKPageSetPrivateClickMeasurementConversionURLForTesting(WKPageRef page, WKURLRef urlString, WKPageSetPrivateClickMeasurementConversionURLForTestingFunction callback, void* callbackContext);
  • trunk/Source/WebKit/UIProcess/WebPageProxy.cpp

    r270028 r270136  
    46614661            privateClickMeasurement = navigation->privateClickMeasurement();
    46624662        if (privateClickMeasurement) {
    4663             if (privateClickMeasurement->destination().matches(frame->url()))
     4663            if (privateClickMeasurement->attributeOnSite().matches(frame->url()))
    46644664                websiteDataStore().networkProcess().send(Messages::NetworkProcess::StorePrivateClickMeasurement(m_websiteDataStore->sessionID(), *privateClickMeasurement), 0);
    46654665        }
     
    99669966}
    99679967
     9968void WebPageProxy::markAttributedPrivateClickMeasurementsAsExpiredForTesting(CompletionHandler<void()>&& completionHandler)
     9969{
     9970    websiteDataStore().networkProcess().sendWithAsyncReply(Messages::NetworkProcess::MarkAttributedPrivateClickMeasurementsAsExpiredForTesting(m_websiteDataStore->sessionID()), WTFMove(completionHandler));
     9971}
     9972
     9973void WebPageProxy::simulateResourceLoadStatisticsSessionRestart(CompletionHandler<void()>&& completionHandler)
     9974{
     9975    websiteDataStore().networkProcess().sendWithAsyncReply(Messages::NetworkProcess::SimulateResourceLoadStatisticsSessionRestart(m_websiteDataStore->sessionID()), WTFMove(completionHandler));
     9976}
     9977
    99689978void WebPageProxy::setPrivateClickMeasurementConversionURLForTesting(const URL& url, CompletionHandler<void()>&& completionHandler)
    99699979{
  • trunk/Source/WebKit/UIProcess/WebPageProxy.h

    r269953 r270136  
    16891689    void clearPrivateClickMeasurement(CompletionHandler<void()>&&);
    16901690    void setPrivateClickMeasurementOverrideTimerForTesting(bool value, CompletionHandler<void()>&&);
     1691    void markAttributedPrivateClickMeasurementsAsExpiredForTesting(CompletionHandler<void()>&&);
     1692    void simulateResourceLoadStatisticsSessionRestart(CompletionHandler<void()>&&);
    16911693    void setPrivateClickMeasurementConversionURLForTesting(const URL&, CompletionHandler<void()>&&);
    16921694    void markPrivateClickMeasurementsAsExpiredForTesting(CompletionHandler<void()>&&);
  • trunk/Tools/ChangeLog

    r270132 r270136  
     12020-11-20  Kate Cheney  <katherine_cheney@apple.com>
     2
     3        PCM: Persist pending ad clicks and attributions so they can survive browser restart
     4        https://bugs.webkit.org/show_bug.cgi?id=219134
     5        <rdar://problem/70470129>
     6
     7        Reviewed by John Wilander.
     8
     9        Add support for testing of expired ad-clicks and attributions.
     10        Update names after Private Click Measurement standards discussions.
     11
     12        * TestWebKitAPI/Tests/WebCore/PrivateClickMeasurement.cpp:
     13        (TestWebKitAPI::TEST):
     14        * WebKitTestRunner/InjectedBundle/Bindings/TestRunner.idl:
     15        * WebKitTestRunner/InjectedBundle/TestRunner.cpp:
     16        (WTR::TestRunner::markAttributedPrivateClickMeasurementsAsExpiredForTesting):
     17        (WTR::TestRunner::simulateResourceLoadStatisticsSessionRestart):
     18        * WebKitTestRunner/InjectedBundle/TestRunner.h:
     19        * WebKitTestRunner/TestController.cpp:
     20        (WTR::TestController::markAttributedPrivateClickMeasurementsAsExpiredForTesting):
     21        (WTR::TestController::simulateResourceLoadStatisticsSessionRestart):
     22        * WebKitTestRunner/TestController.h:
     23        * WebKitTestRunner/TestInvocation.cpp:
     24        (WTR::TestInvocation::didReceiveSynchronousMessageFromInjectedBundle):
     25
    1262020-11-20  Geoffrey Garen  <ggaren@apple.com>
    227
  • trunk/Tools/TestWebKitAPI/Tests/WebCore/PrivateClickMeasurement.cpp

    r269886 r270136  
    4444TEST(PrivateClickMeasurement, ValidMinValues)
    4545{
    46     PrivateClickMeasurement attribution { PrivateClickMeasurement::Campaign(min6BitValue), PrivateClickMeasurement::Source { webKitURL }, PrivateClickMeasurement::Destination { exampleURL } };
    47     attribution.convertAndGetEarliestTimeToSend(PrivateClickMeasurement::Conversion(min6BitValue, PrivateClickMeasurement::Priority(min6BitValue)));
     46    PrivateClickMeasurement attribution { PrivateClickMeasurement::SourceID(min6BitValue), PrivateClickMeasurement::SourceSite { webKitURL }, PrivateClickMeasurement::AttributeOnSite { exampleURL } };
     47    attribution.attributeAndGetEarliestTimeToSend(PrivateClickMeasurement::AttributionTriggerData(min6BitValue, PrivateClickMeasurement::Priority(min6BitValue)));
    4848
    4949    auto attributionURL = attribution.reportURL();
     
    5656TEST(PrivateClickMeasurement, ValidMidValues)
    5757{
    58     PrivateClickMeasurement attribution { PrivateClickMeasurement::Campaign((uint32_t)12), PrivateClickMeasurement::Source { webKitURL }, PrivateClickMeasurement::Destination { exampleURL } };
    59     attribution.convertAndGetEarliestTimeToSend(PrivateClickMeasurement::Conversion((uint32_t)44, PrivateClickMeasurement::Priority((uint32_t)22)));
     58    PrivateClickMeasurement attribution { PrivateClickMeasurement::SourceID((uint32_t)12), PrivateClickMeasurement::SourceSite { webKitURL }, PrivateClickMeasurement::AttributeOnSite { exampleURL } };
     59    attribution.attributeAndGetEarliestTimeToSend(PrivateClickMeasurement::AttributionTriggerData((uint32_t)44, PrivateClickMeasurement::Priority((uint32_t)22)));
    6060
    6161    auto attributionURL = attribution.reportURL();
     
    6868TEST(PrivateClickMeasurement, ValidMaxValues)
    6969{
    70     PrivateClickMeasurement attribution { PrivateClickMeasurement::Campaign(PrivateClickMeasurement::MaxEntropy), PrivateClickMeasurement::Source { webKitURL }, PrivateClickMeasurement::Destination { exampleURL } };
    71     attribution.convertAndGetEarliestTimeToSend(PrivateClickMeasurement::Conversion(PrivateClickMeasurement::MaxEntropy, PrivateClickMeasurement::Priority(PrivateClickMeasurement::MaxEntropy)));
     70    PrivateClickMeasurement attribution { PrivateClickMeasurement::SourceID(PrivateClickMeasurement::MaxEntropy), PrivateClickMeasurement::SourceSite { webKitURL }, PrivateClickMeasurement::AttributeOnSite { exampleURL } };
     71    attribution.attributeAndGetEarliestTimeToSend(PrivateClickMeasurement::AttributionTriggerData(PrivateClickMeasurement::MaxEntropy, PrivateClickMeasurement::Priority(PrivateClickMeasurement::MaxEntropy)));
    7272
    7373    auto attributionURL = attribution.reportURL();
     
    8080TEST(PrivateClickMeasurement, EarliestTimeToSendAttributionMinimumDelay)
    8181{
    82     PrivateClickMeasurement attribution { PrivateClickMeasurement::Campaign(PrivateClickMeasurement::MaxEntropy), PrivateClickMeasurement::Source { webKitURL }, PrivateClickMeasurement::Destination { exampleURL } };
     82    PrivateClickMeasurement attribution { PrivateClickMeasurement::SourceID(PrivateClickMeasurement::MaxEntropy), PrivateClickMeasurement::SourceSite { webKitURL }, PrivateClickMeasurement::AttributeOnSite { exampleURL } };
    8383    auto now = WallTime::now();
    84     attribution.convertAndGetEarliestTimeToSend(PrivateClickMeasurement::Conversion(PrivateClickMeasurement::MaxEntropy, PrivateClickMeasurement::Priority(PrivateClickMeasurement::MaxEntropy)));
     84    attribution.attributeAndGetEarliestTimeToSend(PrivateClickMeasurement::AttributionTriggerData(PrivateClickMeasurement::MaxEntropy, PrivateClickMeasurement::Priority(PrivateClickMeasurement::MaxEntropy)));
    8585    auto earliestTimeToSend = attribution.earliestTimeToSend();
    8686    ASSERT_TRUE(earliestTimeToSend);
     
    9191{
    9292    const URL conversionURLWithoutPriority { { }, "https://webkit.org/.well-known/private-click-measurement/22"_s };
    93     auto optionalConversion = PrivateClickMeasurement::parseConversionRequest(conversionURLWithoutPriority);
     93    auto optionalConversion = PrivateClickMeasurement::parseAttributionRequest(conversionURLWithoutPriority);
    9494    ASSERT_TRUE(optionalConversion);
    9595    ASSERT_EQ(optionalConversion->data, (uint32_t)22);
    9696
    9797    const URL conversionURLWithoutPriorityMaxEntropy { { }, "https://webkit.org/.well-known/private-click-measurement/63"_s };
    98     optionalConversion = PrivateClickMeasurement::parseConversionRequest(conversionURLWithoutPriorityMaxEntropy);
     98    optionalConversion = PrivateClickMeasurement::parseAttributionRequest(conversionURLWithoutPriorityMaxEntropy);
    9999    ASSERT_TRUE(optionalConversion);
    100100    ASSERT_EQ(optionalConversion->data, (uint32_t)63);
    101101   
    102102    const URL conversionURLWithoutPriorityAndLeadingZero { { }, "https://webkit.org/.well-known/private-click-measurement/02"_s };
    103     optionalConversion = PrivateClickMeasurement::parseConversionRequest(conversionURLWithoutPriorityAndLeadingZero);
     103    optionalConversion = PrivateClickMeasurement::parseAttributionRequest(conversionURLWithoutPriorityAndLeadingZero);
    104104    ASSERT_TRUE(optionalConversion);
    105105    ASSERT_EQ(optionalConversion->data, (uint32_t)2);
    106106
    107107    const URL conversionURLWithPriority { { }, "https://webkit.org/.well-known/private-click-measurement/22/12"_s };
    108     optionalConversion = PrivateClickMeasurement::parseConversionRequest(conversionURLWithPriority);
     108    optionalConversion = PrivateClickMeasurement::parseAttributionRequest(conversionURLWithPriority);
    109109    ASSERT_TRUE(optionalConversion);
    110110    ASSERT_EQ(optionalConversion->data, (uint32_t)22);
     
    112112
    113113    const URL conversionURLWithPriorityMaxEntropy { { }, "https://webkit.org/.well-known/private-click-measurement/63/63"_s };
    114     optionalConversion = PrivateClickMeasurement::parseConversionRequest(conversionURLWithPriorityMaxEntropy);
     114    optionalConversion = PrivateClickMeasurement::parseAttributionRequest(conversionURLWithPriorityMaxEntropy);
    115115    ASSERT_TRUE(optionalConversion);
    116116    ASSERT_EQ(optionalConversion->data, (uint32_t)63);
     
    118118   
    119119    const URL conversionURLWithPriorityAndLeadingZero { { }, "https://webkit.org/.well-known/private-click-measurement/22/02"_s };
    120     optionalConversion = PrivateClickMeasurement::parseConversionRequest(conversionURLWithPriorityAndLeadingZero);
     120    optionalConversion = PrivateClickMeasurement::parseAttributionRequest(conversionURLWithPriorityAndLeadingZero);
    121121    ASSERT_TRUE(optionalConversion);
    122122    ASSERT_EQ(optionalConversion->data, (uint32_t)22);
     
    128128TEST(PrivateClickMeasurement, InvalidCampaignId)
    129129{
    130     PrivateClickMeasurement attribution { PrivateClickMeasurement::Campaign(PrivateClickMeasurement::MaxEntropy + 1), PrivateClickMeasurement::Source { webKitURL }, PrivateClickMeasurement::Destination { exampleURL } };
    131     attribution.convertAndGetEarliestTimeToSend(PrivateClickMeasurement::Conversion(PrivateClickMeasurement::MaxEntropy, PrivateClickMeasurement::Priority(PrivateClickMeasurement::MaxEntropy)));
     130    PrivateClickMeasurement attribution { PrivateClickMeasurement::SourceID(PrivateClickMeasurement::MaxEntropy + 1), PrivateClickMeasurement::SourceSite { webKitURL }, PrivateClickMeasurement::AttributeOnSite { exampleURL } };
     131    attribution.attributeAndGetEarliestTimeToSend(PrivateClickMeasurement::AttributionTriggerData(PrivateClickMeasurement::MaxEntropy, PrivateClickMeasurement::Priority(PrivateClickMeasurement::MaxEntropy)));
    132132
    133133    ASSERT_TRUE(attribution.reportURL().isEmpty());
     
    136136TEST(PrivateClickMeasurement, InvalidSourceHost)
    137137{
    138     PrivateClickMeasurement attribution { PrivateClickMeasurement::Campaign(PrivateClickMeasurement::MaxEntropy), PrivateClickMeasurement::Source { emptyURL }, PrivateClickMeasurement::Destination { exampleURL } };
    139     attribution.convertAndGetEarliestTimeToSend(PrivateClickMeasurement::Conversion(PrivateClickMeasurement::MaxEntropy, PrivateClickMeasurement::Priority(PrivateClickMeasurement::MaxEntropy)));
     138    PrivateClickMeasurement attribution { PrivateClickMeasurement::SourceID(PrivateClickMeasurement::MaxEntropy), PrivateClickMeasurement::SourceSite { emptyURL }, PrivateClickMeasurement::AttributeOnSite { exampleURL } };
     139    attribution.attributeAndGetEarliestTimeToSend(PrivateClickMeasurement::AttributionTriggerData(PrivateClickMeasurement::MaxEntropy, PrivateClickMeasurement::Priority(PrivateClickMeasurement::MaxEntropy)));
    140140
    141141    ASSERT_TRUE(attribution.reportURL().isEmpty());
     
    144144TEST(PrivateClickMeasurement, InvalidDestinationHost)
    145145{
    146     PrivateClickMeasurement attribution { PrivateClickMeasurement::Campaign(PrivateClickMeasurement::MaxEntropy + 1), PrivateClickMeasurement::Source { webKitURL }, PrivateClickMeasurement::Destination { emptyURL } };
    147     attribution.convertAndGetEarliestTimeToSend(PrivateClickMeasurement::Conversion(PrivateClickMeasurement::MaxEntropy, PrivateClickMeasurement::Priority(PrivateClickMeasurement::MaxEntropy)));
     146    PrivateClickMeasurement attribution { PrivateClickMeasurement::SourceID(PrivateClickMeasurement::MaxEntropy + 1), PrivateClickMeasurement::SourceSite { webKitURL }, PrivateClickMeasurement::AttributeOnSite { emptyURL } };
     147    attribution.attributeAndGetEarliestTimeToSend(PrivateClickMeasurement::AttributionTriggerData(PrivateClickMeasurement::MaxEntropy, PrivateClickMeasurement::Priority(PrivateClickMeasurement::MaxEntropy)));
    148148
    149149    ASSERT_TRUE(attribution.reportURL().isEmpty());
     
    152152TEST(PrivateClickMeasurement, InvalidConversionData)
    153153{
    154     PrivateClickMeasurement attribution { PrivateClickMeasurement::Campaign(PrivateClickMeasurement::MaxEntropy), PrivateClickMeasurement::Source { webKitURL }, PrivateClickMeasurement::Destination { exampleURL } };
    155     attribution.convertAndGetEarliestTimeToSend(PrivateClickMeasurement::Conversion((PrivateClickMeasurement::MaxEntropy + 1), PrivateClickMeasurement::Priority(PrivateClickMeasurement::MaxEntropy)));
     154    PrivateClickMeasurement attribution { PrivateClickMeasurement::SourceID(PrivateClickMeasurement::MaxEntropy), PrivateClickMeasurement::SourceSite { webKitURL }, PrivateClickMeasurement::AttributeOnSite { exampleURL } };
     155    attribution.attributeAndGetEarliestTimeToSend(PrivateClickMeasurement::AttributionTriggerData((PrivateClickMeasurement::MaxEntropy + 1), PrivateClickMeasurement::Priority(PrivateClickMeasurement::MaxEntropy)));
    156156
    157157    ASSERT_TRUE(attribution.reportURL().isEmpty());
     
    160160TEST(PrivateClickMeasurement, InvalidPriority)
    161161{
    162     PrivateClickMeasurement attribution { PrivateClickMeasurement::Campaign(PrivateClickMeasurement::MaxEntropy), PrivateClickMeasurement::Source { webKitURL }, PrivateClickMeasurement::Destination { exampleURL } };
    163     attribution.convertAndGetEarliestTimeToSend(PrivateClickMeasurement::Conversion(PrivateClickMeasurement::MaxEntropy, PrivateClickMeasurement::Priority(PrivateClickMeasurement::MaxEntropy + 1)));
     162    PrivateClickMeasurement attribution { PrivateClickMeasurement::SourceID(PrivateClickMeasurement::MaxEntropy), PrivateClickMeasurement::SourceSite { webKitURL }, PrivateClickMeasurement::AttributeOnSite { exampleURL } };
     163    attribution.attributeAndGetEarliestTimeToSend(PrivateClickMeasurement::AttributionTriggerData(PrivateClickMeasurement::MaxEntropy, PrivateClickMeasurement::Priority(PrivateClickMeasurement::MaxEntropy + 1)));
    164164
    165165    ASSERT_TRUE(attribution.reportURL().isEmpty());
     
    168168TEST(PrivateClickMeasurement, InvalidMissingConversion)
    169169{
    170     PrivateClickMeasurement attribution { PrivateClickMeasurement::Campaign(PrivateClickMeasurement::MaxEntropy), PrivateClickMeasurement::Source { webKitURL }, PrivateClickMeasurement::Destination { exampleURL } };
     170    PrivateClickMeasurement attribution { PrivateClickMeasurement::SourceID(PrivateClickMeasurement::MaxEntropy), PrivateClickMeasurement::SourceSite { webKitURL }, PrivateClickMeasurement::AttributeOnSite { exampleURL } };
    171171
    172172    ASSERT_TRUE(attribution.reportURL().isEmpty());
     
    177177{
    178178    const URL conversionURLWithSingleDigitConversionData { { }, "https://webkit.org/.well-known/private-click-measurement/2"_s };
    179     auto optionalConversion = PrivateClickMeasurement::parseConversionRequest(conversionURLWithSingleDigitConversionData);
     179    auto optionalConversion = PrivateClickMeasurement::parseAttributionRequest(conversionURLWithSingleDigitConversionData);
    180180    ASSERT_FALSE(optionalConversion);
    181181   
    182182    const URL conversionURLWithNonNumeralConversionData { { }, "https://webkit.org/.well-known/private-click-measurement/2s"_s };
    183     optionalConversion = PrivateClickMeasurement::parseConversionRequest(conversionURLWithNonNumeralConversionData);
     183    optionalConversion = PrivateClickMeasurement::parseAttributionRequest(conversionURLWithNonNumeralConversionData);
    184184    ASSERT_FALSE(optionalConversion);
    185185
    186186    const URL conversionURLWithNegativeConversionData { { }, "https://webkit.org/.well-known/private-click-measurement/-2"_s };
    187     optionalConversion = PrivateClickMeasurement::parseConversionRequest(conversionURLWithNegativeConversionData);
     187    optionalConversion = PrivateClickMeasurement::parseAttributionRequest(conversionURLWithNegativeConversionData);
    188188    ASSERT_FALSE(optionalConversion);
    189189
    190190    const URL conversionURLWithTooLargeConversionData { { }, "https://webkit.org/.well-known/private-click-measurement/64"_s };
    191     optionalConversion = PrivateClickMeasurement::parseConversionRequest(conversionURLWithTooLargeConversionData);
     191    optionalConversion = PrivateClickMeasurement::parseAttributionRequest(conversionURLWithTooLargeConversionData);
    192192    ASSERT_FALSE(optionalConversion);
    193193
    194194    const URL conversionURLWithSingleDigitPriority { { }, "https://webkit.org/.well-known/private-click-measurement/22/2"_s };
    195     optionalConversion = PrivateClickMeasurement::parseConversionRequest(conversionURLWithSingleDigitPriority);
     195    optionalConversion = PrivateClickMeasurement::parseAttributionRequest(conversionURLWithSingleDigitPriority);
    196196    ASSERT_FALSE(optionalConversion);
    197197   
    198198    const URL conversionURLWithNonNumeralPriority { { }, "https://webkit.org/.well-known/private-click-measurement/22/2s"_s };
    199     optionalConversion = PrivateClickMeasurement::parseConversionRequest(conversionURLWithNonNumeralPriority);
     199    optionalConversion = PrivateClickMeasurement::parseAttributionRequest(conversionURLWithNonNumeralPriority);
    200200    ASSERT_FALSE(optionalConversion);
    201201   
    202202    const URL conversionURLWithNegativePriority { { }, "https://webkit.org/.well-known/private-click-measurement/22/-2"_s };
    203     optionalConversion = PrivateClickMeasurement::parseConversionRequest(conversionURLWithNegativePriority);
     203    optionalConversion = PrivateClickMeasurement::parseAttributionRequest(conversionURLWithNegativePriority);
    204204    ASSERT_FALSE(optionalConversion);
    205205   
    206206    const URL conversionURLWithTooLargePriority { { }, "https://webkit.org/.well-known/private-click-measurement/22/64"_s };
    207     optionalConversion = PrivateClickMeasurement::parseConversionRequest(conversionURLWithTooLargePriority);
     207    optionalConversion = PrivateClickMeasurement::parseAttributionRequest(conversionURLWithTooLargePriority);
    208208    ASSERT_FALSE(optionalConversion);
    209209
    210210    const URL conversionURLWithTooLargeConversionDataAndPriority { { }, "https://webkit.org/.well-known/private-click-measurement/64/22"_s };
    211     optionalConversion = PrivateClickMeasurement::parseConversionRequest(conversionURLWithTooLargeConversionDataAndPriority);
     211    optionalConversion = PrivateClickMeasurement::parseAttributionRequest(conversionURLWithTooLargeConversionDataAndPriority);
    212212    ASSERT_FALSE(optionalConversion);
    213213
    214214    const URL conversionURLWithTooLargeConversionDataAndTooLargePriority { { }, "https://webkit.org/.well-known/private-click-measurement/64/64"_s };
    215     optionalConversion = PrivateClickMeasurement::parseConversionRequest(conversionURLWithTooLargeConversionDataAndTooLargePriority);
     215    optionalConversion = PrivateClickMeasurement::parseAttributionRequest(conversionURLWithTooLargeConversionDataAndTooLargePriority);
    216216    ASSERT_FALSE(optionalConversion);
    217217
    218218    const URL conversionURLWithExtraLeadingSlash = { { }, "https://webkit.org/.well-known/private-click-measurement//22/12"_s };
    219     optionalConversion = PrivateClickMeasurement::parseConversionRequest(conversionURLWithExtraLeadingSlash);
     219    optionalConversion = PrivateClickMeasurement::parseAttributionRequest(conversionURLWithExtraLeadingSlash);
    220220    ASSERT_FALSE(optionalConversion);
    221221
    222222    const URL conversionURLWithExtraTrailingSlash = { { }, "https://webkit.org/.well-known/private-click-measurement/22/12/"_s };
    223     optionalConversion = PrivateClickMeasurement::parseConversionRequest(conversionURLWithExtraTrailingSlash);
     223    optionalConversion = PrivateClickMeasurement::parseAttributionRequest(conversionURLWithExtraTrailingSlash);
    224224    ASSERT_FALSE(optionalConversion);
    225225
    226226    const URL conversionURLWithTrailingQuestionMark = { { }, "https://webkit.org/.well-known/private-click-measurement/22/12?"_s };
    227     optionalConversion = PrivateClickMeasurement::parseConversionRequest(conversionURLWithTrailingQuestionMark);
     227    optionalConversion = PrivateClickMeasurement::parseAttributionRequest(conversionURLWithTrailingQuestionMark);
    228228    ASSERT_FALSE(optionalConversion);
    229229}
     
    233233    // Protocol.
    234234    const URL conversionURLWithHttpProtocol { { }, "http://webkit.org/.well-known/private-click-measurement/2"_s };
    235     auto optionalConversion = PrivateClickMeasurement::parseConversionRequest(conversionURLWithHttpProtocol);
     235    auto optionalConversion = PrivateClickMeasurement::parseAttributionRequest(conversionURLWithHttpProtocol);
    236236    ASSERT_FALSE(optionalConversion);
    237237
    238238    const URL conversionURLWithWssProtocol { { }, "wss://webkit.org/.well-known/private-click-measurement/2"_s };
    239     optionalConversion = PrivateClickMeasurement::parseConversionRequest(conversionURLWithWssProtocol);
     239    optionalConversion = PrivateClickMeasurement::parseAttributionRequest(conversionURLWithWssProtocol);
    240240    ASSERT_FALSE(optionalConversion);
    241241
    242242    const URL conversionURLWithFileProtocol { { }, "file:///.well-known/private-click-measurement/2"_s };
    243     optionalConversion = PrivateClickMeasurement::parseConversionRequest(conversionURLWithFileProtocol);
     243    optionalConversion = PrivateClickMeasurement::parseAttributionRequest(conversionURLWithFileProtocol);
    244244    ASSERT_FALSE(optionalConversion);
    245245
    246246    // Username and password.
    247247    const URL conversionURLWithUserName { { }, "https://user@webkit.org/.well-known/private-click-measurement/2"_s };
    248     optionalConversion = PrivateClickMeasurement::parseConversionRequest(conversionURLWithUserName);
     248    optionalConversion = PrivateClickMeasurement::parseAttributionRequest(conversionURLWithUserName);
    249249    ASSERT_FALSE(optionalConversion);
    250250
    251251    const URL conversionURLWithPassword = { { }, "https://:pwd@webkit.org/.well-known/private-click-measurement/22/12?"_s };
    252     optionalConversion = PrivateClickMeasurement::parseConversionRequest(conversionURLWithPassword);
     252    optionalConversion = PrivateClickMeasurement::parseAttributionRequest(conversionURLWithPassword);
    253253    ASSERT_FALSE(optionalConversion);
    254254
    255255    const URL conversionURLWithUsernameAndPassword = { { }, "https://user:pwd@webkit.org/.well-known/private-click-measurement/22/12?"_s };
    256     optionalConversion = PrivateClickMeasurement::parseConversionRequest(conversionURLWithUsernameAndPassword);
     256    optionalConversion = PrivateClickMeasurement::parseAttributionRequest(conversionURLWithUsernameAndPassword);
    257257    ASSERT_FALSE(optionalConversion);
    258258
    259259    // Query string.
    260260    const URL conversionURLWithTrailingQuestionMark = { { }, "https://webkit.org/.well-known/private-click-measurement/22/12?"_s };
    261     optionalConversion = PrivateClickMeasurement::parseConversionRequest(conversionURLWithTrailingQuestionMark);
     261    optionalConversion = PrivateClickMeasurement::parseAttributionRequest(conversionURLWithTrailingQuestionMark);
    262262    ASSERT_FALSE(optionalConversion);
    263263
    264264    const URL conversionURLWithQueryString = { { }, "https://webkit.org/.well-known/private-click-measurement/22/12?extra=data"_s };
    265     optionalConversion = PrivateClickMeasurement::parseConversionRequest(conversionURLWithQueryString);
     265    optionalConversion = PrivateClickMeasurement::parseAttributionRequest(conversionURLWithQueryString);
    266266    ASSERT_FALSE(optionalConversion);
    267267   
    268268    // Fragment.
    269269    const URL conversionURLWithTrailingHash = { { }, "https://webkit.org/.well-known/private-click-measurement/22/12#"_s };
    270     optionalConversion = PrivateClickMeasurement::parseConversionRequest(conversionURLWithTrailingHash);
     270    optionalConversion = PrivateClickMeasurement::parseAttributionRequest(conversionURLWithTrailingHash);
    271271    ASSERT_FALSE(optionalConversion);
    272272
    273273    const URL conversionURLWithFragment = { { }, "https://webkit.org/.well-known/private-click-measurement/22/12#fragment"_s };
    274     optionalConversion = PrivateClickMeasurement::parseConversionRequest(conversionURLWithFragment);
     274    optionalConversion = PrivateClickMeasurement::parseAttributionRequest(conversionURLWithFragment);
    275275    ASSERT_FALSE(optionalConversion);
    276276}
  • trunk/Tools/WebKitTestRunner/InjectedBundle/Bindings/TestRunner.idl

    r269810 r270136  
    402402    undefined clearPrivateClickMeasurementsThroughWebsiteDataRemoval();
    403403    undefined setPrivateClickMeasurementOverrideTimerForTesting(boolean value);
     404    undefined markAttributedPrivateClickMeasurementsAsExpiredForTesting();
     405    undefined simulateResourceLoadStatisticsSessionRestart();
    404406    undefined setPrivateClickMeasurementConversionURLForTesting(DOMString url);
    405407    undefined markPrivateClickMeasurementsAsExpiredForTesting();
  • trunk/Tools/WebKitTestRunner/InjectedBundle/TestRunner.cpp

    r269810 r270136  
    20162016}
    20172017
     2018void TestRunner::markAttributedPrivateClickMeasurementsAsExpiredForTesting()
     2019{
     2020    postSynchronousPageMessage("MarkAttributedPrivateClickMeasurementsAsExpiredForTesting");
     2021}
     2022
     2023void TestRunner::simulateResourceLoadStatisticsSessionRestart()
     2024{
     2025    postSynchronousPageMessage("SimulateResourceLoadStatisticsSessionRestart");
     2026}
     2027
    20182028void TestRunner::setPrivateClickMeasurementConversionURLForTesting(JSStringRef urlString)
    20192029{
  • trunk/Tools/WebKitTestRunner/InjectedBundle/TestRunner.h

    r269810 r270136  
    515515    void setPrivateClickMeasurementConversionURLForTesting(JSStringRef);
    516516    void markPrivateClickMeasurementsAsExpiredForTesting();
     517    void markAttributedPrivateClickMeasurementsAsExpiredForTesting();
     518    void simulateResourceLoadStatisticsSessionRestart();
    517519
    518520    void setIsSpeechRecognitionPermissionGranted(bool);
  • trunk/Tools/WebKitTestRunner/TestController.cpp

    r269810 r270136  
    35933593}
    35943594
     3595void TestController::markAttributedPrivateClickMeasurementsAsExpiredForTesting()
     3596{
     3597    PrivateClickMeasurementVoidCallbackContext callbackContext(*this);
     3598    WKPageMarkAttributedPrivateClickMeasurementsAsExpiredForTesting(m_mainWebView->page(), privateClickMeasurementVoidCallback, &callbackContext);
     3599    runUntil(callbackContext.done, noTimeout);
     3600}
     3601
     3602void TestController::simulateResourceLoadStatisticsSessionRestart()
     3603{
     3604    PrivateClickMeasurementVoidCallbackContext callbackContext(*this);
     3605    WKPageSimulateResourceLoadStatisticsSessionRestart(m_mainWebView->page(), privateClickMeasurementVoidCallback, &callbackContext);
     3606    runUntil(callbackContext.done, noTimeout);
     3607}
     3608
    35953609void TestController::setPrivateClickMeasurementConversionURLForTesting(WKURLRef url)
    35963610{
  • trunk/Tools/WebKitTestRunner/TestController.h

    r269810 r270136  
    345345    void clearPrivateClickMeasurementsThroughWebsiteDataRemoval();
    346346    void setPrivateClickMeasurementOverrideTimerForTesting(bool value);
     347    void markAttributedPrivateClickMeasurementsAsExpiredForTesting();
     348    void simulateResourceLoadStatisticsSessionRestart();
    347349    void setPrivateClickMeasurementConversionURLForTesting(WKURLRef);
    348350    void markPrivateClickMeasurementsAsExpiredForTesting();
  • trunk/Tools/WebKitTestRunner/TestInvocation.cpp

    r269810 r270136  
    13301330    }
    13311331   
     1332    if (WKStringIsEqualToUTF8CString(messageName, "MarkAttributedPrivateClickMeasurementsAsExpiredForTesting")) {
     1333        TestController::singleton().markAttributedPrivateClickMeasurementsAsExpiredForTesting();
     1334        return nullptr;
     1335    }
     1336   
     1337    if (WKStringIsEqualToUTF8CString(messageName, "SimulateResourceLoadStatisticsSessionRestart")) {
     1338        TestController::singleton().simulateResourceLoadStatisticsSessionRestart();
     1339        return nullptr;
     1340    }
     1341   
    13321342    if (WKStringIsEqualToUTF8CString(messageName, "SetPrivateClickMeasurementConversionURLForTesting")) {
    13331343        ASSERT(WKGetTypeID(messageBody) == WKURLGetTypeID());
Note: See TracChangeset for help on using the changeset viewer.