/* -*- 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 "". * * When a method quits due to unexpected situation, log the reason with * LogLevel::Error. In this case, the log should start with * "(), 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 " (),". */ LazyLogModule sISMLog("IMEStateManager"); static const char* GetBoolName(bool aBool) { return aBool ? "true" : "false"; } StaticRefPtr IMEStateManager::sFocusedElement; StaticRefPtr IMEStateManager::sFocusedPresContext; nsIWidget* IMEStateManager::sTextInputHandlingWidget = nullptr; nsIWidget* IMEStateManager::sFocusedIMEWidget = nullptr; StaticRefPtr IMEStateManager::sFocusedIMEBrowserParent; nsIWidget* IMEStateManager::sActiveInputContextWidget = nullptr; StaticRefPtr 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::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 mFocusedPresContext; const RefPtr mFocusedElement; const bool mInstallMenuKeyboardListener; }; StaticRefPtr 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 oldWidget = sTextInputHandlingWidget; // In the chrome-process case, we'll get sTextInputHandlingWidget from a // PresShell later. sTextInputHandlingWidget = aFocus ? nsCOMPtr(aFocus->GetTextInputHandlingWidget()).get() : nullptr; if (oldWidget && sTextCompositions) { RefPtr 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 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 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 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 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 = 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 focusedElement = sFocusedElement; RefPtr focusedPresContext = sFocusedPresContext; RefPtr browserParentBlurred = sPendingFocusedBrowserSwitchingData.ref().mBrowserParentBlurred; RefPtr 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 oldWidget = sTextInputHandlingWidget; const nsCOMPtr 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 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 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 focusedPresContext = sFocusedPresContext; RefPtr 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 composition = GetTextCompositionFor(aFocusedPresContextAtRequested); if (!composition) { return; } if (nsCOMPtr 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 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 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 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 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 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 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(aEditorBase.GetPresShell()); if (NS_WARN_IF(!presShell)) { MOZ_LOG(sISMLog, LogLevel::Error, (" UpdateIMEState(), FAILED due to " "editor doesn't have PresShell")); return; } const RefPtr 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 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 // 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 contentObserver = *sActiveIMEContentObserver; OwningNonNull 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 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 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 fm = nsFocusManager::GetFocusManager(); if (MOZ_UNLIKELY(!fm)) { return false; } nsCOMPtr nextContent; const RefPtr inputContent = const_cast(aInputContent); const nsCOMPtr 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 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 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 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 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 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 = 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 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 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 focusedIMEWidget(sFocusedIMEWidget); sFocusedIMEWidget = nullptr; sFocusedIMEBrowserParent = nullptr; focusedIMEWidget->NotifyIME(IMENotification(NOTIFY_IME_OF_BLUR)); } #ifdef DEBUG if (aBrowserParent) { nsCOMPtr 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 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 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 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 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 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(aElement); for (nsINode* node = candidateRootNode; node && IsEditable(node); node = node->GetParentNode()) { // If the node has independent selection like or //