Bug 1832306 - Make BrowserParent stop handling access keys with delayed events r=smaug,NeilDeakin

`EventStateManager::WalkESMTreeToHandleAccessKey()` requests all `BrowserParent`
instances to handle access key in content processes:
https://searchfox.org/mozilla-central/rev/a4fd6daad3a4123d995478467c1274653b283801/dom/events/EventStateManager.cpp#1379,1398,1410-1411

Then, content processes will reply if no element matches with the access key:
https://searchfox.org/mozilla-central/rev/8e1b221afcdae76284b1439c547b032d1f84d236/dom/ipc/BrowserChild.cpp#2304-2305,2310

Finally, the parent process handles the keypress event:
https://searchfox.org/mozilla-central/rev/8e1b221afcdae76284b1439c547b032d1f84d236/dom/ipc/BrowserParent.cpp#2710-2712,2720,2725

However, this is odd because if multiple remote processes calls
`BrowserParent::RecvAccessKeyNotHandled()`, the parent process will handle
same `keypress` event multiple times.

The approach of this patch is, `BrowserParent` should store main data of
sending `eKeyPress` event, and `RecvAccessKeyNotHandled()` handles only if
the coming event matches with the stored one and clear the data to avoid
handling multiple times.

Even with this approach, we cannot avoid one `eKeyPress` event handled in
multiple content processes, or in both the main process and a content process.

Differential Revision: https://phabricator.services.mozilla.com/D179172
This commit is contained in:
Masayuki Nakano 2023-06-15 13:33:19 +00:00
parent eb795dabb9
commit 828d108d6f
2 changed files with 116 additions and 10 deletions

View File

