Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

/* Any copyright is dedicated to the Public Domain.
/**
* Tests for nsIClearDataService.clearPrivateBrowsingData().
*/
"use strict";
add_task(async function test_clearPrivateBrowsingData_fires_notification() {
let notificationFired = false;
let observer = {
observe(_subject, topic) {
if (topic === "last-pb-context-exited") {
notificationFired = true;
}
},
};
Services.obs.addObserver(observer, "last-pb-context-exited");
try {
let flags = await new Promise(resolve => {
Services.clearData.clearPrivateBrowsingData({
onDataDeleted(aFailedFlags) {
resolve(aFailedFlags);
},
});
});
Assert.ok(notificationFired, "last-pb-context-exited notification fired");
Assert.equal(flags, 0, "No failure flags");
} finally {
Services.obs.removeObserver(observer, "last-pb-context-exited");
}
});
add_task(async function test_clearPrivateBrowsingData_passes_collector() {
let receivedCollector = null;
let observer = {
observe(subject, topic) {
if (topic === "last-pb-context-exited") {
try {
receivedCollector = subject.QueryInterface(Ci.nsIPBMCleanupCollector);
} catch (e) {}
}
},
};
Services.obs.addObserver(observer, "last-pb-context-exited");
try {
await new Promise(resolve => {
Services.clearData.clearPrivateBrowsingData({
onDataDeleted() {
resolve();
},
});
});
Assert.ok(receivedCollector, "Subject is an nsIPBMCleanupCollector");
} finally {
Services.obs.removeObserver(observer, "last-pb-context-exited");
}
});
add_task(async function test_clearPrivateBrowsingData_awaits_async_observers() {
let asyncWorkDone = false;
let observer = {
observe(subject, topic) {
if (topic !== "last-pb-context-exited") {
return;
}
let collector;
try {
collector = subject.QueryInterface(Ci.nsIPBMCleanupCollector);
} catch (e) {
return;
}
let cb = collector.addPendingCleanup();
// Simulate async cleanup that takes some time
Promise.resolve().then(() => {
asyncWorkDone = true;
cb.complete(Cr.NS_OK);
});
},
};
Services.obs.addObserver(observer, "last-pb-context-exited");
try {
await new Promise(resolve => {
Services.clearData.clearPrivateBrowsingData({
onDataDeleted() {
resolve();
},
});
});
Assert.ok(asyncWorkDone, "Async observer work completed before callback");
} finally {
Services.obs.removeObserver(observer, "last-pb-context-exited");
}
});
add_task(
async function test_clearPrivateBrowsingData_awaits_multiple_observers() {
let completionOrder = [];
let observer1 = {
observe(subject, topic) {
if (topic !== "last-pb-context-exited") {
return;
}
let collector;
try {
collector = subject.QueryInterface(Ci.nsIPBMCleanupCollector);
} catch (e) {
return;
}
let cb = collector.addPendingCleanup();
// Complete after a microtask
Promise.resolve()
.then(() => Promise.resolve())
.then(() => {
completionOrder.push("observer1");
cb.complete(Cr.NS_OK);
});
},
};
let observer2 = {
observe(subject, topic) {
if (topic !== "last-pb-context-exited") {
return;
}
let collector;
try {
collector = subject.QueryInterface(Ci.nsIPBMCleanupCollector);
} catch (e) {
return;
}
let cb = collector.addPendingCleanup();
// Complete immediately
Promise.resolve().then(() => {
completionOrder.push("observer2");
cb.complete(Cr.NS_OK);
});
},
};
Services.obs.addObserver(observer1, "last-pb-context-exited");
Services.obs.addObserver(observer2, "last-pb-context-exited");
try {
await new Promise(resolve => {
Services.clearData.clearPrivateBrowsingData({
onDataDeleted() {
resolve();
},
});
});
Assert.equal(
completionOrder.length,
2,
"Both observers completed before callback"
);
} finally {
Services.obs.removeObserver(observer1, "last-pb-context-exited");
Services.obs.removeObserver(observer2, "last-pb-context-exited");
}
}
);
add_task(
async function test_clearPrivateBrowsingData_propagates_observer_failure() {
let observer = {
observe(subject, topic) {
if (topic !== "last-pb-context-exited") {
return;
}
let collector;
try {
collector = subject.QueryInterface(Ci.nsIPBMCleanupCollector);
} catch (e) {
return;
}
let cb = collector.addPendingCleanup();
Promise.resolve().then(() => cb.complete(Cr.NS_ERROR_FAILURE));
},
};
Services.obs.addObserver(observer, "last-pb-context-exited");
try {
let flags = await new Promise(resolve => {
Services.clearData.clearPrivateBrowsingData({
onDataDeleted(aFailedFlags) {
resolve(aFailedFlags);
},
});
});
Assert.notEqual(
flags,
0,
"Failure flags are non-zero when observer fails"
);
} finally {
Services.obs.removeObserver(observer, "last-pb-context-exited");
}
}
);
add_task(async function test_clearPrivateBrowsingData_partial_failure() {
let observer1 = {
observe(subject, topic) {
if (topic !== "last-pb-context-exited") {
return;
}
let collector;
try {
collector = subject.QueryInterface(Ci.nsIPBMCleanupCollector);
} catch (e) {
return;
}
let cb = collector.addPendingCleanup();
Promise.resolve().then(() => cb.complete(Cr.NS_OK));
},
};
let observer2 = {
observe(subject, topic) {
if (topic !== "last-pb-context-exited") {
return;
}
let collector;
try {
collector = subject.QueryInterface(Ci.nsIPBMCleanupCollector);
} catch (e) {
return;
}
let cb = collector.addPendingCleanup();
Promise.resolve().then(() => cb.complete(Cr.NS_ERROR_FAILURE));
},
};
Services.obs.addObserver(observer1, "last-pb-context-exited");
Services.obs.addObserver(observer2, "last-pb-context-exited");
try {
let flags = await new Promise(resolve => {
Services.clearData.clearPrivateBrowsingData({
onDataDeleted(aFailedFlags) {
resolve(aFailedFlags);
},
});
});
Assert.notEqual(
flags,
0,
"Failure flags are non-zero when any observer fails"
);
} finally {
Services.obs.removeObserver(observer1, "last-pb-context-exited");
Services.obs.removeObserver(observer2, "last-pb-context-exited");
}
});
add_task(async function test_clearPrivateBrowsingData_completes() {
// clearPrivateBrowsingData should resolve even when called multiple times.
for (let i = 0; i < 3; i++) {
let flags = await new Promise(resolve => {
Services.clearData.clearPrivateBrowsingData({
onDataDeleted(aFailedFlags) {
resolve(aFailedFlags);
},
});
});
Assert.equal(flags, 0, `No failure flags on iteration ${i}`);
}
});
add_task(async function test_clearPrivateBrowsingData_null_callback() {
// Should not throw when called with null callback.
Services.clearData.clearPrivateBrowsingData(null);
// Wait for the cleanup to finish so the guard clears.
await new Promise(resolve =>
ChromeUtils.idleDispatch(resolve, { timeout: 100 })
);
});
add_task(async function test_clearPrivateBrowsingData_rejects_overlap() {
let observer = {
observe(subject, topic) {
if (topic !== "last-pb-context-exited") {
return;
}
let collector;
try {
collector = subject.QueryInterface(Ci.nsIPBMCleanupCollector);
} catch (e) {
return;
}
let cb = collector.addPendingCleanup();
// Delay completion so the first call is still in progress.
ChromeUtils.idleDispatch(() => cb.complete(Cr.NS_OK), { timeout: 100 });
},
};
Services.obs.addObserver(observer, "last-pb-context-exited");
try {
let firstDone = false;
let firstPromise = new Promise(resolve => {
Services.clearData.clearPrivateBrowsingData({
onDataDeleted() {
firstDone = true;
resolve();
},
});
});
// Second call while first is still in progress should return an error.
Assert.ok(!firstDone, "First cleanup still in progress");
Assert.throws(
() => Services.clearData.clearPrivateBrowsingData(null),
/NS_ERROR_ABORT/,
"Overlapping call returns NS_ERROR_ABORT"
);
await firstPromise;
// After first completes, a new call should succeed.
await new Promise(resolve => {
Services.clearData.clearPrivateBrowsingData({
onDataDeleted() {
resolve();
},
});
});
} finally {
Services.obs.removeObserver(observer, "last-pb-context-exited");
}
});