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
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 |