Bug 1891710: part 2) Enable <Element-outerHTML.html> WPT for Trusted Types. r=smaug
[gecko.git] / mobile / android / actors / GeckoViewPermissionChild.sys.mjs
blobbbe9457cc5eeb0c21ba2320c78acf349aac00a8a
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";
7 const lazy = {};
9 ChromeUtils.defineESModuleGetters(lazy, {
10   E10SUtils: "resource://gre/modules/E10SUtils.sys.mjs",
11 });
13 const PERM_ACCESS_FINE_LOCATION = "android.permission.ACCESS_FINE_LOCATION";
15 const MAPPED_TO_EXTENSION_PERMISSIONS = [
16   "persistent-storage",
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",
25       ...aPermission,
26     });
27   }
29   addCameraPermission() {
30     return this.sendQuery("AddCameraPermission");
31   }
33   getAppPermissions(aPermissions) {
34     return this.sendQuery("GetAppPermissions", aPermissions);
35   }
37   mediaRecordingStatusChanged(aDevices) {
38     return this.eventDispatcher.sendRequest({
39       type: "GeckoView:MediaRecordingStatusChanged",
40       devices: aDevices,
41     });
42   }
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).
49   //
50   // WebAPI permission that are expected to be mapped to extensions permissions are listed
51   // in the MAPPED_TO_EXTENSION_PERMISSIONS array.
52   //
53   // @param {nsIContentPermissionType} perm
54   //        The WebAPI permission being requested
55   // @param {nsIContentPermissionRequest} aRequest
56   //        The nsIContentPermissionRequest as received by the promptPermission method.
57   //
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.
65       return null;
66     }
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)) {
71       return null;
72     }
74     // Disallow if the extension is not active anymore.
75     if (!aRequest.principal.addonPolicy.active) {
76       return { allow: false };
77     }
79     // Check if the permission have been already granted to the extension, if it is allow it right away.
80     const isGranted =
81       Services.perms.testPermissionFromPrincipal(
82         aRequest.principal,
83         perm.type
84       ) === Services.perms.ALLOW_ACTION;
85     if (isGranted) {
86       return {
87         allow: true,
88         permission: { [perm.type]: Services.perms.ALLOW_ACTION },
89       };
90     }
92     // continue with the regular permission prompting flow otherwise.
93     return null;
94   }
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 };
101     }
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(
109       perm,
110       aRequest
111     );
112     if (extensionResult) {
113       return extensionResult;
114     }
116     if (
117       perm.type === "desktop-notification" &&
118       !aRequest.hasValidTransientUserGestureActivation &&
119       Services.prefs.getBoolPref(
120         "dom.webnotifications.requireuserinteraction",
121         true
122       )
123     ) {
124       // We need user interaction and don't have it.
125       return { allow: false };
126     }
128     const principal =
129       perm.type === "storage-access"
130         ? aRequest.principal
131         : aRequest.topLevelPrincipal;
133     let allowOrDeny;
134     try {
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),
140         perm: perm.type,
141         value: perm.capability,
142         contextId: principal.originAttributes.geckoViewSessionContextId ?? null,
143         privateMode: principal.privateBrowsingId != 0,
144       });
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,
151           ]);
152           allowOrDeny = granted
153             ? Services.perms.ALLOW_ACTION
154             : Services.perms.DENY_ACTION;
155         }
156       }
157     } catch (error) {
158       console.error("Permission error:", error);
159       allowOrDeny = Services.perms.DENY_ACTION;
160     }
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") {
169       if (allow) {
170         return { allow, permission: { "storage-access": "allow" } };
171       }
172       return { allow };
173     }
175     Services.perms.addFromPrincipal(
176       principal,
177       perm.type,
178       allowOrDeny,
179       Services.perms.EXPIRE_NEVER
180     );
182     return { allow };
183   }
186 const { debug, warn } = GeckoViewPermissionChild.initLogging(
187   "GeckoViewPermissionChild"