Title: [290815] trunk
Revision
290815
Author
[email protected]
Date
2022-03-03 21:28:22 -0800 (Thu, 03 Mar 2022)

Log Message

Enforce silent push quota
https://bugs.webkit.org/show_bug.cgi?id=236863

Reviewed by Brady Eidson.

Source/WebCore:

All PushSubscriptions are created with the userVisibleOnly flag set. This means that all
push events should result in a user-facing notification.

To enforce this, we use a quota system. A push subscription can process up to three silent
pushes. After three silent pushes, we remove the push subscription. To continue to receive
pushes, the user must navigate back to the website and the site must request permission to
send pushes again.

Originally we were going to use a more complicated quota system that moved subscriptions
from waking to non-waking before finally unsubscribing, but this seemed like it could lead
to subscriptions being in a partially-working state that would be hard to explain and reason
about. As a result, I've removed the wakeState column from PushDatabase and replaced it
a silentPushCount column.

To track whether or not a service worker has shown a notification while processing a push
event, we use a boolean flag on ServiceWorkerGlobalScope. This should be sufficient because
we currently only process one push event at a time.

Covered by existing and new API tests.

* Modules/push-api/PushDatabase.cpp:
(WebCore::PushRecord::isolatedCopy const):
(WebCore::PushRecord::isolatedCopy):
(WebCore::PushDatabase::insertRecord):
(WebCore::makePushRecordFromRow):
(WebCore::PushDatabase::getRecordByTopic):
(WebCore::PushDatabase::getRecordByBundleIdentifierAndScope):
(WebCore::PushDatabase::getTopics):
(WebCore::PushDatabase::incrementSilentPushCount):
(WebCore::PushDatabase::removeRecordsByBundleIdentifierAndSecurityOrigin):
* Modules/push-api/PushDatabase.h:
* workers/service/ServiceWorkerGlobalScope.h:
* workers/service/ServiceWorkerRegistration.cpp:
(WebCore::ServiceWorkerRegistration::showNotification):
* workers/service/context/ServiceWorkerThread.cpp:
(WebCore::ServiceWorkerThread::queueTaskToFirePushEvent):

Source/WebKit:

 - If a push event doesn't result in a notification or if the associated promise rejects,
   then we increment the silent push count associated with that origin.
 - PushService now removes a subscription if the origin has reached its quota of silent
   pushes.

Covered by existing and new API tests.

* NetworkProcess/NetworkProcess.cpp:
(WebKit::NetworkProcess::processPushMessage):
* NetworkProcess/Notifications/NetworkNotificationManager.cpp:
(WebKit::NetworkNotificationManager::incrementSilentPushCount):
(WebKit::ReplyCaller<int>::callReply):
* Shared/WebPushDaemonConstants.h:
(WebKit::WebPushD::messageTypeSendsReply):
* UIProcess/API/Cocoa/WKProcessPool.mm:
(-[WKProcessPool _notificationManagerForTesting]):
* webpushd/PushService.mm:
(WebPushD::updateTopicLists):
(WebPushD::PushService::incrementSilentPushCount):
* webpushd/WebPushDaemon.mm:
(WebPushD::MessageInfo::incrementSilentPushCount::encodeReply):
(WebPushD::Daemon::decodeAndHandleMessage):
(WebPushD::Daemon::incrementSilentPushCount):

Tools:

Modified existing push tests to show a notification. Added a new test case to make sure that
subscriptions are removed when an origin reaches its quota of silent pushes.

* TestWebKitAPI/TestNotificationProvider.cpp:
(TestWebKitAPI::notificationPermissions):
(TestWebKitAPI::TestNotificationProvider::TestNotificationProvider):
(TestWebKitAPI::TestNotificationProvider::~TestNotificationProvider):
(TestWebKitAPI::TestNotificationProvider::notificationPermissions):
(TestWebKitAPI::TestNotificationProvider::setPermission):
* TestWebKitAPI/Tests/WebCore/PushDatabase.cpp:
(TestWebKitAPI::makeTemporaryDatabasePath):
(TestWebKitAPI::getTopicsSync):
(TestWebKitAPI::PushDatabaseTest::getTopics):
(TestWebKitAPI::PushDatabaseTest::removeRecordsByBundleIdentifierAndSecurityOrigin):
(TestWebKitAPI::PushDatabaseTest::incrementSilentPushCount):
(TestWebKitAPI::operator==):
(TestWebKitAPI::TEST_F):
(TestWebKitAPI::TEST):
* TestWebKitAPI/cocoa/HTTPServer.mm:
(TestWebKitAPI::HTTPServer::origin const):

LayoutTests:

Update PushEvent tests to check that notifications are displayed.

* http/wpt/push-api/pushEvent.any.js:
(promise_test.async const):
(promise_test.async if):
* http/wpt/push-api/pushEvent.any.serviceworker-expected.txt:

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (290814 => 290815)


--- trunk/LayoutTests/ChangeLog	2022-03-04 04:40:06 UTC (rev 290814)
+++ trunk/LayoutTests/ChangeLog	2022-03-04 05:28:22 UTC (rev 290815)
@@ -1,3 +1,17 @@
+2022-03-03  Ben Nham  <[email protected]>
+
+        Enforce silent push quota
+        https://bugs.webkit.org/show_bug.cgi?id=236863
+
+        Reviewed by Brady Eidson.
+
+        Update PushEvent tests to check that notifications are displayed.
+
+        * http/wpt/push-api/pushEvent.any.js:
+        (promise_test.async const):
+        (promise_test.async if):
+        * http/wpt/push-api/pushEvent.any.serviceworker-expected.txt:
+
 2022-03-03  Simon Fraser  <[email protected]>
 
         Element with position: sticky after sticking, starts to move incorrectly when scrolling

Modified: trunk/LayoutTests/http/wpt/push-api/pushEvent.any.js (290814 => 290815)


