Bug 1674044 - Make `IMEStateManager` put off to handle focused browser change while the application becomes activated r=hsivonen

`IMEStateManager::OnFocusMovedBetweenBrowsers()` is called by
`BrowserParent::UnsetTopLevelWebFocusAll()` from
`nsFocusManager::WindowRaised()` when a window becomes active from the app
itself is deactivated.  At this time, `aBlur` is  set to the last
`BrowserParent` which had focus before inactivated, and `aFocus` is always
`nullptr` because of assuming the following code in
`nsFocusManager::WindowRised()` will call `OnFocusMovedBetweenBrowsers()` to
set focus to focused browser part at that time.  However, the first call of
`OnFocusMovedBetweenBrowsers()` requesting to commit composition if there is
a composition because it believes that the focused process is just blurred and
nobody would take focus.

So, `IMEStateManager::OnFocusMovedBetweenBrowsers()` needs to wait to handle
its jobs until another its call for setting focus to the remote process when
the app is activated.

Therefore, this patch makes `IMEStateManager` manage whether the process is
active or not from the point of view of IME handling.  I.e., in the main
process, it can be treated as active when a window is the focused window in
the desktop, and in a content process, it can be treated as active when the
process has focus.  So, this is managed by `sIsActive`.

Then, `IMEStateManager` needs information of first blurred `BrowserParent`
and `last focused `BrowserParent` in the case for putting off to handle its
jobs.  Therefore, this patch adds
`IMEStateManager::sPendingFocusedBrowserSwitchingData` to manage them.

Finally, this patch makes `IMEStateManager::OnFocusMovedBetweenBrowsers()`
cancel pending handling if first blurred `BrowserParent` and last focused
`RrowserParent`are same.  And also making
`IMEStateManager::OnFocusChangeInternal()` call `OnFocusMoveBetweenBrowsers()`
if there is pending jobs of `OnFocusMoveBetweenBrowsers()`.

UThis requires an API to create the case to all windows deactive to test it.
However, there is no way to do that.  Therefore, this patch does not have
any tests.

Differential Revision: https://phabricator.services.mozilla.com/D97687
This commit is contained in:
Masayuki Nakano 2020-11-20 14:55:31 +00:00
Родитель b85ecde81f
Коммит e256502715
2 изменённых файлов: 148 добавлений и 28 удалений

Просмотреть файл

@ -79,6 +79,9 @@ InputContext IMEStateManager::sActiveChildInputContext;
bool IMEStateManager::sInstalledMenuKeyboardListener = false;
bool IMEStateManager::sIsGettingNewIMEState = false;
bool IMEStateManager::sCleaningUpForStoppingIMEStateManagement = false;
bool IMEStateManager::sIsActive = false;
Maybe<IMEStateManager::PendingFocusedBrowserSwitchingData>
IMEStateManager::sPendingFocusedBrowserSwitchingData;
// static
void IMEStateManager::Init() {
@ -91,9 +94,12 @@ void IMEStateManager::Init() {
void IMEStateManager::Shutdown() {
MOZ_LOG(
sISMLog, LogLevel::Info,
("Shutdown(), sTextCompositions=0x%p, sTextCompositions->Length()=%zu",
sTextCompositions, sTextCompositions ? sTextCompositions->Length() : 0));
("Shutdown(), sTextCompositions=0x%p, sTextCompositions->Length()=%zu, "
"sPendingFocusedBrowserSwitchingData.isSome()=%s",
sTextCompositions, sTextCompositions ? sTextCompositions->Length() : 0,
GetBoolName(sPendingFocusedBrowserSwitchingData.isSome())));
sPendingFocusedBrowserSwitchingData.reset();
MOZ_ASSERT(!sTextCompositions || !sTextCompositions->Length());
delete sTextCompositions;
sTextCompositions = nullptr;
@ -108,6 +114,43 @@ void IMEStateManager::OnFocusMovedBetweenBrowsers(BrowserParent* aBlur,
MOZ_ASSERT(aBlur != aFocus);
MOZ_ASSERT(XRE_IsParentProcess());
if (sPendingFocusedBrowserSwitchingData.isSome()) {
MOZ_ASSERT(aBlur ==
sPendingFocusedBrowserSwitchingData.ref().mBrowserParentFocused);
// If focus is not changed between browsers actually, we need to do
// nothing here. Let's cancel handling what this method does.
if (sPendingFocusedBrowserSwitchingData.ref().mBrowserParentBlurred ==
aFocus) {
sPendingFocusedBrowserSwitchingData.reset();
MOZ_LOG(sISMLog, LogLevel::Info,
(" OnFocusMovedBetweenBrowsers(), canceled all pending focus "
"moves between browsers"));
return;
}
aBlur = sPendingFocusedBrowserSwitchingData.ref().mBrowserParentBlurred;
sPendingFocusedBrowserSwitchingData.ref().mBrowserParentFocused = aFocus;
MOZ_ASSERT(aBlur != aFocus);
}
// If application was inactive, but is now activated, and the last focused
// this is called by BrowserParent::UnsetTopLevelWebFocusAll() from
// nsFocusManager::WindowRaised(). If a content has focus in a remote
// process and it has composition, it may get focus back later and the
// composition shouldn't be commited now. Therefore, we should put off to
// handle this until getting another call of this method or a call of
//`OnFocusChangeInternal()`.
if (aBlur && !aFocus && !sIsActive && sWidget && sTextCompositions &&
sTextCompositions->GetCompositionFor(sWidget)) {
if (sPendingFocusedBrowserSwitchingData.isNothing()) {
sPendingFocusedBrowserSwitchingData.emplace(aBlur, aFocus);
}
MOZ_LOG(sISMLog, LogLevel::Debug,
(" OnFocusMovedBetweenBrowsers(), put off to handle it until "
"next OnFocusChangeInternal() call"));
return;
}
sPendingFocusedBrowserSwitchingData.reset();
nsCOMPtr<nsIWidget> oldWidget = sWidget;
nsCOMPtr<nsIWidget> newWidget =
aFocus ? aFocus->GetTextInputHandlingWidget() : nullptr;
@ -221,6 +264,7 @@ void IMEStateManager::StopIMEStateManagement() {
sActiveInputContextWidget = nullptr;
sPresContext = nullptr;
sContent = nullptr;
sIsActive = false;
DestroyIMEContentObserver();
}
@ -388,6 +432,15 @@ nsresult IMEStateManager::OnChangeFocusInternal(nsPresContext* aPresContext,
nsIContent* aContent,
InputContextAction aAction) {
bool remoteHasFocus = EventStateManager::IsRemoteTarget(aContent);
// If we've handled focused content, we were inactive but now active,
// a remote process has focus, and setting focus to same content in the main
// process, it means that we're restoring focus without changing DOM focus
// both in the main process and the remote process.
const bool restoringContextForRemoteContent =
XRE_IsParentProcess() && remoteHasFocus && !sIsActive && aPresContext &&
sPresContext && sContent && sPresContext.get() == aPresContext &&
sContent.get() == aContent &&
aAction.mFocusChange != InputContextAction::MENU_GOT_PSEUDO_FOCUS;
MOZ_LOG(sISMLog, LogLevel::Info,
("OnChangeFocusInternal(aPresContext=0x%p (available: %s), "
@ -395,14 +448,40 @@ nsresult IMEStateManager::OnChangeFocusInternal(nsPresContext* aPresContext,
"mFocusChange=%s }), "
"sPresContext=0x%p (available: %s), sContent=0x%p, "
"sWidget=0x%p (available: %s), BrowserParent::GetFocused()=0x%p, "
"sActiveIMEContentObserver=0x%p, sInstalledMenuKeyboardListener=%s",
"sActiveIMEContentObserver=0x%p, sInstalledMenuKeyboardListener=%s, "
"sIsActive=%s, restoringContextForRemoteContent=%s",
aPresContext, GetBoolName(CanHandleWith(aPresContext)), aContent,
GetBoolName(remoteHasFocus), ToString(aAction.mCause).c_str(),
ToString(aAction.mFocusChange).c_str(), sPresContext.get(),
GetBoolName(CanHandleWith(sPresContext)), sContent.get(), sWidget,
GetBoolName(sWidget && !sWidget->Destroyed()),
BrowserParent::GetFocused(), sActiveIMEContentObserver.get(),
GetBoolName(sInstalledMenuKeyboardListener)));
GetBoolName(sInstalledMenuKeyboardListener), GetBoolName(sIsActive),
GetBoolName(restoringContextForRemoteContent)));
sIsActive = !!aPresContext;
if (sPendingFocusedBrowserSwitchingData.isSome()) {
MOZ_ASSERT(XRE_IsParentProcess());
nsCOMPtr<nsIContent> currentContent = sContent.get();
RefPtr<nsPresContext> currentPresContext = sPresContext.get();
RefPtr<BrowserParent> browserParentBlurred =
sPendingFocusedBrowserSwitchingData.ref().mBrowserParentBlurred;
RefPtr<BrowserParent> browserParentFocused =
sPendingFocusedBrowserSwitchingData.ref().mBrowserParentFocused;
OnFocusMovedBetweenBrowsers(browserParentBlurred, browserParentFocused);
// If another call of this method happens during the
// OnFocusMovedBetweenBrowsers call, we shouldn't take back focus to
// the old one.
if (currentContent != sContent.get() ||
currentPresContext != sPresContext.get()) {
MOZ_LOG(sISMLog, LogLevel::Debug,
(" OnChangeFocusInternal(aPresContext=0x%p, aContent=0x%p) "
"stoped handling it because the focused content was changed to "
"sPresContext=0x%p, sContent=0x%p by another call",
aPresContext, aContent, sPresContext.get(), sContent.get()));
return NS_OK;
}
}
// If new aPresShell has been destroyed, this should handle the focus change
// as nobody is getting focus.
@ -420,7 +499,7 @@ nsresult IMEStateManager::OnChangeFocusInternal(nsPresContext* aPresContext,
bool focusActuallyChanging =
(sContent != aContent || sPresContext != aPresContext ||
oldWidget != newWidget ||
(remoteHasFocus &&
(remoteHasFocus && !restoringContextForRemoteContent &&
(aAction.mFocusChange != InputContextAction::MENU_GOT_PSEUDO_FOCUS)));
// If old widget has composition, we may need to commit composition since
@ -467,15 +546,14 @@ nsresult IMEStateManager::OnChangeFocusInternal(nsPresContext* aPresContext,
if (!aPresContext) {
MOZ_LOG(sISMLog, LogLevel::Debug,
(" OnChangeFocusInternal(), "
"no nsPresContext is being activated"));
(" OnChangeFocusInternal(), no nsPresContext is being activated"));
return NS_OK;
}
if (NS_WARN_IF(!newWidget)) {
MOZ_LOG(sISMLog, LogLevel::Error,
(" OnChangeFocusInternal(), FAILED due to "
"no widget to manage its IME state"));
(" OnChangeFocusInternal(), FAILED due to no widget to manage its "
"IME state"));
return NS_OK;
}
@ -512,18 +590,17 @@ nsresult IMEStateManager::OnChangeFocusInternal(nsPresContext* aPresContext,
context.mOrigin == InputContext::ORIGIN_CONTENT) {
setIMEState = false;
MOZ_LOG(sISMLog, LogLevel::Debug,
(" OnChangeFocusInternal(), doesn't set IME "
"state because focused element (or document) is in a child "
"process "
"and the IME state is already disabled by a remote process"));
(" OnChangeFocusInternal(), doesn't set IME state because "
"focused element (or document) is in a child process and the "
"IME state is already disabled by a remote process"));
} else {
// When new remote process gets focus, we should forget input context
// coming from old focused remote process.
ResetActiveChildInputContext();
MOZ_LOG(sISMLog, LogLevel::Debug,
(" OnChangeFocusInternal(), will disable IME "
"until new focused element (or document) in the child process "
"will get focus actually"));
(" OnChangeFocusInternal(), will disable IME until new "
"focused element (or document) in the child process will get "
"focus actually"));
}
} else if (newWidget->GetInputContext().mOrigin !=
InputContext::ORIGIN_MAIN) {
@ -533,11 +610,10 @@ nsresult IMEStateManager::OnChangeFocusInternal(nsPresContext* aPresContext,
// composition. Then, we shouldn't commit the composition with making
// IME state disabled.
setIMEState = false;
MOZ_LOG(sISMLog, LogLevel::Debug,
(" OnChangeFocusInternal(), doesn't set IME "
"state because focused element (or document) is already in the "
"child "
"process"));
MOZ_LOG(
sISMLog, LogLevel::Debug,
(" OnChangeFocusInternal(), doesn't set IME state because focused "
"element (or document) is already in the child process"));
}
} else {
// When this process gets focus, we should forget input context coming
@ -552,8 +628,8 @@ nsresult IMEStateManager::OnChangeFocusInternal(nsPresContext* aPresContext,
InputContext context = newWidget->GetInputContext();
if (context.mIMEState.mEnabled == newState.mEnabled) {
MOZ_LOG(sISMLog, LogLevel::Debug,
(" OnChangeFocusInternal(), "
"neither focus nor IME state is changing"));
(" OnChangeFocusInternal(), neither focus nor IME state is "
"changing"));
return NS_OK;
}
aAction.mFocusChange = InputContextAction::FOCUS_NOT_CHANGED;
@ -594,11 +670,10 @@ nsresult IMEStateManager::OnChangeFocusInternal(nsPresContext* aPresContext,
if (newState.mEnabled == IMEState::PLUGIN) {
CreateIMEContentObserver(nullptr);
if (sActiveIMEContentObserver) {
MOZ_LOG(
sISMLog, LogLevel::Debug,
(" OnChangeFocusInternal(), an "
"IMEContentObserver instance is created for plugin and trying to "
"flush its pending notifications..."));
MOZ_LOG(sISMLog, LogLevel::Debug,
(" OnChangeFocusInternal(), an IMEContentObserver instance is "
"created for plugin and trying to flush its pending "
"notifications..."));
sActiveIMEContentObserver->TryToFlushPendingNotifications(false);
}
}

Просмотреть файл

@ -8,6 +8,7 @@
#define mozilla_IMEStateManager_h_
#include "mozilla/EventForwards.h"
#include "mozilla/Maybe.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/dom/BrowserParent.h"
#include "nsIWidget.h"
@ -391,6 +392,50 @@ class IMEStateManager {
// only while `IMEStateManager::StopIMEStateManagement()`.
static bool sCleaningUpForStoppingIMEStateManagement;
// Set to true when:
// - In the main process, a window belonging to this app is active in the
// desktop.
// - In a content process, the process has focus.
//
// This is updated by `OnChangeFocusInternal()` is called in the main
// process. Therefore, this indicates the active state which
// `IMEStateManager` notified the focus change, there is timelag from
// the `nsFocusManager`'s status update. This allows that all methods
// to handle something specially when they are called while the process
// is being activated or inactivated. E.g., `OnFocusMovedBetweenBrowsers()`
// is called twice before `OnChangeFocusInternal()` when the main process
// becomes active. In this case, it wants to wait a following call of
// `OnChangeFocusInternal()` to keep active composition. See also below.
static bool sIsActive;
// While the application is being activated, `OnFocusMovedBetweenBrowsers()`
// are called twice before `OnChangeFocusInternal()`. First time, aBlur is
// the last focused `BrowserParent` at deactivating and aFocus is always
// `nullptr`. Then, it'll be called again with actually focused
// `BrowserParent` when a content in a remote process has focus. If we need
// to keep active composition while all windows are deactivated, we shouldn't
// commit it at the first call since usually, the second call's aFocus
// and the first call's aBlur are same `BrowserParent`. For solving this
// issue, we need to merge the given `BrowserParent`s of multiple calls of
// `OnFocusMovedBetweenBrowsers()`. The following struct is the data for
// calling `OnFocusMovedBetweenBrowsers()` later from
// `OnChangeFocusInternal()`. Note that focus can be moved even while the
// main process is not active because JS can change focus. In such case,
// composition is committed at that time. Therefore, this is required only
// when the main process is activated and there is a composition in a remote
// process.
struct PendingFocusedBrowserSwitchingData final {
RefPtr<BrowserParent> mBrowserParentBlurred;
RefPtr<BrowserParent> mBrowserParentFocused;
PendingFocusedBrowserSwitchingData() = delete;
explicit PendingFocusedBrowserSwitchingData(BrowserParent* aBlur,
BrowserParent* aFocus)
: mBrowserParentBlurred(aBlur), mBrowserParentFocused(aFocus) {}
};
static Maybe<PendingFocusedBrowserSwitchingData>
sPendingFocusedBrowserSwitchingData;
class MOZ_STACK_CLASS GettingNewIMEStateBlocker final {
public:
GettingNewIMEStateBlocker()