Bug 1875065 - [bidi] Implement "permissions.setPermission" command. r=webdriver-reviewers,jdescottes,whimboo

Differential Revision: https://phabricator.services.mozilla.com/D208864
This commit is contained in:
Alexandra Borovova 2024-05-08 16:50:30 +00:00
parent 0b87a388da
commit c7406cf09f
9 changed files with 262 additions and 139 deletions

View file

@ -3357,22 +3357,16 @@ GeckoDriver.prototype.setPermission = async function (cmd) {
const { descriptor, state, oneRealm = false } = cmd.parameters;
const browsingContext = lazy.assert.open(this.getBrowsingContext());
// XXX: We currently depend on camera/microphone tests throwing UnsupportedOperationError,
// the fix is ongoing in bug 1609427.
if (["camera", "microphone"].includes(descriptor.name)) {
throw new lazy.error.UnsupportedOperationError(
"setPermission: camera and microphone permissions are currently unsupported"
);
}
lazy.permissions.validatePermission(descriptor.name);
// XXX: Allowing this permission causes timing related Android crash, see also bug 1878741
// Bug 1878741: Allowing this permission causes timing related Android crash.
if (descriptor.name === "notifications") {
if (Services.prefs.getBoolPref("notification.prompt.testing", false)) {
// Okay, do nothing. The notifications module will work without permission.
return;
}
throw new lazy.error.UnsupportedOperationError(
"setPermission: expected notification.prompt.testing to be set"
`Setting "descriptor.name" "notifications" expected "notification.prompt.testing" preference to be set`
);
}
@ -3389,7 +3383,26 @@ GeckoDriver.prototype.setPermission = async function (cmd) {
lazy.assert.boolean(oneRealm);
lazy.permissions.set(params.type, params.state, oneRealm, browsingContext);
if (!lazy.MarionettePrefs.setPermissionEnabled) {
throw new lazy.error.UnsupportedOperationError(
"'Set Permission' is not available"
);
}
let origin = browsingContext.currentURI.prePath;
// storage-access is a special case.
if (descriptor.name === "storage-access") {
origin = browsingContext.top.currentURI.prePath;
params = {
type: lazy.permissions.getStorageAccessPermissionsType(
browsingContext.currentWindowGlobal.documentURI
),
};
}
lazy.permissions.set(params, state, origin);
};
/**

View file

@ -6,71 +6,48 @@ const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
MarionettePrefs: "chrome://remote/content/marionette/prefs.sys.mjs",
});
/** @namespace */
export const permissions = {};
function mapToInternalPermissionParameters(browsingContext, permissionType) {
const currentURI = browsingContext.currentWindowGlobal.documentURI;
// storage-access is quite special...
if (permissionType === "storage-access") {
const thirdPartyPrincipalSite = Services.eTLD.getSite(currentURI);
const topLevelURI = browsingContext.top.currentWindowGlobal.documentURI;
const topLevelPrincipal =
Services.scriptSecurityManager.createContentPrincipal(topLevelURI, {});
return {
name: "3rdPartyFrameStorage^" + thirdPartyPrincipalSite,
principal: topLevelPrincipal,
};
}
const currentPrincipal =
Services.scriptSecurityManager.createContentPrincipal(currentURI, {});
return {
name: permissionType,
principal: currentPrincipal,
};
}
/**
* Get a permission type for the "storage-access" permission.
*
* @param {nsIURI} uri
* The URI to use for building the permission type.
*
* @returns {string} permissionType
* The permission type for the "storage-access" permission.
*/
permissions.getStorageAccessPermissionsType = function (uri) {
const thirdPartyPrincipalSite = Services.eTLD.getSite(uri);
return "3rdPartyFrameStorage^" + thirdPartyPrincipalSite;
};
/**
* Set a permission's state.
* Note: Currently just a shim to support testdriver's set_permission.
* Set a permission given a permission descriptor, a permission state,
* an origin.
*
* @param {object} permissionType
* The Gecko internal permission type
* @param {PermissionDescriptor} descriptor
* The descriptor of the permission which will be updated.
* @param {string} state
* State of the permission. It can be `granted`, `denied` or `prompt`.
* @param {boolean} oneRealm
* Currently ignored
* @param {browsingContext=} browsingContext
* Current browsing context object
* @param {string} origin
* The origin which is used as a target for permission update.
*
* @throws {UnsupportedOperationError}
* If `marionette.setpermission.enabled` is not set or
* an unsupported permission is used.
* If <var>state</var> has unsupported value.
*/
permissions.set = function (permissionType, state, oneRealm, browsingContext) {
if (!lazy.MarionettePrefs.setPermissionEnabled) {
throw new lazy.error.UnsupportedOperationError(
"'Set Permission' is not available"
);
}
const { name, principal } = mapToInternalPermissionParameters(
browsingContext,
permissionType
);
permissions.set = function (descriptor, state, origin) {
const principal =
Services.scriptSecurityManager.createContentPrincipalFromOrigin(origin);
switch (state) {
case "granted": {
Services.perms.addFromPrincipal(
principal,
name,
descriptor.type,
Services.perms.ALLOW_ACTION
);
return;
@ -78,13 +55,13 @@ permissions.set = function (permissionType, state, oneRealm, browsingContext) {
case "denied": {
Services.perms.addFromPrincipal(
principal,
name,
descriptor.type,
Services.perms.DENY_ACTION
);
return;
}
case "prompt": {
Services.perms.removeFromPrincipal(principal, name);
Services.perms.removeFromPrincipal(principal, descriptor.type);
return;
}
default:
@ -93,3 +70,21 @@ permissions.set = function (permissionType, state, oneRealm, browsingContext) {
);
}
};
/**
* Validate the permission.
*
* @param {string} permissionName
* The name of the permission which will be validated.
*
* @throws {UnsupportedOperationError}
* If <var>permissionName</var> is not supported.
*/
permissions.validatePermission = function (permissionName) {
// Bug 1609427: PermissionDescriptor for "camera" and "microphone" are not yet implemented.
if (["camera", "microphone"].includes(permissionName)) {
throw new lazy.error.UnsupportedOperationError(
`"descriptor.name" "${permissionName}" is currently unsupported`
);
}
};

View file

@ -1028,6 +1028,13 @@
"expectations": ["FAIL"],
"comment": "TODO: add a comment explaining why this expectation is required (include links to issues)"
},
{
"testIdPattern": "[browsercontext.spec] BrowserContext BrowserContext.overridePermissions should deny permission when not listed",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["firefox", "webDriverBiDi"],
"expectations": ["FAIL"],
"comment": "https://bugzilla.mozilla.org/show_bug.cgi?id=1894217"
},
{
"testIdPattern": "[browsercontext.spec] BrowserContext BrowserContext.overridePermissions should fail when bad permission is given",
"platforms": ["darwin", "linux", "win32"],
@ -1041,6 +1048,13 @@
"expectations": ["FAIL"],
"comment": "TODO: add a comment explaining why this expectation is required (include links to issues)"
},
{
"testIdPattern": "[browsercontext.spec] BrowserContext BrowserContext.overridePermissions should grant permission when listed",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["firefox", "webDriverBiDi"],
"expectations": ["FAIL"],
"comment": "https://bugzilla.mozilla.org/show_bug.cgi?id=1894217"
},
{
"testIdPattern": "[browsercontext.spec] BrowserContext BrowserContext.overridePermissions should grant persistent-storage",
"platforms": ["darwin", "linux", "win32"],
@ -1048,6 +1062,13 @@
"expectations": ["FAIL"],
"comment": "TODO: add a comment explaining why this expectation is required (include links to issues)"
},
{
"testIdPattern": "[browsercontext.spec] BrowserContext BrowserContext.overridePermissions should grant persistent-storage",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["firefox", "webDriverBiDi"],
"expectations": ["FAIL"],
"comment": "https://bugzilla.mozilla.org/show_bug.cgi?id=1894217"
},
{
"testIdPattern": "[browsercontext.spec] BrowserContext BrowserContext.overridePermissions should isolate permissions between browser contexts",
"platforms": ["darwin", "linux", "win32"],
@ -1055,6 +1076,13 @@
"expectations": ["FAIL"],
"comment": "TODO: add a comment explaining why this expectation is required (include links to issues)"
},
{
"testIdPattern": "[browsercontext.spec] BrowserContext BrowserContext.overridePermissions should isolate permissions between browser contexts",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["firefox", "webDriverBiDi"],
"expectations": ["FAIL"],
"comment": "https://bugzilla.mozilla.org/show_bug.cgi?id=1894217"
},
{
"testIdPattern": "[browsercontext.spec] BrowserContext BrowserContext.overridePermissions should reset permissions",
"platforms": ["darwin", "linux", "win32"],
@ -1062,12 +1090,26 @@
"expectations": ["FAIL"],
"comment": "TODO: add a comment explaining why this expectation is required (include links to issues)"
},
{
"testIdPattern": "[browsercontext.spec] BrowserContext BrowserContext.overridePermissions should reset permissions",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["firefox", "webDriverBiDi"],
"expectations": ["FAIL"],
"comment": "https://bugzilla.mozilla.org/show_bug.cgi?id=1894217"
},
{
"testIdPattern": "[browsercontext.spec] BrowserContext BrowserContext.overridePermissions should trigger permission onchange",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["cdp", "firefox"],
"expectations": ["FAIL"],
"comment": "TODO: add a comment explaining why this expectation is required (include links to issues)"
},
{
"testIdPattern": "[browsercontext.spec] BrowserContext BrowserContext.overridePermissions should trigger permission onchange",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["cdp", "firefox"],
"parameters": ["firefox", "webDriverBiDi"],
"expectations": ["FAIL"],
"comment": "TODO: add a comment explaining why this expectation is required (include links to issues)"
"comment": "https://bugzilla.mozilla.org/show_bug.cgi?id=1894217"
},
{
"testIdPattern": "[browsercontext.spec] BrowserContext should fire target events",

View file

@ -21,6 +21,7 @@ remote.jar:
content/webdriver-bidi/modules/root/input.sys.mjs (modules/root/input.sys.mjs)
content/webdriver-bidi/modules/root/log.sys.mjs (modules/root/log.sys.mjs)
content/webdriver-bidi/modules/root/network.sys.mjs (modules/root/network.sys.mjs)
content/webdriver-bidi/modules/root/permissions.sys.mjs (modules/root/permissions.sys.mjs)
content/webdriver-bidi/modules/root/script.sys.mjs (modules/root/script.sys.mjs)
content/webdriver-bidi/modules/root/session.sys.mjs (modules/root/session.sys.mjs)
content/webdriver-bidi/modules/root/storage.sys.mjs (modules/root/storage.sys.mjs)

View file

@ -18,6 +18,8 @@ ChromeUtils.defineESModuleGetters(modules.root, {
log: "chrome://remote/content/webdriver-bidi/modules/root/log.sys.mjs",
network:
"chrome://remote/content/webdriver-bidi/modules/root/network.sys.mjs",
permissions:
"chrome://remote/content/webdriver-bidi/modules/root/permissions.sys.mjs",
script: "chrome://remote/content/webdriver-bidi/modules/root/script.sys.mjs",
session:
"chrome://remote/content/webdriver-bidi/modules/root/session.sys.mjs",

View file

@ -0,0 +1,140 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import { Module } from "chrome://remote/content/shared/messagehandler/Module.sys.mjs";
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
assert: "chrome://remote/content/shared/webdriver/Assert.sys.mjs",
error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
permissions: "chrome://remote/content/shared/Permissions.sys.mjs",
});
export const PermissionState = {
denied: "denied",
granted: "granted",
prompt: "prompt",
};
class PermissionsModule extends Module {
constructor(messageHandler) {
super(messageHandler);
}
destroy() {}
/**
* An object that holds the information about permission descriptor
* for Webdriver BiDi permissions.setPermission command.
*
* @typedef PermissionDescriptor
*
* @property {string} name
* The name of the permission.
*/
/**
* Set to a given permission descriptor a given state on a provided origin.
*
* @param {object=} options
* @param {PermissionDescriptor} options.descriptor
* The descriptor of the permission which will be updated.
* @param {PermissionState} options.state
* The state which will be set to the permission.
* @param {string} options.origin
* The origin which is used as a target for permission update.
* @param {string=} options.userContext [unsupported]
* The id of the user context which should be used as a target
* for permission update.
*
* @throws {InvalidArgumentError}
* Raised if an argument is of an invalid type or value.
* @throws {UnsupportedOperationError}
* Raised when unsupported permissions are set or <var>userContext</var>
* argument is used.
*/
async setPermission(options = {}) {
const {
descriptor,
state,
origin,
userContext: userContextId = null,
} = options;
lazy.assert.object(
descriptor,
`Expected "descriptor" to be an object, got ${descriptor}`
);
const permissionName = descriptor.name;
lazy.assert.string(
permissionName,
`Expected "descriptor.name" to be a string, got ${permissionName}`
);
lazy.permissions.validatePermission(permissionName);
// Bug 1878741: Allowing this permission causes timing related Android crash.
if (descriptor.name === "notifications") {
if (Services.prefs.getBoolPref("notification.prompt.testing", false)) {
// Okay, do nothing. The notifications module will work without permission.
return;
}
throw new lazy.error.UnsupportedOperationError(
`Setting "descriptor.name" "notifications" expected "notification.prompt.testing" preference to be set`
);
}
if (permissionName === "storage-access") {
// TODO: Bug 1895457. Add support for "storage-access" permission.
throw new lazy.error.UnsupportedOperationError(
`"descriptor.name" "${permissionName}" is currently unsupported`
);
}
const permissionStateTypes = Object.keys(PermissionState);
lazy.assert.that(
state => permissionStateTypes.includes(state),
`Expected "state" to be one of ${permissionStateTypes}, got ${state}`
)(state);
lazy.assert.string(
origin,
`Expected "origin" to be a string, got ${origin}`
);
lazy.assert.that(
origin => URL.canParse(origin),
`Expected "origin" to be a valid URL, got ${origin}`
)(origin);
if (userContextId !== null) {
lazy.assert.string(
userContextId,
`Expected "userContext" to be a string, got ${userContextId}`
);
// TODO: Bug 1894217. Add support for "userContext" argument.
throw new lazy.error.UnsupportedOperationError(
`"userContext" is not supported yet`
);
}
const activeWindow = Services.wm.getMostRecentBrowserWindow();
let typedDescriptor;
try {
typedDescriptor = activeWindow.navigator.permissions.parseSetParameters({
descriptor,
state,
});
} catch (err) {
throw new lazy.error.InvalidArgumentError(
`The conversion of "descriptor" was not successful: ${err.message}`
);
}
lazy.permissions.set(typedDescriptor, state, origin);
}
}
export const permissions = PermissionsModule;

View file

@ -48,3 +48,7 @@ class TestSetPermission(MarionetteTestCase):
self.assertEqual(
self.query_permission({"name": "midi", "sysex": True}), "prompt"
)
def test_storage_access(self):
self.marionette.set_permission({"name": "storage-access"}, "granted")
self.assertEqual(self.query_permission({"name": "storage-access"}), "granted")

View file

@ -1,68 +0,0 @@
[invalid.py]
expected:
if (processor == "x86") and not debug: [OK, TIMEOUT]
[test_params_descriptor_invalid_type[False\]]
expected: FAIL
[test_params_descriptor_invalid_type[SOME_STRING\]]
expected: FAIL
[test_params_descriptor_invalid_type[42\]]
expected: FAIL
[test_params_descriptor_invalid_type[descriptor3\]]
expected: FAIL
[test_params_descriptor_invalid_type[descriptor4\]]
expected: FAIL
[test_params_descriptor_invalid_type[descriptor5\]]
expected: FAIL
[test_params_descriptor_invalid_type[None\]]
expected: FAIL
[test_params_descriptor_invalid_type[descriptor7\]]
expected: FAIL
[test_params_descriptor_invalid_value[descriptor0\]]
expected: FAIL
[test_params_state_invalid_type[False\]]
expected: FAIL
[test_params_state_invalid_type[42\]]
expected: FAIL
[test_params_state_invalid_type[state2\]]
expected: FAIL
[test_params_state_invalid_type[state3\]]
expected: FAIL
[test_params_state_invalid_type[None\]]
expected: FAIL
[test_params_state_invalid_type[state5\]]
expected: FAIL
[test_params_state_invalid_value[Granted\]]
expected: FAIL
[test_params_origin_invalid_type[False\]]
expected: FAIL
[test_params_origin_invalid_type[42\]]
expected: FAIL
[test_params_origin_invalid_type[None\]]
expected: FAIL
[test_params_state_invalid_value[UNKNOWN\]]
expected: FAIL
[test_params_origin_invalid_type[user_context2\]]
expected: FAIL
[test_params_origin_invalid_type[user_context3\]]
expected: FAIL

View file

@ -1,13 +1,4 @@
[set_permission.py]
[test_set_permission]
expected: FAIL
[test_set_permission_insecure_context]
expected: FAIL
[test_set_permission_new_context]
expected: FAIL
[test_set_permission_origin_unknown[UNKNOWN\]]
expected: FAIL
@ -16,3 +7,6 @@
[test_set_permission_user_context]
expected: FAIL
bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1894217
disabled:
if os == "android": bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1877953