--- trunk/LayoutTests/http/wpt/push-api/pushEvent.any.js	2022-03-04 04:40:06 UTC (rev 290814)
+++ trunk/LayoutTests/http/wpt/push-api/pushEvent.any.js	2022-03-04 05:28:22 UTC (rev 290815)
@@ -60,12 +60,24 @@
 
 promise_test(async () => {
     const promise = self.internals.schedulePushEvent("test");
-    const event = await new Promise(resolve => self._onpush_ = resolve);
+    const event = await new Promise(resolve => self._onpush_ = (event) => {
+        self.registration.showNotification("notification");
+        resolve(event);
+    });
     assert_equals(event.data.text(), "test");
     assert_true(await promise);
 }, "Simulating firing of a push event");
 
 promise_test(async () => {
+    const promise = self.internals.schedulePushEvent("test");
+    const event = await new Promise(resolve => self._onpush_ = (event) => {
+        resolve(event);
+    });
+    assert_equals(event.data.text(), "test");
+    assert_false(await promise);
+}, "Simulating firing of a push event without notification");
+
+promise_test(async () => {
     if (!self.internals)
         return;
 
@@ -77,6 +89,7 @@
     promise.then(() => isPushEventPromiseResolved = true);
 
     const event = await new Promise(resolve => self._onpush_ = (event) => {
+        self.registration.showNotification("notification");
         event.waitUntil(waitUntilPromise);
         resolve(event);
     });
@@ -93,8 +106,33 @@
     if (!self.internals)
         return;
 
+    let resolveWaitUntilPromise;
+    const waitUntilPromise = new Promise(resolve => resolveWaitUntilPromise = resolve);
+
+    let isPushEventPromiseResolved = false;
     const promise = internals.schedulePushEvent("test");
+    promise.then(() => isPushEventPromiseResolved = true);
+
     const event = await new Promise(resolve => self._onpush_ = (event) => {
+        event.waitUntil(waitUntilPromise);
+        resolve(event);
+    });
+    assert_equals(event.data.text(), "test");
+
+    await new Promise(resolve => self.setTimeout(resolve, 100));
+    assert_false(isPushEventPromiseResolved);
+
+    resolveWaitUntilPromise();
+    assert_false(await promise);
+}, "Simulating firing of a push event - successful waitUntil without notification");
+
+promise_test(async () => {
+    if (!self.internals)
+        return;
+
+    const promise = internals.schedulePushEvent("test");
+    const event = await new Promise(resolve => self._onpush_ = (event) => {
+        self.registration.showNotification("notification");
         event.waitUntil(Promise.resolve());
         event.waitUntil(Promise.reject('error'));
         resolve(event);

Modified: trunk/LayoutTests/http/wpt/push-api/pushEvent.any.serviceworker-expected.txt (290814 => 290815)


--- trunk/LayoutTests/http/wpt/push-api/pushEvent.any.serviceworker-expected.txt	2022-03-04 04:40:06 UTC (rev 290814)
+++ trunk/LayoutTests/http/wpt/push-api/pushEvent.any.serviceworker-expected.txt	2022-03-04 05:28:22 UTC (rev 290815)
@@ -5,6 +5,8 @@
 PASS PushEvent with bad json
 PASS Wait for activate promise
 PASS Simulating firing of a push event
+PASS Simulating firing of a push event without notification
 PASS Simulating firing of a push event - successful waitUntil
+PASS Simulating firing of a push event - successful waitUntil without notification
 PASS Simulating firing of a push event - unsuccessful waitUntil
 

Modified: trunk/Source/WebCore/ChangeLog (290814 => 290815)


--- trunk/Source/WebCore/ChangeLog	2022-03-04 04:40:06 UTC (rev 290814)
+++ trunk/Source/WebCore/ChangeLog	2022-03-04 05:28:22 UTC (rev 290815)
@@ -1,3 +1,47 @@
+2022-03-03  Ben Nham  <[email protected]>
+
+        Enforce silent push quota
+        https://bugs.webkit.org/show_bug.cgi?id=236863
+
+        Reviewed by Brady Eidson.
+
+        All PushSubscriptions are created with the userVisibleOnly flag set. This means that all
+        push events should result in a user-facing notification.
+
+        To enforce this, we use a quota system. A push subscription can process up to three silent
+        pushes. After three silent pushes, we remove the push subscription. To continue to receive
+        pushes, the user must navigate back to the website and the site must request permission to
+        send pushes again.
+
+        Originally we were going to use a more complicated quota system that moved subscriptions
+        from waking to non-waking before finally unsubscribing, but this seemed like it could lead
+        to subscriptions being in a partially-working state that would be hard to explain and reason
+        about. As a result, I've removed the wakeState column from PushDatabase and replaced it
+        a silentPushCount column.
+
+        To track whether or not a service worker has shown a notification while processing a push
+        event, we use a boolean flag on ServiceWorkerGlobalScope. This should be sufficient because
+        we currently only process one push event at a time.
+
+        Covered by existing and new API tests.
+
+        * Modules/push-api/PushDatabase.cpp:
+        (WebCore::PushRecord::isolatedCopy const):
+        (WebCore::PushRecord::isolatedCopy):
+        (WebCore::PushDatabase::insertRecord):
+        (WebCore::makePushRecordFromRow):
+        (WebCore::PushDatabase::getRecordByTopic):
+        (WebCore::PushDatabase::getRecordByBundleIdentifierAndScope):
+        (WebCore::PushDatabase::getTopics):
+        (WebCore::PushDatabase::incrementSilentPushCount):
+        (WebCore::PushDatabase::removeRecordsByBundleIdentifierAndSecurityOrigin):
+        * Modules/push-api/PushDatabase.h:
+        * workers/service/ServiceWorkerGlobalScope.h:
+        * workers/service/ServiceWorkerRegistration.cpp:
+        (WebCore::ServiceWorkerRegistration::showNotification):
+        * workers/service/context/ServiceWorkerThread.cpp:
+        (WebCore::ServiceWorkerThread::queueTaskToFirePushEvent):
+
 2022-03-03  Devin Rousso  <[email protected]>
 
         [GPU Process] dont load Apple Pay button/logo PDFs in the WebProcess

Modified: trunk/Source/WebCore/Modules/push-api/PushDatabase.cpp (290814 => 290815)


--- trunk/Source/WebCore/Modules/push-api/PushDatabase.cpp	2022-03-04 04:40:06 UTC (rev 290814)
+++ trunk/Source/WebCore/Modules/push-api/PushDatabase.cpp	2022-03-04 05:28:22 UTC (rev 290815)
@@ -37,6 +37,7 @@
 #include <wtf/Expected.h>
 #include <wtf/FileSystem.h>
 #include <wtf/RunLoop.h>
+#include <wtf/Scope.h>
 #include <wtf/UniqueRef.h>
 
 #define PUSHDB_RELEASE_LOG(fmt, ...) RELEASE_LOG(Push, "%p - PushDatabase::" fmt, this, ##__VA_ARGS__)
@@ -43,19 +44,24 @@
 #define PUSHDB_RELEASE_LOG_ERROR(fmt, ...) RELEASE_LOG_ERROR(Push, "%p - PushDatabase::" fmt, this, ##__VA_ARGS__)
 #define PUSHDB_RELEASE_LOG_BIND_ERROR() PUSHDB_RELEASE_LOG_ERROR("Failed to bind statement (%d): %s", m_db->lastError(), m_db->lastErrorMsg())
 
+#define kPushRecordColumns " sub.rowID, ss.bundleID, ss.securityOrigin, sub.scope, sub.endpoint, sub.topic, sub.serverVAPIDPublicKey, sub.clientPublicKey, sub.clientPrivateKey, sub.sharedAuthSecret, sub.expirationTime "
+
 namespace WebCore {
 
-static constexpr int currentPushDatabaseVersion = 1;
+static constexpr int currentPushDatabaseVersion = 2;
+#define kCurrentPushDatabaseVersionString "2"
 
 static const ASCIILiteral pushDatabaseSchemaV1Statements[] = {
     "PRAGMA auto_vacuum=INCREMENTAL"_s,
     "CREATE TABLE SubscriptionSets("
+    "  rowID INTEGER PRIMARY KEY AUTOINCREMENT,"
     "  creationTime INT NOT NULL,"
     "  bundleID TEXT NOT NULL,"
     "  securityOrigin TEXT NOT NULL,"
-    "  wakeState INT NOT NULL,"
+    "  silentPushCount INT NOT NULL,"
     "  UNIQUE(bundleID, securityOrigin))"_s,
     "CREATE TABLE Subscriptions("
+    "  rowID INTEGER PRIMARY KEY AUTOINCREMENT,"
     "  creationTime INT NOT NULL,"
     "  subscriptionSetID INT NOT NULL,"
     "  scope TEXT NOT NULL,"
@@ -68,7 +74,7 @@
     "  expirationTime INT,"
     "  UNIQUE(scope, subscriptionSetID))"_s,
     "CREATE INDEX Subscriptions_SubscriptionSetID_Index ON Subscriptions(subscriptionSetID)"_s,
-    "PRAGMA user_version = 1"_s
+    "PRAGMA user_version = " kCurrentPushDatabaseVersionString ""_s
 };
 
 PushRecord PushRecord::isolatedCopy() const &
@@ -84,8 +90,7 @@
         clientPublicKey,
         clientPrivateKey,
         sharedAuthSecret,
-        expirationTime,
-        wakeState
+        expirationTime
     };
 }
 
@@ -102,11 +107,20 @@
         WTFMove(clientPublicKey),
         WTFMove(clientPrivateKey),
         WTFMove(sharedAuthSecret),
-        expirationTime,
-        wakeState
+        expirationTime
     };
 }
 
+RemovedPushRecord RemovedPushRecord::isolatedCopy() const &
+{
+    return { identifier, topic.isolatedCopy(), serverVAPIDPublicKey };
+}
+
+RemovedPushRecord RemovedPushRecord::isolatedCopy() &&
+{
+    return { identifier, WTFMove(topic).isolatedCopy(), WTFMove(serverVAPIDPublicKey) };
+}
+
 enum class ShouldDeleteAndRetry { No, Yes };
 
 static Expected<UniqueRef<SQLiteDatabase>, ShouldDeleteAndRetry> openAndMigrateDatabaseImpl(const String& path)
@@ -280,7 +294,7 @@
         int64_t subscriptionSetID = 0;
 
         {
-            auto sql = cachedStatementOnQueue("SELECT rowid, wakeState FROM SubscriptionSets WHERE bundleID = ? AND securityOrigin = ?"_s);
+            auto sql = cachedStatementOnQueue("SELECT rowID FROM SubscriptionSets WHERE bundleID = ? AND securityOrigin = ?"_s);
             if (!sql
                 || sql->bindText(1, record.bundleID) != SQLITE_OK
                 || sql->bindText(2, record.securityOrigin) != SQLITE_OK) {
@@ -289,19 +303,16 @@
                 return;
             }
 
-            if (sql->step() == SQLITE_ROW) {
+            if (sql->step() == SQLITE_ROW)
                 subscriptionSetID = sql->columnInt64(0);
-                record.wakeState = static_cast<PushWakeState>(sql->columnInt(1));
-            }
         }
 
         if (!subscriptionSetID) {
-            auto sql = cachedStatementOnQueue("INSERT INTO SubscriptionSets VALUES(?, ?, ?, ?)"_s);
+            auto sql = cachedStatementOnQueue("INSERT INTO SubscriptionSets VALUES(NULL, ?, ?, ?, 0)"_s);
             if (!sql
                 || sql->bindInt64(1, time(nullptr)) != SQLITE_OK
                 || sql->bindText(2, record.bundleID) != SQLITE_OK
-                || sql->bindText(3, record.securityOrigin) != SQLITE_OK
-                || sql->bindInt(4, static_cast<int>(record.wakeState)) != SQLITE_OK) {
+                || sql->bindText(3, record.securityOrigin) != SQLITE_OK) {
                 PUSHDB_RELEASE_LOG_BIND_ERROR();
                 completeOnMainQueue(WTFMove(completionHandler), std::optional<PushRecord> { });
                 return;
@@ -316,7 +327,7 @@
         }
 
         {
-            auto sql = cachedStatementOnQueue("INSERT INTO Subscriptions VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"_s);
+            auto sql = cachedStatementOnQueue("INSERT INTO Subscriptions VALUES(NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"_s);
             if (!sql
                 || sql->bindInt64(1, time(nullptr)) != SQLITE_OK
                 || sql->bindInt64(2, subscriptionSetID) != SQLITE_OK
@@ -423,21 +434,20 @@
     });
 }
 
-static PushRecord makePushRecordFromRow(SQLiteStatementAutoResetScope& sql)
+static PushRecord makePushRecordFromRow(SQLiteStatementAutoResetScope& sql, int columnIndex)
 {
     PushRecord record;
-    record.identifier = makeObjectIdentifier<PushSubscriptionIdentifierType>(sql->columnInt64(0));
-    record.bundleID = sql->columnText(1);
-    record.securityOrigin = sql->columnText(2);
-    record.scope = sql->columnText(3);
-    record.endpoint = sql->columnText(4);
-    record.topic = sql->columnText(5);
-    record.serverVAPIDPublicKey = sql->columnBlob(6);
-    record.clientPublicKey = sql->columnBlob(7);
-    record.clientPrivateKey = sql->columnBlob(8);
-    record.sharedAuthSecret = sql->columnBlob(9);
-    record.expirationTime = expirationTimeFromValue(sql->columnValue(10));
-    record.wakeState = static_cast<PushWakeState>(sql->columnInt(11));
+    record.identifier = makeObjectIdentifier<PushSubscriptionIdentifierType>(sql->columnInt64(columnIndex++));
+    record.bundleID = sql->columnText(columnIndex++);
+    record.securityOrigin = sql->columnText(columnIndex++);
+    record.scope = sql->columnText(columnIndex++);
+    record.endpoint = sql->columnText(columnIndex++);
+    record.topic = sql->columnText(columnIndex++);
+    record.serverVAPIDPublicKey = sql->columnBlob(columnIndex++);
+    record.clientPublicKey = sql->columnBlob(columnIndex++);
+    record.clientPrivateKey = sql->columnBlob(columnIndex++);
+    record.sharedAuthSecret = sql->columnBlob(columnIndex++);
+    record.expirationTime = expirationTimeFromValue(sql->columnValue(columnIndex++));
 
     return record;
 }
@@ -447,10 +457,7 @@
     dispatchOnWorkQueue([this, topic = crossThreadCopy(topic), completionHandler = WTFMove(completionHandler)]() mutable {
         // Force SQLite to consult the Subscriptions(scope) index first via CROSS JOIN.
         auto sql = cachedStatementOnQueue(
-            "SELECT "
-            "  sub.rowID, ss.bundleID, ss.securityOrigin, sub.scope, sub.endpoint, sub.topic, "
-            "  sub.serverVAPIDPublicKey, sub.clientPublicKey, sub.clientPrivateKey, "
-            "  sub.sharedAuthSecret, sub.expirationTime, ss.wakeState "
+            "SELECT " kPushRecordColumns
             "FROM Subscriptions sub "
             "CROSS JOIN SubscriptionSets ss "
             "ON sub.subscriptionSetID = ss.rowid "
@@ -467,7 +474,7 @@
             return;
         }
 
-        completeOnMainQueue(WTFMove(completionHandler), makePushRecordFromRow(sql));
+        completeOnMainQueue(WTFMove(completionHandler), makePushRecordFromRow(sql, 0));
     });
 }
 
@@ -476,10 +483,7 @@
     dispatchOnWorkQueue([this, bundleID = crossThreadCopy(bundleID), scope = crossThreadCopy(scope), completionHandler = WTFMove(completionHandler)]() mutable {
         // Force SQLite to consult the Subscriptions(scope) index first via CROSS JOIN.
         auto sql = cachedStatementOnQueue(
-            "SELECT "
-            "  sub.rowID, ss.bundleID, ss.securityOrigin, sub.scope, sub.endpoint, sub.topic, "
-            "  sub.serverVAPIDPublicKey, sub.clientPublicKey, sub.clientPrivateKey, "
-            "  sub.sharedAuthSecret, sub.expirationTime, ss.wakeState "
+            "SELECT " kPushRecordColumns
             "FROM Subscriptions sub "
             "CROSS JOIN SubscriptionSets ss "
             "ON sub.subscriptionSetID = ss.rowid "
@@ -496,7 +500,7 @@
             return;
         }
 
-        completeOnMainQueue(WTFMove(completionHandler), makePushRecordFromRow(sql));
+        completeOnMainQueue(WTFMove(completionHandler), makePushRecordFromRow(sql, 0));
     });
 }
 
@@ -512,41 +516,190 @@
     });
 }
 
