Mirror of roytam1's Pale Moon fork just in case Moonchild and Tobin decide to go after him https://github.com/roytam1/palemoon27
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

5840 lines
199 KiB

/* -*- 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/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 "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 "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 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();
nsRefPtr<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
static nsIDocument *
GetDocumentFromWindow(nsIDOMWindow *aWindow)
{
nsCOMPtr<nsPIDOMWindow> win = do_QueryInterface(aWindow);
return win ? win->GetExtantDoc() : nullptr;
}
/******************************************************************/
/* 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;
int32_t EventStateManager::sUserInputEventDepth = 0;
bool EventStateManager::sNormalLMouseEventInProcess = false;
EventStateManager* EventStateManager::sActiveESM = nullptr;
nsIDocument* EventStateManager::sMouseOverDocument = nullptr;
nsWeakFrame EventStateManager::sLastDragOverFrame = nullptr;
LayoutDeviceIntPoint EventStateManager::sLastRefPoint = kInvalidRefPoint;
LayoutDeviceIntPoint EventStateManager::sLastScreenPoint = LayoutDeviceIntPoint(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::sHandlingInputStart;
EventStateManager::WheelPrefs*
EventStateManager::WheelPrefs::sInstance = nullptr;
EventStateManager::DeltaAccumulator*
EventStateManager::DeltaAccumulator::sInstance = nullptr;
EventStateManager::EventStateManager()
: mLockCursor(0)
, mPreLockPoint(0,0)
, mCurrentTarget(nullptr)
// init d&d gesture state machine variables
, mGestureDownPoint(0,0)
, mPresContext(nullptr)
, mLClickCount(0)
, mMClickCount(0)
, mRClickCount(0)
, 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)
{
ReleaseCurrentIMEContentObserver();
mIMEContentObserver = aIMEContentObserver;
}
void
EventStateManager::OnStopObservingContent(
IMEContentObserver* aIMEContentObserver)
{
aIMEContentObserver->DisconnectFromEventStateManager();
NS_ENSURE_TRUE_VOID(mIMEContentObserver == aIMEContentObserver);
mIMEContentObserver = nullptr;
}
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;
}
#if(0)
// This is obnoxious, and seems to no longer be relevant.
NS_WARN_IF_FALSE(!aTargetFrame ||
!aTargetFrame->GetContent() ||
aTargetFrame->GetContent() == aTargetContent ||
aTargetFrame->GetContent()->GetFlattenedTreeParent() == aTargetContent,
"aTargetFrame should be related with aTargetContent");
#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->mFlags.mIsTrusted &&
((mouseEvent && mouseEvent->IsReal() &&
mouseEvent->mMessage != eMouseEnterIntoWidget &&
mouseEvent->mMessage != eMouseExitFromWidget) ||
aEvent->mClass == eWheelEventClass ||
aEvent->mClass == ePointerEventClass ||
aEvent->mClass == eTouchEventClass ||
aEvent->mClass == eKeyboardEventClass)) {
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 == NS_WHEEL_WHEEL || aEvent->mMessage == NS_TOUCH_END ||
aEvent->mMessage == NS_POINTER_UP)) {
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->mFlags.mIsTrusted &&
((mouseEvent && mouseEvent->IsReal()) ||
aEvent->mClass == eWheelEventClass) &&
!sIsPointerLocked) {
sLastScreenPoint =
UIEvent::CalculateScreenPoint(aPresContext, aEvent);
sLastClientPoint =
UIEvent::CalculateClientPoint(aPresContext, aEvent, nullptr);
}
*aStatus = nsEventStatus_eIgnore;
switch (aEvent->mMessage) {
case NS_CONTEXTMENU:
if (sIsPointerLocked) {
return NS_ERROR_DOM_INVALID_STATE_ERR;
}
break;
case eMouseDown: {
switch (mouseEvent->button) {
case WidgetMouseEvent::eLeftButton:
BeginTrackingDragGesture(aPresContext, mouseEvent, aTargetFrame);
mLClickCount = mouseEvent->clickCount;
SetClickCount(aPresContext, mouseEvent, aStatus);
sNormalLMouseEventInProcess = true;
break;
case WidgetMouseEvent::eMiddleButton:
mMClickCount = mouseEvent->clickCount;
SetClickCount(aPresContext, mouseEvent, aStatus);
break;
case WidgetMouseEvent::eRightButton:
mRClickCount = mouseEvent->clickCount;
SetClickCount(aPresContext, mouseEvent, aStatus);
break;
}
break;
}
case eMouseUp: {
switch (mouseEvent->button) {
case WidgetMouseEvent::eLeftButton:
if (Prefs::ClickHoldContextMenu()) {
KillClickHoldTimer();
}
StopTrackingDragGesture();
sNormalLMouseEventInProcess = false;
// then fall through...
case WidgetMouseEvent::eRightButton:
case WidgetMouseEvent::eMiddleButton:
SetClickCount(aPresContext, 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->mFlags.mNoCrossProcessBoundaryForwarding = true;
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->mFlags.mNoCrossProcessBoundaryForwarding = true;
// 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->exit != 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->reason = WidgetMouseEvent::eSynthesized;
// then fall through...
} else {
if (sPointerEventEnabled) {
// We should synthetize corresponding pointer events
GeneratePointerEnterExit(NS_POINTER_LEAVE, mouseEvent);
}
GenerateMouseEnterExit(mouseEvent);
//This is a window level mouse exit event and should stop here
aEvent->mMessage = eVoidEvent;
break;
}
case eMouseMove:
case NS_POINTER_DOWN:
case NS_POINTER_MOVE: {
// 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 NS_DRAGDROP_GESTURE:
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 NS_DRAGDROP_OVER:
// NS_DRAGDROP_DROP is fired before NS_DRAGDROP_DRAGDROP so send
// the enter/exit events before NS_DRAGDROP_DROP.
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 &&
(modifierMask == Prefs::ChromeAccessModifierMask() ||
modifierMask == Prefs::ContentAccessModifierMask())) {
HandleAccessKey(aPresContext, keyEvent, aStatus, nullptr,
eAccessKeyProcessingNormal, modifierMask);
}
}
// then fall through...
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 DOM Level 3
// 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.
WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent();
nsRefPtr<TextComposition> composition =
IMEStateManager::GetTextCompositionFor(keyEvent);
keyEvent->mIsComposing = !!composition;
}
break;
case NS_WHEEL_WHEEL:
case NS_WHEEL_START:
case NS_WHEEL_STOP:
{
NS_ASSERTION(aEvent->mFlags.mIsTrusted,
"Untrusted wheel event shouldn't be here");
nsIContent* content = GetFocusedContent();
if (content) {
mCurrentTargetContent = content;
}
if (aEvent->mMessage != NS_WHEEL_WHEEL) {
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 NS_QUERY_SELECTED_TEXT:
DoQuerySelectedText(aEvent->AsQueryContentEvent());
break;
case NS_QUERY_TEXT_CONTENT:
{
if (RemoteQueryContentEvent(aEvent)) {
break;
}
ContentEventHandler handler(mPresContext);
handler.OnQueryTextContent(aEvent->AsQueryContentEvent());
}
break;
case NS_QUERY_CARET_RECT:
{
if (RemoteQueryContentEvent(aEvent)) {
break;
}
ContentEventHandler handler(mPresContext);
handler.OnQueryCaretRect(aEvent->AsQueryContentEvent());
}
break;
case NS_QUERY_TEXT_RECT:
{
if (RemoteQueryContentEvent(aEvent)) {
break;
}
ContentEventHandler handler(mPresContext);
handler.OnQueryTextRect(aEvent->AsQueryContentEvent());
}
break;
case NS_QUERY_EDITOR_RECT:
{
if (RemoteQueryContentEvent(aEvent)) {
break;
}
ContentEventHandler handler(mPresContext);
handler.OnQueryEditorRect(aEvent->AsQueryContentEvent());
}
break;
case NS_QUERY_CONTENT_STATE:
{
// XXX remote event
ContentEventHandler handler(mPresContext);
handler.OnQueryContentState(aEvent->AsQueryContentEvent());
}
break;
case NS_QUERY_SELECTION_AS_TRANSFERABLE:
{
// XXX remote event
ContentEventHandler handler(mPresContext);
handler.OnQuerySelectionAsTransferable(aEvent->AsQueryContentEvent());
}
break;
case NS_QUERY_CHARACTER_AT_POINT:
{
// XXX remote event
ContentEventHandler handler(mPresContext);
handler.OnQueryCharacterAtPoint(aEvent->AsQueryContentEvent());
}
break;
case NS_QUERY_DOM_WIDGET_HITTEST:
{
// XXX remote event
ContentEventHandler handler(mPresContext);
handler.OnQueryDOMWidgetHittest(aEvent->AsQueryContentEvent());
}
break;
case NS_SELECTION_SET:
IMEStateManager::HandleSelectionEvent(aPresContext, GetFocusedContent(),
aEvent->AsSelectionEvent());
break;
case NS_CONTENT_COMMAND_CUT:
case NS_CONTENT_COMMAND_COPY:
case NS_CONTENT_COMMAND_PASTE:
case NS_CONTENT_COMMAND_DELETE:
case NS_CONTENT_COMMAND_UNDO:
case NS_CONTENT_COMMAND_REDO:
case NS_CONTENT_COMMAND_PASTE_TRANSFERABLE:
{
DoContentCommandEvent(aEvent->AsContentCommandEvent());
}
break;
case NS_CONTENT_COMMAND_SCROLL:
{
DoContentCommandScrollEvent(aEvent->AsContentCommandEvent());
}
break;
case NS_COMPOSITION_START:
if (aEvent->mFlags.mIsTrusted) {
// If the event is trusted event, set the selected text to data of
// composition event.
WidgetCompositionEvent* compositionEvent = aEvent->AsCompositionEvent();
WidgetQueryContentEvent selectedText(true, NS_QUERY_SELECTED_TEXT,
compositionEvent->widget);
DoQuerySelectedText(&selectedText);
NS_ASSERTION(selectedText.mSucceeded, "Failed to get selected text");
compositionEvent->mData = selectedText.mReply.mString;
}
break;
default:
break;
}
return NS_OK;
}
// 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;
}
if (shouldActivate)
content->PerformAccesskey(shouldActivate, aIsTrustedEvent);
else {
nsIFocusManager* fm = nsFocusManager::GetFocusManager();
if (fm) {
nsCOMPtr<nsIDOMElement> element = do_QueryInterface(content);
fm->SetFocus(element, nsIFocusManager::FLAG_BYKEY);
}
}
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);
}
}
void
EventStateManager::HandleAccessKey(nsPresContext* aPresContext,
WidgetKeyboardEvent* aEvent,
nsEventStatus* aStatus,
nsIDocShellTreeItem* aBubbledFrom,
ProcessingAccessKeyState aAccessKeyState,
int32_t aModifierMask)
{
nsCOMPtr<nsIDocShell> docShell = aPresContext->GetDocShell();
// 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.
nsAutoTArray<uint32_t, 10> accessCharCodes;
nsContentUtils::GetAccessKeyCandidates(aEvent, accessCharCodes);
if (ExecuteAccessKey(accessCharCodes, aEvent->mFlags.mIsTrusted)) {
*aStatus = nsEventStatus_eConsumeNoDefault;
return;
}
}
// after the local accesskey handling
if (nsEventStatus_eConsumeNoDefault != *aStatus) {
// checking all sub docshells
if (!docShell) {
NS_WARNING("no docShellTreeNode for presContext");
return;
}
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(subPC, aEvent, aStatus, nullptr,
eAccessKeyProcessingDown, aModifierMask);
if (nsEventStatus_eConsumeNoDefault == *aStatus)
break;
}
}
}// if end . checking all sub docshell ends here.
// bubble up the process to the parent docshell if necessary
if (eAccessKeyProcessingDown != aAccessKeyState && nsEventStatus_eConsumeNoDefault != *aStatus) {
if (!docShell) {
NS_WARNING("no docShellTreeItem for presContext");
return;
}
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(parentPC, aEvent, aStatus, docShell,
eAccessKeyProcessingUp, aModifierMask);
}
}// if end. bubble up process
}// 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: {
if (remote->Manager()->IsContentParent()) {
remote->Manager()->AsContentParent()->MaybeInvokeDragSession(remote);
}
nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession();
uint32_t dropEffect = nsIDragService::DRAGDROP_ACTION_NONE;
uint32_t action = nsIDragService::DRAGDROP_ACTION_NONE;
if (dragSession) {
dragSession->DragEventDispatchedToChildProcess();
dragSession->GetDragAction(&action);
nsCOMPtr<nsIDOMDataTransfer> initialDataTransfer;
dragSession->GetDataTransfer(getter_AddRefs(initialDataTransfer));
if (initialDataTransfer) {
initialDataTransfer->GetDropEffectInt(&dropEffect);
}
}
bool retval = remote->SendRealDragEvent(*aEvent->AsDragEvent(),
action, dropEffect);
return retval;
}
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;
}
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 NS_CONTEXTMENU:
case eMouseEnterIntoWidget:
case eMouseExitFromWidget:
return true;
default:
return false;
}
case eTouchEventClass:
switch (aEvent.mMessage) {
case NS_TOUCH_START:
case NS_TOUCH_MOVE:
case NS_TOUCH_END:
case NS_TOUCH_CANCEL:
return true;
default:
return false;
}
case eDragEventClass:
switch (aEvent.mMessage) {
case NS_DRAGDROP_OVER:
case NS_DRAGDROP_EXIT:
case NS_DRAGDROP_DROP:
return true;
default:
break;
}
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.
nsAutoTArray<nsCOMPtr<nsIContent>, 1> targets;
if (aEvent->mClass != eTouchEventClass ||
aEvent->mMessage == NS_TOUCH_START) {
// If this event only has one target, and it's remote, add it to
// the array.
nsIFrame* frame = 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()->touches;
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;
}
nsRefPtr<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->mFlags.mIsTrusted ||
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)
{
nsRefPtr<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)) {
allowedToDispatch = false;
}
}
if (allowedToDispatch) {
// init the event while mCurrentTarget is still good
WidgetMouseEvent event(true, NS_CONTEXTMENU, targetWidget,
WidgetMouseEvent::eReal);
event.clickCount = 1;
FillInEventFromGestureDown(&event);
// stop selection tracking, we're in control now
if (mCurrentTarget)
{
nsRefPtr<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->GetCrossShadowCurrentDoc();
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 NS_CONTEXTMENU 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->widget)
return;
// Note that |inDownEvent| could be either a mouse down event or a
// synthesized mouse move event.
mGestureDownPoint = inDownEvent->refPoint + inDownEvent->widget->WidgetToScreenOffset();
if (inDownFrame) {
inDownFrame->GetContentForEvent(inDownEvent,
getter_AddRefs(mGestureDownContent));
mGestureDownFrameOwner = inDownFrame->GetContent();
if (!mGestureDownFrameOwner) {
mGestureDownFrameOwner = mGestureDownContent;
}
}
mGestureModifiers = inDownEvent->modifiers;
mGestureDownButtons = inDownEvent->buttons;
if (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->widget == 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->refPoint = mGestureDownPoint - aEvent->widget->WidgetToScreenOffset();
aEvent->modifiers = 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,
WidgetMouseEvent* 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)
{
nsRefPtr<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->refPoint + aEvent->widget->WidgetToScreenOffset();
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<nsISupports> container = aPresContext->GetContainerWeak();
nsCOMPtr<nsPIDOMWindow> window = do_GetInterface(container);
if (!window)
return;
nsRefPtr<DataTransfer> dataTransfer =
new DataTransfer(window, NS_DRAGDROP_START, 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->mFlags.mIsTrusted,
NS_DRAGDROP_START, widget);
FillInEventFromGestureDown(&startEvent);
WidgetDragEvent gestureEvent(aEvent->mFlags.mIsTrusted,
NS_DRAGDROP_GESTURE, widget);
FillInEventFromGestureDown(&gestureEvent);
startEvent.dataTransfer = gestureEvent.dataTransfer = dataTransfer;
startEvent.inputSource = gestureEvent.inputSource = aEvent->inputSource;
// 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 both the dragstart and draggesture events to the DOM. For
// elements in an editor, only fire the draggesture event so that the
// editor code can handle it but content doesn't see a dragstart.
nsEventStatus status = nsEventStatus_eIgnore;
EventDispatcher::Dispatch(targetContent, aPresContext, &startEvent,
nullptr, &status);
WidgetDragEvent* event = &startEvent;
if (status != nsEventStatus_eConsumeNoDefault) {
status = nsEventStatus_eIgnore;
EventDispatcher::Dispatch(targetContent, aPresContext, &gestureEvent,
nullptr, &status);
event = &gestureEvent;
}
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->mFlags.mPropagationStopped = true;
}
}
// Note that frame event handling doesn't care about NS_DRAGDROP_GESTURE,
// which is just as well since we don't really know which frame to
// send it to
// Reset mCurretTargetContent to what it was
mCurrentTargetContent = targetBeforeEvent;
}
// Now flush all pending notifications, for better responsiveness
// while dragging.
FlushPendingEvents(aPresContext);
}
} // GenerateDragGesture
void
EventStateManager::DetermineDragTargetAndDefaultData(nsPIDOMWindow* 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 draggesture/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<nsISupportsArray> 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.
nsRefPtr<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;
nsIFocusManager* fm = nsFocusManager::GetFocusManager();
if(!fm) return NS_ERROR_FAILURE;
nsCOMPtr<nsIDOMWindow> focusedWindow;
fm->GetFocusedWindow(getter_AddRefs(focusedWindow));
nsCOMPtr<nsPIDOMWindow> ourWindow = do_QueryInterface(focusedWindow);
if(!ourWindow) return NS_ERROR_FAILURE;
nsIDOMWindow *rootWindow = ourWindow->GetPrivateRoot();
if(!rootWindow) return NS_ERROR_FAILURE;
nsCOMPtr<nsIDOMWindow> contentWindow;
rootWindow->GetContent(getter_AddRefs(contentWindow));
if(!contentWindow) return NS_ERROR_FAILURE;
nsIDocument *doc = GetDocumentFromWindow(contentWindow);
if(!doc) return NS_ERROR_FAILURE;
nsIPresShell *presShell = doc->GetShell();
if(!presShell) return NS_ERROR_FAILURE;
nsPresContext *presContext = presShell->GetPresContext();
if(!presContext) return NS_ERROR_FAILURE;
nsCOMPtr<nsIDocShell> docshell(presContext->GetDocShell());
if(!docshell) return NS_ERROR_FAILURE;
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);
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);
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;
if (Preferences::GetBool("browser.zoom.full") || content->OwnerDoc()->IsSyntheticDocument()) {
ChangeFullZoom(change);
} else {
ChangeTextSize(change);
}
EnsureDocument(mPresContext);
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.
nsIScrollableFrame* scrollTarget =
ComputeScrollTarget(aTargetFrame, aEvent,
COMPUTE_LEGACY_MOUSE_SCROLL_EVENT_TARGET);
nsIFrame* scrollFrame = do_QueryFrame(scrollTarget);
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->deltaMode) {
case nsIDOMWheelEvent::DOM_DELTA_PAGE:
scrollDeltaX =
!aEvent->lineOrPageDeltaX ? 0 :
(aEvent->lineOrPageDeltaX > 0 ? nsIDOMUIEvent::SCROLL_PAGE_DOWN :
nsIDOMUIEvent::SCROLL_PAGE_UP);
scrollDeltaY =
!aEvent->lineOrPageDeltaY ? 0 :
(aEvent->lineOrPageDeltaY > 0 ? nsIDOMUIEvent::SCROLL_PAGE_DOWN :
nsIDOMUIEvent::SCROLL_PAGE_UP);
pixelDeltaX = RoundDown(aEvent->deltaX * scrollAmountInCSSPixels.width);
pixelDeltaY = RoundDown(aEvent->deltaY * scrollAmountInCSSPixels.height);
break;
case nsIDOMWheelEvent::DOM_DELTA_LINE:
scrollDeltaX = aEvent->lineOrPageDeltaX;
scrollDeltaY = aEvent->lineOrPageDeltaY;
pixelDeltaX = RoundDown(aEvent->deltaX * scrollAmountInCSSPixels.width);
pixelDeltaY = RoundDown(aEvent->deltaY * scrollAmountInCSSPixels.height);
break;
case nsIDOMWheelEvent::DOM_DELTA_PIXEL:
scrollDeltaX = aEvent->lineOrPageDeltaX;
scrollDeltaY = aEvent->lineOrPageDeltaY;
pixelDeltaX = RoundDown(aEvent->deltaX);
pixelDeltaY = RoundDown(aEvent->deltaY);
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->mFlags.mDefaultPrevented,
"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 || stateX.mDefaultPrevented) {
*aStatus = nsEventStatus_eConsumeNoDefault;
aEvent->mFlags.mDefaultPrevented = true;
aEvent->mFlags.mDefaultPreventedByContent |=
stateY.mDefaultPreventedByContent || 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->mFlags.mIsTrusted, NS_MOUSE_SCROLL,
aEvent->widget);
event.mFlags.mDefaultPrevented = aState.mDefaultPrevented;
event.mFlags.mDefaultPreventedByContent = aState.mDefaultPreventedByContent;
event.refPoint = aEvent->refPoint;
event.widget = aEvent->widget;
event.time = aEvent->time;
event.timeStamp = aEvent->timeStamp;
event.modifiers = aEvent->modifiers;
event.buttons = aEvent->buttons;
event.isHorizontal = (aDeltaDirection == DELTA_DIRECTION_X);
event.delta = aDelta;
event.inputSource = aEvent->inputSource;
nsEventStatus status = nsEventStatus_eIgnore;
EventDispatcher::Dispatch(targetContent, aTargetFrame->PresContext(),
&event, nullptr, &status);
aState.mDefaultPrevented =
event.mFlags.mDefaultPrevented || status == nsEventStatus_eConsumeNoDefault;
aState.mDefaultPreventedByContent = event.mFlags.mDefaultPreventedByContent;
}
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->mFlags.mIsTrusted, NS_MOUSE_PIXEL_SCROLL,
aEvent->widget);
event.mFlags.mDefaultPrevented = aState.mDefaultPrevented;
event.mFlags.mDefaultPreventedByContent = aState.mDefaultPreventedByContent;
event.refPoint = aEvent->refPoint;
event.widget = aEvent->widget;
event.time = aEvent->time;
event.timeStamp = aEvent->timeStamp;
event.modifiers = aEvent->modifiers;
event.buttons = aEvent->buttons;
event.isHorizontal = (aDeltaDirection == DELTA_DIRECTION_X);
event.delta = aPixelDelta;
event.inputSource = aEvent->inputSource;
nsEventStatus status = nsEventStatus_eIgnore;
EventDispatcher::Dispatch(targetContent, aTargetFrame->PresContext(),
&event, nullptr, &status);
aState.mDefaultPrevented =
event.mFlags.mDefaultPrevented || status == nsEventStatus_eConsumeNoDefault;
aState.mDefaultPreventedByContent = event.mFlags.mDefaultPreventedByContent;
}
nsIScrollableFrame*
EventStateManager::ComputeScrollTarget(nsIFrame* aTargetFrame,
WidgetWheelEvent* aEvent,
ComputeScrollTargetOptions aOptions)
{
return ComputeScrollTarget(aTargetFrame, aEvent->deltaX, aEvent->deltaY,
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
nsIScrollableFrame*
EventStateManager::ComputeScrollTarget(nsIFrame* aTargetFrame,
double aDirectionX,
double aDirectionY,
WidgetWheelEvent* aEvent,
ComputeScrollTargetOptions 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) {
nsIScrollableFrame* frameToScroll =
lastScrollFrame->GetScrollTargetFrame();
if (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);
nsIScrollableFrame* frameToScroll = nullptr;
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.
frameToScroll = scrollFrame->GetScrollTargetFrame();
if (!frameToScroll) {
continue;
}
// 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 = frameToScroll->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(frameToScroll, 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->deltaMode == 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);
}
nsRefPtr<nsFontMetrics> fm;
nsLayoutUtils::GetFontMetricsForFrame(rootFrame, getter_AddRefs(fm),
nsLayoutUtils::FontSizeInflationFor(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);
nsIFrame* lastScrollFrame = WheelTransaction::GetTargetFrame();
if (!lastScrollFrame) {
WheelTransaction::BeginTransaction(scrollFrame, aEvent);
} else if (lastScrollFrame != scrollFrame) {
WheelTransaction::EndTransaction();
WheelTransaction::BeginTransaction(scrollFrame, aEvent);
} else {
WheelTransaction::UpdateTransaction(aEvent);
}
// When the scroll event will not scroll any views, UpdateTransaction
// fired MozMouseScrollFailed event which is for automated testing.
// In the event handler, the target frame might be destroyed. Then,
// we should not try scrolling anything.
if (!scrollFrameWeak.IsAlive()) {
WheelTransaction::EndTransaction();
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->deltaMode) {
case nsIDOMWheelEvent