void IMEContentObserver::Destroy() { // If CreateTextStateManager failed, mRootContent will be null, // and we should not call NotifyIME(IMENotification(NOTIFY_IME_OF_BLUR)) if (mRootContent) { if (IMEStateManager::IsTestingIME() && mEditableNode) { nsIDocument* doc = mEditableNode->OwnerDoc(); (new AsyncEventDispatcher(doc, NS_LITERAL_STRING("MozIMEFocusOut"), false, false))->RunDOMEventWhenSafe(); } mWidget->NotifyIME(IMENotification(NOTIFY_IME_OF_BLUR)); } // Even if there are some pending notification, it'll never notify the widget. mWidget = nullptr; if (mUpdatePreference.WantSelectionChange() && mSelection) { nsCOMPtr<nsISelectionPrivate> selPrivate(do_QueryInterface(mSelection)); if (selPrivate) { selPrivate->RemoveSelectionListener(this); } } mSelection = nullptr; if (mUpdatePreference.WantTextChange() && mRootContent) { mRootContent->RemoveMutationObserver(this); } if (mUpdatePreference.WantPositionChanged() && mDocShell) { mDocShell->RemoveWeakScrollObserver(this); mDocShell->RemoveWeakReflowObserver(this); } mRootContent = nullptr; mEditableNode = nullptr; mDocShell = nullptr; mUpdatePreference.mWantUpdates = nsIMEUpdatePreference::NOTIFY_NOTHING; }
void IMEContentObserver::NotifyIMEOfBlur() { // Prevent any notifications to be sent IME. nsCOMPtr<nsIWidget> widget; mWidget.swap(widget); // If we hasn't been set focus, we shouldn't send blur notification to IME. if (!mIMEHasFocus) { return; } // mWidget must have been non-nullptr if IME has focus. MOZ_RELEASE_ASSERT(widget); // For now, we need to send blur notification in any condition because // we don't have any simple ways to send blur notification asynchronously. // After this call, Destroy() or Unlink() will stop observing the content // and forget everything. Therefore, if it's not safe to send notification // when script blocker is unlocked, we cannot send blur notification after // that and before next focus notification. // Anyway, as far as we know, IME doesn't try to query content when it loses // focus. So, this may not cause any problem. mIMEHasFocus = false; IMEStateManager::NotifyIME(IMENotification(NOTIFY_IME_OF_BLUR), widget); }
NS_IMETHOD Run() { if (mDispatcher->GetWidget()) { mDispatcher->GetWidget()->NotifyIME( IMENotification(NOTIFY_IME_OF_POSITION_CHANGE)); } return NS_OK; }
nsresult TextComposition::RequestToCommit(nsIWidget* aWidget, bool aDiscard) { // If this composition is already requested to be committed or canceled, // or has already finished in IME, we don't need to request it again because // request from this instance shouldn't cause committing nor canceling current // composition in IME, and even if the first request failed, new request // won't success, probably. And we shouldn't synthesize events for // committing or canceling composition twice or more times. if (!CanRequsetIMEToCommitOrCancelComposition()) { return NS_OK; } RefPtr<TextComposition> kungFuDeathGrip(this); const nsAutoString lastData(mLastData); { AutoRestore<bool> saveRequestingCancel(mIsRequestingCancel); AutoRestore<bool> saveRequestingCommit(mIsRequestingCommit); if (aDiscard) { mIsRequestingCancel = true; mIsRequestingCommit = false; } else { mIsRequestingCancel = false; mIsRequestingCommit = true; } // FYI: CompositionEvents caused by a call of NotifyIME() may be // discarded by PresShell if it's not safe to dispatch the event. nsresult rv = aWidget->NotifyIME(IMENotification(aDiscard ? REQUEST_TO_CANCEL_COMPOSITION : REQUEST_TO_COMMIT_COMPOSITION)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } mRequestedToCommitOrCancel = true; // If the request is performed synchronously, this must be already destroyed. if (Destroyed()) { return NS_OK; } // Otherwise, synthesize the commit in content. nsAutoString data(aDiscard ? EmptyString() : lastData); if (data == mLastData) { DispatchCompositionEventRunnable(eCompositionCommitAsIs, EmptyString(), true); } else { DispatchCompositionEventRunnable(eCompositionCommit, data, true); } return NS_OK; }
NS_IMETHODIMP IMEContentObserver::PositionChangeEvent::Run() { if (!CanNotifyIME()) { return NS_OK; } if (!IsSafeToNotifyIME()) { mIMEContentObserver->PostPositionChangeNotification(); return NS_OK; } IMEStateManager::NotifyIME(IMENotification(NOTIFY_IME_OF_POSITION_CHANGE), mIMEContentObserver->mWidget); return NS_OK; }
NS_IMETHODIMP IMEContentObserver::FocusSetEvent::Run() { if (!CanNotifyIME()) { // If IMEContentObserver has already gone, we don't need to notify IME of // focus. mIMEContentObserver->ClearPendingNotifications(); return NS_OK; } if (!IsSafeToNotifyIME()) { mIMEContentObserver->PostFocusSetNotification(); return NS_OK; } mIMEContentObserver->mIMEHasFocus = true; IMEStateManager::NotifyIME(IMENotification(NOTIFY_IME_OF_FOCUS), mIMEContentObserver->mWidget); return NS_OK; }
nsresult TextComposition::RequestToCommit(nsIWidget* aWidget, bool aDiscard) { // If this composition is already requested to be committed or canceled, // we don't need to request it again because even if the first request // failed, new request won't success, probably. And we shouldn't synthesize // events for committing or canceling composition twice or more times. if (mRequestedToCommitOrCancel) { return NS_OK; } nsRefPtr<TextComposition> kungFuDeathGrip(this); const nsAutoString lastData(mLastData); { AutoRestore<bool> saveRequestingCancel(mIsRequestingCancel); AutoRestore<bool> saveRequestingCommit(mIsRequestingCommit); if (aDiscard) { mIsRequestingCancel = true; mIsRequestingCommit = false; } else { mIsRequestingCancel = false; mIsRequestingCommit = true; } if (!mIsSynthesizedForTests) { // FYI: CompositionEvent and TextEvent caused by a call of NotifyIME() // may be discarded by PresShell if it's not safe to dispatch the // event. nsresult rv = aWidget->NotifyIME(IMENotification(aDiscard ? REQUEST_TO_CANCEL_COMPOSITION : REQUEST_TO_COMMIT_COMPOSITION)); if (rv == NS_ERROR_NOT_IMPLEMENTED) { return rv; } if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } else { // Emulates to commit or cancel the composition // FYI: These events may be discarded by PresShell if it's not safe to // dispatch the event. nsCOMPtr<nsIWidget> widget(aWidget); nsAutoString commitData(aDiscard ? EmptyString() : lastData); bool changingData = lastData != commitData; WidgetTextEvent textEvent(true, NS_TEXT_TEXT, widget); textEvent.theText = commitData; textEvent.mFlags.mIsSynthesizedForTests = true; MaybeDispatchCompositionUpdate(&textEvent); // If changing the data or committing string isn't empty, we need to // dispatch text event for setting the composition string without // IME selection. if (!Destroyed() && !widget->Destroyed() && (changingData || !commitData.IsEmpty())) { nsEventStatus status = nsEventStatus_eIgnore; widget->DispatchEvent(&textEvent, status); } if (!Destroyed() && !widget->Destroyed()) { nsEventStatus status = nsEventStatus_eIgnore; WidgetCompositionEvent endEvent(true, NS_COMPOSITION_END, widget); endEvent.data = commitData; endEvent.mFlags.mIsSynthesizedForTests = true; widget->DispatchEvent(&endEvent, status); } } } mRequestedToCommitOrCancel = true; // If the request is performed synchronously, this must be already destroyed. if (Destroyed()) { return NS_OK; } // Otherwise, synthesize the commit in content. nsAutoString data(aDiscard ? EmptyString() : lastData); bool changingData = lastData != data; if (changingData) { DispatchCompositionEventRunnable(NS_COMPOSITION_UPDATE, data, true); } // If the last composition string and new data are different, we need to // dispatch text event for removing IME selection. However, if the commit // string is empty string and it's not changed from the last data, we don't // need to dispatch text event. if (changingData || !data.IsEmpty()) { DispatchCompositionEventRunnable(NS_TEXT_TEXT, data, true); } DispatchCompositionEventRunnable(NS_COMPOSITION_END, data, true); return NS_OK; }
// static void IMEHandler::SetInputContext(nsWindow* aWindow, InputContext& aInputContext, const InputContextAction& aAction) { // FYI: If there is no composition, this call will do nothing. NotifyIME(aWindow, IMENotification(REQUEST_TO_COMMIT_COMPOSITION)); const InputContext& oldInputContext = aWindow->GetInputContext(); // Assume that SetInputContext() is called only when aWindow has focus. sPluginHasFocus = (aInputContext.mIMEState.mEnabled == IMEState::PLUGIN); bool enable = WinUtils::IsIMEEnabled(aInputContext); bool adjustOpenState = (enable && aInputContext.mIMEState.mOpen != IMEState::DONT_CHANGE_OPEN_STATE); bool open = (adjustOpenState && aInputContext.mIMEState.mOpen == IMEState::OPEN); aInputContext.mNativeIMEContext = nullptr; #ifdef NS_ENABLE_TSF // Note that even while a plugin has focus, we need to notify TSF of that. if (sIsInTSFMode) { nsTextStore::SetInputContext(aWindow, aInputContext, aAction); if (IsTSFAvailable()) { aInputContext.mNativeIMEContext = nsTextStore::GetTextStore(); if (sIsIMMEnabled) { // Associate IME context for IMM-IMEs. AssociateIMEContext(aWindow, enable); } else if (oldInputContext.mIMEState.mEnabled == IMEState::PLUGIN) { // Disassociate the IME context from the window when plugin loses focus // in pure TSF mode. AssociateIMEContext(aWindow, false); } if (adjustOpenState) { nsTextStore::SetIMEOpenState(open); } return; } } else { // Set at least InputScope even when TextStore is not available. SetInputScopeForIMM32(aWindow, aInputContext.mHTMLInputType); } #endif // #ifdef NS_ENABLE_TSF AssociateIMEContext(aWindow, enable); nsIMEContext IMEContext(aWindow->GetWindowHandle()); if (adjustOpenState) { IMEContext.SetOpenState(open); } if (aInputContext.mNativeIMEContext) { return; } // The old InputContext must store the default IMC or old TextStore. // When IME context is disassociated from the window, use it. aInputContext.mNativeIMEContext = enable ? static_cast<void*>(IMEContext.get()) : oldInputContext.mNativeIMEContext; }