gecko-dev/browser/actors/ClickHandlerChild.jsm
Gijs Kruitbosch f7af2c33b1 Bug 1742801 - do not consume the user gesture from ClickHandlerChild if ClickHandlerParent will ignore the click anyway, r=edgar
This commit does a couple of things:
- move whereToOpenLink and getRootEvent implementations into BrowserUtils,
  so they can be used from the child process.
- forward callers in utilityOverlay.js to the BrowserUtils ones
  (bug 1742889 will get rid of the forwarding and update all the callers;
   we might be able to get this and bug 1739929 into beta if risk is low
   enough, and touching a bunch of extra files really doesn't help with
   that)
- move the lazy-load of BrowserUtils from browser.js to utilityOverlay.js
  This is safe because everywhere that loads browser.js also loads
  utilityOverlay.js. It's needed because there are some places that use
  utilityOverlay.js but not browser.js, and so now they need access to
  BrowserUtils.jsm.
- use whereToOpenLink to determine if we should avoid consuming the transient
  user gesture activation in the child click handling code.
- add an automated test based on the testcase in the bug.

When working on this, I initially put the check using whereToOpenLink in
the toplevel of the function, and then when I ran places test to check that
I hadn't broken any places consumers of whereToOpenLink or getRootEvent,
realized that I had broken `browser_markPageAsFollowedLink.js`, because it
relies on "normal" (ie no modifier key, left button) link clicks making it
to ClickHandlerParent.jsm . I filed bug 1742894 about this. I've not tried
to fix that here, instead I've tried to ensure that paths through this
function are as untouched as possible while still fixing bug 1739929 and
bug 1742801.

Differential Revision: https://phabricator.services.mozilla.com/D132102
2021-11-25 22:49:00 +00:00

169 lines
5.2 KiB
JavaScript

/* -*- 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/. */
var EXPORTED_SYMBOLS = ["ClickHandlerChild"];
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
ChromeUtils.defineModuleGetter(
this,
"PrivateBrowsingUtils",
"resource://gre/modules/PrivateBrowsingUtils.jsm"
);
ChromeUtils.defineModuleGetter(
this,
"WebNavigationFrames",
"resource://gre/modules/WebNavigationFrames.jsm"
);
ChromeUtils.defineModuleGetter(
this,
"E10SUtils",
"resource://gre/modules/E10SUtils.jsm"
);
ChromeUtils.defineModuleGetter(
this,
"BrowserUtils",
"resource://gre/modules/BrowserUtils.jsm"
);
class ClickHandlerChild extends JSWindowActorChild {
handleEvent(event) {
if (
event.defaultPrevented ||
event.button == 2 ||
(event.type == "click" && event.button == 1)
) {
return;
}
// Don't do anything on editable things, we shouldn't open links in
// contenteditables, and editor needs to possibly handle middlemouse paste
let composedTarget = event.composedTarget;
if (
composedTarget.isContentEditable ||
(composedTarget.ownerDocument &&
composedTarget.ownerDocument.designMode == "on") ||
ChromeUtils.getClassName(composedTarget) == "HTMLInputElement" ||
ChromeUtils.getClassName(composedTarget) == "HTMLTextAreaElement"
) {
return;
}
let originalTarget = event.originalTarget;
let ownerDoc = originalTarget.ownerDocument;
if (!ownerDoc) {
return;
}
// Handle click events from about pages
if (event.button == 0) {
if (ownerDoc.documentURI.startsWith("about:blocked")) {
return;
}
}
// For untrusted events, require a valid transient user gesture activation.
if (!event.isTrusted && !ownerDoc.hasValidTransientUserGestureActivation) {
return;
}
let [href, node, principal] = BrowserUtils.hrefAndLinkNodeForClickEvent(
event
);
let csp = ownerDoc.csp;
if (csp) {
csp = E10SUtils.serializeCSP(csp);
}
let referrerInfo = Cc["@mozilla.org/referrer-info;1"].createInstance(
Ci.nsIReferrerInfo
);
if (node) {
referrerInfo.initWithElement(node);
} else {
referrerInfo.initWithDocument(ownerDoc);
}
referrerInfo = E10SUtils.serializeReferrerInfo(referrerInfo);
let frameID = WebNavigationFrames.getFrameId(ownerDoc.defaultView);
let json = {
button: event.button,
shiftKey: event.shiftKey,
ctrlKey: event.ctrlKey,
metaKey: event.metaKey,
altKey: event.altKey,
href: null,
title: null,
frameID,
triggeringPrincipal: principal,
csp,
referrerInfo,
originAttributes: principal ? principal.originAttributes : {},
isContentWindowPrivate: PrivateBrowsingUtils.isContentWindowPrivate(
ownerDoc.defaultView
),
};
if (href) {
try {
Services.scriptSecurityManager.checkLoadURIStrWithPrincipal(
principal,
href
);
} catch (e) {
return;
}
if (
!event.isTrusted &&
BrowserUtils.whereToOpenLink(event) != "current"
) {
// If we'll open the link, we want to consume the user gesture
// activation to ensure that we don't allow multiple links to open
// from one user gesture.
// Avoid doing so for links opened in the current tab, which get
// handled later, by gecko, as otherwise its popup blocker will stop
// the link from opening.
// We will do the same check (whereToOpenLink) again in the parent and
// avoid handling the click for such links... but we still need the
// click information in the parent because otherwise places link
// tracking breaks. (bug 1742894 tracks improving this.)
ownerDoc.consumeTransientUserGestureActivation();
// We don't care about the return value because we already checked that
// hasValidTransientUserGestureActivation was true earlier in this
// function.
}
json.href = href;
if (node) {
json.title = node.getAttribute("title");
}
json.originPrincipal = ownerDoc.nodePrincipal;
json.originStoragePrincipal = ownerDoc.effectiveStoragePrincipal;
json.triggeringPrincipal = ownerDoc.nodePrincipal;
// If a link element is clicked with middle button, user wants to open
// the link somewhere rather than pasting clipboard content. Therefore,
// when it's clicked with middle button, we should prevent multiple
// actions here to avoid leaking clipboard content unexpectedly.
// Note that whether the link will work actually or not does not matter
// because in this case, user does not intent to paste clipboard content.
if (event.button === 1) {
event.preventMultipleActions();
}
this.sendAsyncMessage("Content:Click", json);
return;
}
// This might be middle mouse navigation.
if (event.button == 1) {
this.sendAsyncMessage("Content:Click", json);
}
}
}