-void PushDatabase::getTopicsByWakeState(CompletionHandler<void(PushWakeStateToTopicMap&&)>&& completionHandler)
+void PushDatabase::getTopics(CompletionHandler<void(Vector<String>&&)>&& completionHandler)
 {
     dispatchOnWorkQueue([this, completionHandler = WTFMove(completionHandler)]() mutable {
-        constexpr int numberOfWakeStates = static_cast<int>(PushWakeState::NumberOfStates);
-        Vector<Vector<String>, numberOfWakeStates> topicsByWakeState(numberOfWakeStates); // used because HashMap::isolatedCopy doesn't exist
-
-        auto sql = cachedStatementOnQueue(
-            "SELECT ss.wakeState, sub.topic "
-            "FROM Subscriptions sub "
-            "JOIN SubscriptionSets ss "
-            "ON sub.subscriptionSetID = ss.rowid"_s);
+        Vector<String> topics;
+        auto sql = cachedStatementOnQueue("SELECT topic FROM Subscriptions"_s);
         if (!sql) {
             PUSHDB_RELEASE_LOG_BIND_ERROR();
-            WorkQueue::main().dispatch([completionHandler = WTFMove(completionHandler)]() mutable {
-                completionHandler({ });
-            });
+            completeOnMainQueue(WTFMove(completionHandler), Vector<String> { });
             return;
         }
 
-        while (sql->step() == SQLITE_ROW) {
-            auto pushWakeState = sql->columnInt(0);
-            auto topic = sql->columnText(1);
-            if (pushWakeState >= 0 && pushWakeState < numberOfWakeStates)
-                topicsByWakeState[pushWakeState].append(WTFMove(topic));
+        while (sql->step() == SQLITE_ROW)
+            topics.append(sql->columnText(0));
+
+        completeOnMainQueue(WTFMove(completionHandler), WTFMove(topics));
+    });
+}
+
+void PushDatabase::incrementSilentPushCount(const String& bundleID, const String& securityOrigin, CompletionHandler<void(unsigned)>&& completionHandler)
+{
+    dispatchOnWorkQueue([this, bundleID = crossThreadCopy(bundleID), securityOrigin = crossThreadCopy(securityOrigin), completionHandler = WTFMove(completionHandler)]() mutable {
+        auto scope = makeScopeExit([&completionHandler] {
+            completeOnMainQueue(WTFMove(completionHandler), 0u);
+        });
+
+        int silentPushCount = 0;
+        SQLiteTransaction transaction(m_db);
+        transaction.begin();
+
+        {
+            auto sql = cachedStatementOnQueue("UPDATE SubscriptionSets SET silentPushCount = silentPushCount + 1 WHERE bundleID = ? AND securityOrigin = ?"_s);
+
+            if (!sql || sql->bindText(1, bundleID) != SQLITE_OK || sql->bindText(2, securityOrigin) != SQLITE_OK) {
+                PUSHDB_RELEASE_LOG_BIND_ERROR();
+                return;
+            }
+
+            if (sql->step() != SQLITE_DONE)
+                return;
         }
 
-        WorkQueue::main().dispatch([completionHandler = WTFMove(completionHandler), topicsByWakeState = crossThreadCopy(WTFMove(topicsByWakeState))]() mutable {
-            PushWakeStateToTopicMap result;
-            for (int i = 0; i < static_cast<int>(PushWakeState::NumberOfStates); ++i)
-                result.add(static_cast<PushWakeState>(i), WTFMove(topicsByWakeState[i]));
-            completionHandler(WTFMove(result));
+        {
+            auto sql = cachedStatementOnQueue("SELECT silentPushCount FROM SubscriptionSets WHERE bundleID = ? AND securityOrigin = ?"_s);
+
+            if (!sql || sql->bindText(1, bundleID) != SQLITE_OK || sql->bindText(2, securityOrigin) != SQLITE_OK) {
+                PUSHDB_RELEASE_LOG_BIND_ERROR();
+                return;
+            }
+
+            if (sql->step() == SQLITE_ROW)
+                silentPushCount = sql->columnInt(0);
+        }
+
+        transaction.commit();
+
+        scope.release();
+        completeOnMainQueue(WTFMove(completionHandler), silentPushCount);
+    });
+}
+
+void PushDatabase::removeRecordsByBundleIdentifier(const String& bundleID, CompletionHandler<void(Vector<RemovedPushRecord>&&)>&& completionHandler)
+{
+    dispatchOnWorkQueue([this, bundleID = crossThreadCopy(bundleID), completionHandler = WTFMove(completionHandler)]() mutable {
+        auto scope = makeScopeExit([&completionHandler] {
+            completeOnMainQueue(WTFMove(completionHandler), Vector<RemovedPushRecord> { });
         });
+
+        Vector<RemovedPushRecord> removedPushRecords;
+        SQLiteTransaction transaction(m_db);
+        transaction.begin();
+
+        {
+            auto sql = cachedStatementOnQueue(
+                "SELECT sub.subscriptionSetID, sub.rowid, sub.topic, sub.serverVAPIDPublicKey "
+                "FROM SubscriptionSets ss "
+                "JOIN Subscriptions sub "
+                "ON ss.rowid = sub.subscriptionSetID "
+                "WHERE ss.bundleID = ?"_s);
+            if (!sql || sql->bindText(1, bundleID) != SQLITE_OK) {
+                PUSHDB_RELEASE_LOG_BIND_ERROR();
+                return;
+            }
+
+            while (sql->step() == SQLITE_ROW) {
+                auto identifier = makeObjectIdentifier<PushSubscriptionIdentifierType>(sql->columnInt(1));
+                auto topic = sql->columnText(2);
+                auto serverVAPIDPublicKey = sql->columnBlob(3);
+                removedPushRecords.append({ identifier, WTFMove(topic), WTFMove(serverVAPIDPublicKey) });
+            }
+        }
+
+        {
+            auto sql = cachedStatementOnQueue("DELETE FROM Subscriptions WHERE subscriptionSetID IN (SELECT rowid FROM SubscriptionSets WHERE bundleID = ?)"_s);
+            if (!sql || sql->bindText(1, bundleID) != SQLITE_OK) {
+                PUSHDB_RELEASE_LOG_BIND_ERROR();
+                return;
+            }
+
+            if (sql->step() != SQLITE_DONE)
+                return;
+        }
+
+        {
+            auto sql = cachedStatementOnQueue("DELETE FROM SubscriptionSets WHERE bundleID = ?"_s);
+            if (!sql || sql->bindText(1, bundleID) != SQLITE_OK) {
+                PUSHDB_RELEASE_LOG_BIND_ERROR();
+                return;
+            }
+
+            if (sql->step() != SQLITE_DONE)
+                return;
+        }
+
+        transaction.commit();
+
+        scope.release();
+        completeOnMainQueue(WTFMove(completionHandler), WTFMove(removedPushRecords));
     });
 }
 
+
+void PushDatabase::removeRecordsByBundleIdentifierAndSecurityOrigin(const String& bundleID, const String& securityOrigin, CompletionHandler<void(Vector<RemovedPushRecord>&&)>&& completionHandler)
+{
+    dispatchOnWorkQueue([this, bundleID = crossThreadCopy(bundleID), securityOrigin = crossThreadCopy(securityOrigin), completionHandler = WTFMove(completionHandler)]() mutable {
+        auto scope = makeScopeExit([&completionHandler] {
+            completeOnMainQueue(WTFMove(completionHandler), Vector<RemovedPushRecord> { });
+        });
+
+        Vector<RemovedPushRecord> removedPushRecords;
+        SQLiteTransaction transaction(m_db);
+        transaction.begin();
+
+        int64_t subscriptionSetID = 0;
+
+        {
+            auto sql = cachedStatementOnQueue(
+                "SELECT sub.subscriptionSetID, sub.rowid, sub.topic, sub.serverVAPIDPublicKey "
+                "FROM SubscriptionSets ss "
+                "JOIN Subscriptions sub "
+                "ON ss.rowid = sub.subscriptionSetID "
+                "WHERE ss.bundleID = ? AND ss.securityOrigin = ?"_s);
+            if (!sql || sql->bindText(1, bundleID) != SQLITE_OK || sql->bindText(2, securityOrigin) != SQLITE_OK) {
+                PUSHDB_RELEASE_LOG_BIND_ERROR();
+                return;
+            }
+
+            while (sql->step() == SQLITE_ROW) {
+                subscriptionSetID = sql->columnInt(0);
+                auto identifier = makeObjectIdentifier<PushSubscriptionIdentifierType>(sql->columnInt(1));
+                auto topic = sql->columnText(2);
+                auto serverVAPIDPublicKey = sql->columnBlob(3);
+                removedPushRecords.append({ identifier, WTFMove(topic), WTFMove(serverVAPIDPublicKey) });
+            }
+        }
+
+        {
+            auto sql = cachedStatementOnQueue("DELETE FROM Subscriptions WHERE subscriptionSetID = ?"_s);
+            if (!sql || sql->bindInt(1, subscriptionSetID) != SQLITE_OK) {
+                PUSHDB_RELEASE_LOG_BIND_ERROR();
+                return;
+            }
+
+            if (sql->step() != SQLITE_DONE)
+                return;
+        }
+
+        {
+            auto sql = cachedStatementOnQueue("DELETE FROM SubscriptionSets WHERE rowid = ?"_s);
+            if (!sql || sql->bindInt(1, subscriptionSetID) != SQLITE_OK) {
+                PUSHDB_RELEASE_LOG_BIND_ERROR();
+                return;
+            }
+
+            if (sql->step() != SQLITE_DONE)
+                return;
+        }
+
+        transaction.commit();
+
+        scope.release();
+        completeOnMainQueue(WTFMove(completionHandler), WTFMove(removedPushRecords));
+    });
+}
+
 } // namespace WebCore
 
 #endif // ENABLE(SERVICE_WORKER)

Modified: trunk/Source/WebCore/Modules/push-api/PushDatabase.h (290814 => 290815)


--- trunk/Source/WebCore/Modules/push-api/PushDatabase.h	2022-03-04 04:40:06 UTC (rev 290814)
+++ trunk/Source/WebCore/Modules/push-api/PushDatabase.h	2022-03-04 05:28:22 UTC (rev 290815)
@@ -42,14 +42,6 @@
 
 namespace WebCore {
 
-// Wake state that applies for devices that race-to-sleep.
-enum class PushWakeState : uint8_t {
-    Waking, // All pushes will wake device.
-    Opportunistic, // Low priority pushes may not wake device.
-    NonWaking, // No pushes will wake device.
-    NumberOfStates
-};
-
 struct PushRecord {
     PushSubscriptionIdentifier identifier;
     String bundleID;
@@ -62,12 +54,20 @@
     Vector<uint8_t> clientPrivateKey;
     Vector<uint8_t> sharedAuthSecret;
     std::optional<EpochTimeStamp> expirationTime { };
-    PushWakeState wakeState { PushWakeState::Waking };
 
     WEBCORE_EXPORT PushRecord isolatedCopy() const &;
     WEBCORE_EXPORT PushRecord isolatedCopy() &&;
 };
 
+struct RemovedPushRecord {
+    PushSubscriptionIdentifier identifier;
+    String topic;
+    Vector<uint8_t> serverVAPIDPublicKey;
+
+    WEBCORE_EXPORT RemovedPushRecord isolatedCopy() const &;
+    WEBCORE_EXPORT RemovedPushRecord isolatedCopy() &&;
+};
+
 class PushDatabase {
     WTF_MAKE_FAST_ALLOCATED;
 public:
@@ -81,10 +81,13 @@
     WEBCORE_EXPORT void getRecordByTopic(const String& topic, CompletionHandler<void(std::optional<PushRecord>&&)>&&);
     WEBCORE_EXPORT void getRecordByBundleIdentifierAndScope(const String& bundleID, const String& scope, CompletionHandler<void(std::optional<PushRecord>&&)>&&);
     WEBCORE_EXPORT void getIdentifiers(CompletionHandler<void(HashSet<PushSubscriptionIdentifier>&&)>&&);
+    WEBCORE_EXPORT void getTopics(CompletionHandler<void(Vector<String>&&)>&&);
 
-    using PushWakeStateToTopicMap = HashMap<PushWakeState, Vector<String>, WTF::IntHash<PushWakeState>, WTF::StrongEnumHashTraits<PushWakeState>>;
-    WEBCORE_EXPORT void getTopicsByWakeState(CompletionHandler<void(PushWakeStateToTopicMap&&)>&&);
+    WEBCORE_EXPORT void incrementSilentPushCount(const String& bundleID, const String& securityOrigin, CompletionHandler<void(unsigned)>&&);
 
+    WEBCORE_EXPORT void removeRecordsByBundleIdentifier(const String& bundleID, CompletionHandler<void(Vector<RemovedPushRecord>&&)>&&);
+    WEBCORE_EXPORT void removeRecordsByBundleIdentifierAndSecurityOrigin(const String& bundleID, const String& securityOrigin, CompletionHandler<void(Vector<RemovedPushRecord>&&)>&&);
+
 private:
     PushDatabase(Ref<WorkQueue>&&, UniqueRef<WebCore::SQLiteDatabase>&&);
     WebCore::SQLiteStatementAutoResetScope cachedStatementOnQueue(ASCIILiteral query);

Modified: trunk/Source/WebCore/workers/service/ServiceWorkerGlobalScope.h (290814 => 290815)


--- trunk/Source/WebCore/workers/service/ServiceWorkerGlobalScope.h	2022-03-04 04:40:06 UTC (rev 290814)
+++ trunk/Source/WebCore/workers/service/ServiceWorkerGlobalScope.h	2022-03-04 05:28:22 UTC (rev 290815)
@@ -32,6 +32,7 @@
 #include "ServiceWorkerContextData.h"
 #include "ServiceWorkerRegistration.h"
 #include "WorkerGlobalScope.h"
+#include <wtf/MonotonicTime.h>
 #include <wtf/URLHash.h>
 
 namespace WebCore {
@@ -88,6 +89,9 @@
     void postTaskToFireNotificationEvent(NotificationEventType, Notification&, const String& action);
 #endif
 
+    bool hasPendingSilentPushEvent() const { return m_hasPendingSilentPushEvent; }
+    void setHasPendingSilentPushEvent(bool value) { m_hasPendingSilentPushEvent = value; }
+
 private:
     ServiceWorkerGlobalScope(ServiceWorkerContextData&&, ServiceWorkerData&&, const WorkerParameters&, Ref<SecurityOrigin>&&, ServiceWorkerThread&, Ref<SecurityOrigin>&& topOrigin, IDBClient::IDBConnectionProxy*, SocketProvider*, std::unique_ptr<NotificationClient>&&, PAL::SessionID);
     void notifyServiceWorkerPageOfCreationIfNecessary();
@@ -96,6 +100,7 @@
     bool hasPendingEvents() const { return !m_extendedEvents.isEmpty(); }
 
     NotificationClient* notificationClient() final { return m_notificationClient.get(); }
+
     std::optional<PAL::SessionID> sessionID() const final { return m_sessionID; }
 
     ServiceWorkerContextData m_contextData;
@@ -109,6 +114,7 @@
     HashMap<uint64_t, RefPtr<DeferredPromise>> m_pendingSkipWaitingPromises;
     PAL::SessionID m_sessionID;
     std::unique_ptr<NotificationClient> m_notificationClient;
+    bool m_hasPendingSilentPushEvent { false };
 };
 
 } // namespace WebCore

Modified: trunk/Source/WebCore/workers/service/ServiceWorkerRegistration.cpp (290814 => 290815)


--- trunk/Source/WebCore/workers/service/ServiceWorkerRegistration.cpp	2022-03-04 04:40:06 UTC (rev 290814)
+++ trunk/Source/WebCore/workers/service/ServiceWorkerRegistration.cpp	2022-03-04 05:28:22 UTC (rev 290815)
@@ -40,6 +40,7 @@
 #include "NotificationPermission.h"
 #include "ServiceWorker.h"
 #include "ServiceWorkerContainer.h"
+#include "ServiceWorkerGlobalScope.h"
 #include "ServiceWorkerTypes.h"
 #include "WorkerGlobalScope.h"
 #include <wtf/IsoMallocInlines.h>
@@ -286,6 +287,9 @@
         return;
     }
 
+    if (context.isServiceWorkerGlobalScope())
+        downcast<ServiceWorkerGlobalScope>(context).setHasPendingSilentPushEvent(false);
+
     // The Notification is kept alive by virtue of being show()'n soon.
     // FIXME: When implementing getNotifications(), store this Notification in the registration's notification list.
     auto notification = Notification::create(context, title, options);

Modified: trunk/Source/WebCore/workers/service/context/ServiceWorkerThread.cpp (290814 => 290815)


--- trunk/Source/WebCore/workers/service/context/ServiceWorkerThread.cpp	2022-03-04 04:40:06 UTC (rev 290814)
+++ trunk/Source/WebCore/workers/service/context/ServiceWorkerThread.cpp	2022-03-04 05:28:22 UTC (rev 290815)
@@ -224,10 +224,12 @@
     serviceWorkerGlobalScope.eventLoop().queueTask(TaskSource::DOMManipulation, [weakThis = WeakPtr { *this }, serviceWorkerGlobalScope = Ref { serviceWorkerGlobalScope }, data = "" callback = WTFMove(callback)]() mutable {
         RELEASE_LOG(ServiceWorker, "ServiceWorkerThread::queueTaskToFirePushEvent firing event for worker %" PRIu64, serviceWorkerGlobalScope->thread().identifier().toUInt64());
 
+        serviceWorkerGlobalScope->setHasPendingSilentPushEvent(true);
+
         auto pushEvent = PushEvent::create(eventNames().pushEvent, { }, WTFMove(data), ExtendableEvent::IsTrusted::Yes);
         serviceWorkerGlobalScope->dispatchEvent(pushEvent);
 
-        pushEvent->whenAllExtendLifetimePromisesAreSettled([weakThis = WTFMove(weakThis), callback = WTFMove(callback)](auto&& extendLifetimePromises) mutable {
+        pushEvent->whenAllExtendLifetimePromisesAreSettled([serviceWorkerGlobalScope, eventCreationTime = pushEvent->timeStamp(), callback = WTFMove(callback)](auto&& extendLifetimePromises) mutable {
             bool hasRejectedAnyPromise = false;
             for (auto& promise : extendLifetimePromises) {
                 if (promise->status() == DOMPromise::Status::Rejected) {
@@ -235,7 +237,13 @@
                     break;
                 }
             }
-            callback(!hasRejectedAnyPromise);
+
+            bool showedNotification = !serviceWorkerGlobalScope->hasPendingSilentPushEvent();
+            bool success = !hasRejectedAnyPromise && showedNotification;
+
+            RELEASE_LOG_ERROR_IF(!success, ServiceWorker, "ServiceWorkerThread::queueTaskToFirePushEvent failed to process push event (rejectedPromise = %d, showedNotification = %d)", hasRejectedAnyPromise, showedNotification);
+
+            callback(success);
         });
     });
 }

Modified: trunk/Source/WebKit/ChangeLog (290814 => 290815)


--- trunk/Source/WebKit/ChangeLog	2022-03-04 04:40:06 UTC (rev 290814)
+++ trunk/Source/WebKit/ChangeLog	2022-03-04 05:28:22 UTC (rev 290815)
@@ -1,5 +1,36 @@
 2022-03-03  Ben Nham  <[email protected]>
 
+        Enforce silent push quota
+        https://bugs.webkit.org/show_bug.cgi?id=236863
+
+        Reviewed by Brady Eidson.
+
+         - If a push event doesn't result in a notification or if the associated promise rejects,
+           then we increment the silent push count associated with that origin.
+         - PushService now removes a subscription if the origin has reached its quota of silent
+           pushes.
+
+        Covered by existing and new API tests.
+
+        * NetworkProcess/NetworkProcess.cpp:
+        (WebKit::NetworkProcess::processPushMessage):
+        * NetworkProcess/Notifications/NetworkNotificationManager.cpp:
+        (WebKit::NetworkNotificationManager::incrementSilentPushCount):
+        (WebKit::ReplyCaller<int>::callReply):
+        * Shared/WebPushDaemonConstants.h:
+        (WebKit::WebPushD::messageTypeSendsReply):
+        * UIProcess/API/Cocoa/WKProcessPool.mm:
+        (-[WKProcessPool _notificationManagerForTesting]):
+        * webpushd/PushService.mm:
+        (WebPushD::updateTopicLists):
+        (WebPushD::PushService::incrementSilentPushCount):
+        * webpushd/WebPushDaemon.mm:
+        (WebPushD::MessageInfo::incrementSilentPushCount::encodeReply):
+        (WebPushD::Daemon::decodeAndHandleMessage):
+        (WebPushD::Daemon::incrementSilentPushCount):
+
+2022-03-03  Ben Nham  <[email protected]>
+
         Allow webpushd to launch browser in background
         https://bugs.webkit.org/show_bug.cgi?id=237114
 

Modified: trunk/Source/WebKit/NetworkProcess/NetworkProcess.cpp (290814 => 290815)


--- trunk/Source/WebKit/NetworkProcess/NetworkProcess.cpp	2022-03-04 04:40:06 UTC (rev 290814)
+++ trunk/Source/WebKit/NetworkProcess/NetworkProcess.cpp	2022-03-04 05:28:22 UTC (rev 290815)
@@ -2232,10 +2232,10 @@
         session->storageManager().moveData(dataTypes, oldOrigin, newOrigin, [aggregator] { });
 }
 
-#if ENABLE(SERVICE_WORKER)
+#if ENABLE(SERVICE_WORKER) && ENABLE(BUILT_IN_NOTIFICATIONS)
+
 void NetworkProcess::getPendingPushMessages(PAL::SessionID sessionID, CompletionHandler<void(const Vector<WebPushMessage>&)>&& callback)
 {
-#if ENABLE(BUILT_IN_NOTIFICATIONS)
     if (auto* session = networkSession(sessionID)) {
         LOG(Notifications, "NetworkProcess getting pending push messages for session ID %" PRIu64, sessionID.toUInt64());
         session->notificationManager().getPendingPushMessages(WTFMove(callback));
@@ -2242,8 +2242,6 @@
         return;
     } else
         LOG(Notifications, "NetworkProcess could not find session for ID %llu to get pending push messages", sessionID.toUInt64());
-#endif
-    callback({ });
 }
 
 void NetworkProcess::processPushMessage(PAL::SessionID sessionID, WebPushMessage&& pushMessage, CompletionHandler<void(bool)>&& callback)
@@ -2250,12 +2248,36 @@
 {
     if (auto* session = networkSession(sessionID)) {
         LOG(Push, "Networking process handling a push message from UI process in session %llu", sessionID.toUInt64());
-        session->ensureSWServer().processPushMessage(WTFMove(pushMessage.pushData), WTFMove(pushMessage.registrationURL), WTFMove(callback));
+        auto origin = SecurityOriginData::fromURL(pushMessage.registrationURL);
+        session->ensureSWServer().processPushMessage(WTFMove(pushMessage.pushData), WTFMove(pushMessage.registrationURL), [this, protectedThis = Ref { *this }, sessionID, origin = WTFMove(origin), callback = WTFMove(callback)](bool result) mutable {
+            NetworkSession* session;
+            if (!result && (session = networkSession(sessionID))) {
+                session->notificationManager().incrementSilentPushCount(WTFMove(origin), [callback = WTFMove(callback), result](unsigned) mutable {
+                    callback(result);
+                });
+                return;
+            }
+
+            callback(result);
+        });
     } else
         LOG(Push, "Networking process asked to handle a push message from UI process in session %llu, but that session doesn't exist", sessionID.toUInt64());
 }
-#endif // ENABLE(SERVICE_WORKER)
 
+#else
+
+void NetworkProcess::getPendingPushMessages(PAL::SessionID, CompletionHandler<void(const Vector<WebPushMessage>&)>&& callback)
+{
+    callback({ });
+}
+
+void NetworkProcess::processPushMessage(PAL::SessionID, WebPushMessage&&, CompletionHandler<void(bool)>&& callback)
+{
+    callback(false);
+}
+
+#endif // ENABLE(SERVICE_WORKER) && ENABLE(BUILT_IN_NOTIFICATIONS)
+
 void NetworkProcess::deletePushAndNotificationRegistration(PAL::SessionID sessionID, const SecurityOriginData& origin, CompletionHandler<void(const String&)>&& callback)
 {
 #if ENABLE(BUILT_IN_NOTIFICATIONS)

Modified: trunk/Source/WebKit/NetworkProcess/Notifications/NetworkNotificationManager.cpp (290814 => 290815)


--- trunk/Source/WebKit/NetworkProcess/Notifications/NetworkNotificationManager.cpp	2022-03-04 04:40:06 UTC (rev 290814)
+++ trunk/Source/WebKit/NetworkProcess/Notifications/NetworkNotificationManager.cpp	2022-03-04 05:28:22 UTC (rev 290815)
@@ -175,6 +175,16 @@
     sendMessageWithReply<WebPushD::MessageType::GetPushPermissionState>(WTFMove(completionHandler), WTFMove(scopeURL));
 }
 
+void NetworkNotificationManager::incrementSilentPushCount(WebCore::SecurityOriginData&& origin, CompletionHandler<void(unsigned)>&& completionHandler)
+{
+    if (!m_connection) {
+        completionHandler(0);
+        return;
+    }
+
+    sendMessageWithReply<WebPushD::MessageType::IncrementSilentPushCount>(WTFMove(completionHandler), WTFMove(origin));
+}
+
 template<WebPushD::MessageType messageType, typename... Args>
 void NetworkNotificationManager::sendMessage(Args&&... args) const
 {
@@ -228,6 +238,17 @@
     }
 };
 
+template<> struct ReplyCaller<unsigned> {
+    static void callReply(Daemon::Decoder&& decoder, CompletionHandler<void(bool)>&& completionHandler)
+    {
+        std::optional<int> value;
+        decoder >> value;
+        if (!value)
+            return completionHandler(0);
+        completionHandler(*value);
+    }
+};
+
 template<> struct ReplyCaller<Vector<String>&&> {
     static void callReply(Daemon::Decoder&& decoder, CompletionHandler<void(Vector<String>&&)>&& completionHandler)
     {

Modified: trunk/Source/WebKit/NetworkProcess/Notifications/NetworkNotificationManager.h (290814 => 290815)


--- trunk/Source/WebKit/NetworkProcess/Notifications/NetworkNotificationManager.h	2022-03-04 04:40:06 UTC (rev 290814)
+++ trunk/Source/WebKit/NetworkProcess/Notifications/NetworkNotificationManager.h	2022-03-04 05:28:22 UTC (rev 290815)
@@ -61,6 +61,7 @@
     void unsubscribeFromPushService(URL&& scopeURL, WebCore::PushSubscriptionIdentifier, CompletionHandler<void(Expected<bool, WebCore::ExceptionData>&&)>&&);
     void getPushSubscription(URL&& scopeURL, CompletionHandler<void(Expected<std::optional<WebCore::PushSubscriptionData>, WebCore::ExceptionData>&&)>&&);
     void getPushPermissionState(URL&& scopeURL, CompletionHandler<void(Expected<uint8_t, WebCore::ExceptionData>&&)>&&);
+    void incrementSilentPushCount(WebCore::SecurityOriginData&&, CompletionHandler<void(unsigned)>&&);
 
 private:
     NetworkNotificationManager(NetworkSession&, const String& webPushMachServiceName);

Modified: trunk/Source/WebKit/Shared/WebPushDaemonConstants.h (290814 => 290815)


--- trunk/Source/WebKit/Shared/WebPushDaemonConstants.h	2022-03-04 04:40:06 UTC (rev 290814)
+++ trunk/Source/WebKit/Shared/WebPushDaemonConstants.h	2022-03-04 05:28:22 UTC (rev 290815)
@@ -27,6 +27,9 @@
 
 namespace WebKit::WebPushD {
 
+// If an origin processes more than this many silent pushes, then it will be unsubscribed from push.
+constexpr unsigned maxSilentPushCount = 3;
+
 constexpr const char* protocolVersionKey = "protocol version";
 constexpr uint64_t protocolVersionValue = 1;
 constexpr const char* protocolEncodedMessageKey = "encoded message";
@@ -48,7 +51,8 @@
     SubscribeToPushService,
     UnsubscribeFromPushService,
     GetPushSubscription,
-    GetPushPermissionState
+    GetPushPermissionState,
+    IncrementSilentPushCount,
 };
 
 inline bool messageTypeSendsReply(MessageType messageType)
@@ -65,6 +69,7 @@
     case MessageType::UnsubscribeFromPushService:
     case MessageType::GetPushSubscription:
     case MessageType::GetPushPermissionState:
+    case MessageType::IncrementSilentPushCount:
         return true;
     case MessageType::SetDebugModeIsEnabled:
     case MessageType::UpdateConnectionConfiguration:

Modified: trunk/Source/WebKit/UIProcess/API/Cocoa/WKProcessPool.mm (290814 => 290815)


--- trunk/Source/WebKit/UIProcess/API/Cocoa/WKProcessPool.mm	2022-03-04 04:40:06 UTC (rev 290814)
+++ trunk/Source/WebKit/UIProcess/API/Cocoa/WKProcessPool.mm	2022-03-04 05:28:22 UTC (rev 290815)
@@ -33,6 +33,7 @@
 #import "Logging.h"
 #import "SandboxUtilities.h"
 #import "UIGamepadProvider.h"
+#import "WKAPICast.h"
 #import "WKDownloadInternal.h"
 #import "WKObject.h"
 #import "WKWebViewInternal.h"
@@ -41,6 +42,7 @@
 #import "WebBackForwardCache.h"
 #import "WebCertificateInfo.h"
 #import "WebCookieManagerProxy.h"
+#import "WebNotificationManagerProxy.h"
 #import "WebProcessCache.h"
 #import "WebProcessMessages.h"
 #import "WebProcessPool.h"
@@ -612,4 +614,9 @@
     _processPool->terminateAllWebContentProcesses();
 }
 
+- (WKNotificationManagerRef)_notificationManagerForTesting
+{
+    return WebKit::toAPI(_processPool->supplement<WebKit::WebNotificationManagerProxy>());
+}
+
 @end

Modified: trunk/Source/WebKit/UIProcess/API/Cocoa/WKProcessPoolPrivate.h (290814 => 290815)


--- trunk/Source/WebKit/UIProcess/API/Cocoa/WKProcessPoolPrivate.h	2022-03-04 04:40:06 UTC (rev 290814)
+++ trunk/Source/WebKit/UIProcess/API/Cocoa/WKProcessPoolPrivate.h	2022-03-04 05:28:22 UTC (rev 290815)
@@ -23,6 +23,7 @@
  * THE POSSIBILITY OF SUCH DAMAGE.
  */
 
+#import <WebKit/WKBase.h>
 #import <WebKit/WKProcessPool.h>
 #import <WebKit/WKSecurityOrigin.h>
 
@@ -137,6 +138,8 @@
 
 @property (nonatomic, getter=_isCookieStoragePartitioningEnabled, setter=_setCookieStoragePartitioningEnabled:) BOOL _cookieStoragePartitioningEnabled WK_API_DEPRECATED("Partitioned cookies are no longer supported", macos(10.12.3, 10.14.4), ios(10.3, 12.2));
 
+- (WKNotificationManagerRef)_notificationManagerForTesting;
+
 // Test only.
 - (void)_seedResourceLoadStatisticsForTestingWithFirstParty:(NSURL *)firstPartyURL thirdParty:(NSURL *)thirdPartyURL shouldScheduleNotification:(BOOL)shouldScheduleNotification completionHandler:(void(^)(void))completionHandler  WK_API_AVAILABLE(macos(10.15.4), ios(13.4));
 - (void)_garbageCollectJavaScriptObjectsForTesting WK_API_AVAILABLE(macos(11.0), ios(14.0));

Modified: trunk/Source/WebKit/webpushd/PushService.h (290814 => 290815)


--- trunk/Source/WebKit/webpushd/PushService.h	2022-03-04 04:40:06 UTC (rev 290814)
+++ trunk/Source/WebKit/webpushd/PushService.h	2022-03-04 05:28:22 UTC (rev 290815)
@@ -61,6 +61,8 @@
     void subscribe(const String& bundleIdentifier, const String& scope, const Vector<uint8_t>& vapidPublicKey, CompletionHandler<void(const Expected<WebCore::PushSubscriptionData, WebCore::ExceptionData>&)>&&);
     void unsubscribe(const String& bundleIdentifier, const String& scope, WebCore::PushSubscriptionIdentifier, CompletionHandler<void(const Expected<bool, WebCore::ExceptionData>&)>&&);
 
+    void incrementSilentPushCount(const String& bundleIdentifier, const String& securityOrigin, CompletionHandler<void(unsigned)>&&);
+
     void didCompleteGetSubscriptionRequest(GetSubscriptionRequest&);
     void didCompleteSubscribeRequest(SubscribeRequest&);
     void didCompleteUnsubscribeRequest(UnsubscribeRequest&);

Modified: trunk/Source/WebKit/webpushd/PushService.mm (290814 => 290815)


--- trunk/Source/WebKit/webpushd/PushService.mm	2022-03-04 04:40:06 UTC (rev 290814)
+++ trunk/Source/WebKit/webpushd/PushService.mm	2022-03-04 05:28:22 UTC (rev 290815)
@@ -29,6 +29,7 @@
 #import "ApplePushServiceConnection.h"
 #import "MockPushServiceConnection.h"
 #import "Logging.h"
+#import "WebPushDaemonConstants.h"
 #import <Foundation/Foundation.h>
 #import <WebCore/PushMessageCrypto.h>
 #import <WebCore/SecurityOrigin.h>
@@ -44,19 +45,9 @@
 
 static void updateTopicLists(PushServiceConnection& connection, PushDatabase& database, CompletionHandler<void()> completionHandler)
 {
-    database.getTopicsByWakeState([&connection, completionHandler = WTFMove(completionHandler)](auto&& topicMap) mutable {
+    database.getTopics([&connection, completionHandler = WTFMove(completionHandler)](auto&& topics) mutable {
         // FIXME: move topics to ignored list based on user preferences.
-        PushServiceConnection::TopicLists topicLists;
-
-        if (auto it = topicMap.find(PushWakeState::Waking); it != topicMap.end())
-            std::swap(topicLists.enabledTopics, it->value);
-        if (auto it = topicMap.find(PushWakeState::Opportunistic); it != topicMap.end())
-            std::swap(topicLists.opportunisticTopics, it->value);
-        if (auto it = topicMap.find(PushWakeState::NonWaking); it != topicMap.end())
-            std::swap(topicLists.nonWakingTopics, it->value);
-
-        connection.setTopicLists(WTFMove(topicLists));
-
+        connection.setEnabledTopics(WTFMove(topics));
         completionHandler();
     });
 }
@@ -482,6 +473,31 @@
     finishedPushServiceRequest(m_unsubscribeRequests, request);
 }
 
+void PushService::incrementSilentPushCount(const String& bundleIdentifier, const String& securityOrigin, CompletionHandler<void(unsigned)>&& handler)
+{
+    m_database->incrementSilentPushCount(bundleIdentifier, securityOrigin, [this, bundleIdentifier, securityOrigin, handler = WTFMove(handler)](unsigned silentPushCount) mutable {
+        if (silentPushCount < WebKit::WebPushD::maxSilentPushCount) {
+            handler(silentPushCount);
+            return;
+        }
+
+        RELEASE_LOG(Push, "Removing all subscriptions associated with %{public}s %{sensitive}s since it processed %u silent pushes", bundleIdentifier.utf8().data(), securityOrigin.utf8().data(), silentPushCount);
+
+        m_database->removeRecordsByBundleIdentifierAndSecurityOrigin(bundleIdentifier, securityOrigin, [this, bundleIdentifier, securityOrigin, silentPushCount, handler = WTFMove(handler)](auto&& removedRecords) mutable {
+            for (auto& record : removedRecords) {
+                m_connection->unsubscribe(record.topic, record.serverVAPIDPublicKey, [topic = record.topic](bool unsubscribed, NSError* error) {
+                    if (!unsubscribed)
+                        RELEASE_LOG_ERROR(Push, "IncrementSilentPushRequest couldn't remove subscription for topic %{sensitive}s: %{public}s code: %lld)", topic.utf8().data(), error.domain.UTF8String ?: "none", static_cast<int64_t>(error.code));
+                });
+            }
+
+            updateTopicLists(m_connection, m_database, [silentPushCount, handler = WTFMove(handler)]() mutable {
+                handler(silentPushCount);
+            });
+        });
+    });
+}
+
 enum class ContentEncoding {
     Empty,
     AESGCM,

Modified: trunk/Source/WebKit/webpushd/WebPushDaemon.h (290814 => 290815)


--- trunk/Source/WebKit/webpushd/WebPushDaemon.h	2022-03-04 04:40:06 UTC (rev 290814)
+++ trunk/Source/WebKit/webpushd/WebPushDaemon.h	2022-03-04 05:28:22 UTC (rev 290815)
@@ -46,6 +46,10 @@
 enum class MessageLevel : uint8_t;
 }
 
+namespace WebCore {
+struct SecurityOriginData;
+}
+
 using WebKit::WebPushD::PushMessageForTesting;
 using WebKit::WebPushD::WebPushDaemonConnectionConfiguration;
 
@@ -80,6 +84,7 @@
     void unsubscribeFromPushService(ClientConnection*, const URL& scopeURL, WebCore::PushSubscriptionIdentifier, CompletionHandler<void(const Expected<bool, WebCore::ExceptionData>&)>&& replySender);
     void getPushSubscription(ClientConnection*, const URL& scopeURL, CompletionHandler<void(const Expected<std::optional<WebCore::PushSubscriptionData>, WebCore::ExceptionData>&)>&& replySender);
     void getPushPermissionState(ClientConnection*, const URL& scopeURL, CompletionHandler<void(const Expected<uint8_t, WebCore::ExceptionData>&)>&& replySender);
+    void incrementSilentPushCount(ClientConnection*, const WebCore::SecurityOriginData&, CompletionHandler<void(unsigned)>&&);
 
     void broadcastDebugMessage(JSC::MessageLevel, const String&);
     void broadcastAllConnectionIdentities();

Modified: trunk/Source/WebKit/webpushd/WebPushDaemon.mm (290814 => 290815)


--- trunk/Source/WebKit/webpushd/WebPushDaemon.mm	2022-03-04 04:40:06 UTC (rev 290814)
+++ trunk/Source/WebKit/webpushd/WebPushDaemon.mm	2022-03-04 05:28:22 UTC (rev 290815)
@@ -35,6 +35,7 @@
 #import "MockAppBundleRegistry.h"
 
 #import <WebCore/PushPermissionState.h>
+#import <WebCore/SecurityOriginData.h>
 #import <pal/spi/cocoa/LaunchServicesSPI.h>
 #import <wtf/CompletionHandler.h>
 #import <wtf/HexNumber.h>
@@ -118,6 +119,11 @@
 REPLY(const Expected<uint8_t, WebCore::ExceptionData>&)
 END
 
+FUNCTION(incrementSilentPushCount)
+ARGUMENTS(WebCore::SecurityOriginData)
+REPLY(unsigned)
+END
+
 #undef FUNCTION
 #undef ARGUMENTS
 #undef REPLY
@@ -200,6 +206,13 @@
     return encoder.takeBuffer();
 }
 
+WebPushD::EncodedMessage incrementSilentPushCount::encodeReply(unsigned reply)
+{
+    WebKit::Daemon::Encoder encoder;
+    encoder << reply;
+    return encoder.takeBuffer();
+}
+
 } // namespace MessageInfo
 
 template<typename Info>
@@ -402,6 +415,9 @@
     case MessageType::GetPushPermissionState:
         handleWebPushDMessageWithReply<MessageInfo::getPushPermissionState>(clientConnection, encodedMessage, WTFMove(replySender));
         break;
+    case MessageType::IncrementSilentPushCount:
+        handleWebPushDMessageWithReply<MessageInfo::incrementSilentPushCount>(clientConnection, encodedMessage, WTFMove(replySender));
+        break;
     }
 }
 
