diff options
Diffstat (limited to 'dom/events/EventStateManager.cpp')
-rw-r--r-- | dom/events/EventStateManager.cpp | 6002 |
1 files changed, 6002 insertions, 0 deletions
diff --git a/dom/events/EventStateManager.cpp b/dom/events/EventStateManager.cpp new file mode 100644 index 000000000..659629066 --- /dev/null +++ b/dom/events/EventStateManager.cpp @@ -0,0 +1,6002 @@ +/* -*- 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" + +#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 nsWeakFrame 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 + +// mask values for ui.key.chromeAccess and ui.key.contentAccess +#define NS_MODIFIER_SHIFT 1 +#define NS_MODIFIER_CONTROL 2 +#define NS_MODIFIER_ALT 4 +#define NS_MODIFIER_META 8 +#define NS_MODIFIER_OS 16 + +/******************************************************************/ +/* mozilla::UITimerCallback */ +/******************************************************************/ + +class UITimerCallback final : public nsITimerCallback +{ +public: + UITimerCallback() : mPreviousCount(0) {} + NS_DECL_ISUPPORTS + NS_DECL_NSITIMERCALLBACK +private: + ~UITimerCallback() {} + uint32_t mPreviousCount; +}; + +NS_IMPL_ISUPPORTS(UITimerCallback, nsITimerCallback) + +// 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; +} + +/******************************************************************/ +/* mozilla::OverOutElementsWrapper */ +/******************************************************************/ + +OverOutElementsWrapper::OverOutElementsWrapper() + : mLastOverFrame(nullptr) +{ +} + +OverOutElementsWrapper::~OverOutElementsWrapper() +{ +} + +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; +nsWeakFrame 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; + } +} + +nsresult +EventStateManager::UpdateUserActivityTimer() +{ + if (!gUserInteractionTimerCallback) + return NS_OK; + + if (!gUserInteractionTimer) + CallCreateInstance("@mozilla.org/timer;1", &gUserInteractionTimer); + + 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(); + } +} + +static bool +IsMessageMouseUserActivity(EventMessage aMessage) +{ + return aMessage == eMouseMove || + aMessage == eMouseUp || + aMessage == eMouseDown || + aMessage == eMouseDoubleClick || + aMessage == eMouseClick || + aMessage == eMouseActivate || + aMessage == eMouseLongTap; +} + +static bool +IsMessageGamepadUserActivity(EventMessage aMessage) +{ +#ifndef MOZ_GAMEPAD + return false; +#else + return aMessage == eGamepadButtonDown || + aMessage == eGamepadButtonUp || + aMessage == eGamepadAxisMove; +#endif +} + +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)). + // Flag mNoCrossProcessBoundaryForwarding 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); + } + + // Flag 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 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(); + + int32_t modifierMask = 0; + if (keyEvent->IsShift()) + modifierMask |= NS_MODIFIER_SHIFT; + if (keyEvent->IsControl()) + modifierMask |= NS_MODIFIER_CONTROL; + if (keyEvent->IsAlt()) + modifierMask |= NS_MODIFIER_ALT; + if (keyEvent->IsMeta()) + modifierMask |= NS_MODIFIER_META; + if (keyEvent->IsOS()) + modifierMask |= NS_MODIFIER_OS; + + // Prevent keyboard scrolling while an accesskey modifier is in use. + if (modifierMask) { + bool matchesContentAccessKey = (modifierMask == Prefs::ContentAccessModifierMask()); + + if (modifierMask == Prefs::ChromeAccessModifierMask() || + matchesContentAccessKey) { + AutoTArray<uint32_t, 10> accessCharCodes; + keyEvent->GetAccessKeyCandidates(accessCharCodes); + + if (HandleAccessKey(keyEvent, aPresContext, accessCharCodes, + modifierMask, matchesContentAccessKey)) { + *aStatus = nsEventStatus_eConsumeNoDefault; + } + } + } + } + // then fall through... + MOZ_FALLTHROUGH; + case eBeforeKeyDown: + case eKeyDown: + case eAfterKeyDown: + case eBeforeKeyUp: + case eKeyUp: + case eAfterKeyUp: + { + 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; + } + 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 +int32_t +EventStateManager::GetAccessModifierMaskFor(nsISupports* aDocShell) +{ + nsCOMPtr<nsIDocShellTreeItem> treeItem(do_QueryInterface(aDocShell)); + if (!treeItem) + return -1; // invalid modifier + + switch (treeItem->ItemType()) { + case nsIDocShellTreeItem::typeChrome: + return Prefs::ChromeAccessModifierMask(); + + case nsIDocShellTreeItem::typeContent: + return Prefs::ContentAccessModifierMask(); + + default: + return -1; // invalid modifier + } +} + +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::ExecuteAccessKey(nsTArray<uint32_t>& aAccessCharCodes, + bool aIsTrustedEvent) +{ + 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)) { + 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); + + nsCOMPtr<nsISupports> container = aElement->OwnerDoc()->GetDocShell(); + int32_t modifierMask = GetAccessModifierMaskFor(container); + + if (modifierMask == -1) { + return; + } + + if (modifierMask & NS_MODIFIER_CONTROL) { + nsContentUtils::GetControlText(modifierText); + aPrefix.Append(modifierText + separator); + } + if (modifierMask & NS_MODIFIER_META) { + nsContentUtils::GetMetaText(modifierText); + aPrefix.Append(modifierText + separator); + } + if (modifierMask & NS_MODIFIER_OS) { + nsContentUtils::GetOSText(modifierText); + aPrefix.Append(modifierText + separator); + } + if (modifierMask & NS_MODIFIER_ALT) { + nsContentUtils::GetAltText(modifierText); + aPrefix.Append(modifierText + separator); + } + if (modifierMask & NS_MODIFIER_SHIFT) { + nsContentUtils::GetShiftText(modifierText); + aPrefix.Append(modifierText + separator); + } +} + +struct MOZ_STACK_CLASS AccessKeyInfo +{ + WidgetKeyboardEvent* event; + nsTArray<uint32_t>& charCodes; + int32_t modifierMask; + + AccessKeyInfo(WidgetKeyboardEvent* aEvent, nsTArray<uint32_t>& aCharCodes, int32_t aModifierMask) + : event(aEvent) + , charCodes(aCharCodes) + , modifierMask(aModifierMask) + { + } +}; + +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) { + accessKeyInfo->event->mAccessKeyForwardedToChild = true; + aTabParent->HandleAccessKey(*accessKeyInfo->event, + accessKeyInfo->charCodes, + accessKeyInfo->modifierMask); + return true; + } + + return false; +} + +bool +EventStateManager::HandleAccessKey(WidgetKeyboardEvent* aEvent, + nsPresContext* aPresContext, + nsTArray<uint32_t>& aAccessCharCodes, + bool aMatchesContentAccessKey, + nsIDocShellTreeItem* aBubbledFrom, + ProcessingAccessKeyState aAccessKeyState, + int32_t aModifierMask) +{ + EnsureDocument(mPresContext); + nsCOMPtr<nsIDocShell> docShell = aPresContext->GetDocShell(); + if (NS_WARN_IF(!docShell) || NS_WARN_IF(!mDocument)) { + return false; + } + + // Alt or other accesskey modifier is down, we may need to do an accesskey. + if (mAccessKeys.Count() > 0 && + aModifierMask == GetAccessModifierMaskFor(docShell)) { + // Someone registered an accesskey. Find and activate it. + if (ExecuteAccessKey(aAccessCharCodes, aEvent->IsTrusted())) { + 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->HandleAccessKey(aEvent, subPC, aAccessCharCodes, + aMatchesContentAccessKey, nullptr, + eAccessKeyProcessingDown, aModifierMask)) { + 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->HandleAccessKey(aEvent, parentPC, aAccessCharCodes, + aMatchesContentAccessKey, docShell, + eAccessKeyProcessingDown, aModifierMask)) { + return true; + } + } + }// if end. bubble up process + + // If the content access key modifier is pressed, try remote children + if (aMatchesContentAccessKey && mDocument && mDocument->GetWindow()) { + // If the focus is currently on a node with a TabParent, the key event will + // get forwarded to the child process and HandleAccessKey called from there. + // If focus is somewhere else, then we need to check the remote children. + nsFocusManager* fm = nsFocusManager::GetFocusManager(); + nsIContent* focusedContent = fm ? fm->GetFocusedContent() : nullptr; + if (TabParent::GetFrom(focusedContent)) { + // A remote child process is focused. The key event should get sent to + // the child process. + aEvent->mAccessKeyForwardedToChild = true; + } else { + AccessKeyInfo accessKeyInfo(aEvent, aAccessCharCodes, aModifierMask); + nsContentUtils::CallOnAllRemoteChildren(mDocument->GetWindow(), + HandleAccessKeyInRemoteChild, &accessKeyInfo); + } + } + + return false; +}// end of HandleAccessKey + +bool +EventStateManager::DispatchCrossProcessEvent(WidgetEvent* aEvent, + nsFrameLoader* aFrameLoader, + nsEventStatus *aStatus) { + TabParent* remote = TabParent::GetFrom(aFrameLoader); + if (!remote) { + return false; + } + + switch (aEvent->mClass) { + case eMouseEventClass: { + return remote->SendRealMouseEvent(*aEvent->AsMouseEvent()); + } + case eKeyboardEventClass: { + return remote->SendRealKeyEvent(*aEvent->AsKeyboardEvent()); + } + case eWheelEventClass: { + return remote->SendMouseWheelEvent(*aEvent->AsWheelEvent()); + } + 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; + return remote->SendRealTouchEvent(*aEvent->AsTouchEvent()); + } + case eDragEventClass: { + RefPtr<TabParent> tabParent = remote; + if (tabParent->Manager()->IsContentParent()) { + tabParent->Manager()->AsContentParent()->MaybeInvokeDragSession(tabParent); + } + + 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); + } + } + + bool retval = tabParent->SendRealDragEvent(*aEvent->AsDragEvent(), + action, dropEffect); + + return retval; + } + case ePluginEventClass: { + *aStatus = nsEventStatus_eConsumeNoDefault; + return remote->SendPluginEvent(*aEvent->AsPluginEvent()); + } + default: { + MOZ_CRASH("Attempt to send non-whitelisted event?"); + } + } +} + +bool +EventStateManager::IsRemoteTarget(nsIContent* target) { + if (!target) { + return false; + } + + // <browser/iframe remote=true> from XUL + if (target->IsAnyOfXULElements(nsGkAtoms::browser, nsGkAtoms::iframe) && + target->AttrValueIs(kNameSpaceID_None, nsGkAtoms::Remote, + nsGkAtoms::_true, eIgnoreCase)) { + return true; + } + + // <frame/iframe mozbrowser/mozapp> + nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(target); + if (browserFrame && browserFrame->GetReallyIsBrowserOrApp()) { + return !!TabParent::GetFrom(target); + } + + return false; +} + +static bool +CrossProcessSafeEvent(const WidgetEvent& aEvent) +{ + switch (aEvent.mClass) { + case eKeyboardEventClass: + case eWheelEventClass: + return true; + case eMouseEventClass: + switch (aEvent.mMessage) { + case eMouseDown: + case eMouseUp: + case eMouseMove: + case eContextMenu: + case eMouseEnterIntoWidget: + case eMouseExitFromWidget: + case eMouseTouchDrag: + return true; + default: + return false; + } + case eTouchEventClass: + switch (aEvent.mMessage) { + case eTouchStart: + case eTouchMove: + case eTouchEnd: + case eTouchCancel: + return true; + default: + return false; + } + case eDragEventClass: + switch (aEvent.mMessage) { + case eDragOver: + case eDragExit: + case eDrop: + return true; + default: + return false; + } + default: + return false; + } +} + +bool +EventStateManager::HandleCrossProcessEvent(WidgetEvent* aEvent, + nsEventStatus *aStatus) { + if (*aStatus == nsEventStatus_eConsumeNoDefault || + aEvent->mFlags.mNoCrossProcessBoundaryForwarding || + !CrossProcessSafeEvent(*aEvent)) { + return false; + } + + // 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. + bool dispatched = false; + 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; + } + + dispatched |= DispatchCrossProcessEvent(aEvent, frameLoader, aStatus); + } + return dispatched; +} + +// +// 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->InitWithFuncCallback(sClickHoldCallback, this, + clickHoldDelay, + nsITimer::TYPE_ONE_SHOT); + } +} // 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->GetType() == NS_FORM_INPUT_FILE; + } + else if (mGestureDownContent->IsAnyOfHTMLElements(nsGkAtoms::applet, + 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) + + nsWeakFrame 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); + + nsWeakFrame 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; + } + + nsIAtom* currentFrameType = current->GetType(); + + // Scrollbars should always be draggable + if (currentFrameType == nsGkAtoms::scrollbarFrame) { + 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->GetType() == nsGkAtoms::menuFrame) { + // 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, + nsEventStatus& aStatus, + bool dispatchedToContentProcess) +{ + if (aStatus == nsEventStatus_eConsumeNoDefault || + aKeyboardEvent->mInputMethodAppState == WidgetKeyboardEvent::eHandling) { + 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 (dispatchedToContentProcess) + 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; + + bool dispatchedToContentProcess = 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 (dispatchedToContentProcess && !nsIPresShell::GetCapturingContent()) { + nsIContent* content = mCurrentTarget ? mCurrentTarget->GetContent() : nullptr; + nsIPresShell::SetCapturingContent(content, 0); + } + + nsCOMPtr<nsIContent> activeContent; + if (nsEventStatus_eConsumeNoDefault != *aStatus) { + 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::Popup) { + 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"); + + 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 (dispatchedToContentProcess) { + 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()); + break; + + case eBeforeKeyUp: + case eKeyUp: + case eAfterKeyUp: + break; + + case eKeyPress: + { + WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent(); + PostHandleKeyboardEvent(keyEvent, *aStatus, dispatchedToContentProcess); + } + 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) {} + + virtual void HandleEvent(EventChainPostVisitor& aVisitor) + { + 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() +{ + if (sUserInputEventDepth <= 0) { + return false; + } + + TimeDuration timeout = nsContentUtils::HandlingUserInputTimeout(); + return timeout <= TimeDuration(0) || + (TimeStamp::Now() - sHandlingInputStart) <= timeout; +} + +static void +CreateMouseOrPointerWidgetEvent(WidgetMouseEvent* aMouseEvent, + EventMessage aMessage, + nsIContent* aRelatedContent, + nsAutoPtr<WidgetMouseEvent>& aNewEvent) +{ + WidgetPointerEvent* sourcePointer = aMouseEvent->AsPointerEvent(); + if (sourcePointer) { + PROFILER_LABEL("Input", "DispatchPointerEvent", + js::ProfileEntry::Category::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 = + nsIPresShell::GetPointerCapturingContent(sourcePointer->pointerId) + ? nullptr + : 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; + } + + nsCOMPtr<nsIContent> targetContent = aTargetContent; + nsCOMPtr<nsIContent> relatedContent = aRelatedContent; + + nsAutoPtr<WidgetMouseEvent> dispatchEvent; + CreateMouseOrPointerWidgetEvent(aMouseEvent, aMessage, + relatedContent, dispatchEvent); + + nsWeakFrame previousTarget = mCurrentTarget; + mCurrentTargetContent = targetContent; + + nsIFrame* targetFrame = nullptr; + + if (aMouseEvent->AsMouseEvent()) { + PROFILER_LABEL("Input", "DispatchMouseEvent", + js::ProfileEntry::Category::EVENTS); + } + + nsEventStatus status = nsEventStatus_eIgnore; + ESMEventCB callback(targetContent); + EventDispatcher::Dispatch(targetContent, 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(targetContent); + + // If we are entering/leaving remote content, dispatch a mouse enter/exit + // event to the remote frame. + if (IsRemoteTarget(targetContent)) { + if (aMessage == eMouseOut) { + // For remote content, send a "top-level" widget mouse exit event. + nsAutoPtr<WidgetMouseEvent> remoteEvent; + CreateMouseOrPointerWidgetEvent(aMouseEvent, eMouseExitFromWidget, + relatedContent, 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, + relatedContent, 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(); + } + } + } + + ~EnterLeaveDispatcher() + { + 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) +{ + OverOutElementsWrapper* wrapper = GetWrapperByEventID(aMouseEvent); + + if (!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); + } + + // In case we go out from capturing element (retargetedByPointerCapture is true) + // we should dispatch ePointerLeave event and only for capturing element. + RefPtr<nsIContent> movingInto = aMouseEvent->retargetedByPointerCapture + ? wrapper->mLastOverElement->GetParent() + : aMovingInto; + + EnterLeaveDispatcher leaveDispatcher(this, wrapper->mLastOverElement, + movingInto, aMouseEvent, + isPointer ? ePointerLeave : eMouseLeave); + + // Fire mouseout + DispatchMouseOrPointerEvent(aMouseEvent, isPointer ? ePointerOut : eMouseOut, + wrapper->mLastOverElement, aMovingInto); + + 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"); + + // If pointer capture is set, we should suppress pointerover/pointerenter events + // for all elements except element which have pointer capture. + bool dispatch = !aMouseEvent->retargetedByPointerCapture; + + OverOutElementsWrapper* wrapper = GetWrapperByEventID(aMouseEvent); + + if (wrapper->mLastOverElement == aContent && dispatch) + 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 && dispatch) + 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; + + Maybe<EnterLeaveDispatcher> enterDispatcher; + if (dispatch) { + enterDispatcher.emplace(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); + } + + if (dispatch) { + // Fire mouseover + wrapper->mLastOverFrame = + DispatchMouseOrPointerEvent(aMouseEvent, + isPointer ? ePointerOver : eMouseOver, + aContent, lastOverElement); + wrapper->mLastOverElement = aContent; + } else { + wrapper->mLastOverFrame = nullptr; + wrapper->mLastOverElement = nullptr; + } + + // 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: + { + // 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) { + 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. + + OverOutElementsWrapper* helper = GetWrapperByEventID(aMouseEvent); + if (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; + } + RefPtr<OverOutElementsWrapper> helper; + if (!mPointersEnterLeaveHelper.Get(pointer->pointerId, getter_AddRefs(helper))) { + helper = new OverOutElementsWrapper(); + mPointersEnterLeaveHelper.Put(pointer->pointerId, helper); + } + + return helper; +} + +/* 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); + } + + FireDragEnterOrExit(aPresContext, aDragEvent, eDragEnter, + lastContent, targetContent, mCurrentTarget); + + 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, + nsWeakFrame& aTargetFrame) +{ + nsEventStatus status = nsEventStatus_eIgnore; + WidgetDragEvent event(aDragEvent->IsTrusted(), aMessage, aDragEvent->mWidget); + event.mRefPoint = aDragEvent->mRefPoint; + event.mModifiers = aDragEvent->mModifiers; + event.buttons = aDragEvent->buttons; + event.relatedTarget = aRelatedTarget; + event.inputSource = aDragEvent->inputSource; + + 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. + if (aMessage == eDragLeave || aMessage == eDragExit || + aMessage == eDragEnter) { + 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::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); + + WidgetMouseEvent event(aEvent->IsTrusted(), eMouseClick, + 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 = notDispatchToContents; + event.button = aEvent->button; + event.inputSource = aEvent->inputSource; + + 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 + nsWeakFrame currentTarget = mCurrentTarget; + ret = presShell->HandleEventWithTarget(&event, currentTarget, + mouseContent, aStatus); + if (NS_SUCCEEDED(ret) && aEvent->mClickCount == 2 && + mouseContent && mouseContent->IsInComposedDoc()) { + //fire double click + WidgetMouseEvent event2(aEvent->IsTrusted(), eMouseDoubleClick, + aEvent->mWidget, WidgetMouseEvent::eReal); + event2.mRefPoint = aEvent->mRefPoint; + event2.mClickCount = aEvent->mClickCount; + event2.mModifiers = aEvent->mModifiers; + event2.buttons = aEvent->buttons; + event2.mFlags.mNoContentDispatch = notDispatchToContents; + event2.button = aEvent->button; + event2.inputSource = aEvent->inputSource; + + ret = presShell->HandleEventWithTarget(&event2, currentTarget, + mouseContent, aStatus); + } + } + } + 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) +{ + // Find closest common ancestor + if (aNode1 && aNode2) { + // Find the nearest common ancestor by counting the distance to the + // root and then walking up again, in pairs. + int32_t offset = 0; + nsIContent *anc1 = aNode1; + for (;;) { + ++offset; + nsIContent* parent = anc1->GetFlattenedTreeParent(); + if (!parent) + break; + anc1 = parent; + } + nsIContent *anc2 = aNode2; + for (;;) { + --offset; + nsIContent* parent = anc2->GetFlattenedTreeParent(); + if (!parent) + break; + anc2 = parent; + } + if (anc1 == anc2) { + anc1 = aNode1; + anc2 = aNode2; + while (offset > 0) { + anc1 = anc1->GetFlattenedTreeParent(); + --offset; + } + while (offset < 0) { + anc2 = anc2->GetFlattenedTreeParent(); + ++offset; + } + while (anc1 != anc2) { + anc1 = anc1->GetFlattenedTreeParent(); + anc2 = anc2->GetFlattenedTreeParent(); + } + return anc1; + } + } + return nullptr; +} + +/* 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(Flush_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->GetFrameToScrollAsScrollable(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::RegisterCallback(OnPrefChanged, "mousewheel.", nullptr); + Preferences::AddBoolVarCache(&sWheelEventsEnabledOnPlugins, + "plugin.mousewheel.enabled", + true); +} + +EventStateManager::WheelPrefs::~WheelPrefs() +{ + Preferences::UnregisterCallback(OnPrefChanged, "mousewheel.", nullptr); +} + +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; +int32_t EventStateManager::Prefs::sGenericAccessModifierKey = -1; +int32_t EventStateManager::Prefs::sChromeAccessModifierMask = 0; +int32_t EventStateManager::Prefs::sContentAccessModifierMask = 0; + +// 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\""); + rv = Preferences::AddIntVarCache(&sGenericAccessModifierKey, + "ui.key.generalAccessKey", + sGenericAccessModifierKey); + MOZ_ASSERT(NS_SUCCEEDED(rv), + "Failed to observe \"ui.key.generalAccessKey\""); + rv = Preferences::AddIntVarCache(&sChromeAccessModifierMask, + "ui.key.chromeAccess", + sChromeAccessModifierMask); + MOZ_ASSERT(NS_SUCCEEDED(rv), + "Failed to observe \"ui.key.chromeAccess\""); + rv = Preferences::AddIntVarCache(&sContentAccessModifierMask, + "ui.key.contentAccess", + sContentAccessModifierMask); + MOZ_ASSERT(NS_SUCCEEDED(rv), + "Failed to observe \"ui.key.contentAccess\""); + 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"); +} + +// static +int32_t +EventStateManager::Prefs::ChromeAccessModifierMask() +{ + return GetAccessModifierMask(nsIDocShellTreeItem::typeChrome); +} + +// static +int32_t +EventStateManager::Prefs::ContentAccessModifierMask() +{ + return GetAccessModifierMask(nsIDocShellTreeItem::typeContent); +} + +// static +int32_t +EventStateManager::Prefs::GetAccessModifierMask(int32_t aItemType) +{ + switch (sGenericAccessModifierKey) { + case -1: break; // use the individual prefs + case nsIDOMKeyEvent::DOM_VK_SHIFT: return NS_MODIFIER_SHIFT; + case nsIDOMKeyEvent::DOM_VK_CONTROL: return NS_MODIFIER_CONTROL; + case nsIDOMKeyEvent::DOM_VK_ALT: return NS_MODIFIER_ALT; + case nsIDOMKeyEvent::DOM_VK_META: return NS_MODIFIER_META; + case nsIDOMKeyEvent::DOM_VK_WIN: return NS_MODIFIER_OS; + default: return 0; + } + + switch (aItemType) { + case nsIDocShellTreeItem::typeChrome: + return sChromeAccessModifierMask; + case nsIDocShellTreeItem::typeContent: + return sContentAccessModifierMask; + default: + return 0; + } +} + +/******************************************************************/ +/* 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 |