summaryrefslogtreecommitdiffstats
path: root/mobile/android/actors/GeckoViewPermissionChild.sys.mjs
blob: f0224de2e9ed0c4690cc241ff4545511df154343 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
/* 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 { GeckoViewActorChild } from "resource://gre/modules/GeckoViewActorChild.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  E10SUtils: "resource://gre/modules/E10SUtils.sys.mjs",
});

const PERM_ACCESS_FINE_LOCATION = "android.permission.ACCESS_FINE_LOCATION";

export class GeckoViewPermissionChild extends GeckoViewActorChild {
  getMediaPermission(aPermission) {
    return this.eventDispatcher.sendRequestForResult({
      type: "GeckoView:MediaPermission",
      ...aPermission,
    });
  }

  addCameraPermission() {
    return this.sendQuery("AddCameraPermission");
  }

  getAppPermissions(aPermissions) {
    return this.sendQuery("GetAppPermissions", aPermissions);
  }

  mediaRecordingStatusChanged(aDevices) {
    return this.eventDispatcher.sendRequest({
      type: "GeckoView:MediaRecordingStatusChanged",
      devices: aDevices,
    });
  }

  async promptPermission(aRequest) {
    // Only allow exactly one permission request here.
    const types = aRequest.types.QueryInterface(Ci.nsIArray);
    if (types.length !== 1) {
      return { allow: false };
    }

    const perm = types.queryElementAt(0, Ci.nsIContentPermissionType);
    if (
      perm.type === "desktop-notification" &&
      !aRequest.hasValidTransientUserGestureActivation &&
      Services.prefs.getBoolPref(
        "dom.webnotifications.requireuserinteraction",
        true
      )
    ) {
      // We need user interaction and don't have it.
      return { allow: false };
    }

    const principal =
      perm.type === "storage-access"
        ? aRequest.principal
        : aRequest.topLevelPrincipal;

    let allowOrDeny;
    try {
      allowOrDeny = await this.eventDispatcher.sendRequestForResult({
        type: "GeckoView:ContentPermission",
        uri: principal.URI.displaySpec,
        thirdPartyOrigin: aRequest.principal.origin,
        principal: lazy.E10SUtils.serializePrincipal(principal),
        perm: perm.type,
        value: perm.capability,
        contextId: principal.originAttributes.geckoViewSessionContextId ?? null,
        privateMode: principal.privateBrowsingId != 0,
      });

      if (allowOrDeny === Services.perms.ALLOW_ACTION) {
        // Ask for app permission after asking for content permission.
        if (perm.type === "geolocation") {
          const granted = await this.getAppPermissions([
            PERM_ACCESS_FINE_LOCATION,
          ]);
          allowOrDeny = granted
            ? Services.perms.ALLOW_ACTION
            : Services.perms.DENY_ACTION;
        }
      }
    } catch (error) {
      console.error("Permission error: " + error);
      allowOrDeny = Services.perms.DENY_ACTION;
    }

    // Manually release the target request here to facilitate garbage collection.
    aRequest = undefined;

    const allow = allowOrDeny === Services.perms.ALLOW_ACTION;

    // The storage access code adds itself to the perm manager; no need for us to do it.
    if (perm.type === "storage-access") {
      if (allow) {
        return { allow, permission: { "storage-access": "allow" } };
      }
      return { allow };
    }

    Services.perms.addFromPrincipal(
      principal,
      perm.type,
      allowOrDeny,
      Services.perms.EXPIRE_NEVER
    );

    return { allow };
  }
}

const { debug, warn } = GeckoViewPermissionChild.initLogging(
  "GeckoViewPermissionChild"
);