gecko-dev/dom/events/EventStateManager.cpp

5958 строки
207 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */
#include "mozilla/Attributes.h"
#include "mozilla/EventDispatcher.h"
#include "mozilla/EventStateManager.h"
#include "mozilla/EventStates.h"
#include "mozilla/IMEStateManager.h"
#include "mozilla/MiscEvents.h"
#include "mozilla/MathAlgorithms.h"
#include "mozilla/MouseEvents.h"
#include "mozilla/TextComposition.h"
#include "mozilla/TextEvents.h"
#include "mozilla/TouchEvents.h"
#include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/DragEvent.h"
#include "mozilla/dom/Event.h"
#include "mozilla/dom/TabChild.h"
#include "mozilla/dom/TabParent.h"
#include "mozilla/dom/UIEvent.h"
#include "ContentEventHandler.h"
#include "IMEContentObserver.h"
#include "WheelHandlingHelper.h"
#include "nsCOMPtr.h"
#include "nsFocusManager.h"
#include "nsIContent.h"
#include "nsIContentInlines.h"
#include "nsIDocument.h"
#include "nsIFrame.h"
#include "nsIWidget.h"
#include "nsPresContext.h"
#include "nsIPresShell.h"
#include "nsGkAtoms.h"
#include "nsIFormControl.h"
#include "nsIComboboxControlFrame.h"
#include "nsIScrollableFrame.h"
#include "nsIDOMHTMLElement.h"
#include "nsIDOMXULControlElement.h"
#include "nsNameSpaceManager.h"
#include "nsIBaseWindow.h"
#include "nsISelection.h"
#include "nsITextControlElement.h"
#include "nsFrameSelection.h"
#include "nsPIDOMWindow.h"
#include "nsPIWindowRoot.h"
#include "nsIWebNavigation.h"
#include "nsIContentViewer.h"
#include "nsFrameManager.h"
#include "nsITabChild.h"
#include "nsPluginFrame.h"
#include "nsMenuPopupFrame.h"
#include "nsIDOMXULElement.h"
#include "nsIDOMKeyEvent.h"
#include "nsIObserverService.h"
#include "nsIDocShell.h"
#include "nsIDOMWheelEvent.h"
#include "nsIDOMUIEvent.h"
#include "nsIMozBrowserFrame.h"
#include "nsSubDocumentFrame.h"
#include "nsLayoutUtils.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsUnicharUtils.h"
#include "nsContentUtils.h"
#include "imgIContainer.h"
#include "nsIProperties.h"
#include "nsISupportsPrimitives.h"
#include "nsServiceManagerUtils.h"
#include "nsITimer.h"
#include "nsFontMetrics.h"
#include "nsIDOMXULDocument.h"
#include "nsIDragService.h"
#include "nsIDragSession.h"
#include "mozilla/dom/DataTransfer.h"
#include "nsContentAreaDragDrop.h"
#ifdef MOZ_XUL
#include "nsTreeBodyFrame.h"
#endif
#include "nsIController.h"
#include "nsICommandParams.h"
#include "mozilla/Services.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/HTMLLabelElement.h"
#include "mozilla/Preferences.h"
#include "mozilla/LookAndFeel.h"
#include "GeckoProfiler.h"
#include "Units.h"
#include "mozilla/layers/APZCTreeManager.h"
#include "nsIObjectLoadingContent.h"
#ifdef XP_MACOSX
#import <ApplicationServices/ApplicationServices.h>
#endif
namespace mozilla {
using namespace dom;
//#define DEBUG_DOCSHELL_FOCUS
#define NS_USER_INTERACTION_INTERVAL 5000 // ms
static const LayoutDeviceIntPoint kInvalidRefPoint = LayoutDeviceIntPoint(-1,-1);
static uint32_t gMouseOrKeyboardEventCounter = 0;
static nsITimer* gUserInteractionTimer = nullptr;
static nsITimerCallback* gUserInteractionTimerCallback = nullptr;
static const double kCursorLoadingTimeout = 1000; // ms
static AutoWeakFrame gLastCursorSourceFrame;
static TimeStamp gLastCursorUpdateTime;
static inline int32_t
RoundDown(double aDouble)
{
return (aDouble > 0) ? static_cast<int32_t>(floor(aDouble)) :
static_cast<int32_t>(ceil(aDouble));
}
#ifdef DEBUG_DOCSHELL_FOCUS
static void
PrintDocTree(nsIDocShellTreeItem* aParentItem, int aLevel)
{
for (int32_t i=0;i<aLevel;i++) printf(" ");
int32_t childWebshellCount;
aParentItem->GetChildCount(&childWebshellCount);
nsCOMPtr<nsIDocShell> parentAsDocShell(do_QueryInterface(aParentItem));
int32_t type = aParentItem->ItemType();
nsCOMPtr<nsIPresShell> presShell = parentAsDocShell->GetPresShell();
RefPtr<nsPresContext> presContext;
parentAsDocShell->GetPresContext(getter_AddRefs(presContext));
nsCOMPtr<nsIContentViewer> cv;
parentAsDocShell->GetContentViewer(getter_AddRefs(cv));
nsCOMPtr<nsIDOMDocument> domDoc;
if (cv)
cv->GetDOMDocument(getter_AddRefs(domDoc));
nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
nsCOMPtr<nsIDOMWindow> domwin = doc ? doc->GetWindow() : nullptr;
nsIURI* uri = doc ? doc->GetDocumentURI() : nullptr;
printf("DS %p Type %s Cnt %d Doc %p DW %p EM %p%c",
static_cast<void*>(parentAsDocShell.get()),
type==nsIDocShellTreeItem::typeChrome?"Chrome":"Content",
childWebshellCount, static_cast<void*>(doc.get()),
static_cast<void*>(domwin.get()),
static_cast<void*>(presContext ? presContext->EventStateManager() : nullptr),
uri ? ' ' : '\n');
if (uri) {
nsAutoCString spec;
uri->GetSpec(spec);
printf("\"%s\"\n", spec.get());
}
if (childWebshellCount > 0) {
for (int32_t i = 0; i < childWebshellCount; i++) {
nsCOMPtr<nsIDocShellTreeItem> child;
aParentItem->GetChildAt(i, getter_AddRefs(child));
PrintDocTree(child, aLevel + 1);
}
}
}
static void
PrintDocTreeAll(nsIDocShellTreeItem* aItem)
{
nsCOMPtr<nsIDocShellTreeItem> item = aItem;
for(;;) {
nsCOMPtr<nsIDocShellTreeItem> parent;
item->GetParent(getter_AddRefs(parent));
if (!parent)
break;
item = parent;
}
PrintDocTree(item, 0);
}
#endif
/******************************************************************/
/* mozilla::UITimerCallback */
/******************************************************************/
class UITimerCallback final :
public nsITimerCallback,
public nsINamed
{
public:
UITimerCallback() : mPreviousCount(0) {}
NS_DECL_ISUPPORTS
NS_DECL_NSITIMERCALLBACK
NS_DECL_NSINAMED
private:
~UITimerCallback() = default;
uint32_t mPreviousCount;
};
NS_IMPL_ISUPPORTS(UITimerCallback, nsITimerCallback, nsINamed)
// If aTimer is nullptr, this method always sends "user-interaction-inactive"
// notification.
NS_IMETHODIMP
UITimerCallback::Notify(nsITimer* aTimer)
{
nsCOMPtr<nsIObserverService> obs =
mozilla::services::GetObserverService();
if (!obs)
return NS_ERROR_FAILURE;
if ((gMouseOrKeyboardEventCounter == mPreviousCount) || !aTimer) {
gMouseOrKeyboardEventCounter = 0;
obs->NotifyObservers(nullptr, "user-interaction-inactive", nullptr);
if (gUserInteractionTimer) {
gUserInteractionTimer->Cancel();
NS_RELEASE(gUserInteractionTimer);
}
} else {
obs->NotifyObservers(nullptr, "user-interaction-active", nullptr);
EventStateManager::UpdateUserActivityTimer();
}
mPreviousCount = gMouseOrKeyboardEventCounter;
return NS_OK;
}
NS_IMETHODIMP
UITimerCallback::GetName(nsACString& aName)
{
aName.AssignASCII("UITimerCallback_timer");
return NS_OK;
}
/******************************************************************/
/* mozilla::OverOutElementsWrapper */
/******************************************************************/
OverOutElementsWrapper::OverOutElementsWrapper()
: mLastOverFrame(nullptr)
{
}
OverOutElementsWrapper::~OverOutElementsWrapper() = default;
NS_IMPL_CYCLE_COLLECTION(OverOutElementsWrapper,
mLastOverElement,
mFirstOverEventElement,
mFirstOutEventElement)
NS_IMPL_CYCLE_COLLECTING_ADDREF(OverOutElementsWrapper)
NS_IMPL_CYCLE_COLLECTING_RELEASE(OverOutElementsWrapper)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(OverOutElementsWrapper)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
/******************************************************************/
/* mozilla::EventStateManager */
/******************************************************************/
static uint32_t sESMInstanceCount = 0;
static bool sPointerEventEnabled = false;
uint64_t EventStateManager::sUserInputCounter = 0;
int32_t EventStateManager::sUserInputEventDepth = 0;
bool EventStateManager::sNormalLMouseEventInProcess = false;
EventStateManager* EventStateManager::sActiveESM = nullptr;
nsIDocument* EventStateManager::sMouseOverDocument = nullptr;
AutoWeakFrame EventStateManager::sLastDragOverFrame = nullptr;
LayoutDeviceIntPoint EventStateManager::sPreLockPoint = LayoutDeviceIntPoint(0, 0);
LayoutDeviceIntPoint EventStateManager::sLastRefPoint = kInvalidRefPoint;
CSSIntPoint EventStateManager::sLastScreenPoint = CSSIntPoint(0, 0);
LayoutDeviceIntPoint EventStateManager::sSynthCenteringPoint = kInvalidRefPoint;
CSSIntPoint EventStateManager::sLastClientPoint = CSSIntPoint(0, 0);
bool EventStateManager::sIsPointerLocked = false;
// Reference to the pointer locked element.
nsWeakPtr EventStateManager::sPointerLockedElement;
// Reference to the document which requested pointer lock.
nsWeakPtr EventStateManager::sPointerLockedDoc;
nsCOMPtr<nsIContent> EventStateManager::sDragOverContent = nullptr;
TimeStamp EventStateManager::sLatestUserInputStart;
TimeStamp EventStateManager::sHandlingInputStart;
EventStateManager::WheelPrefs*
EventStateManager::WheelPrefs::sInstance = nullptr;
bool EventStateManager::WheelPrefs::sWheelEventsEnabledOnPlugins = true;
EventStateManager::DeltaAccumulator*
EventStateManager::DeltaAccumulator::sInstance = nullptr;
EventStateManager::EventStateManager()
: mLockCursor(0)
, mLastFrameConsumedSetCursor(false)
, mCurrentTarget(nullptr)
// init d&d gesture state machine variables
, mGestureDownPoint(0,0)
, mPresContext(nullptr)
, mLClickCount(0)
, mMClickCount(0)
, mRClickCount(0)
, mInTouchDrag(false)
, m_haveShutdown(false)
{
if (sESMInstanceCount == 0) {
gUserInteractionTimerCallback = new UITimerCallback();
if (gUserInteractionTimerCallback)
NS_ADDREF(gUserInteractionTimerCallback);
UpdateUserActivityTimer();
}
++sESMInstanceCount;
static bool sAddedPointerEventEnabled = false;
if (!sAddedPointerEventEnabled) {
Preferences::AddBoolVarCache(&sPointerEventEnabled,
"dom.w3c_pointer_events.enabled", false);
sAddedPointerEventEnabled = true;
}
WheelTransaction::InitializeStatics();
}
nsresult
EventStateManager::UpdateUserActivityTimer()
{
if (!gUserInteractionTimerCallback)
return NS_OK;
if (!gUserInteractionTimer) {
CallCreateInstance("@mozilla.org/timer;1", &gUserInteractionTimer);
if (gUserInteractionTimer) {
gUserInteractionTimer->SetTarget(
SystemGroup::EventTargetFor(TaskCategory::Other));
}
}
if (gUserInteractionTimer) {
gUserInteractionTimer->InitWithCallback(gUserInteractionTimerCallback,
NS_USER_INTERACTION_INTERVAL,
nsITimer::TYPE_ONE_SHOT);
}
return NS_OK;
}
nsresult
EventStateManager::Init()
{
nsCOMPtr<nsIObserverService> observerService =
mozilla::services::GetObserverService();
if (!observerService)
return NS_ERROR_FAILURE;
observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true);
if (sESMInstanceCount == 1) {
Prefs::Init();
}
return NS_OK;
}
EventStateManager::~EventStateManager()
{
ReleaseCurrentIMEContentObserver();
if (sActiveESM == this) {
sActiveESM = nullptr;
}
if (Prefs::ClickHoldContextMenu())
KillClickHoldTimer();
if (mDocument == sMouseOverDocument)
sMouseOverDocument = nullptr;
--sESMInstanceCount;
if(sESMInstanceCount == 0) {
WheelTransaction::Shutdown();
if (gUserInteractionTimerCallback) {
gUserInteractionTimerCallback->Notify(nullptr);
NS_RELEASE(gUserInteractionTimerCallback);
}
if (gUserInteractionTimer) {
gUserInteractionTimer->Cancel();
NS_RELEASE(gUserInteractionTimer);
}
Prefs::Shutdown();
WheelPrefs::Shutdown();
DeltaAccumulator::Shutdown();
}
if (sDragOverContent && sDragOverContent->OwnerDoc() == mDocument) {
sDragOverContent = nullptr;
}
if (!m_haveShutdown) {
Shutdown();
// Don't remove from Observer service in Shutdown because Shutdown also
// gets called from xpcom shutdown observer. And we don't want to remove
// from the service in that case.
nsCOMPtr<nsIObserverService> observerService =
mozilla::services::GetObserverService();
if (observerService) {
observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
}
}
}
nsresult
EventStateManager::Shutdown()
{
m_haveShutdown = true;
return NS_OK;
}
NS_IMETHODIMP
EventStateManager::Observe(nsISupports* aSubject,
const char* aTopic,
const char16_t *someData)
{
if (!nsCRT::strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
Shutdown();
}
return NS_OK;
}
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(EventStateManager)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
NS_INTERFACE_MAP_ENTRY(nsIObserver)
NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(EventStateManager)
NS_IMPL_CYCLE_COLLECTING_RELEASE(EventStateManager)
NS_IMPL_CYCLE_COLLECTION(EventStateManager,
mCurrentTargetContent,
mGestureDownContent,
mGestureDownFrameOwner,
mLastLeftMouseDownContent,
mLastLeftMouseDownContentParent,
mLastMiddleMouseDownContent,
mLastMiddleMouseDownContentParent,
mLastRightMouseDownContent,
mLastRightMouseDownContentParent,
mActiveContent,
mHoverContent,
mURLTargetContent,
mMouseEnterLeaveHelper,
mPointersEnterLeaveHelper,
mDocument,
mIMEContentObserver,
mAccessKeys)
void
EventStateManager::ReleaseCurrentIMEContentObserver()
{
if (mIMEContentObserver) {
mIMEContentObserver->DisconnectFromEventStateManager();
}
mIMEContentObserver = nullptr;
}
void
EventStateManager::OnStartToObserveContent(
IMEContentObserver* aIMEContentObserver)
{
if (mIMEContentObserver == aIMEContentObserver) {
return;
}
ReleaseCurrentIMEContentObserver();
mIMEContentObserver = aIMEContentObserver;
}
void
EventStateManager::OnStopObservingContent(
IMEContentObserver* aIMEContentObserver)
{
aIMEContentObserver->DisconnectFromEventStateManager();
NS_ENSURE_TRUE_VOID(mIMEContentObserver == aIMEContentObserver);
mIMEContentObserver = nullptr;
}
void
EventStateManager::TryToFlushPendingNotificationsToIME()
{
if (mIMEContentObserver) {
mIMEContentObserver->TryToFlushPendingNotifications(true);
}
}
static bool
IsMessageMouseUserActivity(EventMessage aMessage)
{
return aMessage == eMouseMove ||
aMessage == eMouseUp ||
aMessage == eMouseDown ||
aMessage == eMouseAuxClick ||
aMessage == eMouseDoubleClick ||
aMessage == eMouseClick ||
aMessage == eMouseActivate ||
aMessage == eMouseLongTap;
}
static bool
IsMessageGamepadUserActivity(EventMessage aMessage)
{
return aMessage == eGamepadButtonDown ||
aMessage == eGamepadButtonUp ||
aMessage == eGamepadAxisMove;
}
nsresult
EventStateManager::PreHandleEvent(nsPresContext* aPresContext,
WidgetEvent* aEvent,
nsIFrame* aTargetFrame,
nsIContent* aTargetContent,
nsEventStatus* aStatus)
{
NS_ENSURE_ARG_POINTER(aStatus);
NS_ENSURE_ARG(aPresContext);
if (!aEvent) {
NS_ERROR("aEvent is null. This should never happen.");
return NS_ERROR_NULL_POINTER;
}
NS_WARNING_ASSERTION(
!aTargetFrame || !aTargetFrame->GetContent() ||
aTargetFrame->GetContent() == aTargetContent ||
aTargetFrame->GetContent()->GetFlattenedTreeParent() == aTargetContent ||
aTargetFrame->IsGeneratedContentFrame(),
"aTargetFrame should be related with aTargetContent");
#if DEBUG
if (aTargetFrame && aTargetFrame->IsGeneratedContentFrame()) {
nsCOMPtr<nsIContent> targetContent;
aTargetFrame->GetContentForEvent(aEvent, getter_AddRefs(targetContent));
MOZ_ASSERT(aTargetContent == targetContent,
"Unexpected target for generated content frame!");
}
#endif
mCurrentTarget = aTargetFrame;
mCurrentTargetContent = nullptr;
// Do not take account eMouseEnterIntoWidget/ExitFromWidget so that loading
// a page when user is not active doesn't change the state to active.
WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
if (aEvent->IsTrusted() &&
((mouseEvent && mouseEvent->IsReal() &&
IsMessageMouseUserActivity(mouseEvent->mMessage)) ||
aEvent->mClass == eWheelEventClass ||
aEvent->mClass == ePointerEventClass ||
aEvent->mClass == eTouchEventClass ||
aEvent->mClass == eKeyboardEventClass ||
IsMessageGamepadUserActivity(aEvent->mMessage))) {
if (gMouseOrKeyboardEventCounter == 0) {
nsCOMPtr<nsIObserverService> obs =
mozilla::services::GetObserverService();
if (obs) {
obs->NotifyObservers(nullptr, "user-interaction-active", nullptr);
UpdateUserActivityTimer();
}
}
++gMouseOrKeyboardEventCounter;
nsCOMPtr<nsINode> node = do_QueryInterface(aTargetContent);
if (node &&
(aEvent->mMessage == eKeyUp || aEvent->mMessage == eMouseUp ||
aEvent->mMessage == eWheel || aEvent->mMessage == eTouchEnd ||
aEvent->mMessage == ePointerUp)) {
nsIDocument* doc = node->OwnerDoc();
while (doc && !doc->UserHasInteracted()) {
doc->SetUserHasInteracted(true);
doc = nsContentUtils::IsChildOfSameType(doc) ?
doc->GetParentDocument() : nullptr;
}
}
}
WheelTransaction::OnEvent(aEvent);
// Focus events don't necessarily need a frame.
if (!mCurrentTarget && !aTargetContent) {
NS_ERROR("mCurrentTarget and aTargetContent are null");
return NS_ERROR_NULL_POINTER;
}
#ifdef DEBUG
if (aEvent->HasDragEventMessage() && sIsPointerLocked) {
NS_ASSERTION(sIsPointerLocked,
"sIsPointerLocked is true. Drag events should be suppressed when "
"the pointer is locked.");
}
#endif
// Store last known screenPoint and clientPoint so pointer lock
// can use these values as constants.
if (aEvent->IsTrusted() &&
((mouseEvent && mouseEvent->IsReal()) ||
aEvent->mClass == eWheelEventClass) &&
!sIsPointerLocked) {
sLastScreenPoint =
Event::GetScreenCoords(aPresContext, aEvent, aEvent->mRefPoint);
sLastClientPoint =
Event::GetClientCoords(aPresContext, aEvent, aEvent->mRefPoint,
CSSIntPoint(0, 0));
}
*aStatus = nsEventStatus_eIgnore;
if (aEvent->mClass == eQueryContentEventClass) {
HandleQueryContentEvent(aEvent->AsQueryContentEvent());
return NS_OK;
}
WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
if (touchEvent && mInTouchDrag) {
if (touchEvent->mMessage == eTouchMove) {
GenerateDragGesture(aPresContext, touchEvent);
} else {
mInTouchDrag = false;
StopTrackingDragGesture();
}
}
switch (aEvent->mMessage) {
case eContextMenu:
if (sIsPointerLocked) {
return NS_ERROR_DOM_INVALID_STATE_ERR;
}
break;
case eMouseTouchDrag:
mInTouchDrag = true;
BeginTrackingDragGesture(aPresContext, mouseEvent, aTargetFrame);
break;
case eMouseDown: {
switch (mouseEvent->button) {
case WidgetMouseEvent::eLeftButton:
BeginTrackingDragGesture(aPresContext, mouseEvent, aTargetFrame);
mLClickCount = mouseEvent->mClickCount;
SetClickCount(mouseEvent, aStatus);
sNormalLMouseEventInProcess = true;
break;
case WidgetMouseEvent::eMiddleButton:
mMClickCount = mouseEvent->mClickCount;
SetClickCount(mouseEvent, aStatus);
break;
case WidgetMouseEvent::eRightButton:
mRClickCount = mouseEvent->mClickCount;
SetClickCount(mouseEvent, aStatus);
break;
}
break;
}
case eMouseUp: {
switch (mouseEvent->button) {
case WidgetMouseEvent::eLeftButton:
if (Prefs::ClickHoldContextMenu()) {
KillClickHoldTimer();
}
StopTrackingDragGesture();
sNormalLMouseEventInProcess = false;
// then fall through...
MOZ_FALLTHROUGH;
case WidgetMouseEvent::eRightButton:
case WidgetMouseEvent::eMiddleButton:
SetClickCount(mouseEvent, aStatus);
break;
}
break;
}
case eMouseEnterIntoWidget:
// In some cases on e10s eMouseEnterIntoWidget
// event was sent twice into child process of content.
// (From specific widget code (sending is not permanent) and
// from ESM::DispatchMouseOrPointerEvent (sending is permanent)).
// IsCrossProcessForwardingStopped() helps to suppress sending accidental
// event from widget code.
aEvent->StopCrossProcessForwarding();
break;
case eMouseExitFromWidget:
// If this is a remote frame, we receive eMouseExitFromWidget from the
// parent the mouse exits our content. Since the parent may update the
// cursor while the mouse is outside our frame, and since PuppetWidget
// caches the current cursor internally, re-entering our content (say from
// over a window edge) wont update the cursor if the cached value and the
// current cursor match. So when the mouse exits a remote frame, clear the
// cached widget cursor so a proper update will occur when the mouse
// re-enters.
if (XRE_IsContentProcess()) {
ClearCachedWidgetCursor(mCurrentTarget);
}
// IsCrossProcessForwardingStopped() helps to suppress double event sending
// into process of content.
// For more information see comment above, at eMouseEnterIntoWidget case.
aEvent->StopCrossProcessForwarding();
// If the event is not a top-level window exit, then it's not
// really an exit --- we may have traversed widget boundaries but
// we're still in our toplevel window.
if (mouseEvent->mExitFrom != WidgetMouseEvent::eTopLevel) {
// Treat it as a synthetic move so we don't generate spurious
// "exit" or "move" events. Any necessary "out" or "over" events
// will be generated by GenerateMouseEnterExit
mouseEvent->mMessage = eMouseMove;
mouseEvent->mReason = WidgetMouseEvent::eSynthesized;
// then fall through...
} else {
if (sPointerEventEnabled) {
// We should synthetize corresponding pointer events
GeneratePointerEnterExit(ePointerLeave, mouseEvent);
}
GenerateMouseEnterExit(mouseEvent);
//This is a window level mouse exit event and should stop here
aEvent->mMessage = eVoidEvent;
break;
}
MOZ_FALLTHROUGH;
case eMouseMove:
case ePointerDown:
case ePointerMove: {
// on the Mac, GenerateDragGesture() may not return until the drag
// has completed and so |aTargetFrame| may have been deleted (moving
// a bookmark, for example). If this is the case, however, we know
// that ClearFrameRefs() has been called and it cleared out
// |mCurrentTarget|. As a result, we should pass |mCurrentTarget|
// into UpdateCursor().
GenerateDragGesture(aPresContext, mouseEvent);
UpdateCursor(aPresContext, aEvent, mCurrentTarget, aStatus);
GenerateMouseEnterExit(mouseEvent);
// Flush pending layout changes, so that later mouse move events
// will go to the right nodes.
FlushPendingEvents(aPresContext);
break;
}
case ePointerGotCapture:
GenerateMouseEnterExit(mouseEvent);
break;
case eDragStart:
if (Prefs::ClickHoldContextMenu()) {
// an external drag gesture event came in, not generated internally
// by Gecko. Make sure we get rid of the click-hold timer.
KillClickHoldTimer();
}
break;
case eDragOver:
// Send the enter/exit events before eDrop.
GenerateDragDropEnterExit(aPresContext, aEvent->AsDragEvent());
break;
case eKeyPress:
{
WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent();
if (keyEvent->ModifiersMatchWithAccessKey(AccessKeyType::eChrome) ||
keyEvent->ModifiersMatchWithAccessKey(AccessKeyType::eContent)) {
// If the eKeyPress event will be sent to a remote process, this
// process needs to wait reply from the remote process for checking if
// preceding eKeyDown event is consumed. If preceding eKeyDown event
// is consumed in the remote process, TabChild won't send the event
// back to this process. So, only when this process receives a reply
// eKeyPress event in TabParent, we should handle accesskey in this
// process.
if (IsRemoteTarget(GetFocusedContent())) {
// However, if there is no accesskey target for the key combination,
// we don't need to wait reply from the remote process. Otherwise,
// Mark the event as waiting reply from remote process and stop
// propagation in this process.
if (CheckIfEventMatchesAccessKey(keyEvent, aPresContext)) {
keyEvent->StopPropagation();
keyEvent->MarkAsWaitingReplyFromRemoteProcess();
}
}
// If the event target is in this process, we can handle accesskey now
// since if preceding eKeyDown event was consumed, eKeyPress event
// won't be dispatched by widget. So, coming eKeyPress event means
// that the preceding eKeyDown event wasn't consumed in this case.
else {
AutoTArray<uint32_t, 10> accessCharCodes;
keyEvent->GetAccessKeyCandidates(accessCharCodes);
if (HandleAccessKey(keyEvent, aPresContext, accessCharCodes)) {
*aStatus = nsEventStatus_eConsumeNoDefault;
}
}
}
}
// then fall through...
MOZ_FALLTHROUGH;
case eKeyDown:
case eKeyUp:
{
nsIContent* content = GetFocusedContent();
if (content)
mCurrentTargetContent = content;
// NOTE: Don't refer TextComposition::IsComposing() since UI Events
// defines that KeyboardEvent.isComposing is true when it's
// dispatched after compositionstart and compositionend.
// TextComposition::IsComposing() is false even before
// compositionend if there is no composing string.
// And also don't expose other document's composition state.
// A native IME context is typically shared by multiple documents.
// So, don't use GetTextCompositionFor(nsIWidget*) here.
RefPtr<TextComposition> composition =
IMEStateManager::GetTextCompositionFor(aPresContext);
aEvent->AsKeyboardEvent()->mIsComposing = !!composition;
// Widget may need to perform default action for specific keyboard
// event if it's not consumed. In this case, widget has already marked
// the event as "waiting reply from remote process". However, we need
// to reset it if the target (focused content) isn't in a remote process
// because PresShell needs to check if it's marked as so before
// dispatching events into the DOM tree.
if (aEvent->IsWaitingReplyFromRemoteProcess() &&
!aEvent->PropagationStopped() &&
!IsRemoteTarget(content)) {
aEvent->ResetWaitingReplyFromRemoteProcessState();
}
}
break;
case eWheel:
case eWheelOperationStart:
case eWheelOperationEnd:
{
NS_ASSERTION(aEvent->IsTrusted(),
"Untrusted wheel event shouldn't be here");
nsIContent* content = GetFocusedContent();
if (content) {
mCurrentTargetContent = content;
}
if (aEvent->mMessage != eWheel) {
break;
}
WidgetWheelEvent* wheelEvent = aEvent->AsWheelEvent();
WheelPrefs::GetInstance()->ApplyUserPrefsToDelta(wheelEvent);
// If we won't dispatch a DOM event for this event, nothing to do anymore.
if (!wheelEvent->IsAllowedToDispatchDOMEvent()) {
break;
}
// Init lineOrPageDelta values for line scroll events for some devices
// on some platforms which might dispatch wheel events which don't have
// lineOrPageDelta values. And also, if delta values are customized by
// prefs, this recomputes them.
DeltaAccumulator::GetInstance()->
InitLineOrPageDelta(aTargetFrame, this, wheelEvent);
}
break;
case eSetSelection:
IMEStateManager::HandleSelectionEvent(aPresContext, GetFocusedContent(),
aEvent->AsSelectionEvent());
break;
case eContentCommandCut:
case eContentCommandCopy:
case eContentCommandPaste:
case eContentCommandDelete:
case eContentCommandUndo:
case eContentCommandRedo:
case eContentCommandPasteTransferable:
case eContentCommandLookUpDictionary:
DoContentCommandEvent(aEvent->AsContentCommandEvent());
break;
case eContentCommandScroll:
DoContentCommandScrollEvent(aEvent->AsContentCommandEvent());
break;
case eCompositionStart:
if (aEvent->IsTrusted()) {
// If the event is trusted event, set the selected text to data of
// composition event.
WidgetCompositionEvent* compositionEvent = aEvent->AsCompositionEvent();
WidgetQueryContentEvent selectedText(true, eQuerySelectedText,
compositionEvent->mWidget);
HandleQueryContentEvent(&selectedText);
NS_ASSERTION(selectedText.mSucceeded, "Failed to get selected text");
compositionEvent->mData = selectedText.mReply.mString;
}
break;
default:
break;
}
return NS_OK;
}
void
EventStateManager::HandleQueryContentEvent(WidgetQueryContentEvent* aEvent)
{
switch (aEvent->mMessage) {
case eQuerySelectedText:
case eQueryTextContent:
case eQueryCaretRect:
case eQueryTextRect:
case eQueryEditorRect:
if (!IsTargetCrossProcess(aEvent)) {
break;
}
// Will not be handled locally, remote the event
GetCrossProcessTarget()->HandleQueryContentEvent(*aEvent);
return;
// Following events have not been supported in e10s mode yet.
case eQueryContentState:
case eQuerySelectionAsTransferable:
case eQueryCharacterAtPoint:
case eQueryDOMWidgetHittest:
case eQueryTextRectArray:
break;
default:
return;
}
// If there is an IMEContentObserver, we need to handle QueryContentEvent
// with it.
if (mIMEContentObserver) {
RefPtr<IMEContentObserver> contentObserver = mIMEContentObserver;
contentObserver->HandleQueryContentEvent(aEvent);
return;
}
ContentEventHandler handler(mPresContext);
handler.HandleQueryContentEvent(aEvent);
}
static AccessKeyType
GetAccessKeyTypeFor(nsISupports* aDocShell)
{
nsCOMPtr<nsIDocShellTreeItem> treeItem(do_QueryInterface(aDocShell));
if (!treeItem) {
return AccessKeyType::eNone;
}
switch (treeItem->ItemType()) {
case nsIDocShellTreeItem::typeChrome:
return AccessKeyType::eChrome;
case nsIDocShellTreeItem::typeContent:
return AccessKeyType::eContent;
default:
return AccessKeyType::eNone;
}
}
static bool
IsAccessKeyTarget(nsIContent* aContent, nsIFrame* aFrame, nsAString& aKey)
{
// Use GetAttr because we want Unicode case=insensitive matching
// XXXbz shouldn't this be case-sensitive, per spec?
nsString contentKey;
if (!aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, contentKey) ||
!contentKey.Equals(aKey, nsCaseInsensitiveStringComparator()))
return false;
nsCOMPtr<nsIDOMXULDocument> xulDoc =
do_QueryInterface(aContent->OwnerDoc());
if (!xulDoc && !aContent->IsXULElement())
return true;
// For XUL we do visibility checks.
if (!aFrame)
return false;
if (aFrame->IsFocusable())
return true;
if (!aFrame->IsVisibleConsideringAncestors())
return false;
// XUL controls can be activated.
nsCOMPtr<nsIDOMXULControlElement> control(do_QueryInterface(aContent));
if (control)
return true;
// HTML area, label and legend elements are never focusable, so
// we need to check for them explicitly before giving up.
if (aContent->IsAnyOfHTMLElements(nsGkAtoms::area,
nsGkAtoms::label,
nsGkAtoms::legend)) {
return true;
}
// XUL label elements are never focusable, so we need to check for them
// explicitly before giving up.
if (aContent->IsXULElement(nsGkAtoms::label)) {
return true;
}
return false;
}
bool
EventStateManager::CheckIfEventMatchesAccessKey(WidgetKeyboardEvent* aEvent,
nsPresContext* aPresContext)
{
AutoTArray<uint32_t, 10> accessCharCodes;
aEvent->GetAccessKeyCandidates(accessCharCodes);
return WalkESMTreeToHandleAccessKey(const_cast<WidgetKeyboardEvent*>(aEvent),
aPresContext, accessCharCodes,
nullptr, eAccessKeyProcessingNormal,
false);
}
bool
EventStateManager::LookForAccessKeyAndExecute(
nsTArray<uint32_t>& aAccessCharCodes,
bool aIsTrustedEvent,
bool aExecute)
{
int32_t count, start = -1;
nsIContent* focusedContent = GetFocusedContent();
if (focusedContent) {
start = mAccessKeys.IndexOf(focusedContent);
if (start == -1 && focusedContent->GetBindingParent())
start = mAccessKeys.IndexOf(focusedContent->GetBindingParent());
}
nsIContent *content;
nsIFrame *frame;
int32_t length = mAccessKeys.Count();
for (uint32_t i = 0; i < aAccessCharCodes.Length(); ++i) {
uint32_t ch = aAccessCharCodes[i];
nsAutoString accessKey;
AppendUCS4ToUTF16(ch, accessKey);
for (count = 1; count <= length; ++count) {
content = mAccessKeys[(start + count) % length];
frame = content->GetPrimaryFrame();
if (IsAccessKeyTarget(content, frame, accessKey)) {
if (!aExecute) {
return true;
}
bool shouldActivate = Prefs::KeyCausesActivation();
while (shouldActivate && ++count <= length) {
nsIContent *oc = mAccessKeys[(start + count) % length];
nsIFrame *of = oc->GetPrimaryFrame();
if (IsAccessKeyTarget(oc, of, accessKey))
shouldActivate = false;
}
bool focusChanged = false;
if (shouldActivate) {
focusChanged = content->PerformAccesskey(shouldActivate, aIsTrustedEvent);
} else {
nsIFocusManager* fm = nsFocusManager::GetFocusManager();
if (fm) {
nsCOMPtr<nsIDOMElement> element = do_QueryInterface(content);
fm->SetFocus(element, nsIFocusManager::FLAG_BYKEY);
focusChanged = true;
}
}
if (focusChanged && aIsTrustedEvent) {
// If this is a child process, inform the parent that we want the focus, but
// pass false since we don't want to change the window order.
nsIDocShell* docShell = mPresContext->GetDocShell();
nsCOMPtr<nsITabChild> child =
docShell ? docShell->GetTabChild() : nullptr;
if (child) {
child->SendRequestFocus(false);
}
}
return true;
}
}
}
return false;
}
// static
void
EventStateManager::GetAccessKeyLabelPrefix(Element* aElement, nsAString& aPrefix)
{
aPrefix.Truncate();
nsAutoString separator, modifierText;
nsContentUtils::GetModifierSeparatorText(separator);
AccessKeyType accessKeyType =
GetAccessKeyTypeFor(aElement->OwnerDoc()->GetDocShell());
if (accessKeyType == AccessKeyType::eNone) {
return;
}
Modifiers modifiers = WidgetKeyboardEvent::AccessKeyModifiers(accessKeyType);
if (modifiers == MODIFIER_NONE) {
return;
}
if (modifiers & MODIFIER_CONTROL) {
nsContentUtils::GetControlText(modifierText);
aPrefix.Append(modifierText + separator);
}
if (modifiers & MODIFIER_META) {
nsContentUtils::GetMetaText(modifierText);
aPrefix.Append(modifierText + separator);
}
if (modifiers & MODIFIER_OS) {
nsContentUtils::GetOSText(modifierText);
aPrefix.Append(modifierText + separator);
}
if (modifiers & MODIFIER_ALT) {
nsContentUtils::GetAltText(modifierText);
aPrefix.Append(modifierText + separator);
}
if (modifiers & MODIFIER_SHIFT) {
nsContentUtils::GetShiftText(modifierText);
aPrefix.Append(modifierText + separator);
}
}
struct MOZ_STACK_CLASS AccessKeyInfo
{
WidgetKeyboardEvent* event;
nsTArray<uint32_t>& charCodes;
AccessKeyInfo(WidgetKeyboardEvent* aEvent,
nsTArray<uint32_t>& aCharCodes)
: event(aEvent)
, charCodes(aCharCodes)
{
}
};
static bool
HandleAccessKeyInRemoteChild(TabParent* aTabParent, void* aArg)
{
AccessKeyInfo* accessKeyInfo = static_cast<AccessKeyInfo*>(aArg);
// Only forward accesskeys for the active tab.
bool active;
aTabParent->GetDocShellIsActive(&active);
if (active) {
// Even if there is no target for the accesskey in this process,
// the event may match with a content accesskey. If so, the keyboard
// event should be handled with reply event for preventing double action.
// (e.g., Alt+Shift+F on Windows may focus a content in remote and open
// "File" menu.)
accessKeyInfo->event->StopPropagation();
accessKeyInfo->event->MarkAsWaitingReplyFromRemoteProcess();
aTabParent->HandleAccessKey(*accessKeyInfo->event,
accessKeyInfo->charCodes);
return true;
}
return false;
}
bool
EventStateManager::WalkESMTreeToHandleAccessKey(
WidgetKeyboardEvent* aEvent,
nsPresContext* aPresContext,
nsTArray<uint32_t>& aAccessCharCodes,
nsIDocShellTreeItem* aBubbledFrom,
ProcessingAccessKeyState aAccessKeyState,
bool aExecute)
{
EnsureDocument(mPresContext);
nsCOMPtr<nsIDocShell> docShell = aPresContext->GetDocShell();
if (NS_WARN_IF(!docShell) || NS_WARN_IF(!mDocument)) {
return false;
}
AccessKeyType accessKeyType = GetAccessKeyTypeFor(docShell);
if (accessKeyType == AccessKeyType::eNone) {
return false;
}
// Alt or other accesskey modifier is down, we may need to do an accesskey.
if (mAccessKeys.Count() > 0 &&
aEvent->ModifiersMatchWithAccessKey(accessKeyType)) {
// Someone registered an accesskey. Find and activate it.
if (LookForAccessKeyAndExecute(aAccessCharCodes,
aEvent->IsTrusted(), aExecute)) {
return true;
}
}
int32_t childCount;
docShell->GetChildCount(&childCount);
for (int32_t counter = 0; counter < childCount; counter++) {
// Not processing the child which bubbles up the handling
nsCOMPtr<nsIDocShellTreeItem> subShellItem;
docShell->GetChildAt(counter, getter_AddRefs(subShellItem));
if (aAccessKeyState == eAccessKeyProcessingUp &&
subShellItem == aBubbledFrom) {
continue;
}
nsCOMPtr<nsIDocShell> subDS = do_QueryInterface(subShellItem);
if (subDS && IsShellVisible(subDS)) {
nsCOMPtr<nsIPresShell> subPS = subDS->GetPresShell();
// Docshells need not have a presshell (eg. display:none
// iframes, docshells in transition between documents, etc).
if (!subPS) {
// Oh, well. Just move on to the next child
continue;
}
nsPresContext *subPC = subPS->GetPresContext();
EventStateManager* esm =
static_cast<EventStateManager*>(subPC->EventStateManager());
if (esm &&
esm->WalkESMTreeToHandleAccessKey(aEvent, subPC, aAccessCharCodes,
nullptr, eAccessKeyProcessingDown,
aExecute)) {
return true;
}
}
}// if end . checking all sub docshell ends here.
// bubble up the process to the parent docshell if necessary
if (eAccessKeyProcessingDown != aAccessKeyState) {
nsCOMPtr<nsIDocShellTreeItem> parentShellItem;
docShell->GetParent(getter_AddRefs(parentShellItem));
nsCOMPtr<nsIDocShell> parentDS = do_QueryInterface(parentShellItem);
if (parentDS) {
nsCOMPtr<nsIPresShell> parentPS = parentDS->GetPresShell();
NS_ASSERTION(parentPS, "Our PresShell exists but the parent's does not?");
nsPresContext *parentPC = parentPS->GetPresContext();
NS_ASSERTION(parentPC, "PresShell without PresContext");
EventStateManager* esm =
static_cast<EventStateManager*>(parentPC->EventStateManager());
if (esm &&
esm->WalkESMTreeToHandleAccessKey(aEvent, parentPC, aAccessCharCodes,
docShell, eAccessKeyProcessingDown,
aExecute)) {
return true;
}
}
}// if end. bubble up process
// If the content access key modifier is pressed, try remote children
if (aExecute &&
aEvent->ModifiersMatchWithAccessKey(AccessKeyType::eContent) &&
mDocument && mDocument->GetWindow()) {
// If the focus is currently on a node with a TabParent, the key event
// should've gotten forwarded to the child process and HandleAccessKey
// called from there.
if (TabParent::GetFrom(GetFocusedContent())) {
// If access key may be only in remote contents, this method won't handle
// access key synchronously. In this case, only reply event should reach
// here.
MOZ_ASSERT(aEvent->IsHandledInRemoteProcess() ||
!aEvent->IsWaitingReplyFromRemoteProcess());
}
// If focus is somewhere else, then we need to check the remote children.
// However, if the event has already been handled in a remote process,
// then, focus is moved from the remote process after posting the event.
// In such case, we shouldn't retry to handle access keys in remote
// processes.
else if (!aEvent->IsHandledInRemoteProcess()) {
AccessKeyInfo accessKeyInfo(aEvent, aAccessCharCodes);
nsContentUtils::CallOnAllRemoteChildren(mDocument->GetWindow(),
HandleAccessKeyInRemoteChild,
&accessKeyInfo);
}
}
return false;
}// end of HandleAccessKey
void
EventStateManager::DispatchCrossProcessEvent(WidgetEvent* aEvent,
nsFrameLoader* aFrameLoader,
nsEventStatus *aStatus)
{
TabParent* remote = TabParent::GetFrom(aFrameLoader);
if (!remote) {
return;
}
switch (aEvent->mClass) {
case eMouseEventClass: {
remote->SendRealMouseEvent(*aEvent->AsMouseEvent());
return;
}
case eKeyboardEventClass: {
remote->SendRealKeyEvent(*aEvent->AsKeyboardEvent());
return;
}
case eWheelEventClass: {
remote->SendMouseWheelEvent(*aEvent->AsWheelEvent());
return;
}
case eTouchEventClass: {
// Let the child process synthesize a mouse event if needed, and
// ensure we don't synthesize one in this process.
*aStatus = nsEventStatus_eConsumeNoDefault;
remote->SendRealTouchEvent(*aEvent->AsTouchEvent());
return;
}
case eDragEventClass: {
if (remote->Manager()->IsContentParent()) {
remote->Manager()->AsContentParent()->MaybeInvokeDragSession(remote);
}
nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession();
uint32_t dropEffect = nsIDragService::DRAGDROP_ACTION_NONE;
uint32_t action = nsIDragService::DRAGDROP_ACTION_NONE;
if (dragSession) {
dragSession->DragEventDispatchedToChildProcess();
dragSession->GetDragAction(&action);
nsCOMPtr<nsIDOMDataTransfer> initialDataTransfer;
dragSession->GetDataTransfer(getter_AddRefs(initialDataTransfer));
if (initialDataTransfer) {
initialDataTransfer->GetDropEffectInt(&dropEffect);
}
}
remote->SendRealDragEvent(*aEvent->AsDragEvent(), action, dropEffect);
return;
}
case ePluginEventClass: {
*aStatus = nsEventStatus_eConsumeNoDefault;
remote->SendPluginEvent(*aEvent->AsPluginEvent());
return;
}
default: {
MOZ_CRASH("Attempt to send non-whitelisted event?");
}
}
}
bool
EventStateManager::IsRemoteTarget(nsIContent* target)
{
return !!TabParent::GetFrom(target);
}
bool
EventStateManager::HandleCrossProcessEvent(WidgetEvent* aEvent,
nsEventStatus *aStatus) {
if (*aStatus == nsEventStatus_eConsumeNoDefault ||
!aEvent->CanBeSentToRemoteProcess()) {
return false;
}
MOZ_ASSERT(!aEvent->HasBeenPostedToRemoteProcess(),
"Why do we need to post same event to remote processes again?");
// Collect the remote event targets we're going to forward this
// event to.
//
// NB: the elements of |targets| must be unique, for correctness.
AutoTArray<nsCOMPtr<nsIContent>, 1> targets;
if (aEvent->mClass != eTouchEventClass || aEvent->mMessage == eTouchStart) {
// If this event only has one target, and it's remote, add it to
// the array.
nsIFrame* frame =
aEvent->mMessage == eDragExit ? sLastDragOverFrame.GetFrame() : GetEventTarget();
nsIContent* target = frame ? frame->GetContent() : nullptr;
if (IsRemoteTarget(target)) {
targets.AppendElement(target);
}
} else {
// This is a touch event with possibly multiple touch points.
// Each touch point may have its own target. So iterate through
// all of them and collect the unique set of targets for event
// forwarding.
//
// This loop is similar to the one used in
// PresShell::DispatchTouchEvent().
const WidgetTouchEvent::TouchArray& touches =
aEvent->AsTouchEvent()->mTouches;
for (uint32_t i = 0; i < touches.Length(); ++i) {
Touch* touch = touches[i];
// NB: the |mChanged| check is an optimization, subprocesses can
// compute this for themselves. If the touch hasn't changed, we
// may be able to avoid forwarding the event entirely (which is
// not free).
if (!touch || !touch->mChanged) {
continue;
}
nsCOMPtr<EventTarget> targetPtr = touch->mTarget;
if (!targetPtr) {
continue;
}
nsCOMPtr<nsIContent> target = do_QueryInterface(targetPtr);
if (IsRemoteTarget(target) && !targets.Contains(target)) {
targets.AppendElement(target);
}
}
}
if (targets.Length() == 0) {
return false;
}
// Look up the frame loader for all the remote targets we found, and
// then dispatch the event to the remote content they represent.
for (uint32_t i = 0; i < targets.Length(); ++i) {
nsIContent* target = targets[i];
nsCOMPtr<nsIFrameLoaderOwner> loaderOwner = do_QueryInterface(target);
if (!loaderOwner) {
continue;
}
RefPtr<nsFrameLoader> frameLoader = loaderOwner->GetFrameLoader();
if (!frameLoader) {
continue;
}
uint32_t eventMode;
frameLoader->GetEventMode(&eventMode);
if (eventMode == nsIFrameLoader::EVENT_MODE_DONT_FORWARD_TO_CHILD) {
continue;
}
DispatchCrossProcessEvent(aEvent, frameLoader, aStatus);
}
return aEvent->HasBeenPostedToRemoteProcess();
}
//
// CreateClickHoldTimer
//
// Fire off a timer for determining if the user wants click-hold. This timer
// is a one-shot that will be cancelled when the user moves enough to fire
// a drag.
//
void
EventStateManager::CreateClickHoldTimer(nsPresContext* inPresContext,
nsIFrame* inDownFrame,
WidgetGUIEvent* inMouseDownEvent)
{
if (!inMouseDownEvent->IsTrusted() ||
IsRemoteTarget(mGestureDownContent) ||
sIsPointerLocked) {
return;
}
// just to be anal (er, safe)
if (mClickHoldTimer) {
mClickHoldTimer->Cancel();
mClickHoldTimer = nullptr;
}
// if content clicked on has a popup, don't even start the timer
// since we'll end up conflicting and both will show.
if (mGestureDownContent) {
// check for the |popup| attribute
if (nsContentUtils::HasNonEmptyAttr(mGestureDownContent, kNameSpaceID_None,
nsGkAtoms::popup))
return;
// check for a <menubutton> like bookmarks
if (mGestureDownContent->IsXULElement(nsGkAtoms::menubutton))
return;
}
mClickHoldTimer = do_CreateInstance("@mozilla.org/timer;1");
if (mClickHoldTimer) {
int32_t clickHoldDelay =
Preferences::GetInt("ui.click_hold_context_menus.delay", 500);
mClickHoldTimer->SetTarget(SystemGroup::EventTargetFor(TaskCategory::Other));
mClickHoldTimer->InitWithNamedFuncCallback(
sClickHoldCallback,
this,
clickHoldDelay,
nsITimer::TYPE_ONE_SHOT,
"EventStateManager::CreateClickHoldTimer");
}
} // CreateClickHoldTimer
//
// KillClickHoldTimer
//
// Stop the timer that would show the context menu dead in its tracks
//
void
EventStateManager::KillClickHoldTimer()
{
if (mClickHoldTimer) {
mClickHoldTimer->Cancel();
mClickHoldTimer = nullptr;
}
}
//
// sClickHoldCallback
//
// This fires after the mouse has been down for a certain length of time.
//
void
EventStateManager::sClickHoldCallback(nsITimer* aTimer, void* aESM)
{
RefPtr<EventStateManager> self = static_cast<EventStateManager*>(aESM);
if (self) {
self->FireContextClick();
}
// NOTE: |aTimer| and |self->mAutoHideTimer| are invalid after calling ClosePopup();
} // sAutoHideCallback
//
// FireContextClick
//
// If we're this far, our timer has fired, which means the mouse has been down
// for a certain period of time and has not moved enough to generate a dragGesture.
// We can be certain the user wants a context-click at this stage, so generate
// a dom event and fire it in.
//
// After the event fires, check if PreventDefault() has been set on the event which
// means that someone either ate the event or put up a context menu. This is our cue
// to stop tracking the drag gesture. If we always did this, draggable items w/out
// a context menu wouldn't be draggable after a certain length of time, which is
// _not_ what we want.
//
void
EventStateManager::FireContextClick()
{
if (!mGestureDownContent || !mPresContext || sIsPointerLocked) {
return;
}
#ifdef XP_MACOSX
// Hack to ensure that we don't show a context menu when the user
// let go of the mouse after a long cpu-hogging operation prevented
// us from handling any OS events. See bug 117589.
if (!CGEventSourceButtonState(kCGEventSourceStateCombinedSessionState, kCGMouseButtonLeft))
return;
#endif
nsEventStatus status = nsEventStatus_eIgnore;
// Dispatch to the DOM. We have to fake out the ESM and tell it that the
// current target frame is actually where the mouseDown occurred, otherwise it
// will use the frame the mouse is currently over which may or may not be
// the same. (Note: saari and I have decided that we don't have to reset |mCurrentTarget|
// when we're through because no one else is doing anything more with this
// event and it will get reset on the very next event to the correct frame).
mCurrentTarget = mPresContext->GetPrimaryFrameFor(mGestureDownContent);
// make sure the widget sticks around
nsCOMPtr<nsIWidget> targetWidget;
if (mCurrentTarget && (targetWidget = mCurrentTarget->GetNearestWidget())) {
NS_ASSERTION(mPresContext == mCurrentTarget->PresContext(),
"a prescontext returned a primary frame that didn't belong to it?");
// before dispatching, check that we're not on something that
// doesn't get a context menu
bool allowedToDispatch = true;
if (mGestureDownContent->IsAnyOfXULElements(nsGkAtoms::scrollbar,
nsGkAtoms::scrollbarbutton,
nsGkAtoms::button)) {
allowedToDispatch = false;
} else if (mGestureDownContent->IsXULElement(nsGkAtoms::toolbarbutton)) {
// a <toolbarbutton> that has the container attribute set
// will already have its own dropdown.
if (nsContentUtils::HasNonEmptyAttr(mGestureDownContent,
kNameSpaceID_None, nsGkAtoms::container)) {
allowedToDispatch = false;
} else {
// If the toolbar button has an open menu, don't attempt to open
// a second menu
if (mGestureDownContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::open,
nsGkAtoms::_true, eCaseMatters)) {
allowedToDispatch = false;
}
}
}
else if (mGestureDownContent->IsHTMLElement()) {
nsCOMPtr<nsIFormControl> formCtrl(do_QueryInterface(mGestureDownContent));
if (formCtrl) {
allowedToDispatch = formCtrl->IsTextOrNumberControl(/*aExcludePassword*/ false) ||
formCtrl->ControlType() == NS_FORM_INPUT_FILE;
}
else if (mGestureDownContent->IsAnyOfHTMLElements(nsGkAtoms::embed,
nsGkAtoms::object,
nsGkAtoms::label)) {
allowedToDispatch = false;
}
}
if (allowedToDispatch) {
// init the event while mCurrentTarget is still good
WidgetMouseEvent event(true, eContextMenu, targetWidget,
WidgetMouseEvent::eReal);
event.mClickCount = 1;
FillInEventFromGestureDown(&event);
// stop selection tracking, we're in control now
if (mCurrentTarget)
{
RefPtr<nsFrameSelection> frameSel =
mCurrentTarget->GetFrameSelection();
if (frameSel && frameSel->GetDragState()) {
// note that this can cause selection changed events to fire if we're in
// a text field, which will null out mCurrentTarget
frameSel->SetDragState(false);
}
}
nsIDocument* doc = mGestureDownContent->GetComposedDoc();
AutoHandlingUserInputStatePusher userInpStatePusher(true, &event, doc);
// dispatch to DOM
EventDispatcher::Dispatch(mGestureDownContent, mPresContext, &event,
nullptr, &status);
// We don't need to dispatch to frame handling because no frames
// watch eContextMenu except for nsMenuFrame and that's only for
// dismissal. That's just as well since we don't really know
// which frame to send it to.
}
}
// now check if the event has been handled. If so, stop tracking a drag
if (status == nsEventStatus_eConsumeNoDefault) {
StopTrackingDragGesture();
}
KillClickHoldTimer();
} // FireContextClick
//
// BeginTrackingDragGesture
//
// Record that the mouse has gone down and that we should move to TRACKING state
// of d&d gesture tracker.
//
// We also use this to track click-hold context menus. When the mouse goes down,
// fire off a short timer. If the timer goes off and we have yet to fire the
// drag gesture (ie, the mouse hasn't moved a certain distance), then we can
// assume the user wants a click-hold, so fire a context-click event. We only
// want to cancel the drag gesture if the context-click event is handled.
//
void
EventStateManager::BeginTrackingDragGesture(nsPresContext* aPresContext,
WidgetMouseEvent* inDownEvent,
nsIFrame* inDownFrame)
{
if (!inDownEvent->mWidget) {
return;
}
// Note that |inDownEvent| could be either a mouse down event or a
// synthesized mouse move event.
mGestureDownPoint =
inDownEvent->mRefPoint + inDownEvent->mWidget->WidgetToScreenOffset();
if (inDownFrame) {
inDownFrame->GetContentForEvent(inDownEvent,
getter_AddRefs(mGestureDownContent));
mGestureDownFrameOwner = inDownFrame->GetContent();
if (!mGestureDownFrameOwner) {
mGestureDownFrameOwner = mGestureDownContent;
}
}
mGestureModifiers = inDownEvent->mModifiers;
mGestureDownButtons = inDownEvent->buttons;
if (inDownEvent->mMessage != eMouseTouchDrag && Prefs::ClickHoldContextMenu()) {
// fire off a timer to track click-hold
CreateClickHoldTimer(aPresContext, inDownFrame, inDownEvent);
}
}
void
EventStateManager::BeginTrackingRemoteDragGesture(nsIContent* aContent)
{
mGestureDownContent = aContent;
mGestureDownFrameOwner = aContent;
}
//
// StopTrackingDragGesture
//
// Record that the mouse has gone back up so that we should leave the TRACKING
// state of d&d gesture tracker and return to the START state.
//
void
EventStateManager::StopTrackingDragGesture()
{
mGestureDownContent = nullptr;
mGestureDownFrameOwner = nullptr;
}
void
EventStateManager::FillInEventFromGestureDown(WidgetMouseEvent* aEvent)
{
NS_ASSERTION(aEvent->mWidget == mCurrentTarget->GetNearestWidget(),
"Incorrect widget in event");
// Set the coordinates in the new event to the coordinates of
// the old event, adjusted for the fact that the widget might be
// different
aEvent->mRefPoint =
mGestureDownPoint - aEvent->mWidget->WidgetToScreenOffset();
aEvent->mModifiers = mGestureModifiers;
aEvent->buttons = mGestureDownButtons;
}
//
// GenerateDragGesture
//
// If we're in the TRACKING state of the d&d gesture tracker, check the current position
// of the mouse in relation to the old one. If we've moved a sufficient amount from
// the mouse down, then fire off a drag gesture event.
void
EventStateManager::GenerateDragGesture(nsPresContext* aPresContext,
WidgetInputEvent* aEvent)
{
NS_ASSERTION(aPresContext, "This shouldn't happen.");
if (IsTrackingDragGesture()) {
mCurrentTarget = mGestureDownFrameOwner->GetPrimaryFrame();
if (!mCurrentTarget || !mCurrentTarget->GetNearestWidget()) {
StopTrackingDragGesture();
return;
}
// Check if selection is tracking drag gestures, if so
// don't interfere!
if (mCurrentTarget)
{
RefPtr<nsFrameSelection> frameSel = mCurrentTarget->GetFrameSelection();
if (frameSel && frameSel->GetDragState()) {
StopTrackingDragGesture();
return;
}
}
// If non-native code is capturing the mouse don't start a drag.
if (nsIPresShell::IsMouseCapturePreventingDrag()) {
StopTrackingDragGesture();
return;
}
static int32_t pixelThresholdX = 0;
static int32_t pixelThresholdY = 0;
if (!pixelThresholdX) {
pixelThresholdX =
LookAndFeel::GetInt(LookAndFeel::eIntID_DragThresholdX, 0);
pixelThresholdY =
LookAndFeel::GetInt(LookAndFeel::eIntID_DragThresholdY, 0);
if (!pixelThresholdX)
pixelThresholdX = 5;
if (!pixelThresholdY)
pixelThresholdY = 5;
}
// fire drag gesture if mouse has moved enough
LayoutDeviceIntPoint pt = aEvent->mWidget->WidgetToScreenOffset() +
(aEvent->AsTouchEvent() ? aEvent->AsTouchEvent()->mTouches[0]->mRefPoint
: aEvent->mRefPoint);
LayoutDeviceIntPoint distance = pt - mGestureDownPoint;
if (Abs(distance.x) > AssertedCast<uint32_t>(pixelThresholdX) ||
Abs(distance.y) > AssertedCast<uint32_t>(pixelThresholdY)) {
if (Prefs::ClickHoldContextMenu()) {
// stop the click-hold before we fire off the drag gesture, in case
// it takes a long time
KillClickHoldTimer();
}
nsCOMPtr<nsIDocShell> docshell = aPresContext->GetDocShell();
if (!docshell) {
return;
}
nsCOMPtr<nsPIDOMWindowOuter> window = docshell->GetWindow();
if (!window)
return;
RefPtr<DataTransfer> dataTransfer =
new DataTransfer(window, eDragStart, false, -1);
nsCOMPtr<nsISelection> selection;
nsCOMPtr<nsIContent> eventContent, targetContent;
mCurrentTarget->GetContentForEvent(aEvent, getter_AddRefs(eventContent));
if (eventContent)
DetermineDragTargetAndDefaultData(window, eventContent, dataTransfer,
getter_AddRefs(selection),
getter_AddRefs(targetContent));
// Stop tracking the drag gesture now. This should stop us from
// reentering GenerateDragGesture inside DOM event processing.
StopTrackingDragGesture();
if (!targetContent)
return;
// Use our targetContent, now that we've determined it, as the
// parent object of the DataTransfer.
dataTransfer->SetParentObject(targetContent);
sLastDragOverFrame = nullptr;
nsCOMPtr<nsIWidget> widget = mCurrentTarget->GetNearestWidget();
// get the widget from the target frame
WidgetDragEvent startEvent(aEvent->IsTrusted(), eDragStart, widget);
FillInEventFromGestureDown(&startEvent);
startEvent.mDataTransfer = dataTransfer;
if (aEvent->AsMouseEvent()) {
startEvent.inputSource = aEvent->AsMouseEvent()->inputSource;
} else if (aEvent->AsTouchEvent()) {
startEvent.inputSource = nsIDOMMouseEvent::MOZ_SOURCE_TOUCH;
} else {
MOZ_ASSERT(false);
}
// Dispatch to the DOM. By setting mCurrentTarget we are faking
// out the ESM and telling it that the current target frame is
// actually where the mouseDown occurred, otherwise it will use
// the frame the mouse is currently over which may or may not be
// the same. (Note: saari and I have decided that we don't have
// to reset |mCurrentTarget| when we're through because no one
// else is doing anything more with this event and it will get
// reset on the very next event to the correct frame).
// Hold onto old target content through the event and reset after.
nsCOMPtr<nsIContent> targetBeforeEvent = mCurrentTargetContent;
// Set the current target to the content for the mouse down
mCurrentTargetContent = targetContent;
// Dispatch the dragstart event to the DOM.
nsEventStatus status = nsEventStatus_eIgnore;
EventDispatcher::Dispatch(targetContent, aPresContext, &startEvent,
nullptr, &status);
WidgetDragEvent* event = &startEvent;
nsCOMPtr<nsIObserverService> observerService =
mozilla::services::GetObserverService();
// Emit observer event to allow addons to modify the DataTransfer object.
if (observerService) {
observerService->NotifyObservers(dataTransfer,
"on-datatransfer-available",
nullptr);
}
// now that the dataTransfer has been updated in the dragstart and
// draggesture events, make it read only so that the data doesn't
// change during the drag.
dataTransfer->SetReadOnly();
if (status != nsEventStatus_eConsumeNoDefault) {
bool dragStarted = DoDefaultDragStart(aPresContext, event, dataTransfer,
targetContent, selection);
if (dragStarted) {
sActiveESM = nullptr;
aEvent->StopPropagation();
}
}
// Reset mCurretTargetContent to what it was
mCurrentTargetContent = targetBeforeEvent;
}
// Now flush all pending notifications, for better responsiveness
// while dragging.
FlushPendingEvents(aPresContext);
}
} // GenerateDragGesture
void
EventStateManager::DetermineDragTargetAndDefaultData(nsPIDOMWindowOuter* aWindow,
nsIContent* aSelectionTarget,
DataTransfer* aDataTransfer,
nsISelection** aSelection,
nsIContent** aTargetNode)
{
*aTargetNode = nullptr;
// GetDragData determines if a selection, link or image in the content
// should be dragged, and places the data associated with the drag in the
// data transfer.
// mGestureDownContent is the node where the mousedown event for the drag
// occurred, and aSelectionTarget is the node to use when a selection is used
bool canDrag;
nsCOMPtr<nsIContent> dragDataNode;
bool wasAlt = (mGestureModifiers & MODIFIER_ALT) != 0;
nsresult rv = nsContentAreaDragDrop::GetDragData(aWindow, mGestureDownContent,
aSelectionTarget, wasAlt,
aDataTransfer, &canDrag, aSelection,
getter_AddRefs(dragDataNode));
if (NS_FAILED(rv) || !canDrag)
return;
// if GetDragData returned a node, use that as the node being dragged.
// Otherwise, if a selection is being dragged, use the node within the
// selection that was dragged. Otherwise, just use the mousedown target.
nsIContent* dragContent = mGestureDownContent;
if (dragDataNode)
dragContent = dragDataNode;
else if (*aSelection)
dragContent = aSelectionTarget;
nsIContent* originalDragContent = dragContent;
// If a selection isn't being dragged, look for an ancestor with the
// draggable property set. If one is found, use that as the target of the
// drag instead of the node that was clicked on. If a draggable node wasn't
// found, just use the clicked node.
if (!*aSelection) {
while (dragContent) {
nsCOMPtr<nsIDOMHTMLElement> htmlElement = do_QueryInterface(dragContent);
if (htmlElement) {
bool draggable = false;
htmlElement->GetDraggable(&draggable);
if (draggable)
break;
}
else {
nsCOMPtr<nsIDOMXULElement> xulElement = do_QueryInterface(dragContent);
if (xulElement) {
// All XUL elements are draggable, so if a XUL element is
// encountered, stop looking for draggable nodes and just use the
// original clicked node instead.
// XXXndeakin
// In the future, we will want to improve this so that XUL has a
// better way to specify whether something is draggable than just
// on/off.
dragContent = mGestureDownContent;
break;
}
// otherwise, it's not an HTML or XUL element, so just keep looking
}
dragContent = dragContent->GetParent();
}
}
// if no node in the hierarchy was found to drag, but the GetDragData method
// returned a node, use that returned node. Otherwise, nothing is draggable.
if (!dragContent && dragDataNode)
dragContent = dragDataNode;
if (dragContent) {
// if an ancestor node was used instead, clear the drag data
// XXXndeakin rework this a bit. Find a way to just not call GetDragData if we don't need to.
if (dragContent != originalDragContent)
aDataTransfer->ClearAll();
*aTargetNode = dragContent;
NS_ADDREF(*aTargetNode);
}
}
bool
EventStateManager::DoDefaultDragStart(nsPresContext* aPresContext,
WidgetDragEvent* aDragEvent,
DataTransfer* aDataTransfer,
nsIContent* aDragTarget,
nsISelection* aSelection)
{
nsCOMPtr<nsIDragService> dragService =
do_GetService("@mozilla.org/widget/dragservice;1");
if (!dragService)
return false;
// Default handling for the dragstart event.
//
// First, check if a drag session already exists. This means that the drag
// service was called directly within a draggesture handler. In this case,
// don't do anything more, as it is assumed that the handler is managing
// drag and drop manually. Make sure to return true to indicate that a drag
// began.
nsCOMPtr<nsIDragSession> dragSession;
dragService->GetCurrentSession(getter_AddRefs(dragSession));
if (dragSession)
return true;
// No drag session is currently active, so check if a handler added
// any items to be dragged. If not, there isn't anything to drag.
uint32_t count = 0;
if (aDataTransfer)
aDataTransfer->GetMozItemCount(&count);
if (!count)
return false;
// Get the target being dragged, which may not be the same as the
// target of the mouse event. If one wasn't set in the
// aDataTransfer during the event handler, just use the original
// target instead.
nsCOMPtr<nsIContent> dragTarget = aDataTransfer->GetDragTarget();
if (!dragTarget) {
dragTarget = aDragTarget;
if (!dragTarget)
return false;
}
// check which drag effect should initially be used. If the effect was not
// set, just use all actions, otherwise Windows won't allow a drop.
uint32_t action;
aDataTransfer->GetEffectAllowedInt(&action);
if (action == nsIDragService::DRAGDROP_ACTION_UNINITIALIZED)
action = nsIDragService::DRAGDROP_ACTION_COPY |
nsIDragService::DRAGDROP_ACTION_MOVE |
nsIDragService::DRAGDROP_ACTION_LINK;
// get any custom drag image that was set
int32_t imageX, imageY;
Element* dragImage = aDataTransfer->GetDragImage(&imageX, &imageY);
nsCOMPtr<nsIArray> transArray =
aDataTransfer->GetTransferables(dragTarget->AsDOMNode());
if (!transArray)
return false;
// XXXndeakin don't really want to create a new drag DOM event
// here, but we need something to pass to the InvokeDragSession
// methods.
RefPtr<DragEvent> event =
NS_NewDOMDragEvent(dragTarget, aPresContext, aDragEvent);
// Use InvokeDragSessionWithSelection if a selection is being dragged,
// such that the image can be generated from the selected text. However,
// use InvokeDragSessionWithImage if a custom image was set or something
// other than a selection is being dragged.
if (!dragImage && aSelection) {
dragService->InvokeDragSessionWithSelection(aSelection, transArray,
action, event, aDataTransfer);
}
else {
// if dragging within a XUL tree and no custom drag image was
// set, the region argument to InvokeDragSessionWithImage needs
// to be set to the area encompassing the selected rows of the
// tree to ensure that the drag feedback gets clipped to those
// rows. For other content, region should be null.
nsCOMPtr<nsIScriptableRegion> region;
#ifdef MOZ_XUL
if (dragTarget && !dragImage) {
if (dragTarget->NodeInfo()->Equals(nsGkAtoms::treechildren,
kNameSpaceID_XUL)) {
nsTreeBodyFrame* treeBody =
do_QueryFrame(dragTarget->GetPrimaryFrame());
if (treeBody) {
treeBody->GetSelectionRegion(getter_AddRefs(region));
}
}
}
#endif
dragService->InvokeDragSessionWithImage(dragTarget->AsDOMNode(), transArray,
region, action,
dragImage ? dragImage->AsDOMNode() :
nullptr,
imageX, imageY, event,
aDataTransfer);
}
return true;
}
nsresult
EventStateManager::GetContentViewer(nsIContentViewer** aCv)
{
*aCv = nullptr;
nsPIDOMWindowOuter* window = mDocument->GetWindow();
if (!window) return NS_ERROR_FAILURE;
nsCOMPtr<nsPIDOMWindowOuter> rootWindow = window->GetPrivateRoot();
if (!rootWindow) return NS_ERROR_FAILURE;
TabChild* tabChild = TabChild::GetFrom(rootWindow);
if (!tabChild) {
nsIFocusManager* fm = nsFocusManager::GetFocusManager();
if (!fm) return NS_ERROR_FAILURE;
nsCOMPtr<mozIDOMWindowProxy> activeWindow;
fm->GetActiveWindow(getter_AddRefs(activeWindow));
if (rootWindow != activeWindow) return NS_OK;
} else {
if (!tabChild->ParentIsActive()) return NS_OK;
}
nsCOMPtr<nsPIDOMWindowOuter> contentWindow = nsGlobalWindow::Cast(rootWindow)->GetContent();
if (!contentWindow) return NS_ERROR_FAILURE;
nsIDocument *doc = contentWindow->GetDoc();
if (!doc) return NS_ERROR_FAILURE;
nsCOMPtr<nsISupports> container = doc->GetContainer();
if (!container) return NS_ERROR_FAILURE;
nsCOMPtr<nsIDocShell> docshell = do_QueryInterface(container);
docshell->GetContentViewer(aCv);
if (!*aCv) return NS_ERROR_FAILURE;
return NS_OK;
}
nsresult
EventStateManager::ChangeTextSize(int32_t change)
{
nsCOMPtr<nsIContentViewer> cv;
nsresult rv = GetContentViewer(getter_AddRefs(cv));
NS_ENSURE_SUCCESS(rv, rv);
if (cv) {
float textzoom;
float zoomMin = ((float)Preferences::GetInt("zoom.minPercent", 50)) / 100;
float zoomMax = ((float)Preferences::GetInt("zoom.maxPercent", 300)) / 100;
cv->GetTextZoom(&textzoom);
textzoom += ((float)change) / 10;
if (textzoom < zoomMin)
textzoom = zoomMin;
else if (textzoom > zoomMax)
textzoom = zoomMax;
cv->SetTextZoom(textzoom);
}
return NS_OK;
}
nsresult
EventStateManager::ChangeFullZoom(int32_t change)
{
nsCOMPtr<nsIContentViewer> cv;
nsresult rv = GetContentViewer(getter_AddRefs(cv));
NS_ENSURE_SUCCESS(rv, rv);
if (cv) {
float fullzoom;
float zoomMin = ((float)Preferences::GetInt("zoom.minPercent", 50)) / 100;
float zoomMax = ((float)Preferences::GetInt("zoom.maxPercent", 300)) / 100;
cv->GetFullZoom(&fullzoom);
fullzoom += ((float)change) / 10;
if (fullzoom < zoomMin)
fullzoom = zoomMin;
else if (fullzoom > zoomMax)
fullzoom = zoomMax;
cv->SetFullZoom(fullzoom);
}
return NS_OK;
}
void
EventStateManager::DoScrollHistory(int32_t direction)
{
nsCOMPtr<nsISupports> pcContainer(mPresContext->GetContainerWeak());
if (pcContainer) {
nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(pcContainer));
if (webNav) {
// positive direction to go back one step, nonpositive to go forward
if (direction > 0)
webNav->GoBack();
else
webNav->GoForward();
}
}
}
void
EventStateManager::DoScrollZoom(nsIFrame* aTargetFrame,
int32_t adjustment)
{
// Exclude form controls and content in chrome docshells.
nsIContent *content = aTargetFrame->GetContent();
if (content &&
!content->IsNodeOfType(nsINode::eHTML_FORM_CONTROL) &&
!nsContentUtils::IsInChromeDocshell(content->OwnerDoc()))
{
// positive adjustment to decrease zoom, negative to increase
int32_t change = (adjustment > 0) ? -1 : 1;
EnsureDocument(mPresContext);
if (Preferences::GetBool("browser.zoom.full") || content->OwnerDoc()->IsSyntheticDocument()) {
ChangeFullZoom(change);
} else {
ChangeTextSize(change);
}
nsContentUtils::DispatchChromeEvent(mDocument, static_cast<nsIDocument*>(mDocument),
NS_LITERAL_STRING("ZoomChangeUsingMouseWheel"),
true, true);
}
}
static nsIFrame*
GetParentFrameToScroll(nsIFrame* aFrame)
{
if (!aFrame)
return nullptr;
if (aFrame->StyleDisplay()->mPosition == NS_STYLE_POSITION_FIXED &&
nsLayoutUtils::IsReallyFixedPos(aFrame))
return aFrame->PresContext()->GetPresShell()->GetRootScrollFrame();
return aFrame->GetParent();
}
/*static*/ bool
EventStateManager::CanVerticallyScrollFrameWithWheel(nsIFrame* aFrame)
{
nsIContent* c = aFrame->GetContent();
if (!c) {
return true;
}
nsCOMPtr<nsITextControlElement> ctrl =
do_QueryInterface(c->IsInAnonymousSubtree() ? c->GetBindingParent() : c);
if (ctrl && ctrl->IsSingleLineTextControl()) {
return false;
}
return true;
}
void
EventStateManager::DispatchLegacyMouseScrollEvents(nsIFrame* aTargetFrame,
WidgetWheelEvent* aEvent,
nsEventStatus* aStatus)
{
MOZ_ASSERT(aEvent);
MOZ_ASSERT(aStatus);
if (!aTargetFrame || *aStatus == nsEventStatus_eConsumeNoDefault) {
return;
}
// Ignore mouse wheel transaction for computing legacy mouse wheel
// events' delta value.
nsIFrame* scrollFrame =
ComputeScrollTarget(aTargetFrame, aEvent,
COMPUTE_LEGACY_MOUSE_SCROLL_EVENT_TARGET);
nsIScrollableFrame* scrollTarget = do_QueryFrame(scrollFrame);
nsPresContext* pc =
scrollFrame ? scrollFrame->PresContext() : aTargetFrame->PresContext();
// DOM event's delta vales are computed from CSS pixels.
nsSize scrollAmount = GetScrollAmount(pc, aEvent, scrollTarget);
nsIntSize scrollAmountInCSSPixels(
nsPresContext::AppUnitsToIntCSSPixels(scrollAmount.width),
nsPresContext::AppUnitsToIntCSSPixels(scrollAmount.height));
// XXX We don't deal with fractional amount in legacy event, though the
// default action handler (DoScrollText()) deals with it.
// If we implemented such strict computation, we would need additional
// accumulated delta values. It would made the code more complicated.
// And also it would computes different delta values from older version.
// It doesn't make sense to implement such code for legacy events and
// rare cases.
int32_t scrollDeltaX, scrollDeltaY, pixelDeltaX, pixelDeltaY;
switch (aEvent->mDeltaMode) {
case nsIDOMWheelEvent::DOM_DELTA_PAGE:
scrollDeltaX =
!aEvent->mLineOrPageDeltaX ? 0 :
(aEvent->mLineOrPageDeltaX > 0 ? nsIDOMUIEvent::SCROLL_PAGE_DOWN :
nsIDOMUIEvent::SCROLL_PAGE_UP);
scrollDeltaY =
!aEvent->mLineOrPageDeltaY ? 0 :
(aEvent->mLineOrPageDeltaY > 0 ? nsIDOMUIEvent::SCROLL_PAGE_DOWN :
nsIDOMUIEvent::SCROLL_PAGE_UP);
pixelDeltaX = RoundDown(aEvent->mDeltaX * scrollAmountInCSSPixels.width);
pixelDeltaY = RoundDown(aEvent->mDeltaY * scrollAmountInCSSPixels.height);
break;
case nsIDOMWheelEvent::DOM_DELTA_LINE:
scrollDeltaX = aEvent->mLineOrPageDeltaX;
scrollDeltaY = aEvent->mLineOrPageDeltaY;
pixelDeltaX = RoundDown(aEvent->mDeltaX * scrollAmountInCSSPixels.width);
pixelDeltaY = RoundDown(aEvent->mDeltaY * scrollAmountInCSSPixels.height);
break;
case nsIDOMWheelEvent::DOM_DELTA_PIXEL:
scrollDeltaX = aEvent->mLineOrPageDeltaX;
scrollDeltaY = aEvent->mLineOrPageDeltaY;
pixelDeltaX = RoundDown(aEvent->mDeltaX);
pixelDeltaY = RoundDown(aEvent->mDeltaY);
break;
default:
MOZ_CRASH("Invalid deltaMode value comes");
}
// Send the legacy events in following order:
// 1. Vertical scroll
// 2. Vertical pixel scroll (even if #1 isn't consumed)
// 3. Horizontal scroll (even if #1 and/or #2 are consumed)
// 4. Horizontal pixel scroll (even if #3 isn't consumed)
AutoWeakFrame targetFrame(aTargetFrame);
MOZ_ASSERT(*aStatus != nsEventStatus_eConsumeNoDefault &&
!aEvent->DefaultPrevented(),
"If you make legacy events dispatched for default prevented wheel "
"event, you need to initialize stateX and stateY");
EventState stateX, stateY;
if (scrollDeltaY) {
SendLineScrollEvent(aTargetFrame, aEvent, stateY,
scrollDeltaY, DELTA_DIRECTION_Y);
if (!targetFrame.IsAlive()) {
*aStatus = nsEventStatus_eConsumeNoDefault;
return;
}
}
if (pixelDeltaY) {
SendPixelScrollEvent(aTargetFrame, aEvent, stateY,
pixelDeltaY, DELTA_DIRECTION_Y);
if (!targetFrame.IsAlive()) {
*aStatus = nsEventStatus_eConsumeNoDefault;
return;
}
}
if (scrollDeltaX) {
SendLineScrollEvent(aTargetFrame, aEvent, stateX,
scrollDeltaX, DELTA_DIRECTION_X);
if (!targetFrame.IsAlive()) {
*aStatus = nsEventStatus_eConsumeNoDefault;
return;
}
}
if (pixelDeltaX) {
SendPixelScrollEvent(aTargetFrame, aEvent, stateX,
pixelDeltaX, DELTA_DIRECTION_X);
if (!targetFrame.IsAlive()) {
*aStatus = nsEventStatus_eConsumeNoDefault;
return;
}
}
if (stateY.mDefaultPrevented) {
*aStatus = nsEventStatus_eConsumeNoDefault;
aEvent->PreventDefault(!stateY.mDefaultPreventedByContent);
}
if (stateX.mDefaultPrevented) {
*aStatus = nsEventStatus_eConsumeNoDefault;
aEvent->PreventDefault(!stateX.mDefaultPreventedByContent);
}
}
void
EventStateManager::SendLineScrollEvent(nsIFrame* aTargetFrame,
WidgetWheelEvent* aEvent,
EventState& aState,
int32_t aDelta,
DeltaDirection aDeltaDirection)
{
nsCOMPtr<nsIContent> targetContent = aTargetFrame->GetContent();
if (!targetContent)
targetContent = GetFocusedContent();
if (!targetContent)
return;
while (targetContent->IsNodeOfType(nsINode::eTEXT)) {
targetContent = targetContent->GetParent();
}
WidgetMouseScrollEvent event(aEvent->IsTrusted(),
eLegacyMouseLineOrPageScroll, aEvent->mWidget);
event.mFlags.mDefaultPrevented = aState.mDefaultPrevented;
event.mFlags.mDefaultPreventedByContent = aState.mDefaultPreventedByContent;
event.mRefPoint = aEvent->mRefPoint;
event.mTime = aEvent->mTime;
event.mTimeStamp = aEvent->mTimeStamp;
event.mModifiers = aEvent->mModifiers;
event.buttons = aEvent->buttons;
event.mIsHorizontal = (aDeltaDirection == DELTA_DIRECTION_X);
event.mDelta = aDelta;
event.inputSource = aEvent->inputSource;
nsEventStatus status = nsEventStatus_eIgnore;
EventDispatcher::Dispatch(targetContent, aTargetFrame->PresContext(),
&event, nullptr, &status);
aState.mDefaultPrevented =
event.DefaultPrevented() || status == nsEventStatus_eConsumeNoDefault;
aState.mDefaultPreventedByContent = event.DefaultPreventedByContent();
}
void
EventStateManager::SendPixelScrollEvent(nsIFrame* aTargetFrame,
WidgetWheelEvent* aEvent,
EventState& aState,
int32_t aPixelDelta,
DeltaDirection aDeltaDirection)
{
nsCOMPtr<nsIContent> targetContent = aTargetFrame->GetContent();
if (!targetContent) {
targetContent = GetFocusedContent();
if (!targetContent)
return;
}
while (targetContent->IsNodeOfType(nsINode::eTEXT)) {
targetContent = targetContent->GetParent();
}
WidgetMouseScrollEvent event(aEvent->IsTrusted(),
eLegacyMousePixelScroll, aEvent->mWidget);
event.mFlags.mDefaultPrevented = aState.mDefaultPrevented;
event.mFlags.mDefaultPreventedByContent = aState.mDefaultPreventedByContent;
event.mRefPoint = aEvent->mRefPoint;
event.mTime = aEvent->mTime;
event.mTimeStamp = aEvent->mTimeStamp;
event.mModifiers = aEvent->mModifiers;
event.buttons = aEvent->buttons;
event.mIsHorizontal = (aDeltaDirection == DELTA_DIRECTION_X);
event.mDelta = aPixelDelta;
event.inputSource = aEvent->inputSource;
nsEventStatus status = nsEventStatus_eIgnore;
EventDispatcher::Dispatch(targetContent, aTargetFrame->PresContext(),
&event, nullptr, &status);
aState.mDefaultPrevented =
event.DefaultPrevented() || status == nsEventStatus_eConsumeNoDefault;
aState.mDefaultPreventedByContent = event.DefaultPreventedByContent();
}
nsIFrame*
EventStateManager::ComputeScrollTarget(nsIFrame* aTargetFrame,
WidgetWheelEvent* aEvent,
ComputeScrollTargetOptions aOptions)
{
return ComputeScrollTarget(aTargetFrame, aEvent->mDeltaX, aEvent->mDeltaY,
aEvent, aOptions);
}
// Overload ComputeScrollTarget method to allow passing "test" dx and dy when looking
// for which scrollbarmediators to activate when two finger down on trackpad
// and before any actual motion
nsIFrame*
EventStateManager::ComputeScrollTarget(nsIFrame* aTargetFrame,
double aDirectionX,
double aDirectionY,
WidgetWheelEvent* aEvent,
ComputeScrollTargetOptions aOptions)
{
if ((aOptions & INCLUDE_PLUGIN_AS_TARGET) &&
!WheelPrefs::WheelEventsEnabledOnPlugins()) {
aOptions = RemovePluginFromTarget(aOptions);
}
if (aOptions & PREFER_MOUSE_WHEEL_TRANSACTION) {
// If the user recently scrolled with the mousewheel, then they probably
// want to scroll the same view as before instead of the view under the
// cursor. WheelTransaction tracks the frame currently being
// scrolled with the mousewheel. We consider the transaction ended when the
// mouse moves more than "mousewheel.transaction.ignoremovedelay"
// milliseconds after the last scroll operation, or any time the mouse moves
// out of the frame, or when more than "mousewheel.transaction.timeout"
// milliseconds have passed after the last operation, even if the mouse
// hasn't moved.
nsIFrame* lastScrollFrame = WheelTransaction::GetTargetFrame();
if (lastScrollFrame) {
if (aOptions & INCLUDE_PLUGIN_AS_TARGET) {
nsPluginFrame* pluginFrame = do_QueryFrame(lastScrollFrame);
if (pluginFrame &&
pluginFrame->WantsToHandleWheelEventAsDefaultAction()) {
return lastScrollFrame;
}
}
nsIScrollableFrame* scrollableFrame =
lastScrollFrame->GetScrollTargetFrame();
if (scrollableFrame) {
nsIFrame* frameToScroll = do_QueryFrame(scrollableFrame);
MOZ_ASSERT(frameToScroll);
return frameToScroll;
}
}
}
// If the event doesn't cause scroll actually, we cannot find scroll target
// because we check if the event can cause scroll actually on each found
// scrollable frame.
if (!aDirectionX && !aDirectionY) {
return nullptr;
}
bool checkIfScrollableX =
aDirectionX && (aOptions & PREFER_ACTUAL_SCROLLABLE_TARGET_ALONG_X_AXIS);
bool checkIfScrollableY =
aDirectionY && (aOptions & PREFER_ACTUAL_SCROLLABLE_TARGET_ALONG_Y_AXIS);
nsIFrame* scrollFrame =
!(aOptions & START_FROM_PARENT) ? aTargetFrame :
GetParentFrameToScroll(aTargetFrame);
for (; scrollFrame; scrollFrame = GetParentFrameToScroll(scrollFrame)) {
// Check whether the frame wants to provide us with a scrollable view.
nsIScrollableFrame* scrollableFrame = scrollFrame->GetScrollTargetFrame();
if (!scrollableFrame) {
// If the frame is a plugin frame, then, the plugin content may handle
// wheel events. Only when the caller computes the scroll target for
// default action handling, we should assume the plugin frame as
// scrollable if the plugin wants to handle wheel events as default
// action.
if (aOptions & INCLUDE_PLUGIN_AS_TARGET) {
nsPluginFrame* pluginFrame = do_QueryFrame(scrollFrame);
if (pluginFrame &&
pluginFrame->WantsToHandleWheelEventAsDefaultAction()) {
return scrollFrame;
}
}
nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(scrollFrame);
if (menuPopupFrame) {
return nullptr;
}
continue;
}
nsIFrame* frameToScroll = do_QueryFrame(scrollableFrame);
MOZ_ASSERT(frameToScroll);
// Don't scroll vertically by mouse-wheel on a single-line text control.
if (checkIfScrollableY) {
if (!CanVerticallyScrollFrameWithWheel(scrollFrame)) {
continue;
}
}
if (!checkIfScrollableX && !checkIfScrollableY) {
return frameToScroll;
}
ScrollbarStyles ss = scrollableFrame->GetScrollbarStyles();
bool hiddenForV = (NS_STYLE_OVERFLOW_HIDDEN == ss.mVertical);
bool hiddenForH = (NS_STYLE_OVERFLOW_HIDDEN == ss.mHorizontal);
if ((hiddenForV && hiddenForH) ||
(checkIfScrollableY && !checkIfScrollableX && hiddenForV) ||
(checkIfScrollableX && !checkIfScrollableY && hiddenForH)) {
continue;
}
// For default action, we should climb up the tree if cannot scroll it
// by the event actually.
bool canScroll = WheelHandlingUtils::CanScrollOn(scrollableFrame,
aDirectionX, aDirectionY);
// Comboboxes need special care.
nsIComboboxControlFrame* comboBox = do_QueryFrame(scrollFrame);
if (comboBox) {
if (comboBox->IsDroppedDown()) {
// Don't propagate to parent when drop down menu is active.
return canScroll ? frameToScroll : nullptr;
}
// Always propagate when not dropped down (even if focused).
continue;
}
if (canScroll) {
return frameToScroll;
}
}
nsIFrame* newFrame = nsLayoutUtils::GetCrossDocParentFrame(
aTargetFrame->PresContext()->FrameManager()->GetRootFrame());
aOptions =
static_cast<ComputeScrollTargetOptions>(aOptions & ~START_FROM_PARENT);
return newFrame ? ComputeScrollTarget(newFrame, aEvent, aOptions) : nullptr;
}
nsSize
EventStateManager::GetScrollAmount(nsPresContext* aPresContext,
WidgetWheelEvent* aEvent,
nsIScrollableFrame* aScrollableFrame)
{
MOZ_ASSERT(aPresContext);
MOZ_ASSERT(aEvent);
bool isPage = (aEvent->mDeltaMode == nsIDOMWheelEvent::DOM_DELTA_PAGE);
if (aScrollableFrame) {
return isPage ? aScrollableFrame->GetPageScrollAmount() :
aScrollableFrame->GetLineScrollAmount();
}
// If there is no scrollable frame and page scrolling, use view port size.
if (isPage) {
return aPresContext->GetVisibleArea().Size();
}
// If there is no scrollable frame, we should use root frame's information.
nsIFrame* rootFrame = aPresContext->PresShell()->GetRootFrame();
if (!rootFrame) {
return nsSize(0, 0);
}
RefPtr<nsFontMetrics> fm =
nsLayoutUtils::GetInflatedFontMetricsForFrame(rootFrame);
NS_ENSURE_TRUE(fm, nsSize(0, 0));
return nsSize(fm->AveCharWidth(), fm->MaxHeight());
}
void
EventStateManager::DoScrollText(nsIScrollableFrame* aScrollableFrame,
WidgetWheelEvent* aEvent)
{
MOZ_ASSERT(aScrollableFrame);
MOZ_ASSERT(aEvent);
nsIFrame* scrollFrame = do_QueryFrame(aScrollableFrame);
MOZ_ASSERT(scrollFrame);
AutoWeakFrame scrollFrameWeak(scrollFrame);
if (!WheelTransaction::WillHandleDefaultAction(aEvent, scrollFrameWeak)) {
return;
}
// Default action's actual scroll amount should be computed from device
// pixels.
nsPresContext* pc = scrollFrame->PresContext();
nsSize scrollAmount = GetScrollAmount(pc, aEvent, aScrollableFrame);
nsIntSize scrollAmountInDevPixels(
pc->AppUnitsToDevPixels(scrollAmount.width),
pc->AppUnitsToDevPixels(scrollAmount.height));
nsIntPoint actualDevPixelScrollAmount =
DeltaAccumulator::GetInstance()->
ComputeScrollAmountForDefaultAction(aEvent, scrollAmountInDevPixels);
// Don't scroll around the axis whose overflow style is hidden.
ScrollbarStyles overflowStyle = aScrollableFrame->GetScrollbarStyles();
if (overflowStyle.mHorizontal == NS_STYLE_OVERFLOW_HIDDEN) {
actualDevPixelScrollAmount.x = 0;
}
if (overflowStyle.mVertical == NS_STYLE_OVERFLOW_HIDDEN) {
actualDevPixelScrollAmount.y = 0;
}
nsIScrollbarMediator::ScrollSnapMode snapMode = nsIScrollbarMediator::DISABLE_SNAP;
nsIAtom* origin = nullptr;
switch (aEvent->mDeltaMode) {
case nsIDOMWheelEvent::DOM_DELTA_LINE:
origin = nsGkAtoms::mouseWheel;
snapMode = nsIScrollableFrame::ENABLE_SNAP;
break;
case nsIDOMWheelEvent::DOM_DELTA_PAGE:
origin = nsGkAtoms::pages;
snapMode = nsIScrollableFrame::ENABLE_SNAP;
break;
case nsIDOMWheelEvent::DOM_DELTA_PIXEL:
origin = nsGkAtoms::pixels;
break;
default:
MOZ_CRASH("Invalid deltaMode value comes");
}
// We shouldn't scroll more one page at once except when over one page scroll
// is allowed for the event.
nsSize pageSize = aScrollableFrame->GetPageScrollAmount();
nsIntSize devPixelPageSize(pc->AppUnitsToDevPixels(pageSize.width),
pc->AppUnitsToDevPixels(pageSize.height));
if (!WheelPrefs::GetInstance()->IsOverOnePageScrollAllowedX(aEvent) &&
DeprecatedAbs(actualDevPixelScrollAmount.x) > devPixelPageSize.width) {
actualDevPixelScrollAmount.x =
(actualDevPixelScrollAmount.x >= 0) ? devPixelPageSize.width :
-devPixelPageSize.width;
}
if (!WheelPrefs::GetInstance()->IsOverOnePageScrollAllowedY(aEvent) &&
DeprecatedAbs(actualDevPixelScrollAmount.y) > devPixelPageSize.height) {
actualDevPixelScrollAmount.y =
(actualDevPixelScrollAmount.y >= 0) ? devPixelPageSize.height :
-devPixelPageSize.height;
}
bool isDeltaModePixel =
(aEvent->mDeltaMode == nsIDOMWheelEvent::DOM_DELTA_PIXEL);
nsIScrollableFrame::ScrollMode mode;
switch (aEvent->mScrollType) {
case WidgetWheelEvent::SCROLL_DEFAULT:
if (isDeltaModePixel) {
mode = nsIScrollableFrame::NORMAL;
} else if (aEvent->mFlags.mHandledByAPZ) {
mode = nsIScrollableFrame::SMOOTH_MSD;
} else {
mode = nsIScrollableFrame::SMOOTH;
}
break;
case WidgetWheelEvent::SCROLL_SYNCHRONOUSLY:
mode = nsIScrollableFrame::INSTANT;
break;
case WidgetWheelEvent::SCROLL_ASYNCHRONOUSELY:
mode = nsIScrollableFrame::NORMAL;
break;
case WidgetWheelEvent::SCROLL_SMOOTHLY:
mode = nsIScrollableFrame::SMOOTH;
break;
default:
MOZ_CRASH("Invalid mScrollType value comes");
}
nsIScrollableFrame::ScrollMomentum momentum =
aEvent->mIsMomentum ? nsIScrollableFrame::SYNTHESIZED_MOMENTUM_EVENT
: nsIScrollableFrame::NOT_MOMENTUM;
nsIntPoint overflow;
aScrollableFrame->ScrollBy(actualDevPixelScrollAmount,
nsIScrollableFrame::DEVICE_PIXELS,
mode, &overflow, origin, momentum, snapMode);
if (!scrollFrameWeak.IsAlive()) {
// If the scroll causes changing the layout, we can think that the event
// has been completely consumed by the content. Then, users probably don't
// want additional action.
aEvent->mOverflowDeltaX = aEvent->mOverflowDeltaY = 0;
} else if (isDeltaModePixel) {
aEvent->mOverflowDeltaX = overflow.x;
aEvent->mOverflowDeltaY = overflow.y;
} else {
aEvent->mOverflowDeltaX =
static_cast<double>(overflow.x) / scrollAmountInDevPixels.width;
aEvent->mOverflowDeltaY =
static_cast<double>(overflow.y) / scrollAmountInDevPixels.height;
}
// If CSS overflow properties caused not to scroll, the overflowDelta* values
// should be same as delta* values since they may be used as gesture event by
// widget. However, if there is another scrollable element in the ancestor
// along the axis, probably users don't want the operation to cause
// additional action such as moving history. In such case, overflowDelta
// values should stay zero.
if (scrollFrameWeak.IsAlive()) {
if (aEvent->mDeltaX &&
overflowStyle.mHorizontal == NS_STYLE_OVERFLOW_HIDDEN &&
!ComputeScrollTarget(scrollFrame, aEvent,
COMPUTE_SCROLLABLE_ANCESTOR_ALONG_X_AXIS)) {
aEvent->mOverflowDeltaX = aEvent->mDeltaX;
}
if (aEvent->mDeltaY &&
overflowStyle.mVertical == NS_STYLE_OVERFLOW_HIDDEN &&
!ComputeScrollTarget(scrollFrame, aEvent,
COMPUTE_SCROLLABLE_ANCESTOR_ALONG_Y_AXIS)) {
aEvent->mOverflowDeltaY = aEvent->mDeltaY;
}
}
NS_ASSERTION(aEvent->mOverflowDeltaX == 0 ||
(aEvent->mOverflowDeltaX > 0) == (aEvent->mDeltaX > 0),
"The sign of mOverflowDeltaX is different from the scroll direction");
NS_ASSERTION(aEvent->mOverflowDeltaY == 0 ||
(aEvent->mOverflowDeltaY > 0) == (aEvent->mDeltaY > 0),
"The sign of mOverflowDeltaY is different from the scroll direction");
WheelPrefs::GetInstance()->CancelApplyingUserPrefsFromOverflowDelta(aEvent);
}
void
EventStateManager::DecideGestureEvent(WidgetGestureNotifyEvent* aEvent,
nsIFrame* targetFrame)
{
NS_ASSERTION(aEvent->mMessage == eGestureNotify,
"DecideGestureEvent called with a non-gesture event");
/* Check the ancestor tree to decide if any frame is willing* to receive
* a MozPixelScroll event. If that's the case, the current touch gesture
* will be used as a pan gesture; otherwise it will be a regular
* mousedown/mousemove/click event.
*
* *willing: determine if it makes sense to pan the element using scroll events:
* - For web content: if there are any visible scrollbars on the touch point
* - For XUL: if it's an scrollable element that can currently scroll in some
* direction.
*
* Note: we'll have to one-off various cases to ensure a good usable behavior
*/
WidgetGestureNotifyEvent::PanDirection panDirection =
WidgetGestureNotifyEvent::ePanNone;
bool displayPanFeedback = false;
for (nsIFrame* current = targetFrame; current;
current = nsLayoutUtils::GetCrossDocParentFrame(current)) {
// e10s - mark remote content as pannable. This is a work around since
// we don't have access to remote frame scroll info here. Apz data may
// assist is solving this.
if (current && IsRemoteTarget(current->GetContent())) {
panDirection = WidgetGestureNotifyEvent::ePanBoth;
// We don't know when we reach bounds, so just disable feedback for now.
displayPanFeedback = false;
break;
}
LayoutFrameType currentFrameType = current->Type();
// Scrollbars should always be draggable
if (currentFrameType == LayoutFrameType::Scrollbar) {
panDirection = WidgetGestureNotifyEvent::ePanNone;
break;
}
#ifdef MOZ_XUL
// Special check for trees
nsTreeBodyFrame* treeFrame = do_QueryFrame(current);
if (treeFrame) {
if (treeFrame->GetHorizontalOverflow()) {
panDirection = WidgetGestureNotifyEvent::ePanHorizontal;
}
if (treeFrame->GetVerticalOverflow()) {
panDirection = WidgetGestureNotifyEvent::ePanVertical;
}
break;
}
#endif
nsIScrollableFrame* scrollableFrame = do_QueryFrame(current);
if (scrollableFrame) {
if (current->IsFrameOfType(nsIFrame::eXULBox)) {
displayPanFeedback = true;
nsRect scrollRange = scrollableFrame->GetScrollRange();
bool canScrollHorizontally = scrollRange.width > 0;
if (targetFrame->IsMenuFrame()) {
// menu frames report horizontal scroll when they have submenus
// and we don't want that
canScrollHorizontally = false;
displayPanFeedback = false;
}
// Vertical panning has priority over horizontal panning, so
// when vertical movement is possible we can just finish the loop.
if (scrollRange.height > 0) {
panDirection = WidgetGestureNotifyEvent::ePanVertical;
break;
}
if (canScrollHorizontally) {
panDirection = WidgetGestureNotifyEvent::ePanHorizontal;
displayPanFeedback = false;
}
} else { //Not a XUL box
uint32_t scrollbarVisibility = scrollableFrame->GetScrollbarVisibility();
//Check if we have visible scrollbars
if (scrollbarVisibility & nsIScrollableFrame::VERTICAL) {
panDirection = WidgetGestureNotifyEvent::ePanVertical;
displayPanFeedback = true;
break;
}
if (scrollbarVisibility & nsIScrollableFrame::HORIZONTAL) {
panDirection = WidgetGestureNotifyEvent::ePanHorizontal;
displayPanFeedback = true;
}
}
} //scrollableFrame
} //ancestor chain
aEvent->mDisplayPanFeedback = displayPanFeedback;
aEvent->mPanDirection = panDirection;
}
#ifdef XP_MACOSX
static bool
NodeAllowsClickThrough(nsINode* aNode)
{
while (aNode) {
if (aNode->IsXULElement()) {
mozilla::dom::Element* element = aNode->AsElement();
static nsIContent::AttrValuesArray strings[] =
{&nsGkAtoms::always, &nsGkAtoms::never, nullptr};
switch (element->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::clickthrough,
strings, eCaseMatters)) {
case 0:
return true;
case 1:
return false;
}
}
aNode = nsContentUtils::GetCrossDocParentNode(aNode);
}
return true;
}
#endif
void
EventStateManager::PostHandleKeyboardEvent(WidgetKeyboardEvent* aKeyboardEvent,
nsIFrame* aTargetFrame,
nsEventStatus& aStatus)
{
if (aStatus == nsEventStatus_eConsumeNoDefault) {
return;
}
if (!aKeyboardEvent->HasBeenPostedToRemoteProcess()) {
if (aKeyboardEvent->IsWaitingReplyFromRemoteProcess()) {
RefPtr<TabParent> remote = aTargetFrame ?
TabParent::GetFrom(aTargetFrame->GetContent()) : nullptr;
if (remote && !remote->IsReadyToHandleInputEvents()) {
// We need to dispatch the event to the browser element again if we were
// waiting for the key reply but the event wasn't sent to the content
// process due to the remote browser wasn't ready.
WidgetKeyboardEvent keyEvent(*aKeyboardEvent);
aKeyboardEvent->MarkAsHandledInRemoteProcess();
EventDispatcher::Dispatch(remote->GetOwnerElement(), mPresContext,
&keyEvent);
if (keyEvent.DefaultPrevented()) {
aKeyboardEvent->PreventDefault(!keyEvent.DefaultPreventedByContent());
aStatus = nsEventStatus_eConsumeNoDefault;
return;
}
}
}
// The widget expects a reply for every keyboard event. If the event wasn't
// dispatched to a content process (non-e10s or no content process
// running), we need to short-circuit here. Otherwise, we need to wait for
// the content process to handle the event.
aKeyboardEvent->mWidget->PostHandleKeyEvent(aKeyboardEvent);
if (aKeyboardEvent->DefaultPrevented()) {
aStatus = nsEventStatus_eConsumeNoDefault;
return;
}
}
// XXX Currently, our automated tests don't support mKeyNameIndex.
// Therefore, we still need to handle this with keyCode.
switch(aKeyboardEvent->mKeyCode) {
case NS_VK_TAB:
case NS_VK_F6:
// This is to prevent keyboard scrolling while alt modifier in use.
if (!aKeyboardEvent->IsAlt()) {
aStatus = nsEventStatus_eConsumeNoDefault;
// Handling the tab event after it was sent to content is bad,
// because to the FocusManager the remote-browser looks like one
// element, so we would just move the focus to the next element
// in chrome, instead of handling it in content.
if (aKeyboardEvent->HasBeenPostedToRemoteProcess()) {
break;
}
EnsureDocument(mPresContext);
nsIFocusManager* fm = nsFocusManager::GetFocusManager();
if (fm && mDocument) {
// Shift focus forward or back depending on shift key
bool isDocMove =
aKeyboardEvent->IsControl() || aKeyboardEvent->mKeyCode == NS_VK_F6;
uint32_t dir = aKeyboardEvent->IsShift() ?
(isDocMove ? static_cast<uint32_t>(nsIFocusManager::MOVEFOCUS_BACKWARDDOC) :
static_cast<uint32_t>(nsIFocusManager::MOVEFOCUS_BACKWARD)) :
(isDocMove ? static_cast<uint32_t>(nsIFocusManager::MOVEFOCUS_FORWARDDOC) :
static_cast<uint32_t>(nsIFocusManager::MOVEFOCUS_FORWARD));
nsCOMPtr<nsIDOMElement> result;
fm->MoveFocus(mDocument->GetWindow(), nullptr, dir,
nsIFocusManager::FLAG_BYKEY,
getter_AddRefs(result));
}
}
return;
case 0:
// We handle keys with no specific keycode value below.
break;
default:
return;
}
switch(aKeyboardEvent->mKeyNameIndex) {
case KEY_NAME_INDEX_ZoomIn:
case KEY_NAME_INDEX_ZoomOut:
ChangeFullZoom(
aKeyboardEvent->mKeyNameIndex == KEY_NAME_INDEX_ZoomIn ? 1 : -1);
aStatus = nsEventStatus_eConsumeNoDefault;
break;
default:
break;
}
}
nsresult
EventStateManager::PostHandleEvent(nsPresContext* aPresContext,
WidgetEvent* aEvent,
nsIFrame* aTargetFrame,
nsEventStatus* aStatus)
{
NS_ENSURE_ARG(aPresContext);
NS_ENSURE_ARG_POINTER(aStatus);
mCurrentTarget = aTargetFrame;
mCurrentTargetContent = nullptr;
HandleCrossProcessEvent(aEvent, aStatus);
// NOTE: the above call may have destroyed aTargetFrame, please use
// mCurrentTarget henceforth. This is to avoid using it accidentally:
aTargetFrame = nullptr;
// Most of the events we handle below require a frame.
// Add special cases here.
if (!mCurrentTarget && aEvent->mMessage != eMouseUp &&
aEvent->mMessage != eMouseDown) {
return NS_OK;
}
//Keep the prescontext alive, we might need it after event dispatch
RefPtr<nsPresContext> presContext = aPresContext;
nsresult ret = NS_OK;
switch (aEvent->mMessage) {
case eMouseDown:
{
WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
if (mouseEvent->button == WidgetMouseEvent::eLeftButton &&
!sNormalLMouseEventInProcess) {
// We got a mouseup event while a mousedown event was being processed.
// Make sure that the capturing content is cleared.
nsIPresShell::SetCapturingContent(nullptr, 0);
break;
}
// For remote content, capture the event in the parent process at the
// <xul:browser remote> element. This will ensure that subsequent mousemove/mouseup
// events will continue to be dispatched to this element and therefore forwarded
// to the child.
if (aEvent->HasBeenPostedToRemoteProcess() &&
!nsIPresShell::GetCapturingContent()) {
nsIContent* content = mCurrentTarget ? mCurrentTarget->GetContent() : nullptr;
nsIPresShell::SetCapturingContent(content, 0);
}
nsCOMPtr<nsIContent> activeContent;
// When content calls PreventDefault on pointerdown, we also call
// PreventDefault on the subsequent mouse events to suppress default
// behaviors. Normally, aStatus should be nsEventStatus_eConsumeNoDefault
// when the event is DefaultPrevented but it's reset to
// nsEventStatus_eIgnore in EventStateManager::PreHandleEvent. So we also
// check if the event is DefaultPrevented.
if (nsEventStatus_eConsumeNoDefault != *aStatus &&
!aEvent->DefaultPrevented()) {
nsCOMPtr<nsIContent> newFocus;
bool suppressBlur = false;
if (mCurrentTarget) {
mCurrentTarget->GetContentForEvent(aEvent, getter_AddRefs(newFocus));
const nsStyleUserInterface* ui = mCurrentTarget->StyleUserInterface();
activeContent = mCurrentTarget->GetContent();
// In some cases, we do not want to even blur the current focused
// element. Those cases are:
// 1. -moz-user-focus CSS property is set to 'ignore';
// 2. Element with NS_EVENT_STATE_DISABLED
// (aka :disabled pseudo-class for HTML element);
// 3. XUL control element has the disabled property set to 'true'.
//
// We can't use nsIFrame::IsFocusable() because we want to blur when
// we click on a visibility: none element.
// We can't use nsIContent::IsFocusable() because we want to blur when
// we click on a non-focusable element like a <div>.
// We have to use |aEvent->mTarget| to not make sure we do not check
// an anonymous node of the targeted element.
suppressBlur = (ui->mUserFocus == StyleUserFocus::Ignore);
if (!suppressBlur) {
nsCOMPtr<Element> element = do_QueryInterface(aEvent->mTarget);
suppressBlur = element &&
element->State().HasState(NS_EVENT_STATE_DISABLED);
}
if (!suppressBlur) {
nsCOMPtr<nsIDOMXULControlElement> xulControl =
do_QueryInterface(aEvent->mTarget);
if (xulControl) {
bool disabled;
xulControl->GetDisabled(&disabled);
suppressBlur = disabled;
}
}
}
if (!suppressBlur) {
suppressBlur = nsContentUtils::IsUserFocusIgnored(activeContent);
}
nsIFrame* currFrame = mCurrentTarget;
// When a root content which isn't editable but has an editable HTML
// <body> element is clicked, we should redirect the focus to the
// the <body> element. E.g., when an user click bottom of the editor
// where is outside of the <body> element, the <body> should be focused
// and the user can edit immediately after that.
//
// NOTE: The newFocus isn't editable that also means it's not in
// designMode. In designMode, all contents are not focusable.
if (newFocus && !newFocus->IsEditable()) {
nsIDocument *doc = newFocus->GetComposedDoc();
if (doc && newFocus == doc->GetRootElement()) {
nsIContent *bodyContent =
nsLayoutUtils::GetEditableRootContentByContentEditable(doc);
if (bodyContent) {
nsIFrame* bodyFrame = bodyContent->GetPrimaryFrame();
if (bodyFrame) {
currFrame = bodyFrame;
newFocus = bodyContent;
}
}
}
}
// When the mouse is pressed, the default action is to focus the
// target. Look for the nearest enclosing focusable frame.
while (currFrame) {
// If the mousedown happened inside a popup, don't
// try to set focus on one of its containing elements
const nsStyleDisplay* display = currFrame->StyleDisplay();
if (display->mDisplay == StyleDisplay::MozPopup) {
newFocus = nullptr;
break;
}
int32_t tabIndexUnused;
if (currFrame->IsFocusable(&tabIndexUnused, true)) {
newFocus = currFrame->GetContent();
nsCOMPtr<nsIDOMElement> domElement(do_QueryInterface(newFocus));
if (domElement)
break;
}
currFrame = currFrame->GetParent();
}
nsIFocusManager* fm = nsFocusManager::GetFocusManager();
if (fm) {
// if something was found to focus, focus it. Otherwise, if the
// element that was clicked doesn't have -moz-user-focus: ignore,
// clear the existing focus. For -moz-user-focus: ignore, the focus
// is just left as is.
// Another effect of mouse clicking, handled in nsSelection, is that
// it should update the caret position to where the mouse was
// clicked. Because the focus is cleared when clicking on a
// non-focusable node, the next press of the tab key will cause
// focus to be shifted from the caret position instead of the root.
if (newFocus && currFrame) {
// use the mouse flag and the noscroll flag so that the content
// doesn't unexpectedly scroll when clicking an element that is
// only half visible
uint32_t flags = nsIFocusManager::FLAG_BYMOUSE |
nsIFocusManager::FLAG_NOSCROLL;
// If this was a touch-generated event, pass that information:
if (mouseEvent->inputSource == nsIDOMMouseEvent::MOZ_SOURCE_TOUCH) {
flags |= nsIFocusManager::FLAG_BYTOUCH;
}
nsCOMPtr<nsIDOMElement> newFocusElement = do_QueryInterface(newFocus);
fm->SetFocus(newFocusElement, flags);
}
else if (!suppressBlur) {
// clear the focus within the frame and then set it as the
// focused frame
EnsureDocument(mPresContext);
if (mDocument) {
#ifdef XP_MACOSX
if (!activeContent || !activeContent->IsXULElement())
#endif
fm->ClearFocus(mDocument->GetWindow());
fm->SetFocusedWindow(mDocument->GetWindow());
}
}
}
// The rest is left button-specific.
if (mouseEvent->button != WidgetMouseEvent::eLeftButton) {
break;
}
if (activeContent) {
// The nearest enclosing element goes into the
// :active state. If we fail the QI to DOMElement,
// then we know we're only a node, and that we need
// to obtain our parent element and put it into :active
// instead.
nsCOMPtr<nsIDOMElement> elt(do_QueryInterface(activeContent));
if (!elt) {
nsIContent* par = activeContent->GetParent();
if (par)
activeContent = par;
}
}
}
else {
// if we're here, the event handler returned false, so stop
// any of our own processing of a drag. Workaround for bug 43258.
StopTrackingDragGesture();
// When the event was cancelled, there is currently a chrome document
// focused and a mousedown just occurred on a content document, ensure
// that the window that was clicked is focused.
EnsureDocument(mPresContext);
nsIFocusManager* fm = nsFocusManager::GetFocusManager();
if (mDocument && fm) {
nsCOMPtr<mozIDOMWindowProxy> window;
fm->GetFocusedWindow(getter_AddRefs(window));
auto* currentWindow = nsPIDOMWindowOuter::From(window);
if (currentWindow && mDocument->GetWindow() &&
currentWindow != mDocument->GetWindow() &&
!nsContentUtils::IsChromeDoc(mDocument)) {
nsCOMPtr<nsPIDOMWindowOuter> currentTop;
nsCOMPtr<nsPIDOMWindowOuter> newTop;
currentTop = currentWindow->GetTop();
newTop = mDocument->GetWindow()->GetTop();
nsCOMPtr<nsIDocument> currentDoc = currentWindow->GetExtantDoc();
if (nsContentUtils::IsChromeDoc(currentDoc) ||
(currentTop && newTop && currentTop != newTop)) {
fm->SetFocusedWindow(mDocument->GetWindow());
}
}
}
}
SetActiveManager(this, activeContent);
}
break;
case ePointerCancel: {
if(WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent()) {
GenerateMouseEnterExit(mouseEvent);
}
// After firing the pointercancel event, a user agent must also fire a
// pointerout event followed by a pointerleave event.
MOZ_FALLTHROUGH;
}
case ePointerUp: {
WidgetPointerEvent* pointerEvent = aEvent->AsPointerEvent();
// After UP/Cancel Touch pointers become invalid so we can remove relevant helper from Table
// Mouse/Pen pointers are valid all the time (not only between down/up)
if (pointerEvent->inputSource == nsIDOMMouseEvent::MOZ_SOURCE_TOUCH) {
mPointersEnterLeaveHelper.Remove(pointerEvent->pointerId);
GenerateMouseEnterExit(pointerEvent);
}
break;
}
case eMouseUp:
{
ClearGlobalActiveContent(this);
WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
if (mouseEvent && mouseEvent->IsReal()) {
if (!mCurrentTarget) {
GetEventTarget();
}
// Make sure to dispatch the click even if there is no frame for
// the current target element. This is required for Web compatibility.
ret = CheckForAndDispatchClick(mouseEvent, aStatus);
}
nsIPresShell *shell = presContext->GetPresShell();
if (shell) {
RefPtr<nsFrameSelection> frameSelection = shell->FrameSelection();
frameSelection->SetDragState(false);
}
}
break;
case eWheelOperationEnd:
{
MOZ_ASSERT(aEvent->IsTrusted());
ScrollbarsForWheel::MayInactivate();
WidgetWheelEvent* wheelEvent = aEvent->AsWheelEvent();
nsIScrollableFrame* scrollTarget =
do_QueryFrame(ComputeScrollTarget(mCurrentTarget, wheelEvent,
COMPUTE_DEFAULT_ACTION_TARGET));
if (scrollTarget) {
scrollTarget->ScrollSnap();
}
}
break;
case eWheel:
case eWheelOperationStart:
{
MOZ_ASSERT(aEvent->IsTrusted());
if (*aStatus == nsEventStatus_eConsumeNoDefault) {
ScrollbarsForWheel::Inactivate();
break;
}
WidgetWheelEvent* wheelEvent = aEvent->AsWheelEvent();
// Check if the frame to scroll before checking the default action
// because if the scroll target is a plugin, the default action should be
// chosen by the plugin rather than by our prefs.
nsIFrame* frameToScroll =
ComputeScrollTarget(mCurrentTarget, wheelEvent,
COMPUTE_DEFAULT_ACTION_TARGET);
nsPluginFrame* pluginFrame = do_QueryFrame(frameToScroll);
// When APZ is enabled, the actual scroll animation might be handled by
// the compositor.
WheelPrefs::Action action;
if (pluginFrame) {
MOZ_ASSERT(pluginFrame->WantsToHandleWheelEventAsDefaultAction());
action = WheelPrefs::ACTION_SEND_TO_PLUGIN;
} else if (wheelEvent->mFlags.mHandledByAPZ) {
action = WheelPrefs::ACTION_NONE;
} else {
action = WheelPrefs::GetInstance()->ComputeActionFor(wheelEvent);
}
switch (action) {
case WheelPrefs::ACTION_SCROLL: {
// For scrolling of default action, we should honor the mouse wheel
// transaction.
ScrollbarsForWheel::PrepareToScrollText(this, mCurrentTarget, wheelEvent);
if (aEvent->mMessage != eWheel ||
(!wheelEvent->mDeltaX && !wheelEvent->mDeltaY)) {
break;
}
nsIScrollableFrame* scrollTarget = do_QueryFrame(frameToScroll);
ScrollbarsForWheel::SetActiveScrollTarget(scrollTarget);
nsIFrame* rootScrollFrame = !mCurrentTarget ? nullptr :
mCurrentTarget->PresContext()->PresShell()->GetRootScrollFrame();
nsIScrollableFrame* rootScrollableFrame = nullptr;
if (rootScrollFrame) {
rootScrollableFrame = do_QueryFrame(rootScrollFrame);
}
if (!scrollTarget || scrollTarget == rootScrollableFrame) {
wheelEvent->mViewPortIsOverscrolled = true;
}
wheelEvent->mOverflowDeltaX = wheelEvent->mDeltaX;
wheelEvent->mOverflowDeltaY = wheelEvent->mDeltaY;
WheelPrefs::GetInstance()->
CancelApplyingUserPrefsFromOverflowDelta(wheelEvent);
if (scrollTarget) {
DoScrollText(scrollTarget, wheelEvent);
} else {
WheelTransaction::EndTransaction();
ScrollbarsForWheel::Inactivate();
}
break;
}
case WheelPrefs::ACTION_HISTORY: {
// If this event doesn't cause eLegacyMouseLineOrPageScroll event or
// the direction is oblique, don't perform history back/forward.
int32_t intDelta = wheelEvent->GetPreferredIntDelta();
if (!intDelta) {
break;
}
DoScrollHistory(intDelta);
break;
}
case WheelPrefs::ACTION_ZOOM: {
// If this event doesn't cause eLegacyMouseLineOrPageScroll event or
// the direction is oblique, don't perform zoom in/out.
int32_t intDelta = wheelEvent->GetPreferredIntDelta();
if (!intDelta) {
break;
}
DoScrollZoom(mCurrentTarget, intDelta);
break;
}
case WheelPrefs::ACTION_SEND_TO_PLUGIN:
MOZ_ASSERT(pluginFrame);
if (wheelEvent->mMessage != eWheel ||
(!wheelEvent->mDeltaX && !wheelEvent->mDeltaY)) {
break;
}
MOZ_ASSERT(static_cast<void*>(frameToScroll) ==
static_cast<void*>(pluginFrame));
if (!WheelTransaction::WillHandleDefaultAction(wheelEvent,
frameToScroll)) {
break;
}
pluginFrame->HandleWheelEventAsDefaultAction(wheelEvent);
break;
case WheelPrefs::ACTION_NONE:
default:
bool allDeltaOverflown = false;
if (wheelEvent->mFlags.mHandledByAPZ) {
if (wheelEvent->mCanTriggerSwipe) {
// For events that can trigger swipes, APZ needs to know whether
// scrolling is possible in the requested direction. It does this
// by looking at the scroll overflow values on mCanTriggerSwipe
// events after they have been processed.
allDeltaOverflown =
!ComputeScrollTarget(mCurrentTarget, wheelEvent,
COMPUTE_DEFAULT_ACTION_TARGET);
}
} else {
// The event was processed neither by APZ nor by us, so all of the
// delta values must be overflown delta values.
allDeltaOverflown = true;
}
if (!allDeltaOverflown) {
break;
}
wheelEvent->mOverflowDeltaX = wheelEvent->mDeltaX;
wheelEvent->mOverflowDeltaY = wheelEvent->mDeltaY;
WheelPrefs::GetInstance()->
CancelApplyingUserPrefsFromOverflowDelta(wheelEvent);
wheelEvent->mViewPortIsOverscrolled = true;
break;
}
*aStatus = nsEventStatus_eConsumeNoDefault;
}
break;
case eGestureNotify:
{
if (nsEventStatus_eConsumeNoDefault != *aStatus) {
DecideGestureEvent(aEvent->AsGestureNotifyEvent(), mCurrentTarget);
}
}
break;
case eDragEnter:
case eDragOver:
{
NS_ASSERTION(aEvent->mClass == eDragEventClass, "Expected a drag event");
// Check if the drag is occurring inside a scrollable area. If so, scroll
// the area when the mouse is near the edges.
if (mCurrentTarget && aEvent->mMessage == eDragOver) {
nsIFrame* checkFrame = mCurrentTarget;
while (checkFrame) {
nsIScrollableFrame* scrollFrame = do_QueryFrame(checkFrame);
// Break out so only the innermost scrollframe is scrolled.
if (scrollFrame && scrollFrame->DragScroll(aEvent)) {
break;
}
checkFrame = checkFrame->GetParent();
}
}
nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession();
if (!dragSession)
break;
// Reset the flag.
dragSession->SetOnlyChromeDrop(false);
if (mPresContext) {
EnsureDocument(mPresContext);
}
bool isChromeDoc = nsContentUtils::IsChromeDoc(mDocument);
// the initial dataTransfer is the one from the dragstart event that
// was set on the dragSession when the drag began.
nsCOMPtr<nsIDOMDataTransfer> dataTransfer;
nsCOMPtr<nsIDOMDataTransfer> initialDataTransfer;
dragSession->GetDataTransfer(getter_AddRefs(initialDataTransfer));
WidgetDragEvent *dragEvent = aEvent->AsDragEvent();
// collect any changes to moz cursor settings stored in the event's
// data transfer.
UpdateDragDataTransfer(dragEvent);
// cancelling a dragenter or dragover event means that a drop should be
// allowed, so update the dropEffect and the canDrop state to indicate
// that a drag is allowed. If the event isn't cancelled, a drop won't be
// allowed. Essentially, to allow a drop somewhere, specify the effects
// using the effectAllowed and dropEffect properties in a dragenter or
// dragover event and cancel the event. To not allow a drop somewhere,
// don't cancel the event or set the effectAllowed or dropEffect to
// "none". This way, if the event is just ignored, no drop will be
// allowed.
uint32_t dropEffect = nsIDragService::DRAGDROP_ACTION_NONE;
uint32_t action = nsIDragService::DRAGDROP_ACTION_NONE;
if (nsEventStatus_eConsumeNoDefault == *aStatus) {
// if the event has a dataTransfer set, use it.
if (dragEvent->mDataTransfer) {
// get the dataTransfer and the dropEffect that was set on it
dataTransfer = do_QueryInterface(dragEvent->mDataTransfer);
dataTransfer->GetDropEffectInt(&dropEffect);
}
else {
// if dragEvent->mDataTransfer is null, it means that no attempt was
// made to access the dataTransfer during the event, yet the event
// was cancelled. Instead, use the initial data transfer available
// from the drag session. The drop effect would not have been
// initialized (which is done in DragEvent::GetDataTransfer),
// so set it from the drag action. We'll still want to filter it
// based on the effectAllowed below.
dataTransfer = initialDataTransfer;
dragSession->GetDragAction(&action);
// filter the drop effect based on the action. Use UNINITIALIZED as
// any effect is allowed.
dropEffect = nsContentUtils::FilterDropEffect(action,
nsIDragService::DRAGDROP_ACTION_UNINITIALIZED);
}
// At this point, if the dataTransfer is null, it means that the
// drag was originally started by directly calling the drag service.
// Just assume that all effects are allowed.
uint32_t effectAllowed = nsIDragService::DRAGDROP_ACTION_UNINITIALIZED;
if (dataTransfer)
dataTransfer->GetEffectAllowedInt(&effectAllowed);
// set the drag action based on the drop effect and effect allowed.
// The drop effect field on the drag transfer object specifies the
// desired current drop effect. However, it cannot be used if the
// effectAllowed state doesn't include that type of action. If the
// dropEffect is "none", then the action will be 'none' so a drop will
// not be allowed.
if (effectAllowed == nsIDragService::DRAGDROP_ACTION_UNINITIALIZED ||
dropEffect & effectAllowed)
action = dropEffect;
if (action == nsIDragService::DRAGDROP_ACTION_NONE)
dropEffect = nsIDragService::DRAGDROP_ACTION_NONE;
// inform the drag session that a drop is allowed on this node.
dragSession->SetDragAction(action);
dragSession->SetCanDrop(action != nsIDragService::DRAGDROP_ACTION_NONE);
// For now, do this only for dragover.
//XXXsmaug dragenter needs some more work.
if (aEvent->mMessage == eDragOver && !isChromeDoc) {
// Someone has called preventDefault(), check whether is was on
// content or chrome.
dragSession->SetOnlyChromeDrop(
!dragEvent->mDefaultPreventedOnContent);
}
} else if (aEvent->mMessage == eDragOver && !isChromeDoc) {
// No one called preventDefault(), so handle drop only in chrome.
dragSession->SetOnlyChromeDrop(true);
}
if (ContentChild* child = ContentChild::GetSingleton()) {
child->SendUpdateDropEffect(action, dropEffect);
}
if (aEvent->HasBeenPostedToRemoteProcess()) {
dragSession->SetCanDrop(true);
} else if (initialDataTransfer) {
// Now set the drop effect in the initial dataTransfer. This ensures
// that we can get the desired drop effect in the drop event. For events
// dispatched to content, the content process will take care of setting
// this.
initialDataTransfer->SetDropEffectInt(dropEffect);
}
}
break;
case eDrop:
{
sLastDragOverFrame = nullptr;
ClearGlobalActiveContent(this);
break;
}
case eDragExit:
// make sure to fire the enter and exit_synth events after the
// eDragExit event, otherwise we'll clean up too early
GenerateDragDropEnterExit(presContext, aEvent->AsDragEvent());
if (ContentChild* child = ContentChild::GetSingleton()) {
// SendUpdateDropEffect to prevent nsIDragService from waiting for
// response of forwarded dragexit event.
child->SendUpdateDropEffect(nsIDragService::DRAGDROP_ACTION_NONE,
nsIDragService::DRAGDROP_ACTION_NONE);
}
break;
case eKeyUp:
break;
case eKeyPress:
{
WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent();
PostHandleKeyboardEvent(keyEvent, mCurrentTarget, *aStatus);
}
break;
case eMouseEnterIntoWidget:
if (mCurrentTarget) {
nsCOMPtr<nsIContent> targetContent;
mCurrentTarget->GetContentForEvent(aEvent, getter_AddRefs(targetContent));
SetContentState(targetContent, NS_EVENT_STATE_HOVER);
}
break;
#ifdef XP_MACOSX
case eMouseActivate:
if (mCurrentTarget) {
nsCOMPtr<nsIContent> targetContent;
mCurrentTarget->GetContentForEvent(aEvent, getter_AddRefs(targetContent));
if (!NodeAllowsClickThrough(targetContent)) {
*aStatus = nsEventStatus_eConsumeNoDefault;
}
}
break;
#endif
default:
break;
}
//Reset target frame to null to avoid mistargeting after reentrant event
mCurrentTarget = nullptr;
mCurrentTargetContent = nullptr;
return ret;
}
TabParent*
EventStateManager::GetCrossProcessTarget()
{
return IMEStateManager::GetActiveTabParent();
}
bool
EventStateManager::IsTargetCrossProcess(WidgetGUIEvent* aEvent)
{
// Check to see if there is a focused, editable content in chrome,
// in that case, do not forward IME events to content
nsIContent *focusedContent = GetFocusedContent();
if (focusedContent && focusedContent->IsEditable())
return false;
return IMEStateManager::GetActiveTabParent() != nullptr;
}
void
EventStateManager::NotifyDestroyPresContext(nsPresContext* aPresContext)
{
IMEStateManager::OnDestroyPresContext(aPresContext);
if (mHoverContent) {
// Bug 70855: Presentation is going away, possibly for a reframe.
// Reset the hover state so that if we're recreating the presentation,
// we won't have the old hover state still set in the new presentation,
// as if the new presentation is resized, a new element may be hovered.
SetContentState(nullptr, NS_EVENT_STATE_HOVER);
}
mPointersEnterLeaveHelper.Clear();
}
void
EventStateManager::SetPresContext(nsPresContext* aPresContext)
{
mPresContext = aPresContext;
}
void
EventStateManager::ClearFrameRefs(nsIFrame* aFrame)
{
if (aFrame && aFrame == mCurrentTarget) {
mCurrentTargetContent = aFrame->GetContent();
}
}
void
EventStateManager::UpdateCursor(nsPresContext* aPresContext,
WidgetEvent* aEvent,
nsIFrame* aTargetFrame,
nsEventStatus* aStatus)
{
if (aTargetFrame && IsRemoteTarget(aTargetFrame->GetContent())) {
return;
}
int32_t cursor = NS_STYLE_CURSOR_DEFAULT;
imgIContainer* container = nullptr;
bool haveHotspot = false;
float hotspotX = 0.0f, hotspotY = 0.0f;
//If cursor is locked just use the locked one
if (mLockCursor) {
cursor = mLockCursor;
}
//If not locked, look for correct cursor
else if (aTargetFrame) {
nsIFrame::Cursor framecursor;
nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent,
aTargetFrame);
// Avoid setting cursor when the mouse is over a windowless pluign.
if (NS_FAILED(aTargetFrame->GetCursor(pt, framecursor))) {
if (XRE_IsContentProcess()) {
mLastFrameConsumedSetCursor = true;
}
return;
}
// Make sure cursors get reset after the mouse leaves a
// windowless plugin frame.
if (mLastFrameConsumedSetCursor) {
ClearCachedWidgetCursor(aTargetFrame);
mLastFrameConsumedSetCursor = false;
}
// If the current cursor is from the same frame, and it is now
// loading some new image for the cursor, we should wait for a
// while rather than taking its fallback cursor directly.
if (framecursor.mLoading &&
gLastCursorSourceFrame == aTargetFrame &&
TimeStamp::NowLoRes() - gLastCursorUpdateTime <
TimeDuration::FromMilliseconds(kCursorLoadingTimeout)) {
return;
}
cursor = framecursor.mCursor;
container = framecursor.mContainer;
haveHotspot = framecursor.mHaveHotspot;
hotspotX = framecursor.mHotspotX;
hotspotY = framecursor.mHotspotY;
}
if (nsContentUtils::UseActivityCursor()) {
// Check whether or not to show the busy cursor
nsCOMPtr<nsIDocShell> docShell(aPresContext->GetDocShell());
if (!docShell) return;
uint32_t busyFlags = nsIDocShell::BUSY_FLAGS_NONE;
docShell->GetBusyFlags(&busyFlags);
// Show busy cursor everywhere before page loads
// and just replace the arrow cursor after page starts loading
if (busyFlags & nsIDocShell::BUSY_FLAGS_BUSY &&
(cursor == NS_STYLE_CURSOR_AUTO || cursor == NS_STYLE_CURSOR_DEFAULT))
{
cursor = NS_STYLE_CURSOR_SPINNING;
container = nullptr;
}
}
if (aTargetFrame) {
SetCursor(cursor, container, haveHotspot, hotspotX, hotspotY,
aTargetFrame->GetNearestWidget(), false);
gLastCursorSourceFrame = aTargetFrame;
gLastCursorUpdateTime = TimeStamp::NowLoRes();
}
if (mLockCursor || NS_STYLE_CURSOR_AUTO != cursor) {
*aStatus = nsEventStatus_eConsumeDoDefault;
}
}
void
EventStateManager::ClearCachedWidgetCursor(nsIFrame* aTargetFrame)
{
if (!aTargetFrame) {
return;
}
nsIWidget* aWidget = aTargetFrame->GetNearestWidget();
if (!aWidget) {
return;
}
aWidget->ClearCachedCursor();
}
nsresult
EventStateManager::SetCursor(int32_t aCursor, imgIContainer* aContainer,
bool aHaveHotspot,
float aHotspotX, float aHotspotY,
nsIWidget* aWidget, bool aLockCursor)
{
EnsureDocument(mPresContext);
NS_ENSURE_TRUE(mDocument, NS_ERROR_FAILURE);
sMouseOverDocument = mDocument.get();
nsCursor c;
NS_ENSURE_TRUE(aWidget, NS_ERROR_FAILURE);
if (aLockCursor) {
if (NS_STYLE_CURSOR_AUTO != aCursor) {
mLockCursor = aCursor;
}
else {
//If cursor style is set to auto we unlock the cursor again.
mLockCursor = 0;
}
}
switch (aCursor) {
default:
case NS_STYLE_CURSOR_AUTO:
case NS_STYLE_CURSOR_DEFAULT:
c = eCursor_standard;
break;
case NS_STYLE_CURSOR_POINTER:
c = eCursor_hyperlink;
break;
case NS_STYLE_CURSOR_CROSSHAIR:
c = eCursor_crosshair;
break;
case NS_STYLE_CURSOR_MOVE:
c = eCursor_move;
break;
case NS_STYLE_CURSOR_TEXT:
c = eCursor_select;
break;
case NS_STYLE_CURSOR_WAIT:
c = eCursor_wait;
break;
case NS_STYLE_CURSOR_HELP:
c = eCursor_help;
break;
case NS_STYLE_CURSOR_N_RESIZE:
c = eCursor_n_resize;
break;
case NS_STYLE_CURSOR_S_RESIZE:
c = eCursor_s_resize;
break;
case NS_STYLE_CURSOR_W_RESIZE:
c = eCursor_w_resize;
break;
case NS_STYLE_CURSOR_E_RESIZE:
c = eCursor_e_resize;
break;
case NS_STYLE_CURSOR_NW_RESIZE:
c = eCursor_nw_resize;
break;
case NS_STYLE_CURSOR_SE_RESIZE:
c = eCursor_se_resize;
break;
case NS_STYLE_CURSOR_NE_RESIZE:
c = eCursor_ne_resize;
break;
case NS_STYLE_CURSOR_SW_RESIZE:
c = eCursor_sw_resize;
break;
case NS_STYLE_CURSOR_COPY: // CSS3
c = eCursor_copy;
break;
case NS_STYLE_CURSOR_ALIAS:
c = eCursor_alias;
break;
case NS_STYLE_CURSOR_CONTEXT_MENU:
c = eCursor_context_menu;
break;
case NS_STYLE_CURSOR_CELL:
c = eCursor_cell;
break;
case NS_STYLE_CURSOR_GRAB:
c = eCursor_grab;
break;
case NS_STYLE_CURSOR_GRABBING:
c = eCursor_grabbing;
break;
case NS_STYLE_CURSOR_SPINNING:
c = eCursor_spinning;
break;
case NS_STYLE_CURSOR_ZOOM_IN:
c = eCursor_zoom_in;
break;
case NS_STYLE_CURSOR_ZOOM_OUT:
c = eCursor_zoom_out;
break;
case NS_STYLE_CURSOR_NOT_ALLOWED:
c = eCursor_not_allowed;
break;
case NS_STYLE_CURSOR_COL_RESIZE:
c = eCursor_col_resize;
break;
case NS_STYLE_CURSOR_ROW_RESIZE:
c = eCursor_row_resize;
break;
case NS_STYLE_CURSOR_NO_DROP:
c = eCursor_no_drop;
break;
case NS_STYLE_CURSOR_VERTICAL_TEXT:
c = eCursor_vertical_text;
break;
case NS_STYLE_CURSOR_ALL_SCROLL:
c = eCursor_all_scroll;
break;
case NS_STYLE_CURSOR_NESW_RESIZE:
c = eCursor_nesw_resize;
break;
case NS_STYLE_CURSOR_NWSE_RESIZE:
c = eCursor_nwse_resize;
break;
case NS_STYLE_CURSOR_NS_RESIZE:
c = eCursor_ns_resize;
break;
case NS_STYLE_CURSOR_EW_RESIZE:
c = eCursor_ew_resize;
break;
case NS_STYLE_CURSOR_NONE:
c = eCursor_none;
break;
}
// First, try the imgIContainer, if non-null
nsresult rv = NS_ERROR_FAILURE;
if (aContainer) {
uint32_t hotspotX, hotspotY;
// css3-ui says to use the CSS-specified hotspot if present,
// otherwise use the intrinsic hotspot, otherwise use the top left
// corner.
if (aHaveHotspot) {
int32_t imgWidth, imgHeight;
aContainer->GetWidth(&imgWidth);
aContainer->GetHeight(&imgHeight);
// XXX std::max(NS_lround(x), 0)?
hotspotX = aHotspotX > 0.0f
? uint32_t(aHotspotX + 0.5f) : uint32_t(0);
if (hotspotX >= uint32_t(imgWidth))
hotspotX = imgWidth - 1;
hotspotY = aHotspotY > 0.0f
? uint32_t(aHotspotY + 0.5f) : uint32_t(0);
if (hotspotY >= uint32_t(imgHeight))
hotspotY = imgHeight - 1;
} else {
hotspotX = 0;
hotspotY = 0;
nsCOMPtr<nsIProperties> props(do_QueryInterface(aContainer));
if (props) {
nsCOMPtr<nsISupportsPRUint32> hotspotXWrap, hotspotYWrap;
props->Get("hotspotX", NS_GET_IID(nsISupportsPRUint32), getter_AddRefs(hotspotXWrap));
props->Get("hotspotY", NS_GET_IID(nsISupportsPRUint32), getter_AddRefs(hotspotYWrap));
if (hotspotXWrap)
hotspotXWrap->GetData(&hotspotX);
if (hotspotYWrap)
hotspotYWrap->GetData(&hotspotY);
}
}
rv = aWidget->SetCursor(aContainer, hotspotX, hotspotY);
}
if (NS_FAILED(rv))
aWidget->SetCursor(c);
return NS_OK;
}
class MOZ_STACK_CLASS ESMEventCB : public EventDispatchingCallback
{
public:
explicit ESMEventCB(nsIContent* aTarget) : mTarget(aTarget) {}
void HandleEvent(EventChainPostVisitor& aVisitor) override
{
if (aVisitor.mPresContext) {
nsIFrame* frame = aVisitor.mPresContext->GetPrimaryFrameFor(mTarget);
if (frame) {
frame->HandleEvent(aVisitor.mPresContext,
aVisitor.mEvent->AsGUIEvent(),
&aVisitor.mEventStatus);
}
}
}
nsCOMPtr<nsIContent> mTarget;
};
/*static*/ bool
EventStateManager::IsHandlingUserInput()
{
return sUserInputEventDepth > 0;
}
static void
CreateMouseOrPointerWidgetEvent(WidgetMouseEvent* aMouseEvent,
EventMessage aMessage,
nsIContent* aRelatedContent,
nsAutoPtr<WidgetMouseEvent>& aNewEvent)
{
WidgetPointerEvent* sourcePointer = aMouseEvent->AsPointerEvent();
if (sourcePointer) {
AUTO_PROFILER_LABEL("CreateMouseOrPointerWidgetEvent", EVENTS);
nsAutoPtr<WidgetPointerEvent> newPointerEvent;
newPointerEvent =
new WidgetPointerEvent(aMouseEvent->IsTrusted(), aMessage,
aMouseEvent->mWidget);
newPointerEvent->mIsPrimary = sourcePointer->mIsPrimary;
newPointerEvent->mWidth = sourcePointer->mWidth;
newPointerEvent->mHeight = sourcePointer->mHeight;
newPointerEvent->inputSource = sourcePointer->inputSource;
newPointerEvent->relatedTarget = aRelatedContent;
aNewEvent = newPointerEvent.forget();
} else {
aNewEvent =
new WidgetMouseEvent(aMouseEvent->IsTrusted(), aMessage,
aMouseEvent->mWidget, WidgetMouseEvent::eReal);
aNewEvent->relatedTarget = aRelatedContent;
}
aNewEvent->mRefPoint = aMouseEvent->mRefPoint;
aNewEvent->mModifiers = aMouseEvent->mModifiers;
aNewEvent->button = aMouseEvent->button;
aNewEvent->buttons = aMouseEvent->buttons;
aNewEvent->pressure = aMouseEvent->pressure;
aNewEvent->mPluginEvent = aMouseEvent->mPluginEvent;
aNewEvent->inputSource = aMouseEvent->inputSource;
aNewEvent->pointerId = aMouseEvent->pointerId;
}
nsIFrame*
EventStateManager::DispatchMouseOrPointerEvent(WidgetMouseEvent* aMouseEvent,
EventMessage aMessage,
nsIContent* aTargetContent,
nsIContent* aRelatedContent)
{
// http://dvcs.w3.org/hg/webevents/raw-file/default/mouse-lock.html#methods
// "[When the mouse is locked on an element...e]vents that require the concept
// of a mouse cursor must not be dispatched (for example: mouseover, mouseout).
if (sIsPointerLocked &&
(aMessage == eMouseLeave ||
aMessage == eMouseEnter ||
aMessage == eMouseOver ||
aMessage == eMouseOut)) {
mCurrentTargetContent = nullptr;
nsCOMPtr<Element> pointerLockedElement =
do_QueryReferent(EventStateManager::sPointerLockedElement);
if (!pointerLockedElement) {
NS_WARNING("Should have pointer locked element, but didn't.");
return nullptr;
}
nsCOMPtr<nsIContent> content = do_QueryInterface(pointerLockedElement);
return mPresContext->GetPrimaryFrameFor(content);
}
mCurrentTargetContent = nullptr;
if (!aTargetContent) {
return nullptr;
}
nsAutoPtr<WidgetMouseEvent> dispatchEvent;
CreateMouseOrPointerWidgetEvent(aMouseEvent, aMessage,
aRelatedContent, dispatchEvent);
AutoWeakFrame previousTarget = mCurrentTarget;
mCurrentTargetContent = aTargetContent;
nsIFrame* targetFrame = nullptr;
nsEventStatus status = nsEventStatus_eIgnore;
ESMEventCB callback(aTargetContent);
EventDispatcher::Dispatch(aTargetContent, mPresContext, dispatchEvent, nullptr,
&status, &callback);
if (mPresContext) {
// Although the primary frame was checked in event callback, it may not be
// the same object after event dispatch and handling, so refetch it.
targetFrame = mPresContext->GetPrimaryFrameFor(aTargetContent);
// If we are entering/leaving remote content, dispatch a mouse enter/exit
// event to the remote frame.
if (IsRemoteTarget(aTargetContent)) {
if (aMessage == eMouseOut) {
// For remote content, send a "top-level" widget mouse exit event.
nsAutoPtr<WidgetMouseEvent> remoteEvent;
CreateMouseOrPointerWidgetEvent(aMouseEvent, eMouseExitFromWidget,
aRelatedContent, remoteEvent);
remoteEvent->mExitFrom = WidgetMouseEvent::eTopLevel;
// mCurrentTarget is set to the new target, so we must reset it to the
// old target and then dispatch a cross-process event. (mCurrentTarget
// will be set back below.) HandleCrossProcessEvent will query for the
// proper target via GetEventTarget which will return mCurrentTarget.
mCurrentTarget = targetFrame;
HandleCrossProcessEvent(remoteEvent, &status);
} else if (aMessage == eMouseOver) {
nsAutoPtr<WidgetMouseEvent> remoteEvent;
CreateMouseOrPointerWidgetEvent(aMouseEvent, eMouseEnterIntoWidget,
aRelatedContent, remoteEvent);
HandleCrossProcessEvent(remoteEvent, &status);
}
}
}
mCurrentTargetContent = nullptr;
mCurrentTarget = previousTarget;
return targetFrame;
}
class EnterLeaveDispatcher
{
public:
EnterLeaveDispatcher(EventStateManager* aESM,
nsIContent* aTarget, nsIContent* aRelatedTarget,
WidgetMouseEvent* aMouseEvent,
EventMessage aEventMessage)
: mESM(aESM)
, mMouseEvent(aMouseEvent)
, mEventMessage(aEventMessage)
{
nsPIDOMWindowInner* win =
aTarget ? aTarget->OwnerDoc()->GetInnerWindow() : nullptr;
if (aMouseEvent->AsPointerEvent() ? win && win->HasPointerEnterLeaveEventListeners() :
win && win->HasMouseEnterLeaveEventListeners()) {
mRelatedTarget = aRelatedTarget ?
aRelatedTarget->FindFirstNonChromeOnlyAccessContent() : nullptr;
nsINode* commonParent = nullptr;
if (aTarget && aRelatedTarget) {
commonParent =
nsContentUtils::GetCommonAncestor(aTarget, aRelatedTarget);
}
nsIContent* current = aTarget;
// Note, it is ok if commonParent is null!
while (current && current != commonParent) {
if (!current->ChromeOnlyAccess()) {
mTargets.AppendObject(current);
}
// mouseenter/leave is fired only on elements.
current = current->GetParent();
}
}
}
void Dispatch()
{
if (mEventMessage == eMouseEnter || mEventMessage == ePointerEnter) {
for (int32_t i = mTargets.Count() - 1; i >= 0; --i) {
mESM->DispatchMouseOrPointerEvent(mMouseEvent, mEventMessage,
mTargets[i], mRelatedTarget);
}
} else {
for (int32_t i = 0; i < mTargets.Count(); ++i) {
mESM->DispatchMouseOrPointerEvent(mMouseEvent, mEventMessage,
mTargets[i], mRelatedTarget);
}
}
}
EventStateManager* mESM;
nsCOMArray<nsIContent> mTargets;
nsCOMPtr<nsIContent> mRelatedTarget;
WidgetMouseEvent* mMouseEvent;
EventMessage mEventMessage;
};
void
EventStateManager::NotifyMouseOut(WidgetMouseEvent* aMouseEvent,
nsIContent* aMovingInto)
{
RefPtr<OverOutElementsWrapper> wrapper = GetWrapperByEventID(aMouseEvent);
if (!wrapper || !wrapper->mLastOverElement)
return;
// Before firing mouseout, check for recursion
if (wrapper->mLastOverElement == wrapper->mFirstOutEventElement)
return;
if (wrapper->mLastOverFrame) {
// if the frame is associated with a subdocument,
// tell the subdocument that we're moving out of it
nsSubDocumentFrame* subdocFrame = do_QueryFrame(wrapper->mLastOverFrame.GetFrame());
if (subdocFrame) {
nsCOMPtr<nsIDocShell> docshell;
subdocFrame->GetDocShell(getter_AddRefs(docshell));
if (docshell) {
RefPtr<nsPresContext> presContext;
docshell->GetPresContext(getter_AddRefs(presContext));
if (presContext) {
EventStateManager* kidESM = presContext->EventStateManager();
// Not moving into any element in this subdocument
kidESM->NotifyMouseOut(aMouseEvent, nullptr);
}
}
}
}
// That could have caused DOM events which could wreak havoc. Reverify
// things and be careful.
if (!wrapper->mLastOverElement)
return;
// Store the first mouseOut event we fire and don't refire mouseOut
// to that element while the first mouseOut is still ongoing.
wrapper->mFirstOutEventElement = wrapper->mLastOverElement;
// Don't touch hover state if aMovingInto is non-null. Caller will update
// hover state itself, and we have optimizations for hover switching between
// two nearby elements both deep in the DOM tree that would be defeated by
// switching the hover state to null here.
bool isPointer = aMouseEvent->mClass == ePointerEventClass;
if (!aMovingInto && !isPointer) {
// Unset :hover
SetContentState(nullptr, NS_EVENT_STATE_HOVER);
}
EnterLeaveDispatcher leaveDispatcher(this, wrapper->mLastOverElement,
aMovingInto, aMouseEvent,
isPointer ? ePointerLeave : eMouseLeave);
// Fire mouseout
DispatchMouseOrPointerEvent(aMouseEvent, isPointer ? ePointerOut : eMouseOut,
wrapper->mLastOverElement, aMovingInto);
leaveDispatcher.Dispatch();
wrapper->mLastOverFrame = nullptr;
wrapper->mLastOverElement = nullptr;
// Turn recursion protection back off
wrapper->mFirstOutEventElement = nullptr;
}
void
EventStateManager::NotifyMouseOver(WidgetMouseEvent* aMouseEvent,
nsIContent* aContent)
{
NS_ASSERTION(aContent, "Mouse must be over something");
RefPtr<OverOutElementsWrapper> wrapper = GetWrapperByEventID(aMouseEvent);
if (!wrapper || wrapper->mLastOverElement == aContent)
return;
// Before firing mouseover, check for recursion
if (aContent == wrapper->mFirstOverEventElement)
return;
// Check to see if we're a subdocument and if so update the parent
// document's ESM state to indicate that the mouse is over the
// content associated with our subdocument.
EnsureDocument(mPresContext);
if (nsIDocument *parentDoc = mDocument->GetParentDocument()) {
if (nsIContent *docContent = parentDoc->FindContentForSubDocument(mDocument)) {
if (nsIPresShell *parentShell = parentDoc->GetShell()) {
EventStateManager* parentESM =
parentShell->GetPresContext()->EventStateManager();
parentESM->NotifyMouseOver(aMouseEvent, docContent);
}
}
}
// Firing the DOM event in the parent document could cause all kinds
// of havoc. Reverify and take care.
if (wrapper->mLastOverElement == aContent)
return;
// Remember mLastOverElement as the related content for the
// DispatchMouseOrPointerEvent() call below, since NotifyMouseOut() resets it, bug 298477.
nsCOMPtr<nsIContent> lastOverElement = wrapper->mLastOverElement;
bool isPointer = aMouseEvent->mClass == ePointerEventClass;
EnterLeaveDispatcher enterDispatcher(this, aContent, lastOverElement,
aMouseEvent,
isPointer ? ePointerEnter : eMouseEnter);
NotifyMouseOut(aMouseEvent, aContent);
// Store the first mouseOver event we fire and don't refire mouseOver
// to that element while the first mouseOver is still ongoing.
wrapper->mFirstOverEventElement = aContent;
if (!isPointer) {
SetContentState(aContent, NS_EVENT_STATE_HOVER);
}
// Fire mouseover
wrapper->mLastOverFrame =
DispatchMouseOrPointerEvent(aMouseEvent,
isPointer ? ePointerOver : eMouseOver,
aContent, lastOverElement);
enterDispatcher.Dispatch();
wrapper->mLastOverElement = aContent;
// Turn recursion protection back off
wrapper->mFirstOverEventElement = nullptr;
}
// Returns the center point of the window's client area. This is
// in widget coordinates, i.e. relative to the widget's top-left
// corner, not in screen coordinates, the same units that UIEvent::
// refpoint is in. It may not be the exact center of the window if
// the platform requires rounding the coordinate.
static LayoutDeviceIntPoint
GetWindowClientRectCenter(nsIWidget* aWidget)
{
NS_ENSURE_TRUE(aWidget, LayoutDeviceIntPoint(0, 0));
LayoutDeviceIntRect rect = aWidget->GetClientBounds();
LayoutDeviceIntPoint point(rect.x + rect.width / 2,
rect.y + rect.height / 2);
int32_t round = aWidget->RoundsWidgetCoordinatesTo();
point.x = point.x / round * round;
point.y = point.y / round * round;
return point - aWidget->WidgetToScreenOffset();
}
void
EventStateManager::GeneratePointerEnterExit(EventMessage aMessage,
WidgetMouseEvent* aEvent)
{
WidgetPointerEvent pointerEvent(*aEvent);
pointerEvent.mMessage = aMessage;
GenerateMouseEnterExit(&pointerEvent);
}
void
EventStateManager::GenerateMouseEnterExit(WidgetMouseEvent* aMouseEvent)
{
EnsureDocument(mPresContext);
if (!mDocument)
return;
// Hold onto old target content through the event and reset after.
nsCOMPtr<nsIContent> targetBeforeEvent = mCurrentTargetContent;
switch(aMouseEvent->mMessage) {
case eMouseMove:
{
// Mouse movement is reported on the MouseEvent.movement{X,Y} fields.
// Movement is calculated in UIEvent::GetMovementPoint() as:
// previous_mousemove_mRefPoint - current_mousemove_mRefPoint.
if (sIsPointerLocked && aMouseEvent->mWidget) {
// The pointer is locked. If the pointer is not located at the center of
// the window, dispatch a synthetic mousemove to return the pointer there.
// Doing this between "real" pointer moves gives the impression that the
// (locked) pointer can continue moving and won't stop at the screen
// boundary. We cancel the synthetic event so that we don't end up
// dispatching the centering move event to content.
LayoutDeviceIntPoint center =
GetWindowClientRectCenter(aMouseEvent->mWidget);
aMouseEvent->mLastRefPoint = center;
if (aMouseEvent->mRefPoint != center) {
// Mouse move doesn't finish at the center of the window. Dispatch a
// synthetic native mouse event to move the pointer back to the center
// of the window, to faciliate more movement. But first, record that
// we've dispatched a synthetic mouse movement, so we can cancel it
// in the other branch here.
sSynthCenteringPoint = center;
aMouseEvent->mWidget->SynthesizeNativeMouseMove(
center + aMouseEvent->mWidget->WidgetToScreenOffset(), nullptr);
} else if (aMouseEvent->mRefPoint == sSynthCenteringPoint) {
// This is the "synthetic native" event we dispatched to re-center the
// pointer. Cancel it so we don't expose the centering move to content.
aMouseEvent->StopPropagation();
// Clear sSynthCenteringPoint so we don't cancel other events
// targeted at the center.
sSynthCenteringPoint = kInvalidRefPoint;
}
} else if (sLastRefPoint == kInvalidRefPoint) {
// We don't have a valid previous mousemove mRefPoint. This is either
// the first move we've encountered, or the mouse has just re-entered
// the application window. We should report (0,0) movement for this
// case, so make the current and previous mRefPoints the same.
aMouseEvent->mLastRefPoint = aMouseEvent->mRefPoint;
} else {
aMouseEvent->mLastRefPoint = sLastRefPoint;
}
// Update the last known mRefPoint with the current mRefPoint.
sLastRefPoint = aMouseEvent->mRefPoint;
}
MOZ_FALLTHROUGH;
case ePointerMove:
case ePointerDown:
case ePointerGotCapture:
{
// Get the target content target (mousemove target == mouseover target)
nsCOMPtr<nsIContent> targetElement = GetEventTargetContent(aMouseEvent);
if (!targetElement) {
// We're always over the document root, even if we're only
// over dead space in a page (whose frame is not associated with
// any content) or in print preview dead space
targetElement = mDocument->GetRootElement();
}
if (targetElement) {
NotifyMouseOver(aMouseEvent, targetElement);
}
}
break;
case ePointerUp:
{
// Get the target content target (mousemove target == mouseover target)
nsCOMPtr<nsIContent> targetElement = GetEventTargetContent(aMouseEvent);
if (!targetElement) {
// We're always over the document root, even if we're only
// over dead space in a page (whose frame is not associated with
// any content) or in print preview dead space
targetElement = mDocument->GetRootElement();
}
if (targetElement) {
RefPtr<OverOutElementsWrapper> helper = GetWrapperByEventID(aMouseEvent);
if (helper) {
helper->mLastOverElement = targetElement;
}
NotifyMouseOut(aMouseEvent, nullptr);
}
}
break;
case ePointerLeave:
case ePointerCancel:
case eMouseExitFromWidget:
{
// This is actually the window mouse exit or pointer leave event. We're not moving
// into any new element.
RefPtr<OverOutElementsWrapper> helper = GetWrapperByEventID(aMouseEvent);
if (helper && helper->mLastOverFrame &&
nsContentUtils::GetTopLevelWidget(aMouseEvent->mWidget) !=
nsContentUtils::GetTopLevelWidget(helper->mLastOverFrame->GetNearestWidget())) {
// the Mouse/PointerOut event widget doesn't have same top widget with
// mLastOverFrame, it's a spurious event for mLastOverFrame
break;
}
// Reset sLastRefPoint, so that we'll know not to report any
// movement the next time we re-enter the window.
sLastRefPoint = kInvalidRefPoint;
NotifyMouseOut(aMouseEvent, nullptr);
}
break;
default:
break;
}
// reset mCurretTargetContent to what it was
mCurrentTargetContent = targetBeforeEvent;
}
OverOutElementsWrapper*
EventStateManager::GetWrapperByEventID(WidgetMouseEvent* aEvent)
{
WidgetPointerEvent* pointer = aEvent->AsPointerEvent();
if (!pointer) {
MOZ_ASSERT(aEvent->AsMouseEvent() != nullptr);
if (!mMouseEnterLeaveHelper) {
mMouseEnterLeaveHelper = new OverOutElementsWrapper();
}
return mMouseEnterLeaveHelper;
}
return mPointersEnterLeaveHelper.LookupForAdd(pointer->pointerId).OrInsert(
[] () { return new OverOutElementsWrapper(); });
}
/* static */ void
EventStateManager::SetPointerLock(nsIWidget* aWidget,
nsIContent* aElement)
{
// NOTE: aElement will be nullptr when unlocking.
sIsPointerLocked = !!aElement;
// Reset mouse wheel transaction
WheelTransaction::EndTransaction();
// Deal with DnD events
nsCOMPtr<nsIDragService> dragService =
do_GetService("@mozilla.org/widget/dragservice;1");
if (sIsPointerLocked) {
MOZ_ASSERT(aWidget, "Locking pointer requires a widget");
// Store the last known ref point so we can reposition the pointer after unlock.
sPreLockPoint = sLastRefPoint;
// Fire a synthetic mouse move to ensure event state is updated. We first
// set the mouse to the center of the window, so that the mouse event
// doesn't report any movement.
sLastRefPoint = GetWindowClientRectCenter(aWidget);
aWidget->SynthesizeNativeMouseMove(
sLastRefPoint + aWidget->WidgetToScreenOffset(), nullptr);
// Suppress DnD
if (dragService) {
dragService->Suppress();
}
} else {
// Unlocking, so return pointer to the original position by firing a
// synthetic mouse event. We first reset sLastRefPoint to its
// pre-pointerlock position, so that the synthetic mouse event reports
// no movement.
sLastRefPoint = sPreLockPoint;
// Reset SynthCenteringPoint to invalid so that next time we start
// locking pointer, it has its initial value.
sSynthCenteringPoint = kInvalidRefPoint;
if (aWidget) {
aWidget->SynthesizeNativeMouseMove(
sPreLockPoint + aWidget->WidgetToScreenOffset(), nullptr);
}
// Unsuppress DnD
if (dragService) {
dragService->Unsuppress();
}
}
}
void
EventStateManager::GenerateDragDropEnterExit(nsPresContext* aPresContext,
WidgetDragEvent* aDragEvent)
{
//Hold onto old target content through the event and reset after.
nsCOMPtr<nsIContent> targetBeforeEvent = mCurrentTargetContent;
switch(aDragEvent->mMessage) {
case eDragOver:
{
// when dragging from one frame to another, events are fired in the
// order: dragexit, dragenter, dragleave
if (sLastDragOverFrame != mCurrentTarget) {
//We'll need the content, too, to check if it changed separately from the frames.
nsCOMPtr<nsIContent> lastContent;
nsCOMPtr<nsIContent> targetContent;
mCurrentTarget->GetContentForEvent(aDragEvent,
getter_AddRefs(targetContent));
if (sLastDragOverFrame) {
//The frame has changed but the content may not have. Check before dispatching to content
sLastDragOverFrame->GetContentForEvent(aDragEvent,
getter_AddRefs(lastContent));
FireDragEnterOrExit(sLastDragOverFrame->PresContext(),
aDragEvent, eDragExit,
targetContent, lastContent, sLastDragOverFrame);
nsIContent* target = sLastDragOverFrame ? sLastDragOverFrame.GetFrame()->GetContent() : nullptr;
if (IsRemoteTarget(target)) {
// Dragging something and moving from web content to chrome only
// fires dragexit and dragleave to xul:browser. We have to forward
// dragexit to sLastDragOverFrame when its content is a remote
// target. We don't forward dragleave since it's generated from
// dragexit.
WidgetDragEvent remoteEvent(aDragEvent->IsTrusted(), eDragExit,
aDragEvent->mWidget);
remoteEvent.AssignDragEventData(*aDragEvent, true);
nsEventStatus remoteStatus = nsEventStatus_eIgnore;
HandleCrossProcessEvent(&remoteEvent, &remoteStatus);
}
}
AutoWeakFrame currentTraget = mCurrentTarget;
FireDragEnterOrExit(aPresContext, aDragEvent, eDragEnter,
lastContent, targetContent, currentTraget);
if (sLastDragOverFrame) {
FireDragEnterOrExit(sLastDragOverFrame->PresContext(),
aDragEvent, eDragLeave,
targetContent, lastContent, sLastDragOverFrame);
}
sLastDragOverFrame = mCurrentTarget;
}
}
break;
case eDragExit:
{
//This is actually the window mouse exit event.
if (sLastDragOverFrame) {
nsCOMPtr<nsIContent> lastContent;
sLastDragOverFrame->GetContentForEvent(aDragEvent,
getter_AddRefs(lastContent));
RefPtr<nsPresContext> lastDragOverFramePresContext = sLastDragOverFrame->PresContext();
FireDragEnterOrExit(lastDragOverFramePresContext,
aDragEvent, eDragExit,
nullptr, lastContent, sLastDragOverFrame);
FireDragEnterOrExit(lastDragOverFramePresContext,
aDragEvent, eDragLeave,
nullptr, lastContent, sLastDragOverFrame);
sLastDragOverFrame = nullptr;
}
}
break;
default:
break;
}
//reset mCurretTargetContent to what it was
mCurrentTargetContent = targetBeforeEvent;
// Now flush all pending notifications, for better responsiveness.
FlushPendingEvents(aPresContext);
}
void
EventStateManager::FireDragEnterOrExit(nsPresContext* aPresContext,
WidgetDragEvent* aDragEvent,
EventMessage aMessage,
nsIContent* aRelatedTarget,
nsIContent* aTargetContent,
AutoWeakFrame& aTargetFrame)
{
MOZ_ASSERT(aMessage == eDragLeave || aMessage == eDragExit ||
aMessage == eDragEnter);
nsEventStatus status = nsEventStatus_eIgnore;
WidgetDragEvent event(aDragEvent->IsTrusted(), aMessage, aDragEvent->mWidget);
event.AssignDragEventData(*aDragEvent, true);
mCurrentTargetContent = aTargetContent;
if (aTargetContent != aRelatedTarget) {
//XXX This event should still go somewhere!!
if (aTargetContent) {
EventDispatcher::Dispatch(aTargetContent, aPresContext, &event,
nullptr, &status);
}
// adjust the drag hover if the dragenter event was cancelled or this is a drag exit
if (status == nsEventStatus_eConsumeNoDefault || aMessage == eDragExit) {
SetContentState((aMessage == eDragEnter) ? aTargetContent : nullptr,
NS_EVENT_STATE_DRAGOVER);
}
// collect any changes to moz cursor settings stored in the event's
// data transfer.
UpdateDragDataTransfer(&event);
}
// Finally dispatch the event to the frame
if (aTargetFrame)
aTargetFrame->HandleEvent(aPresContext, &event, &status);
}
void
EventStateManager::UpdateDragDataTransfer(WidgetDragEvent* dragEvent)
{
NS_ASSERTION(dragEvent, "drag event is null in UpdateDragDataTransfer!");
if (!dragEvent->mDataTransfer) {
return;
}
nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession();
if (dragSession) {
// the initial dataTransfer is the one from the dragstart event that
// was set on the dragSession when the drag began.
nsCOMPtr<nsIDOMDataTransfer> initialDataTransfer;
dragSession->GetDataTransfer(getter_AddRefs(initialDataTransfer));
if (initialDataTransfer) {
// retrieve the current moz cursor setting and save it.
nsAutoString mozCursor;
dragEvent->mDataTransfer->GetMozCursor(mozCursor);
initialDataTransfer->SetMozCursor(mozCursor);
}
}
}
nsresult
EventStateManager::SetClickCount(WidgetMouseEvent* aEvent,
nsEventStatus* aStatus)
{
nsCOMPtr<nsIContent> mouseContent;
nsIContent* mouseContentParent = nullptr;
if (mCurrentTarget) {
mCurrentTarget->GetContentForEvent(aEvent, getter_AddRefs(mouseContent));
}
if (mouseContent) {
if (mouseContent->IsNodeOfType(nsINode::eTEXT)) {
mouseContent = mouseContent->GetParent();
}
if (mouseContent && mouseContent->IsRootOfNativeAnonymousSubtree()) {
mouseContentParent = mouseContent->GetParent();
}
}
switch (aEvent->button) {
case WidgetMouseEvent::eLeftButton:
if (aEvent->mMessage == eMouseDown) {
mLastLeftMouseDownContent = mouseContent;
mLastLeftMouseDownContentParent = mouseContentParent;
} else if (aEvent->mMessage == eMouseUp) {
if (mLastLeftMouseDownContent == mouseContent ||
mLastLeftMouseDownContentParent == mouseContent ||
mLastLeftMouseDownContent == mouseContentParent) {
aEvent->mClickCount = mLClickCount;
mLClickCount = 0;
} else {
aEvent->mClickCount = 0;
}
mLastLeftMouseDownContent = nullptr;
mLastLeftMouseDownContentParent = nullptr;
}
break;
case WidgetMouseEvent::eMiddleButton:
if (aEvent->mMessage == eMouseDown) {
mLastMiddleMouseDownContent = mouseContent;
mLastMiddleMouseDownContentParent = mouseContentParent;
} else if (aEvent->mMessage == eMouseUp) {
if (mLastMiddleMouseDownContent == mouseContent ||
mLastMiddleMouseDownContentParent == mouseContent ||
mLastMiddleMouseDownContent == mouseContentParent) {
aEvent->mClickCount = mMClickCount;
mMClickCount = 0;
} else {
aEvent->mClickCount = 0;
}
mLastMiddleMouseDownContent = nullptr;
mLastMiddleMouseDownContentParent = nullptr;
}
break;
case WidgetMouseEvent::eRightButton:
if (aEvent->mMessage == eMouseDown) {
mLastRightMouseDownContent = mouseContent;
mLastRightMouseDownContentParent = mouseContentParent;
} else if (aEvent->mMessage == eMouseUp) {
if (mLastRightMouseDownContent == mouseContent ||
mLastRightMouseDownContentParent == mouseContent ||
mLastRightMouseDownContent == mouseContentParent) {
aEvent->mClickCount = mRClickCount;
mRClickCount = 0;
} else {
aEvent->mClickCount = 0;
}
mLastRightMouseDownContent = nullptr;
mLastRightMouseDownContentParent = nullptr;
}
break;
}
return NS_OK;
}
nsresult
EventStateManager::InitAndDispatchClickEvent(WidgetMouseEvent* aEvent,
nsEventStatus* aStatus,
EventMessage aMessage,
nsIPresShell* aPresShell,
nsIContent* aMouseTarget,
AutoWeakFrame aCurrentTarget,
bool aNoContentDispatch)
{
WidgetMouseEvent event(aEvent->IsTrusted(), aMessage,
aEvent->mWidget, WidgetMouseEvent::eReal);
event.mRefPoint = aEvent->mRefPoint;
event.mClickCount = aEvent->mClickCount;
event.mModifiers = aEvent->mModifiers;
event.buttons = aEvent->buttons;
event.mTime = aEvent->mTime;
event.mTimeStamp = aEvent->mTimeStamp;
event.mFlags.mNoContentDispatch = aNoContentDispatch;
event.button = aEvent->button;
event.pointerId = aEvent->pointerId;
event.inputSource = aEvent->inputSource;
return aPresShell->HandleEventWithTarget(&event, aCurrentTarget,
aMouseTarget, aStatus);
}
nsresult
EventStateManager::CheckForAndDispatchClick(WidgetMouseEvent* aEvent,
nsEventStatus* aStatus)
{
nsresult ret = NS_OK;
//If mouse is still over same element, clickcount will be > 1.
//If it has moved it will be zero, so no click.
if (aEvent->mClickCount) {
//Check that the window isn't disabled before firing a click
//(see bug 366544).
if (aEvent->mWidget && !aEvent->mWidget->IsEnabled()) {
return ret;
}
//fire click
bool notDispatchToContents =
(aEvent->button == WidgetMouseEvent::eMiddleButton ||
aEvent->button == WidgetMouseEvent::eRightButton);
bool fireAuxClick = notDispatchToContents;
nsCOMPtr<nsIPresShell> presShell = mPresContext->GetPresShell();
if (presShell) {
nsCOMPtr<nsIContent> mouseContent = GetEventTargetContent(aEvent);
// Click events apply to *elements* not nodes. At this point the target
// content may have been reset to some non-element content, and so we need
// to walk up the closest ancestor element, just like we do in
// nsPresShell::HandlePositionedEvent.
while (mouseContent && !mouseContent->IsElement()) {
mouseContent = mouseContent->GetParent();
}
if (!mouseContent && !mCurrentTarget) {
return NS_OK;
}
// HandleEvent clears out mCurrentTarget which we might need again
AutoWeakFrame currentTarget = mCurrentTarget;
ret = InitAndDispatchClickEvent(aEvent, aStatus, eMouseClick,
presShell, mouseContent, currentTarget,
notDispatchToContents);
if (NS_SUCCEEDED(ret) && aEvent->mClickCount == 2 &&
mouseContent && mouseContent->IsInComposedDoc()) {
//fire double click
ret = InitAndDispatchClickEvent(aEvent, aStatus, eMouseDoubleClick,
presShell, mouseContent, currentTarget,
notDispatchToContents);
}
if (NS_SUCCEEDED(ret) && mouseContent && fireAuxClick &&
mouseContent->IsInComposedDoc()) {
ret = InitAndDispatchClickEvent(aEvent, aStatus, eMouseAuxClick,
presShell, mouseContent, currentTarget,
false);
}
}
}
return ret;
}
nsIFrame*
EventStateManager::GetEventTarget()
{
nsIPresShell *shell;
if (mCurrentTarget ||
!mPresContext ||
!(shell = mPresContext->GetPresShell())) {
return mCurrentTarget;
}
if (mCurrentTargetContent) {
mCurrentTarget = mPresContext->GetPrimaryFrameFor(mCurrentTargetContent);
if (mCurrentTarget) {
return mCurrentTarget;
}
}
nsIFrame* frame = shell->GetEventTargetFrame();
return (mCurrentTarget = frame);
}
already_AddRefed<nsIContent>
EventStateManager::GetEventTargetContent(WidgetEvent* aEvent)
{
if (aEvent &&
(aEvent->mMessage == eFocus || aEvent->mMessage == eBlur)) {
nsCOMPtr<nsIContent> content = GetFocusedContent();
return content.forget();
}
if (mCurrentTargetContent) {
nsCOMPtr<nsIContent> content = mCurrentTargetContent;
return content.forget();
}
nsCOMPtr<nsIContent> content;
nsIPresShell *presShell = mPresContext->GetPresShell();
if (presShell) {
content = presShell->GetEventTargetContent(aEvent);
}
// Some events here may set mCurrentTarget but not set the corresponding
// event target in the PresShell.
if (!content && mCurrentTarget) {
mCurrentTarget->GetContentForEvent(aEvent, getter_AddRefs(content));
}
return content.forget();
}
static Element*
GetLabelTarget(nsIContent* aPossibleLabel)
{
mozilla::dom::HTMLLabelElement* label =
mozilla::dom::HTMLLabelElement::FromContent(aPossibleLabel);
if (!label)
return nullptr;
return label->GetLabeledElement();
}
static nsIContent*
FindCommonAncestor(nsIContent* aNode1, nsIContent* aNode2)
{
if (!aNode1 || !aNode2) {
return nullptr;
}
return nsContentUtils::GetCommonFlattenedTreeAncestor(aNode1, aNode2);
}
/* static */
void
EventStateManager::SetFullScreenState(Element* aElement, bool aIsFullScreen)
{
DoStateChange(aElement, NS_EVENT_STATE_FULL_SCREEN, aIsFullScreen);
}
/* static */
inline void
EventStateManager::DoStateChange(Element* aElement, EventStates aState,
bool aAddState)
{
if (aAddState) {
aElement->AddStates(aState);
} else {
aElement->RemoveStates(aState);
}
}
/* static */
inline void
EventStateManager::DoStateChange(nsIContent* aContent, EventStates aState,
bool aStateAdded)
{
if (aContent->IsElement()) {
DoStateChange(aContent->AsElement(), aState, aStateAdded);
}
}
/* static */
void
EventStateManager::UpdateAncestorState(nsIContent* aStartNode,
nsIContent* aStopBefore,
EventStates aState,
bool aAddState)
{
for (; aStartNode && aStartNode != aStopBefore;
aStartNode = aStartNode->GetFlattenedTreeParent()) {
// We might be starting with a non-element (e.g. a text node) and
// if someone is doing something weird might be ending with a
// non-element too (e.g. a document fragment)
if (!aStartNode->IsElement()) {
continue;
}
Element* element = aStartNode->AsElement();
DoStateChange(element, aState, aAddState);
Element* labelTarget = GetLabelTarget(element);
if (labelTarget) {
DoStateChange(labelTarget, aState, aAddState);
}
}
if (aAddState) {
// We might be in a situation where a node was in hover both
// because it was hovered and because the label for it was
// hovered, and while we stopped hovering the node the label is
// still hovered. Or we might have had two nested labels for the
// same node, and while one is no longer hovered the other still
// is. In that situation, the label that's still hovered will be
// aStopBefore or some ancestor of it, and the call we just made
// to UpdateAncestorState with aAddState = false would have
// removed the hover state from the node. But the node should
// still be in hover state. To handle this situation we need to
// keep walking up the tree and any time we find a label mark its
// corresponding node as still in our state.
for ( ; aStartNode; aStartNode = aStartNode->GetFlattenedTreeParent()) {
if (!aStartNode->IsElement()) {
continue;
}
Element* labelTarget = GetLabelTarget(aStartNode->AsElement());
if (labelTarget && !labelTarget->State().HasState(aState)) {
DoStateChange(labelTarget, aState, true);
}
}
}
}
bool
EventStateManager::SetContentState(nsIContent* aContent, EventStates aState)
{
// We manage 4 states here: ACTIVE, HOVER, DRAGOVER, URLTARGET
// The input must be exactly one of them.
NS_PRECONDITION(aState == NS_EVENT_STATE_ACTIVE ||
aState == NS_EVENT_STATE_HOVER ||
aState == NS_EVENT_STATE_DRAGOVER ||
aState == NS_EVENT_STATE_URLTARGET,
"Unexpected state");
nsCOMPtr<nsIContent> notifyContent1;
nsCOMPtr<nsIContent> notifyContent2;
bool updateAncestors;
if (aState == NS_EVENT_STATE_HOVER || aState == NS_EVENT_STATE_ACTIVE) {
// Hover and active are hierarchical
updateAncestors = true;
// check to see that this state is allowed by style. Check dragover too?
// XXX Is this even what we want?
if (mCurrentTarget)
{
const nsStyleUserInterface* ui = mCurrentTarget->StyleUserInterface();
if (ui->mUserInput == StyleUserInput::None) {
return false;
}
}
if (aState == NS_EVENT_STATE_ACTIVE) {
// Editable content can never become active since their default actions
// are disabled. Watch out for editable content in native anonymous
// subtrees though, as they belong to text controls.
if (aContent && aContent->IsEditable() &&
!aContent->IsInNativeAnonymousSubtree()) {
aContent = nullptr;
}
if (aContent != mActiveContent) {
notifyContent1 = aContent;
notifyContent2 = mActiveContent;
mActiveContent = aContent;
}
} else {
NS_ASSERTION(aState == NS_EVENT_STATE_HOVER, "How did that happen?");
nsIContent* newHover;
if (mPresContext->IsDynamic()) {
newHover = aContent;
} else {
NS_ASSERTION(!aContent ||
aContent->GetComposedDoc() ==
mPresContext->PresShell()->GetDocument(),
"Unexpected document");
nsIFrame *frame = aContent ? aContent->GetPrimaryFrame() : nullptr;
if (frame && nsLayoutUtils::IsViewportScrollbarFrame(frame)) {
// The scrollbars of viewport should not ignore the hover state.
// Because they are *not* the content of the web page.
newHover = aContent;
} else {
// All contents of the web page should ignore the hover state.
newHover = nullptr;
}
}
if (newHover != mHoverContent) {
notifyContent1 = newHover;
notifyContent2 = mHoverContent;
mHoverContent = newHover;
}
}
} else {
updateAncestors = false;
if (aState == NS_EVENT_STATE_DRAGOVER) {
if (aContent != sDragOverContent) {
notifyContent1 = aContent;
notifyContent2 = sDragOverContent;
sDragOverContent = aContent;
}
} else if (aState == NS_EVENT_STATE_URLTARGET) {
if (aContent != mURLTargetContent) {
notifyContent1 = aContent;
notifyContent2 = mURLTargetContent;
mURLTargetContent = aContent;
}
}
}
// We need to keep track of which of notifyContent1 and notifyContent2 is
// getting the state set and which is getting it unset. If both are
// non-null, then notifyContent1 is having the state set and notifyContent2
// is having it unset. But if one of them is null, we need to keep track of
// the right thing for notifyContent1 explicitly.
bool content1StateSet = true;
if (!notifyContent1) {
// This is ok because FindCommonAncestor wouldn't find anything
// anyway if notifyContent1 is null.
notifyContent1 = notifyContent2;
notifyContent2 = nullptr;
content1StateSet = false;
}
if (notifyContent1 && mPresContext) {
EnsureDocument(mPresContext);
if (mDocument) {
nsAutoScriptBlocker scriptBlocker;
if (updateAncestors) {
nsCOMPtr<nsIContent> commonAncestor =
FindCommonAncestor(notifyContent1, notifyContent2);
if (notifyContent2) {
// It's very important to first notify the state removal and
// then the state addition, because due to labels it's
// possible that we're removing state from some element but
// then adding it again (say because mHoverContent changed
// from a control to its label).
UpdateAncestorState(notifyContent2, commonAncestor, aState, false);
}
UpdateAncestorState(notifyContent1, commonAncestor, aState,
content1StateSet);
} else {
if (notifyContent2) {
DoStateChange(notifyContent2, aState, false);
}
DoStateChange(notifyContent1, aState, content1StateSet);
}
}
}
return true;
}
void
EventStateManager::ResetLastOverForContent(
const uint32_t& aIdx,
RefPtr<OverOutElementsWrapper>& aElemWrapper,
nsIContent* aContent)
{
if (aElemWrapper && aElemWrapper->mLastOverElement &&
nsContentUtils::ContentIsDescendantOf(aElemWrapper->mLastOverElement,
aContent)) {
aElemWrapper->mLastOverElement = nullptr;
}
}
void
EventStateManager::ContentRemoved(nsIDocument* aDocument, nsIContent* aContent)
{
/*
* Anchor and area elements when focused or hovered might make the UI to show
* the current link. We want to make sure that the UI gets informed when they
* are actually removed from the DOM.
*/
if (aContent->IsAnyOfHTMLElements(nsGkAtoms::a, nsGkAtoms::area) &&
(aContent->AsElement()->State().HasAtLeastOneOfStates(NS_EVENT_STATE_FOCUS |
NS_EVENT_STATE_HOVER))) {
nsGenericHTMLElement* element = static_cast<nsGenericHTMLElement*>(aContent);
element->LeaveLink(
element->GetPresContext(nsGenericHTMLElement::eForComposedDoc));
}
IMEStateManager::OnRemoveContent(mPresContext, aContent);
// inform the focus manager that the content is being removed. If this
// content is focused, the focus will be removed without firing events.
nsFocusManager* fm = nsFocusManager::GetFocusManager();
if (fm)
fm->ContentRemoved(aDocument, aContent);
if (mHoverContent &&
nsContentUtils::ContentIsDescendantOf(mHoverContent, aContent)) {
// Since hover is hierarchical, set the current hover to the
// content's parent node.
SetContentState(aContent->GetParent(), NS_EVENT_STATE_HOVER);
}
if (mActiveContent &&
nsContentUtils::ContentIsDescendantOf(mActiveContent, aContent)) {
// Active is hierarchical, so set the current active to the
// content's parent node.
SetContentState(aContent->GetParent(), NS_EVENT_STATE_ACTIVE);
}
if (sDragOverContent &&
sDragOverContent->OwnerDoc() == aContent->OwnerDoc() &&
nsContentUtils::ContentIsDescendantOf(sDragOverContent, aContent)) {
sDragOverContent = nullptr;
}
// See bug 292146 for why we want to null this out
ResetLastOverForContent(0, mMouseEnterLeaveHelper, aContent);
for (auto iter = mPointersEnterLeaveHelper.Iter();
!iter.Done();
iter.Next()) {
ResetLastOverForContent(iter.Key(), iter.Data(), aContent);
}
}
bool
EventStateManager::EventStatusOK(WidgetGUIEvent* aEvent)
{
return !(aEvent->mMessage == eMouseDown &&
aEvent->AsMouseEvent()->button == WidgetMouseEvent::eLeftButton &&
!sNormalLMouseEventInProcess);
}
//-------------------------------------------
// Access Key Registration
//-------------------------------------------
void
EventStateManager::RegisterAccessKey(nsIContent* aContent, uint32_t aKey)
{
if (aContent && mAccessKeys.IndexOf(aContent) == -1)
mAccessKeys.AppendObject(aContent);
}
void
EventStateManager::UnregisterAccessKey(nsIContent* aContent, uint32_t aKey)
{
if (aContent)
mAccessKeys.RemoveObject(aContent);
}
uint32_t
EventStateManager::GetRegisteredAccessKey(nsIContent* aContent)
{
MOZ_ASSERT(aContent);
if (mAccessKeys.IndexOf(aContent) == -1)
return 0;
nsAutoString accessKey;
aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, accessKey);
return accessKey.First();
}
void
EventStateManager::EnsureDocument(nsPresContext* aPresContext)
{
if (!mDocument)
mDocument = aPresContext->Document();
}
void
EventStateManager::FlushPendingEvents(nsPresContext* aPresContext)
{
NS_PRECONDITION(nullptr != aPresContext, "nullptr ptr");
nsIPresShell *shell = aPresContext->GetPresShell();
if (shell) {
shell->FlushPendingNotifications(FlushType::InterruptibleLayout);
}
}
nsIContent*
EventStateManager::GetFocusedContent()
{
nsIFocusManager* fm = nsFocusManager::GetFocusManager();
EnsureDocument(mPresContext);
if (!fm || !mDocument)
return nullptr;
nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
return nsFocusManager::GetFocusedDescendant(mDocument->GetWindow(), false,
getter_AddRefs(focusedWindow));
}
//-------------------------------------------------------
// Return true if the docshell is visible
bool
EventStateManager::IsShellVisible(nsIDocShell* aShell)
{
NS_ASSERTION(aShell, "docshell is null");
nsCOMPtr<nsIBaseWindow> basewin = do_QueryInterface(aShell);
if (!basewin)
return true;
bool isVisible = true;
basewin->GetVisibility(&isVisible);
// We should be doing some additional checks here so that
// we don't tab into hidden tabs of tabbrowser. -bryner
return isVisible;
}
nsresult
EventStateManager::DoContentCommandEvent(WidgetContentCommandEvent* aEvent)
{
EnsureDocument(mPresContext);
NS_ENSURE_TRUE(mDocument, NS_ERROR_FAILURE);
nsCOMPtr<nsPIDOMWindowOuter> window(mDocument->GetWindow());
NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
nsCOMPtr<nsPIWindowRoot> root = window->GetTopWindowRoot();
NS_ENSURE_TRUE(root, NS_ERROR_FAILURE);
const char* cmd;
switch (aEvent->mMessage) {
case eContentCommandCut:
cmd = "cmd_cut";
break;
case eContentCommandCopy:
cmd = "cmd_copy";
break;
case eContentCommandPaste:
cmd = "cmd_paste";
break;
case eContentCommandDelete:
cmd = "cmd_delete";
break;
case eContentCommandUndo:
cmd = "cmd_undo";
break;
case eContentCommandRedo:
cmd = "cmd_redo";
break;
case eContentCommandPasteTransferable:
cmd = "cmd_pasteTransferable";
break;
case eContentCommandLookUpDictionary:
cmd = "cmd_lookUpDictionary";
break;
default:
return NS_ERROR_NOT_IMPLEMENTED;
}
nsCOMPtr<nsIController> controller;
nsresult rv = root->GetControllerForCommand(cmd, getter_AddRefs(controller));
NS_ENSURE_SUCCESS(rv, rv);
if (!controller) {
// When GetControllerForCommand succeeded but there is no controller, the
// command isn't supported.
aEvent->mIsEnabled = false;
} else {
bool canDoIt;
rv = controller->IsCommandEnabled(cmd, &canDoIt);
NS_ENSURE_SUCCESS(rv, rv);
aEvent->mIsEnabled = canDoIt;
if (canDoIt && !aEvent->mOnlyEnabledCheck) {
switch (aEvent->mMessage) {
case eContentCommandPasteTransferable: {
nsFocusManager* fm = nsFocusManager::GetFocusManager();
nsIContent* focusedContent = fm ? fm->GetFocusedContent() : nullptr;
RefPtr<TabParent> remote = TabParent::GetFrom(focusedContent);
if (remote) {
NS_ENSURE_TRUE(remote->Manager()->IsContentParent(), NS_ERROR_FAILURE);
nsCOMPtr<nsITransferable> transferable = aEvent->mTransferable;
IPCDataTransfer ipcDataTransfer;
ContentParent* cp = remote->Manager()->AsContentParent();
nsContentUtils::TransferableToIPCTransferable(transferable,
&ipcDataTransfer,
false, nullptr,
cp);
bool isPrivateData = false;
transferable->GetIsPrivateData(&isPrivateData);
nsCOMPtr<nsIPrincipal> requestingPrincipal;
transferable->GetRequestingPrincipal(getter_AddRefs(requestingPrincipal));
remote->SendPasteTransferable(ipcDataTransfer, isPrivateData,
IPC::Principal(requestingPrincipal));
rv = NS_OK;
} else {
nsCOMPtr<nsICommandController> commandController = do_QueryInterface(controller);
NS_ENSURE_STATE(commandController);
nsCOMPtr<nsICommandParams> params = do_CreateInstance("@mozilla.org/embedcomp/command-params;1", &rv);
NS_ENSURE_SUCCESS(rv, rv);
rv = params->SetISupportsValue("transferable", aEvent->mTransferable);
NS_ENSURE_SUCCESS(rv, rv);
rv = commandController->DoCommandWithParams(cmd, params);
}
break;
}
case eContentCommandLookUpDictionary: {
nsCOMPtr<nsICommandController> commandController =
do_QueryInterface(controller);
if (NS_WARN_IF(!commandController)) {
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsICommandParams> params =
do_CreateInstance("@mozilla.org/embedcomp/command-params;1", &rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = params->SetLongValue("x", aEvent->mRefPoint.x);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = params->SetLongValue("y", aEvent->mRefPoint.y);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = commandController->DoCommandWithParams(cmd, params);
break;
}
default:
rv = controller->DoCommand(cmd);
break;
}
NS_ENSURE_SUCCESS(rv, rv);
}
}
aEvent->mSucceeded = true;
return NS_OK;
}
nsresult
EventStateManager::DoContentCommandScrollEvent(
WidgetContentCommandEvent* aEvent)
{
NS_ENSURE_TRUE(mPresContext, NS_ERROR_NOT_AVAILABLE);
nsIPresShell* ps = mPresContext->GetPresShell();
NS_ENSURE_TRUE(ps, NS_ERROR_NOT_AVAILABLE);
NS_ENSURE_TRUE(aEvent->mScroll.mAmount != 0, NS_ERROR_INVALID_ARG);
nsIScrollableFrame::ScrollUnit scrollUnit;
switch (aEvent->mScroll.mUnit) {
case WidgetContentCommandEvent::eCmdScrollUnit_Line:
scrollUnit = nsIScrollableFrame::LINES;
break;
case WidgetContentCommandEvent::eCmdScrollUnit_Page:
scrollUnit = nsIScrollableFrame::PAGES;
break;
case WidgetContentCommandEvent::eCmdScrollUnit_Whole:
scrollUnit = nsIScrollableFrame::WHOLE;
break;
default:
return NS_ERROR_INVALID_ARG;
}
aEvent->mSucceeded = true;
nsIScrollableFrame* sf =
ps->GetScrollableFrameToScroll(nsIPresShell::eEither);
aEvent->mIsEnabled = sf ?
(aEvent->mScroll.mIsHorizontal ?
WheelHandlingUtils::CanScrollOn(sf, aEvent->mScroll.mAmount, 0) :
WheelHandlingUtils::CanScrollOn(sf, 0, aEvent->mScroll.mAmount)) : false;
if (!aEvent->mIsEnabled || aEvent->mOnlyEnabledCheck) {
return NS_OK;
}
nsIntPoint pt(0, 0);
if (aEvent->mScroll.mIsHorizontal) {
pt.x = aEvent->mScroll.mAmount;
} else {
pt.y = aEvent->mScroll.mAmount;
}
// The caller may want synchronous scrolling.
sf->ScrollBy(pt, scrollUnit, nsIScrollableFrame::INSTANT);
return NS_OK;
}
void
EventStateManager::SetActiveManager(EventStateManager* aNewESM,
nsIContent* aContent)
{
if (sActiveESM && aNewESM != sActiveESM) {
sActiveESM->SetContentState(nullptr, NS_EVENT_STATE_ACTIVE);
}
sActiveESM = aNewESM;
if (sActiveESM && aContent) {
sActiveESM->SetContentState(aContent, NS_EVENT_STATE_ACTIVE);
}
}
void
EventStateManager::ClearGlobalActiveContent(EventStateManager* aClearer)
{
if (aClearer) {
aClearer->SetContentState(nullptr, NS_EVENT_STATE_ACTIVE);
if (sDragOverContent) {
aClearer->SetContentState(nullptr, NS_EVENT_STATE_DRAGOVER);
}
}
if (sActiveESM && aClearer != sActiveESM) {
sActiveESM->SetContentState(nullptr, NS_EVENT_STATE_ACTIVE);
}
sActiveESM = nullptr;
}
/******************************************************************/
/* mozilla::EventStateManager::DeltaAccumulator */
/******************************************************************/
void
EventStateManager::DeltaAccumulator::InitLineOrPageDelta(
nsIFrame* aTargetFrame,
EventStateManager* aESM,
WidgetWheelEvent* aEvent)
{
MOZ_ASSERT(aESM);
MOZ_ASSERT(aEvent);
// Reset if the previous wheel event is too old.
if (!mLastTime.IsNull()) {
TimeDuration duration = TimeStamp::Now() - mLastTime;
if (duration.ToMilliseconds() > WheelTransaction::GetTimeoutTime()) {
Reset();
}
}
// If we have accumulated delta, we may need to reset it.
if (IsInTransaction()) {
// If wheel event type is changed, reset the values.
if (mHandlingDeltaMode != aEvent->mDeltaMode ||
mIsNoLineOrPageDeltaDevice != aEvent->mIsNoLineOrPageDelta) {
Reset();
} else {
// If the delta direction is changed, we should reset only the
// accumulated values.
if (mX && aEvent->mDeltaX && ((aEvent->mDeltaX > 0.0) != (mX > 0.0))) {
mX = mPendingScrollAmountX = 0.0;
}
if (mY && aEvent->mDeltaY && ((aEvent->mDeltaY > 0.0) != (mY > 0.0))) {
mY = mPendingScrollAmountY = 0.0;
}
}
}
mHandlingDeltaMode = aEvent->mDeltaMode;
mIsNoLineOrPageDeltaDevice = aEvent->mIsNoLineOrPageDelta;
// If it's handling neither a device that does not provide line or page deltas
// nor delta values multiplied by prefs, we must not modify lineOrPageDelta
// values.
if (!mIsNoLineOrPageDeltaDevice &&
!EventStateManager::WheelPrefs::GetInstance()->
NeedToComputeLineOrPageDelta(aEvent)) {
// Set the delta values to mX and mY. They would be used when above block
// resets mX/mY/mPendingScrollAmountX/mPendingScrollAmountY if the direction
// is changed.
// NOTE: We shouldn't accumulate the delta values, it might could cause
// overflow even though it's not a realistic situation.
if (aEvent->mDeltaX) {
mX = aEvent->mDeltaX;
}
if (aEvent->mDeltaY) {
mY = aEvent->mDeltaY;
}
mLastTime = TimeStamp::Now();
return;
}
mX += aEvent->mDeltaX;
mY += aEvent->mDeltaY;
if (mHandlingDeltaMode == nsIDOMWheelEvent::DOM_DELTA_PIXEL) {
// Records pixel delta values and init mLineOrPageDeltaX and
// mLineOrPageDeltaY for wheel events which are caused by pixel only
// devices. Ignore mouse wheel transaction for computing this. The
// lineOrPageDelta values will be used by dispatching legacy
// eMouseScrollEventClass (DOMMouseScroll) but not be used for scrolling
// of default action. The transaction should be used only for the default
// action.
nsIFrame* frame =
aESM->ComputeScrollTarget(aTargetFrame, aEvent,
COMPUTE_LEGACY_MOUSE_SCROLL_EVENT_TARGET);
nsPresContext* pc =
frame ? frame->PresContext() : aTargetFrame->PresContext();
nsIScrollableFrame* scrollTarget = do_QueryFrame(frame);
nsSize scrollAmount = aESM->GetScrollAmount(pc, aEvent, scrollTarget);
nsIntSize scrollAmountInCSSPixels(
nsPresContext::AppUnitsToIntCSSPixels(scrollAmount.width),
nsPresContext::AppUnitsToIntCSSPixels(scrollAmount.height));
aEvent->mLineOrPageDeltaX = RoundDown(mX) / scrollAmountInCSSPixels.width;
aEvent->mLineOrPageDeltaY = RoundDown(mY) / scrollAmountInCSSPixels.height;
mX -= aEvent->mLineOrPageDeltaX * scrollAmountInCSSPixels.width;
mY -= aEvent->mLineOrPageDeltaY * scrollAmountInCSSPixels.height;
} else {
aEvent->mLineOrPageDeltaX = RoundDown(mX);
aEvent->mLineOrPageDeltaY = RoundDown(mY);
mX -= aEvent->mLineOrPageDeltaX;
mY -= aEvent->mLineOrPageDeltaY;
}
mLastTime = TimeStamp::Now();
}
void
EventStateManager::DeltaAccumulator::Reset()
{
mX = mY = 0.0;
mPendingScrollAmountX = mPendingScrollAmountY = 0.0;
mHandlingDeltaMode = UINT32_MAX;
mIsNoLineOrPageDeltaDevice = false;
}
nsIntPoint
EventStateManager::DeltaAccumulator::ComputeScrollAmountForDefaultAction(
WidgetWheelEvent* aEvent,
const nsIntSize& aScrollAmountInDevPixels)
{
MOZ_ASSERT(aEvent);
// If the wheel event is line scroll and the delta value is computed from
// system settings, allow to override the system speed.
bool allowScrollSpeedOverride =
(!aEvent->mCustomizedByUserPrefs &&
aEvent->mDeltaMode == nsIDOMWheelEvent::DOM_DELTA_LINE);
DeltaValues acceleratedDelta =
WheelTransaction::AccelerateWheelDelta(aEvent, allowScrollSpeedOverride);
nsIntPoint result(0, 0);
if (aEvent->mDeltaMode == nsIDOMWheelEvent::DOM_DELTA_PIXEL) {
mPendingScrollAmountX += acceleratedDelta.deltaX;
mPendingScrollAmountY += acceleratedDelta.deltaY;
} else {
mPendingScrollAmountX +=
aScrollAmountInDevPixels.width * acceleratedDelta.deltaX;
mPendingScrollAmountY +=
aScrollAmountInDevPixels.height * acceleratedDelta.deltaY;
}
result.x = RoundDown(mPendingScrollAmountX);
result.y = RoundDown(mPendingScrollAmountY);
mPendingScrollAmountX -= result.x;
mPendingScrollAmountY -= result.y;
return result;
}
/******************************************************************/
/* mozilla::EventStateManager::WheelPrefs */
/******************************************************************/
// static
EventStateManager::WheelPrefs*
EventStateManager::WheelPrefs::GetInstance()
{
if (!sInstance) {
sInstance = new WheelPrefs();
}
return sInstance;
}
// static
void
EventStateManager::WheelPrefs::Shutdown()
{
delete sInstance;
sInstance = nullptr;
}
// static
void
EventStateManager::WheelPrefs::OnPrefChanged(const char* aPrefName,
void* aClosure)
{
// forget all prefs, it's not problem for performance.
sInstance->Reset();
DeltaAccumulator::GetInstance()->Reset();
}
EventStateManager::WheelPrefs::WheelPrefs()
{
Reset();
Preferences::RegisterPrefixCallback(OnPrefChanged, "mousewheel.");
Preferences::AddBoolVarCache(&sWheelEventsEnabledOnPlugins,
"plugin.mousewheel.enabled",
true);
}
EventStateManager::WheelPrefs::~WheelPrefs()
{
Preferences::UnregisterPrefixCallback(OnPrefChanged, "mousewheel.");
}
void
EventStateManager::WheelPrefs::Reset()
{
memset(mInit, 0, sizeof(mInit));
}
EventStateManager::WheelPrefs::Index
EventStateManager::WheelPrefs::GetIndexFor(WidgetWheelEvent* aEvent)
{
if (!aEvent) {
return INDEX_DEFAULT;
}
Modifiers modifiers =
(aEvent->mModifiers & (MODIFIER_ALT |
MODIFIER_CONTROL |
MODIFIER_META |
MODIFIER_SHIFT |
MODIFIER_OS));
switch (modifiers) {
case MODIFIER_ALT:
return INDEX_ALT;
case MODIFIER_CONTROL:
return INDEX_CONTROL;
case MODIFIER_META:
return INDEX_META;
case MODIFIER_SHIFT:
return INDEX_SHIFT;
case MODIFIER_OS:
return INDEX_OS;
default:
// If two or more modifier keys are pressed, we should use default
// settings.
return INDEX_DEFAULT;
}
}
void
EventStateManager::WheelPrefs::GetBasePrefName(
EventStateManager::WheelPrefs::Index aIndex,
nsACString& aBasePrefName)
{
aBasePrefName.AssignLiteral("mousewheel.");
switch (aIndex) {
case INDEX_ALT:
aBasePrefName.AppendLiteral("with_alt.");
break;
case INDEX_CONTROL:
aBasePrefName.AppendLiteral("with_control.");
break;
case INDEX_META:
aBasePrefName.AppendLiteral("with_meta.");
break;
case INDEX_SHIFT:
aBasePrefName.AppendLiteral("with_shift.");
break;
case INDEX_OS:
aBasePrefName.AppendLiteral("with_win.");
break;
case INDEX_DEFAULT:
default:
aBasePrefName.AppendLiteral("default.");
break;
}
}
void
EventStateManager::WheelPrefs::Init(EventStateManager::WheelPrefs::Index aIndex)
{
if (mInit[aIndex]) {
return;
}
mInit[aIndex] = true;
nsAutoCString basePrefName;
GetBasePrefName(aIndex, basePrefName);
nsAutoCString prefNameX(basePrefName);
prefNameX.AppendLiteral("delta_multiplier_x");
mMultiplierX[aIndex] =
static_cast<double>(Preferences::GetInt(prefNameX.get(), 100)) / 100;
nsAutoCString prefNameY(basePrefName);
prefNameY.AppendLiteral("delta_multiplier_y");
mMultiplierY[aIndex] =
static_cast<double>(Preferences::GetInt(prefNameY.get(), 100)) / 100;
nsAutoCString prefNameZ(basePrefName);
prefNameZ.AppendLiteral("delta_multiplier_z");
mMultiplierZ[aIndex] =
static_cast<double>(Preferences::GetInt(prefNameZ.get(), 100)) / 100;
nsAutoCString prefNameAction(basePrefName);
prefNameAction.AppendLiteral("action");
int32_t action = Preferences::GetInt(prefNameAction.get(), ACTION_SCROLL);
if (action < int32_t(ACTION_NONE) || action > int32_t(ACTION_LAST)) {
NS_WARNING("Unsupported action pref value, replaced with 'Scroll'.");
action = ACTION_SCROLL;
}
mActions[aIndex] = static_cast<Action>(action);
// Compute action values overridden by .override_x pref.
// At present, override is possible only for the x-direction
// because this pref is introduced mainly for tilt wheels.
prefNameAction.AppendLiteral(".override_x");
int32_t actionOverrideX = Preferences::GetInt(prefNameAction.get(), -1);
if (actionOverrideX < -1 || actionOverrideX > int32_t(ACTION_LAST)) {
NS_WARNING("Unsupported action override pref value, didn't override.");
actionOverrideX = -1;
}
mOverriddenActionsX[aIndex] = (actionOverrideX == -1)
? static_cast<Action>(action)
: static_cast<Action>(actionOverrideX);
}
void
EventStateManager::WheelPrefs::ApplyUserPrefsToDelta(WidgetWheelEvent* aEvent)
{
if (aEvent->mCustomizedByUserPrefs) {
return;
}
Index index = GetIndexFor(aEvent);
Init(index);
aEvent->mDeltaX *= mMultiplierX[index];
aEvent->mDeltaY *= mMultiplierY[index];
aEvent->mDeltaZ *= mMultiplierZ[index];
// If the multiplier is 1.0 or -1.0, i.e., it doesn't change the absolute
// value, we should use lineOrPageDelta values which were set by widget.
// Otherwise, we need to compute them from accumulated delta values.
if (!NeedToComputeLineOrPageDelta(aEvent)) {
aEvent->mLineOrPageDeltaX *= static_cast<int32_t>(mMultiplierX[index]);
aEvent->mLineOrPageDeltaY *= static_cast<int32_t>(mMultiplierY[index]);
} else {
aEvent->mLineOrPageDeltaX = 0;
aEvent->mLineOrPageDeltaY = 0;
}
aEvent->mCustomizedByUserPrefs =
((mMultiplierX[index] != 1.0) || (mMultiplierY[index] != 1.0) ||
(mMultiplierZ[index] != 1.0));
}
void
EventStateManager::WheelPrefs::CancelApplyingUserPrefsFromOverflowDelta(
WidgetWheelEvent* aEvent)
{
Index index = GetIndexFor(aEvent);
Init(index);
// XXX If the multiplier pref value is negative, the scroll direction was
// changed and caused to scroll different direction. In such case,
// this method reverts the sign of overflowDelta. Does it make widget
// happy? Although, widget can know the pref applied delta values by
// referrencing the deltaX and deltaY of the event.
if (mMultiplierX[index]) {
aEvent->mOverflowDeltaX /= mMultiplierX[index];
}
if (mMultiplierY[index]) {
aEvent->mOverflowDeltaY /= mMultiplierY[index];
}
}
EventStateManager::WheelPrefs::Action
EventStateManager::WheelPrefs::ComputeActionFor(WidgetWheelEvent* aEvent)
{
Index index = GetIndexFor(aEvent);
Init(index);
bool deltaXPreferred =
(Abs(aEvent->mDeltaX) > Abs(aEvent->mDeltaY) &&
Abs(aEvent->mDeltaX) > Abs(aEvent->mDeltaZ));
Action* actions = deltaXPreferred ? mOverriddenActionsX : mActions;
if (actions[index] == ACTION_NONE || actions[index] == ACTION_SCROLL) {
return actions[index];
}
// Momentum events shouldn't run special actions.
if (aEvent->mIsMomentum) {
// Use the default action. Note that user might kill the wheel scrolling.
Init(INDEX_DEFAULT);
return (actions[INDEX_DEFAULT] == ACTION_SCROLL) ? ACTION_SCROLL :
ACTION_NONE;
}
return actions[index];
}
bool
EventStateManager::WheelPrefs::NeedToComputeLineOrPageDelta(
WidgetWheelEvent* aEvent)
{
Index index = GetIndexFor(aEvent);
Init(index);
return (mMultiplierX[index] != 1.0 && mMultiplierX[index] != -1.0) ||
(mMultiplierY[index] != 1.0 && mMultiplierY[index] != -1.0);
}
void
EventStateManager::WheelPrefs::GetUserPrefsForEvent(WidgetWheelEvent* aEvent,
double* aOutMultiplierX,
double* aOutMultiplierY)
{
Index index = GetIndexFor(aEvent);
Init(index);
*aOutMultiplierX = mMultiplierX[index];
*aOutMultiplierY = mMultiplierY[index];
}
// static
bool
EventStateManager::WheelPrefs::WheelEventsEnabledOnPlugins()
{
if (!sInstance) {
GetInstance(); // initializing sWheelEventsEnabledOnPlugins
}
return sWheelEventsEnabledOnPlugins;
}
bool
EventStateManager::WheelEventIsScrollAction(WidgetWheelEvent* aEvent)
{
return aEvent->mMessage == eWheel &&
WheelPrefs::GetInstance()->ComputeActionFor(aEvent) == WheelPrefs::ACTION_SCROLL;
}
void
EventStateManager::GetUserPrefsForWheelEvent(WidgetWheelEvent* aEvent,
double* aOutMultiplierX,
double* aOutMultiplierY)
{
WheelPrefs::GetInstance()->GetUserPrefsForEvent(
aEvent, aOutMultiplierX, aOutMultiplierY);
}
bool
EventStateManager::WheelPrefs::IsOverOnePageScrollAllowedX(
WidgetWheelEvent* aEvent)
{
Index index = GetIndexFor(aEvent);
Init(index);
return Abs(mMultiplierX[index]) >=
MIN_MULTIPLIER_VALUE_ALLOWING_OVER_ONE_PAGE_SCROLL;
}
bool
EventStateManager::WheelPrefs::IsOverOnePageScrollAllowedY(
WidgetWheelEvent* aEvent)
{
Index index = GetIndexFor(aEvent);
Init(index);
return Abs(mMultiplierY[index]) >=
MIN_MULTIPLIER_VALUE_ALLOWING_OVER_ONE_PAGE_SCROLL;
}
/******************************************************************/
/* mozilla::EventStateManager::Prefs */
/******************************************************************/
bool EventStateManager::Prefs::sKeyCausesActivation = true;
bool EventStateManager::Prefs::sClickHoldContextMenu = false;
// static
void
EventStateManager::Prefs::Init()
{
DebugOnly<nsresult> rv = Preferences::RegisterCallback(OnChange, "dom.popup_allowed_events");
MOZ_ASSERT(NS_SUCCEEDED(rv),
"Failed to observe \"dom.popup_allowed_events\"");
static bool sPrefsAlreadyCached = false;
if (sPrefsAlreadyCached) {
return;
}
rv = Preferences::AddBoolVarCache(&sKeyCausesActivation,
"accessibility.accesskeycausesactivation",
sKeyCausesActivation);
MOZ_ASSERT(NS_SUCCEEDED(rv),
"Failed to observe \"accessibility.accesskeycausesactivation\"");
rv = Preferences::AddBoolVarCache(&sClickHoldContextMenu,
"ui.click_hold_context_menus",
sClickHoldContextMenu);
MOZ_ASSERT(NS_SUCCEEDED(rv),
"Failed to observe \"ui.click_hold_context_menus\"");
sPrefsAlreadyCached = true;
}
// static
void
EventStateManager::Prefs::OnChange(const char* aPrefName, void*)
{
nsDependentCString prefName(aPrefName);
if (prefName.EqualsLiteral("dom.popup_allowed_events")) {
Event::PopupAllowedEventsChanged();
}
}
// static
void
EventStateManager::Prefs::Shutdown()
{
Preferences::UnregisterCallback(OnChange, "dom.popup_allowed_events");
}
/******************************************************************/
/* mozilla::AutoHandlingUserInputStatePusher */
/******************************************************************/
AutoHandlingUserInputStatePusher::AutoHandlingUserInputStatePusher(
bool aIsHandlingUserInput,
WidgetEvent* aEvent,
nsIDocument* aDocument) :
mIsHandlingUserInput(aIsHandlingUserInput),
mIsMouseDown(aEvent && aEvent->mMessage == eMouseDown),
mResetFMMouseButtonHandlingState(false)
{
if (!aIsHandlingUserInput) {
return;
}
EventStateManager::StartHandlingUserInput();
if (mIsMouseDown) {
nsIPresShell::SetCapturingContent(nullptr, 0);
nsIPresShell::AllowMouseCapture(true);
}
if (!aDocument || !aEvent || !aEvent->IsTrusted()) {
return;
}
mResetFMMouseButtonHandlingState =
(aEvent->mMessage == eMouseDown || aEvent->mMessage == eMouseUp);
if (mResetFMMouseButtonHandlingState) {
nsFocusManager* fm = nsFocusManager::GetFocusManager();
NS_ENSURE_TRUE_VOID(fm);
// If it's in modal state, mouse button event handling may be nested.
// E.g., a modal dialog is opened at mousedown or mouseup event handler
// and the dialog is clicked. Therefore, we should store current
// mouse button event handling document if nsFocusManager already has it.
mMouseButtonEventHandlingDocument =
fm->SetMouseButtonHandlingDocument(aDocument);
}
}
AutoHandlingUserInputStatePusher::~AutoHandlingUserInputStatePusher()
{
if (!mIsHandlingUserInput) {
return;
}
EventStateManager::StopHandlingUserInput();
if (mIsMouseDown) {
nsIPresShell::AllowMouseCapture(false);
}
if (mResetFMMouseButtonHandlingState) {
nsFocusManager* fm = nsFocusManager::GetFocusManager();
NS_ENSURE_TRUE_VOID(fm);
nsCOMPtr<nsIDocument> handlingDocument =
fm->SetMouseButtonHandlingDocument(mMouseButtonEventHandlingDocument);
}
}
} // namespace mozilla