/* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ "use strict"; const userAgentID = "2c43af06-ab6e-476a-adc4-16cbda54fb89"; let db; function run_test() { do_get_profile(); setPrefs({ userAgentID, }); db = PushServiceWebSocket.newPushDB(); registerCleanupFunction(() => { return db.drop().then(_ => db.close()); }); run_next_test(); } let unregisterDefers = {}; function promiseUnregister(keyID) { return new Promise(r => (unregisterDefers[keyID] = r)); } function makePushPermission(url, capability) { return { QueryInterface: ChromeUtils.generateQI(["nsIPermission"]), capability: Ci.nsIPermissionManager[capability], expireTime: 0, expireType: Ci.nsIPermissionManager.EXPIRE_NEVER, principal: Services.scriptSecurityManager.createContentPrincipal( Services.io.newURI(url), {} ), type: "desktop-notification", }; } function promiseObserverNotifications(topic, count) { let notifiedScopes = []; let subChangePromise = promiseObserverNotification(topic, (subject, data) => { notifiedScopes.push(data); return notifiedScopes.length == count; }); return subChangePromise.then(_ => notifiedScopes.sort()); } function promiseSubscriptionChanges(count) { return promiseObserverNotifications( PushServiceComponent.subscriptionChangeTopic, count ); } function promiseSubscriptionModifications(count) { return promiseObserverNotifications( PushServiceComponent.subscriptionModifiedTopic, count ); } function allExpired(...keyIDs) { return Promise.all(keyIDs.map(keyID => db.getByKeyID(keyID))).then(records => records.every(record => record.isExpired()) ); } add_task(async function setUp() { // Active registration; quota should be reset to 16. Since the quota isn't // exposed to content, we shouldn't receive a subscription change event. await putTestRecord(db, "active-allow", "https://example.info/page/1", 8); // Expired registration; should be dropped. await putTestRecord(db, "expired-allow", "https://example.info/page/2", 0); // Active registration; should be expired when we change the permission // to "deny". await putTestRecord( db, "active-deny-changed", "https://example.xyz/page/1", 16 ); // Two active registrations for a visited site. These will expire when we // add a "deny" permission. await putTestRecord(db, "active-deny-added-1", "https://example.net/ham", 16); await putTestRecord( db, "active-deny-added-2", "https://example.net/green", 8 ); // An already-expired registration for a visited site. We shouldn't send an // `unregister` request for this one, but still receive an observer // notification when we restore permissions. await putTestRecord(db, "expired-deny-added", "https://example.net/eggs", 0); // A registration that should not be affected by permission list changes // because its quota is set to `Infinity`. await putTestRecord(db, "never-expires", "app://chrome/only", Infinity); // A registration that should be dropped when we clear the permission // list. await putTestRecord(db, "drop-on-clear", "https://example.edu/lonely", 16); let handshakeDone; let handshakePromise = new Promise(resolve => (handshakeDone = resolve)); PushService.init({ serverURI: "wss://push.example.org/", db, makeWebSocket(uri) { return new MockWebSocket(uri, { onHello(request) { this.serverSendMsg( JSON.stringify({ messageType: "hello", status: 200, uaid: userAgentID, }) ); handshakeDone(); }, onUnregister(request) { let resolve = unregisterDefers[request.channelID]; equal( typeof resolve, "function", "Dropped unexpected channel ID " + request.channelID ); delete unregisterDefers[request.channelID]; equal( request.code, 202, "Expected permission revoked unregister reason" ); resolve(); this.serverSendMsg( JSON.stringify({ messageType: "unregister", status: 200, channelID: request.channelID, }) ); }, onACK(request) {}, }); }, }); await handshakePromise; }); add_task(async function test_permissions_allow_added() { let subChangePromise = promiseSubscriptionChanges(1); await PushService._onPermissionChange( makePushPermission("https://example.info", "ALLOW_ACTION"), "added" ); let notifiedScopes = await subChangePromise; deepEqual( notifiedScopes, ["https://example.info/page/2"], "Wrong scopes after adding allow" ); let record = await db.getByKeyID("active-allow"); equal( record.quota, 16, "Should reset quota for active records after adding allow" ); record = await db.getByKeyID("expired-allow"); ok(!record, "Should drop expired records after adding allow"); }); add_task(async function test_permissions_allow_deleted() { let subModifiedPromise = promiseSubscriptionModifications(1); let unregisterPromise = promiseUnregister("active-allow"); await PushService._onPermissionChange( makePushPermission("https://example.info", "ALLOW_ACTION"), "deleted" ); await unregisterPromise; let notifiedScopes = await subModifiedPromise; deepEqual( notifiedScopes, ["https://example.info/page/1"], "Wrong scopes modified after deleting allow" ); let record = await db.getByKeyID("active-allow"); ok(record.isExpired(), "Should expire active record after deleting allow"); }); add_task(async function test_permissions_deny_added() { let subModifiedPromise = promiseSubscriptionModifications(2); let unregisterPromise = Promise.all([ promiseUnregister("active-deny-added-1"), promiseUnregister("active-deny-added-2"), ]); await PushService._onPermissionChange( makePushPermission("https://example.net", "DENY_ACTION"), "added" ); await unregisterPromise; let notifiedScopes = await subModifiedPromise; deepEqual( notifiedScopes, ["https://example.net/green", "https://example.net/ham"], "Wrong scopes modified after adding deny" ); let isExpired = await allExpired("active-deny-added-1", "expired-deny-added"); ok(isExpired, "Should expire all registrations after adding deny"); }); add_task(async function test_permissions_deny_deleted() { await PushService._onPermissionChange( makePushPermission("https://example.net", "DENY_ACTION"), "deleted" ); let isExpired = await allExpired("active-deny-added-1", "expired-deny-added"); ok(isExpired, "Should retain expired registrations after deleting deny"); }); add_task(async function test_permissions_allow_changed() { let subChangePromise = promiseSubscriptionChanges(3); await PushService._onPermissionChange( makePushPermission("https://example.net", "ALLOW_ACTION"), "changed" ); let notifiedScopes = await subChangePromise; deepEqual( notifiedScopes, [ "https://example.net/eggs", "https://example.net/green", "https://example.net/ham", ], "Wrong scopes after changing to allow" ); let droppedRecords = await Promise.all([ db.getByKeyID("active-deny-added-1"), db.getByKeyID("active-deny-added-2"), db.getByKeyID("expired-deny-added"), ]); ok( !droppedRecords.some(Boolean), "Should drop all expired registrations after changing to allow" ); }); add_task(async function test_permissions_deny_changed() { let subModifiedPromise = promiseSubscriptionModifications(1); let unregisterPromise = promiseUnregister("active-deny-changed"); await PushService._onPermissionChange( makePushPermission("https://example.xyz", "DENY_ACTION"), "changed" ); await unregisterPromise; let notifiedScopes = await subModifiedPromise; deepEqual( notifiedScopes, ["https://example.xyz/page/1"], "Wrong scopes modified after changing to deny" ); let record = await db.getByKeyID("active-deny-changed"); ok(record.isExpired(), "Should expire active record after changing to deny"); }); add_task(async function test_permissions_clear() { let subModifiedPromise = promiseSubscriptionModifications(3); deepEqual( await getAllKeyIDs(db), ["active-allow", "active-deny-changed", "drop-on-clear", "never-expires"], "Wrong records in database before clearing" ); let unregisterPromise = Promise.all([ promiseUnregister("active-allow"), promiseUnregister("active-deny-changed"), promiseUnregister("drop-on-clear"), ]); await PushService._onPermissionChange(null, "cleared"); await unregisterPromise; let notifiedScopes = await subModifiedPromise; deepEqual( notifiedScopes, [ "https://example.edu/lonely", "https://example.info/page/1", "https://example.xyz/page/1", ], "Wrong scopes modified after clearing registrations" ); deepEqual( await getAllKeyIDs(db), ["never-expires"], "Unrestricted registrations should not be dropped" ); });