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()]]];