summaryrefslogtreecommitdiffstats
path: root/browser/actors/ClickHandlerParent.sys.mjs
blob: 4078c6404fcc5bff77f953b4e93b4e450d244ab6 (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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
/* -*- mode: js; indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* 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/. */

const lazy = {};

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

let gContentClickListeners = new Set();

// Fill in fields which are not sent by the content process for the click event
// based on known data in the parent process.
function fillInClickEvent(actor, data) {
  const wgp = actor.manager;
  data.frameID = lazy.WebNavigationFrames.getFrameId(wgp.browsingContext);
  data.triggeringPrincipal = wgp.documentPrincipal;
  data.originPrincipal = wgp.documentPrincipal;
  data.originStoragePrincipal = wgp.documentStoragePrincipal;
  data.originAttributes = wgp.documentPrincipal?.originAttributes ?? {};
  data.isContentWindowPrivate = wgp.browsingContext.usePrivateBrowsing;
}

export class MiddleMousePasteHandlerParent extends JSWindowActorParent {
  receiveMessage(message) {
    if (message.name == "MiddleClickPaste") {
      // This is heavily based on contentAreaClick from browser.js (Bug 903016)
      // The data is set up in a way to look like an Event.
      let browser = this.manager.browsingContext.top.embedderElement;
      if (!browser) {
        // Can be null if the tab disappeared by the time we got the message.
        // Just bail.
        return;
      }
      fillInClickEvent(this, message.data);
      browser.ownerGlobal.middleMousePaste(message.data);
    }
  }
}

export class ClickHandlerParent extends JSWindowActorParent {
  static addContentClickListener(listener) {
    gContentClickListeners.add(listener);
  }

  static removeContentClickListener(listener) {
    gContentClickListeners.delete(listener);
  }

  receiveMessage(message) {
    switch (message.name) {
      case "Content:Click":
        fillInClickEvent(this, message.data);
        this.contentAreaClick(message.data);
        this.notifyClickListeners(message.data);
        break;
    }
  }

  /**
   * Handles clicks in the content area.
   *
   * @param data {Object} object that looks like an Event
   * @param browser {Element<browser>}
   */
  contentAreaClick(data) {
    // This is heavily based on contentAreaClick from browser.js (Bug 903016)
    // The data is set up in a way to look like an Event.
    let browser = this.manager.browsingContext.top.embedderElement;
    if (!browser) {
      // Can be null if the tab disappeared by the time we got the message.
      // Just bail.
      return;
    }
    let window = browser.ownerGlobal;

    // If the browser is not in a place where we can open links, bail out.
    // This can happen in osx sheets, dialogs, etc. that are not browser
    // windows.  Specifically the payments UI is in an osx sheet.
    if (window.openLinkIn === undefined) {
      return;
    }

    // Mark the page as a user followed link.  This is done so that history can
    // distinguish automatic embed visits from user activated ones.  For example
    // pages loaded in frames are embed visits and lost with the session, while
    // visits across frames should be preserved.
    try {
      if (!lazy.PrivateBrowsingUtils.isWindowPrivate(window)) {
        lazy.PlacesUIUtils.markPageAsFollowedLink(data.href);
      }
    } catch (ex) {
      /* Skip invalid URIs. */
    }

    // This part is based on handleLinkClick.
    var where = window.whereToOpenLink(data);
    if (where == "current") {
      return;
    }

    // Todo(903022): code for where == save

    let params = {
      charset: browser.characterSet,
      referrerInfo: lazy.E10SUtils.deserializeReferrerInfo(data.referrerInfo),
      isContentWindowPrivate: data.isContentWindowPrivate,
      originPrincipal: data.originPrincipal,
      originStoragePrincipal: data.originStoragePrincipal,
      triggeringPrincipal: data.triggeringPrincipal,
      csp: data.csp ? lazy.E10SUtils.deserializeCSP(data.csp) : null,
      frameID: data.frameID,
      openerBrowser: browser,
      // The child ensures that untrusted events have a valid user activation.
      hasValidUserGestureActivation: true,
      triggeringRemoteType: this.manager.domProcess?.remoteType,
    };

    if (data.globalHistoryOptions) {
      params.globalHistoryOptions = data.globalHistoryOptions;
    } else {
      params.globalHistoryOptions = {
        triggeringSponsoredURL: browser.getAttribute("triggeringSponsoredURL"),
        triggeringSponsoredURLVisitTimeMS: browser.getAttribute(
          "triggeringSponsoredURLVisitTimeMS"
        ),
      };
    }

    // The new tab/window must use the same userContextId.
    if (data.originAttributes.userContextId) {
      params.userContextId = data.originAttributes.userContextId;
    }

    params.allowInheritPrincipal = true;

    window.openLinkIn(data.href, where, params);
  }

  notifyClickListeners(data) {
    for (let listener of gContentClickListeners) {
      try {
        let browser = this.browsingContext.top.embedderElement;

        listener.onContentClick(browser, data);
      } catch (ex) {
        console.error(ex);
      }
    }
  }
}