1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 import { GeckoViewActorChild } from "resource://gre/modules/GeckoViewActorChild.sys.mjs";
9 ChromeUtils.defineESModuleGetters(lazy, {
10 E10SUtils: "resource://gre/modules/E10SUtils.sys.mjs",
13 const PERM_ACCESS_FINE_LOCATION = "android.permission.ACCESS_FINE_LOCATION";
15 const MAPPED_TO_EXTENSION_PERMISSIONS = [
17 // TODO(Bug 1336194): support geolocation manifest permission
18 // (see https://bugzilla.mozilla.org/show_bug.cgi?id=1336194#c17)l
21 export class GeckoViewPermissionChild extends GeckoViewActorChild {
22 getMediaPermission(aPermission) {
23 return this.eventDispatcher.sendRequestForResult({
24 type: "GeckoView:MediaPermission",
29 addCameraPermission() {
30 return this.sendQuery("AddCameraPermission");
33 getAppPermissions(aPermissions) {
34 return this.sendQuery("GetAppPermissions", aPermissions);
37 mediaRecordingStatusChanged(aDevices) {
38 return this.eventDispatcher.sendRequest({
39 type: "GeckoView:MediaRecordingStatusChanged",
44 // Some WebAPI permissions can be requested and granted to extensions through the
45 // the extension manifest.json, which the user have been already prompted for
46 // (e.g. at install time for the one listed in the manifest.json permissions property,
47 // or at runtime through the optional_permissions property and the permissions.request
48 // WebExtensions API method).
50 // WebAPI permission that are expected to be mapped to extensions permissions are listed
51 // in the MAPPED_TO_EXTENSION_PERMISSIONS array.
53 // @param {nsIContentPermissionType} perm
54 // The WebAPI permission being requested
55 // @param {nsIContentPermissionRequest} aRequest
56 // The nsIContentPermissionRequest as received by the promptPermission method.
58 // @returns {null | { allow: boolean, permission: Object }
59 // Returns null if the request was not handled and should continue with the
60 // regular permission prompting flow, otherwise it returns an object to
61 // allow or disallow the permission request right away.
62 checkIfGrantedByExtensionPermissions(perm, aRequest) {
63 if (!aRequest.principal.addonPolicy) {
64 // Not an extension, continue with the regular permission prompting flow.
68 // Return earlier and continue with the regular permission prompting flow if the
69 // the permission is no one that can be requested from the extension manifest file.
70 if (!MAPPED_TO_EXTENSION_PERMISSIONS.includes(perm.type)) {
74 // Disallow if the extension is not active anymore.
75 if (!aRequest.principal.addonPolicy.active) {
76 return { allow: false };
79 // Check if the permission have been already granted to the extension, if it is allow it right away.
81 Services.perms.testPermissionFromPrincipal(
84 ) === Services.perms.ALLOW_ACTION;
88 permission: { [perm.type]: Services.perms.ALLOW_ACTION },
92 // continue with the regular permission prompting flow otherwise.
96 async promptPermission(aRequest) {
97 // Only allow exactly one permission request here.
98 const types = aRequest.types.QueryInterface(Ci.nsIArray);
99 if (types.length !== 1) {
100 return { allow: false };
103 const perm = types.queryElementAt(0, Ci.nsIContentPermissionType);
105 // Check if the request principal is an extension principal and if the permission requested
106 // should be already granted based on the extension permissions (or disallowed right away
107 // because the extension is not enabled anymore.
108 const extensionResult = this.checkIfGrantedByExtensionPermissions(
112 if (extensionResult) {
113 return extensionResult;
117 perm.type === "desktop-notification" &&
118 !aRequest.hasValidTransientUserGestureActivation &&
119 Services.prefs.getBoolPref(
120 "dom.webnotifications.requireuserinteraction",
124 // We need user interaction and don't have it.
125 return { allow: false };
129 perm.type === "storage-access"
131 : aRequest.topLevelPrincipal;
135 allowOrDeny = await this.eventDispatcher.sendRequestForResult({
136 type: "GeckoView:ContentPermission",
137 uri: principal.URI.displaySpec,
138 thirdPartyOrigin: aRequest.principal.origin,
139 principal: lazy.E10SUtils.serializePrincipal(principal),
141 value: perm.capability,
142 contextId: principal.originAttributes.geckoViewSessionContextId ?? null,
143 privateMode: principal.privateBrowsingId != 0,
146 if (allowOrDeny === Services.perms.ALLOW_ACTION) {
147 // Ask for app permission after asking for content permission.
148 if (perm.type === "geolocation") {
149 const granted = await this.getAppPermissions([
150 PERM_ACCESS_FINE_LOCATION,
152 allowOrDeny = granted
153 ? Services.perms.ALLOW_ACTION
154 : Services.perms.DENY_ACTION;
158 console.error("Permission error:", error);
159 allowOrDeny = Services.perms.DENY_ACTION;
162 // Manually release the target request here to facilitate garbage collection.
163 aRequest = undefined;
165 const allow = allowOrDeny === Services.perms.ALLOW_ACTION;
167 // The storage access code adds itself to the perm manager; no need for us to do it.
168 if (perm.type === "storage-access") {
170 return { allow, permission: { "storage-access": "allow" } };
175 Services.perms.addFromPrincipal(
179 Services.perms.EXPIRE_NEVER
186 const { debug, warn } = GeckoViewPermissionChild.initLogging(
187 "GeckoViewPermissionChild"