forked from mirrors/gecko-dev
Notifying IME of the pseudo focus change may cause query content with flushing pending things. Therefore, it should be run when safe. Ideally, all notification should be async, but it requires too big change because all things handled in `IMEStateManager` needs to be async. E.g., `UpdateIMEState` etc. Therefore, this patch just fixes the known issue. Differential Revision: https://phabricator.services.mozilla.com/D199200
2451 lines
98 KiB
C++
2451 lines
98 KiB
C++
/* -*- 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 "IMEStateManager.h"
|
|
|
|
#include "IMEContentObserver.h"
|
|
#include "mozilla/Attributes.h"
|
|
#include "mozilla/AutoRestore.h"
|
|
#include "mozilla/EditorBase.h"
|
|
#include "mozilla/EventListenerManager.h"
|
|
#include "mozilla/EventStateManager.h"
|
|
#include "mozilla/HTMLEditor.h"
|
|
#include "mozilla/Logging.h"
|
|
#include "mozilla/MouseEvents.h"
|
|
#include "mozilla/PresShell.h"
|
|
#include "mozilla/RefPtr.h"
|
|
#include "mozilla/StaticPrefs_dom.h"
|
|
#include "mozilla/StaticPrefs_intl.h"
|
|
#include "mozilla/TextComposition.h"
|
|
#include "mozilla/TextEvents.h"
|
|
#include "mozilla/ToString.h"
|
|
#include "mozilla/Unused.h"
|
|
#include "mozilla/dom/BrowserBridgeChild.h"
|
|
#include "mozilla/dom/BrowserParent.h"
|
|
#include "mozilla/dom/Document.h"
|
|
#include "mozilla/dom/Element.h"
|
|
#include "mozilla/dom/HTMLFormElement.h"
|
|
#include "mozilla/dom/HTMLInputElement.h"
|
|
#include "mozilla/dom/HTMLTextAreaElement.h"
|
|
#include "mozilla/dom/MouseEventBinding.h"
|
|
#include "mozilla/dom/UserActivation.h"
|
|
#include "mozilla/widget/IMEData.h"
|
|
|
|
#include "nsCOMPtr.h"
|
|
#include "nsContentUtils.h"
|
|
#include "nsFocusManager.h"
|
|
#include "nsIContent.h"
|
|
#include "nsIContentInlines.h"
|
|
#include "nsIFormControl.h"
|
|
#include "nsINode.h"
|
|
#include "nsISupports.h"
|
|
#include "nsIURI.h"
|
|
#include "nsIURIMutator.h"
|
|
#include "nsPresContext.h"
|
|
#include "nsThreadUtils.h"
|
|
|
|
namespace mozilla {
|
|
|
|
using namespace dom;
|
|
using namespace widget;
|
|
|
|
/**
|
|
* When a method is called, log its arguments and/or related static variables
|
|
* with LogLevel::Info. However, if it puts too many logs like
|
|
* OnDestroyPresContext(), should long only when the method actually does
|
|
* something. In this case, the log should start with "<method name>".
|
|
*
|
|
* When a method quits due to unexpected situation, log the reason with
|
|
* LogLevel::Error. In this case, the log should start with
|
|
* "<method name>(), FAILED". The indent makes the log look easier.
|
|
*
|
|
* When a method does something only in some situations and it may be important
|
|
* for debug, log the information with LogLevel::Debug. In this case, the log
|
|
* should start with " <method name>(),".
|
|
*/
|
|
LazyLogModule sISMLog("IMEStateManager");
|
|
|
|
static const char* GetBoolName(bool aBool) { return aBool ? "true" : "false"; }
|
|
|
|
StaticRefPtr<Element> IMEStateManager::sFocusedElement;
|
|
StaticRefPtr<nsPresContext> IMEStateManager::sFocusedPresContext;
|
|
nsIWidget* IMEStateManager::sTextInputHandlingWidget = nullptr;
|
|
nsIWidget* IMEStateManager::sFocusedIMEWidget = nullptr;
|
|
StaticRefPtr<BrowserParent> IMEStateManager::sFocusedIMEBrowserParent;
|
|
nsIWidget* IMEStateManager::sActiveInputContextWidget = nullptr;
|
|
StaticRefPtr<IMEContentObserver> IMEStateManager::sActiveIMEContentObserver;
|
|
TextCompositionArray* IMEStateManager::sTextCompositions = nullptr;
|
|
InputContext::Origin IMEStateManager::sOrigin = InputContext::ORIGIN_MAIN;
|
|
InputContext IMEStateManager::sActiveChildInputContext;
|
|
bool IMEStateManager::sInstalledMenuKeyboardListener = false;
|
|
bool IMEStateManager::sIsGettingNewIMEState = false;
|
|
bool IMEStateManager::sCleaningUpForStoppingIMEStateManagement = false;
|
|
bool IMEStateManager::sIsActive = false;
|
|
Maybe<IMEStateManager::PendingFocusedBrowserSwitchingData>
|
|
IMEStateManager::sPendingFocusedBrowserSwitchingData;
|
|
|
|
class PseudoFocusChangeRunnable : public Runnable {
|
|
public:
|
|
explicit PseudoFocusChangeRunnable(bool aInstallingMenuKeyboardListener)
|
|
: Runnable("PseudoFocusChangeRunnable"),
|
|
mFocusedPresContext(IMEStateManager::sFocusedPresContext),
|
|
mFocusedElement(IMEStateManager::sFocusedElement),
|
|
mInstallMenuKeyboardListener(aInstallingMenuKeyboardListener) {}
|
|
|
|
MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override {
|
|
IMEStateManager::SetMenubarPseudoFocus(this, mInstallMenuKeyboardListener,
|
|
mFocusedPresContext);
|
|
return NS_OK;
|
|
}
|
|
|
|
private:
|
|
const RefPtr<nsPresContext> mFocusedPresContext;
|
|
const RefPtr<Element> mFocusedElement;
|
|
const bool mInstallMenuKeyboardListener;
|
|
};
|
|
|
|
StaticRefPtr<PseudoFocusChangeRunnable>
|
|
IMEStateManager::sPseudoFocusChangeRunnable;
|
|
|
|
// static
|
|
void IMEStateManager::Init() {
|
|
sOrigin = XRE_IsParentProcess() ? InputContext::ORIGIN_MAIN
|
|
: InputContext::ORIGIN_CONTENT;
|
|
ResetActiveChildInputContext();
|
|
}
|
|
|
|
// static
|
|
void IMEStateManager::Shutdown() {
|
|
MOZ_LOG(
|
|
sISMLog, LogLevel::Info,
|
|
("Shutdown(), sTextCompositions=0x%p, sTextCompositions->Length()=%zu, "
|
|
"sPendingFocusedBrowserSwitchingData.isSome()=%s",
|
|
sTextCompositions, sTextCompositions ? sTextCompositions->Length() : 0,
|
|
GetBoolName(sPendingFocusedBrowserSwitchingData.isSome())));
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" Shutdown(), sFocusedElement=0x%p, sFocusedPresContext=0x%p, "
|
|
"sTextInputHandlingWidget=0x%p, sFocusedIMEWidget=0x%p, "
|
|
"sFocusedIMEBrowserParent=0x%p, sActiveInputContextWidget=0x%p, "
|
|
"sActiveIMEContentObserver=0x%p",
|
|
sFocusedElement.get(), sFocusedPresContext.get(),
|
|
sTextInputHandlingWidget, sFocusedIMEWidget,
|
|
sFocusedIMEBrowserParent.get(), sActiveInputContextWidget,
|
|
sActiveIMEContentObserver.get()));
|
|
|
|
sPendingFocusedBrowserSwitchingData.reset();
|
|
MOZ_ASSERT(!sTextCompositions || !sTextCompositions->Length());
|
|
delete sTextCompositions;
|
|
sTextCompositions = nullptr;
|
|
// All string instances in the global space need to be empty after XPCOM
|
|
// shutdown.
|
|
sActiveChildInputContext.ShutDown();
|
|
}
|
|
|
|
// static
|
|
void IMEStateManager::OnFocusMovedBetweenBrowsers(BrowserParent* aBlur,
|
|
BrowserParent* aFocus) {
|
|
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 && sTextInputHandlingWidget &&
|
|
sTextCompositions &&
|
|
sTextCompositions->GetCompositionFor(sTextInputHandlingWidget)) {
|
|
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();
|
|
|
|
const nsCOMPtr<nsIWidget> oldWidget = sTextInputHandlingWidget;
|
|
// In the chrome-process case, we'll get sTextInputHandlingWidget from a
|
|
// PresShell later.
|
|
sTextInputHandlingWidget =
|
|
aFocus ? nsCOMPtr<nsIWidget>(aFocus->GetTextInputHandlingWidget()).get()
|
|
: nullptr;
|
|
if (oldWidget && sTextCompositions) {
|
|
RefPtr<TextComposition> composition =
|
|
sTextCompositions->GetCompositionFor(oldWidget);
|
|
if (composition) {
|
|
MOZ_LOG(
|
|
sISMLog, LogLevel::Debug,
|
|
(" OnFocusMovedBetweenBrowsers(), requesting to commit "
|
|
"composition to "
|
|
"the (previous) focused widget (would request=%s)",
|
|
GetBoolName(
|
|
!oldWidget->IMENotificationRequestsRef().WantDuringDeactive())));
|
|
NotifyIME(REQUEST_TO_COMMIT_COMPOSITION, oldWidget,
|
|
composition->GetBrowserParent());
|
|
}
|
|
}
|
|
|
|
// The manager check is to avoid telling the content process to stop
|
|
// IME state management after focus has already moved there between
|
|
// two same-process-hosted out-of-process iframes.
|
|
if (aBlur && (!aFocus || (aBlur->Manager() != aFocus->Manager()))) {
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" OnFocusMovedBetweenBrowsers(), notifying previous "
|
|
"focused child process of parent process or another child process "
|
|
"getting focus"));
|
|
aBlur->StopIMEStateManagement();
|
|
}
|
|
|
|
if (sActiveIMEContentObserver) {
|
|
DestroyIMEContentObserver();
|
|
}
|
|
|
|
if (sFocusedIMEWidget) {
|
|
// sFocusedIMEBrowserParent can be null, if IME focus hasn't been
|
|
// taken before BrowserParent blur.
|
|
// aBlur can be null when keyboard focus moves not actually
|
|
// between tabs but an open menu is involved.
|
|
MOZ_ASSERT(!sFocusedIMEBrowserParent || !aBlur ||
|
|
(sFocusedIMEBrowserParent == aBlur));
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" OnFocusMovedBetweenBrowsers(), notifying IME of blur"));
|
|
NotifyIME(NOTIFY_IME_OF_BLUR, sFocusedIMEWidget, sFocusedIMEBrowserParent);
|
|
|
|
MOZ_ASSERT(!sFocusedIMEBrowserParent);
|
|
MOZ_ASSERT(!sFocusedIMEWidget);
|
|
|
|
} else {
|
|
MOZ_ASSERT(!sFocusedIMEBrowserParent);
|
|
}
|
|
|
|
// We deliberately don't null out sFocusedElement or sFocusedPresContext here.
|
|
// When focus is in remote content, as far as layout in the chrome process is
|
|
// concerned, the corresponding content is the top-level XUL browser. Changes
|
|
// among out-of-process iframes don't change that, so dropping the pointer to
|
|
// the XUL browser upon such a change would break IME handling.
|
|
}
|
|
|
|
// static
|
|
void IMEStateManager::WidgetDestroyed(nsIWidget* aWidget) {
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
("WidgetDestroyed(aWidget=0x%p), sFocusedIMEWidget=0x%p, "
|
|
"sActiveInputContextWidget=0x%p, sFocusedIMEBrowserParent=0x%p",
|
|
aWidget, sFocusedIMEWidget, sActiveInputContextWidget,
|
|
sFocusedIMEBrowserParent.get()));
|
|
if (sTextInputHandlingWidget == aWidget) {
|
|
sTextInputHandlingWidget = nullptr;
|
|
}
|
|
if (sFocusedIMEWidget == aWidget) {
|
|
if (sFocusedIMEBrowserParent) {
|
|
OnFocusMovedBetweenBrowsers(sFocusedIMEBrowserParent, nullptr);
|
|
MOZ_ASSERT(!sFocusedIMEBrowserParent);
|
|
}
|
|
sFocusedIMEWidget = nullptr;
|
|
}
|
|
if (sActiveInputContextWidget == aWidget) {
|
|
sActiveInputContextWidget = nullptr;
|
|
}
|
|
}
|
|
|
|
// static
|
|
void IMEStateManager::WidgetOnQuit(nsIWidget* aWidget) {
|
|
if (sFocusedIMEWidget == aWidget) {
|
|
MOZ_LOG(
|
|
sISMLog, LogLevel::Debug,
|
|
("WidgetOnQuit(aWidget=0x%p (available %s)), sFocusedIMEWidget=0x%p",
|
|
aWidget, GetBoolName(aWidget && !aWidget->Destroyed()),
|
|
sFocusedIMEWidget));
|
|
// Notify IME of blur (which is done by IMEContentObserver::Destroy
|
|
// automatically) when the widget still has IME focus before forgetting the
|
|
// focused widget because the focused widget is required to clean up native
|
|
// IME handler with sending blur notification. Fortunately, the widget
|
|
// has not been destroyed yet here since some methods to sending blur
|
|
// notification won't work with destroyed widget.
|
|
IMEStateManager::DestroyIMEContentObserver();
|
|
// Finally, clean up the widget and related objects for avoiding to leak.
|
|
IMEStateManager::WidgetDestroyed(aWidget);
|
|
}
|
|
}
|
|
|
|
// static
|
|
void IMEStateManager::StopIMEStateManagement() {
|
|
MOZ_ASSERT(XRE_IsContentProcess());
|
|
MOZ_LOG(sISMLog, LogLevel::Info, ("StopIMEStateManagement()"));
|
|
|
|
// NOTE: Don't set input context from here since this has already lost
|
|
// the rights to change input context.
|
|
|
|
// The requestee of this method in the main process must destroy its
|
|
// active IMEContentObserver for making existing composition end and
|
|
// make it be possible to start new composition in new focused process.
|
|
// Therefore, we shouldn't notify the main process of any changes which
|
|
// occurred after here.
|
|
AutoRestore<bool> restoreStoppingIMEStateManagementState(
|
|
sCleaningUpForStoppingIMEStateManagement);
|
|
sCleaningUpForStoppingIMEStateManagement = true;
|
|
|
|
if (sTextCompositions && sFocusedPresContext) {
|
|
NotifyIME(REQUEST_TO_COMMIT_COMPOSITION, sFocusedPresContext, nullptr);
|
|
}
|
|
sActiveInputContextWidget = nullptr;
|
|
sFocusedPresContext = nullptr;
|
|
sFocusedElement = nullptr;
|
|
sIsActive = false;
|
|
DestroyIMEContentObserver();
|
|
}
|
|
|
|
// static
|
|
void IMEStateManager::MaybeStartOffsetUpdatedInChild(nsIWidget* aWidget,
|
|
uint32_t aStartOffset) {
|
|
if (NS_WARN_IF(!sTextCompositions)) {
|
|
MOZ_LOG(sISMLog, LogLevel::Warning,
|
|
("MaybeStartOffsetUpdatedInChild(aWidget=0x%p, aStartOffset=%u), "
|
|
"called when there is no composition",
|
|
aWidget, aStartOffset));
|
|
return;
|
|
}
|
|
|
|
TextComposition* const composition = GetTextCompositionFor(aWidget);
|
|
if (NS_WARN_IF(!composition)) {
|
|
MOZ_LOG(sISMLog, LogLevel::Warning,
|
|
("MaybeStartOffsetUpdatedInChild(aWidget=0x%p, aStartOffset=%u), "
|
|
"called when there is no composition",
|
|
aWidget, aStartOffset));
|
|
return;
|
|
}
|
|
|
|
if (composition->NativeOffsetOfStartComposition() == aStartOffset) {
|
|
return;
|
|
}
|
|
|
|
MOZ_LOG(
|
|
sISMLog, LogLevel::Info,
|
|
("MaybeStartOffsetUpdatedInChild(aWidget=0x%p, aStartOffset=%u), "
|
|
"old offset=%u",
|
|
aWidget, aStartOffset, composition->NativeOffsetOfStartComposition()));
|
|
composition->OnStartOffsetUpdatedInChild(aStartOffset);
|
|
}
|
|
|
|
// static
|
|
nsresult IMEStateManager::OnDestroyPresContext(nsPresContext& aPresContext) {
|
|
// First, if there is a composition in the aPresContext, clean up it.
|
|
if (sTextCompositions) {
|
|
TextCompositionArray::index_type i =
|
|
sTextCompositions->IndexOf(&aPresContext);
|
|
if (i != TextCompositionArray::NoIndex) {
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" OnDestroyPresContext(), "
|
|
"removing TextComposition instance from the array (index=%zu)",
|
|
i));
|
|
// there should be only one composition per presContext object.
|
|
sTextCompositions->ElementAt(i)->Destroy();
|
|
sTextCompositions->RemoveElementAt(i);
|
|
if (sTextCompositions->IndexOf(&aPresContext) !=
|
|
TextCompositionArray::NoIndex) {
|
|
MOZ_LOG(sISMLog, LogLevel::Error,
|
|
(" OnDestroyPresContext(), FAILED to remove "
|
|
"TextComposition instance from the array"));
|
|
MOZ_CRASH("Failed to remove TextComposition instance from the array");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (&aPresContext != sFocusedPresContext) {
|
|
return NS_OK;
|
|
}
|
|
|
|
MOZ_LOG(
|
|
sISMLog, LogLevel::Info,
|
|
("OnDestroyPresContext(aPresContext=0x%p), "
|
|
"sFocusedPresContext=0x%p, sFocusedElement=0x%p, sTextCompositions=0x%p",
|
|
&aPresContext, sFocusedPresContext.get(), sFocusedElement.get(),
|
|
sTextCompositions));
|
|
|
|
DestroyIMEContentObserver();
|
|
|
|
if (sTextInputHandlingWidget) {
|
|
IMEState newState = GetNewIMEState(*sFocusedPresContext, nullptr);
|
|
InputContextAction action(InputContextAction::CAUSE_UNKNOWN,
|
|
InputContextAction::LOST_FOCUS);
|
|
InputContext::Origin origin =
|
|
BrowserParent::GetFocused() ? InputContext::ORIGIN_CONTENT : sOrigin;
|
|
OwningNonNull<nsIWidget> textInputHandlingWidget =
|
|
*sTextInputHandlingWidget;
|
|
SetIMEState(newState, nullptr, nullptr, textInputHandlingWidget, action,
|
|
origin);
|
|
}
|
|
sTextInputHandlingWidget = nullptr;
|
|
sFocusedElement = nullptr;
|
|
sFocusedPresContext = nullptr;
|
|
return NS_OK;
|
|
}
|
|
|
|
// static
|
|
nsresult IMEStateManager::OnRemoveContent(nsPresContext& aPresContext,
|
|
Element& aElement) {
|
|
// First, if there is a composition in the aElement, clean up it.
|
|
if (sTextCompositions) {
|
|
const RefPtr<TextComposition> compositionInContent =
|
|
sTextCompositions->GetCompositionInContent(&aPresContext, &aElement);
|
|
|
|
if (compositionInContent) {
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" OnRemoveContent(), "
|
|
"composition is in the content"));
|
|
|
|
// Try resetting the native IME state. Be aware, typically, this method
|
|
// is called during the content being removed. Then, the native
|
|
// composition events which are caused by following APIs are ignored due
|
|
// to unsafe to run script (in PresShell::HandleEvent()).
|
|
nsresult rv =
|
|
compositionInContent->NotifyIME(REQUEST_TO_CANCEL_COMPOSITION);
|
|
if (NS_FAILED(rv)) {
|
|
compositionInContent->NotifyIME(REQUEST_TO_COMMIT_COMPOSITION);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!sFocusedPresContext || !sFocusedElement ||
|
|
!sFocusedElement->IsInclusiveDescendantOf(&aElement)) {
|
|
return NS_OK;
|
|
}
|
|
MOZ_ASSERT(sFocusedPresContext == &aPresContext);
|
|
|
|
MOZ_LOG(
|
|
sISMLog, LogLevel::Info,
|
|
("OnRemoveContent(aPresContext=0x%p, aElement=0x%p), "
|
|
"sFocusedPresContext=0x%p, sFocusedElement=0x%p, sTextCompositions=0x%p",
|
|
&aPresContext, &aElement, sFocusedPresContext.get(),
|
|
sFocusedElement.get(), sTextCompositions));
|
|
|
|
DestroyIMEContentObserver();
|
|
|
|
// FYI: Don't clear sTextInputHandlingWidget and sFocusedPresContext because
|
|
// the window/document keeps having focus.
|
|
sFocusedElement = nullptr;
|
|
|
|
// Current IME transaction should commit
|
|
if (!sTextInputHandlingWidget) {
|
|
return NS_OK;
|
|
}
|
|
|
|
IMEState newState = GetNewIMEState(*sFocusedPresContext, nullptr);
|
|
InputContextAction action(InputContextAction::CAUSE_UNKNOWN,
|
|
InputContextAction::LOST_FOCUS);
|
|
InputContext::Origin origin =
|
|
BrowserParent::GetFocused() ? InputContext::ORIGIN_CONTENT : sOrigin;
|
|
OwningNonNull<nsIWidget> textInputHandlingWidget = *sTextInputHandlingWidget;
|
|
SetIMEState(newState, &aPresContext, nullptr, textInputHandlingWidget, action,
|
|
origin);
|
|
if (sFocusedPresContext != &aPresContext || sFocusedElement) {
|
|
return NS_OK; // Some body must have set focus
|
|
}
|
|
|
|
if (IsIMEObserverNeeded(newState)) {
|
|
if (RefPtr<HTMLEditor> htmlEditor =
|
|
nsContentUtils::GetHTMLEditor(&aPresContext)) {
|
|
CreateIMEContentObserver(*htmlEditor, nullptr);
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// static
|
|
bool IMEStateManager::CanHandleWith(const nsPresContext* aPresContext) {
|
|
return aPresContext && aPresContext->GetPresShell() &&
|
|
!aPresContext->PresShell()->IsDestroying();
|
|
}
|
|
|
|
// static
|
|
nsresult IMEStateManager::OnChangeFocus(nsPresContext* aPresContext,
|
|
Element* aElement,
|
|
InputContextAction::Cause aCause) {
|
|
MOZ_LOG(sISMLog, LogLevel::Info,
|
|
("OnChangeFocus(aPresContext=0x%p, aElement=0x%p, aCause=%s)",
|
|
aPresContext, aElement, ToString(aCause).c_str()));
|
|
|
|
InputContextAction action(aCause);
|
|
return OnChangeFocusInternal(aPresContext, aElement, action);
|
|
}
|
|
|
|
// static
|
|
nsresult IMEStateManager::OnChangeFocusInternal(nsPresContext* aPresContext,
|
|
Element* aElement,
|
|
InputContextAction aAction) {
|
|
NS_ASSERTION(!aElement || aElement->GetPresContext(
|
|
Element::PresContextFor::eForComposedDoc) ==
|
|
aPresContext,
|
|
"aPresContext does not match with one of aElement");
|
|
|
|
bool remoteHasFocus = EventStateManager::IsRemoteTarget(aElement);
|
|
// 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 &&
|
|
sFocusedPresContext && sFocusedElement &&
|
|
sFocusedPresContext.get() == aPresContext &&
|
|
sFocusedElement.get() == aElement &&
|
|
aAction.mFocusChange != InputContextAction::MENU_GOT_PSEUDO_FOCUS;
|
|
|
|
MOZ_LOG(
|
|
sISMLog, LogLevel::Info,
|
|
("OnChangeFocusInternal(aPresContext=0x%p (available: %s), "
|
|
"aElement=0x%p (remote: %s), aAction={ mCause=%s, "
|
|
"mFocusChange=%s }), sFocusedPresContext=0x%p (available: %s), "
|
|
"sFocusedElement=0x%p, sTextInputHandlingWidget=0x%p (available: %s), "
|
|
"BrowserParent::GetFocused()=0x%p, sActiveIMEContentObserver=0x%p, "
|
|
"sInstalledMenuKeyboardListener=%s, sIsActive=%s, "
|
|
"restoringContextForRemoteContent=%s",
|
|
aPresContext, GetBoolName(CanHandleWith(aPresContext)), aElement,
|
|
GetBoolName(remoteHasFocus), ToString(aAction.mCause).c_str(),
|
|
ToString(aAction.mFocusChange).c_str(), sFocusedPresContext.get(),
|
|
GetBoolName(CanHandleWith(sFocusedPresContext)), sFocusedElement.get(),
|
|
sTextInputHandlingWidget,
|
|
GetBoolName(sTextInputHandlingWidget &&
|
|
!sTextInputHandlingWidget->Destroyed()),
|
|
BrowserParent::GetFocused(), sActiveIMEContentObserver.get(),
|
|
GetBoolName(sInstalledMenuKeyboardListener), GetBoolName(sIsActive),
|
|
GetBoolName(restoringContextForRemoteContent)));
|
|
|
|
sIsActive = !!aPresContext;
|
|
if (sPendingFocusedBrowserSwitchingData.isSome()) {
|
|
MOZ_ASSERT(XRE_IsParentProcess());
|
|
RefPtr<Element> focusedElement = sFocusedElement;
|
|
RefPtr<nsPresContext> focusedPresContext = sFocusedPresContext;
|
|
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 (focusedElement != sFocusedElement.get() ||
|
|
focusedPresContext != sFocusedPresContext.get()) {
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" OnChangeFocusInternal(aPresContext=0x%p, aElement=0x%p) "
|
|
"stoped handling it because the focused content was changed to "
|
|
"sFocusedPresContext=0x%p, sFocusedElement=0x%p by another call",
|
|
aPresContext, aElement, sFocusedPresContext.get(),
|
|
sFocusedElement.get()));
|
|
return NS_OK;
|
|
}
|
|
}
|
|
|
|
// If new aPresShell has been destroyed, this should handle the focus change
|
|
// as nobody is getting focus.
|
|
MOZ_ASSERT_IF(!aPresContext, !aElement);
|
|
if (NS_WARN_IF(aPresContext && !CanHandleWith(aPresContext))) {
|
|
MOZ_LOG(sISMLog, LogLevel::Warning,
|
|
(" OnChangeFocusInternal(), called with destroyed PresShell, "
|
|
"handling this call as nobody getting focus"));
|
|
aPresContext = nullptr;
|
|
aElement = nullptr;
|
|
} else if (!aPresContext) {
|
|
aElement = nullptr;
|
|
}
|
|
|
|
const nsCOMPtr<nsIWidget> oldWidget = sTextInputHandlingWidget;
|
|
const nsCOMPtr<nsIWidget> newWidget =
|
|
aPresContext ? aPresContext->GetTextInputHandlingWidget() : nullptr;
|
|
const bool focusActuallyChanging =
|
|
(sFocusedElement != aElement || sFocusedPresContext != aPresContext ||
|
|
oldWidget != newWidget ||
|
|
(remoteHasFocus && !restoringContextForRemoteContent &&
|
|
(aAction.mFocusChange != InputContextAction::MENU_GOT_PSEUDO_FOCUS)));
|
|
|
|
// If old widget has composition, we may need to commit composition since
|
|
// a native IME context is shared on all editors on some widgets or all
|
|
// widgets (it depends on platforms).
|
|
if (oldWidget && focusActuallyChanging && sTextCompositions) {
|
|
RefPtr<TextComposition> composition =
|
|
sTextCompositions->GetCompositionFor(oldWidget);
|
|
if (composition) {
|
|
// However, don't commit the composition if we're being inactivated
|
|
// but the composition should be kept even during deactive.
|
|
// Note that oldWidget and sFocusedIMEWidget may be different here (in
|
|
// such case, sFocusedIMEWidget is perhaps nullptr). For example, IME
|
|
// may receive only blur notification but still has composition.
|
|
// We need to clean up only the oldWidget's composition state here.
|
|
if (aPresContext ||
|
|
!oldWidget->IMENotificationRequestsRef().WantDuringDeactive()) {
|
|
MOZ_LOG(
|
|
sISMLog, LogLevel::Info,
|
|
(" OnChangeFocusInternal(), requesting to commit composition to "
|
|
"the (previous) focused widget"));
|
|
NotifyIME(REQUEST_TO_COMMIT_COMPOSITION, oldWidget,
|
|
composition->GetBrowserParent());
|
|
}
|
|
}
|
|
}
|
|
|
|
if (sActiveIMEContentObserver) {
|
|
MOZ_ASSERT(!remoteHasFocus || XRE_IsContentProcess(),
|
|
"IMEContentObserver should have been destroyed by "
|
|
"OnFocusMovedBetweenBrowsers.");
|
|
if (!aPresContext) {
|
|
if (!sActiveIMEContentObserver->KeepAliveDuringDeactive()) {
|
|
DestroyIMEContentObserver();
|
|
}
|
|
}
|
|
// Otherwise, i.e., new focused content is in this process, let's check
|
|
// whether the editable content for aElement has already been being observed
|
|
// by the active IMEContentObserver. If not, let's stop observing the
|
|
// editable content which is being blurred.
|
|
else if (!sActiveIMEContentObserver->IsObserving(*aPresContext, aElement)) {
|
|
DestroyIMEContentObserver();
|
|
}
|
|
}
|
|
|
|
if (!aPresContext) {
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" 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"));
|
|
return NS_OK;
|
|
}
|
|
|
|
// Update the cached widget since root view of the presContext may be
|
|
// changed to different view.
|
|
sTextInputHandlingWidget = newWidget;
|
|
|
|
// If a child process has focus, we should disable IME state until the child
|
|
// process actually gets focus because if user types keys before that they
|
|
// are handled by IME.
|
|
IMEState newState = remoteHasFocus ? IMEState(IMEEnabled::Disabled)
|
|
: GetNewIMEState(*aPresContext, aElement);
|
|
bool setIMEState = true;
|
|
|
|
if (remoteHasFocus && XRE_IsParentProcess()) {
|
|
if (aAction.mFocusChange == InputContextAction::MENU_GOT_PSEUDO_FOCUS) {
|
|
// If menu keyboard listener is installed, we need to disable IME now.
|
|
setIMEState = true;
|
|
} else if (aAction.mFocusChange ==
|
|
InputContextAction::MENU_LOST_PSEUDO_FOCUS) {
|
|
// If menu keyboard listener is uninstalled, we need to restore
|
|
// input context which was set by the remote process. However, if
|
|
// the remote process hasn't been set input context yet, we need to
|
|
// wait next SetInputContextForChildProcess() call.
|
|
if (HasActiveChildSetInputContext()) {
|
|
setIMEState = true;
|
|
newState = sActiveChildInputContext.mIMEState;
|
|
} else {
|
|
setIMEState = false;
|
|
}
|
|
} else if (focusActuallyChanging) {
|
|
InputContext context = newWidget->GetInputContext();
|
|
if (context.mIMEState.mEnabled == IMEEnabled::Disabled &&
|
|
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"));
|
|
} 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"));
|
|
}
|
|
} else if (newWidget->GetInputContext().mOrigin !=
|
|
InputContext::ORIGIN_MAIN) {
|
|
// When focus is NOT changed actually, we shouldn't set IME state if
|
|
// current input context was set by a remote process since that means
|
|
// that the window is being activated and the child process may have
|
|
// 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"));
|
|
}
|
|
} else {
|
|
// When this process gets focus, we should forget input context coming
|
|
// from remote process.
|
|
ResetActiveChildInputContext();
|
|
}
|
|
|
|
if (setIMEState) {
|
|
if (!focusActuallyChanging) {
|
|
// actual focus isn't changing, but if IME enabled state is changing,
|
|
// we should do it.
|
|
InputContext context = newWidget->GetInputContext();
|
|
if (context.mIMEState.mEnabled == newState.mEnabled) {
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" OnChangeFocusInternal(), neither focus nor IME state is "
|
|
"changing"));
|
|
return NS_OK;
|
|
}
|
|
aAction.mFocusChange = InputContextAction::FOCUS_NOT_CHANGED;
|
|
|
|
// Even if focus isn't changing actually, we should commit current
|
|
// composition here since the IME state is changing.
|
|
if (sFocusedPresContext && oldWidget && !focusActuallyChanging) {
|
|
NotifyIME(REQUEST_TO_COMMIT_COMPOSITION, oldWidget,
|
|
sFocusedIMEBrowserParent);
|
|
}
|
|
} else if (aAction.mFocusChange == InputContextAction::FOCUS_NOT_CHANGED) {
|
|
// If aElement isn't null or aElement is null but editable, somebody gets
|
|
// focus.
|
|
bool gotFocus = aElement || (newState.mEnabled == IMEEnabled::Enabled);
|
|
aAction.mFocusChange = gotFocus ? InputContextAction::GOT_FOCUS
|
|
: InputContextAction::LOST_FOCUS;
|
|
}
|
|
|
|
if (remoteHasFocus && HasActiveChildSetInputContext() &&
|
|
aAction.mFocusChange == InputContextAction::MENU_LOST_PSEUDO_FOCUS) {
|
|
// Restore the input context in the active remote process when
|
|
// menu keyboard listener is uninstalled and active remote tab has
|
|
// focus.
|
|
SetInputContext(*newWidget, sActiveChildInputContext, aAction);
|
|
} else {
|
|
// Update IME state for new focus widget
|
|
SetIMEState(newState, aPresContext, aElement, *newWidget, aAction,
|
|
remoteHasFocus ? InputContext::ORIGIN_CONTENT : sOrigin);
|
|
}
|
|
}
|
|
|
|
sFocusedPresContext = aPresContext;
|
|
sFocusedElement = aElement;
|
|
|
|
// Don't call CreateIMEContentObserver() here because it will be called from
|
|
// the focus event handler of focused editor.
|
|
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" OnChangeFocusInternal(), modified IME state for "
|
|
"sFocusedPresContext=0x%p, sFocusedElement=0x%p",
|
|
sFocusedPresContext.get(), sFocusedElement.get()));
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// static
|
|
void IMEStateManager::OnInstalledMenuKeyboardListener(bool aInstalling) {
|
|
MOZ_LOG(
|
|
sISMLog, LogLevel::Info,
|
|
("OnInstalledMenuKeyboardListener(aInstalling=%s), "
|
|
"nsContentUtils::IsSafeToRunScript()=%s, "
|
|
"sInstalledMenuKeyboardListener=%s, BrowserParent::GetFocused()=0x%p, "
|
|
"sActiveChildInputContext=%s, sFocusedPresContext=0x%p, "
|
|
"sFocusedElement=0x%p, sPseudoFocusChangeRunnable=0x%p",
|
|
GetBoolName(aInstalling),
|
|
GetBoolName(nsContentUtils::IsSafeToRunScript()),
|
|
GetBoolName(sInstalledMenuKeyboardListener), BrowserParent::GetFocused(),
|
|
ToString(sActiveChildInputContext).c_str(), sFocusedPresContext.get(),
|
|
sFocusedElement.get(), sPseudoFocusChangeRunnable.get()));
|
|
|
|
// Update the state whether the menubar has pseudo focus or not immediately.
|
|
// This will be referred by the runner which is created below.
|
|
sInstalledMenuKeyboardListener = aInstalling;
|
|
// However, this may be called when it's not safe to run script. Therefore,
|
|
// we need to create a runnable to notify IME of the pseudo focus change.
|
|
if (sPseudoFocusChangeRunnable) {
|
|
return;
|
|
}
|
|
sPseudoFocusChangeRunnable = new PseudoFocusChangeRunnable(aInstalling);
|
|
nsContentUtils::AddScriptRunner(sPseudoFocusChangeRunnable);
|
|
}
|
|
|
|
// static
|
|
void IMEStateManager::SetMenubarPseudoFocus(
|
|
PseudoFocusChangeRunnable* aCaller, bool aSetPseudoFocus,
|
|
nsPresContext* aFocusedPresContextAtRequested) {
|
|
MOZ_LOG(
|
|
sISMLog, LogLevel::Info,
|
|
("SetMenubarPseudoFocus(aCaller=0x%p, aSetPseudoFocus=%s, "
|
|
"aFocusedPresContextAtRequested=0x%p), "
|
|
"sInstalledMenuKeyboardListener=%s, sFocusedPresContext=0x%p, "
|
|
"sFocusedElement=0x%p, sPseudoFocusChangeRunnable=0x%p",
|
|
aCaller, GetBoolName(aSetPseudoFocus), aFocusedPresContextAtRequested,
|
|
GetBoolName(sInstalledMenuKeyboardListener), sFocusedPresContext.get(),
|
|
sFocusedElement.get(), sPseudoFocusChangeRunnable.get()));
|
|
|
|
MOZ_ASSERT(sPseudoFocusChangeRunnable.get() == aCaller);
|
|
|
|
// Clear the runnable first for nested call of
|
|
// OnInstalledMenuKeyboardListener().
|
|
RefPtr<PseudoFocusChangeRunnable> runningOne =
|
|
sPseudoFocusChangeRunnable.forget();
|
|
MOZ_ASSERT(!sPseudoFocusChangeRunnable);
|
|
|
|
// If the requested state is still same, let's make the menubar get
|
|
// pseudo focus with the latest focused PresContext and Element.
|
|
// Note that this is no problem even after the focused element is changed
|
|
// after aCaller is created because only sInstalledMenuKeyboardListener
|
|
// manages the state and current focused PresContext and element should be
|
|
// used only for restoring the focus from the menubar. So, restoring
|
|
// focus with the lasted one does make sense.
|
|
if (sInstalledMenuKeyboardListener == aSetPseudoFocus) {
|
|
InputContextAction action(InputContextAction::CAUSE_UNKNOWN,
|
|
aSetPseudoFocus
|
|
? InputContextAction::MENU_GOT_PSEUDO_FOCUS
|
|
: InputContextAction::MENU_LOST_PSEUDO_FOCUS);
|
|
RefPtr<nsPresContext> focusedPresContext = sFocusedPresContext;
|
|
RefPtr<Element> focusedElement = sFocusedElement;
|
|
OnChangeFocusInternal(focusedPresContext, focusedElement, action);
|
|
return;
|
|
}
|
|
|
|
// If the requested state is different from current state, we don't need to
|
|
// make the menubar get and lose pseudo focus because of redundant. However,
|
|
// if there is a composition on the original focused element, we need to
|
|
// commit it because pseudo focus move should've caused it.
|
|
if (!aFocusedPresContextAtRequested) {
|
|
return;
|
|
}
|
|
RefPtr<TextComposition> composition =
|
|
GetTextCompositionFor(aFocusedPresContextAtRequested);
|
|
if (!composition) {
|
|
return;
|
|
}
|
|
if (nsCOMPtr<nsIWidget> widget =
|
|
aFocusedPresContextAtRequested->GetTextInputHandlingWidget()) {
|
|
composition->RequestToCommit(widget, false);
|
|
}
|
|
}
|
|
|
|
// static
|
|
bool IMEStateManager::OnMouseButtonEventInEditor(
|
|
nsPresContext& aPresContext, Element* aElement,
|
|
WidgetMouseEvent& aMouseEvent) {
|
|
MOZ_LOG(sISMLog, LogLevel::Info,
|
|
("OnMouseButtonEventInEditor(aPresContext=0x%p (available: %s), "
|
|
"aElement=0x%p, aMouseEvent=0x%p), sFocusedPresContext=0x%p, "
|
|
"sFocusedElement=0x%p",
|
|
&aPresContext, GetBoolName(CanHandleWith(&aPresContext)), aElement,
|
|
&aMouseEvent, sFocusedPresContext.get(), sFocusedElement.get()));
|
|
|
|
if (sFocusedPresContext != &aPresContext || sFocusedElement != aElement) {
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" OnMouseButtonEventInEditor(), "
|
|
"the mouse event isn't fired on the editor managed by ISM"));
|
|
return false;
|
|
}
|
|
|
|
if (!sActiveIMEContentObserver) {
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" OnMouseButtonEventInEditor(), "
|
|
"there is no active IMEContentObserver"));
|
|
return false;
|
|
}
|
|
|
|
if (!sActiveIMEContentObserver->IsObserving(aPresContext, aElement)) {
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" OnMouseButtonEventInEditor(), "
|
|
"the active IMEContentObserver isn't managing the editor"));
|
|
return false;
|
|
}
|
|
|
|
OwningNonNull<IMEContentObserver> observer = *sActiveIMEContentObserver;
|
|
bool consumed = observer->OnMouseButtonEvent(aPresContext, aMouseEvent);
|
|
MOZ_LOG(sISMLog, LogLevel::Info,
|
|
(" OnMouseButtonEventInEditor(), "
|
|
"mouse event (mMessage=%s, mButton=%d) is %s",
|
|
ToChar(aMouseEvent.mMessage), aMouseEvent.mButton,
|
|
consumed ? "consumed" : "not consumed"));
|
|
return consumed;
|
|
}
|
|
|
|
// static
|
|
void IMEStateManager::OnClickInEditor(nsPresContext& aPresContext,
|
|
Element* aElement,
|
|
const WidgetMouseEvent& aMouseEvent) {
|
|
MOZ_LOG(sISMLog, LogLevel::Info,
|
|
("OnClickInEditor(aPresContext=0x%p (available: %s), aElement=0x%p, "
|
|
"aMouseEvent=0x%p), sFocusedPresContext=0x%p, sFocusedElement=0x%p, "
|
|
"sTextInputHandlingWidget=0x%p (available: %s)",
|
|
&aPresContext, GetBoolName(CanHandleWith(&aPresContext)), aElement,
|
|
&aMouseEvent, sFocusedPresContext.get(), sFocusedElement.get(),
|
|
sTextInputHandlingWidget,
|
|
GetBoolName(sTextInputHandlingWidget &&
|
|
!sTextInputHandlingWidget->Destroyed())));
|
|
|
|
if (sFocusedPresContext != &aPresContext || sFocusedElement != aElement ||
|
|
NS_WARN_IF(!sFocusedPresContext) ||
|
|
NS_WARN_IF(!sTextInputHandlingWidget) ||
|
|
NS_WARN_IF(sTextInputHandlingWidget->Destroyed())) {
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" OnClickInEditor(), "
|
|
"the mouse event isn't fired on the editor managed by ISM"));
|
|
return;
|
|
}
|
|
|
|
const OwningNonNull<nsIWidget> textInputHandlingWidget =
|
|
*sTextInputHandlingWidget;
|
|
MOZ_ASSERT_IF(sFocusedPresContext->GetTextInputHandlingWidget(),
|
|
sFocusedPresContext->GetTextInputHandlingWidget() ==
|
|
textInputHandlingWidget.get());
|
|
|
|
if (!aMouseEvent.IsTrusted()) {
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" OnClickInEditor(), "
|
|
"the mouse event isn't a trusted event"));
|
|
return; // ignore untrusted event.
|
|
}
|
|
|
|
if (aMouseEvent.mButton) {
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" OnClickInEditor(), "
|
|
"the mouse event isn't a left mouse button event"));
|
|
return; // not a left click event.
|
|
}
|
|
|
|
if (aMouseEvent.mClickCount != 1) {
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" OnClickInEditor(), "
|
|
"the mouse event isn't a single click event"));
|
|
return; // should notify only first click event.
|
|
}
|
|
|
|
MOZ_ASSERT_IF(aElement, !EventStateManager::IsRemoteTarget(aElement));
|
|
InputContextAction::Cause cause =
|
|
aMouseEvent.mInputSource == MouseEvent_Binding::MOZ_SOURCE_TOUCH
|
|
? InputContextAction::CAUSE_TOUCH
|
|
: InputContextAction::CAUSE_MOUSE;
|
|
|
|
InputContextAction action(cause, InputContextAction::FOCUS_NOT_CHANGED);
|
|
IMEState newState = GetNewIMEState(aPresContext, aElement);
|
|
// If the state is not editable, there should be no active IMEContentObserver.
|
|
// However, if this click sets focus to the editor, IMEContentObserver may
|
|
// have not been created yet. Instead, if there is active IMEContentObserver,
|
|
// it should be editable.
|
|
MOZ_ASSERT_IF(!newState.IsEditable(), !sActiveIMEContentObserver);
|
|
MOZ_ASSERT_IF(sActiveIMEContentObserver, newState.IsEditable());
|
|
SetIMEState(newState, &aPresContext, aElement, textInputHandlingWidget,
|
|
action, sOrigin);
|
|
}
|
|
|
|
// static
|
|
Element* IMEStateManager::GetFocusedElement() { return sFocusedElement; }
|
|
|
|
// static
|
|
bool IMEStateManager::IsFocusedElement(const nsPresContext& aPresContext,
|
|
const Element* aFocusedElement) {
|
|
if (!sFocusedPresContext || &aPresContext != sFocusedPresContext) {
|
|
return false;
|
|
}
|
|
|
|
if (sFocusedElement == aFocusedElement) {
|
|
return true;
|
|
}
|
|
|
|
// If sFocusedElement is not nullptr, but aFocusedElement is nullptr, it does
|
|
// not have focus from point of view of IMEStateManager.
|
|
if (sFocusedElement) {
|
|
return false;
|
|
}
|
|
|
|
// If the caller does not think that nobody has focus, but we know there is
|
|
// a focused content, the caller must be called with wrong content.
|
|
if (!aFocusedElement) {
|
|
return false;
|
|
}
|
|
|
|
// If the aFocusedElement is in design mode, sFocusedElement may be nullptr.
|
|
if (aFocusedElement->IsInDesignMode()) {
|
|
MOZ_ASSERT(&aPresContext == sFocusedPresContext && !sFocusedElement);
|
|
return true;
|
|
}
|
|
|
|
// Otherwise, only when aFocusedElement is the root element, it can have
|
|
// focus, but IMEStateManager::OnChangeFocus is called with nullptr for
|
|
// aFocusedElement if it was not editable.
|
|
// XXX In this case, should the caller update sFocusedElement?
|
|
return aFocusedElement->IsEditable() && sFocusedPresContext->Document() &&
|
|
sFocusedPresContext->Document()->GetRootElement() == aFocusedElement;
|
|
}
|
|
|
|
// static
|
|
void IMEStateManager::OnFocusInEditor(nsPresContext& aPresContext,
|
|
Element* aElement,
|
|
EditorBase& aEditorBase) {
|
|
MOZ_LOG(sISMLog, LogLevel::Info,
|
|
("OnFocusInEditor(aPresContext=0x%p (available: %s), aElement=0x%p, "
|
|
"aEditorBase=0x%p), sFocusedPresContext=0x%p, sFocusedElement=0x%p, "
|
|
"sActiveIMEContentObserver=0x%p",
|
|
&aPresContext, GetBoolName(CanHandleWith(&aPresContext)), aElement,
|
|
&aEditorBase, sFocusedPresContext.get(), sFocusedElement.get(),
|
|
sActiveIMEContentObserver.get()));
|
|
|
|
if (!IsFocusedElement(aPresContext, aElement)) {
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" OnFocusInEditor(), "
|
|
"an editor not managed by ISM gets focus"));
|
|
return;
|
|
}
|
|
MOZ_ASSERT(sTextInputHandlingWidget);
|
|
|
|
// If the IMEContentObserver instance isn't observing the editable content for
|
|
// aElement, we need to recreate the instance because the active observer is
|
|
// observing different content even if aElement keeps being focused. E.g.,
|
|
// it's an <input> and editing host but was not a text control, but now, it's
|
|
// a text control.
|
|
if (sActiveIMEContentObserver) {
|
|
if (sActiveIMEContentObserver->IsObserving(aPresContext, aElement)) {
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" OnFocusInEditor(), "
|
|
"the editable content for aEditorBase has already been being "
|
|
"observed by sActiveIMEContentObserver"));
|
|
return;
|
|
}
|
|
// If the IMEContentObserver has not finished initializing itself yet,
|
|
// we don't need to recreate it because the following
|
|
// TryToFlushPendingNotifications call must make it initialized.
|
|
const nsCOMPtr<nsIWidget> textInputHandlingWidget =
|
|
sTextInputHandlingWidget;
|
|
if (!sActiveIMEContentObserver->IsBeingInitializedFor(
|
|
aPresContext, aElement, aEditorBase)) {
|
|
DestroyIMEContentObserver();
|
|
}
|
|
if (NS_WARN_IF(!IsFocusedElement(aPresContext, aElement)) ||
|
|
NS_WARN_IF(!sTextInputHandlingWidget) ||
|
|
NS_WARN_IF(sTextInputHandlingWidget != textInputHandlingWidget)) {
|
|
MOZ_LOG(sISMLog, LogLevel::Error,
|
|
(" OnFocusInEditor(), detected unexpected focus change with "
|
|
"re-initializing active IMEContentObserver"));
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!sActiveIMEContentObserver && sTextInputHandlingWidget &&
|
|
IsIMEObserverNeeded(
|
|
sTextInputHandlingWidget->GetInputContext().mIMEState)) {
|
|
CreateIMEContentObserver(aEditorBase, aElement);
|
|
if (sActiveIMEContentObserver) {
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" OnFocusInEditor(), new IMEContentObserver is created (0x%p)",
|
|
sActiveIMEContentObserver.get()));
|
|
}
|
|
}
|
|
|
|
if (sActiveIMEContentObserver) {
|
|
sActiveIMEContentObserver->TryToFlushPendingNotifications(false);
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" OnFocusInEditor(), trying to send pending notifications in "
|
|
"the active IMEContentObserver (0x%p)...",
|
|
sActiveIMEContentObserver.get()));
|
|
}
|
|
}
|
|
|
|
// static
|
|
void IMEStateManager::OnEditorInitialized(EditorBase& aEditorBase) {
|
|
if (!sActiveIMEContentObserver ||
|
|
!sActiveIMEContentObserver->WasInitializedWith(aEditorBase)) {
|
|
return;
|
|
}
|
|
|
|
MOZ_LOG(sISMLog, LogLevel::Info,
|
|
("OnEditorInitialized(aEditorBase=0x%p)", &aEditorBase));
|
|
|
|
sActiveIMEContentObserver->UnsuppressNotifyingIME();
|
|
}
|
|
|
|
// static
|
|
void IMEStateManager::OnEditorDestroying(EditorBase& aEditorBase) {
|
|
if (!sActiveIMEContentObserver ||
|
|
!sActiveIMEContentObserver->WasInitializedWith(aEditorBase)) {
|
|
return;
|
|
}
|
|
|
|
MOZ_LOG(sISMLog, LogLevel::Info,
|
|
("OnEditorDestroying(aEditorBase=0x%p)", &aEditorBase));
|
|
|
|
// The IMEContentObserver shouldn't notify IME of anything until reframing
|
|
// is finished.
|
|
sActiveIMEContentObserver->SuppressNotifyingIME();
|
|
}
|
|
|
|
void IMEStateManager::OnReFocus(nsPresContext& aPresContext,
|
|
Element& aElement) {
|
|
MOZ_LOG(sISMLog, LogLevel::Info,
|
|
("OnReFocus(aPresContext=0x%p (available: %s), aElement=0x%p), "
|
|
"sActiveIMEContentObserver=0x%p, aElement=0x%p",
|
|
&aPresContext, GetBoolName(CanHandleWith(&aPresContext)), &aElement,
|
|
sActiveIMEContentObserver.get(), sFocusedElement.get()));
|
|
|
|
if (NS_WARN_IF(!sTextInputHandlingWidget) ||
|
|
NS_WARN_IF(sTextInputHandlingWidget->Destroyed())) {
|
|
return;
|
|
}
|
|
|
|
// Check if IME has focus. If and only if IME has focus, we may need to
|
|
// update IME state of the widget to re-open VKB. Otherwise, IME will open
|
|
// VKB at getting focus.
|
|
if (!sActiveIMEContentObserver ||
|
|
!sActiveIMEContentObserver->IsObserving(aPresContext, &aElement)) {
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" OnReFocus(), editable content for aElement was not being "
|
|
"observed by the sActiveIMEContentObserver"));
|
|
return;
|
|
}
|
|
|
|
MOZ_ASSERT(&aElement == sFocusedElement.get());
|
|
|
|
if (!UserActivation::IsHandlingUserInput() ||
|
|
UserActivation::IsHandlingKeyboardInput()) {
|
|
return;
|
|
}
|
|
|
|
const OwningNonNull<nsIWidget> textInputHandlingWidget =
|
|
*sTextInputHandlingWidget;
|
|
|
|
// Don't update IME state during composition.
|
|
if (sTextCompositions) {
|
|
if (const TextComposition* composition =
|
|
sTextCompositions->GetCompositionFor(textInputHandlingWidget)) {
|
|
if (composition->IsComposing()) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
InputContextAction action(InputContextAction::CAUSE_UNKNOWN,
|
|
InputContextAction::FOCUS_NOT_CHANGED);
|
|
IMEState newState = GetNewIMEState(aPresContext, &aElement);
|
|
MOZ_ASSERT(newState.IsEditable());
|
|
SetIMEState(newState, &aPresContext, &aElement, textInputHandlingWidget,
|
|
action, sOrigin);
|
|
}
|
|
|
|
// static
|
|
void IMEStateManager::MaybeOnEditableStateDisabled(nsPresContext& aPresContext,
|
|
dom::Element* aElement) {
|
|
MOZ_LOG(
|
|
sISMLog, LogLevel::Info,
|
|
("MaybeOnEditableStateDisabled(aPresContext=0x%p, aElement=0x%p), "
|
|
"sFocusedPresContext=0x%p (available: %s), "
|
|
"sFocusedElement=0x%p, sTextInputHandlingWidget=0x%p (available: %s), "
|
|
"sActiveIMEContentObserver=0x%p, sIsGettingNewIMEState=%s",
|
|
&aPresContext, aElement, sFocusedPresContext.get(),
|
|
GetBoolName(CanHandleWith(sFocusedPresContext)), sFocusedElement.get(),
|
|
sTextInputHandlingWidget,
|
|
GetBoolName(sTextInputHandlingWidget &&
|
|
!sTextInputHandlingWidget->Destroyed()),
|
|
sActiveIMEContentObserver.get(), GetBoolName(sIsGettingNewIMEState)));
|
|
|
|
if (sIsGettingNewIMEState) {
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" MaybeOnEditableStateDisabled(), "
|
|
"does nothing because of called while getting new IME state"));
|
|
return;
|
|
}
|
|
|
|
if (&aPresContext != sFocusedPresContext || aElement != sFocusedElement) {
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" MaybeOnEditableStateDisabled(), "
|
|
"does nothing because of another element already has focus"));
|
|
return;
|
|
}
|
|
|
|
if (NS_WARN_IF(!sTextInputHandlingWidget) ||
|
|
NS_WARN_IF(sTextInputHandlingWidget->Destroyed())) {
|
|
MOZ_LOG(sISMLog, LogLevel::Error,
|
|
(" MaybeOnEditableStateDisabled(), FAILED due to "
|
|
"the widget for the managing the nsPresContext has gone"));
|
|
return;
|
|
}
|
|
|
|
const OwningNonNull<nsIWidget> textInputHandlingWidget =
|
|
*sTextInputHandlingWidget;
|
|
MOZ_ASSERT_IF(sFocusedPresContext->GetTextInputHandlingWidget(),
|
|
sFocusedPresContext->GetTextInputHandlingWidget() ==
|
|
textInputHandlingWidget.get());
|
|
|
|
const IMEState newIMEState = GetNewIMEState(aPresContext, aElement);
|
|
// If IME state becomes editable, HTMLEditor should also be initialized with
|
|
// same path as it gets focus. Therefore, IMEStateManager::UpdateIMEState
|
|
// should be called by HTMLEditor instead.
|
|
if (newIMEState.IsEditable()) {
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" MaybeOnEditableStateDisabled(), "
|
|
"does nothing because IME state does not become disabled"));
|
|
return;
|
|
}
|
|
|
|
// Otherwise, disable IME on the widget and destroy active IMEContentObserver
|
|
// if there is.
|
|
const InputContext inputContext = textInputHandlingWidget->GetInputContext();
|
|
if (inputContext.mIMEState.mEnabled == newIMEState.mEnabled) {
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" MaybeOnEditableStateDisabled(), "
|
|
"does nothing because IME state is not changed"));
|
|
return;
|
|
}
|
|
|
|
if (sActiveIMEContentObserver) {
|
|
DestroyIMEContentObserver();
|
|
}
|
|
|
|
InputContextAction action(InputContextAction::CAUSE_UNKNOWN,
|
|
InputContextAction::FOCUS_NOT_CHANGED);
|
|
SetIMEState(newIMEState, &aPresContext, aElement, textInputHandlingWidget,
|
|
action, sOrigin);
|
|
}
|
|
|
|
// static
|
|
void IMEStateManager::UpdateIMEState(const IMEState& aNewIMEState,
|
|
Element* aElement, EditorBase& aEditorBase,
|
|
const UpdateIMEStateOptions& aOptions) {
|
|
MOZ_LOG(
|
|
sISMLog, LogLevel::Info,
|
|
("UpdateIMEState(aNewIMEState=%s, aElement=0x%p, aEditorBase=0x%p, "
|
|
"aOptions=0x%0x), sFocusedPresContext=0x%p (available: %s), "
|
|
"sFocusedElement=0x%p, sTextInputHandlingWidget=0x%p (available: %s), "
|
|
"sActiveIMEContentObserver=0x%p, sIsGettingNewIMEState=%s",
|
|
ToString(aNewIMEState).c_str(), aElement, &aEditorBase,
|
|
aOptions.serialize(), sFocusedPresContext.get(),
|
|
GetBoolName(CanHandleWith(sFocusedPresContext)), sFocusedElement.get(),
|
|
sTextInputHandlingWidget,
|
|
GetBoolName(sTextInputHandlingWidget &&
|
|
!sTextInputHandlingWidget->Destroyed()),
|
|
sActiveIMEContentObserver.get(), GetBoolName(sIsGettingNewIMEState)));
|
|
|
|
if (sIsGettingNewIMEState) {
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" UpdateIMEState(), "
|
|
"does nothing because of called while getting new IME state"));
|
|
return;
|
|
}
|
|
|
|
RefPtr<PresShell> presShell(aEditorBase.GetPresShell());
|
|
if (NS_WARN_IF(!presShell)) {
|
|
MOZ_LOG(sISMLog, LogLevel::Error,
|
|
(" UpdateIMEState(), FAILED due to "
|
|
"editor doesn't have PresShell"));
|
|
return;
|
|
}
|
|
|
|
const RefPtr<nsPresContext> presContext =
|
|
aElement
|
|
? aElement->GetPresContext(Element::PresContextFor::eForComposedDoc)
|
|
: aEditorBase.GetPresContext();
|
|
if (NS_WARN_IF(!presContext)) {
|
|
MOZ_LOG(sISMLog, LogLevel::Error,
|
|
(" UpdateIMEState(), FAILED due to "
|
|
"editor doesn't have PresContext"));
|
|
return;
|
|
}
|
|
|
|
// IMEStateManager::UpdateIMEState() should be called after
|
|
// IMEStateManager::OnChangeFocus() is called for setting focus to aElement
|
|
// and aEditorBase. However, when aEditorBase is an HTMLEditor, this may be
|
|
// called by nsIEditor::PostCreate() before IMEStateManager::OnChangeFocus().
|
|
// Similarly, when aEditorBase is a TextEditor, this may be called by
|
|
// nsIEditor::SetFlags(). In such cases, this method should do nothing
|
|
// because input context should be updated when
|
|
// IMEStateManager::OnChangeFocus() is called later.
|
|
if (sFocusedPresContext != presContext) {
|
|
MOZ_LOG(sISMLog, LogLevel::Warning,
|
|
(" UpdateIMEState(), does nothing due to "
|
|
"the editor hasn't managed by IMEStateManager yet"));
|
|
return;
|
|
}
|
|
|
|
// If IMEStateManager doesn't manage any document, this cannot update IME
|
|
// state of any widget.
|
|
if (NS_WARN_IF(!sFocusedPresContext)) {
|
|
MOZ_LOG(sISMLog, LogLevel::Error,
|
|
(" UpdateIMEState(), FAILED due to "
|
|
"no managing nsPresContext"));
|
|
return;
|
|
}
|
|
|
|
if (NS_WARN_IF(!sTextInputHandlingWidget) ||
|
|
NS_WARN_IF(sTextInputHandlingWidget->Destroyed())) {
|
|
MOZ_LOG(sISMLog, LogLevel::Error,
|
|
(" UpdateIMEState(), FAILED due to "
|
|
"the widget for the managing nsPresContext has gone"));
|
|
return;
|
|
}
|
|
|
|
const OwningNonNull<nsIWidget> textInputHandlingWidget =
|
|
*sTextInputHandlingWidget;
|
|
MOZ_ASSERT_IF(sFocusedPresContext->GetTextInputHandlingWidget(),
|
|
sFocusedPresContext->GetTextInputHandlingWidget() ==
|
|
textInputHandlingWidget.get());
|
|
|
|
// TODO: Investigate if we could put off to initialize IMEContentObserver
|
|
// later because a lot of callers need to be marked as
|
|
// MOZ_CAN_RUN_SCRIPT otherwise.
|
|
|
|
// If there is an active observer and we need an observer, we want to keep
|
|
// using the observer as far as possible because it's already notified IME of
|
|
// focus. However, between the call of OnChangeFocus() and UpdateIMEState(),
|
|
// focus and/or IME state may have been changed. If so, we need to update
|
|
// the observer for current situation.
|
|
const bool hasActiveObserverAndNeedObserver =
|
|
sActiveIMEContentObserver && IsIMEObserverNeeded(aNewIMEState);
|
|
|
|
// If the active observer was not initialized with aEditorBase, it means
|
|
// that the old editor lost focus but new editor gets focus **without**
|
|
// focus move in the DOM tree. This may happen with changing the type of
|
|
// <input> element, etc. In this case, we need to let IME know a focus move
|
|
// with recreating IMEContentObserver because editable target has been
|
|
// changed.
|
|
const bool needToRecreateObserver =
|
|
hasActiveObserverAndNeedObserver &&
|
|
!sActiveIMEContentObserver->WasInitializedWith(aEditorBase);
|
|
|
|
// If the active observer was initialized with same editor, we can/should
|
|
// keep using it for avoiding to notify IME of a focus change. However, we
|
|
// may need to re-initialize it because it may have temporarily stopped
|
|
// observing the content.
|
|
if (hasActiveObserverAndNeedObserver && !needToRecreateObserver) {
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" UpdateIMEState(), try to reinitialize the active "
|
|
"IMEContentObserver"));
|
|
OwningNonNull<IMEContentObserver> contentObserver =
|
|
*sActiveIMEContentObserver;
|
|
OwningNonNull<nsPresContext> presContext = *sFocusedPresContext;
|
|
if (!contentObserver->MaybeReinitialize(
|
|
textInputHandlingWidget, presContext, aElement, aEditorBase)) {
|
|
MOZ_LOG(sISMLog, LogLevel::Error,
|
|
(" UpdateIMEState(), failed to reinitialize the active "
|
|
"IMEContentObserver"));
|
|
}
|
|
if (NS_WARN_IF(textInputHandlingWidget->Destroyed())) {
|
|
MOZ_LOG(sISMLog, LogLevel::Error,
|
|
(" UpdateIMEState(), widget has gone during re-initializing "
|
|
"the active IMEContentObserver"));
|
|
return;
|
|
}
|
|
}
|
|
|
|
// If there is no active IMEContentObserver or it isn't observing the
|
|
// editable content for aElement, we should recreate an observer. E.g.,
|
|
// aElement which is an editing host may have been changed from/to a text
|
|
// control.
|
|
const bool createNewObserver =
|
|
IsIMEObserverNeeded(aNewIMEState) &&
|
|
(!sActiveIMEContentObserver || needToRecreateObserver ||
|
|
!sActiveIMEContentObserver->IsObserving(*sFocusedPresContext, aElement));
|
|
// If we're recreating new IMEContentObserver or new state does not need
|
|
// IMEContentObserver, destroy the active one.
|
|
const bool destroyCurrentObserver =
|
|
sActiveIMEContentObserver &&
|
|
(createNewObserver || !IsIMEObserverNeeded(aNewIMEState));
|
|
|
|
// If IME state is changed, e.g., from "enabled" or "password" to "disabled",
|
|
// we cannot keep the composing state because the new "disabled" state does
|
|
// not support composition, and also from "enabled" to "password" and vice
|
|
// versa, IME may use different composing rules. Therefore, for avoiding
|
|
// IME to be confused, we should fix the composition first.
|
|
const bool updateIMEState =
|
|
aOptions.contains(UpdateIMEStateOption::ForceUpdate) ||
|
|
(textInputHandlingWidget->GetInputContext().mIMEState.mEnabled !=
|
|
aNewIMEState.mEnabled);
|
|
if (NS_WARN_IF(textInputHandlingWidget->Destroyed())) {
|
|
MOZ_LOG(
|
|
sISMLog, LogLevel::Error,
|
|
(" UpdateIMEState(), widget has gone during getting input context"));
|
|
return;
|
|
}
|
|
|
|
if (updateIMEState &&
|
|
!aOptions.contains(UpdateIMEStateOption::DontCommitComposition)) {
|
|
NotifyIME(REQUEST_TO_COMMIT_COMPOSITION, textInputHandlingWidget,
|
|
sFocusedIMEBrowserParent);
|
|
if (NS_WARN_IF(textInputHandlingWidget->Destroyed())) {
|
|
MOZ_LOG(sISMLog, LogLevel::Error,
|
|
(" UpdateIMEState(), widget has gone during committing "
|
|
"composition"));
|
|
return;
|
|
}
|
|
// FIXME: If committing composition changes IME state recursively, we should
|
|
// not keep updating IME state here. However, how can we manage it?
|
|
// Is a generation of the state is required?
|
|
}
|
|
|
|
// Notify IME of blur first.
|
|
if (destroyCurrentObserver) {
|
|
DestroyIMEContentObserver();
|
|
if (NS_WARN_IF(textInputHandlingWidget->Destroyed()) ||
|
|
NS_WARN_IF(sTextInputHandlingWidget != textInputHandlingWidget)) {
|
|
MOZ_LOG(sISMLog, LogLevel::Error,
|
|
(" UpdateIMEState(), has set input context, but the widget is "
|
|
"not focused"));
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Then, notify our IME handler of new IME state.
|
|
if (updateIMEState) {
|
|
InputContextAction action(InputContextAction::CAUSE_UNKNOWN,
|
|
InputContextAction::FOCUS_NOT_CHANGED);
|
|
RefPtr<nsPresContext> editorPresContext = aEditorBase.GetPresContext();
|
|
if (NS_WARN_IF(!editorPresContext)) {
|
|
MOZ_LOG(sISMLog, LogLevel::Error,
|
|
(" UpdateIMEState(), nsPresContext for editor has already been "
|
|
"lost"));
|
|
return;
|
|
}
|
|
SetIMEState(aNewIMEState, editorPresContext, aElement,
|
|
textInputHandlingWidget, action, sOrigin);
|
|
if (NS_WARN_IF(textInputHandlingWidget->Destroyed()) ||
|
|
NS_WARN_IF(sTextInputHandlingWidget != textInputHandlingWidget)) {
|
|
MOZ_LOG(sISMLog, LogLevel::Error,
|
|
(" UpdateIMEState(), has set input context, but the widget is "
|
|
"not focused"));
|
|
return;
|
|
}
|
|
if (NS_WARN_IF(
|
|
sTextInputHandlingWidget->GetInputContext().mIMEState.mEnabled !=
|
|
aNewIMEState.mEnabled)) {
|
|
MOZ_LOG(sISMLog, LogLevel::Error,
|
|
(" UpdateIMEState(), has set input context, but IME enabled "
|
|
"state was overridden by somebody else"));
|
|
return;
|
|
}
|
|
}
|
|
|
|
NS_ASSERTION(IsFocusedElement(*presContext, aElement),
|
|
"aElement does not match with sFocusedElement");
|
|
|
|
// Finally, create new observer if required.
|
|
if (createNewObserver) {
|
|
if (!sActiveIMEContentObserver && sFocusedPresContext &&
|
|
sTextInputHandlingWidget) {
|
|
// XXX In this case, it might not be enough safe to notify IME of
|
|
// anything. So, don't try to flush pending notifications of
|
|
// IMEContentObserver here.
|
|
CreateIMEContentObserver(aEditorBase, aElement);
|
|
} else {
|
|
MOZ_LOG(sISMLog, LogLevel::Error,
|
|
(" UpdateIMEState(), wanted to create IMEContentObserver, but "
|
|
"lost focus"));
|
|
}
|
|
}
|
|
}
|
|
|
|
// static
|
|
IMEState IMEStateManager::GetNewIMEState(const nsPresContext& aPresContext,
|
|
Element* aElement) {
|
|
MOZ_LOG(
|
|
sISMLog, LogLevel::Info,
|
|
("GetNewIMEState(aPresContext=0x%p, aElement=0x%p), "
|
|
"sInstalledMenuKeyboardListener=%s",
|
|
&aPresContext, aElement, GetBoolName(sInstalledMenuKeyboardListener)));
|
|
|
|
if (!CanHandleWith(&aPresContext)) {
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" GetNewIMEState() returns IMEEnabled::Disabled because "
|
|
"the nsPresContext has been destroyed"));
|
|
return IMEState(IMEEnabled::Disabled);
|
|
}
|
|
|
|
// On Printing or Print Preview, we don't need IME.
|
|
if (aPresContext.Type() == nsPresContext::eContext_PrintPreview ||
|
|
aPresContext.Type() == nsPresContext::eContext_Print) {
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" GetNewIMEState() returns IMEEnabled::Disabled because "
|
|
"the nsPresContext is for print or print preview"));
|
|
return IMEState(IMEEnabled::Disabled);
|
|
}
|
|
|
|
if (sInstalledMenuKeyboardListener) {
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" GetNewIMEState() returns IMEEnabled::Disabled because "
|
|
"menu keyboard listener was installed"));
|
|
return IMEState(IMEEnabled::Disabled);
|
|
}
|
|
|
|
if (!aElement) {
|
|
// Even if there are no focused content, the focused document might be
|
|
// editable, such case is design mode.
|
|
if (aPresContext.Document() && aPresContext.Document()->IsInDesignMode()) {
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" GetNewIMEState() returns IMEEnabled::Enabled because "
|
|
"design mode editor has focus"));
|
|
return IMEState(IMEEnabled::Enabled);
|
|
}
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" GetNewIMEState() returns IMEEnabled::Disabled because "
|
|
"no content has focus"));
|
|
return IMEState(IMEEnabled::Disabled);
|
|
}
|
|
|
|
// If aElement is in designMode, aElement should be the root node of the
|
|
// document.
|
|
if (aElement && aElement->IsInDesignMode()) {
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" GetNewIMEState() returns IMEEnabled::Enabled because "
|
|
"a content node in design mode editor has focus"));
|
|
return IMEState(IMEEnabled::Enabled);
|
|
}
|
|
|
|
// nsIContent::GetDesiredIMEState() may cause a call of UpdateIMEState()
|
|
// from EditorBase::PostCreate() because GetDesiredIMEState() needs to
|
|
// retrieve an editor instance for the element if it's editable element.
|
|
// For avoiding such nested IME state updates, we should set
|
|
// sIsGettingNewIMEState here and UpdateIMEState() should check it.
|
|
GettingNewIMEStateBlocker blocker;
|
|
|
|
IMEState newIMEState = aElement->GetDesiredIMEState();
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" GetNewIMEState() returns %s", ToString(newIMEState).c_str()));
|
|
return newIMEState;
|
|
}
|
|
|
|
// static
|
|
void IMEStateManager::ResetActiveChildInputContext() {
|
|
sActiveChildInputContext.mIMEState.mEnabled = IMEEnabled::Unknown;
|
|
}
|
|
|
|
// static
|
|
bool IMEStateManager::HasActiveChildSetInputContext() {
|
|
return sActiveChildInputContext.mIMEState.mEnabled != IMEEnabled::Unknown;
|
|
}
|
|
|
|
// static
|
|
void IMEStateManager::SetInputContextForChildProcess(
|
|
BrowserParent* aBrowserParent, const InputContext& aInputContext,
|
|
const InputContextAction& aAction) {
|
|
MOZ_LOG(
|
|
sISMLog, LogLevel::Info,
|
|
("SetInputContextForChildProcess(aBrowserParent=0x%p, "
|
|
"aInputContext=%s , aAction={ mCause=%s, mAction=%s }), "
|
|
"sFocusedPresContext=0x%p (available: %s), "
|
|
"sTextInputHandlingWidget=0x%p (available: %s), "
|
|
"BrowserParent::GetFocused()=0x%p, sInstalledMenuKeyboardListener=%s",
|
|
aBrowserParent, ToString(aInputContext).c_str(),
|
|
ToString(aAction.mCause).c_str(), ToString(aAction.mFocusChange).c_str(),
|
|
sFocusedPresContext.get(),
|
|
GetBoolName(CanHandleWith(sFocusedPresContext)),
|
|
sTextInputHandlingWidget,
|
|
GetBoolName(sTextInputHandlingWidget &&
|
|
!sTextInputHandlingWidget->Destroyed()),
|
|
BrowserParent::GetFocused(),
|
|
GetBoolName(sInstalledMenuKeyboardListener)));
|
|
|
|
if (aBrowserParent != BrowserParent::GetFocused()) {
|
|
MOZ_LOG(sISMLog, LogLevel::Error,
|
|
(" SetInputContextForChildProcess(), FAILED, "
|
|
"because non-focused tab parent tries to set input context"));
|
|
return;
|
|
}
|
|
|
|
if (NS_WARN_IF(!CanHandleWith(sFocusedPresContext))) {
|
|
MOZ_LOG(sISMLog, LogLevel::Error,
|
|
(" SetInputContextForChildProcess(), FAILED, "
|
|
"due to no focused presContext"));
|
|
return;
|
|
}
|
|
|
|
if (NS_WARN_IF(!sTextInputHandlingWidget) ||
|
|
NS_WARN_IF(sTextInputHandlingWidget->Destroyed())) {
|
|
MOZ_LOG(sISMLog, LogLevel::Error,
|
|
(" SetInputContextForChildProcess(), FAILED, "
|
|
"due to the widget for the nsPresContext has gone"));
|
|
return;
|
|
}
|
|
|
|
const OwningNonNull<nsIWidget> textInputHandlingWidget =
|
|
*sTextInputHandlingWidget;
|
|
MOZ_ASSERT_IF(sFocusedPresContext->GetTextInputHandlingWidget(),
|
|
sFocusedPresContext->GetTextInputHandlingWidget() ==
|
|
textInputHandlingWidget.get());
|
|
MOZ_ASSERT(aInputContext.mOrigin == InputContext::ORIGIN_CONTENT);
|
|
|
|
sActiveChildInputContext = aInputContext;
|
|
MOZ_ASSERT(HasActiveChildSetInputContext());
|
|
|
|
// If input context is changed in remote process while menu keyboard listener
|
|
// is installed, this process shouldn't set input context now. When it's
|
|
// uninstalled, input context should be restored from
|
|
// sActiveChildInputContext.
|
|
if (sInstalledMenuKeyboardListener) {
|
|
MOZ_LOG(sISMLog, LogLevel::Info,
|
|
(" SetInputContextForChildProcess(), waiting to set input context "
|
|
"until menu keyboard listener is uninstalled"));
|
|
return;
|
|
}
|
|
|
|
SetInputContext(textInputHandlingWidget, aInputContext, aAction);
|
|
}
|
|
|
|
MOZ_CAN_RUN_SCRIPT static bool IsNextFocusableElementTextControl(
|
|
const Element* aInputContent) {
|
|
RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager();
|
|
if (MOZ_UNLIKELY(!fm)) {
|
|
return false;
|
|
}
|
|
nsCOMPtr<nsIContent> nextContent;
|
|
const RefPtr<Element> inputContent = const_cast<Element*>(aInputContent);
|
|
const nsCOMPtr<nsPIDOMWindowOuter> outerWindow =
|
|
aInputContent->OwnerDoc()->GetWindow();
|
|
nsresult rv = fm->DetermineElementToMoveFocus(
|
|
outerWindow, inputContent, nsIFocusManager::MOVEFOCUS_FORWARD, true,
|
|
false, getter_AddRefs(nextContent));
|
|
if (NS_WARN_IF(NS_FAILED(rv)) || !nextContent) {
|
|
return false;
|
|
}
|
|
nextContent = nextContent->FindFirstNonChromeOnlyAccessContent();
|
|
nsCOMPtr<nsIFormControl> nextControl = do_QueryInterface(nextContent);
|
|
if (!nextControl || !nextControl->IsTextControl(false)) {
|
|
return false;
|
|
}
|
|
|
|
// XXX We don't consider next element is date/time control yet.
|
|
nsGenericHTMLElement* nextElement =
|
|
nsGenericHTMLElement::FromNodeOrNull(nextContent);
|
|
if (!nextElement) {
|
|
return false;
|
|
}
|
|
|
|
// FIXME: Should probably use nsIFrame::IsFocusable if possible, to account
|
|
// for things like visibility: hidden or so.
|
|
if (!nextElement->IsFocusableWithoutStyle(false)) {
|
|
return false;
|
|
}
|
|
|
|
// Check readonly attribute.
|
|
if (nextElement->IsHTMLElement(nsGkAtoms::textarea)) {
|
|
auto* textAreaElement = HTMLTextAreaElement::FromNode(nextElement);
|
|
return !textAreaElement->ReadOnly();
|
|
}
|
|
|
|
// If neither textarea nor input, what element type?
|
|
MOZ_DIAGNOSTIC_ASSERT(nextElement->IsHTMLElement(nsGkAtoms::input));
|
|
|
|
auto* inputElement = HTMLInputElement::FromNode(nextElement);
|
|
return !inputElement->ReadOnly();
|
|
}
|
|
|
|
static void GetInputType(const IMEState& aState, const nsIContent& aContent,
|
|
nsAString& aInputType) {
|
|
// NOTE: If you change here, you may need to update
|
|
// widget::InputContext::GatNativeKeyBindings too.
|
|
if (aContent.IsHTMLElement(nsGkAtoms::input)) {
|
|
const HTMLInputElement* inputElement =
|
|
HTMLInputElement::FromNode(&aContent);
|
|
if (inputElement->HasBeenTypePassword() && aState.IsEditable()) {
|
|
aInputType.AssignLiteral("password");
|
|
} else {
|
|
inputElement->GetType(aInputType);
|
|
}
|
|
} else if (aContent.IsHTMLElement(nsGkAtoms::textarea)) {
|
|
aInputType.Assign(nsGkAtoms::textarea->GetUTF16String());
|
|
}
|
|
}
|
|
|
|
MOZ_CAN_RUN_SCRIPT static void GetActionHint(const IMEState& aState,
|
|
const nsIContent& aContent,
|
|
nsAString& aActionHint) {
|
|
MOZ_ASSERT(aContent.IsHTMLElement());
|
|
|
|
if (aState.IsEditable()) {
|
|
nsGenericHTMLElement::FromNode(&aContent)->GetEnterKeyHint(aActionHint);
|
|
|
|
// If enterkeyhint is set, we don't infer action hint.
|
|
if (!aActionHint.IsEmpty()) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!aContent.IsHTMLElement(nsGkAtoms::input)) {
|
|
return;
|
|
}
|
|
|
|
// Get the input content corresponding to the focused node,
|
|
// which may be an anonymous child of the input content.
|
|
MOZ_ASSERT(&aContent == aContent.FindFirstNonChromeOnlyAccessContent());
|
|
const HTMLInputElement* inputElement = HTMLInputElement::FromNode(aContent);
|
|
if (!inputElement) {
|
|
return;
|
|
}
|
|
|
|
// If we don't have an action hint and
|
|
// return won't submit the form, use "maybenext".
|
|
bool willSubmit = false;
|
|
bool isLastElement = false;
|
|
HTMLFormElement* formElement = inputElement->GetForm();
|
|
// is this a form and does it have a default submit element?
|
|
if (formElement) {
|
|
if (formElement->IsLastActiveElement(inputElement)) {
|
|
isLastElement = true;
|
|
}
|
|
|
|
if (formElement->GetDefaultSubmitElement()) {
|
|
willSubmit = true;
|
|
// is this an html form...
|
|
} else {
|
|
// ... and does it only have a single text input element ?
|
|
if (!formElement->ImplicitSubmissionIsDisabled() ||
|
|
// ... or is this the last non-disabled element?
|
|
isLastElement) {
|
|
willSubmit = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!isLastElement && formElement) {
|
|
// If next tabbable content in form is text control, hint should be "next"
|
|
// even there is submit in form.
|
|
// MOZ_KnownLive(inputElement) because it's an alias of aContent.
|
|
if (IsNextFocusableElementTextControl(MOZ_KnownLive(inputElement))) {
|
|
// This is focusable text control
|
|
// XXX What good hint for read only field?
|
|
aActionHint.AssignLiteral("maybenext");
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!willSubmit) {
|
|
aActionHint.Truncate();
|
|
return;
|
|
}
|
|
|
|
if (inputElement->ControlType() == FormControlType::InputSearch) {
|
|
aActionHint.AssignLiteral("search");
|
|
return;
|
|
}
|
|
|
|
aActionHint.AssignLiteral("go");
|
|
}
|
|
|
|
static void GetInputMode(const IMEState& aState, const nsIContent& aContent,
|
|
nsAString& aInputMode) {
|
|
if (aState.IsEditable()) {
|
|
aContent.AsElement()->GetAttr(nsGkAtoms::inputmode, aInputMode);
|
|
if (aContent.IsHTMLElement(nsGkAtoms::input) &&
|
|
aInputMode.EqualsLiteral("mozAwesomebar")) {
|
|
if (!nsContentUtils::IsChromeDoc(aContent.OwnerDoc())) {
|
|
// mozAwesomebar should be allowed only in chrome
|
|
aInputMode.Truncate();
|
|
}
|
|
} else {
|
|
ToLowerCase(aInputMode);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void GetAutocapitalize(const IMEState& aState, const Element& aElement,
|
|
const InputContext& aInputContext,
|
|
nsAString& aAutocapitalize) {
|
|
if (aElement.IsHTMLElement() && aState.IsEditable() &&
|
|
aInputContext.IsAutocapitalizeSupported()) {
|
|
nsGenericHTMLElement::FromNode(&aElement)->GetAutocapitalize(
|
|
aAutocapitalize);
|
|
}
|
|
}
|
|
|
|
// static
|
|
void IMEStateManager::SetIMEState(const IMEState& aState,
|
|
const nsPresContext* aPresContext,
|
|
Element* aElement, nsIWidget& aWidget,
|
|
InputContextAction aAction,
|
|
InputContext::Origin aOrigin) {
|
|
MOZ_LOG(sISMLog, LogLevel::Info,
|
|
("SetIMEState(aState=%s, nsPresContext=0x%p, aElement=0x%p "
|
|
"(BrowserParent=0x%p), aWidget=0x%p, aAction={ mCause=%s, "
|
|
"mFocusChange=%s }, aOrigin=%s)",
|
|
ToString(aState).c_str(), aPresContext, aElement,
|
|
BrowserParent::GetFrom(aElement), &aWidget,
|
|
ToString(aAction.mCause).c_str(),
|
|
ToString(aAction.mFocusChange).c_str(), ToChar(aOrigin)));
|
|
|
|
InputContext context;
|
|
context.mIMEState = aState;
|
|
if (aPresContext) {
|
|
if (nsIURI* uri = aPresContext->Document()->GetDocumentURI()) {
|
|
// We don't need to and should not expose special URLs such as:
|
|
// about: Any apps like IME should work normally and constantly in any
|
|
// default pages such as about:blank, about:home, etc in either
|
|
// the main process or a content process.
|
|
// blob: This may contain big data. If we copy it to the main process,
|
|
// it may make the heap dirty which makes the process slower.
|
|
// chrome: Same as about, any apps should work normally and constantly in
|
|
// any chrome documents.
|
|
// data: Any native apps in the environment shouldn't change the behavior
|
|
// with the data URL's content and it may contain too big data.
|
|
// file: The file path may contain private things and we shouldn't let
|
|
// other apps like IME know which one is touched by the user because
|
|
// malicious text services may like files which are explicitly used
|
|
// by the user better.
|
|
if (uri->SchemeIs("http") || uri->SchemeIs("https")) {
|
|
// Note that we don't need to expose UserPass, Query and Reference to
|
|
// IME since they may contain sensitive data, but non-malicious text
|
|
// services must not require these data.
|
|
nsCOMPtr<nsIURI> exposableURL;
|
|
if (NS_SUCCEEDED(NS_MutateURI(uri)
|
|
.SetQuery(""_ns)
|
|
.SetRef(""_ns)
|
|
.SetUserPass(""_ns)
|
|
.Finalize(exposableURL))) {
|
|
context.mURI = std::move(exposableURL);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
context.mOrigin = aOrigin;
|
|
|
|
context.mHasHandledUserInput =
|
|
aPresContext && aPresContext->PresShell()->HasHandledUserInput();
|
|
|
|
context.mInPrivateBrowsing =
|
|
aPresContext &&
|
|
nsContentUtils::IsInPrivateBrowsing(aPresContext->Document());
|
|
|
|
const RefPtr<Element> focusedElement =
|
|
aElement ? Element::FromNodeOrNull(
|
|
aElement->FindFirstNonChromeOnlyAccessContent())
|
|
: nullptr;
|
|
|
|
if (focusedElement && focusedElement->IsHTMLElement()) {
|
|
GetInputType(aState, *focusedElement, context.mHTMLInputType);
|
|
GetActionHint(aState, *focusedElement, context.mActionHint);
|
|
GetInputMode(aState, *focusedElement, context.mHTMLInputMode);
|
|
GetAutocapitalize(aState, *focusedElement, context,
|
|
context.mAutocapitalize);
|
|
}
|
|
|
|
if (aAction.mCause == InputContextAction::CAUSE_UNKNOWN &&
|
|
nsContentUtils::LegacyIsCallerChromeOrNativeCode()) {
|
|
aAction.mCause = InputContextAction::CAUSE_UNKNOWN_CHROME;
|
|
}
|
|
|
|
if ((aAction.mCause == InputContextAction::CAUSE_UNKNOWN ||
|
|
aAction.mCause == InputContextAction::CAUSE_UNKNOWN_CHROME) &&
|
|
UserActivation::IsHandlingUserInput()) {
|
|
aAction.mCause =
|
|
UserActivation::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) {
|
|
MOZ_LOG(
|
|
sISMLog, LogLevel::Info,
|
|
("SetInputContext(aWidget=0x%p, aInputContext=%s, "
|
|
"aAction={ mCause=%s, mAction=%s }), BrowserParent::GetFocused()=0x%p",
|
|
&aWidget, ToString(aInputContext).c_str(),
|
|
ToString(aAction.mCause).c_str(), ToString(aAction.mFocusChange).c_str(),
|
|
BrowserParent::GetFocused()));
|
|
|
|
OwningNonNull<nsIWidget> widget = aWidget;
|
|
widget->SetInputContext(aInputContext, aAction);
|
|
sActiveInputContextWidget = widget;
|
|
}
|
|
|
|
// static
|
|
void IMEStateManager::EnsureTextCompositionArray() {
|
|
if (sTextCompositions) {
|
|
return;
|
|
}
|
|
sTextCompositions = new TextCompositionArray();
|
|
}
|
|
|
|
// static
|
|
void IMEStateManager::DispatchCompositionEvent(
|
|
nsINode* aEventTargetNode, nsPresContext* aPresContext,
|
|
BrowserParent* aBrowserParent, WidgetCompositionEvent* aCompositionEvent,
|
|
nsEventStatus* aStatus, EventDispatchingCallback* aCallBack,
|
|
bool aIsSynthesized) {
|
|
MOZ_LOG(
|
|
sISMLog, LogLevel::Info,
|
|
("DispatchCompositionEvent(aNode=0x%p, "
|
|
"aPresContext=0x%p, aCompositionEvent={ mMessage=%s, "
|
|
"mNativeIMEContext={ mRawNativeIMEContext=0x%" PRIXPTR ", "
|
|
"mOriginProcessID=0x%" PRIX64 " }, mWidget(0x%p)={ "
|
|
"GetNativeIMEContext()={ mRawNativeIMEContext=0x%" PRIXPTR ", "
|
|
"mOriginProcessID=0x%" PRIX64 " }, Destroyed()=%s }, "
|
|
"mFlags={ mIsTrusted=%s, mPropagationStopped=%s } }, "
|
|
"aIsSynthesized=%s), browserParent=%p",
|
|
aEventTargetNode, aPresContext, ToChar(aCompositionEvent->mMessage),
|
|
aCompositionEvent->mNativeIMEContext.mRawNativeIMEContext,
|
|
aCompositionEvent->mNativeIMEContext.mOriginProcessID,
|
|
aCompositionEvent->mWidget.get(),
|
|
aCompositionEvent->mWidget->GetNativeIMEContext().mRawNativeIMEContext,
|
|
aCompositionEvent->mWidget->GetNativeIMEContext().mOriginProcessID,
|
|
GetBoolName(aCompositionEvent->mWidget->Destroyed()),
|
|
GetBoolName(aCompositionEvent->mFlags.mIsTrusted),
|
|
GetBoolName(aCompositionEvent->mFlags.mPropagationStopped),
|
|
GetBoolName(aIsSynthesized), aBrowserParent));
|
|
|
|
if (NS_WARN_IF(!aCompositionEvent->IsTrusted()) ||
|
|
NS_WARN_IF(aCompositionEvent->PropagationStopped())) {
|
|
return;
|
|
}
|
|
|
|
MOZ_ASSERT(aCompositionEvent->mMessage != eCompositionUpdate,
|
|
"compositionupdate event shouldn't be dispatched manually");
|
|
|
|
EnsureTextCompositionArray();
|
|
|
|
RefPtr<TextComposition> composition =
|
|
sTextCompositions->GetCompositionFor(aCompositionEvent);
|
|
if (!composition) {
|
|
// If synthesized event comes after delayed native composition events
|
|
// for request of commit or cancel, we should ignore it.
|
|
if (NS_WARN_IF(aIsSynthesized)) {
|
|
return;
|
|
}
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" DispatchCompositionEvent(), "
|
|
"adding new TextComposition to the array"));
|
|
MOZ_ASSERT(aCompositionEvent->mMessage == eCompositionStart);
|
|
composition = new TextComposition(aPresContext, aEventTargetNode,
|
|
aBrowserParent, aCompositionEvent);
|
|
sTextCompositions->AppendElement(composition);
|
|
}
|
|
#ifdef DEBUG
|
|
else {
|
|
MOZ_ASSERT(aCompositionEvent->mMessage != eCompositionStart);
|
|
}
|
|
#endif // #ifdef DEBUG
|
|
|
|
// Dispatch the event on composing target.
|
|
composition->DispatchCompositionEvent(aCompositionEvent, aStatus, aCallBack,
|
|
aIsSynthesized);
|
|
|
|
// WARNING: the |composition| might have been destroyed already.
|
|
|
|
// Remove the ended composition from the array.
|
|
// NOTE: When TextComposition is synthesizing compositionend event for
|
|
// emulating a commit, the instance shouldn't be removed from the array
|
|
// because IME may perform it later. Then, we need to ignore the
|
|
// following commit events in TextComposition::DispatchEvent().
|
|
// However, if commit or cancel for a request is performed synchronously
|
|
// during not safe to dispatch events, PresShell must have discarded
|
|
// compositionend event. Then, the synthesized compositionend event is
|
|
// the last event for the composition. In this case, we need to
|
|
// destroy the TextComposition with synthesized compositionend event.
|
|
if ((!aIsSynthesized ||
|
|
composition->WasNativeCompositionEndEventDiscarded()) &&
|
|
aCompositionEvent->CausesDOMCompositionEndEvent()) {
|
|
TextCompositionArray::index_type i =
|
|
sTextCompositions->IndexOf(aCompositionEvent->mWidget);
|
|
if (i != TextCompositionArray::NoIndex) {
|
|
MOZ_LOG(
|
|
sISMLog, LogLevel::Debug,
|
|
(" DispatchCompositionEvent(), "
|
|
"removing TextComposition from the array since NS_COMPOSTION_END "
|
|
"was dispatched"));
|
|
sTextCompositions->ElementAt(i)->Destroy();
|
|
sTextCompositions->RemoveElementAt(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
// static
|
|
IMEContentObserver* IMEStateManager::GetActiveContentObserver() {
|
|
return sActiveIMEContentObserver;
|
|
}
|
|
|
|
// static
|
|
nsIContent* IMEStateManager::GetRootContent(nsPresContext* aPresContext) {
|
|
Document* doc = aPresContext->Document();
|
|
if (NS_WARN_IF(!doc)) {
|
|
return nullptr;
|
|
}
|
|
return doc->GetRootElement();
|
|
}
|
|
|
|
// static
|
|
void IMEStateManager::HandleSelectionEvent(
|
|
nsPresContext* aPresContext, nsIContent* aEventTargetContent,
|
|
WidgetSelectionEvent* aSelectionEvent) {
|
|
RefPtr<BrowserParent> browserParent = GetActiveBrowserParent();
|
|
if (!browserParent) {
|
|
browserParent = BrowserParent::GetFrom(aEventTargetContent
|
|
? aEventTargetContent
|
|
: GetRootContent(aPresContext));
|
|
}
|
|
|
|
MOZ_LOG(
|
|
sISMLog, LogLevel::Info,
|
|
("HandleSelectionEvent(aPresContext=0x%p, "
|
|
"aEventTargetContent=0x%p, aSelectionEvent={ mMessage=%s, "
|
|
"mFlags={ mIsTrusted=%s } }), browserParent=%p",
|
|
aPresContext, aEventTargetContent, ToChar(aSelectionEvent->mMessage),
|
|
GetBoolName(aSelectionEvent->mFlags.mIsTrusted), browserParent.get()));
|
|
|
|
if (!aSelectionEvent->IsTrusted()) {
|
|
return;
|
|
}
|
|
|
|
RefPtr<TextComposition> composition =
|
|
sTextCompositions
|
|
? sTextCompositions->GetCompositionFor(aSelectionEvent->mWidget)
|
|
: nullptr;
|
|
if (composition) {
|
|
// When there is a composition, TextComposition should guarantee that the
|
|
// selection event will be handled in same target as composition events.
|
|
composition->HandleSelectionEvent(aSelectionEvent);
|
|
} else {
|
|
// When there is no composition, the selection event should be handled
|
|
// in the aPresContext or browserParent.
|
|
TextComposition::HandleSelectionEvent(aPresContext, browserParent,
|
|
aSelectionEvent);
|
|
}
|
|
}
|
|
|
|
// static
|
|
void IMEStateManager::OnCompositionEventDiscarded(
|
|
WidgetCompositionEvent* aCompositionEvent) {
|
|
// Note that this method is never called for synthesized events for emulating
|
|
// commit or cancel composition.
|
|
|
|
MOZ_LOG(
|
|
sISMLog, LogLevel::Info,
|
|
("OnCompositionEventDiscarded(aCompositionEvent={ "
|
|
"mMessage=%s, mNativeIMEContext={ mRawNativeIMEContext=0x%" PRIXPTR ", "
|
|
"mOriginProcessID=0x%" PRIX64 " }, mWidget(0x%p)={ "
|
|
"GetNativeIMEContext()={ mRawNativeIMEContext=0x%" PRIXPTR ", "
|
|
"mOriginProcessID=0x%" PRIX64 " }, Destroyed()=%s }, "
|
|
"mFlags={ mIsTrusted=%s } })",
|
|
ToChar(aCompositionEvent->mMessage),
|
|
aCompositionEvent->mNativeIMEContext.mRawNativeIMEContext,
|
|
aCompositionEvent->mNativeIMEContext.mOriginProcessID,
|
|
aCompositionEvent->mWidget.get(),
|
|
aCompositionEvent->mWidget->GetNativeIMEContext().mRawNativeIMEContext,
|
|
aCompositionEvent->mWidget->GetNativeIMEContext().mOriginProcessID,
|
|
GetBoolName(aCompositionEvent->mWidget->Destroyed()),
|
|
GetBoolName(aCompositionEvent->mFlags.mIsTrusted)));
|
|
|
|
if (!aCompositionEvent->IsTrusted()) {
|
|
return;
|
|
}
|
|
|
|
// Ignore compositionstart for now because sTextCompositions may not have
|
|
// been created yet.
|
|
if (aCompositionEvent->mMessage == eCompositionStart) {
|
|
return;
|
|
}
|
|
|
|
RefPtr<TextComposition> composition =
|
|
sTextCompositions->GetCompositionFor(aCompositionEvent->mWidget);
|
|
if (!composition) {
|
|
// If the PresShell has been being destroyed during composition,
|
|
// a TextComposition instance for the composition was already removed from
|
|
// the array and destroyed in OnDestroyPresContext(). Therefore, we may
|
|
// fail to retrieve a TextComposition instance here.
|
|
MOZ_LOG(sISMLog, LogLevel::Info,
|
|
(" OnCompositionEventDiscarded(), "
|
|
"TextComposition instance for the widget has already gone"));
|
|
return;
|
|
}
|
|
composition->OnCompositionEventDiscarded(aCompositionEvent);
|
|
}
|
|
|
|
// static
|
|
nsresult IMEStateManager::NotifyIME(IMEMessage aMessage, nsIWidget* aWidget,
|
|
BrowserParent* aBrowserParent) {
|
|
return IMEStateManager::NotifyIME(IMENotification(aMessage), aWidget,
|
|
aBrowserParent);
|
|
}
|
|
|
|
// static
|
|
nsresult IMEStateManager::NotifyIME(const IMENotification& aNotification,
|
|
nsIWidget* aWidget,
|
|
BrowserParent* aBrowserParent) {
|
|
MOZ_LOG(sISMLog, LogLevel::Info,
|
|
("NotifyIME(aNotification={ mMessage=%s }, "
|
|
"aWidget=0x%p, aBrowserParent=0x%p), sFocusedIMEWidget=0x%p, "
|
|
"BrowserParent::GetFocused()=0x%p, sFocusedIMEBrowserParent=0x%p, "
|
|
"aBrowserParent == BrowserParent::GetFocused()=%s, "
|
|
"aBrowserParent == sFocusedIMEBrowserParent=%s, "
|
|
"CanSendNotificationToWidget()=%s",
|
|
ToChar(aNotification.mMessage), aWidget, aBrowserParent,
|
|
sFocusedIMEWidget, BrowserParent::GetFocused(),
|
|
sFocusedIMEBrowserParent.get(),
|
|
GetBoolName(aBrowserParent == BrowserParent::GetFocused()),
|
|
GetBoolName(aBrowserParent == sFocusedIMEBrowserParent),
|
|
GetBoolName(CanSendNotificationToWidget())));
|
|
|
|
if (NS_WARN_IF(!aWidget)) {
|
|
MOZ_LOG(sISMLog, LogLevel::Error,
|
|
(" NotifyIME(), FAILED due to no widget"));
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
switch (aNotification.mMessage) {
|
|
case NOTIFY_IME_OF_FOCUS: {
|
|
MOZ_ASSERT(CanSendNotificationToWidget());
|
|
|
|
// If focus notification comes from a remote browser which already lost
|
|
// focus, we shouldn't accept the focus notification. Then, following
|
|
// notifications from the process will be ignored.
|
|
if (aBrowserParent != BrowserParent::GetFocused()) {
|
|
MOZ_LOG(sISMLog, LogLevel::Warning,
|
|
(" NotifyIME(), WARNING, the received focus notification is "
|
|
"ignored, because its associated BrowserParent did not match"
|
|
"the focused BrowserParent."));
|
|
return NS_OK;
|
|
}
|
|
// If IME focus is already set, first blur the currently-focused
|
|
// IME widget
|
|
if (sFocusedIMEWidget) {
|
|
// XXX Why don't we first request the previously-focused IME
|
|
// widget to commit the composition?
|
|
MOZ_ASSERT(
|
|
sFocusedIMEBrowserParent || aBrowserParent,
|
|
"This case shouldn't be caused by focus move in this process");
|
|
MOZ_LOG(sISMLog, LogLevel::Warning,
|
|
(" NotifyIME(), WARNING, received focus notification with ")
|
|
"non-null sFocusedIMEWidget. How come "
|
|
"OnFocusMovedBetweenBrowsers did not blur it already?");
|
|
nsCOMPtr<nsIWidget> focusedIMEWidget(sFocusedIMEWidget);
|
|
sFocusedIMEWidget = nullptr;
|
|
sFocusedIMEBrowserParent = nullptr;
|
|
focusedIMEWidget->NotifyIME(IMENotification(NOTIFY_IME_OF_BLUR));
|
|
}
|
|
#ifdef DEBUG
|
|
if (aBrowserParent) {
|
|
nsCOMPtr<nsIWidget> browserParentWidget =
|
|
aBrowserParent->GetTextInputHandlingWidget();
|
|
MOZ_ASSERT(browserParentWidget == aWidget);
|
|
} else {
|
|
MOZ_ASSERT(sFocusedPresContext);
|
|
MOZ_ASSERT_IF(
|
|
sFocusedPresContext->GetTextInputHandlingWidget(),
|
|
sFocusedPresContext->GetTextInputHandlingWidget() == aWidget);
|
|
}
|
|
#endif
|
|
sFocusedIMEBrowserParent = aBrowserParent;
|
|
sFocusedIMEWidget = aWidget;
|
|
nsCOMPtr<nsIWidget> widget(aWidget);
|
|
MOZ_LOG(
|
|
sISMLog, LogLevel::Info,
|
|
(" NotifyIME(), about to call widget->NotifyIME() for IME focus"));
|
|
return widget->NotifyIME(aNotification);
|
|
}
|
|
case NOTIFY_IME_OF_BLUR: {
|
|
if (aBrowserParent != sFocusedIMEBrowserParent) {
|
|
MOZ_LOG(sISMLog, LogLevel::Warning,
|
|
(" NotifyIME(), WARNING, the received blur notification is "
|
|
"ignored "
|
|
"because it's not from current focused IME browser"));
|
|
return NS_OK;
|
|
}
|
|
if (!sFocusedIMEWidget) {
|
|
MOZ_LOG(
|
|
sISMLog, LogLevel::Error,
|
|
(" NotifyIME(), WARNING, received blur notification but there is "
|
|
"no focused IME widget"));
|
|
return NS_OK;
|
|
}
|
|
if (NS_WARN_IF(sFocusedIMEWidget != aWidget)) {
|
|
MOZ_LOG(sISMLog, LogLevel::Warning,
|
|
(" NotifyIME(), WARNING, the received blur notification is "
|
|
"ignored "
|
|
"because it's not for current focused IME widget"));
|
|
return NS_OK;
|
|
}
|
|
nsCOMPtr<nsIWidget> focusedIMEWidget(sFocusedIMEWidget);
|
|
sFocusedIMEWidget = nullptr;
|
|
sFocusedIMEBrowserParent = nullptr;
|
|
return CanSendNotificationToWidget()
|
|
? focusedIMEWidget->NotifyIME(
|
|
IMENotification(NOTIFY_IME_OF_BLUR))
|
|
: NS_OK;
|
|
}
|
|
case NOTIFY_IME_OF_SELECTION_CHANGE:
|
|
case NOTIFY_IME_OF_TEXT_CHANGE:
|
|
case NOTIFY_IME_OF_POSITION_CHANGE:
|
|
case NOTIFY_IME_OF_MOUSE_BUTTON_EVENT:
|
|
case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED: {
|
|
if (aBrowserParent != sFocusedIMEBrowserParent) {
|
|
MOZ_LOG(
|
|
sISMLog, LogLevel::Warning,
|
|
(" NotifyIME(), WARNING, the received content change notification "
|
|
"is ignored because it's not from current focused IME browser"));
|
|
return NS_OK;
|
|
}
|
|
if (!sFocusedIMEWidget) {
|
|
MOZ_LOG(
|
|
sISMLog, LogLevel::Warning,
|
|
(" NotifyIME(), WARNING, the received content change notification "
|
|
"is ignored because there is no focused IME widget"));
|
|
return NS_OK;
|
|
}
|
|
if (NS_WARN_IF(sFocusedIMEWidget != aWidget)) {
|
|
MOZ_LOG(
|
|
sISMLog, LogLevel::Warning,
|
|
(" NotifyIME(), WARNING, the received content change notification "
|
|
"is ignored because it's not for current focused IME widget"));
|
|
return NS_OK;
|
|
}
|
|
if (!CanSendNotificationToWidget()) {
|
|
return NS_OK;
|
|
}
|
|
nsCOMPtr<nsIWidget> widget(aWidget);
|
|
return widget->NotifyIME(aNotification);
|
|
}
|
|
default:
|
|
// Other notifications should be sent only when there is composition.
|
|
// So, we need to handle the others below.
|
|
break;
|
|
}
|
|
|
|
if (!sTextCompositions) {
|
|
MOZ_LOG(sISMLog, LogLevel::Info,
|
|
(" NotifyIME(), the request to IME is ignored because "
|
|
"there have been no compositions yet"));
|
|
return NS_OK;
|
|
}
|
|
|
|
RefPtr<TextComposition> composition =
|
|
sTextCompositions->GetCompositionFor(aWidget);
|
|
if (!composition) {
|
|
MOZ_LOG(sISMLog, LogLevel::Info,
|
|
(" NotifyIME(), the request to IME is ignored because "
|
|
"there is no active composition"));
|
|
return NS_OK;
|
|
}
|
|
|
|
if (aBrowserParent != composition->GetBrowserParent()) {
|
|
MOZ_LOG(
|
|
sISMLog, LogLevel::Warning,
|
|
(" NotifyIME(), WARNING, the request to IME is ignored because "
|
|
"it does not come from the remote browser which has the composition "
|
|
"on aWidget"));
|
|
return NS_OK;
|
|
}
|
|
|
|
switch (aNotification.mMessage) {
|
|
case REQUEST_TO_COMMIT_COMPOSITION:
|
|
return composition->RequestToCommit(aWidget, false);
|
|
case REQUEST_TO_CANCEL_COMPOSITION:
|
|
return composition->RequestToCommit(aWidget, true);
|
|
default:
|
|
MOZ_CRASH("Unsupported notification");
|
|
}
|
|
MOZ_CRASH(
|
|
"Failed to handle the notification for non-synthesized composition");
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
// static
|
|
nsresult IMEStateManager::NotifyIME(IMEMessage aMessage,
|
|
nsPresContext* aPresContext,
|
|
BrowserParent* aBrowserParent) {
|
|
MOZ_LOG(sISMLog, LogLevel::Info,
|
|
("NotifyIME(aMessage=%s, aPresContext=0x%p, aBrowserParent=0x%p)",
|
|
ToChar(aMessage), aPresContext, aBrowserParent));
|
|
// This assertion is just for clarify which message may be set, so feel free
|
|
// to add other messages if you want.
|
|
MOZ_ASSERT(aMessage == REQUEST_TO_CANCEL_COMPOSITION ||
|
|
aMessage == REQUEST_TO_COMMIT_COMPOSITION ||
|
|
aMessage == NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED);
|
|
// However, these messages require additional information. Therefore, this
|
|
// overload shouldn't be used for them.
|
|
MOZ_ASSERT(aMessage != NOTIFY_IME_OF_FOCUS &&
|
|
aMessage != NOTIFY_IME_OF_BLUR &&
|
|
aMessage != NOTIFY_IME_OF_TEXT_CHANGE &&
|
|
aMessage != NOTIFY_IME_OF_SELECTION_CHANGE &&
|
|
aMessage != NOTIFY_IME_OF_MOUSE_BUTTON_EVENT);
|
|
|
|
if (NS_WARN_IF(!CanHandleWith(aPresContext))) {
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
nsCOMPtr<nsIWidget> widget =
|
|
aPresContext == sFocusedPresContext && sTextInputHandlingWidget
|
|
? sTextInputHandlingWidget
|
|
: aPresContext->GetTextInputHandlingWidget();
|
|
if (NS_WARN_IF(!widget)) {
|
|
MOZ_LOG(sISMLog, LogLevel::Error,
|
|
(" NotifyIME(), FAILED due to no widget for the nsPresContext"));
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
return NotifyIME(aMessage, widget, aBrowserParent);
|
|
}
|
|
|
|
// static
|
|
bool IMEStateManager::IsEditable(nsINode* node) {
|
|
if (node->IsEditable()) {
|
|
return true;
|
|
}
|
|
// |node| might be readwrite (for example, a text control)
|
|
if (node->IsElement() &&
|
|
node->AsElement()->State().HasState(ElementState::READWRITE)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// static
|
|
nsINode* IMEStateManager::GetRootEditableNode(const nsPresContext& aPresContext,
|
|
const Element* aElement) {
|
|
if (aElement) {
|
|
// If the focused content is in design mode, return is composed document
|
|
// because aElement may be in UA widget shadow tree.
|
|
if (aElement->IsInDesignMode()) {
|
|
return aElement->GetComposedDoc();
|
|
}
|
|
|
|
nsINode* candidateRootNode = const_cast<Element*>(aElement);
|
|
for (nsINode* node = candidateRootNode; node && IsEditable(node);
|
|
node = node->GetParentNode()) {
|
|
// If the node has independent selection like <input type="text"> or
|
|
// <textarea>, the node should be the root editable node for aElement.
|
|
// FYI: <select> element also has independent selection but IsEditable()
|
|
// returns false.
|
|
// XXX: If somebody adds new editable element which has independent
|
|
// selection but doesn't own editor, we'll need more checks here.
|
|
// XXX: If aElement is not in native anonymous subtree, checking
|
|
// independent selection must be wrong, see bug 1731005.
|
|
if (node->IsContent() && node->AsContent()->HasIndependentSelection()) {
|
|
return node;
|
|
}
|
|
candidateRootNode = node;
|
|
}
|
|
return candidateRootNode;
|
|
}
|
|
|
|
return aPresContext.Document() && aPresContext.Document()->IsInDesignMode()
|
|
? aPresContext.Document()
|
|
: nullptr;
|
|
}
|
|
|
|
// static
|
|
bool IMEStateManager::IsIMEObserverNeeded(const IMEState& aState) {
|
|
return aState.IsEditable();
|
|
}
|
|
|
|
// static
|
|
void IMEStateManager::DestroyIMEContentObserver() {
|
|
if (!sActiveIMEContentObserver) {
|
|
MOZ_LOG(sISMLog, LogLevel::Verbose,
|
|
("DestroyIMEContentObserver() does nothing"));
|
|
return;
|
|
}
|
|
|
|
MOZ_LOG(sISMLog, LogLevel::Info,
|
|
("DestroyIMEContentObserver(), destroying "
|
|
"the active IMEContentObserver..."));
|
|
RefPtr<IMEContentObserver> tsm = sActiveIMEContentObserver.get();
|
|
sActiveIMEContentObserver = nullptr;
|
|
tsm->Destroy();
|
|
}
|
|
|
|
// static
|
|
void IMEStateManager::CreateIMEContentObserver(EditorBase& aEditorBase,
|
|
Element* aFocusedElement) {
|
|
MOZ_ASSERT(!sActiveIMEContentObserver);
|
|
MOZ_ASSERT(sTextInputHandlingWidget);
|
|
MOZ_ASSERT(sFocusedPresContext);
|
|
MOZ_ASSERT(IsIMEObserverNeeded(
|
|
sTextInputHandlingWidget->GetInputContext().mIMEState));
|
|
|
|
MOZ_LOG(sISMLog, LogLevel::Info,
|
|
("CreateIMEContentObserver(aEditorBase=0x%p, aFocusedElement=0x%p), "
|
|
"sFocusedPresContext=0x%p, sFocusedElement=0x%p, "
|
|
"sTextInputHandlingWidget=0x%p (available: %s), "
|
|
"sActiveIMEContentObserver=0x%p, "
|
|
"sActiveIMEContentObserver->IsObserving(sFocusedPresContext, "
|
|
"sFocusedElement)=%s",
|
|
&aEditorBase, aFocusedElement, sFocusedPresContext.get(),
|
|
sFocusedElement.get(), sTextInputHandlingWidget,
|
|
GetBoolName(sTextInputHandlingWidget &&
|
|
!sTextInputHandlingWidget->Destroyed()),
|
|
sActiveIMEContentObserver.get(),
|
|
GetBoolName(sActiveIMEContentObserver && sFocusedPresContext &&
|
|
sActiveIMEContentObserver->IsObserving(
|
|
*sFocusedPresContext, sFocusedElement))));
|
|
|
|
if (NS_WARN_IF(sTextInputHandlingWidget->Destroyed())) {
|
|
MOZ_LOG(sISMLog, LogLevel::Error,
|
|
(" CreateIMEContentObserver(), FAILED due to "
|
|
"the widget for the nsPresContext has gone"));
|
|
return;
|
|
}
|
|
|
|
const OwningNonNull<nsIWidget> textInputHandlingWidget =
|
|
*sTextInputHandlingWidget;
|
|
MOZ_ASSERT_IF(sFocusedPresContext->GetTextInputHandlingWidget(),
|
|
sFocusedPresContext->GetTextInputHandlingWidget() ==
|
|
textInputHandlingWidget.get());
|
|
|
|
MOZ_LOG(sISMLog, LogLevel::Debug,
|
|
(" CreateIMEContentObserver() is creating an "
|
|
"IMEContentObserver instance..."));
|
|
sActiveIMEContentObserver = new IMEContentObserver();
|
|
|
|
// IMEContentObserver::Init() might create another IMEContentObserver
|
|
// instance. So, sActiveIMEContentObserver would be replaced with new one.
|
|
// We should hold the current instance here.
|
|
OwningNonNull<IMEContentObserver> activeIMEContentObserver =
|
|
*sActiveIMEContentObserver;
|
|
OwningNonNull<nsPresContext> focusedPresContext = *sFocusedPresContext;
|
|
RefPtr<Element> focusedElement = aFocusedElement;
|
|
activeIMEContentObserver->Init(textInputHandlingWidget, focusedPresContext,
|
|
focusedElement, aEditorBase);
|
|
}
|
|
|
|
// static
|
|
nsresult IMEStateManager::GetFocusSelectionAndRootElement(
|
|
Selection** aSelection, Element** aRootElement) {
|
|
if (!sActiveIMEContentObserver) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
return sActiveIMEContentObserver->GetSelectionAndRoot(aSelection,
|
|
aRootElement);
|
|
}
|
|
|
|
// static
|
|
TextComposition* IMEStateManager::GetTextCompositionFor(nsIWidget* aWidget) {
|
|
return sTextCompositions ? sTextCompositions->GetCompositionFor(aWidget)
|
|
: nullptr;
|
|
}
|
|
|
|
// static
|
|
TextComposition* IMEStateManager::GetTextCompositionFor(
|
|
const WidgetCompositionEvent* aCompositionEvent) {
|
|
return sTextCompositions
|
|
? sTextCompositions->GetCompositionFor(aCompositionEvent)
|
|
: nullptr;
|
|
}
|
|
|
|
// static
|
|
TextComposition* IMEStateManager::GetTextCompositionFor(
|
|
nsPresContext* aPresContext) {
|
|
return sTextCompositions ? sTextCompositions->GetCompositionFor(aPresContext)
|
|
: nullptr;
|
|
}
|
|
|
|
} // namespace mozilla
|