Commit 175ac10
committed
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:
Canonical link: https://commits.webkit.org/231845@main
git-svn-id: https://svn.webkit.org/repository/webkit/trunk@270136 268f45cc-cd09-0410-ab3c-d52691b4dbfc1 parent 6d41054 commit 175ac10
60 files changed
Lines changed: 1842 additions & 535 deletions
File tree
- LayoutTests
- http/tests
- contentextensions
- privateClickMeasurement
- resources
- Source
- WebCore
- html
- loader
- WebKit
- NetworkProcess
- Classifier
- UIProcess
- API/C
- Tools
- TestWebKitAPI/Tests/WebCore
- WebKitTestRunner
- InjectedBundle
- Bindings
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
1 | 42 | | |
2 | 43 | | |
3 | 44 | | |
| |||
Lines changed: 6 additions & 6 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
10 | | - | |
| 10 | + | |
11 | 11 | | |
12 | | - | |
| 12 | + | |
13 | 13 | | |
14 | | - | |
15 | | - | |
16 | | - | |
17 | | - | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
Lines changed: 6 additions & 6 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | | - | |
| 1 | + | |
2 | 2 | | |
3 | 3 | | |
4 | | - | |
| 4 | + | |
5 | 5 | | |
6 | | - | |
7 | | - | |
8 | | - | |
9 | | - | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
Lines changed: 1 addition & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
10 | | - | |
| 10 | + | |
11 | 11 | | |
12 | 12 | | |
13 | 13 | | |
| |||
Lines changed: 8 additions & 9 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | | - | |
| 1 | + | |
2 | 2 | | |
3 | 3 | | |
4 | | - | |
| 4 | + | |
5 | 5 | | |
6 | | - | |
7 | | - | |
8 | | - | |
9 | | - | |
10 | | - | |
11 | | - | |
12 | | - | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
Lines changed: 1 addition & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
10 | | - | |
| 10 | + | |
11 | 11 | | |
12 | 12 | | |
13 | 13 | | |
| |||
Lines changed: 8 additions & 9 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | | - | |
| 1 | + | |
2 | 2 | | |
3 | 3 | | |
4 | | - | |
| 4 | + | |
5 | 5 | | |
6 | | - | |
7 | | - | |
8 | | - | |
9 | | - | |
10 | | - | |
11 | | - | |
12 | | - | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
Lines changed: 1 addition & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
10 | | - | |
| 10 | + | |
11 | 11 | | |
12 | 12 | | |
13 | 13 | | |
| |||
Lines changed: 8 additions & 9 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | | - | |
| 1 | + | |
2 | 2 | | |
3 | 3 | | |
4 | | - | |
| 4 | + | |
5 | 5 | | |
6 | | - | |
7 | | - | |
8 | | - | |
9 | | - | |
10 | | - | |
11 | | - | |
12 | | - | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
Lines changed: 1 addition & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
10 | | - | |
| 10 | + | |
11 | 11 | | |
12 | 12 | | |
13 | 13 | | |
| |||
0 commit comments