зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1745720 - use 'chromelinkclick' command event to avoid instantiating ClickHandlerChild except for link clicks, r=smaug,mconley
Instead of relying on untrusted click/auxclick events anywhere instantiating the actor, and then having to look for links, after this patch we'll only instantiate the actor for actual link clicks. This patch moves to using a chrome-only command event (with type `chromelinkclick`) dispatched from the link click post-visitor to accomplish that. In future we should probably move both this and the middle-click-to-paste handling into DOM code (or, for the latter, remove it) but this is a less invasive solution. This also moves the middle-click-to-paste handling into its own listener. It needs to listen to page events in general (not just links) but is disabled everywhere by default, so registering an actor for everyone doesn't seem like a good trade-off. To avoid duplicating all the logic (we do need to avoid doing middle-click navigation based on the clipboard when clicking on links!), as well as keeping patch size down, the actual control flow goes through the click handler actor still. Differential Revision: https://phabricator.services.mozilla.com/D134011
This commit is contained in:
Родитель
c150b0a2c0
Коммит
6b4f4672d0
|
@ -3,9 +3,12 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
var EXPORTED_SYMBOLS = ["ClickHandlerChild"];
|
var EXPORTED_SYMBOLS = ["ClickHandlerChild", "MiddleMousePasteHandlerChild"];
|
||||||
|
|
||||||
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||||
|
const { XPCOMUtils } = ChromeUtils.import(
|
||||||
|
"resource://gre/modules/XPCOMUtils.jsm"
|
||||||
|
);
|
||||||
|
|
||||||
ChromeUtils.defineModuleGetter(
|
ChromeUtils.defineModuleGetter(
|
||||||
this,
|
this,
|
||||||
|
@ -29,15 +32,44 @@ ChromeUtils.defineModuleGetter(
|
||||||
"resource://gre/modules/BrowserUtils.jsm"
|
"resource://gre/modules/BrowserUtils.jsm"
|
||||||
);
|
);
|
||||||
|
|
||||||
class ClickHandlerChild extends JSWindowActorChild {
|
class MiddleMousePasteHandlerChild extends JSWindowActorChild {
|
||||||
handleEvent(event) {
|
handleEvent(clickEvent) {
|
||||||
if (
|
if (
|
||||||
event.defaultPrevented ||
|
clickEvent.defaultPrevented ||
|
||||||
event.button == 2 ||
|
clickEvent.button != 1 ||
|
||||||
(event.type == "click" && event.button == 1)
|
MiddleMousePasteHandlerChild.autoscrollEnabled
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
this.manager
|
||||||
|
.getActor("ClickHandler")
|
||||||
|
.handleClickEvent(
|
||||||
|
clickEvent,
|
||||||
|
/* is from middle mouse paste handler */ true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
onProcessedClick(data) {
|
||||||
|
this.sendAsyncMessage("MiddleClickPaste", data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
XPCOMUtils.defineLazyPreferenceGetter(
|
||||||
|
MiddleMousePasteHandlerChild,
|
||||||
|
"autoscrollEnabled",
|
||||||
|
"general.autoScroll",
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
class ClickHandlerChild extends JSWindowActorChild {
|
||||||
|
handleEvent(wrapperEvent) {
|
||||||
|
this.handleClickEvent(wrapperEvent.sourceEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleClickEvent(event, isFromMiddleMousePasteHandler = false) {
|
||||||
|
if (event.defaultPrevented || event.button == 2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Don't do anything on editable things, we shouldn't open links in
|
// Don't do anything on editable things, we shouldn't open links in
|
||||||
// contenteditables, and editor needs to possibly handle middlemouse paste
|
// contenteditables, and editor needs to possibly handle middlemouse paste
|
||||||
let composedTarget = event.composedTarget;
|
let composedTarget = event.composedTarget;
|
||||||
|
@ -107,7 +139,7 @@ class ClickHandlerChild extends JSWindowActorChild {
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (href) {
|
if (href && !isFromMiddleMousePasteHandler) {
|
||||||
try {
|
try {
|
||||||
Services.scriptSecurityManager.checkLoadURIStrWithPrincipal(
|
Services.scriptSecurityManager.checkLoadURIStrWithPrincipal(
|
||||||
principal,
|
principal,
|
||||||
|
@ -157,12 +189,11 @@ class ClickHandlerChild extends JSWindowActorChild {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.sendAsyncMessage("Content:Click", json);
|
this.sendAsyncMessage("Content:Click", json);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This might be middle mouse navigation.
|
// This might be middle mouse navigation, in which case pass this back:
|
||||||
if (event.button == 1) {
|
if (!href && event.button == 1 && isFromMiddleMousePasteHandler) {
|
||||||
this.sendAsyncMessage("Content:Click", json);
|
this.manager.getActor("MiddleMousePasteHandler").onProcessedClick(json);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,9 +5,7 @@
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var EXPORTED_SYMBOLS = ["ClickHandlerParent"];
|
var EXPORTED_SYMBOLS = ["ClickHandlerParent", "MiddleMousePasteHandlerParent"];
|
||||||
|
|
||||||
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
|
||||||
|
|
||||||
ChromeUtils.defineModuleGetter(
|
ChromeUtils.defineModuleGetter(
|
||||||
this,
|
this,
|
||||||
|
@ -27,6 +25,22 @@ ChromeUtils.defineModuleGetter(
|
||||||
|
|
||||||
let gContentClickListeners = new Set();
|
let gContentClickListeners = new Set();
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
browser.ownerGlobal.middleMousePaste(message.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class ClickHandlerParent extends JSWindowActorParent {
|
class ClickHandlerParent extends JSWindowActorParent {
|
||||||
static addContentClickListener(listener) {
|
static addContentClickListener(listener) {
|
||||||
gContentClickListeners.add(listener);
|
gContentClickListeners.add(listener);
|
||||||
|
@ -62,17 +76,6 @@ class ClickHandlerParent extends JSWindowActorParent {
|
||||||
}
|
}
|
||||||
let window = browser.ownerGlobal;
|
let window = browser.ownerGlobal;
|
||||||
|
|
||||||
if (!data.href) {
|
|
||||||
// Might be middle mouse navigation.
|
|
||||||
if (
|
|
||||||
Services.prefs.getBoolPref("middlemouse.contentLoadURL") &&
|
|
||||||
!Services.prefs.getBoolPref("general.autoScroll")
|
|
||||||
) {
|
|
||||||
window.middleMousePaste(data);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the browser is not in a place where we can open links, bail out.
|
// 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
|
// This can happen in osx sheets, dialogs, etc. that are not browser
|
||||||
// windows. Specifically the payments UI is in an osx sheet.
|
// windows. Specifically the payments UI is in an osx sheet.
|
||||||
|
|
|
@ -393,14 +393,34 @@ let JSWINDOWACTORS = {
|
||||||
child: {
|
child: {
|
||||||
moduleURI: "resource:///actors/ClickHandlerChild.jsm",
|
moduleURI: "resource:///actors/ClickHandlerChild.jsm",
|
||||||
events: {
|
events: {
|
||||||
click: { capture: true, mozSystemGroup: true, wantUntrusted: true },
|
chromelinkclick: { capture: true, mozSystemGroup: true },
|
||||||
auxclick: { capture: true, mozSystemGroup: true, wantUntrusted: true },
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
allFrames: true,
|
allFrames: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/* Note: this uses the same JSMs as ClickHandler, but because it
|
||||||
|
* relies on "normal" click events anywhere on the page (not just
|
||||||
|
* links) and is expensive, and only does something for the
|
||||||
|
* small group of people who have the feature enabled, it is its
|
||||||
|
* own actor which is only registered if the pref is enabled.
|
||||||
|
*/
|
||||||
|
MiddleMousePasteHandler: {
|
||||||
|
parent: {
|
||||||
|
moduleURI: "resource:///actors/ClickHandlerParent.jsm",
|
||||||
|
},
|
||||||
|
child: {
|
||||||
|
moduleURI: "resource:///actors/ClickHandlerChild.jsm",
|
||||||
|
events: {
|
||||||
|
auxclick: { capture: true, mozSystemGroup: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
enablePreference: "middlemouse.contentLoadURL",
|
||||||
|
|
||||||
|
allFrames: true,
|
||||||
|
},
|
||||||
|
|
||||||
ContentSearch: {
|
ContentSearch: {
|
||||||
parent: {
|
parent: {
|
||||||
moduleURI: "resource:///actors/ContentSearchParent.jsm",
|
moduleURI: "resource:///actors/ContentSearchParent.jsm",
|
||||||
|
|
|
@ -88,6 +88,7 @@
|
||||||
#include "mozilla/dom/HTMLTemplateElement.h"
|
#include "mozilla/dom/HTMLTemplateElement.h"
|
||||||
#include "mozilla/dom/KeyframeAnimationOptionsBinding.h"
|
#include "mozilla/dom/KeyframeAnimationOptionsBinding.h"
|
||||||
#include "mozilla/dom/KeyframeEffect.h"
|
#include "mozilla/dom/KeyframeEffect.h"
|
||||||
|
#include "mozilla/dom/MouseEvent.h"
|
||||||
#include "mozilla/dom/MouseEventBinding.h"
|
#include "mozilla/dom/MouseEventBinding.h"
|
||||||
#include "mozilla/dom/MutationEventBinding.h"
|
#include "mozilla/dom/MutationEventBinding.h"
|
||||||
#include "mozilla/dom/MutationObservers.h"
|
#include "mozilla/dom/MutationObservers.h"
|
||||||
|
@ -100,6 +101,7 @@
|
||||||
#include "mozilla/dom/ShadowRoot.h"
|
#include "mozilla/dom/ShadowRoot.h"
|
||||||
#include "mozilla/dom/Text.h"
|
#include "mozilla/dom/Text.h"
|
||||||
#include "mozilla/dom/WindowBinding.h"
|
#include "mozilla/dom/WindowBinding.h"
|
||||||
|
#include "mozilla/dom/XULCommandEvent.h"
|
||||||
#include "mozilla/dom/nsCSPContext.h"
|
#include "mozilla/dom/nsCSPContext.h"
|
||||||
#include "mozilla/gfx/BasePoint.h"
|
#include "mozilla/gfx/BasePoint.h"
|
||||||
#include "mozilla/gfx/BaseRect.h"
|
#include "mozilla/gfx/BaseRect.h"
|
||||||
|
@ -3068,12 +3070,51 @@ void Element::GetEventTargetParentForLinks(EventChainPreVisitor& aVisitor) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This dispatches a 'chromelinkclick' CustomEvent to chrome-only listeners,
|
||||||
|
// so that frontend can handle middle-clicks and ctrl/cmd/shift/etc.-clicks
|
||||||
|
// on links, without getting a call for every single click the user makes.
|
||||||
|
// Only supported for click or auxclick events.
|
||||||
|
void Element::DispatchChromeOnlyLinkClickEvent(
|
||||||
|
EventChainPostVisitor& aVisitor) {
|
||||||
|
MOZ_ASSERT(aVisitor.mEvent->mMessage == eMouseAuxClick ||
|
||||||
|
aVisitor.mEvent->mMessage == eMouseClick,
|
||||||
|
"DispatchChromeOnlyLinkClickEvent supports only click and "
|
||||||
|
"auxclick source events");
|
||||||
|
Document* doc = OwnerDoc();
|
||||||
|
RefPtr<XULCommandEvent> event =
|
||||||
|
new XULCommandEvent(doc, aVisitor.mPresContext, nullptr);
|
||||||
|
RefPtr<dom::Event> mouseDOMEvent = aVisitor.mDOMEvent;
|
||||||
|
if (!mouseDOMEvent) {
|
||||||
|
mouseDOMEvent = EventDispatcher::CreateEvent(
|
||||||
|
aVisitor.mEvent->mOriginalTarget, aVisitor.mPresContext,
|
||||||
|
aVisitor.mEvent, u""_ns);
|
||||||
|
aVisitor.mDOMEvent = mouseDOMEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseEvent* mouseEvent = mouseDOMEvent->AsMouseEvent();
|
||||||
|
event->InitCommandEvent(
|
||||||
|
u"chromelinkclick"_ns, /* CanBubble */ true,
|
||||||
|
/* Cancelable */ true, nsGlobalWindowInner::Cast(doc->GetInnerWindow()),
|
||||||
|
0, mouseEvent->CtrlKey(), mouseEvent->AltKey(), mouseEvent->ShiftKey(),
|
||||||
|
mouseEvent->MetaKey(), mouseEvent->Button(), mouseDOMEvent,
|
||||||
|
mouseEvent->MozInputSource(), IgnoreErrors());
|
||||||
|
// Note: we're always trusted, but the event we pass as the `sourceEvent`
|
||||||
|
// might not be. Frontend code will check that event's trusted property to
|
||||||
|
// make that determination; doing it this way means we don't also start
|
||||||
|
// acting on web-generated custom 'chromelinkclick' events which would
|
||||||
|
// provide additional attack surface for a malicious actor.
|
||||||
|
event->SetTrusted(true);
|
||||||
|
event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true;
|
||||||
|
DispatchEvent(*event);
|
||||||
|
}
|
||||||
|
|
||||||
nsresult Element::PostHandleEventForLinks(EventChainPostVisitor& aVisitor) {
|
nsresult Element::PostHandleEventForLinks(EventChainPostVisitor& aVisitor) {
|
||||||
// Optimisation: return early if this event doesn't interest us.
|
// Optimisation: return early if this event doesn't interest us.
|
||||||
// IMPORTANT: this switch and the switch below it must be kept in sync!
|
// IMPORTANT: this switch and the switch below it must be kept in sync!
|
||||||
switch (aVisitor.mEvent->mMessage) {
|
switch (aVisitor.mEvent->mMessage) {
|
||||||
case eMouseDown:
|
case eMouseDown:
|
||||||
case eMouseClick:
|
case eMouseClick:
|
||||||
|
case eMouseAuxClick:
|
||||||
case eLegacyDOMActivate:
|
case eLegacyDOMActivate:
|
||||||
case eKeyPress:
|
case eKeyPress:
|
||||||
break;
|
break;
|
||||||
|
@ -3128,26 +3169,30 @@ nsresult Element::PostHandleEventForLinks(EventChainPostVisitor& aVisitor) {
|
||||||
case eMouseClick: {
|
case eMouseClick: {
|
||||||
WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent();
|
WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent();
|
||||||
if (mouseEvent->IsLeftClickEvent()) {
|
if (mouseEvent->IsLeftClickEvent()) {
|
||||||
if (mouseEvent->IsControl() || mouseEvent->IsMeta() ||
|
if (!mouseEvent->IsControl() && !mouseEvent->IsMeta() &&
|
||||||
mouseEvent->IsAlt() || mouseEvent->IsShift()) {
|
!mouseEvent->IsAlt() && !mouseEvent->IsShift()) {
|
||||||
break;
|
// The default action is simply to dispatch DOMActivate
|
||||||
|
nsEventStatus status = nsEventStatus_eIgnore;
|
||||||
|
// DOMActivate event should be trusted since the activation is
|
||||||
|
// actually occurred even if the cause is an untrusted click event.
|
||||||
|
InternalUIEvent actEvent(true, eLegacyDOMActivate, mouseEvent);
|
||||||
|
actEvent.mDetail = 1;
|
||||||
|
|
||||||
|
rv = EventDispatcher::Dispatch(this, aVisitor.mPresContext, &actEvent,
|
||||||
|
nullptr, &status);
|
||||||
|
if (NS_SUCCEEDED(rv)) {
|
||||||
|
aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The default action is simply to dispatch DOMActivate
|
DispatchChromeOnlyLinkClickEvent(aVisitor);
|
||||||
nsEventStatus status = nsEventStatus_eIgnore;
|
|
||||||
// DOMActive event should be trusted since the activation is actually
|
|
||||||
// occurred even if the cause is an untrusted click event.
|
|
||||||
InternalUIEvent actEvent(true, eLegacyDOMActivate, mouseEvent);
|
|
||||||
actEvent.mDetail = 1;
|
|
||||||
|
|
||||||
rv = EventDispatcher::Dispatch(this, aVisitor.mPresContext, &actEvent,
|
|
||||||
nullptr, &status);
|
|
||||||
if (NS_SUCCEEDED(rv)) {
|
|
||||||
aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case eMouseAuxClick: {
|
||||||
|
DispatchChromeOnlyLinkClickEvent(aVisitor);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case eLegacyDOMActivate: {
|
case eLegacyDOMActivate: {
|
||||||
if (aVisitor.mEvent->mOriginalTarget == this) {
|
if (aVisitor.mEvent->mOriginalTarget == this) {
|
||||||
nsAutoString target;
|
nsAutoString target;
|
||||||
|
|
|
@ -1950,6 +1950,8 @@ class Element : public FragmentOrElement {
|
||||||
*/
|
*/
|
||||||
void GetEventTargetParentForLinks(EventChainPreVisitor& aVisitor);
|
void GetEventTargetParentForLinks(EventChainPreVisitor& aVisitor);
|
||||||
|
|
||||||
|
void DispatchChromeOnlyLinkClickEvent(EventChainPostVisitor& aVisitor);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle default actions for link event if the event isn't consumed yet.
|
* Handle default actions for link event if the event isn't consumed yet.
|
||||||
*/
|
*/
|
||||||
|
|
Загрузка…
Ссылка в новой задаче