@@ -628,6 +644,18 @@
     replySender(static_cast<uint8_t>(WebCore::PushPermissionState::Denied));
 }
 
+void Daemon::incrementSilentPushCount(ClientConnection* connection, const WebCore::SecurityOriginData& securityOrigin, CompletionHandler<void(unsigned)>&& replySender)
+{
+    runAfterStartingPushService([this, bundleIdentifier = connection->hostAppCodeSigningIdentifier(), securityOrigin = securityOrigin.toString(), replySender = WTFMove(replySender)]() mutable {
+        if (!m_pushService) {
+            replySender(0);
+            return;
+        }
+
+        m_pushService->incrementSilentPushCount(bundleIdentifier, securityOrigin, WTFMove(replySender));
+    });
+}
+
 ClientConnection* Daemon::toClientConnection(xpc_connection_t connection)
 {
     auto clientConnection = m_connectionMap.get(connection);

Modified: trunk/Tools/ChangeLog (290814 => 290815)


--- trunk/Tools/ChangeLog	2022-03-04 04:40:06 UTC (rev 290814)
+++ trunk/Tools/ChangeLog	2022-03-04 05:28:22 UTC (rev 290815)
@@ -1,3 +1,31 @@
+2022-03-03  Ben Nham  <[email protected]>
+
+        Enforce silent push quota
+        https://bugs.webkit.org/show_bug.cgi?id=236863
+
+        Reviewed by Brady Eidson.
+
+        Modified existing push tests to show a notification. Added a new test case to make sure that
+        subscriptions are removed when an origin reaches its quota of silent pushes.
+
+        * TestWebKitAPI/TestNotificationProvider.cpp:
+        (TestWebKitAPI::notificationPermissions):
+        (TestWebKitAPI::TestNotificationProvider::TestNotificationProvider):
+        (TestWebKitAPI::TestNotificationProvider::~TestNotificationProvider):
+        (TestWebKitAPI::TestNotificationProvider::notificationPermissions):
+        (TestWebKitAPI::TestNotificationProvider::setPermission):
+        * TestWebKitAPI/Tests/WebCore/PushDatabase.cpp:
+        (TestWebKitAPI::makeTemporaryDatabasePath):
+        (TestWebKitAPI::getTopicsSync):
+        (TestWebKitAPI::PushDatabaseTest::getTopics):
+        (TestWebKitAPI::PushDatabaseTest::removeRecordsByBundleIdentifierAndSecurityOrigin):
+        (TestWebKitAPI::PushDatabaseTest::incrementSilentPushCount):
+        (TestWebKitAPI::operator==):
+        (TestWebKitAPI::TEST_F):
+        (TestWebKitAPI::TEST):
+        * TestWebKitAPI/cocoa/HTTPServer.mm:
+        (TestWebKitAPI::HTTPServer::origin const):
+
 2022-03-03  Dewei Zhu  <[email protected]>
 
         'run-benchmark' script should log http requests during benchmark run.

Modified: trunk/Tools/TestWebKitAPI/Sources.txt (290814 => 290815)


--- trunk/Tools/TestWebKitAPI/Sources.txt	2022-03-04 04:40:06 UTC (rev 290814)
+++ trunk/Tools/TestWebKitAPI/Sources.txt	2022-03-04 05:28:22 UTC (rev 290815)
@@ -25,5 +25,6 @@
 _javascript_Test.cpp
 PlatformUtilities.cpp
 TestsController.cpp
+TestNotificationProvider.cpp
 TestUtilities.cpp
 

Added: trunk/Tools/TestWebKitAPI/TestNotificationProvider.cpp (0 => 290815)


--- trunk/Tools/TestWebKitAPI/TestNotificationProvider.cpp	                        (rev 0)
+++ trunk/Tools/TestWebKitAPI/TestNotificationProvider.cpp	2022-03-04 05:28:22 UTC (rev 290815)
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2022 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "TestNotificationProvider.h"
+
+#include "WTFStringUtilities.h"
+#include <WebKit/WKNotificationManager.h>
+#include <WebKit/WKNumber.h>
+#include <WebKit/WKSecurityOriginRef.h>
+#include <WebKit/WKString.h>
+#include <wtf/text/WTFString.h>
+
+namespace TestWebKitAPI {
+
+static WKDictionaryRef notificationPermissions(const void* clientInfo)
+{
+    return static_cast<TestNotificationProvider*>(const_cast<void*>(clientInfo))->notificationPermissions();
+}
+
+TestNotificationProvider::TestNotificationProvider(Vector<WKNotificationManagerRef>&& managers)
+    : m_managers(WTFMove(managers))
+    , m_permissions(adoptWK(WKMutableDictionaryCreate()))
+{
+    m_provider = {
+        { 0, this },
+        0, // showWebNotification
+        0, // closeWebNotification
+        0, // didDestroyNotification
+        0, // addNotificationManager
+        0, // removeNotificationManager
+        &TestWebKitAPI::notificationPermissions,
+        0, // clearNotifications
+    };
+
+    for (auto& manager : m_managers)
+        WKNotificationManagerSetProvider(manager, &m_provider.base);
+}
+
+TestNotificationProvider::~TestNotificationProvider()
+{
+    for (auto& manager : m_managers)
+        WKNotificationManagerSetProvider(manager, nullptr);
+}
+
+WKDictionaryRef TestNotificationProvider::notificationPermissions() const
+{
+    WKRetain(m_permissions.get());
+    return m_permissions.get();
+}
+
+void TestNotificationProvider::setPermission(const String& origin, bool allowed)
+{
+    auto wkAllowed = adoptWK(WKBooleanCreate(allowed));
+    auto wkOriginString = adoptWK(WKStringCreateWithUTF8CString(origin.utf8().data()));
+    WKDictionarySetItem(m_permissions.get(), wkOriginString.get(), wkAllowed.get());
+
+    auto wkOrigin = adoptWK(WKSecurityOriginCreateFromString(wkOriginString.get()));
+    for (auto& manager : m_managers)
+        WKNotificationManagerProviderDidUpdateNotificationPolicy(manager, wkOrigin.get(), allowed);
+}
+
+} // namespace TestWebKitAPI

Added: trunk/Tools/TestWebKitAPI/TestNotificationProvider.h (0 => 290815)


--- trunk/Tools/TestWebKitAPI/TestNotificationProvider.h	                        (rev 0)
+++ trunk/Tools/TestWebKitAPI/TestNotificationProvider.h	2022-03-04 05:28:22 UTC (rev 290815)
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2022 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <WebKit/WKMutableDictionary.h>
+#include <WebKit/WKNotificationManager.h>
+#include <WebKit/WKNotificationProvider.h>
+#include <WebKit/WKRetainPtr.h>
+#include <wtf/FastMalloc.h>
+#include <wtf/Vector.h>
+
+namespace TestWebKitAPI {
+
+class TestNotificationProvider {
+    WTF_MAKE_FAST_ALLOCATED;
+public:
+    explicit TestNotificationProvider(Vector<WKNotificationManagerRef>&&);
+    ~TestNotificationProvider();
+
+    WKDictionaryRef notificationPermissions() const;
+    void setPermission(const String& origin, bool allowed);
+
+private:
+    Vector<WKNotificationManagerRef> m_managers;
+    WKRetainPtr<WKMutableDictionaryRef> m_permissions;
+    WKNotificationProviderV0 m_provider;
+};
+
+}

Modified: trunk/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj (290814 => 290815)


--- trunk/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj	2022-03-04 04:40:06 UTC (rev 290814)
+++ trunk/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj	2022-03-04 05:28:22 UTC (rev 290815)
@@ -3002,6 +3002,8 @@
 		E5AA8D1C25151CC60051CC45 /* DateInputTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = DateInputTests.mm; sourceTree = "<group>"; };
 		EB230D3D245E722E00C66AD1 /* IDBCheckpointWAL.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = IDBCheckpointWAL.html; sourceTree = "<group>"; };
 		EB230D3E245E726300C66AD1 /* IDBCheckpointWAL.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = IDBCheckpointWAL.mm; sourceTree = "<group>"; };
+		EB4D320727C045430081A8E4 /* TestNotificationProvider.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = TestNotificationProvider.cpp; sourceTree = "<group>"; };
+		EB4D320827C045440081A8E4 /* TestNotificationProvider.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TestNotificationProvider.h; sourceTree = "<group>"; };
 		EB9AD8C627646E7300D893A4 /* PushDatabase.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PushDatabase.cpp; sourceTree = "<group>"; };
 		EBA75C48275ED7BE00D6D31C /* PushMessageCrypto.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = PushMessageCrypto.cpp; sourceTree = "<group>"; };
 		EC79F168BE454E579E417B05 /* Markable.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Markable.cpp; sourceTree = "<group>"; };
@@ -3290,6 +3292,8 @@
 				BC131883117114A800B69727 /* PlatformUtilities.h */,
 				BC90951B125533D700083756 /* PlatformWebView.h */,
 				BCB9E7FA112359A300A137E0 /* Test.h */,
+				EB4D320727C045430081A8E4 /* TestNotificationProvider.cpp */,
+				EB4D320827C045440081A8E4 /* TestNotificationProvider.h */,
 				BC131AA8117131FC00B69727 /* TestsController.cpp */,
 				BCB9E7C711234E3A00A137E0 /* TestsController.h */,
 				7BB754B0274E39A100D00EC1 /* TestUtilities.cpp */,

Modified: trunk/Tools/TestWebKitAPI/Tests/WebCore/PushDatabase.cpp (290814 => 290815)


--- trunk/Tools/TestWebKitAPI/Tests/WebCore/PushDatabase.cpp	2022-03-04 04:40:06 UTC (rev 290814)
+++ trunk/Tools/TestWebKitAPI/Tests/WebCore/PushDatabase.cpp	2022-03-04 05:28:22 UTC (rev 290815)
@@ -35,18 +35,26 @@
 
 namespace TestWebKitAPI {
 
-static PushDatabase::PushWakeStateToTopicMap getTopicsByWakeStateSync(PushDatabase& database)
+static String makeTemporaryDatabasePath()
 {
+    FileSystem::PlatformFileHandle handle;
+    auto path = FileSystem::openTemporaryFile("PushDatabase", handle, ".db");
+    FileSystem::closeFile(handle);
+    return path;
+}
+
+static Vector<String> getTopicsSync(PushDatabase& database)
+{
     bool done = false;
-    PushDatabase::PushWakeStateToTopicMap getResult;
+    Vector<String> topics;
 
-    database.getTopicsByWakeState([&done, &getResult](PushDatabase::PushWakeStateToTopicMap&& result) {
-        getResult = WTFMove(result);
+    database.getTopics([&done, &topics](auto&& result) {
+        topics = WTFMove(result);
         done = true;
     });
     Util::run(&done);
 
-    return getResult;
+    return topics;
 }
 
 class PushDatabaseTest : public testing::Test {
@@ -55,34 +63,45 @@
 
     PushRecord record1 {
         PushSubscriptionIdentifier(),
-        "com.apple.Safari"_s,
-        "https://www.apple.com"_s,
-        "https://www.apple.com/mac"_s,
-        "https://pushEndpoint1"_s,
-        "topic1"_s,
-        { 0, 1 },
-        { 1, 2 },
-        { 2, 3 },
-        { 4, 5 },
-        convertSecondsToEpochTimeStamp(1643350000),
-        PushWakeState::Opportunistic
-    };
-    PushRecord record2 {
-        PushSubscriptionIdentifier(),
         "com.apple.webapp"_s,
         "https://www.apple.com"_s,
         "https://www.apple.com/iphone"_s,
         "https://pushEndpoint2"_s,
-        "topic2"_s,
+        "topic1"_s,
         { 5, 6 },
         { 6, 7 },
         { 7, 8 },
         { 8, 9 }
     };
+    PushRecord record2 {
+        PushSubscriptionIdentifier(),
+        "com.apple.Safari"_s,
+        "https://www.webkit.org"_s,
+        "https://www.webkit.org/blog"_s,
+        "https://pushEndpoint4"_s,
+        "topic4"_s,
+        { 14, 15 },
+        { 16, 17 },
+        { 18, 19 },
+        { 20, 21 }
+    };
     PushRecord record3 {
         PushSubscriptionIdentifier(),
         "com.apple.Safari"_s,
         "https://www.apple.com"_s,
+        "https://www.apple.com/mac"_s,
+        "https://pushEndpoint1"_s,
+        "topic2"_s,
+        { 0, 1 },
+        { 1, 2 },
+        { 2, 3 },
+        { 4, 5 },
+        convertSecondsToEpochTimeStamp(1643350000),
+    };
+    PushRecord record4 {
+        PushSubscriptionIdentifier(),
+        "com.apple.Safari"_s,
+        "https://www.apple.com"_s,
         "https://www.apple.com/iphone"_s,
         "https://pushEndpoint3"_s,
         "topic3"_s,
@@ -95,6 +114,7 @@
     std::optional<PushRecord> insertResult1;
     std::optional<PushRecord> insertResult2;
     std::optional<PushRecord> insertResult3;
+    std::optional<PushRecord> insertResult4;
 
     std::optional<PushRecord> insertRecord(const PushRecord& record)
     {
@@ -167,15 +187,57 @@
         return rowIdentifiers;
     }
 
-    PushDatabase::PushWakeStateToTopicMap getTopicsByWakeState()
+    Vector<String> getTopics()
     {
-        return getTopicsByWakeStateSync(*db);
+        return getTopicsSync(*db);
     }
 
+    Vector<RemovedPushRecord> removeRecordsByBundleIdentifier(const String& bundleID)
+    {
+        bool done = false;
+        Vector<RemovedPushRecord> removedRecords;
+
+        db->removeRecordsByBundleIdentifier(bundleID, [&done, &removedRecords](auto&& result) {
+            removedRecords = WTFMove(result);
+            done = true;
+        });
+        Util::run(&done);
+
+        return removedRecords;
+    }
+
+    Vector<RemovedPushRecord> removeRecordsByBundleIdentifierAndSecurityOrigin(const String& bundleID, const String& securityOrigin)
+    {
+        bool done = false;
+        Vector<RemovedPushRecord> removedRecords;
+
+        db->removeRecordsByBundleIdentifierAndSecurityOrigin(bundleID, securityOrigin, [&done, &removedRecords](auto&& result) {
+            removedRecords = WTFMove(result);
+            done = true;
+        });
+        Util::run(&done);
+
+        return removedRecords;
+    }
+
+    unsigned incrementSilentPushCount(const String& bundleID, const String& securityOrigin)
+    {
+        bool done = false;
+        unsigned count = 0;
+
+        db->incrementSilentPushCount(bundleID, securityOrigin, [&done, &count](int result) {
+            count = result;
+            done = true;
+        });
+        Util::run(&done);
+
+        return count;
+    }
+
     void SetUp() final
     {
         bool done = false;
-        PushDatabase::create(SQLiteDatabase::inMemoryPath(), [this, &done](std::unique_ptr<PushDatabase>&& database) {
+        PushDatabase::create(makeTemporaryDatabasePath(), [this, &done](std::unique_ptr<PushDatabase>&& database) {
             db = WTFMove(database);
             done = true;
         });
@@ -185,6 +247,7 @@
         ASSERT_TRUE(insertResult1 = insertRecord(record1));
         ASSERT_TRUE(insertResult2 = insertRecord(record2));
         ASSERT_TRUE(insertResult3 = insertRecord(record3));
+        ASSERT_TRUE(insertResult4 = insertRecord(record4));
     }
 };
 
@@ -200,8 +263,7 @@
         && a.clientPublicKey == b.clientPublicKey
         && a.clientPrivateKey == b.clientPrivateKey
         && a.sharedAuthSecret == b.sharedAuthSecret
-        && a.expirationTime == b.expirationTime
-        && a.wakeState == b.wakeState;
+        && a.expirationTime == b.expirationTime;
 }
 
 TEST_F(PushDatabaseTest, InsertRecord)
@@ -214,19 +276,20 @@
     expectedRecord2.identifier = makeObjectIdentifier<PushSubscriptionIdentifierType>(2);
     EXPECT_TRUE(expectedRecord2 == *insertResult2);
 
-    // Since record3 is in the same SubscriptionSet as record1, it should
-    // inherit record1's wake state.
     auto expectedRecord3 = record3;
     expectedRecord3.identifier = makeObjectIdentifier<PushSubscriptionIdentifierType>(3);
-    expectedRecord3.wakeState = expectedRecord1.wakeState;
     EXPECT_TRUE(expectedRecord3 == *insertResult3);
 
+    auto expectedRecord4 = record4;
+    expectedRecord4.identifier = makeObjectIdentifier<PushSubscriptionIdentifierType>(4);
+    EXPECT_TRUE(expectedRecord4 == *insertResult4);
+
     // Inserting a record with the same (bundleID, scope) as record 1 should fail.
-    PushRecord record4 = record1;
-    record4.endpoint = "https://www.webkit.org"_s;
-    EXPECT_FALSE(insertRecord(WTFMove(record4)));
+    PushRecord record5 = record1;
+    record4.endpoint = "https://pushEndpoint5"_s;
+    EXPECT_FALSE(insertRecord(WTFMove(record5)));
 
-    EXPECT_EQ(getRowIdentifiers(), (HashSet<uint64_t> { 1, 2, 3 }));
+    EXPECT_EQ(getRowIdentifiers(), (HashSet<uint64_t> { 1, 2, 3, 4 }));
 }
 
 TEST_F(PushDatabaseTest, RemoveRecord)
@@ -233,9 +296,61 @@
 {
     EXPECT_TRUE(removeRecordByRowIdentifier(1));
     EXPECT_FALSE(removeRecordByRowIdentifier(1));
-    EXPECT_EQ(getRowIdentifiers(), (HashSet<uint64_t> { 2, 3 }));
+    EXPECT_EQ(getRowIdentifiers(), (HashSet<uint64_t> { 2, 3, 4 }));
 }
 
+TEST_F(PushDatabaseTest, RemoveRecordsByBundleIdentifier)
+{
+    // record2, record3, and record4 have the same bundleID.
+    auto removedRecords = removeRecordsByBundleIdentifier(record2.bundleID);
+    bool containsRecord2 = removedRecords.findIf([topic = record2.topic](auto& record) {
+        return topic == record.topic;
+    }) != notFound;
+    bool containsRecord3 = removedRecords.findIf([topic = record3.topic](auto& record) {
+        return topic == record.topic;
+    }) != notFound;
+    bool containsRecord4 = removedRecords.findIf([topic = record4.topic](auto& record) {
+        return topic == record.topic;
+    }) != notFound;
+
+    EXPECT_TRUE(containsRecord2);
+    EXPECT_TRUE(containsRecord3);
+    EXPECT_TRUE(containsRecord4);
+    EXPECT_EQ(removedRecords.size(), 3u);
+    EXPECT_EQ(getRowIdentifiers(), (HashSet<uint64_t> { 1 }));
+
+    // Inserting a new record should produce a new identifier.
+    PushRecord record5 = record3;
+    auto insertResult = insertRecord(WTFMove(record5));
+    EXPECT_TRUE(insertResult);
+    EXPECT_EQ(insertResult->identifier, makeObjectIdentifier<PushSubscriptionIdentifierType>(5));
+    EXPECT_EQ(getRowIdentifiers(), (HashSet<uint64_t> { 1, 5 }));
+}
+
+TEST_F(PushDatabaseTest, RemoveRecordsByBundleIdentifierAndSecurityOrigin)
+{
+    // record3 and record4 have the same bundleID and securityOrigin.
+    auto removedRecords = removeRecordsByBundleIdentifierAndSecurityOrigin(record3.bundleID, record3.securityOrigin);
+    bool containsRecord3 = removedRecords.findIf([topic = record3.topic](auto& record) {
+        return topic == record.topic;
+    }) != notFound;
+    bool containsRecord4 = removedRecords.findIf([topic = record4.topic](auto& record) {
+        return topic == record.topic;
+    }) != notFound;
+
+    EXPECT_TRUE(containsRecord3);
+    EXPECT_TRUE(containsRecord4);
+    EXPECT_EQ(removedRecords.size(), 2u);
+    EXPECT_EQ(getRowIdentifiers(), (HashSet<uint64_t> { 1, 2 }));
+
+    // Inserting a new record should produce a new identifier.
+    PushRecord record5 = record3;
+    auto insertResult = insertRecord(WTFMove(record5));
+    EXPECT_TRUE(insertResult);
+    EXPECT_EQ(insertResult->identifier, makeObjectIdentifier<PushSubscriptionIdentifierType>(5));
+    EXPECT_EQ(getRowIdentifiers(), (HashSet<uint64_t> { 1, 2, 5 }));
+}
+
 TEST_F(PushDatabaseTest, GetRecordByTopic)
 {
     auto result1 = getRecordByTopic(record1.topic);
@@ -259,29 +374,27 @@
     EXPECT_FALSE(result3);
 }
 
-TEST_F(PushDatabaseTest, GetTopicsByWakeState)
+TEST_F(PushDatabaseTest, GetTopics)
 {
-    HashMap<String, PushWakeState> expected {
-        { "topic1", PushWakeState::Opportunistic },
-        { "topic2", PushWakeState::Waking },
-        { "topic3", PushWakeState::Opportunistic }
-    };
-    HashMap<String, PushWakeState> actual;
+    Vector<String> expected { "topic1"_s, "topic2"_s, "topic3"_s, "topic4"_s };
+    EXPECT_EQ(getTopics(), expected);
+}
 
-    for (const auto& [wakeState, topics] : getTopicsByWakeState()) {
-        for (const auto& topic : topics)
-            actual.add(topic, wakeState);
-    }
+TEST_F(PushDatabaseTest, IncrementSilentPushCount)
+{
+    auto count = incrementSilentPushCount(record1.bundleID, record1.securityOrigin);
+    EXPECT_EQ(count, 1u);
 
-    EXPECT_EQ(expected, actual);
-}
+    // record1 and record3 have different bundleID and securityOrigin.
+    count = incrementSilentPushCount(record3.bundleID, record3.securityOrigin);
+    EXPECT_EQ(count, 1u);
 
-static String makeTemporaryDatabasePath()
-{
-    FileSystem::PlatformFileHandle handle;
-    auto path = FileSystem::openTemporaryFile("PushDatabase", handle, ".db");
-    FileSystem::closeFile(handle);
-    return path;
+    // record3 and record4 have the same bundleID and securityOrigin.
+    count = incrementSilentPushCount(record4.bundleID, record4.securityOrigin);
+    EXPECT_EQ(count, 2u);
+
+    count = incrementSilentPushCount("nonexistent"_s, "nonexistent"_s);
+    EXPECT_EQ(count, 0u);
 }
 
 TEST(PushDatabase, ManyInFlightOps)
@@ -314,7 +427,6 @@
             { 2, 3 },
             { 4, 5 },
             convertSecondsToEpochTimeStamp(1643350000),
-            PushWakeState::Waking
         };
 
         for (unsigned i = 0; i < recordCount; i++) {
@@ -339,8 +451,7 @@
         Util::run(&done);
         ASSERT_TRUE(createResult);
 
-        auto getResult = getTopicsByWakeStateSync(*createResult);
-        auto topics = getResult.take(PushWakeState::Waking);
+        auto topics = getTopicsSync(*createResult);
         EXPECT_EQ(topics.size(), recordCount);
     }
 }

Modified: trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/PushAPI.mm (290814 => 290815)


--- trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/PushAPI.mm	2022-03-04 04:40:06 UTC (rev 290814)
+++ trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/PushAPI.mm	2022-03-04 05:28:22 UTC (rev 290815)
@@ -29,7 +29,10 @@
 #import "HTTPServer.h"
 #import "PlatformUtilities.h"
 #import "Test.h"
+#import "TestNotificationProvider.h"
 #import "TestWKWebView.h"
+#import <WebKit/WKNotificationProvider.h>
+#import <WebKit/WKProcessPoolPrivate.h>
 #import <WebKit/WKWebViewPrivate.h>
 #import <WebKit/WKWebsiteDataStorePrivate.h>
 #import <WebKit/_WKWebsiteDataStoreConfiguration.h>
@@ -89,6 +92,7 @@
     port.postMessage("Ready");
 });
 self.addEventListener("push", (event) => {
+    self.registration.showNotification("notification");
     try {
         if (!event.data) {
             port.postMessage("Received: null data");
@@ -138,6 +142,9 @@
 
     auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
 
+    auto provider = TestWebKitAPI::TestNotificationProvider({ [[configuration processPool] _notificationManagerForTesting], WKNotificationManagerGetSharedServiceWorkerNotificationManager() });
+    provider.setPermission(server.origin(), true);
+
     auto messageHandler = adoptNS([[PushAPIMessageHandlerWithExpectedMessage alloc] init]);
     [[configuration userContentController] addScriptMessageHandler:messageHandler.get() name:@"sw"];
 
@@ -188,6 +195,7 @@
     port.postMessage("Ready");
 });
 self.addEventListener("push", (event) => {
+    self.registration.showNotification("notification");
     if (!event.data)
         return;
     const value = event.data.text();
@@ -225,6 +233,9 @@
     auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
     clearWebsiteDataStore([configuration websiteDataStore]);
 
+    auto provider = TestWebKitAPI::TestNotificationProvider({ [[configuration processPool] _notificationManagerForTesting], WKNotificationManagerGetSharedServiceWorkerNotificationManager() });
+    provider.setPermission(server.origin(), true);
+
     auto messageHandler = adoptNS([[PushAPIMessageHandlerWithExpectedMessage alloc] init]);
     [[configuration userContentController] addScriptMessageHandler:messageHandler.get() name:@"sw"];
 
@@ -270,6 +281,9 @@
     auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
     clearWebsiteDataStore([configuration websiteDataStore]);
 
+    auto provider = TestWebKitAPI::TestNotificationProvider({ [[configuration processPool] _notificationManagerForTesting], WKNotificationManagerGetSharedServiceWorkerNotificationManager() });
+    provider.setPermission(server.origin(), true);
+
     auto messageHandler = adoptNS([[PushAPIMessageHandlerWithExpectedMessage alloc] init]);
     [[configuration userContentController] addScriptMessageHandler:messageHandler.get() name:@"sw"];
 

Modified: trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/WebPushDaemon.mm (290814 => 290815)


--- trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/WebPushDaemon.mm	2022-03-04 04:40:06 UTC (rev 290814)
+++ trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/WebPushDaemon.mm	2022-03-04 05:28:22 UTC (rev 290815)
@@ -29,10 +29,12 @@
 #import "HTTPServer.h"
 #import "PlatformUtilities.h"
 #import "Test.h"
+#import "TestNotificationProvider.h"
 #import "TestURLSchemeHandler.h"
 #import "TestWKWebView.h"
 #import "Utilities.h"
 #import <WebKit/WKPreferencesPrivate.h>
+#import <WebKit/WKProcessPoolPrivate.h>
 #import <WebKit/WKUIDelegatePrivate.h>
 #import <WebKit/WKWebsiteDataStorePrivate.h>
 #import <WebKit/WebPushDaemonConstants.h>
@@ -426,10 +428,6 @@
 
         m_notificationMessageHandler = adoptNS([[NotificationScriptMessageHandler alloc] init]);
         [[m_configuration userContentController] addScriptMessageHandler:m_notificationMessageHandler.get() name:@"note"];
-
-        m_webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:m_configuration.get()]);
-        m_uiDelegate = adoptNS([[NotificationPermissionDelegate alloc] init]);
-        [m_webView setUIDelegate:m_uiDelegate.get()];
     }
 
     void loadRequest(const char* htmlSource, const char* serviceWorkerScriptSource)
@@ -444,6 +442,12 @@
             { "/sw.js", { { { "Content-Type", "application/_javascript_" } }, serviceWorkerScriptSource } }
         }, TestWebKitAPI::HTTPServer::Protocol::Http));
 