@ -8,6 +8,7 @@
#include "BrowserParent.h"
#include "mozilla/AlreadyAddRefed.h"
#include "mozilla/EventForwards.h"
#ifdef ACCESSIBILITY
# include "mozilla/a11y/DocAccessibleParent.h"
@ -46,6 +47,7 @@
#include "mozilla/layers/InputAPZContext.h"
#include "mozilla/layout/RemoteLayerTreeOwner.h"
#include "mozilla/LookAndFeel.h"
#include "mozilla/Maybe.h"
#include "mozilla/MiscEvents.h"
#include "mozilla/MouseEvents.h"
#include "mozilla/NativeKeyBindingsType.h"
@ -181,7 +183,79 @@ BrowserParent* BrowserParent::sLastMouseRemoteTarget = nullptr;
// from the ones registered by webProgressListeners.
#define NOTIFY_FLAG_SHIFT 16
namespace mozilla::dom {
namespace mozilla {
/**
* Store data of a keypress event which is requesting to handled it in a remote
* process or some remote processes.
*/
class RequestingAccessKeyEventData {
public:
RequestingAccessKeyEventData() = delete;
static void OnBrowserParentCreated() {
MOZ_ASSERT(sBrowserParentCount <= INT32_MAX);
sBrowserParentCount++;
}
static void OnBrowserParentDestroyed() {
MOZ_ASSERT(sBrowserParentCount > 0);
sBrowserParentCount--;
// To avoid memory leak, we need to reset sData when the last BrowserParent
// is destroyed.
if (!sBrowserParentCount) {
Clear();
}
}
static void Set(const WidgetKeyboardEvent& aKeyPressEvent) {
MOZ_ASSERT(aKeyPressEvent.mMessage == eKeyPress);
MOZ_ASSERT(sBrowserParentCount > 0);
sData =
Some(Data{aKeyPressEvent.mAlternativeCharCodes, aKeyPressEvent.mKeyCode,
aKeyPressEvent.mCharCode, aKeyPressEvent.mKeyNameIndex,
aKeyPressEvent.mCodeNameIndex, aKeyPressEvent.mKeyValue,
aKeyPressEvent.mModifiers});
}
static void Clear() { sData.reset(); }
[[nodiscard]] static bool Equals(const WidgetKeyboardEvent& aKeyPressEvent) {
MOZ_ASSERT(sBrowserParentCount > 0);
return sData.isSome() && sData->Equals(aKeyPressEvent);
}
[[nodiscard]] static bool IsSet() {
MOZ_ASSERT(sBrowserParentCount > 0);
return sData.isSome();
}
private:
struct Data {
[[nodiscard]] bool Equals(const WidgetKeyboardEvent& aKeyPressEvent) {
return mKeyCode == aKeyPressEvent.mKeyCode &&
mCharCode == aKeyPressEvent.mCharCode &&
mKeyNameIndex == aKeyPressEvent.mKeyNameIndex &&
mCodeNameIndex == aKeyPressEvent.mCodeNameIndex &&
mKeyValue == aKeyPressEvent.mKeyValue &&
mModifiers == aKeyPressEvent.mModifiers &&
mAlternativeCharCodes == aKeyPressEvent.mAlternativeCharCodes;
}
CopyableTArray<AlternativeCharCode> mAlternativeCharCodes;
uint32_t mKeyCode;
uint32_t mCharCode;
KeyNameIndex mKeyNameIndex;
CodeNameIndex mCodeNameIndex;
nsString mKeyValue;
Modifiers mModifiers;
};
static Maybe<Data> sData;
static int32_t sBrowserParentCount;
};
int32_t RequestingAccessKeyEventData::sBrowserParentCount = 0;
Maybe<RequestingAccessKeyEventData::Data> RequestingAccessKeyEventData::sData;
namespace dom {
BrowserParent::LayerToBrowserParentTable*
BrowserParent::sLayerToBrowserParentTable = nullptr;
@ -238,6 +312,9 @@ BrowserParent::BrowserParent(ContentParent* aManager, const TabId& aTabId,
mLockedNativePointer(false),
mShowingTooltip(false) {
MOZ_ASSERT(aManager);
RequestingAccessKeyEventData::OnBrowserParentCreated();
// When the input event queue is disabled, we don't need to handle the case
// that some input events are dispatched before PBrowserConstructor.
mIsReadyToHandleInputEvents = !ContentParent::IsInputEventQueueSupported();
@ -259,7 +336,9 @@ BrowserParent::BrowserParent(ContentParent* aManager, const TabId& aTabId,
}
}
BrowserParent::~BrowserParent() = default;
BrowserParent::~BrowserParent() {
RequestingAccessKeyEventData::OnBrowserParentDestroyed();
}
/* static */
BrowserParent* BrowserParent::GetFocused() { return sFocus; }
@ -1100,6 +1179,7 @@ void BrowserParent::HandleAccessKey(const WidgetKeyboardEvent& aEvent,
// because the event may be dispatched to it as normal keyboard event.
// Therefore, we should use local copy to send it.
WidgetKeyboardEvent localEvent(aEvent);
RequestingAccessKeyEventData::Set(localEvent);
Unused << SendHandleAccessKey(localEvent, aCharCodes);
}
}
@ -2718,11 +2798,27 @@ mozilla::ipc::IPCResult BrowserParent::RecvAccessKeyNotHandled(
// This is called only when this process had focus and HandleAccessKey
// message was posted to all remote process and each remote process didn't
// execute any content access keys.
// XXX If there were two or more remote processes, this may be called
// twice or more for a keyboard event, that must be a bug. But how to
// detect if received event has already been handled?
MOZ_ASSERT(aEvent.mMessage == eKeyPress);
if (MOZ_UNLIKELY(aEvent.mMessage != eKeyPress || !aEvent.IsTrusted())) {
return IPC_FAIL(this, "Called with unexpected event");
}
// If there is no requesting event, the event may have already been handled
// when it's returned from another remote process.
if (MOZ_UNLIKELY(!RequestingAccessKeyEventData::IsSet())) {
return IPC_OK();
}
// If the event does not match with the one which we requested a remote
// process to handle access key of (that means that we has already requested
// for another key press), we should ignore this call because user focuses
// to the last key press.
if (MOZ_UNLIKELY(!RequestingAccessKeyEventData::Equals(aEvent))) {
return IPC_OK();
}
RequestingAccessKeyEventData::Clear();
WidgetKeyboardEvent localEvent(aEvent);
localEvent.MarkAsHandledInRemoteProcess();
localEvent.mMessage = eAccessKeyNotFound;
@ -4010,4 +4106,5 @@ mozilla::ipc::IPCResult BrowserParent::RecvShowDynamicToolbar() {
return IPC_OK();
}
} // namespace mozilla::dom
} // namespace dom
} // namespace mozilla

View File

@ -80,12 +80,21 @@ enum class AccessKeyType {
******************************************************************************/
struct AlternativeCharCode {
AlternativeCharCode() : mUnshiftedCharCode(0), mShiftedCharCode(0) {}
AlternativeCharCode() = default;
AlternativeCharCode(uint32_t aUnshiftedCharCode, uint32_t aShiftedCharCode)
: mUnshiftedCharCode(aUnshiftedCharCode),
mShiftedCharCode(aShiftedCharCode) {}
uint32_t mUnshiftedCharCode;
uint32_t mShiftedCharCode;
uint32_t mUnshiftedCharCode = 0u;
uint32_t mShiftedCharCode = 0u;
bool operator==(const AlternativeCharCode& aOther) const {
return mUnshiftedCharCode == aOther.mUnshiftedCharCode &&
mShiftedCharCode == aOther.mShiftedCharCode;
}
bool operator!=(const AlternativeCharCode& aOther) const {
return !(*this == aOther);
}
};
/******************************************************************************