Bug 1406446 - part 1: InputContextAction should treat focus change during handling a user input as caused by user input even if it's caused by JS r?smaug
Currently, widget doesn't show VKB when input context change is caused by JS.
However, if it's caused by an event handler of a user input, user may expect
to open VKB. For example, if a touch event in fake editor causes moving
focus to actual editable node, user expect to show VKB.
Therefore, InputContextAction should declare two causes. One is unknown but
occurred during handling non-keyboard event. The other is unknown but occurred
during handling keyboard event.
However, EventStateManager doesn't have an API to check if it's being handling
a keyboard event. Therefore, this patch adds it first.
AutoHandlingUserInputStatePusher sends event type to StartHandlingUserInput()
and StopHandlingUserInput() of EventStateManager and sUserKeyboardEventDepth
manages the number of nested keyboard event handling. Therefore,
EventStateManager::IsHandlingKeyboardInput() can return if it's handling a
keyboard event.
IMEStateManager uses this new API to adjust the cause of changes of input
context.
Finally, InputContextAction::IsUserInput() is renamed to IsHandlingUserInput()
for consistency with EventStateManager and starts to return true when the
input context change is caused by script while it's handling a user input.
MozReview-Commit-ID: 5JsLqdqeGah
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -4021,17 +4021,17 @@ private:
NS_IMPL_ISUPPORTS(HandlingUserInputHelper, nsIJSRAIIHelper)
HandlingUserInputHelper::HandlingUserInputHelper(bool aHandlingUserInput)
: mHandlingUserInput(aHandlingUserInput),
mDestructCalled(false)
{
if (aHandlingUserInput) {
- EventStateManager::StartHandlingUserInput();
+ EventStateManager::StartHandlingUserInput(eVoidEvent);
}
}
HandlingUserInputHelper::~HandlingUserInputHelper()
{
// We assert, but just in case, make sure we notify the ESM.
MOZ_ASSERT(mDestructCalled);
if (!mDestructCalled) {
@@ -4043,17 +4043,17 @@ NS_IMETHODIMP
HandlingUserInputHelper::Destruct()
{
if (NS_WARN_IF(mDestructCalled)) {
return NS_ERROR_FAILURE;
}
mDestructCalled = true;
if (mHandlingUserInput) {
- EventStateManager::StopHandlingUserInput();
+ EventStateManager::StopHandlingUserInput(eVoidEvent);
}
return NS_OK;
}
} // unnamed namespace
NS_IMETHODIMP
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -260,16 +260,17 @@ NS_INTERFACE_MAP_END
/******************************************************************/
/* mozilla::EventStateManager */
/******************************************************************/
static uint32_t sESMInstanceCount = 0;
uint64_t EventStateManager::sUserInputCounter = 0;
int32_t EventStateManager::sUserInputEventDepth = 0;
+int32_t EventStateManager::sUserKeyboardEventDepth = 0;
bool EventStateManager::sNormalLMouseEventInProcess = false;
EventStateManager* EventStateManager::sActiveESM = nullptr;
nsIDocument* EventStateManager::sMouseOverDocument = nullptr;
AutoWeakFrame EventStateManager::sLastDragOverFrame = nullptr;
LayoutDeviceIntPoint EventStateManager::sPreLockPoint = LayoutDeviceIntPoint(0, 0);
LayoutDeviceIntPoint EventStateManager::sLastRefPoint = kInvalidRefPoint;
CSSIntPoint EventStateManager::sLastScreenPoint = CSSIntPoint(0, 0);
LayoutDeviceIntPoint EventStateManager::sSynthCenteringPoint = kInvalidRefPoint;
@@ -3936,16 +3937,47 @@ public:
};
/*static*/ bool
EventStateManager::IsHandlingUserInput()
{
return sUserInputEventDepth > 0;
}
+/*static*/ bool
+EventStateManager::IsHandlingKeyboardInput()
+{
+ return sUserKeyboardEventDepth > 0;
+}
+
+/*static*/ void
+EventStateManager::StartHandlingUserInput(EventMessage aMessage)
+{
+ ++sUserInputEventDepth;
+ ++sUserInputCounter;
+ if (sUserInputEventDepth == 1) {
+ sLatestUserInputStart = sHandlingInputStart = TimeStamp::Now();
+ }
+ if (WidgetEvent::IsKeyEventMessage(aMessage)) {
+ ++sUserKeyboardEventDepth;
+ }
+}
+
+/*static*/ void
+EventStateManager::StopHandlingUserInput(EventMessage aMessage)
+{
+ --sUserInputEventDepth;
+ if (sUserInputEventDepth == 0) {
+ sHandlingInputStart = TimeStamp();
+ }
+ if (WidgetEvent::IsKeyEventMessage(aMessage)) {
+ --sUserKeyboardEventDepth;
+ }
+}
+
static void
CreateMouseOrPointerWidgetEvent(WidgetMouseEvent* aMouseEvent,
EventMessage aMessage,
nsIContent* aRelatedContent,
nsAutoPtr<WidgetMouseEvent>& aNewEvent)
{
WidgetPointerEvent* sourcePointer = aMouseEvent->AsPointerEvent();
if (sourcePointer) {
@@ -6030,56 +6062,53 @@ EventStateManager::Prefs::Shutdown()
/******************************************************************/
/* mozilla::AutoHandlingUserInputStatePusher */
/******************************************************************/
AutoHandlingUserInputStatePusher::AutoHandlingUserInputStatePusher(
bool aIsHandlingUserInput,
WidgetEvent* aEvent,
- nsIDocument* aDocument) :
- mIsHandlingUserInput(aIsHandlingUserInput),
- mIsMouseDown(aEvent && aEvent->mMessage == eMouseDown),
- mResetFMMouseButtonHandlingState(false)
+ nsIDocument* aDocument)
+ : mMessage(aEvent ? aEvent->mMessage : eVoidEvent)
+ , mIsHandlingUserInput(aIsHandlingUserInput)
{
if (!aIsHandlingUserInput) {
return;
}
- EventStateManager::StartHandlingUserInput();
- if (mIsMouseDown) {
+ EventStateManager::StartHandlingUserInput(mMessage);
+ if (mMessage == eMouseDown) {
nsIPresShell::SetCapturingContent(nullptr, 0);
nsIPresShell::AllowMouseCapture(true);
}
if (!aDocument || !aEvent || !aEvent->IsTrusted()) {
return;
}
- mResetFMMouseButtonHandlingState =
- (aEvent->mMessage == eMouseDown || aEvent->mMessage == eMouseUp);
- if (mResetFMMouseButtonHandlingState) {
+ if (NeedsToResetFocusManagerMouseButtonHandlingState()) {
nsFocusManager* fm = nsFocusManager::GetFocusManager();
NS_ENSURE_TRUE_VOID(fm);
// If it's in modal state, mouse button event handling may be nested.
// E.g., a modal dialog is opened at mousedown or mouseup event handler
// and the dialog is clicked. Therefore, we should store current
// mouse button event handling document if nsFocusManager already has it.
mMouseButtonEventHandlingDocument =
fm->SetMouseButtonHandlingDocument(aDocument);
}
}
AutoHandlingUserInputStatePusher::~AutoHandlingUserInputStatePusher()
{
if (!mIsHandlingUserInput) {
return;
}
- EventStateManager::StopHandlingUserInput();
- if (mIsMouseDown) {
+ EventStateManager::StopHandlingUserInput(mMessage);
+ if (mMessage == eMouseDown) {
nsIPresShell::AllowMouseCapture(false);
}
- if (mResetFMMouseButtonHandlingState) {
+ if (NeedsToResetFocusManagerMouseButtonHandlingState()) {
nsFocusManager* fm = nsFocusManager::GetFocusManager();
NS_ENSURE_TRUE_VOID(fm);
nsCOMPtr<nsIDocument> handlingDocument =
fm->SetMouseButtonHandlingDocument(mMouseButtonEventHandlingDocument);
}
}
} // namespace mozilla
--- a/dom/events/EventStateManager.h
+++ b/dom/events/EventStateManager.h
@@ -228,45 +228,42 @@ public:
*/
bool CheckIfEventMatchesAccessKey(WidgetKeyboardEvent* aEvent,
nsPresContext* aPresContext);
nsresult SetCursor(int32_t aCursor, imgIContainer* aContainer,
bool aHaveHotspot, float aHotspotX, float aHotspotY,
nsIWidget* aWidget, bool aLockCursor);
- static void StartHandlingUserInput()
- {
- ++sUserInputEventDepth;
- ++sUserInputCounter;
- if (sUserInputEventDepth == 1) {
- sLatestUserInputStart = sHandlingInputStart = TimeStamp::Now();
- }
- }
-
- static void StopHandlingUserInput()
- {
- --sUserInputEventDepth;
- if (sUserInputEventDepth == 0) {
- sHandlingInputStart = TimeStamp();
- }
- }
+ /**
+ * StartHandlingUserInput() is called when we start to handle a user input.
+ * StopHandlingUserInput() is called when we finish handling a user input.
+ * If the caller knows which input event caused that, it should set
+ * aMessage to the event message. Otherwise, set eVoidEvent.
+ * Note that StopHandlingUserInput() caller should call it with exactly same
+ * event message as its corresponding StartHandlingUserInput() call because
+ * these methods may count the number of specific event message.
+ */
+ static void StartHandlingUserInput(EventMessage aMessage);
+ static void StopHandlingUserInput(EventMessage aMessage);
static TimeStamp GetHandlingInputStart() {
return sHandlingInputStart;
}
/**
* Returns true if the current code is being executed as a result of
- * user input. This includes anything that is initiated by user,
- * with the exception of page load events or mouse over events. If
- * this method is called from asynchronously executed code, such as
- * during layout reflows, it will return false.
+ * user input or keyboard input. The former includes anything that is
+ * initiated by user, with the exception of page load events or mouse
+ * over events. And the latter returns true when one of the user inputs
+ * is an input from keyboard. If these methods are called from asynchronously
+ * executed code, such as during layout reflows, it will return false.
*/
static bool IsHandlingUserInput();
+ static bool IsHandlingKeyboardInput();
/**
* Get the number of user inputs handled since process start. This
* includes anything that is initiated by user, with the exception
* of page load events or mouse over events.
*/
static uint64_t UserInputCount()
{
@@ -1087,22 +1084,24 @@ public:
// Array for accesskey support
nsCOMArray<nsIContent> mAccessKeys;
// The number of user inputs handled since process start. This
// includes anything that is initiated by user, with the exception
// of page load events or mouse over events.
static uint64_t sUserInputCounter;
- // The current depth of user inputs. This includes anything that is
- // initiated by user, with the exception of page load events or
- // mouse over events. Incremented whenever we start handling a user
- // input, decremented when we have finished handling a user
- // input. This depth is *not* reset in case of nested event loops.
+ // The current depth of user and keyboard inputs. sUserInputEventDepth
+ // is the number of any user input events, page load events and mouse over
+ // events. sUserKeyboardEventDepth is the number of keyboard input events.
+ // Incremented whenever we start handling a user input, decremented when we
+ // have finished handling a user input. This depth is *not* reset in case
+ // of nested event loops.
static int32_t sUserInputEventDepth;
+ static int32_t sUserKeyboardEventDepth;
static bool sNormalLMouseEventInProcess;
static EventStateManager* sActiveESM;
static void ClearGlobalActiveContent(EventStateManager* aClearer);
// Functions used for click hold context menus
@@ -1125,21 +1124,24 @@ class AutoHandlingUserInputStatePusher
{
public:
AutoHandlingUserInputStatePusher(bool aIsHandlingUserInput,
WidgetEvent* aEvent,
nsIDocument* aDocument);
~AutoHandlingUserInputStatePusher();
protected:
+ nsCOMPtr<nsIDocument> mMouseButtonEventHandlingDocument;
+ EventMessage mMessage;
bool mIsHandlingUserInput;
- bool mIsMouseDown;
- bool mResetFMMouseButtonHandlingState;
- nsCOMPtr<nsIDocument> mMouseButtonEventHandlingDocument;
+ bool NeedsToResetFocusManagerMouseButtonHandlingState() const
+ {
+ return mMessage == eMouseDown || mMessage == eMouseUp;
+ }
private:
// Hide so that this class can only be stack-allocated
static void* operator new(size_t /*size*/) CPP_THROW_NEW { return nullptr; }
static void operator delete(void* /*memory*/) {}
};
} // namespace mozilla
--- a/dom/events/IMEStateManager.cpp
+++ b/dom/events/IMEStateManager.cpp
@@ -1360,16 +1360,24 @@ IMEStateManager::SetIMEState(const IMESt
// XXX I think that we should use nsContentUtils::IsCallerChrome() instead
// of the process type.
if (aAction.mCause == InputContextAction::CAUSE_UNKNOWN &&
!XRE_IsContentProcess()) {
aAction.mCause = InputContextAction::CAUSE_UNKNOWN_CHROME;
}
+ if ((aAction.mCause == InputContextAction::CAUSE_UNKNOWN ||
+ aAction.mCause == InputContextAction::CAUSE_UNKNOWN_CHROME) &&
+ EventStateManager::IsHandlingUserInput()) {
+ aAction.mCause = EventStateManager::IsHandlingKeyboardInput() ?
+ InputContextAction::CAUSE_UNKNOWN_DURING_KEYBOARD_INPUT :
+ InputContextAction::CAUSE_UNKNOWN_DURING_NON_KEYBOARD_INPUT;
+ }
+
SetInputContext(aWidget, context, aAction);
}
// static
void
IMEStateManager::SetInputContext(nsIWidget* aWidget,
const InputContext& aInputContext,
const InputContextAction& aAction)
--- a/widget/BasicEvents.h
+++ b/widget/BasicEvents.h
@@ -764,19 +764,23 @@ public:
* Returns true if the event mMessage is one of mouse events.
*/
bool HasMouseEventMessage() const;
/**
* Returns true if the event mMessage is one of drag events.
*/
bool HasDragEventMessage() const;
/**
- * Returns true if the event mMessage is one of key events.
+ * Returns true if aMessage or mMessage is one of key events.
*/
- bool HasKeyEventMessage() const;
+ static bool IsKeyEventMessage(EventMessage aMessage);
+ bool HasKeyEventMessage() const
+ {
+ return IsKeyEventMessage(mMessage);
+ }
/**
* Returns true if the event mMessage is one of composition events or text
* event.
*/
bool HasIMEEventMessage() const;
/**
* Returns true if the event mMessage is one of plugin activation events.
*/
--- a/widget/IMEData.h
+++ b/widget/IMEData.h
@@ -365,17 +365,22 @@ struct InputContextAction final
// The cause is unknown but originated from chrome. Focus might have been
// changed by chrome script.
CAUSE_UNKNOWN_CHROME,
// The cause is user's keyboard operation.
CAUSE_KEY,
// The cause is user's mouse operation.
CAUSE_MOUSE,
// The cause is user's touch operation (implies mouse)
- CAUSE_TOUCH
+ CAUSE_TOUCH,
+ // The cause is unknown but it occurs during user input except keyboard
+ // input. E.g., an event handler of a user input event moves focus.
+ CAUSE_UNKNOWN_DURING_NON_KEYBOARD_INPUT,
+ // The cause is unknown but it occurs during keyboard input.
+ CAUSE_UNKNOWN_DURING_KEYBOARD_INPUT,
};
Cause mCause;
/**
* mFocusChange indicates what happened for focus.
*/
enum FocusChange
{
@@ -400,34 +405,59 @@ struct InputContextAction final
bool ContentGotFocusByTrustedCause() const
{
return (mFocusChange == GOT_FOCUS &&
mCause != CAUSE_UNKNOWN);
}
bool UserMightRequestOpenVKB() const
{
- return (mFocusChange == FOCUS_NOT_CHANGED &&
- (mCause == CAUSE_MOUSE || mCause == CAUSE_TOUCH));
- }
-
- static bool IsUserAction(Cause aCause)
- {
- switch (aCause) {
- case CAUSE_KEY:
+ // If focus is changed, user must not request to open VKB.
+ if (mFocusChange != FOCUS_NOT_CHANGED) {
+ return false;
+ }
+ switch (mCause) {
+ // If user clicks or touches focused editor, user must request to open
+ // VKB.
case CAUSE_MOUSE:
case CAUSE_TOUCH:
+ // If script does something during a user input and that causes changing
+ // input context, user might request to open VKB. E.g., user clicks
+ // dummy editor and JS moves focus to an actual editable node. However,
+ // this should return false if the user input is a keyboard event since
+ // physical keyboard operation shouldn't cause opening VKB.
+ case CAUSE_UNKNOWN_DURING_NON_KEYBOARD_INPUT:
return true;
default:
return false;
}
}
- bool IsUserAction() const {
- return IsUserAction(mCause);
+ /**
+ * IsHandlingUserInput() returns true if it's caused by a user action directly
+ * or it's caused by script or something but it occurred while we're handling
+ * a user action. E.g., when it's caused by Element.focus() in an event
+ * handler of a user input, this returns true.
+ */
+ static bool IsHandlingUserInput(Cause aCause)
+ {
+ switch (aCause) {
+ case CAUSE_KEY:
+ case CAUSE_MOUSE:
+ case CAUSE_TOUCH:
+ case CAUSE_UNKNOWN_DURING_NON_KEYBOARD_INPUT:
+ case CAUSE_UNKNOWN_DURING_KEYBOARD_INPUT:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ bool IsHandlingUserInput() const {
+ return IsHandlingUserInput(mCause);
}
InputContextAction()
: mCause(CAUSE_UNKNOWN)
, mFocusChange(FOCUS_NOT_CHANGED)
{
}
--- a/widget/WidgetEventImpl.cpp
+++ b/widget/WidgetEventImpl.cpp
@@ -267,20 +267,21 @@ WidgetEvent::HasDragEventMessage() const
case eDrop:
case eDragLeave:
return true;
default:
return false;
}
}
+/* static */
bool
-WidgetEvent::HasKeyEventMessage() const
+WidgetEvent::IsKeyEventMessage(EventMessage aMessage)
{
- switch (mMessage) {
+ switch (aMessage) {
case eKeyDown:
case eKeyPress:
case eKeyUp:
case eKeyDownOnPlugin:
case eKeyUpOnPlugin:
case eAccessKeyNotFound:
return true;
default:
--- a/widget/windows/WinIMEHandler.cpp
+++ b/widget/windows/WinIMEHandler.cpp
@@ -798,17 +798,17 @@ IMEHandler::NeedOnScreenKeyboard()
return true;
}
// If the last focus cause was not user-initiated (ie a result of code
// setting focus to an element) then don't auto-show a keyboard. This
// avoids cases where the keyboard would pop up "just" because e.g. a
// web page chooses to focus a search field on the page, even when that
// really isn't what the user is trying to do at that moment.
- if (!InputContextAction::IsUserAction(sLastContextActionCause)) {
+ if (!InputContextAction::IsHandlingUserInput(sLastContextActionCause)) {
return false;
}
// This function should be only invoked for machines with touch screens.
if ((::GetSystemMetrics(SM_DIGITIZER) & NID_INTEGRATED_TOUCH)
!= NID_INTEGRATED_TOUCH) {
Preferences::SetString(kOskDebugReason,
L"IKPOS: Touch screen not found.");