tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 );