GeckoViewPermissionChild.sys.mjs (6255B)
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/. */ 4 5 import { GeckoViewActorChild } from "resource://gre/modules/GeckoViewActorChild.sys.mjs"; 6 7 const lazy = {}; 8 9 ChromeUtils.defineESModuleGetters(lazy, { 10 E10SUtils: "resource://gre/modules/E10SUtils.sys.mjs", 11 }); 12 13 const PERM_ACCESS_FINE_LOCATION = "android.permission.ACCESS_FINE_LOCATION"; 14 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 19 ]; 20 21 export class GeckoViewPermissionChild extends GeckoViewActorChild { 22 getMediaPermission(aPermission) { 23 return this.sendQuery("GeckoView:MediaPermission", { 24 ...aPermission, 25 }); 26 } 27 28 addCameraPermission() { 29 return this.sendQuery("AddCameraPermission"); 30 } 31 32 getAppPermissions(aPermissions) { 33 return this.sendQuery("GetAppPermissions", aPermissions); 34 } 35 36 mediaRecordingStatusChanged(aDevices) { 37 return this.sendAsyncMessage("GeckoView:MediaRecordingStatusChanged", { 38 devices: aDevices, 39 }); 40 } 41 42 // Some WebAPI permissions can be requested and granted to extensions through the 43 // the extension manifest.json, which the user have been already prompted for 44 // (e.g. at install time for the one listed in the manifest.json permissions property, 45 // or at runtime through the optional_permissions property and the permissions.request 46 // WebExtensions API method). 47 // 48 // WebAPI permission that are expected to be mapped to extensions permissions are listed 49 // in the MAPPED_TO_EXTENSION_PERMISSIONS array. 50 // 51 // @param {nsIContentPermissionType} perm 52 // The WebAPI permission being requested 53 // @param {nsIContentPermissionRequest} aRequest 54 // The nsIContentPermissionRequest as received by the promptPermission method. 55 // 56 // @returns {null | { allow: boolean, permission: Object } 57 // Returns null if the request was not handled and should continue with the 58 // regular permission prompting flow, otherwise it returns an object to 59 // allow or disallow the permission request right away. 60 checkIfGrantedByExtensionPermissions(perm, aRequest) { 61 if (!aRequest.principal.addonPolicy) { 62 // Not an extension, continue with the regular permission prompting flow. 63 return null; 64 } 65 66 // Return earlier and continue with the regular permission prompting flow if the 67 // the permission is no one that can be requested from the extension manifest file. 68 if (!MAPPED_TO_EXTENSION_PERMISSIONS.includes(perm.type)) { 69 return null; 70 } 71 72 // Disallow if the extension is not active anymore. 73 if (!aRequest.principal.addonPolicy.active) { 74 return { allow: false }; 75 } 76 77 // Check if the permission have been already granted to the extension, if it is allow it right away. 78 const isGranted = 79 Services.perms.testPermissionFromPrincipal( 80 aRequest.principal, 81 perm.type 82 ) === Services.perms.ALLOW_ACTION; 83 if (isGranted) { 84 return { 85 allow: true, 86 permission: { [perm.type]: Services.perms.ALLOW_ACTION }, 87 }; 88 } 89 90 // continue with the regular permission prompting flow otherwise. 91 return null; 92 } 93 94 async promptPermission(aRequest) { 95 // Only allow exactly one permission request here. 96 const types = aRequest.types.QueryInterface(Ci.nsIArray); 97 if (types.length !== 1) { 98 return { allow: false }; 99 } 100 101 const perm = types.queryElementAt(0, Ci.nsIContentPermissionType); 102 103 // Check if the request principal is an extension principal and if the permission requested 104 // should be already granted based on the extension permissions (or disallowed right away 105 // because the extension is not enabled anymore. 106 const extensionResult = this.checkIfGrantedByExtensionPermissions( 107 perm, 108 aRequest 109 ); 110 if (extensionResult) { 111 return extensionResult; 112 } 113 114 if ( 115 perm.type === "desktop-notification" && 116 !aRequest.hasValidTransientUserGestureActivation && 117 Services.prefs.getBoolPref( 118 "dom.webnotifications.requireuserinteraction", 119 true 120 ) 121 ) { 122 // We need user interaction and don't have it. 123 return { allow: false }; 124 } 125 126 const principal = 127 perm.type === "storage-access" 128 ? aRequest.principal 129 : aRequest.topLevelPrincipal; 130 131 let allowOrDeny; 132 try { 133 allowOrDeny = await this.sendQuery("GeckoView:ContentPermission", { 134 uri: principal.URI.displaySpec, 135 thirdPartyOrigin: aRequest.principal.origin, 136 principal: lazy.E10SUtils.serializePrincipal(principal), 137 perm: perm.type, 138 value: perm.capability, 139 contextId: principal.originAttributes.geckoViewSessionContextId ?? null, 140 privateMode: principal.privateBrowsingId != 0, 141 }); 142 143 if (allowOrDeny === Services.perms.ALLOW_ACTION) { 144 // Ask for app permission after asking for content permission. 145 if (perm.type === "geolocation") { 146 const granted = await this.getAppPermissions([ 147 PERM_ACCESS_FINE_LOCATION, 148 ]); 149 allowOrDeny = granted 150 ? Services.perms.ALLOW_ACTION 151 : Services.perms.DENY_ACTION; 152 } 153 } 154 } catch (error) { 155 console.error("Permission error:", error); 156 allowOrDeny = Services.perms.DENY_ACTION; 157 } 158 159 // Manually release the target request here to facilitate garbage collection. 160 aRequest = undefined; 161 162 const allow = allowOrDeny === Services.perms.ALLOW_ACTION; 163 164 // The storage access code adds itself to the perm manager; no need for us to do it. 165 if (perm.type === "storage-access") { 166 if (allow) { 167 return { allow, permission: { "storage-access": "allow" } }; 168 } 169 return { allow }; 170 } 171 172 Services.perms.addFromPrincipal( 173 principal, 174 perm.type, 175 allowOrDeny, 176 Services.perms.EXPIRE_NEVER 177 ); 178 179 return { allow }; 180 } 181 } 182 183 const { debug, warn } = GeckoViewPermissionChild.initLogging( 184 "GeckoViewPermissionChild" 185 );