diff -r 000000000000 -r 6474c204b198 dom/events/IMEStateManager.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dom/events/IMEStateManager.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,778 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 sw=2 et 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 "mozilla/IMEStateManager.h" + +#include "mozilla/Attributes.h" +#include "mozilla/EventStates.h" +#include "mozilla/Preferences.h" +#include "mozilla/Services.h" +#include "mozilla/TextComposition.h" +#include "mozilla/TextEvents.h" +#include "mozilla/dom/HTMLFormElement.h" + +#include "HTMLInputElement.h" +#include "IMEContentObserver.h" + +#include "nsCOMPtr.h" +#include "nsContentUtils.h" +#include "nsIContent.h" +#include "nsIDocument.h" +#include "nsIDOMMouseEvent.h" +#include "nsIForm.h" +#include "nsIFormControl.h" +#include "nsINode.h" +#include "nsIObserverService.h" +#include "nsIPresShell.h" +#include "nsISelection.h" +#include "nsISupports.h" +#include "nsPresContext.h" + +namespace mozilla { + +using namespace dom; +using namespace widget; + +nsIContent* IMEStateManager::sContent = nullptr; +nsPresContext* IMEStateManager::sPresContext = nullptr; +bool IMEStateManager::sInstalledMenuKeyboardListener = false; +bool IMEStateManager::sIsTestingIME = false; + +// sActiveIMEContentObserver points to the currently active IMEContentObserver. +// sActiveIMEContentObserver is null if there is no focused editor. +IMEContentObserver* IMEStateManager::sActiveIMEContentObserver = nullptr; +TextCompositionArray* IMEStateManager::sTextCompositions = nullptr; + +void +IMEStateManager::Shutdown() +{ + MOZ_ASSERT(!sTextCompositions || !sTextCompositions->Length()); + delete sTextCompositions; + sTextCompositions = nullptr; +} + +nsresult +IMEStateManager::OnDestroyPresContext(nsPresContext* aPresContext) +{ + NS_ENSURE_ARG_POINTER(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) { + // there should be only one composition per presContext object. + sTextCompositions->ElementAt(i)->Destroy(); + sTextCompositions->RemoveElementAt(i); + MOZ_ASSERT(sTextCompositions->IndexOf(aPresContext) == + TextCompositionArray::NoIndex); + } + } + + if (aPresContext != sPresContext) { + return NS_OK; + } + + DestroyTextStateManager(); + + nsCOMPtr widget = sPresContext->GetRootWidget(); + if (widget) { + IMEState newState = GetNewIMEState(sPresContext, nullptr); + InputContextAction action(InputContextAction::CAUSE_UNKNOWN, + InputContextAction::LOST_FOCUS); + SetIMEState(newState, nullptr, widget, action); + } + NS_IF_RELEASE(sContent); + sPresContext = nullptr; + return NS_OK; +} + +nsresult +IMEStateManager::OnRemoveContent(nsPresContext* aPresContext, + nsIContent* aContent) +{ + NS_ENSURE_ARG_POINTER(aPresContext); + + // First, if there is a composition in the aContent, clean up it. + if (sTextCompositions) { + nsRefPtr compositionInContent = + sTextCompositions->GetCompositionInContent(aPresContext, aContent); + + if (compositionInContent) { + // 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()). + nsCOMPtr widget = aPresContext->GetRootWidget(); + if (widget) { + nsresult rv = + compositionInContent->NotifyIME(REQUEST_TO_CANCEL_COMPOSITION); + if (NS_FAILED(rv)) { + compositionInContent->NotifyIME(REQUEST_TO_COMMIT_COMPOSITION); + } + // By calling the APIs, the composition may have been finished normally. + compositionInContent = + sTextCompositions->GetCompositionFor( + compositionInContent->GetPresContext(), + compositionInContent->GetEventTargetNode()); + } + } + + // If the compositionInContent is still available, we should finish the + // composition just on the content forcibly. + if (compositionInContent) { + compositionInContent->SynthesizeCommit(true); + } + } + + if (!sPresContext || !sContent || + !nsContentUtils::ContentIsDescendantOf(sContent, aContent)) { + return NS_OK; + } + + DestroyTextStateManager(); + + // Current IME transaction should commit + nsCOMPtr widget = sPresContext->GetRootWidget(); + if (widget) { + IMEState newState = GetNewIMEState(sPresContext, nullptr); + InputContextAction action(InputContextAction::CAUSE_UNKNOWN, + InputContextAction::LOST_FOCUS); + SetIMEState(newState, nullptr, widget, action); + } + + NS_IF_RELEASE(sContent); + sPresContext = nullptr; + + return NS_OK; +} + +nsresult +IMEStateManager::OnChangeFocus(nsPresContext* aPresContext, + nsIContent* aContent, + InputContextAction::Cause aCause) +{ + InputContextAction action(aCause); + return OnChangeFocusInternal(aPresContext, aContent, action); +} + +nsresult +IMEStateManager::OnChangeFocusInternal(nsPresContext* aPresContext, + nsIContent* aContent, + InputContextAction aAction) +{ + bool focusActuallyChanging = + (sContent != aContent || sPresContext != aPresContext); + + nsCOMPtr oldWidget = + sPresContext ? sPresContext->GetRootWidget() : nullptr; + if (oldWidget && focusActuallyChanging) { + // If we're deactivating, we shouldn't commit composition forcibly because + // the user may want to continue the composition. + if (aPresContext) { + NotifyIME(REQUEST_TO_COMMIT_COMPOSITION, oldWidget); + } + } + + if (sActiveIMEContentObserver && + (aPresContext || !sActiveIMEContentObserver->KeepAliveDuringDeactive()) && + !sActiveIMEContentObserver->IsManaging(aPresContext, aContent)) { + DestroyTextStateManager(); + } + + if (!aPresContext) { + return NS_OK; + } + + nsCOMPtr widget = + (sPresContext == aPresContext) ? oldWidget.get() : + aPresContext->GetRootWidget(); + if (!widget) { + return NS_OK; + } + + IMEState newState = GetNewIMEState(aPresContext, aContent); + if (!focusActuallyChanging) { + // actual focus isn't changing, but if IME enabled state is changing, + // we should do it. + InputContext context = widget->GetInputContext(); + if (context.mIMEState.mEnabled == newState.mEnabled) { + // the enabled state isn't 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 (sPresContext && oldWidget && !focusActuallyChanging) { + NotifyIME(REQUEST_TO_COMMIT_COMPOSITION, oldWidget); + } + } else if (aAction.mFocusChange == InputContextAction::FOCUS_NOT_CHANGED) { + // If aContent isn't null or aContent is null but editable, somebody gets + // focus. + bool gotFocus = aContent || (newState.mEnabled == IMEState::ENABLED); + aAction.mFocusChange = + gotFocus ? InputContextAction::GOT_FOCUS : InputContextAction::LOST_FOCUS; + } + + // Update IME state for new focus widget + SetIMEState(newState, aContent, widget, aAction); + + sPresContext = aPresContext; + if (sContent != aContent) { + NS_IF_RELEASE(sContent); + NS_IF_ADDREF(sContent = aContent); + } + + // Don't call CreateIMEContentObserver() here, it should be called from + // focus event handler of editor. + + return NS_OK; +} + +void +IMEStateManager::OnInstalledMenuKeyboardListener(bool aInstalling) +{ + sInstalledMenuKeyboardListener = aInstalling; + + InputContextAction action(InputContextAction::CAUSE_UNKNOWN, + aInstalling ? InputContextAction::MENU_GOT_PSEUDO_FOCUS : + InputContextAction::MENU_LOST_PSEUDO_FOCUS); + OnChangeFocusInternal(sPresContext, sContent, action); +} + +void +IMEStateManager::OnClickInEditor(nsPresContext* aPresContext, + nsIContent* aContent, + nsIDOMMouseEvent* aMouseEvent) +{ + if (sPresContext != aPresContext || sContent != aContent) { + return; + } + + nsCOMPtr widget = aPresContext->GetRootWidget(); + NS_ENSURE_TRUE_VOID(widget); + + bool isTrusted; + nsresult rv = aMouseEvent->GetIsTrusted(&isTrusted); + NS_ENSURE_SUCCESS_VOID(rv); + if (!isTrusted) { + return; // ignore untrusted event. + } + + int16_t button; + rv = aMouseEvent->GetButton(&button); + NS_ENSURE_SUCCESS_VOID(rv); + if (button != 0) { + return; // not a left click event. + } + + int32_t clickCount; + rv = aMouseEvent->GetDetail(&clickCount); + NS_ENSURE_SUCCESS_VOID(rv); + if (clickCount != 1) { + return; // should notify only first click event. + } + + InputContextAction action(InputContextAction::CAUSE_MOUSE, + InputContextAction::FOCUS_NOT_CHANGED); + IMEState newState = GetNewIMEState(aPresContext, aContent); + SetIMEState(newState, aContent, widget, action); +} + +void +IMEStateManager::OnFocusInEditor(nsPresContext* aPresContext, + nsIContent* aContent) +{ + if (sPresContext != aPresContext || sContent != aContent) { + return; + } + + // If the IMEContentObserver instance isn't managing the editor actually, + // we need to recreate the instance. + if (sActiveIMEContentObserver) { + if (sActiveIMEContentObserver->IsManaging(aPresContext, aContent)) { + return; + } + DestroyTextStateManager(); + } + + CreateIMEContentObserver(); +} + +void +IMEStateManager::UpdateIMEState(const IMEState& aNewIMEState, + nsIContent* aContent) +{ + if (!sPresContext) { + NS_WARNING("ISM doesn't know which editor has focus"); + return; + } + nsCOMPtr widget = sPresContext->GetRootWidget(); + if (!widget) { + NS_WARNING("focused widget is not found"); + return; + } + + // If the IMEContentObserver instance isn't managing the editor's current + // editable root content, the editor frame might be reframed. We should + // recreate the instance at that time. + bool createTextStateManager = + (!sActiveIMEContentObserver || + !sActiveIMEContentObserver->IsManaging(sPresContext, aContent)); + + bool updateIMEState = + (widget->GetInputContext().mIMEState.mEnabled != aNewIMEState.mEnabled); + + if (updateIMEState) { + // commit current composition before modifying IME state. + NotifyIME(REQUEST_TO_COMMIT_COMPOSITION, widget); + } + + if (createTextStateManager) { + DestroyTextStateManager(); + } + + if (updateIMEState) { + InputContextAction action(InputContextAction::CAUSE_UNKNOWN, + InputContextAction::FOCUS_NOT_CHANGED); + SetIMEState(aNewIMEState, aContent, widget, action); + } + + if (createTextStateManager) { + CreateIMEContentObserver(); + } +} + +IMEState +IMEStateManager::GetNewIMEState(nsPresContext* aPresContext, + nsIContent* aContent) +{ + // On Printing or Print Preview, we don't need IME. + if (aPresContext->Type() == nsPresContext::eContext_PrintPreview || + aPresContext->Type() == nsPresContext::eContext_Print) { + return IMEState(IMEState::DISABLED); + } + + if (sInstalledMenuKeyboardListener) { + return IMEState(IMEState::DISABLED); + } + + if (!aContent) { + // Even if there are no focused content, the focused document might be + // editable, such case is design mode. + nsIDocument* doc = aPresContext->Document(); + if (doc && doc->HasFlag(NODE_IS_EDITABLE)) { + return IMEState(IMEState::ENABLED); + } + return IMEState(IMEState::DISABLED); + } + + return aContent->GetDesiredIMEState(); +} + +// Helper class, used for IME enabled state change notification +class IMEEnabledStateChangedEvent : public nsRunnable { +public: + IMEEnabledStateChangedEvent(uint32_t aState) + : mState(aState) + { + } + + NS_IMETHOD Run() + { + nsCOMPtr observerService = + services::GetObserverService(); + if (observerService) { + nsAutoString state; + state.AppendInt(mState); + observerService->NotifyObservers(nullptr, "ime-enabled-state-changed", + state.get()); + } + return NS_OK; + } + +private: + uint32_t mState; +}; + +void +IMEStateManager::SetIMEState(const IMEState& aState, + nsIContent* aContent, + nsIWidget* aWidget, + InputContextAction aAction) +{ + NS_ENSURE_TRUE_VOID(aWidget); + + InputContext oldContext = aWidget->GetInputContext(); + + InputContext context; + context.mIMEState = aState; + + if (aContent && aContent->GetNameSpaceID() == kNameSpaceID_XHTML && + (aContent->Tag() == nsGkAtoms::input || + aContent->Tag() == nsGkAtoms::textarea)) { + if (aContent->Tag() != nsGkAtoms::textarea) { + // has an anonymous descendant + // that gets focus whenever anyone tries to focus the number control. We + // need to check if aContent is one of those anonymous text controls and, + // if so, use the number control instead: + nsIContent* content = aContent; + HTMLInputElement* inputElement = + HTMLInputElement::FromContentOrNull(aContent); + if (inputElement) { + HTMLInputElement* ownerNumberControl = + inputElement->GetOwnerNumberControl(); + if (ownerNumberControl) { + content = ownerNumberControl; // an + } + } + content->GetAttr(kNameSpaceID_None, nsGkAtoms::type, + context.mHTMLInputType); + } else { + context.mHTMLInputType.Assign(nsGkAtoms::textarea->GetUTF16String()); + } + + if (Preferences::GetBool("dom.forms.inputmode", false)) { + aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::inputmode, + context.mHTMLInputInputmode); + } + + aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::moz_action_hint, + context.mActionHint); + + // if we don't have an action hint and return won't submit the form use "next" + if (context.mActionHint.IsEmpty() && aContent->Tag() == nsGkAtoms::input) { + bool willSubmit = false; + nsCOMPtr control(do_QueryInterface(aContent)); + mozilla::dom::Element* formElement = control->GetFormElement(); + nsCOMPtr form; + if (control) { + // is this a form and does it have a default submit element? + if ((form = do_QueryInterface(formElement)) && + form->GetDefaultSubmitElement()) { + willSubmit = true; + // is this an html form and does it only have a single text input element? + } else if (formElement && formElement->Tag() == nsGkAtoms::form && + formElement->IsHTML() && + !static_cast(formElement)-> + ImplicitSubmissionIsDisabled()) { + willSubmit = true; + } + } + context.mActionHint.Assign( + willSubmit ? (control->GetType() == NS_FORM_INPUT_SEARCH ? + NS_LITERAL_STRING("search") : NS_LITERAL_STRING("go")) : + (formElement ? + NS_LITERAL_STRING("next") : EmptyString())); + } + } + + // XXX I think that we should use nsContentUtils::IsCallerChrome() instead + // of the process type. + if (aAction.mCause == InputContextAction::CAUSE_UNKNOWN && + XRE_GetProcessType() != GeckoProcessType_Content) { + aAction.mCause = InputContextAction::CAUSE_UNKNOWN_CHROME; + } + + aWidget->SetInputContext(context, aAction); + if (oldContext.mIMEState.mEnabled == context.mIMEState.mEnabled) { + return; + } + + nsContentUtils::AddScriptRunner( + new IMEEnabledStateChangedEvent(context.mIMEState.mEnabled)); +} + +void +IMEStateManager::EnsureTextCompositionArray() +{ + if (sTextCompositions) { + return; + } + sTextCompositions = new TextCompositionArray(); +} + +void +IMEStateManager::DispatchCompositionEvent(nsINode* aEventTargetNode, + nsPresContext* aPresContext, + WidgetEvent* aEvent, + nsEventStatus* aStatus, + EventDispatchingCallback* aCallBack) +{ + MOZ_ASSERT(aEvent->eventStructType == NS_COMPOSITION_EVENT || + aEvent->eventStructType == NS_TEXT_EVENT); + if (!aEvent->mFlags.mIsTrusted || aEvent->mFlags.mPropagationStopped) { + return; + } + + EnsureTextCompositionArray(); + + WidgetGUIEvent* GUIEvent = aEvent->AsGUIEvent(); + + nsRefPtr composition = + sTextCompositions->GetCompositionFor(GUIEvent->widget); + if (!composition) { + MOZ_ASSERT(GUIEvent->message == NS_COMPOSITION_START); + composition = new TextComposition(aPresContext, aEventTargetNode, GUIEvent); + sTextCompositions->AppendElement(composition); + } +#ifdef DEBUG + else { + MOZ_ASSERT(GUIEvent->message != NS_COMPOSITION_START); + } +#endif // #ifdef DEBUG + + // Dispatch the event on composing target. + composition->DispatchEvent(GUIEvent, aStatus, aCallBack); + + // WARNING: the |composition| might have been destroyed already. + + // Remove the ended composition from the array. + if (aEvent->message == NS_COMPOSITION_END) { + TextCompositionArray::index_type i = + sTextCompositions->IndexOf(GUIEvent->widget); + if (i != TextCompositionArray::NoIndex) { + sTextCompositions->ElementAt(i)->Destroy(); + sTextCompositions->RemoveElementAt(i); + } + } +} + +// static +nsresult +IMEStateManager::NotifyIME(IMEMessage aMessage, + nsIWidget* aWidget) +{ + NS_ENSURE_TRUE(aWidget, NS_ERROR_INVALID_ARG); + + nsRefPtr composition; + if (sTextCompositions) { + composition = sTextCompositions->GetCompositionFor(aWidget); + } + if (!composition || !composition->IsSynthesizedForTests()) { + switch (aMessage) { + case NOTIFY_IME_OF_CURSOR_POS_CHANGED: + return aWidget->NotifyIME(IMENotification(aMessage)); + case REQUEST_TO_COMMIT_COMPOSITION: + case REQUEST_TO_CANCEL_COMPOSITION: + case NOTIFY_IME_OF_COMPOSITION_UPDATE: + return composition ? + aWidget->NotifyIME(IMENotification(aMessage)) : NS_OK; + default: + MOZ_CRASH("Unsupported notification"); + } + MOZ_CRASH( + "Failed to handle the notification for non-synthesized composition"); + } + + // If the composition is synthesized events for automated tests, we should + // dispatch composition events for emulating the native composition behavior. + // NOTE: The dispatched events are discarded if it's not safe to run script. + switch (aMessage) { + case REQUEST_TO_COMMIT_COMPOSITION: { + nsCOMPtr widget(aWidget); + nsEventStatus status = nsEventStatus_eIgnore; + if (!composition->LastData().IsEmpty()) { + WidgetTextEvent textEvent(true, NS_TEXT_TEXT, widget); + textEvent.theText = composition->LastData(); + textEvent.mFlags.mIsSynthesizedForTests = true; + widget->DispatchEvent(&textEvent, status); + if (widget->Destroyed()) { + return NS_OK; + } + } + + status = nsEventStatus_eIgnore; + WidgetCompositionEvent endEvent(true, NS_COMPOSITION_END, widget); + endEvent.data = composition->LastData(); + endEvent.mFlags.mIsSynthesizedForTests = true; + widget->DispatchEvent(&endEvent, status); + + return NS_OK; + } + case REQUEST_TO_CANCEL_COMPOSITION: { + nsCOMPtr widget(aWidget); + nsEventStatus status = nsEventStatus_eIgnore; + if (!composition->LastData().IsEmpty()) { + WidgetCompositionEvent updateEvent(true, NS_COMPOSITION_UPDATE, widget); + updateEvent.data = composition->LastData(); + updateEvent.mFlags.mIsSynthesizedForTests = true; + widget->DispatchEvent(&updateEvent, status); + if (widget->Destroyed()) { + return NS_OK; + } + + status = nsEventStatus_eIgnore; + WidgetTextEvent textEvent(true, NS_TEXT_TEXT, widget); + textEvent.theText = composition->LastData(); + textEvent.mFlags.mIsSynthesizedForTests = true; + widget->DispatchEvent(&textEvent, status); + if (widget->Destroyed()) { + return NS_OK; + } + } + + status = nsEventStatus_eIgnore; + WidgetCompositionEvent endEvent(true, NS_COMPOSITION_END, widget); + endEvent.data = composition->LastData(); + endEvent.mFlags.mIsSynthesizedForTests = true; + widget->DispatchEvent(&endEvent, status); + + return NS_OK; + } + default: + return NS_OK; + } +} + +// static +nsresult +IMEStateManager::NotifyIME(IMEMessage aMessage, + nsPresContext* aPresContext) +{ + NS_ENSURE_TRUE(aPresContext, NS_ERROR_INVALID_ARG); + + nsIWidget* widget = aPresContext->GetRootWidget(); + if (!widget) { + return NS_ERROR_NOT_AVAILABLE; + } + return NotifyIME(aMessage, widget); +} + +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(NS_EVENT_STATE_MOZ_READWRITE)) { + return true; + } + return false; +} + +nsINode* +IMEStateManager::GetRootEditableNode(nsPresContext* aPresContext, + nsIContent* aContent) +{ + if (aContent) { + nsINode* root = nullptr; + nsINode* node = aContent; + while (node && IsEditable(node)) { + root = node; + node = node->GetParentNode(); + } + return root; + } + if (aPresContext) { + nsIDocument* document = aPresContext->Document(); + if (document && document->IsEditable()) { + return document; + } + } + return nullptr; +} + +bool +IMEStateManager::IsEditableIMEState(nsIWidget* aWidget) +{ + switch (aWidget->GetInputContext().mIMEState.mEnabled) { + case IMEState::ENABLED: + case IMEState::PASSWORD: + return true; + case IMEState::PLUGIN: + case IMEState::DISABLED: + return false; + default: + MOZ_CRASH("Unknown IME enable state"); + } +} + +void +IMEStateManager::DestroyTextStateManager() +{ + if (!sActiveIMEContentObserver) { + return; + } + + nsRefPtr tsm; + tsm.swap(sActiveIMEContentObserver); + tsm->Destroy(); +} + +void +IMEStateManager::CreateIMEContentObserver() +{ + if (sActiveIMEContentObserver) { + NS_WARNING("text state observer has been there already"); + MOZ_ASSERT(sActiveIMEContentObserver->IsManaging(sPresContext, sContent)); + return; + } + + nsCOMPtr widget = sPresContext->GetRootWidget(); + if (!widget) { + return; // Sometimes, there are no widgets. + } + + // If it's not text ediable, we don't need to create IMEContentObserver. + if (!IsEditableIMEState(widget)) { + return; + } + + static bool sInitializeIsTestingIME = true; + if (sInitializeIsTestingIME) { + Preferences::AddBoolVarCache(&sIsTestingIME, "test.IME", false); + sInitializeIsTestingIME = false; + } + + sActiveIMEContentObserver = new IMEContentObserver(); + NS_ADDREF(sActiveIMEContentObserver); + + // IMEContentObserver::Init() might create another IMEContentObserver + // instance. So, sActiveIMEContentObserver would be replaced with new one. + // We should hold the current instance here. + nsRefPtr kungFuDeathGrip(sActiveIMEContentObserver); + sActiveIMEContentObserver->Init(widget, sPresContext, sContent); +} + +nsresult +IMEStateManager::GetFocusSelectionAndRoot(nsISelection** aSelection, + nsIContent** aRootContent) +{ + if (!sActiveIMEContentObserver) { + return NS_ERROR_NOT_AVAILABLE; + } + return sActiveIMEContentObserver->GetSelectionAndRoot(aSelection, + aRootContent); +} + +// static +already_AddRefed +IMEStateManager::GetTextCompositionFor(nsIWidget* aWidget) +{ + if (!sTextCompositions) { + return nullptr; + } + nsRefPtr textComposition = + sTextCompositions->GetCompositionFor(aWidget); + return textComposition.forget(); +} + +// static +already_AddRefed +IMEStateManager::GetTextCompositionFor(WidgetGUIEvent* aEvent) +{ + MOZ_ASSERT(aEvent->AsCompositionEvent() || aEvent->AsTextEvent() || + aEvent->AsKeyboardEvent(), + "aEvent has to be WidgetCompositionEvent, WidgetTextEvent or " + "WidgetKeyboardEvent"); + return GetTextCompositionFor(aEvent->widget); +} + +} // namespace mozilla