Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

/* Any copyright is dedicated to the Public Domain.
*/
"use strict";
const { SitePermissions } = ChromeUtils.importESModule(
);
const TemporaryPermissions = SitePermissions._temporaryPermissions;
const PERM_A = "foo";
const PERM_B = "bar";
const PERM_C = "foobar";
const BROWSER_A = createDummyBrowser("https://example.com/foo");
const BROWSER_B = createDummyBrowser("https://example.org/foo");
const EXPIRY_MS_A = 1000000;
const EXPIRY_MS_B = 1000001;
function createDummyBrowser(spec) {
let uri = Services.io.newURI(spec);
return {
currentURI: uri,
contentPrincipal: Services.scriptSecurityManager.createContentPrincipal(
uri,
{}
),
dispatchEvent: () => {},
ownerGlobal: {
CustomEvent: class CustomEvent {},
},
};
}
function navigateDummyBrowser(browser, uri) {
// Callers may pass in either uri strings or nsIURI objects.
if (typeof uri == "string") {
uri = Services.io.newURI(uri);
}
browser.currentURI = uri;
browser.contentPrincipal =
Services.scriptSecurityManager.createContentPrincipal(
browser.currentURI,
{}
);
}
/**
* Tests that temporary permissions with different block states are stored
* (set, overwrite, delete) correctly.
*/
add_task(async function testAllowBlock() {
// Set two temporary permissions on the same browser.
SitePermissions.setForPrincipal(
null,
PERM_A,
SitePermissions.ALLOW,
SitePermissions.SCOPE_TEMPORARY,
BROWSER_A,
EXPIRY_MS_A
);
SitePermissions.setForPrincipal(
null,
PERM_B,
SitePermissions.BLOCK,
SitePermissions.SCOPE_TEMPORARY,
BROWSER_A,
EXPIRY_MS_A
);
// Test that the permissions have been set correctly.
Assert.deepEqual(
SitePermissions.getForPrincipal(null, PERM_A, BROWSER_A),
{
state: SitePermissions.ALLOW,
scope: SitePermissions.SCOPE_TEMPORARY,
},
"SitePermissions returns expected permission state for perm A."
);
Assert.deepEqual(
SitePermissions.getForPrincipal(null, PERM_B, BROWSER_A),
{
state: SitePermissions.BLOCK,
scope: SitePermissions.SCOPE_TEMPORARY,
},
"SitePermissions returns expected permission state for perm B."
);
Assert.deepEqual(
TemporaryPermissions.get(BROWSER_A, PERM_A),
{
id: PERM_A,
state: SitePermissions.ALLOW,
scope: SitePermissions.SCOPE_TEMPORARY,
},
"TemporaryPermissions returns expected permission state for perm A."
);
Assert.deepEqual(
TemporaryPermissions.get(BROWSER_A, PERM_B),
{
id: PERM_B,
state: SitePermissions.BLOCK,
scope: SitePermissions.SCOPE_TEMPORARY,
},
"TemporaryPermissions returns expected permission state for perm B."
);
// Test internal data structure of TemporaryPermissions.
let entry = TemporaryPermissions._stateByBrowser.get(BROWSER_A);
ok(entry, "Should have an entry for browser A");
ok(
!TemporaryPermissions._stateByBrowser.has(BROWSER_B),
"Should have no entry for browser B"
);
let { browser, uriToPerm } = entry;
Assert.equal(
browser?.get(),
BROWSER_A,
"Entry should have a weak reference to the browser."
);
ok(uriToPerm, "Entry should have uriToPerm object.");
Assert.equal(Object.keys(uriToPerm).length, 2, "uriToPerm has 2 entries.");
let permissionsA = uriToPerm[BROWSER_A.contentPrincipal.origin];
let permissionsB =
uriToPerm[Services.eTLD.getBaseDomain(BROWSER_A.currentURI)];
ok(permissionsA, "Allow should be keyed under origin");
ok(permissionsB, "Block should be keyed under baseDomain");
let permissionA = permissionsA[PERM_A];
let permissionB = permissionsB[PERM_B];
Assert.equal(
permissionA.state,
SitePermissions.ALLOW,
"Should have correct state"
);
let expireTimeoutA = permissionA.expireTimeout;
Assert.ok(
Number.isInteger(expireTimeoutA),
"Should have valid expire timeout"
);
Assert.equal(
permissionB.state,
SitePermissions.BLOCK,
"Should have correct state"
);
let expireTimeoutB = permissionB.expireTimeout;
Assert.ok(
Number.isInteger(expireTimeoutB),
"Should have valid expire timeout"
);
// Overwrite permission A.
SitePermissions.setForPrincipal(
null,
PERM_A,
SitePermissions.ALLOW,
SitePermissions.SCOPE_TEMPORARY,
BROWSER_A,
EXPIRY_MS_B
);
Assert.ok(
permissionsA[PERM_A].expireTimeout != expireTimeoutA,
"Overwritten permission A should have new timer"
);
// Overwrite permission B - this time with a non-block state which means it
// should be keyed by origin now.
SitePermissions.setForPrincipal(
null,
PERM_B,
SitePermissions.ALLOW,
SitePermissions.SCOPE_TEMPORARY,
BROWSER_A,
EXPIRY_MS_A
);
let baseDomainEntry =
uriToPerm[Services.eTLD.getBaseDomain(BROWSER_A.currentURI)];
Assert.ok(
!baseDomainEntry || !baseDomainEntry[PERM_B],
"Should not longer have baseDomain permission entry"
);
permissionsB = uriToPerm[BROWSER_A.contentPrincipal.origin];
permissionB = permissionsB[PERM_B];
Assert.ok(
permissionsB && permissionB,
"Overwritten permission should be keyed under origin"
);
Assert.equal(
permissionB.state,
SitePermissions.ALLOW,
"Should have correct updated state"
);
Assert.ok(
permissionB.expireTimeout != expireTimeoutB,
"Overwritten permission B should have new timer"
);
// Remove permissions
SitePermissions.removeFromPrincipal(null, PERM_A, BROWSER_A);
SitePermissions.removeFromPrincipal(null, PERM_B, BROWSER_A);
// Test that permissions have been removed correctly
Assert.deepEqual(
SitePermissions.getForPrincipal(null, PERM_A, BROWSER_A),
{
state: SitePermissions.UNKNOWN,
scope: SitePermissions.SCOPE_PERSISTENT,
},
"SitePermissions returns UNKNOWN state for A."
);
Assert.deepEqual(
SitePermissions.getForPrincipal(null, PERM_B, BROWSER_A),
{
state: SitePermissions.UNKNOWN,
scope: SitePermissions.SCOPE_PERSISTENT,
},
"SitePermissions returns UNKNOWN state for B."
);
Assert.equal(
TemporaryPermissions.get(BROWSER_A, PERM_A),
null,
"TemporaryPermissions returns null for perm A."
);
Assert.equal(
TemporaryPermissions.get(BROWSER_A, PERM_B),
null,
"TemporaryPermissions returns null for perm B."
);
});
/**
* Tests TemporaryPermissions#getAll.
*/
add_task(async function testGetAll() {
SitePermissions.setForPrincipal(
null,
PERM_A,
SitePermissions.ALLOW,
SitePermissions.SCOPE_TEMPORARY,
BROWSER_A,
EXPIRY_MS_A
);
SitePermissions.setForPrincipal(
null,
PERM_B,
SitePermissions.BLOCK,
SitePermissions.SCOPE_TEMPORARY,
BROWSER_B,
EXPIRY_MS_A
);
SitePermissions.setForPrincipal(
null,
PERM_C,
SitePermissions.PROMPT,
SitePermissions.SCOPE_TEMPORARY,
BROWSER_B,
EXPIRY_MS_A
);
Assert.deepEqual(TemporaryPermissions.getAll(BROWSER_A), [
{
id: PERM_A,
state: SitePermissions.ALLOW,
scope: SitePermissions.SCOPE_TEMPORARY,
},
]);
let permsBrowserB = TemporaryPermissions.getAll(BROWSER_B);
Assert.equal(
permsBrowserB.length,
2,
"There should be 2 permissions set for BROWSER_B"
);
let permB;
let permC;
if (permsBrowserB[0].id == PERM_B) {
permB = permsBrowserB[0];
permC = permsBrowserB[1];
} else {
permB = permsBrowserB[1];
permC = permsBrowserB[0];
}
Assert.deepEqual(permB, {
id: PERM_B,
state: SitePermissions.BLOCK,
scope: SitePermissions.SCOPE_TEMPORARY,
});
Assert.deepEqual(permC, {
id: PERM_C,
state: SitePermissions.PROMPT,
scope: SitePermissions.SCOPE_TEMPORARY,
});
});
/**
* Tests SitePermissions#clearTemporaryBlockPermissions and
* TemporaryPermissions#clear.
*/
add_task(async function testClear() {
SitePermissions.setForPrincipal(
null,
PERM_A,
SitePermissions.ALLOW,
SitePermissions.SCOPE_TEMPORARY,
BROWSER_A,
EXPIRY_MS_A
);
SitePermissions.setForPrincipal(
null,
PERM_B,
SitePermissions.BLOCK,
SitePermissions.SCOPE_TEMPORARY,
BROWSER_A,
EXPIRY_MS_A
);
SitePermissions.setForPrincipal(
null,
PERM_C,
SitePermissions.BLOCK,
SitePermissions.SCOPE_TEMPORARY,
BROWSER_B,
EXPIRY_MS_A
);
let stateByBrowser = SitePermissions._temporaryPermissions._stateByBrowser;
Assert.ok(stateByBrowser.has(BROWSER_A), "Browser map should have BROWSER_A");
Assert.ok(stateByBrowser.has(BROWSER_B), "Browser map should have BROWSER_B");
SitePermissions.clearTemporaryBlockPermissions(BROWSER_A);
// We only clear block permissions, so we should still see PERM_A.
Assert.deepEqual(
SitePermissions.getForPrincipal(null, PERM_A, BROWSER_A),
{
state: SitePermissions.ALLOW,
scope: SitePermissions.SCOPE_TEMPORARY,
},
"SitePermissions returns ALLOW state for PERM_A."
);
// We don't clear BROWSER_B so it should still be there.
Assert.ok(stateByBrowser.has(BROWSER_B), "Should still have BROWSER_B.");
// Now clear allow permissions for A explicitly.
SitePermissions._temporaryPermissions.clear(BROWSER_A, SitePermissions.ALLOW);
Assert.ok(!stateByBrowser.has(BROWSER_A), "Should no longer have BROWSER_A.");
let browser = stateByBrowser.get(BROWSER_B);
Assert.ok(browser, "Should still have BROWSER_B");
Assert.deepEqual(
SitePermissions.getForPrincipal(null, PERM_A, BROWSER_A),
{
state: SitePermissions.UNKNOWN,
scope: SitePermissions.SCOPE_PERSISTENT,
},
"SitePermissions returns UNKNOWN state for PERM_A."
);
Assert.deepEqual(
SitePermissions.getForPrincipal(null, PERM_B, BROWSER_A),
{
state: SitePermissions.UNKNOWN,
scope: SitePermissions.SCOPE_PERSISTENT,
},
"SitePermissions returns UNKNOWN state for PERM_B."
);
Assert.deepEqual(
SitePermissions.getForPrincipal(null, PERM_C, BROWSER_B),
{
state: SitePermissions.BLOCK,
scope: SitePermissions.SCOPE_TEMPORARY,
},
"SitePermissions returns BLOCK state for PERM_C."
);
SitePermissions._temporaryPermissions.clear(BROWSER_B);
Assert.ok(!stateByBrowser.has(BROWSER_B), "Should no longer have BROWSER_B.");
Assert.deepEqual(
SitePermissions.getForPrincipal(null, PERM_C, BROWSER_B),
{
state: SitePermissions.UNKNOWN,
scope: SitePermissions.SCOPE_PERSISTENT,
},
"SitePermissions returns UNKNOWN state for PERM_C."
);
});
/**
* Tests that the temporary permissions setter calls the callback on permission
* expire with the associated browser.
*/
add_task(async function testCallbackOnExpiry() {
let promiseExpireA = new Promise(resolve => {
TemporaryPermissions.set(
BROWSER_A,
PERM_A,
SitePermissions.BLOCK,
100,
undefined,
resolve
);
});
let promiseExpireB = new Promise(resolve => {
TemporaryPermissions.set(
BROWSER_B,
PERM_A,
SitePermissions.BLOCK,
100,
BROWSER_B.contentPrincipal,
resolve
);
});
let [browserA, browserB] = await Promise.all([
promiseExpireA,
promiseExpireB,
]);
Assert.equal(
browserA,
BROWSER_A,
"Should get callback with browser on expiry for A"
);
Assert.equal(
browserB,
BROWSER_B,
"Should get callback with browser on expiry for B"
);
});
/**
* Tests that the temporary permissions setter calls the callback on permission
* expire with the associated browser if the browser associated browser has
* changed after setting the permission.
*/
add_task(async function testCallbackOnExpiryUpdatedBrowser() {
let promiseExpire = new Promise(resolve => {
TemporaryPermissions.set(
BROWSER_A,
PERM_A,
SitePermissions.BLOCK,
200,
undefined,
resolve
);
});
TemporaryPermissions.copy(BROWSER_A, BROWSER_B);
let browser = await promiseExpire;
Assert.equal(
browser,
BROWSER_B,
"Should get callback with updated browser on expiry."
);
});
/**
* Tests that the permission setter throws an exception if an invalid expiry
* time is passed.
*/
add_task(async function testInvalidExpiryTime() {
let expectedError = /expireTime must be a positive integer/;
Assert.throws(() => {
SitePermissions.setForPrincipal(
null,
PERM_A,
SitePermissions.ALLOW,
SitePermissions.SCOPE_TEMPORARY,
BROWSER_A,
null
);
}, expectedError);
Assert.throws(() => {
SitePermissions.setForPrincipal(
null,
PERM_A,
SitePermissions.ALLOW,
SitePermissions.SCOPE_TEMPORARY,
BROWSER_A,
0
);
}, expectedError);
Assert.throws(() => {
SitePermissions.setForPrincipal(
null,
PERM_A,
SitePermissions.ALLOW,
SitePermissions.SCOPE_TEMPORARY,
BROWSER_A,
-100
);
}, expectedError);
});
/**
* Tests that we block by base domain but allow by origin.
*/
add_task(async function testTemporaryPermissionScope() {
let states = {
},
},
};
for (let state of [SitePermissions.BLOCK, SitePermissions.ALLOW]) {
let matchStrict = state != SitePermissions.BLOCK;
let lists = matchStrict ? states.strict : states.nonStrict;
Object.entries(lists).forEach(([type, list]) => {
let expectSet = type == "same";
for (let uri of list) {
let browser = createDummyBrowser(uri);
SitePermissions.setForPrincipal(
null,
PERM_A,
state,
SitePermissions.SCOPE_TEMPORARY,
browser,
EXPIRY_MS_A
);
ok(true, "origin:" + browser.contentPrincipal.origin);
for (let otherUri of list) {
if (uri == otherUri) {
continue;
}
navigateDummyBrowser(browser, otherUri);
ok(true, "new origin:" + browser.contentPrincipal.origin);
Assert.deepEqual(
SitePermissions.getForPrincipal(null, PERM_A, browser),
{
state: expectSet ? state : SitePermissions.UNKNOWN,
scope: expectSet
? SitePermissions.SCOPE_TEMPORARY
: SitePermissions.SCOPE_PERSISTENT,
},
`${
state == SitePermissions.BLOCK ? "Block" : "Allow"
} Permission originally set for ${uri} should ${
expectSet ? "not" : "also"
} be set for ${otherUri}.`
);
}
SitePermissions._temporaryPermissions.clear(browser);
}
});
}
});
/**
* Tests that we can override the principal to use for keying temporary
* permissions.
*/
add_task(async function testOverrideBrowserURI() {
let testBrowser = createDummyBrowser("https://old.example.com/foo");
let overrideURI = Services.io.newURI("https://test.example.org/test/path");
SitePermissions.setForPrincipal(
Services.scriptSecurityManager.createContentPrincipal(overrideURI, {}),
PERM_A,
SitePermissions.ALLOW,
SitePermissions.SCOPE_TEMPORARY,
testBrowser,
EXPIRY_MS_A
);
Assert.deepEqual(
SitePermissions.getForPrincipal(null, PERM_A, testBrowser),
{
state: SitePermissions.UNKNOWN,
scope: SitePermissions.SCOPE_PERSISTENT,
},
"Permission should not be set for old URI."
);
// "Navigate" to new URI
navigateDummyBrowser(testBrowser, overrideURI);
Assert.deepEqual(
SitePermissions.getForPrincipal(null, PERM_A, testBrowser),
{
state: SitePermissions.ALLOW,
scope: SitePermissions.SCOPE_TEMPORARY,
},
"Permission should be set for new URI."
);
SitePermissions._temporaryPermissions.clear(testBrowser);
});
/**
* Tests that TemporaryPermissions does not throw for incompatible URI or
* browser.currentURI.
*/
add_task(async function testPermissionUnsupportedScheme() {
let aboutURI = Services.io.newURI("about:blank");
// Incompatible override URI should not throw or store any permissions.
SitePermissions.setForPrincipal(
Services.scriptSecurityManager.createContentPrincipal(aboutURI, {}),
PERM_A,
SitePermissions.ALLOW,
SitePermissions.SCOPE_TEMPORARY,
BROWSER_A,
EXPIRY_MS_B
);
Assert.ok(
SitePermissions._temporaryPermissions._stateByBrowser.has(BROWSER_A),
"Should not have stored permission for unsupported URI scheme."
);
let browser = createDummyBrowser("https://example.com/");
// Set a permission so we get an entry in the browser map.
SitePermissions.setForPrincipal(
null,
PERM_B,
SitePermissions.BLOCK,
SitePermissions.SCOPE_TEMPORARY,
browser
);
// Change browser URI to about:blank.
navigateDummyBrowser(browser, aboutURI);
// Setting permission for browser with unsupported URI should not throw.
SitePermissions.setForPrincipal(
null,
PERM_A,
SitePermissions.ALLOW,
SitePermissions.SCOPE_TEMPORARY,
browser
);
Assert.ok(true, "Set should not throw for unsupported URI");
SitePermissions.removeFromPrincipal(null, PERM_A, browser);
Assert.ok(true, "Remove should not throw for unsupported URI");
Assert.deepEqual(
SitePermissions.getForPrincipal(null, PERM_A, browser),
{
state: SitePermissions.UNKNOWN,
scope: SitePermissions.SCOPE_PERSISTENT,
},
"Should return no permission set for unsupported URI."
);
Assert.ok(true, "Get should not throw for unsupported URI");
// getAll should not throw, but return empty permissions array.
let permissions = SitePermissions.getAllForBrowser(browser);
Assert.ok(
Array.isArray(permissions) && !permissions.length,
"Should return empty array for browser on about:blank"
);
SitePermissions._temporaryPermissions.clear(browser);
});