+        m_notificationProvider = makeUnique<TestWebKitAPI::TestNotificationProvider>(Vector<WKNotificationManagerRef> { [[m_configuration processPool] _notificationManagerForTesting], WKNotificationManagerGetSharedServiceWorkerNotificationManager() });
+        m_notificationProvider->setPermission(m_server->origin(), true);
+
+        m_webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:m_configuration.get()]);
+        m_uiDelegate = adoptNS([[NotificationPermissionDelegate alloc] init]);
+        [m_webView setUIDelegate:m_uiDelegate.get()];
         [m_webView loadRequest:m_server->request()];
     }
 
@@ -452,6 +456,36 @@
         cleanUpTestWebPushD(m_tempDirectory.get());
     }
 
+    RetainPtr<NSDictionary> injectPushMessage(NSDictionary *pushUserInfo)
+    {
+        String scope = m_server->request().URL.absoluteString;
+        String topic = "com.apple.WebKit.TestWebKitAPI "_str + scope;
+        id obj = @{
+            @"topic": (NSString *)topic,
+            @"userInfo": pushUserInfo
+        };
+        NSData *data = "" dataWithJSONObject:obj options:0 error:nullptr];
+
+        String message { static_cast<const char *>(data.bytes), static_cast<unsigned>(data.length) };
+        auto encodedMessage = encodeString(WTFMove(message));
+
+        auto utilityConnection = createAndConfigureConnectionToService("org.webkit.webpushtestdaemon.service");
+        sendMessageToDaemonWaitingForReply(utilityConnection.get(), MessageType::InjectEncryptedPushMessageForTesting, encodedMessage);
+
+        // Fetch push messages
+        __block bool gotMessages = false;
+        __block RetainPtr<NSArray<NSDictionary *>> messages;
+        [m_dataStore _getPendingPushMessages:^(NSArray<NSDictionary *> *rawMessages) {
+            messages = rawMessages;
+            gotMessages = true;
+        }];
+        TestWebKitAPI::Util::run(&gotMessages);
+
+        EXPECT_EQ([messages count], 1u);
+
+        return [messages objectAtIndex:0];
+    }
+
     RetainPtr<NSURL> m_tempDirectory;
     RetainPtr<WKWebsiteDataStore> m_dataStore;
     RetainPtr<WKWebViewConfiguration> m_configuration;
