Source code

Revision control

Copy as Markdown

Other Tools

Test Info:

/* Any copyright is dedicated to the Public Domain.
"use strict";
var { AppConstants } = ChromeUtils.importESModule(
);
const { ExtensionPermissions } = ChromeUtils.importESModule(
);
let gHandlerService = Cc["@mozilla.org/uriloader/handler-service;1"].getService(
Ci.nsIHandlerService
);
const ROOT_PATH = getRootDirectory(gTestPath).replace(
""
);
// Testing multiple protocol / origin combinations takes long on debug.
requestLongerTimeout(7);
const DIALOG_URL_APP_CHOOSER =
const DIALOG_URL_PERMISSION =
const PROTOCOL_HANDLER_OPEN_PERM_KEY = "open-protocol-handler";
const PERMISSION_KEY_DELIMITER = "^";
const TEST_PROTOS = ["foo", "bar"];
let testDir = getChromeDir(getResolvedURI(gTestPath));
const ORIGIN1 = "https://example.com";
const ORIGIN2 = "https://example.org";
const ORIGIN3 = Services.io.newFileURI(testDir).spec;
const PRINCIPAL1 =
Services.scriptSecurityManager.createContentPrincipalFromOrigin(ORIGIN1);
const PRINCIPAL2 =
Services.scriptSecurityManager.createContentPrincipalFromOrigin(ORIGIN2);
const PRINCIPAL3 =
Services.scriptSecurityManager.createContentPrincipalFromOrigin(ORIGIN3);
const NULL_PRINCIPAL_SCHEME = Services.scriptSecurityManager
.createNullPrincipal({})
.scheme.toLowerCase();
/**
* Get the open protocol handler permission key for a given protocol scheme.
* @param {string} aProtocolScheme - Scheme of protocol to construct permission
* key with.
*/
function getSkipProtoDialogPermissionKey(aProtocolScheme) {
return (
PROTOCOL_HANDLER_OPEN_PERM_KEY + PERMISSION_KEY_DELIMITER + aProtocolScheme
);
}
function getSystemProtocol() {
// TODO add a scheme for Windows 10 or greater once support is added (see bug 1764599).
if (AppConstants.platform == "macosx") {
return "itunes";
}
info(
"Skipping this test since there isn't a suitable default protocol on this platform"
);
return null;
}
/**
* Creates dummy web protocol handlers used for testing.
*/
function initTestHandlers() {
TEST_PROTOS.forEach(scheme => {
let webHandler = Cc[
"@mozilla.org/uriloader/web-handler-app;1"
].createInstance(Ci.nsIWebHandlerApp);
webHandler.name = scheme + "Handler";
webHandler.uriTemplate = ORIGIN1 + "/?url=%s";
let handlerInfo = HandlerServiceTestUtils.getBlankHandlerInfo(scheme);
handlerInfo.possibleApplicationHandlers.appendElement(webHandler);
handlerInfo.preferredApplicationHandler = webHandler;
gHandlerService.store(handlerInfo);
});
}
/**
* Update whether the protocol handler dialog is shown for our test protocol +
* handler.
* @param {string} scheme - Scheme of the protocol to change the ask state for.
* @param {boolean} ask - true => show dialog, false => skip dialog.
*/
function updateAlwaysAsk(scheme, ask) {
let handlerInfo = HandlerServiceTestUtils.getHandlerInfo(scheme);
handlerInfo.alwaysAskBeforeHandling = ask;
gHandlerService.store(handlerInfo);
}
/**
* Test whether the protocol handler dialog is set to show for our
* test protocol + handler.
* @param {string} scheme - Scheme of the protocol to test the ask state for.
* @param {boolean} ask - true => show dialog, false => skip dialog.
*/
function testAlwaysAsk(scheme, ask) {
is(
HandlerServiceTestUtils.getHandlerInfo(scheme).alwaysAskBeforeHandling,
ask,
"Should have correct alwaysAsk state."
);
}
/**
* Triggers the load via a server redirect.
* @param {string} serverRedirect - The redirect type.
*/
function useServerRedirect(serverRedirect) {
return async (browser, scheme) => {
let uri = `${scheme}://test`;
let innerParams = new URLSearchParams();
innerParams.set("uri", uri);
innerParams.set("redirectType", serverRedirect);
let params = new URLSearchParams();
params.set(
"uri",
ROOT_PATH +
"redirect_helper.sjs?" +
innerParams.toString()
);
uri =
ROOT_PATH +
"redirect_helper.sjs?" +
params.toString();
BrowserTestUtils.startLoadingURIString(browser, uri);
};
}
/**
* Triggers the load with a specific principal or the browser's current
* principal.
* @param {nsIPrincipal} [principal] - Principal to use to trigger the load.
*/
function useTriggeringPrincipal(principal = undefined) {
return async (browser, scheme) => {
let uri = `${scheme}://test`;
let triggeringPrincipal = principal ?? browser.contentPrincipal;
info("Loading uri: " + uri);
browser.loadURI(Services.io.newURI(uri), { triggeringPrincipal });
};
}
/**
* Navigates to a test URL with the given protocol scheme and waits for the
* result.
* @param {MozBrowser} browser - Browser to navigate.
* @param {string} scheme - Scheme of the test url. e.g. irc
* @param {Object} [options] - Test options.
* @param {Object} [options.permDialogOptions] - Test options for the permission
* dialog. If defined, we expect this dialog to be shown.
* @param {Object} [options.chooserDialogOptions] - Test options for the chooser
* dialog. If defined, we expect this dialog to be shown.
* @param {Function} [options.triggerLoad] - An async callback function to
* trigger the load. Will be passed the browser and scheme to use.
* @param {nsIPrincipal} [options.triggeringPrincipal] - Principal to trigger
* the load with. Defaults to the browsers content principal.
* @returns {Promise} - A promise which resolves once the test is complete.
*/
async function testOpenProto(
browser,
scheme,
{
permDialogOptions,
chooserDialogOptions,
triggerLoad = useTriggeringPrincipal(),
} = {}
) {
let permDialogOpenPromise;
let chooserDialogOpenPromise;
if (permDialogOptions) {
info("Should see permission dialog");
permDialogOpenPromise = waitForProtocolPermissionDialog(browser, true);
}
if (chooserDialogOptions) {
info("Should see chooser dialog");
chooserDialogOpenPromise = waitForProtocolAppChooserDialog(browser, true);
}
await triggerLoad(browser, scheme);
let webHandlerLoadedPromise;
let webHandlerShouldOpen =
(!permDialogOptions && !chooserDialogOptions) ||
((permDialogOptions?.actionConfirm || permDialogOptions?.actionChangeApp) &&
chooserDialogOptions?.actionConfirm);
// Register web handler load listener if we expect to trigger it.
if (webHandlerShouldOpen) {
webHandlerLoadedPromise = waitForHandlerURL(browser, scheme);
}
if (permDialogOpenPromise) {
let dialog = await permDialogOpenPromise;
let dialogEl = getDialogElementFromSubDialog(dialog);
let dialogType = getDialogType(dialog);
let {
hasCheckbox,
checkboxOrigin,
hasChangeApp,
chooserIsNext,
actionCheckbox,
actionConfirm,
actionChangeApp,
checkContents,
} = permDialogOptions;
if (actionChangeApp) {
actionConfirm = false;
}
let descriptionEl = dialogEl.querySelector("#description");
ok(
descriptionEl && BrowserTestUtils.isVisible(descriptionEl),
"Has a visible description element."
);
ok(
!descriptionEl.innerHTML.toLowerCase().includes(NULL_PRINCIPAL_SCHEME),
"Description does not include NullPrincipal scheme."
);
await testCheckbox(dialogEl, dialogType, {
hasCheckbox,
actionCheckbox,
checkboxOrigin,
});
// Check the button label depending on whether we would show the chooser
// dialog next or directly open the handler.
let acceptBtnLabel = dialogEl.getButton("accept")?.label;
if (chooserIsNext) {
is(
acceptBtnLabel,
"Choose Application",
"Accept button has choose app label"
);
} else {
is(acceptBtnLabel, "Open Link", "Accept button has open link label");
}
let changeAppLink = dialogEl.ownerDocument.getElementById("change-app");
if (typeof hasChangeApp == "boolean") {
ok(changeAppLink, "Permission dialog should have changeApp link label");
is(
!changeAppLink.hidden,
hasChangeApp,
"Permission dialog change app link label"
);
}
if (checkContents) {
checkContents(dialogEl);
}
if (actionChangeApp) {
let dialogClosedPromise = waitForProtocolPermissionDialog(browser, false);
changeAppLink.click();
await dialogClosedPromise;
} else {
await closeDialog(browser, dialog, actionConfirm, scheme);
}
}
if (chooserDialogOpenPromise) {
let dialog = await chooserDialogOpenPromise;
let dialogEl = getDialogElementFromSubDialog(dialog);
let dialogType = getDialogType(dialog);
let { hasCheckbox, actionCheckbox, actionConfirm } = chooserDialogOptions;
await testCheckbox(dialogEl, dialogType, {
hasCheckbox,
actionCheckbox,
});
await closeDialog(browser, dialog, actionConfirm, scheme);
}
if (webHandlerShouldOpen) {
info("Waiting for web handler to open");
await webHandlerLoadedPromise;
} else {
info("Web handler open canceled");
}
}
/**
* Inspects the checkbox state and interacts with it.
* @param {dialog} dialogEl
* @param {string} dialogType - String identifier of dialog type.
* Either "permission" or "chooser".
* @param {Object} options - Test Options.
* @param {boolean} [options.hasCheckbox] - Whether the dialog is expected to
* have a visible checkbox.
* @param {boolean} [options.hasCheckboxState] - The check state of the checkbox
* to test for. true = checked, false = unchecked.
* @param {boolean} [options.actionCheckbox] - The state to set on the checkbox.
* true = checked, false = unchecked.
*/
async function testCheckbox(
dialogEl,
dialogType,
{ hasCheckbox, hasCheckboxState = false, actionCheckbox, checkboxOrigin }
) {
let checkbox = dialogEl.ownerDocument.getElementById("remember");
if (typeof hasCheckbox == "boolean") {
is(
checkbox && BrowserTestUtils.isVisible(checkbox),
hasCheckbox,
"Dialog checkbox has correct visibility."
);
let checkboxLabel = dialogEl.ownerDocument.getElementById("remember-label");
is(
checkbox && BrowserTestUtils.isVisible(checkboxLabel),
hasCheckbox,
"Dialog checkbox label has correct visibility."
);
if (hasCheckbox) {
ok(
!checkboxLabel.innerHTML.toLowerCase().includes(NULL_PRINCIPAL_SCHEME),
"Dialog checkbox label does not include NullPrincipal scheme."
);
}
}
if (typeof hasCheckboxState == "boolean") {
is(checkbox.checked, hasCheckboxState, "Dialog checkbox has correct state");
}
if (checkboxOrigin) {
let doc = dialogEl.ownerDocument;
let hostFromLabel = doc.l10n.getAttributes(
doc.getElementById("remember-label")
).args.host;
is(hostFromLabel, checkboxOrigin, "Checkbox should be for correct domain.");
}
if (typeof actionCheckbox == "boolean") {
checkbox.click();
}
}
/**
* Get the dialog element which is a child of the SubDialogs browser frame.
* @param {SubDialog} subDialog - Dialog to get the dialog element for.
*/
function getDialogElementFromSubDialog(subDialog) {
let dialogEl = subDialog._frame.contentDocument.querySelector("dialog");
ok(dialogEl, "SubDialog should have dialog element");
return dialogEl;
}
/**
* Wait for the test handler to be opened.
* @param {MozBrowser} browser - The browser the load should occur in.
* @param {string} scheme - Scheme which triggered the handler to open.
*/
function waitForHandlerURL(browser, scheme) {
return BrowserTestUtils.browserLoaded(
browser,
false,
url => url == `${ORIGIN1}/?url=${scheme}%3A%2F%2Ftest`
);
}
/**
* Test for open-protocol-handler permission.
* @param {nsIPrincipal} principal - The principal to test the permission on.
* @param {string} scheme - Scheme to generate permission key.
* @param {boolean} hasPerm - Whether we expect the princial to set the
* permission (true), or not (false).
*/
function testPermission(principal, scheme, hasPerm) {
let permKey = getSkipProtoDialogPermissionKey(scheme);
let result = Services.perms.testPermissionFromPrincipal(principal, permKey);
let message = `${permKey} ${hasPerm ? "is" : "is not"} set for ${
principal.origin
}.`;
is(result == Services.perms.ALLOW_ACTION, hasPerm, message);
}
/**
* Get the checkbox element of the dialog used to remember the handler choice or
* store the permission.
* @param {SubDialog} dialog - Protocol handler dialog embedded in a SubDialog.
* @param {string} dialogType - Type of the dialog which holds the checkbox.
* @returns {HTMLInputElement} - Checkbox of the dialog.
*/
function getDialogCheckbox(dialog, dialogType) {
let id;
if (dialogType == "permission") {
id = "remember-permission";
} else {
id = "remember";
}
return dialog._frame.contentDocument.getElementById(id);
}
function getDialogType(dialog) {
let url = dialog._frame.currentURI.spec;
if (url === DIALOG_URL_PERMISSION) {
return "permission";
}
if (url === DIALOG_URL_APP_CHOOSER) {
return "chooser";
}
throw new Error("Dialog with unexpected url");
}
/**
* Exit a protocol handler SubDialog and wait for it to be fully closed.
* @param {MozBrowser} browser - Browser element of the tab where the dialog is
* shown.
* @param {SubDialog} dialog - SubDialog object which holds the protocol handler
* @param {boolean} confirm - Whether to confirm (true) or cancel (false) the
* dialog.
* @param {string} scheme - The scheme of the protocol the dialog is opened for.
* dialog.
*/
async function closeDialog(browser, dialog, confirm, scheme) {
let dialogClosedPromise = waitForSubDialog(browser, dialog._openedURL, false);
let dialogEl = getDialogElementFromSubDialog(dialog);
if (confirm) {
if (getDialogType(dialog) == "chooser") {
// Select our test protocol handler
let listItem = dialogEl.ownerDocument.querySelector(
`richlistitem[name="${scheme}Handler"]`
);
listItem.click();
}
dialogEl.setAttribute("buttondisabledaccept", false);
dialogEl.acceptDialog();
} else {
dialogEl.cancelDialog();
}
return dialogClosedPromise;
}
registerCleanupFunction(function () {
// Clean up test handlers
TEST_PROTOS.forEach(scheme => {
let handlerInfo = HandlerServiceTestUtils.getHandlerInfo(scheme);
gHandlerService.remove(handlerInfo);
});
// Clear permissions
Services.perms.removeAll();
});
add_setup(async function () {
await SpecialPowers.pushPrefEnv({
set: [["security.external_protocol_requires_permission", true]],
});
initTestHandlers();
});
/**
* Tests that when "remember" is unchecked, we only allow the protocol to be
* opened once and don't store any permission.
*/
add_task(async function test_permission_allow_once() {
for (let scheme of TEST_PROTOS) {
await BrowserTestUtils.withNewTab(ORIGIN1, async browser => {
await testOpenProto(browser, scheme, {
permDialogOptions: {
hasCheckbox: true,
hasChangeApp: false,
chooserIsNext: true,
actionConfirm: true,
},
chooserDialogOptions: { hasCheckbox: true, actionConfirm: true },
});
});
// No permission should be set
testPermission(PRINCIPAL1, scheme, false);
testPermission(PRINCIPAL2, scheme, false);
// No preferred app should be set
testAlwaysAsk(scheme, true);
// If we open again we should see the permission dialog
await BrowserTestUtils.withNewTab(ORIGIN1, async browser => {
await testOpenProto(browser, scheme, {
permDialogOptions: {
hasCheckbox: true,
hasChangeApp: false,
chooserIsNext: true,
actionConfirm: false,
},
});
});
}
});
/**
* Tests that when checking the "remember" checkbox, the protocol permission
* is set correctly and allows the caller to skip the permission dialog in
* subsequent calls.
*/
add_task(async function test_permission_allow_persist() {
for (let [origin, principal] of [
[ORIGIN1, PRINCIPAL1],
[ORIGIN3, PRINCIPAL3],
]) {
for (let scheme of TEST_PROTOS) {
info("Testing with origin " + origin);
info("testing with principal of origin " + principal.origin);
info("testing with protocol " + scheme);
// Set a permission for an unrelated protocol.
// We should still see the permission dialog.
Services.perms.addFromPrincipal(
principal,
getSkipProtoDialogPermissionKey("foobar"),
Services.perms.ALLOW_ACTION
);
await BrowserTestUtils.withNewTab(origin, async browser => {
await testOpenProto(browser, scheme, {
permDialogOptions: {
hasCheckbox: true,
hasChangeApp: false,
chooserIsNext: true,
actionCheckbox: true,
actionConfirm: true,
},
chooserDialogOptions: { hasCheckbox: true, actionConfirm: true },
});
});
// Permission should be set
testPermission(principal, scheme, true);
testPermission(PRINCIPAL2, scheme, false);
// No preferred app should be set
testAlwaysAsk(scheme, true);
// If we open again with the origin where we granted permission, we should
// directly get the chooser dialog.
await BrowserTestUtils.withNewTab(origin, async browser => {
await testOpenProto(browser, scheme, {
chooserDialogOptions: {
hasCheckbox: true,
actionConfirm: false,
},
});
});
// If we open with the other origin, we should see the permission dialog
await BrowserTestUtils.withNewTab(ORIGIN2, async browser => {
await testOpenProto(browser, scheme, {
permDialogOptions: {
hasCheckbox: true,
hasChangeApp: false,
chooserIsNext: true,
actionConfirm: false,
},
});
});
// Cleanup permissions
Services.perms.removeAll();
}
}
});
/**
* Tests that if a preferred protocol handler is set, the permission dialog
* shows the application name and a link which leads to the app chooser.
*/
add_task(async function test_permission_application_set() {
let scheme = TEST_PROTOS[0];
updateAlwaysAsk(scheme, false);
await BrowserTestUtils.withNewTab(ORIGIN1, async browser => {
await testOpenProto(browser, scheme, {
permDialogOptions: {
hasCheckbox: true,
hasChangeApp: true,
chooserIsNext: false,
actionChangeApp: true,
},
chooserDialogOptions: { hasCheckbox: true, actionConfirm: true },
});
});
// Cleanup
updateAlwaysAsk(scheme, true);
});
/**
* Tests that we correctly handle system principals. They should always
* show the permission dialog, but give the option to choose another
* app if there isn't a default handler.
*/
add_task(async function test_permission_system_principal() {
let scheme = TEST_PROTOS[0];
await BrowserTestUtils.withNewTab(ORIGIN1, async browser => {
await testOpenProto(browser, scheme, {
permDialogOptions: {
hasCheckbox: false,
hasChangeApp: false,
chooserIsNext: true,
actionChangeApp: false,
},
triggerLoad: useTriggeringPrincipal(
Services.scriptSecurityManager.getSystemPrincipal()
),
});
});
});
/**
* Tests that we correctly handle system principals and show
* a simplified permission dialog if there is a default handler.
*/
add_task(async function test_permission_system_principal() {
let scheme = getSystemProtocol();
if (!scheme) {
return;
}
await BrowserTestUtils.withNewTab(ORIGIN1, async browser => {
await testOpenProto(browser, scheme, {
permDialogOptions: {
hasCheckbox: false,
hasChangeApp: false,
chooserIsNext: false,
actionChangeApp: false,
},
triggerLoad: useTriggeringPrincipal(
Services.scriptSecurityManager.getSystemPrincipal()
),
});
});
});
/**
* Tests that we don't show the permission dialog if the permission is disabled
* by pref.
*/
add_task(async function test_permission_disabled() {
let scheme = TEST_PROTOS[0];
await SpecialPowers.pushPrefEnv({
set: [["security.external_protocol_requires_permission", false]],
});
await BrowserTestUtils.withNewTab(ORIGIN1, async browser => {
await testOpenProto(browser, scheme, {
chooserDialogOptions: { hasCheckbox: true, actionConfirm: true },
});
});
await SpecialPowers.popPrefEnv();
});
/**
* Tests that we directly open the handler if permission and handler are set.
*/
add_task(async function test_app_and_permission_set() {
let scheme = TEST_PROTOS[1];
updateAlwaysAsk(scheme, false);
Services.perms.addFromPrincipal(
PRINCIPAL2,
getSkipProtoDialogPermissionKey(scheme),
Services.perms.ALLOW_ACTION
);
await BrowserTestUtils.withNewTab(ORIGIN2, async browser => {
await testOpenProto(browser, scheme);
});
// Cleanup
Services.perms.removeAll();
updateAlwaysAsk(scheme, true);
});
/**
* Tests that the alwaysAsk state is not updated if the user cancels the dialog
*/
add_task(async function test_change_app_checkbox_cancel() {
let scheme = TEST_PROTOS[0];
await BrowserTestUtils.withNewTab(ORIGIN1, async browser => {
await testOpenProto(browser, scheme, {
permDialogOptions: {
hasCheckbox: true,
chooserIsNext: true,
hasChangeApp: false,
actionConfirm: true,
},
chooserDialogOptions: {
hasCheckbox: true,
actionCheckbox: true, // Activate checkbox
actionConfirm: false, // Cancel dialog
},
});
});
// Should not have applied value from checkbox
testAlwaysAsk(scheme, true);
});
/**
* Tests that the external protocol dialogs behave correctly when a null
* principal is passed.
*/
add_task(async function test_null_principal() {
let scheme = TEST_PROTOS[0];
await BrowserTestUtils.withNewTab(ORIGIN1, async browser => {
await testOpenProto(browser, scheme, {
triggerLoad: () => {
let uri = `${scheme}://test`;
ContentTask.spawn(browser, { uri }, args => {
let frame = content.document.createElement("iframe");
frame.src = `data:text/html,<script>location.href="${args.uri}"</script>`;
content.document.body.appendChild(frame);
});
},
permDialogOptions: {
hasCheckbox: false,
chooserIsNext: true,
hasChangeApp: false,
actionConfirm: true,
},
chooserDialogOptions: {
hasCheckbox: true,
actionConfirm: false, // Cancel dialog
},
});
});
});
/**
* Tests that the external protocol dialogs behave correctly when no principal
* is passed.
*/
add_task(async function test_no_principal() {
let scheme = TEST_PROTOS[1];
await BrowserTestUtils.withNewTab(ORIGIN1, async browser => {
await testOpenProto(browser, scheme, {
triggerLoad: () => {
let uri = `${scheme}://test`;
let contentDispatchChooser = Cc[
"@mozilla.org/content-dispatch-chooser;1"
].createInstance(Ci.nsIContentDispatchChooser);
let handler = HandlerServiceTestUtils.getHandlerInfo(scheme);
contentDispatchChooser.handleURI(
handler,
Services.io.newURI(uri),
null,
browser.browsingContext
);
},
permDialogOptions: {
hasCheckbox: false,
chooserIsNext: true,
hasChangeApp: false,
actionConfirm: true,
},
chooserDialogOptions: {
hasCheckbox: true,
actionConfirm: false, // Cancel dialog
},
});
});
});
/**
* Tests that if a URI scheme has a non-standard protocol, an OS default exists,
* and the user hasn't selected an alternative only the permission dialog is shown.
*/
add_task(async function test_non_standard_protocol() {
let scheme = getSystemProtocol();
if (!scheme) {
return;
}
await BrowserTestUtils.withNewTab(ORIGIN1, async browser => {
await testOpenProto(browser, scheme, {
permDialogOptions: {
hasCheckbox: true,
hasChangeApp: true,
chooserIsNext: false,
actionChangeApp: false,
},
});
});
});
/**
* Tests that we show the permission dialog for extension content scripts.
*/
add_task(async function test_extension_content_script_permission() {
let scheme = TEST_PROTOS[0];
await BrowserTestUtils.withNewTab(ORIGIN1, async browser => {
let testExtension;
await testOpenProto(browser, scheme, {
triggerLoad: async () => {
let uri = `${scheme}://test`;
const EXTENSION_DATA = {
manifest: {
content_scripts: [
{
matches: [browser.currentURI.spec],
js: ["navigate.js"],
},
],
browser_specific_settings: {
gecko: { id: "allowed@mochi.test" },
},
},
files: {
"navigate.js": `window.location.href = "${uri}";`,
},
useAddonManager: "permanent",
};
testExtension = ExtensionTestUtils.loadExtension(EXTENSION_DATA);
await testExtension.startup();
},
permDialogOptions: {
hasCheckbox: true,
chooserIsNext: true,
hasChangeApp: false,
actionCheckbox: true,
actionConfirm: true,
checkContents: dialogEl => {
let description = dialogEl.querySelector("#description");
let { id, args } =
description.ownerDocument.l10n.getAttributes(description);
is(
id,
"permission-dialog-description-extension",
"Should be using the correct string."
);
is(
args.extension,
"Generated extension",
"Should have the correct extension name."
);
},
},
chooserDialogOptions: {
hasCheckbox: true,
actionConfirm: false, // Cancel dialog
},
});
let extensionPrincipal =
Services.scriptSecurityManager.createContentPrincipal(
Services.io.newURI(`moz-extension://${testExtension.uuid}/`),
{}
);
let extensionPrivatePrincipal =
Services.scriptSecurityManager.createContentPrincipal(
Services.io.newURI(`moz-extension://${testExtension.uuid}/`),
{ privateBrowsingId: 1 }
);
let key = getSkipProtoDialogPermissionKey(scheme);
is(
Services.perms.testPermissionFromPrincipal(extensionPrincipal, key),
Services.perms.ALLOW_ACTION,
"Should have permanently allowed the extension"
);
is(
Services.perms.testPermissionFromPrincipal(
extensionPrivatePrincipal,
key
),
Services.perms.UNKNOWN_ACTION,
"Should not have changed the private principal permission"
);
is(
Services.perms.testPermissionFromPrincipal(PRINCIPAL1, key),
Services.perms.UNKNOWN_ACTION,
"Should not have allowed the page"
);
await testExtension.unload();
is(
Services.perms.testPermissionFromPrincipal(extensionPrincipal, key),
Services.perms.UNKNOWN_ACTION,
"Should have cleared the extension's normal principal permission"
);
is(
Services.perms.testPermissionFromPrincipal(
extensionPrivatePrincipal,
key
),
Services.perms.UNKNOWN_ACTION,
"Should have cleared the private browsing principal"
);
});
});
/**
* Tests that we show the permission dialog for extension content scripts.
*/
add_task(async function test_extension_private_content_script_permission() {
let scheme = TEST_PROTOS[0];
let win = await BrowserTestUtils.openNewBrowserWindow({ private: true });
await BrowserTestUtils.withNewTab(
{ gBrowser: win.gBrowser, url: ORIGIN1 },
async browser => {
let testExtension;
await testOpenProto(browser, scheme, {
triggerLoad: async () => {
let uri = `${scheme}://test`;
const EXTENSION_DATA = {
manifest: {
content_scripts: [
{
matches: [browser.currentURI.spec],
js: ["navigate.js"],
},
],
browser_specific_settings: {
gecko: { id: "allowed@mochi.test" },
},
},
files: {
"navigate.js": `window.location.href = "${uri}";`,
},
useAddonManager: "permanent",
};
testExtension = ExtensionTestUtils.loadExtension(EXTENSION_DATA);
await testExtension.startup();
let perms = {
permissions: ["internal:privateBrowsingAllowed"],
origins: [],
};
await ExtensionPermissions.add("allowed@mochi.test", perms);
let addon = await AddonManager.getAddonByID("allowed@mochi.test");
await addon.reload();
},
permDialogOptions: {
hasCheckbox: true,
chooserIsNext: true,
hasChangeApp: false,
actionCheckbox: true,
actionConfirm: true,
checkContents: dialogEl => {
let description = dialogEl.querySelector("#description");
let { id, args } =
description.ownerDocument.l10n.getAttributes(description);
is(
id,
"permission-dialog-description-extension",
"Should be using the correct string."
);
is(
args.extension,
"Generated extension",
"Should have the correct extension name."
);
},
},
chooserDialogOptions: {
hasCheckbox: true,
actionConfirm: false, // Cancel dialog
},
});
let extensionPrincipal =
Services.scriptSecurityManager.createContentPrincipal(
Services.io.newURI(`moz-extension://${testExtension.uuid}/`),
{}
);
let extensionPrivatePrincipal =
Services.scriptSecurityManager.createContentPrincipal(
Services.io.newURI(`moz-extension://${testExtension.uuid}/`),
{ privateBrowsingId: 1 }
);
let key = getSkipProtoDialogPermissionKey(scheme);
is(
Services.perms.testPermissionFromPrincipal(extensionPrincipal, key),
Services.perms.UNKNOWN_ACTION,
"Should not have changed the extension's normal principal permission"
);
is(
Services.perms.testPermissionFromPrincipal(
extensionPrivatePrincipal,
key
),
Services.perms.ALLOW_ACTION,
"Should have allowed the private browsing principal"
);
is(
Services.perms.testPermissionFromPrincipal(PRINCIPAL1, key),
Services.perms.UNKNOWN_ACTION,
"Should not have allowed the page"
);
await testExtension.unload();
is(
Services.perms.testPermissionFromPrincipal(extensionPrincipal, key),
Services.perms.UNKNOWN_ACTION,
"Should have cleared the extension's normal principal permission"
);
is(
Services.perms.testPermissionFromPrincipal(
extensionPrivatePrincipal,
key
),
Services.perms.UNKNOWN_ACTION,
"Should have cleared the private browsing principal"
);
}
);
await BrowserTestUtils.closeWindow(win);
});
/**
* Tests that we do not show the permission dialog for extension content scripts
* when the page already has permission.
*/
add_task(async function test_extension_allowed_content() {
let scheme = TEST_PROTOS[0];
await BrowserTestUtils.withNewTab(ORIGIN1, async browser => {
let testExtension;
let key = getSkipProtoDialogPermissionKey(scheme);
Services.perms.addFromPrincipal(
PRINCIPAL1,
key,
Services.perms.ALLOW_ACTION,
Services.perms.EXPIRE_NEVER
);
await testOpenProto(browser, scheme, {
triggerLoad: async () => {
let uri = `${scheme}://test`;
const EXTENSION_DATA = {
manifest: {
content_scripts: [
{
matches: [browser.currentURI.spec],
js: ["navigate.js"],
},
],
},
files: {
"navigate.js": `window.location.href = "${uri}";`,
},
};
testExtension = ExtensionTestUtils.loadExtension(EXTENSION_DATA);
await testExtension.startup();
},
chooserDialogOptions: {
hasCheckbox: true,
actionConfirm: false, // Cancel dialog
},
});
let extensionPrincipal =
Services.scriptSecurityManager.createContentPrincipal(
Services.io.newURI(`moz-extension://${testExtension.uuid}/`),
{}
);
is(
Services.perms.testPermissionFromPrincipal(extensionPrincipal, key),
Services.perms.UNKNOWN_ACTION,
"Should not have permanently allowed the extension"
);
await testExtension.unload();
Services.perms.removeFromPrincipal(PRINCIPAL1, key);
});
});
/**
* Tests that we do not show the permission dialog for extension content scripts
* when the extension already has permission.
*/
add_task(async function test_extension_allowed_extension() {
let scheme = TEST_PROTOS[0];
await BrowserTestUtils.withNewTab(ORIGIN1, async browser => {
let testExtension;
let key = getSkipProtoDialogPermissionKey(scheme);
await testOpenProto(browser, scheme, {
triggerLoad: async () => {
const EXTENSION_DATA = {
manifest: {
permissions: [`${ORIGIN1}/*`],
},
background() {
browser.test.onMessage.addListener(async (msg, uri) => {
switch (msg) {
case "engage":
browser.tabs.executeScript({
code: `window.location.href = "${uri}";`,
});
break;
default:
browser.test.fail(`Unexpected message received: ${msg}`);
}
});
},
};
testExtension = ExtensionTestUtils.loadExtension(EXTENSION_DATA);
await testExtension.startup();
let extensionPrincipal =
Services.scriptSecurityManager.createContentPrincipal(
Services.io.newURI(`moz-extension://${testExtension.uuid}/`),
{}
);
Services.perms.addFromPrincipal(
extensionPrincipal,
key,
Services.perms.ALLOW_ACTION,
Services.perms.EXPIRE_NEVER
);
testExtension.sendMessage("engage", `${scheme}://test`);
},
chooserDialogOptions: {
hasCheckbox: true,
actionConfirm: false, // Cancel dialog
},
});
await testExtension.unload();
Services.perms.removeFromPrincipal(PRINCIPAL1, key);
});
});
/**
* Tests that we show the permission dialog for extensions directly opening a
* protocol.
*/
add_task(async function test_extension_principal() {
let scheme = TEST_PROTOS[0];
await BrowserTestUtils.withNewTab(ORIGIN1, async browser => {
let testExtension;
await testOpenProto(browser, scheme, {
triggerLoad: async () => {
const EXTENSION_DATA = {
background() {
browser.test.onMessage.addListener(async (msg, url) => {
switch (msg) {
case "engage":
browser.tabs.update({
url,
});
break;
default:
browser.test.fail(`Unexpected message received: ${msg}`);
}
});
},
};
testExtension = ExtensionTestUtils.loadExtension(EXTENSION_DATA);
await testExtension.startup();
testExtension.sendMessage("engage", `${scheme}://test`);
},
permDialogOptions: {
hasCheckbox: true,
chooserIsNext: true,
hasChangeApp: false,
actionCheckbox: true,
actionConfirm: true,
checkContents: dialogEl => {
let description = dialogEl.querySelector("#description");
let { id, args } =
description.ownerDocument.l10n.getAttributes(description);
is(
id,
"permission-dialog-description-extension",
"Should be using the correct string."
);
is(
args.extension,
"Generated extension",
"Should have the correct extension name."
);
},
},
chooserDialogOptions: {
hasCheckbox: true,
actionConfirm: false, // Cancel dialog
},
});
let extensionPrincipal =
Services.scriptSecurityManager.createContentPrincipal(
Services.io.newURI(`moz-extension://${testExtension.uuid}/`),
{}
);
let key = getSkipProtoDialogPermissionKey(scheme);
is(
Services.perms.testPermissionFromPrincipal(extensionPrincipal, key),
Services.perms.ALLOW_ACTION,
"Should have permanently allowed the extension"
);
is(
Services.perms.testPermissionFromPrincipal(PRINCIPAL1, key),
Services.perms.UNKNOWN_ACTION,
"Should not have allowed the page"
);
await testExtension.unload();
});
});
/**
* Test that we use the redirect principal for the dialog when applicable.
*/
add_task(async function test_redirect_principal() {
let scheme = TEST_PROTOS[0];
await BrowserTestUtils.withNewTab("about:blank", async browser => {
await testOpenProto(browser, scheme, {
triggerLoad: useServerRedirect("location"),
permDialogOptions: {
checkboxOrigin: ORIGIN1,
chooserIsNext: true,
hasCheckbox: true,
actionConfirm: false, // Cancel dialog
},
});
});
});
/**
* Test that we use the redirect principal for the dialog for refresh headers.
*/
add_task(async function test_redirect_principal() {
let scheme = TEST_PROTOS[0];
await BrowserTestUtils.withNewTab("about:blank", async browser => {
await testOpenProto(browser, scheme, {
triggerLoad: useServerRedirect("refresh"),
permDialogOptions: {
checkboxOrigin: ORIGIN1,
chooserIsNext: true,
hasCheckbox: true,
actionConfirm: false, // Cancel dialog
},
});
});
});
/**
* Test that we use the redirect principal for the dialog for meta refreshes.
*/
add_task(async function test_redirect_principal() {
let scheme = TEST_PROTOS[0];
await BrowserTestUtils.withNewTab("about:blank", async browser => {
await testOpenProto(browser, scheme, {
triggerLoad: useServerRedirect("meta-refresh"),
permDialogOptions: {
checkboxOrigin: ORIGIN1,
chooserIsNext: true,
hasCheckbox: true,
actionConfirm: false, // Cancel dialog
},
});
});
});
/**
* Test that we use the redirect principal for the dialog for JS redirects.
*/
add_task(async function test_redirect_principal_js() {
let scheme = TEST_PROTOS[0];
await BrowserTestUtils.withNewTab("about:blank", async browser => {
await testOpenProto(browser, scheme, {
triggerLoad: () => {
let uri = `${scheme}://test`;
let innerParams = new URLSearchParams();
innerParams.set("uri", uri);
let params = new URLSearchParams();
params.set(
"uri",
ROOT_PATH +
"script_redirect.html?" +
innerParams.toString()
);
uri =
ROOT_PATH +
"script_redirect.html?" +
params.toString();
BrowserTestUtils.startLoadingURIString(browser, uri);
},
permDialogOptions: {
checkboxOrigin: ORIGIN1,
chooserIsNext: true,
hasCheckbox: true,
actionConfirm: false, // Cancel dialog
},
});
});
});
/**
* Test that we use the redirect principal for the dialog for link clicks.
*/
add_task(async function test_redirect_principal_links() {
let scheme = TEST_PROTOS[0];
await BrowserTestUtils.withNewTab("about:blank", async browser => {
await testOpenProto(browser, scheme, {
triggerLoad: async () => {
let uri = `${scheme}://test`;
let params = new URLSearchParams();
params.set("uri", uri);
uri =
ROOT_PATH +
"redirect_helper.sjs?" +
params.toString();
await ContentTask.spawn(browser, { uri }, args => {
let textLink = content.document.createElement("a");
textLink.href = args.uri;
textLink.textContent = "click me";
content.document.body.appendChild(textLink);
textLink.click();
});
},
permDialogOptions: {
checkboxOrigin: ORIGIN1,
chooserIsNext: true,
hasCheckbox: true,
actionConfirm: false, // Cancel dialog
},
});
});
});