Source code

Revision control

Copy as Markdown

Other Tools

/* Any copyright is dedicated to the Public Domain.
"use strict";
// Browser-scoped permission removals must reach the content process. The
// parent's own table is cleared synchronously, so these tests deliberately
// assert the *child's* view, read from a tab that shares the content process
// and outlives the tab (or the document) the permission belonged to.
const ORIGIN = "https://example.com/";
const OTHER_ORIGIN = "https://example.org/";
let gTabs = new Set();
function getPrincipal(origin) {
return Services.scriptSecurityManager.createContentPrincipalFromOrigin(
origin
);
}
// Reads the content process' copy of a browser-scoped permission. `browser`
// only selects which process to ask; `browserId` is the tab being asked about
// and need not belong to `browser`.
async function testForBrowserInChild(browser, origin, type, browserId) {
return SpecialPowers.spawn(browser, [origin, type, browserId], (o, t, id) => {
let principal =
Services.scriptSecurityManager.createContentPrincipalFromOrigin(o);
return Services.perms.testForBrowser(principal, t, id);
});
}
function getContentProcessID(browser) {
return browser.browsingContext.currentWindowGlobal.domProcess.childID;
}
async function openTab(url = ORIGIN) {
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url);
gTabs.add(tab);
return tab;
}
function closeTab(tab) {
gTabs.delete(tab);
return BrowserTestUtils.removeTab(tab);
}
// Opens two same-origin tabs pinned to a single content process. The first is
// returned as the observer: it stays open so its process survives whatever
// happens to the second tab, letting us inspect what that process still
// believes.
async function openObserverAndSubjectTabs() {
let observerTab = await openTab();
let subjectTab = await openTab();
Assert.equal(
getContentProcessID(observerTab.linkedBrowser),
getContentProcessID(subjectTab.linkedBrowser),
"Both tabs should share one content process"
);
return { observerTab, subjectTab };
}
add_setup(async function () {
// Force same-origin tabs to share a process so one can outlive the other.
await SpecialPowers.pushPrefEnv({
set: [["dom.ipc.processCount.webIsolated", 1]],
});
});
registerCleanupFunction(() => {
for (let tab of gTabs) {
BrowserTestUtils.removeTab(tab);
}
gTabs.clear();
});
// Bug 2064718: on tab close the BrowserId is unregistered from the
// BrowsingContext map before "browsing-context-discarded" fires, so the clear
// could not be routed to the tab's content process and the entry leaked there.
add_task(async function testTabCloseClearsPermissionInChild() {
let { observerTab, subjectTab } = await openObserverAndSubjectTabs();
let browser = subjectTab.linkedBrowser;
let principal = getPrincipal(ORIGIN);
let browserId = browser.browserId;
Services.perms.addFromPrincipalForBrowser(
principal,
"geo",
Services.perms.ALLOW_ACTION,
browserId,
0
);
await waitForPermissionState(browser, "geolocation", "granted");
Assert.equal(
await testForBrowserInChild(browser, ORIGIN, "geo", browserId),
Services.perms.ALLOW_ACTION,
"Content process should see the permission before tab close"
);
await closeTab(subjectTab);
Assert.equal(
Services.perms.testForBrowser(principal, "geo", browserId),
Services.perms.UNKNOWN_ACTION,
"Parent should have dropped the permission on tab close"
);
await TestUtils.waitForCondition(
async () =>
(await testForBrowserInChild(
observerTab.linkedBrowser,
ORIGIN,
"geo",
browserId
)) == Services.perms.UNKNOWN_ACTION,
"Waiting for the content process to drop the closed tab's permission"
);
await closeTab(observerTab);
});
// Bug 2064718: a removal was only routed to the tab's *current* content
// process, so a process left behind by a process switch kept serving the
// permission after it had been revoked.
add_task(async function testRemovalReachesProcessLeftBehindByProcessSwitch() {
let { observerTab, subjectTab } = await openObserverAndSubjectTabs();
let browser = subjectTab.linkedBrowser;
let principal = getPrincipal(ORIGIN);
let browserId = browser.browserId;
Services.perms.addFromPrincipalForBrowser(
principal,
"geo",
Services.perms.ALLOW_ACTION,
browserId,
0
);
await waitForPermissionState(browser, "geolocation", "granted");
// Navigate cross-site so the tab switches process. The original process stays
// alive because observerTab is still in it, still holding the entry.
let loaded = BrowserTestUtils.browserLoaded(browser, false, OTHER_ORIGIN);
BrowserTestUtils.startLoadingURIString(browser, OTHER_ORIGIN);
await loaded;
Assert.notEqual(
getContentProcessID(browser),
getContentProcessID(observerTab.linkedBrowser),
"Tab should have switched away from the original process"
);
Assert.equal(
await testForBrowserInChild(
observerTab.linkedBrowser,
ORIGIN,
"geo",
browserId
),
Services.perms.ALLOW_ACTION,
"Original process should still hold the entry after the process switch"
);
Services.perms.removeFromPrincipalForBrowser(principal, "geo", browserId);
await TestUtils.waitForCondition(
async () =>
(await testForBrowserInChild(
observerTab.linkedBrowser,
ORIGIN,
"geo",
browserId
)) == Services.perms.UNKNOWN_ACTION,
"Waiting for the process left behind to drop the revoked permission"
);
await closeTab(subjectTab);
await closeTab(observerTab);
});
// The same delivery path is used for a single removal and for a bulk clear on
// a live tab. Both are covered here because they take different IPC messages.
add_task(async function testExplicitRemovalClearsPermissionInChild() {
let { observerTab, subjectTab } = await openObserverAndSubjectTabs();
let browser = subjectTab.linkedBrowser;
let principal = getPrincipal(ORIGIN);
let browserId = browser.browserId;
for (let removal of [
() =>
Services.perms.removeFromPrincipalForBrowser(principal, "geo", browserId),
() => Services.perms.removeAllForBrowser(browserId),
() =>
Services.perms.removeByActionForBrowser(
browserId,
Services.perms.ALLOW_ACTION
),
]) {
Services.perms.addFromPrincipalForBrowser(
principal,
"geo",
Services.perms.ALLOW_ACTION,
browserId,
0
);
await waitForPermissionState(browser, "geolocation", "granted");
removal();
await TestUtils.waitForCondition(
async () =>
(await testForBrowserInChild(browser, ORIGIN, "geo", browserId)) ==
Services.perms.UNKNOWN_ACTION,
"Waiting for the content process to drop the removed permission"
);
Assert.equal(
await testForBrowserInChild(
observerTab.linkedBrowser,
ORIGIN,
"geo",
browserId
),
Services.perms.UNKNOWN_ACTION,
"Permission should be gone from the shared content process"
);
}
await closeTab(subjectTab);
await closeTab(observerTab);
});