@@ -458,6 +492,7 @@
     RetainPtr<TestMessageHandler> m_testMessageHandler;
     RetainPtr<NotificationScriptMessageHandler> m_notificationMessageHandler;
     std::unique_ptr<TestWebKitAPI::HTTPServer> m_server;
+    std::unique_ptr<TestWebKitAPI::TestNotificationProvider> m_notificationProvider;
     RetainPtr<WKWebView> m_webView;
     RetainPtr<id<WKUIDelegatePrivate>> m_uiDelegate;
 };
@@ -501,6 +536,7 @@
     });
     self.addEventListener("push", (event) => {
         try {
+            self.registration.showNotification("notification");
             if (!event.data) {
                 port.postMessage("Received: null data");
                 return;
@@ -524,43 +560,20 @@
     }];
 
     loadRequest(htmlSource, serviceWorkerSource);
-
     TestWebKitAPI::Util::run(&ready);
 
-    String bundleIdentifier = "com.apple.WebKit.TestWebKitAPI"_s;
-    String scope = m_server->request().URL.absoluteString;
-    String topic = bundleIdentifier + " "_s + scope;
+    auto message = injectPushMessage(pushUserInfo);
 
-    id obj = @{
-        @"topic": (NSString *)topic,
-        @"userInfo": pushUserInfo
-    };
-    NSData *data = "" dataWithJSONObject:obj options:0 error:nullptr];
-
-    String message { static_cast<const char *>(data.bytes), static_cast<unsigned>(data.length) };
-    auto encodedMessage = encodeString(WTFMove(message));
-
-    auto utilityConnection = createAndConfigureConnectionToService("org.webkit.webpushtestdaemon.service");
-    sendMessageToDaemonWaitingForReply(utilityConnection.get(), MessageType::InjectEncryptedPushMessageForTesting, encodedMessage);
-
-    // Fetch push messages
-    __block bool gotMessages = false;
-    __block RetainPtr<NSArray<NSDictionary *>> messages;
-    [m_dataStore _getPendingPushMessages:^(NSArray<NSDictionary *> *rawMessages) {
-        messages = rawMessages;
-        gotMessages = true;
-    }];
-    TestWebKitAPI::Util::run(&gotMessages);
-
-    EXPECT_EQ([messages count], 1u);
-
-    // Handle push message
     __block bool pushMessageProcessed = false;
-    [m_dataStore _processPushMessage:[messages firstObject] completionHandler:^(bool result) {
+    __block bool pushMessageProcessedResult = false;
+    [m_dataStore _processPushMessage:message.get() completionHandler:^(bool result) {
+        pushMessageProcessedResult = result;
         pushMessageProcessed = true;
     }];
     TestWebKitAPI::Util::run(&gotExpectedMessage);
     TestWebKitAPI::Util::run(&pushMessageProcessed);
+
+    EXPECT_TRUE(pushMessageProcessedResult);
 }
 
 TEST_F(WebPushDInjectedPushTest, HandleInjectedEmptyPush)
@@ -805,6 +818,77 @@
 #endif // #if USE(APPLE_INTERNAL_SDK)
 #endif // ENABLE(INSTALL_COORDINATION_BUNDLES)
 
+TEST_F(WebPushDTest, TooManySilentPushesCausesUnsubscribe)
+{
+    static const char* htmlSource = R"HTML(
+    <script src=""
+    <script>
+    let pushManager = null;
+
+    navigator.serviceWorker.register('/sw.js').then(async () => {
+        const registration = await navigator.serviceWorker.ready;
+        let result = null;
+        try {
+            pushManager = registration.pushManager;
+            let subscription = await pushManager.subscribe({
+                userVisibleOnly: true,
+                applicationServerKey: VALID_SERVER_KEY
+            });
+            result = "Subscribed";
+        } catch (e) {
+            result = "Error: " + e;
+        }
+        window.webkit.messageHandlers.note.postMessage(result);
+    });
+
+    function getPushSubscription()
+    {
+        pushManager.getSubscription().then((subscription) => {
+            window.webkit.messageHandlers.note.postMessage(subscription ? "Subscribed" : "Unsubscribed");
+        });
+    }
+    </script>
+    )HTML";
+    static const char* serviceWorkerScriptSource = "self.addEventListener('push', (event) => { });";
+
+    __block RetainPtr<id> message = nil;
+    __block bool gotMessage = false;
+    [m_notificationMessageHandler setMessageHandler:^(id receivedMessage) {
+        message = receivedMessage;
+        gotMessage = true;
+    }];
+
+    loadRequest(htmlSource, serviceWorkerScriptSource);
+
+    TestWebKitAPI::Util::run(&gotMessage);
+    ASSERT_TRUE([message isEqualToString:@"Subscribed"]);
+
+    for (unsigned i = 0; i < WebKit::WebPushD::maxSilentPushCount; i++) {
+        gotMessage = false;
+        [m_webView evaluateJavaScript:@"getPushSubscription()" completionHandler:^(id, NSError*) { }];
+        TestWebKitAPI::Util::run(&gotMessage);
+        ASSERT_TRUE([message isEqualToString:@"Subscribed"]);
+
+        __block bool processedPush = false;
+        __block bool pushResult = false;
+        auto message = injectPushMessage(@{ });
+
+        [m_dataStore _processPushMessage:message.get() completionHandler:^(bool result) {
+            pushResult = result;
+            processedPush = true;
+        }];
+        TestWebKitAPI::Util::run(&processedPush);
+
+        // WebContent should fail processing the push since no notification was shown.
+        EXPECT_FALSE(pushResult);
+    }
+
+    gotMessage = false;
+    [m_webView evaluateJavaScript:@"getPushSubscription()" completionHandler:^(id, NSError*) { }];
+    TestWebKitAPI::Util::run(&gotMessage);
+    ASSERT_TRUE([message isEqualToString:@"Unsubscribed"]);
+}
+
 } // namespace TestWebKitAPI
 
 #endif // PLATFORM(MAC) || PLATFORM(IOS)

