summaryrefslogtreecommitdiffstats
path: root/mobile/android/actors/GeckoViewClipboardPermissionChild.jsm
blob: 2a5ffd5eaa692b72c5060333cd45ef4d83ceb21e (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
/* 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/. */
"use strict";

const { GeckoViewActorChild } = ChromeUtils.importESModule(
  "resource://gre/modules/GeckoViewActorChild.sys.mjs"
);

const EXPORTED_SYMBOLS = ["GeckoViewClipboardPermissionChild"];

class GeckoViewClipboardPermissionChild extends GeckoViewActorChild {
  constructor() {
    super();
    this._pendingPromise = null;
  }

  async promptPermissionForClipboardRead() {
    const uri = this.contentWindow.location.href;

    const { x, y } = await this.sendQuery(
      "ClipboardReadTextPaste:GetLastPointerLocation"
    );

    const promise = this.eventDispatcher.sendRequestForResult({
      type: "GeckoView:ClipboardPermissionRequest",
      uri,
      screenPoint: {
        x,
        y,
      },
    });

    this._pendingPromise = promise;

    try {
      const allowOrDeny = await promise;
      if (this._pendingPromise !== promise) {
        // Current pending promise is newer. So it means that this promise
        // is already resolved or rejected. Do nothing.
        return;
      }
      this.contentWindow.navigator.clipboard.onUserReactedToPasteMenuPopup(
        allowOrDeny
      );
      this._pendingPromise = null;
    } catch (error) {
      debug`Permission error: ${error}`;

      if (this._pendingPromise !== promise) {
        // Current pending promise is newer. So it means that this promise
        // is already resolved or rejected. Do nothing.
        return;
      }

      this.contentWindow.navigator.clipboard.onUserReactedToPasteMenuPopup(
        false
      );
      this._pendingPromise = null;
    }
  }

  handleEvent(aEvent) {
    debug`handleEvent: ${aEvent.type}`;

    switch (aEvent.type) {
      case "MozClipboardReadPaste":
        if (aEvent.isTrusted) {
          this.promptPermissionForClipboardRead();
        }
        break;

      // page hide or deactivate cancel clipboard permission.
      case "pagehide":
      // fallthrough for the next three events.
      case "deactivate":
      case "mousedown":
      case "mozvisualscroll":
        // Gecko desktop uses XUL popup to show clipboard permission prompt.
        // So it will be closed automatically by scroll and other user
        // activation. So GeckoView has to close permission prompt by some user
        // activations, too.

        this.eventDispatcher.sendRequest({
          type: "GeckoView:DismissClipboardPermissionRequest",
        });
        if (this._pendingPromise) {
          this.contentWindow.navigator.clipboard.onUserReactedToPasteMenuPopup(
            false
          );
          this._pendingPromise = null;
        }
        break;
    }
  }
}

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