void TextComposition::SynthesizeCommit(bool aDiscard) { // backup this instance and use it since this instance might be destroyed // by nsIMEStateManager if this is managed by it. TextComposition composition = *this; nsAutoString data(aDiscard ? EmptyString() : composition.mLastData); if (composition.mLastData != data) { composition.DispatchCompsotionEventRunnable(NS_COMPOSITION_UPDATE, data); composition.DispatchCompsotionEventRunnable(NS_TEXT_TEXT, data); } composition.DispatchCompsotionEventRunnable(NS_COMPOSITION_END, data); }
void nsIMEStateManager::DispatchCompositionEvent(nsINode* aEventTargetNode, nsPresContext* aPresContext, nsEvent* aEvent, nsEventStatus* aStatus, nsDispatchingCallback* aCallBack) { MOZ_ASSERT(aEvent->eventStructType == NS_COMPOSITION_EVENT || aEvent->eventStructType == NS_TEXT_EVENT); if (!NS_IS_TRUSTED_EVENT(aEvent) || (aEvent->flags & NS_EVENT_FLAG_STOP_DISPATCH) != 0) { return; } EnsureTextCompositionArray(); nsGUIEvent* GUIEvent = static_cast<nsGUIEvent*>(aEvent); TextComposition* composition = sTextCompositions->GetCompositionFor(GUIEvent->widget); if (!composition) { MOZ_ASSERT(GUIEvent->message == NS_COMPOSITION_START); TextComposition newComposition(aPresContext, aEventTargetNode, GUIEvent); composition = sTextCompositions->AppendElement(newComposition); } #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->RemoveElementAt(i); } } }
nsresult nsIMEStateManager::OnRemoveContent(nsPresContext* aPresContext, nsIContent* aContent) { NS_ENSURE_ARG_POINTER(aPresContext); // First, if there is a composition in the aContent, clean up it. if (sTextCompositions) { TextComposition* compositionInContent = sTextCompositions->GetCompositionInContent(aPresContext, aContent); if (compositionInContent) { // Store the composition before accessing the native IME. TextComposition storedComposition = *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<nsIWidget> widget = aPresContext->GetNearestWidget(); if (widget) { nsresult rv = storedComposition.NotifyIME(REQUEST_TO_CANCEL_COMPOSITION); if (NS_FAILED(rv)) { storedComposition.NotifyIME(REQUEST_TO_COMMIT_COMPOSITION); } // By calling the APIs, the composition may have been finished normally. compositionInContent = sTextCompositions->GetCompositionFor( storedComposition.GetPresContext(), storedComposition.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; } // Current IME transaction should commit nsCOMPtr<nsIWidget> widget = sPresContext->GetNearestWidget(); 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 nsTextEditRules::TruncateInsertionIfNeeded(Selection* aSelection, const nsAString *aInString, nsAString *aOutString, int32_t aMaxLength, bool *aTruncated) { if (!aSelection || !aInString || !aOutString) {return NS_ERROR_NULL_POINTER;} nsresult res = NS_OK; *aOutString = *aInString; if (aTruncated) { *aTruncated = false; } NS_ENSURE_STATE(mEditor); if ((-1 != aMaxLength) && IsPlaintextEditor() && !mEditor->IsIMEComposing() ) { // Get the current text length. // Get the length of inString. // Get the length of the selection. // If selection is collapsed, it is length 0. // Subtract the length of the selection from the len(doc) // since we'll delete the selection on insert. // This is resultingDocLength. // Get old length of IME composing string // which will be replaced by new one. // If (resultingDocLength) is at or over max, cancel the insert // If (resultingDocLength) + (length of input) > max, // set aOutString to subset of inString so length = max int32_t docLength; res = mEditor->GetTextLength(&docLength); if (NS_FAILED(res)) { return res; } int32_t start, end; nsContentUtils::GetSelectionInTextControl(aSelection, mEditor->GetRoot(), start, end); TextComposition* composition = mEditor->GetComposition(); int32_t oldCompStrLength = composition ? composition->String().Length() : 0; const int32_t selectionLength = end - start; const int32_t resultingDocLength = docLength - selectionLength - oldCompStrLength; if (resultingDocLength >= aMaxLength) { aOutString->Truncate(); if (aTruncated) { *aTruncated = true; } } else { int32_t inCount = aOutString->Length(); if (inCount + resultingDocLength > aMaxLength) { aOutString->Truncate(aMaxLength - resultingDocLength); if (aTruncated) { *aTruncated = true; } } } } return res; }
nsresult TextEditRules::TruncateInsertionIfNeeded(Selection* aSelection, const nsAString* aInString, nsAString* aOutString, int32_t aMaxLength, bool* aTruncated) { if (!aSelection || !aInString || !aOutString) { return NS_ERROR_NULL_POINTER; } if (!aOutString->Assign(*aInString, mozilla::fallible)) { return NS_ERROR_OUT_OF_MEMORY; } if (aTruncated) { *aTruncated = false; } NS_ENSURE_STATE(mTextEditor); if (-1 != aMaxLength && IsPlaintextEditor() && !mTextEditor->IsIMEComposing()) { // Get the current text length. // Get the length of inString. // Get the length of the selection. // If selection is collapsed, it is length 0. // Subtract the length of the selection from the len(doc) // since we'll delete the selection on insert. // This is resultingDocLength. // Get old length of IME composing string // which will be replaced by new one. // If (resultingDocLength) is at or over max, cancel the insert // If (resultingDocLength) + (length of input) > max, // set aOutString to subset of inString so length = max int32_t docLength; nsresult rv = mTextEditor->GetTextLength(&docLength); if (NS_FAILED(rv)) { return rv; } uint32_t start, end; nsContentUtils::GetSelectionInTextControl(aSelection, mTextEditor->GetRoot(), start, end); TextComposition* composition = mTextEditor->GetComposition(); uint32_t oldCompStrLength = composition ? composition->String().Length() : 0; const uint32_t selectionLength = end - start; const int32_t resultingDocLength = docLength - selectionLength - oldCompStrLength; if (resultingDocLength >= aMaxLength) { // This call is guaranteed to reduce the capacity of the string, so it // cannot cause an OOM. aOutString->Truncate(); if (aTruncated) { *aTruncated = true; } } else { int32_t oldLength = aOutString->Length(); if (oldLength + resultingDocLength > aMaxLength) { int32_t newLength = aMaxLength - resultingDocLength; MOZ_ASSERT(newLength > 0); char16_t newLastChar = aOutString->CharAt(newLength - 1); char16_t removingFirstChar = aOutString->CharAt(newLength); // Don't separate the string between a surrogate pair. if (NS_IS_HIGH_SURROGATE(newLastChar) && NS_IS_LOW_SURROGATE(removingFirstChar)) { newLength--; } // XXX What should we do if we're removing IVS and its preceding // character won't be removed? // This call is guaranteed to reduce the capacity of the string, so it // cannot cause an OOM. aOutString->Truncate(newLength); if (aTruncated) { *aTruncated = true; } } } } return NS_OK; }
// static nsresult nsIMEStateManager::NotifyIME(NotificationToIME aNotification, nsIWidget* aWidget) { NS_ENSURE_TRUE(aWidget, NS_ERROR_INVALID_ARG); TextComposition* composition = nullptr; if (sTextCompositions) { composition = sTextCompositions->GetCompositionFor(aWidget); } if (!composition || !composition->IsSynthesizedForTests()) { switch (aNotification) { case NOTIFY_IME_OF_CURSOR_POS_CHANGED: return aWidget->ResetInputState(); case REQUEST_TO_COMMIT_COMPOSITION: return composition ? aWidget->ResetInputState() : NS_OK; case REQUEST_TO_CANCEL_COMPOSITION: return composition ? aWidget->CancelIMEComposition() : NS_OK; default: MOZ_NOT_REACHED("Unsupported notification"); return NS_ERROR_INVALID_ARG; } MOZ_NOT_REACHED( "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 (aNotification) { case REQUEST_TO_COMMIT_COMPOSITION: { nsCOMPtr<nsIWidget> widget(aWidget); TextComposition backup = *composition; nsEventStatus status = nsEventStatus_eIgnore; if (!backup.GetLastData().IsEmpty()) { nsTextEvent textEvent(true, NS_TEXT_TEXT, widget); textEvent.theText = backup.GetLastData(); textEvent.flags |= NS_EVENT_FLAG_SYNTHETIC_TEST_EVENT; widget->DispatchEvent(&textEvent, status); if (widget->Destroyed()) { return NS_OK; } } status = nsEventStatus_eIgnore; nsCompositionEvent endEvent(true, NS_COMPOSITION_END, widget); endEvent.data = backup.GetLastData(); endEvent.flags |= NS_EVENT_FLAG_SYNTHETIC_TEST_EVENT; widget->DispatchEvent(&endEvent, status); return NS_OK; } case REQUEST_TO_CANCEL_COMPOSITION: { nsCOMPtr<nsIWidget> widget(aWidget); TextComposition backup = *composition; nsEventStatus status = nsEventStatus_eIgnore; if (!backup.GetLastData().IsEmpty()) { nsCompositionEvent updateEvent(true, NS_COMPOSITION_UPDATE, widget); updateEvent.data = backup.GetLastData(); updateEvent.flags |= NS_EVENT_FLAG_SYNTHETIC_TEST_EVENT; widget->DispatchEvent(&updateEvent, status); if (widget->Destroyed()) { return NS_OK; } status = nsEventStatus_eIgnore; nsTextEvent textEvent(true, NS_TEXT_TEXT, widget); textEvent.theText = backup.GetLastData(); textEvent.flags |= NS_EVENT_FLAG_SYNTHETIC_TEST_EVENT; widget->DispatchEvent(&textEvent, status); if (widget->Destroyed()) { return NS_OK; } } status = nsEventStatus_eIgnore; nsCompositionEvent endEvent(true, NS_COMPOSITION_END, widget); endEvent.data = backup.GetLastData(); endEvent.flags |= NS_EVENT_FLAG_SYNTHETIC_TEST_EVENT; widget->DispatchEvent(&endEvent, status); return NS_OK; } default: return NS_OK; } }