Source code

Revision control

Copy as Markdown

Other Tools

Test Info:

/* Any copyright is dedicated to the Public Domain.
/* import-globals-from ../../../serviceworkers/test/browser_head.js */
Services.scriptloader.loadSubScript(
this
);
const TEST_ORIGIN = "https://test1.example.org";
const CHANNEL_NAME = "push-message-handled";
const PUSH_NOTIFIER = Cc["@mozilla.org/push/Notifier;1"].getService(
Ci.nsIPushNotifier
);
const PUSH_MESSAGE_HANDLED_TOPIC = Cc["@mozilla.org/push/Service;1"].getService(
Ci.nsIPushService
).pushMessageHandledTopic;
add_task(async function test_push_message_handled_after_push_event_settles() {
const swDesc = {
origin: TEST_ORIGIN,
scope: "push-handled",
script: "push_message_handled_worker.js",
};
registerCleanupFunction(async () => {
await clear_qm_origin_group_via_clearData(TEST_ORIGIN);
});
await install_sw(swDesc);
const scope = `${TEST_ORIGIN}/${DIR_PATH}/${swDesc.scope}`;
const principal = getPrincipal(scope);
const helperTab = await BrowserTestUtils.openNewForegroundTab(
gBrowser,
`${TEST_ORIGIN}/${DIR_PATH}/empty_with_utils.html`
);
registerCleanupFunction(() => BrowserTestUtils.removeTab(helperTab));
// Starts listening before the push is sent, so the worker's reply cannot be
// missed
await SpecialPowers.spawn(helperTab.linkedBrowser, [CHANNEL_NAME], name => {
const utils = content.wrappedJSObject;
utils.setupMessagingChannel(name);
utils.pushReceived = utils.waitForBroadcastMessage(name, "push-received");
});
const handledMessages = [];
const observer = (subject, topic, data) => handledMessages.push(data);
Services.obs.addObserver(observer, PUSH_MESSAGE_HANDLED_TOPIC);
registerCleanupFunction(() =>
Services.obs.removeObserver(observer, PUSH_MESSAGE_HANDLED_TOPIC)
);
const notified = PUSH_NOTIFIER.notifyPush(scope, principal, "message-1");
await SpecialPowers.spawn(
helperTab.linkedBrowser,
[],
() => content.wrappedJSObject.pushReceived
);
// Give a wrongly early notification a chance to arrive
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
await new Promise(resolve => setTimeout(resolve, 100));
ok(
!handledMessages.includes("message-1"),
"The message should not be handled while its push event is pending"
);
await SpecialPowers.spawn(helperTab.linkedBrowser, [CHANNEL_NAME], name =>
content.wrappedJSObject.broadcastAndWaitFor(name, "release", "released")
);
await TestUtils.waitForCondition(
() => handledMessages.includes("message-1"),
"Waiting for the message to be reported as handled"
);
ok(true, "The message should be handled once its push event settles");
await notified;
ok(true, "The notifyPush promise should resolve once the message is handled");
});
add_task(async function test_push_message_handled_without_service_worker() {
const scope = `${TEST_ORIGIN}/${DIR_PATH}/no-such-scope`;
const principal = getPrincipal(scope);
const handled = TestUtils.topicObserved(
PUSH_MESSAGE_HANDLED_TOPIC,
(subject, data) => data == "message-2"
);
await Assert.rejects(
PUSH_NOTIFIER.notifyPush(scope, principal, "message-2"),
/NS_ERROR_FAILURE/,
"The notifier should fail without a matching registration"
);
await handled;
ok(true, "The message should be handled right away without a service worker");
});
add_task(async function test_push_message_handled_when_worker_shuts_down() {
const swDesc = {
origin: TEST_ORIGIN,
scope: "push-handled-hang",
script: "push_message_handled_worker.js?hang-activate",
};
const tab = await BrowserTestUtils.openNewForegroundTab(
gBrowser,
`${TEST_ORIGIN}/${DIR_PATH}/empty.html`
);
registerCleanupFunction(() => BrowserTestUtils.removeTab(tab));
// Registers the worker and waits until it hangs in its activate event
await SpecialPowers.spawn(
tab.linkedBrowser,
[swDesc.script, swDesc.scope],
async (script, swScope) => {
const reg = await content.navigator.serviceWorker.register(script, {
scope: swScope,
});
const worker = reg.installing || reg.waiting || reg.active;
await new Promise(resolve => {
const check = () => {
if (worker.state == "activating") {
worker.removeEventListener("statechange", check);
resolve();
}
};
worker.addEventListener("statechange", check);
check();
});
}
);
const scope = `${TEST_ORIGIN}/${DIR_PATH}/${swDesc.scope}`;
const handled = TestUtils.topicObserved(
PUSH_MESSAGE_HANDLED_TOPIC,
(subject, data) => data == "message-3"
);
// The message is queued until the worker finishes activating, which it
// never does. Dropping it later rejects the promise, which we don't care
// about here
PUSH_NOTIFIER.notifyPush(scope, getPrincipal(scope), "message-3").catch(
() => {}
);
// Shutting the worker down drops the queued message
const reg = swm_lookup_reg(swDesc);
await reg.activeWorker.terminateWorker();
await handled;
ok(true, "A dropped message should still be reported as handled");
});
add_task(async function test_push_message_handled_with_unresponsive_worker() {
const IDLE_TIMEOUT_MS = 1000;
const TIMER_SLACK_MS = 20;
await SpecialPowers.pushPrefEnv({
set: [
["dom.serviceWorkers.idle_timeout", IDLE_TIMEOUT_MS],
["dom.serviceWorkers.idle_extended_timeout", IDLE_TIMEOUT_MS],
],
});
const swDesc = {
origin: TEST_ORIGIN,
scope: "push-handled-unresponsive",
script: "push_message_handled_worker.js",
};
await install_sw(swDesc);
const scope = `${TEST_ORIGIN}/${DIR_PATH}/${swDesc.scope}`;
const handled = TestUtils.topicObserved(
PUSH_MESSAGE_HANDLED_TOPIC,
(subject, data) => data == "message-4"
);
const startedAt = ChromeUtils.now();
// Nothing ever releases the push event of this worker, so only the worker's
// own idle timers can end it
PUSH_NOTIFIER.notifyPush(scope, getPrincipal(scope), "message-4").catch(
() => {}
);
await handled;
Assert.greaterOrEqual(
ChromeUtils.now() - startedAt,
IDLE_TIMEOUT_MS - TIMER_SLACK_MS,
"The message should be handled once the worker's idle timers end the push event"
);
await SpecialPowers.popPrefEnv();
});