Modified: trunk/Tools/TestWebKitAPI/cocoa/HTTPServer.h (290814 => 290815)


--- trunk/Tools/TestWebKitAPI/cocoa/HTTPServer.h	2022-03-04 04:40:06 UTC (rev 290814)
+++ trunk/Tools/TestWebKitAPI/cocoa/HTTPServer.h	2022-03-04 05:28:22 UTC (rev 290815)
@@ -50,6 +50,7 @@
     HTTPServer(Function<void(Connection)>&&, Protocol = Protocol::Http);
     ~HTTPServer();
     uint16_t port() const;
+    String origin() const;
     NSURLRequest *request(const String& path = "/"_str) const;
     NSURLRequest *requestWithLocalhost(const String& path = "/"_str) const;
     size_t totalRequests() const;

Modified: trunk/Tools/TestWebKitAPI/cocoa/HTTPServer.mm (290814 => 290815)


--- trunk/Tools/TestWebKitAPI/cocoa/HTTPServer.mm	2022-03-04 04:40:06 UTC (rev 290814)
+++ trunk/Tools/TestWebKitAPI/cocoa/HTTPServer.mm	2022-03-04 05:28:22 UTC (rev 290815)
@@ -376,6 +376,11 @@
     return scheme;
 }
 
+String HTTPServer::origin() const
+{
+    return [NSString stringWithFormat:@"%s://127.0.0.1:%d", scheme(), port()];
+}
+
 NSURLRequest *HTTPServer::request(const String& path) const
 {
     return [NSURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"%s://127.0.0.1:%d%@", scheme(), port(), path.createCFString().get()]]];
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to