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