`.
if (NS_WARN_IF(
NS_FAILED(mStartOfRemovingTextRangeCache
.ComputeAndCacheFlatTextLengthBeforeEndOfContent(
__FUNCTION__, *prevSibling, mRootElement)))) {
return;
}
} else {
// At removing a child node of containerNode, we need the line break
// caused by open tag of containerNode.
if (NS_WARN_IF(
NS_FAILED(mStartOfRemovingTextRangeCache
.ComputeAndCacheFlatTextLengthBeforeFirstContent(
__FUNCTION__, *containerNode, mRootElement)))) {
return;
}
}
offset = Some(mStartOfRemovingTextRangeCache.GetFlatTextLength());
}
// We do not need a text change notification since removing aChild does not
// change flattened text and no pending added length.
if (textLengthOrError.inspect() == 0u) {
return;
}
TextChangeData data(*offset, *offset + textLengthOrError.inspect(), *offset,
IsEditorHandlingEventForComposition(),
IsEditorComposing());
MaybeNotifyIMEOfTextChange(data);
}
MOZ_CAN_RUN_SCRIPT_BOUNDARY void IMEContentObserver::ParentChainChanged(
nsIContent* aContent) {
// When the observing element itself is directly removed from the document
// without a focus move, i.e., it's the root of the removed document fragment
// and the editor was handling the design mode, we have already stopped
// observing the element because IMEStateManager::OnRemoveContent() should
// have already been called for it and the instance which was observing the
// node has already been destroyed. Therefore, this is called only when
// this is observing the in the design mode and it's disconnected from
// the tree by an element removal. Even in this case, IMEStateManager
// never gets a focus change notification, but we need to notify IME of focus
// change because we cannot interact with IME anymore due to no editable
// content. Therefore, this method notifies IMEStateManager of the
// disconnection of the observing node to emulate a blur from the editable
// content.
MOZ_ASSERT(mIsObserving);
OwningNonNull
observer(*this);
IMEStateManager::OnParentChainChangedOfObservingElement(observer);
}
void IMEContentObserver::OnTextControlValueChangedWhileNotObservable(
const nsAString& aNewValue) {
MOZ_ASSERT(mEditorBase);
MOZ_ASSERT(mEditorBase->IsTextEditor());
if (!mTextControlValueLength && aNewValue.IsEmpty()) {
return;
}
MOZ_LOG(sIMECOLog, LogLevel::Debug,
("0x%p OnTextControlValueChangedWhileNotObservable()", this));
uint32_t newLength = ContentEventHandler::GetNativeTextLength(aNewValue);
TextChangeData data(0, mTextControlValueLength, newLength, false, false);
MaybeNotifyIMEOfTextChange(data);
}
void IMEContentObserver::BeginDocumentUpdate() {
MOZ_LOG(sIMECOLog, LogLevel::Debug, ("0x%p BeginDocumentUpdate()", this));
}
void IMEContentObserver::EndDocumentUpdate() {
MOZ_LOG(sIMECOLog, LogLevel::Debug, ("0x%p EndDocumentUpdate()", this));
if (mAddedContentCache.HasCache() && !EditorIsHandlingEditSubAction()) {
NotifyIMEOfCachedConsecutiveNewNodes(__FUNCTION__);
}
}
void IMEContentObserver::SuppressNotifyingIME() {
mSuppressNotifications++;
MOZ_LOG(sIMECOLog, LogLevel::Debug,
("0x%p SuppressNotifyingIME(), mSuppressNotifications=%u", this,
mSuppressNotifications));
}
void IMEContentObserver::UnsuppressNotifyingIME() {
MOZ_LOG(sIMECOLog, LogLevel::Debug,
("0x%p UnsuppressNotifyingIME(), mSuppressNotifications=%u", this,
mSuppressNotifications));
if (!mSuppressNotifications || --mSuppressNotifications) {
return;
}
FlushMergeableNotifications();
}
void IMEContentObserver::OnEditActionHandled() {
MOZ_LOG(sIMECOLog, LogLevel::Debug, ("0x%p OnEditActionHandled()", this));
if (mAddedContentCache.HasCache()) {
NotifyIMEOfCachedConsecutiveNewNodes(__FUNCTION__);
}
mEndOfAddedTextCache.Clear(__FUNCTION__);
mStartOfRemovingTextRangeCache.Clear(__FUNCTION__);
FlushMergeableNotifications();
}
void IMEContentObserver::BeforeEditAction() {
MOZ_LOG(sIMECOLog, LogLevel::Debug, ("0x%p BeforeEditAction()", this));
if (mAddedContentCache.HasCache()) {
NotifyIMEOfCachedConsecutiveNewNodes(__FUNCTION__);
}
mEndOfAddedTextCache.Clear(__FUNCTION__);
mStartOfRemovingTextRangeCache.Clear(__FUNCTION__);
}
void IMEContentObserver::CancelEditAction() {
MOZ_LOG(sIMECOLog, LogLevel::Debug, ("0x%p CancelEditAction()", this));
if (mAddedContentCache.HasCache()) {
NotifyIMEOfCachedConsecutiveNewNodes(__FUNCTION__);
}
mEndOfAddedTextCache.Clear(__FUNCTION__);
mStartOfRemovingTextRangeCache.Clear(__FUNCTION__);
FlushMergeableNotifications();
}
bool IMEContentObserver::EditorIsHandlingEditSubAction() const {
return mEditorBase && mEditorBase->IsInEditSubAction();
}
void IMEContentObserver::PostFocusSetNotification() {
MOZ_LOG(sIMECOLog, LogLevel::Debug,
("0x%p PostFocusSetNotification()", this));
mNeedsToNotifyIMEOfFocusSet = true;
}
void IMEContentObserver::PostTextChangeNotification() {
MOZ_LOG(sIMECOLog, LogLevel::Debug,
("0x%p PostTextChangeNotification(mTextChangeData=%s)", this,
ToString(mTextChangeData).c_str()));
MOZ_ASSERT(mTextChangeData.IsValid(),
"mTextChangeData must have text change data");
mNeedsToNotifyIMEOfTextChange = true;
// Even if the observer hasn't received selection change, selection in the
// flat text may have already been changed. For example, when previous ``
// element of another `
` element which contains caret is removed by a DOM
// mutation, selection change event won't be fired, but selection start offset
// should be decreased by the length of removed `
` element.
// In such case, HandleQueryContentEvent shouldn't use the selection cache
// anymore. Therefore, we also need to post selection change notification
// too. eQuerySelectedText event may be dispatched at sending a text change
// notification.
mNeedsToNotifyIMEOfSelectionChange = true;
}
void IMEContentObserver::PostSelectionChangeNotification() {
MOZ_LOG(sIMECOLog, LogLevel::Debug,
("0x%p PostSelectionChangeNotification(), mSelectionData={ "
"mCausedByComposition=%s, mCausedBySelectionEvent=%s }",
this, ToChar(mSelectionData.mCausedByComposition),
ToChar(mSelectionData.mCausedBySelectionEvent)));
mNeedsToNotifyIMEOfSelectionChange = true;
}
void IMEContentObserver::MaybeNotifyIMEOfFocusSet() {
MOZ_LOG(sIMECOLog, LogLevel::Debug,
("0x%p MaybeNotifyIMEOfFocusSet()", this));
PostFocusSetNotification();
FlushMergeableNotifications();
}
void IMEContentObserver::MaybeNotifyIMEOfTextChange(
const TextChangeDataBase& aTextChangeData) {
MOZ_LOG(sIMECOLog, LogLevel::Debug,
("0x%p MaybeNotifyIMEOfTextChange(aTextChangeData=%s)", this,
ToString(aTextChangeData).c_str()));
if (mEditorBase && mEditorBase->IsTextEditor()) {
MOZ_DIAGNOSTIC_ASSERT(static_cast(mTextControlValueLength) +
aTextChangeData.Difference() >=
0);
mTextControlValueLength += aTextChangeData.Difference();
}
mTextChangeData += aTextChangeData;
PostTextChangeNotification();
FlushMergeableNotifications();
}
void IMEContentObserver::CancelNotifyingIMEOfTextChange() {
MOZ_LOG(sIMECOLog, LogLevel::Debug,
("0x%p CancelNotifyingIMEOfTextChange()", this));
mTextChangeData.Clear();
mNeedsToNotifyIMEOfTextChange = false;
}
void IMEContentObserver::MaybeNotifyIMEOfSelectionChange(
bool aCausedByComposition, bool aCausedBySelectionEvent,
bool aOccurredDuringComposition) {
MOZ_LOG(
sIMECOLog, LogLevel::Debug,
("0x%p MaybeNotifyIMEOfSelectionChange(aCausedByComposition=%s, "
"aCausedBySelectionEvent=%s, aOccurredDuringComposition)",
this, ToChar(aCausedByComposition), ToChar(aCausedBySelectionEvent)));
mSelectionData.AssignReason(aCausedByComposition, aCausedBySelectionEvent,
aOccurredDuringComposition);
PostSelectionChangeNotification();
FlushMergeableNotifications();
}
void IMEContentObserver::MaybeNotifyIMEOfPositionChange() {
MOZ_LOG(sIMECOLog, LogLevel::Verbose,
("0x%p MaybeNotifyIMEOfPositionChange()", this));
// If reflow is caused by ContentEventHandler during PositionChangeEvent
// sending NOTIFY_IME_OF_POSITION_CHANGE, we don't need to notify IME of it
// again since ContentEventHandler returns the result including this reflow's
// result.
if (mIsHandlingQueryContentEvent &&
mSendingNotification == NOTIFY_IME_OF_POSITION_CHANGE) {
MOZ_LOG(sIMECOLog, LogLevel::Verbose,
("0x%p MaybeNotifyIMEOfPositionChange(), ignored since caused by "
"ContentEventHandler during sending NOTIFY_IME_OF_POSITION_CHANGE",
this));
return;
}
PostPositionChangeNotification();
FlushMergeableNotifications();
}
void IMEContentObserver::CancelNotifyingIMEOfPositionChange() {
MOZ_LOG(sIMECOLog, LogLevel::Debug,
("0x%p CancelNotifyIMEOfPositionChange()", this));
mNeedsToNotifyIMEOfPositionChange = false;
}
void IMEContentObserver::MaybeNotifyCompositionEventHandled() {
MOZ_LOG(sIMECOLog, LogLevel::Debug,
("0x%p MaybeNotifyCompositionEventHandled()", this));
PostCompositionEventHandledNotification();
FlushMergeableNotifications();
}
bool IMEContentObserver::UpdateSelectionCache(bool aRequireFlush /* = true */) {
MOZ_ASSERT(IsSafeToNotifyIME());
mSelectionData.ClearSelectionData();
// XXX Cannot we cache some information for reducing the cost to compute
// selection offset and writing mode?
WidgetQueryContentEvent querySelectedTextEvent(true, eQuerySelectedText,
mWidget);
querySelectedTextEvent.mNeedsToFlushLayout = aRequireFlush;
ContentEventHandler handler(GetPresContext());
handler.OnQuerySelectedText(&querySelectedTextEvent);
if (NS_WARN_IF(querySelectedTextEvent.Failed()) ||
NS_WARN_IF(querySelectedTextEvent.mReply->mContentsRoot !=
mRootElement)) {
return false;
}
mFocusedWidget = querySelectedTextEvent.mReply->mFocusedWidget;
mSelectionData.Assign(querySelectedTextEvent);
// WARNING: Don't set the reason of selection change here because it should be
// set the reason at sending the notification.
MOZ_LOG(sIMECOLog, LogLevel::Debug,
("0x%p UpdateSelectionCache(), mSelectionData=%s", this,
ToString(mSelectionData).c_str()));
return true;
}
void IMEContentObserver::PostPositionChangeNotification() {
MOZ_LOG(sIMECOLog, LogLevel::Debug,
("0x%p PostPositionChangeNotification()", this));
mNeedsToNotifyIMEOfPositionChange = true;
}
void IMEContentObserver::PostCompositionEventHandledNotification() {
MOZ_LOG(sIMECOLog, LogLevel::Debug,
("0x%p PostCompositionEventHandledNotification()", this));
mNeedsToNotifyIMEOfCompositionEventHandled = true;
}
bool IMEContentObserver::IsReflowLocked() const {
nsPresContext* presContext = GetPresContext();
if (NS_WARN_IF(!presContext)) {
return false;
}
PresShell* presShell = presContext->GetPresShell();
if (NS_WARN_IF(!presShell)) {
return false;
}
// During reflow, we shouldn't notify IME because IME may query content
// synchronously. Then, it causes ContentEventHandler will try to flush
// pending notifications during reflow.
return presShell->IsReflowLocked();
}
bool IMEContentObserver::IsSafeToNotifyIME() const {
// If this is already detached from the widget, this doesn't need to notify
// anything.
if (!mWidget) {
MOZ_LOG(sIMECOLog, LogLevel::Debug,
("0x%p IsSafeToNotifyIME(), it's not safe because of no widget",
this));
return false;
}
// Don't notify IME of anything if it's not good time to do it.
if (mSuppressNotifications) {
MOZ_LOG(sIMECOLog, LogLevel::Debug,
("0x%p IsSafeToNotifyIME(), it's not safe because of no widget",
this));
return false;
}
if (!mESM || NS_WARN_IF(!GetPresContext())) {
MOZ_LOG(sIMECOLog, LogLevel::Debug,
("0x%p IsSafeToNotifyIME(), it's not safe because of no "
"EventStateManager and/or PresContext",
this));
return false;
}
// If it's in reflow, we should wait to finish the reflow.
// FYI: This should be called again from Reflow() or ReflowInterruptible().
if (IsReflowLocked()) {
MOZ_LOG(
sIMECOLog, LogLevel::Debug,
("0x%p IsSafeToNotifyIME(), it's not safe because of reflow locked",
this));
return false;
}
// If we're in handling an edit action, this method will be called later.
if (EditorIsHandlingEditSubAction()) {
MOZ_LOG(sIMECOLog, LogLevel::Debug,
("0x%p IsSafeToNotifyIME(), it's not safe because of focused "
"editor handling somethings",
this));
return false;
}
return true;
}
void IMEContentObserver::FlushMergeableNotifications() {
if (!IsSafeToNotifyIME()) {
// So, if this is already called, this should do nothing.
MOZ_LOG(sIMECOLog, LogLevel::Warning,
("0x%p FlushMergeableNotifications(), Warning, do nothing due to "
"unsafe to notify IME",
this));
return;
}
// Notifying something may cause nested call of this method. For example,
// when somebody notified one of the notifications may dispatch query content
// event. Then, it causes flushing layout which may cause another layout
// change notification.
if (mQueuedSender) {
// So, if this is already called, this should do nothing.
MOZ_LOG(sIMECOLog, LogLevel::Warning,
("0x%p FlushMergeableNotifications(), Warning, do nothing due to "
"already flushing pending notifications",
this));
return;
}
// If text change notification and/or position change notification becomes
// unnecessary, let's cancel them.
if (mNeedsToNotifyIMEOfTextChange && !NeedsTextChangeNotification()) {
CancelNotifyingIMEOfTextChange();
}
if (mNeedsToNotifyIMEOfPositionChange && !NeedsPositionChangeNotification()) {
CancelNotifyingIMEOfPositionChange();
}
if (!NeedsToNotifyIMEOfSomething()) {
MOZ_LOG(sIMECOLog, LogLevel::Warning,
("0x%p FlushMergeableNotifications(), Warning, due to no pending "
"notifications",
this));
return;
}
// NOTE: Reset each pending flag because sending notification may cause
// another change.
MOZ_LOG(
sIMECOLog, LogLevel::Info,
("0x%p FlushMergeableNotifications(), creating IMENotificationSender...",
this));
// If contents in selection range is modified, the selection range still
// has removed node from the tree. In such case, ContentIterator won't
// work well. Therefore, we shouldn't use AddScriptRunner() here since
// it may kick runnable event immediately after DOM tree is changed but
// the selection range isn't modified yet.
mQueuedSender = new IMENotificationSender(this);
mQueuedSender->Dispatch(mDocShell);
MOZ_LOG(sIMECOLog, LogLevel::Debug,
("0x%p FlushMergeableNotifications(), finished", this));
}
void IMEContentObserver::TryToFlushPendingNotifications(bool aAllowAsync) {
// If a sender instance is sending notifications, we shouldn't try to create
// a new sender again because the sender will recreate by itself if there are
// new pending notifications.
if (mSendingNotification != NOTIFY_IME_OF_NOTHING) {
return;
}
// When the caller allows to put off notifying IME, we can wait the next
// call of this method or to run the queued sender.
if (mQueuedSender && XRE_IsContentProcess() && aAllowAsync) {
return;
}
if (!mQueuedSender) {
// If it was not safe to dispatch notifications when the pending
// notifications are posted, this may not have IMENotificationSender
// instance because it couldn't dispatch it, e.g., when an edit sub-action
// is being handled in the editor, we shouldn't do it even if it's safe to
// run script. Therefore, we need to create the sender instance here in the
// case.
if (!NeedsToNotifyIMEOfSomething()) {
return;
}
mQueuedSender = new IMENotificationSender(this);
}
MOZ_LOG(sIMECOLog, LogLevel::Debug,
("0x%p TryToFlushPendingNotifications(), performing queued "
"IMENotificationSender forcibly",
this));
RefPtr queuedSender = mQueuedSender;
queuedSender->Run();
}
/******************************************************************************
* mozilla::IMEContentObserver::AChangeEvent
******************************************************************************/
bool IMEContentObserver::AChangeEvent::CanNotifyIME(
ChangeEventType aChangeEventType) const {
RefPtr observer = GetObserver();
if (NS_WARN_IF(!observer)) {
return false;
}
const LogLevel debugOrVerbose =
aChangeEventType == ChangeEventType::eChangeEventType_Position
? LogLevel::Verbose
: LogLevel::Debug;
if (aChangeEventType == eChangeEventType_CompositionEventHandled) {
if (observer->mWidget) {
return true;
}
MOZ_LOG(sIMECOLog, debugOrVerbose,
("0x%p AChangeEvent::CanNotifyIME(), Cannot notify IME of "
"composition event handled because of no widget",
this));
return false;
}
State state = observer->GetState();
// If it's not initialized, we should do nothing.
if (state == eState_NotObserving) {
MOZ_LOG(sIMECOLog, debugOrVerbose,
("0x%p AChangeEvent::CanNotifyIME(), Cannot notify IME because "
"of not observing",
this));
return false;
}
// If setting focus, just check the state.
if (aChangeEventType == eChangeEventType_Focus) {
if (!observer->mIMEHasFocus) {
return true;
}
MOZ_LOG(sIMECOLog, debugOrVerbose,
("0x%p AChangeEvent::CanNotifyIME(), Cannot notify IME of focus "
"change because of already focused",
this));
NS_WARNING("IME already has focus");
return false;
}
// If we've not notified IME of focus yet, we shouldn't notify anything.
if (!observer->mIMEHasFocus) {
MOZ_LOG(sIMECOLog, debugOrVerbose,
("0x%p AChangeEvent::CanNotifyIME(), Cannot notify IME because "
"of not focused",
this));
return false;
}
// If IME has focus, IMEContentObserver must hold the widget.
MOZ_ASSERT(observer->mWidget);
return true;
}
bool IMEContentObserver::AChangeEvent::IsSafeToNotifyIME(
ChangeEventType aChangeEventType) const {
const LogLevel warningOrVerbose =
aChangeEventType == ChangeEventType::eChangeEventType_Position
? LogLevel::Verbose
: LogLevel::Warning;
if (NS_WARN_IF(!nsContentUtils::IsSafeToRunScript())) {
MOZ_LOG(sIMECOLog, warningOrVerbose,
("0x%p AChangeEvent::IsSafeToNotifyIME(), Warning, Cannot notify "
"IME because of not safe to run script",
this));
return false;
}
RefPtr observer = GetObserver();
if (!observer) {
MOZ_LOG(sIMECOLog, warningOrVerbose,
("0x%p AChangeEvent::IsSafeToNotifyIME(), Warning, Cannot notify "
"IME because of no observer",
this));
return false;
}
// While we're sending a notification, we shouldn't send another notification
// recursively.
if (observer->mSendingNotification != NOTIFY_IME_OF_NOTHING) {
MOZ_LOG(sIMECOLog, warningOrVerbose,
("0x%p AChangeEvent::IsSafeToNotifyIME(), Warning, Cannot notify "
"IME because of the observer sending another notification",
this));
return false;
}
State state = observer->GetState();
if (aChangeEventType == eChangeEventType_Focus) {
if (NS_WARN_IF(state != eState_Initializing && state != eState_Observing)) {
MOZ_LOG(sIMECOLog, warningOrVerbose,
("0x%p AChangeEvent::IsSafeToNotifyIME(), Warning, Cannot "
"notify IME of focus because of not observing",
this));
return false;
}
} else if (aChangeEventType == eChangeEventType_CompositionEventHandled) {
// It doesn't need to check the observing status.
} else if (state != eState_Observing) {
MOZ_LOG(sIMECOLog, warningOrVerbose,
("0x%p AChangeEvent::IsSafeToNotifyIME(), Warning, Cannot notify "
"IME because of not observing",
this));
return false;
}
return observer->IsSafeToNotifyIME();
}
/******************************************************************************
* mozilla::IMEContentObserver::IMENotificationSender
******************************************************************************/
void IMEContentObserver::IMENotificationSender::Dispatch(
nsIDocShell* aDocShell) {
if (XRE_IsContentProcess() && aDocShell) {
if (RefPtr presContext = aDocShell->GetPresContext()) {
if (nsRefreshDriver* refreshDriver = presContext->RefreshDriver()) {
refreshDriver->AddEarlyRunner(this);
return;
}
}
}
NS_DispatchToCurrentThread(this);
}
NS_IMETHODIMP
IMEContentObserver::IMENotificationSender::Run() {
if (NS_WARN_IF(mIsRunning)) {
MOZ_LOG(
sIMECOLog, LogLevel::Error,
("0x%p IMENotificationSender::Run(), FAILED, due to called recursively",
this));
return NS_OK;
}
RefPtr observer = GetObserver();
if (!observer) {
return NS_OK;
}
AutoRestore running(mIsRunning);
mIsRunning = true;
// This instance was already performed forcibly.
if (observer->mQueuedSender != this) {
return NS_OK;
}
// NOTE: Reset each pending flag because sending notification may cause
// another change.
if (observer->mNeedsToNotifyIMEOfFocusSet) {
observer->mNeedsToNotifyIMEOfFocusSet = false;
SendFocusSet();
observer->mQueuedSender = nullptr;
// If it's not safe to notify IME of focus, SendFocusSet() sets
// mNeedsToNotifyIMEOfFocusSet true again. For guaranteeing to send the
// focus notification later, we should put a new sender into the queue but
// this case must be rare. Note that if mIMEContentObserver is already
// destroyed, mNeedsToNotifyIMEOfFocusSet is never set true again.
if (observer->mNeedsToNotifyIMEOfFocusSet) {
MOZ_ASSERT(!observer->mIMEHasFocus);
MOZ_LOG(sIMECOLog, LogLevel::Debug,
("0x%p IMENotificationSender::Run(), posting "
"IMENotificationSender to current thread",
this));
observer->mQueuedSender = new IMENotificationSender(observer);
observer->mQueuedSender->Dispatch(observer->mDocShell);
return NS_OK;
}
// This is the first notification to IME. So, we don't need to notify
// anymore since IME starts to query content after it gets focus.
observer->ClearPendingNotifications();
return NS_OK;
}
if (observer->mNeedsToNotifyIMEOfTextChange) {
observer->mNeedsToNotifyIMEOfTextChange = false;
SendTextChange();
}
// If a text change notification causes another text change again, we should
// notify IME of that before sending a selection change notification.
if (!observer->mNeedsToNotifyIMEOfTextChange) {
// Be aware, PuppetWidget depends on the order of this. A selection change
// notification should not be sent before a text change notification because
// PuppetWidget shouldn't query new text content every selection change.
if (observer->mNeedsToNotifyIMEOfSelectionChange) {
observer->mNeedsToNotifyIMEOfSelectionChange = false;
SendSelectionChange();
}
}
// If a text change notification causes another text change again or a
// selection change notification causes either a text change or another
// selection change, we should notify IME of those before sending a position
// change notification.
if (!observer->mNeedsToNotifyIMEOfTextChange &&
!observer->mNeedsToNotifyIMEOfSelectionChange) {
if (observer->mNeedsToNotifyIMEOfPositionChange) {
observer->mNeedsToNotifyIMEOfPositionChange = false;
SendPositionChange();
}
}
// Composition event handled notification should be sent after all the
// other notifications because this notifies widget of finishing all pending
// events are handled completely.
if (!observer->mNeedsToNotifyIMEOfTextChange &&
!observer->mNeedsToNotifyIMEOfSelectionChange &&
!observer->mNeedsToNotifyIMEOfPositionChange) {
if (observer->mNeedsToNotifyIMEOfCompositionEventHandled) {
observer->mNeedsToNotifyIMEOfCompositionEventHandled = false;
SendCompositionEventHandled();
}
}
observer->mQueuedSender = nullptr;
// If notifications caused some new change, we should notify them now.
if (observer->NeedsToNotifyIMEOfSomething()) {
if (observer->GetState() == eState_StoppedObserving) {
MOZ_LOG(sIMECOLog, LogLevel::Debug,
("0x%p IMENotificationSender::Run(), waiting "
"IMENotificationSender to be reinitialized",
this));
} else {
MOZ_LOG(sIMECOLog, LogLevel::Debug,
("0x%p IMENotificationSender::Run(), posting "
"IMENotificationSender to current thread",
this));
observer->mQueuedSender = new IMENotificationSender(observer);
observer->mQueuedSender->Dispatch(observer->mDocShell);
}
}
return NS_OK;
}
void IMEContentObserver::IMENotificationSender::SendFocusSet() {
RefPtr observer = GetObserver();
if (!observer) {
return;
}
if (!CanNotifyIME(eChangeEventType_Focus)) {
// If IMEContentObserver has already gone, we don't need to notify IME of
// focus.
MOZ_LOG(sIMECOLog, LogLevel::Warning,
("0x%p IMENotificationSender::SendFocusSet(), Warning, does not "
"send notification due to impossible to notify IME of focus",
this));
observer->ClearPendingNotifications();
return;
}
if (!IsSafeToNotifyIME(eChangeEventType_Focus)) {
MOZ_LOG(
sIMECOLog, LogLevel::Warning,
("0x%p IMENotificationSender::SendFocusSet(), Warning, does not send "
"notification due to unsafe, retrying to send NOTIFY_IME_OF_FOCUS...",
this));
observer->PostFocusSetNotification();
return;
}
observer->mIMEHasFocus = true;
// Initialize selection cache with the first selection data. However, this
// may be handled synchronously when the editor gets focus. In that case,
// some frames may be dirty and they may be required to get caret frame in
// ContentEventHandler::Init() to get the nearest widget from the selection.
// Therefore, we need to update selection cache with flushing the pending
// notifications.
observer->UpdateSelectionCache(true);
MOZ_LOG(sIMECOLog, LogLevel::Info,
("0x%p IMENotificationSender::SendFocusSet(), sending "
"NOTIFY_IME_OF_FOCUS...",
this));
MOZ_RELEASE_ASSERT(observer->mSendingNotification == NOTIFY_IME_OF_NOTHING);
observer->mSendingNotification = NOTIFY_IME_OF_FOCUS;
IMEStateManager::NotifyIME(IMENotification(NOTIFY_IME_OF_FOCUS),
observer->mWidget);
observer->mSendingNotification = NOTIFY_IME_OF_NOTHING;
// IMENotificationRequests referred by ObserveEditableNode() may be different
// before or after widget receives NOTIFY_IME_OF_FOCUS. Therefore, we need
// to guarantee to call ObserveEditableNode() after sending
// NOTIFY_IME_OF_FOCUS.
observer->OnIMEReceivedFocus();
MOZ_LOG(
sIMECOLog, LogLevel::Debug,
("0x%p IMENotificationSender::SendFocusSet(), sent NOTIFY_IME_OF_FOCUS",
this));
}
void IMEContentObserver::IMENotificationSender::SendSelectionChange() {
RefPtr observer = GetObserver();
if (!observer) {
return;
}
if (!CanNotifyIME(eChangeEventType_Selection)) {
MOZ_LOG(sIMECOLog, LogLevel::Warning,
("0x%p IMENotificationSender::SendSelectionChange(), Warning, "
"does not send notification due to impossible to notify IME of "
"selection change",
this));
return;
}
if (!IsSafeToNotifyIME(eChangeEventType_Selection)) {
MOZ_LOG(sIMECOLog, LogLevel::Warning,
("0x%p IMENotificationSender::SendSelectionChange(), Warning, "
"does not send notification due to unsafe, retrying to send "
"NOTIFY_IME_OF_SELECTION_CHANGE...",
this));
observer->PostSelectionChangeNotification();
return;
}
SelectionChangeData lastSelChangeData = observer->mSelectionData;
if (NS_WARN_IF(!observer->UpdateSelectionCache())) {
MOZ_LOG(sIMECOLog, LogLevel::Error,
("0x%p IMENotificationSender::SendSelectionChange(), FAILED, due "
"to UpdateSelectionCache() failure",
this));
return;
}
// The state may be changed since querying content causes flushing layout.
if (!CanNotifyIME(eChangeEventType_Selection)) {
MOZ_LOG(sIMECOLog, LogLevel::Error,
("0x%p IMENotificationSender::SendSelectionChange(), FAILED, due "
"to flushing layout having changed something",
this));
return;
}
// If the selection isn't changed actually, we shouldn't notify IME of
// selection change.
SelectionChangeData& newSelChangeData = observer->mSelectionData;
if (lastSelChangeData.IsInitialized() &&
lastSelChangeData.EqualsRangeAndDirectionAndWritingMode(
newSelChangeData)) {
MOZ_LOG(
sIMECOLog, LogLevel::Debug,
("0x%p IMENotificationSender::SendSelectionChange(), not notifying IME "
"of NOTIFY_IME_OF_SELECTION_CHANGE due to not changed actually",
this));
return;
}
MOZ_LOG(sIMECOLog, LogLevel::Info,
("0x%p IMENotificationSender::SendSelectionChange(), sending "
"NOTIFY_IME_OF_SELECTION_CHANGE... newSelChangeData=%s",
this, ToString(newSelChangeData).c_str()));
IMENotification notification(NOTIFY_IME_OF_SELECTION_CHANGE);
notification.SetData(observer->mSelectionData);
MOZ_RELEASE_ASSERT(observer->mSendingNotification == NOTIFY_IME_OF_NOTHING);
observer->mSendingNotification = NOTIFY_IME_OF_SELECTION_CHANGE;
IMEStateManager::NotifyIME(notification, observer->mWidget);
observer->mSendingNotification = NOTIFY_IME_OF_NOTHING;
MOZ_LOG(sIMECOLog, LogLevel::Debug,
("0x%p IMENotificationSender::SendSelectionChange(), sent "
"NOTIFY_IME_OF_SELECTION_CHANGE",
this));
}
void IMEContentObserver::IMENotificationSender::SendTextChange() {
RefPtr observer = GetObserver();
if (!observer) {
return;
}
if (!CanNotifyIME(eChangeEventType_Text)) {
MOZ_LOG(
sIMECOLog, LogLevel::Warning,
("0x%p IMENotificationSender::SendTextChange(), Warning, does not "
"send notification due to impossible to notify IME of text change",
this));
return;
}
if (!IsSafeToNotifyIME(eChangeEventType_Text)) {
MOZ_LOG(sIMECOLog, LogLevel::Warning,
("0x%p IMENotificationSender::SendTextChange(), Warning, does "
"not send notification due to unsafe, retrying to send "
"NOTIFY_IME_OF_TEXT_CHANGE...",
this));
observer->PostTextChangeNotification();
return;
}
// If text change notification is unnecessary anymore, just cancel it.
if (!observer->NeedsTextChangeNotification()) {
MOZ_LOG(sIMECOLog, LogLevel::Warning,
("0x%p IMENotificationSender::SendTextChange(), Warning, "
"canceling sending NOTIFY_IME_OF_TEXT_CHANGE",
this));
observer->CancelNotifyingIMEOfTextChange();
return;
}
MOZ_LOG(sIMECOLog, LogLevel::Info,
("0x%p IMENotificationSender::SendTextChange(), sending "
"NOTIFY_IME_OF_TEXT_CHANGE... mIMEContentObserver={ "
"mTextChangeData=%s }",
this, ToString(observer->mTextChangeData).c_str()));
IMENotification notification(NOTIFY_IME_OF_TEXT_CHANGE);
notification.SetData(observer->mTextChangeData);
observer->mTextChangeData.Clear();
MOZ_RELEASE_ASSERT(observer->mSendingNotification == NOTIFY_IME_OF_NOTHING);
observer->mSendingNotification = NOTIFY_IME_OF_TEXT_CHANGE;
IMEStateManager::NotifyIME(notification, observer->mWidget);
observer->mSendingNotification = NOTIFY_IME_OF_NOTHING;
MOZ_LOG(sIMECOLog, LogLevel::Debug,
("0x%p IMENotificationSender::SendTextChange(), sent "
"NOTIFY_IME_OF_TEXT_CHANGE",
this));
}
void IMEContentObserver::IMENotificationSender::SendPositionChange() {
RefPtr observer = GetObserver();
if (!observer) {
return;
}
if (!CanNotifyIME(eChangeEventType_Position)) {
MOZ_LOG(sIMECOLog, LogLevel::Verbose,
("0x%p IMENotificationSender::SendPositionChange(), Warning, "
"does not send notification due to impossible to notify IME of "
"position change",
this));
return;
}
if (!IsSafeToNotifyIME(eChangeEventType_Position)) {
MOZ_LOG(sIMECOLog, LogLevel::Verbose,
("0x%p IMENotificationSender::SendPositionChange(), Warning, "
"does not send notification due to unsafe, retrying to send "
"NOTIFY_IME_OF_POSITION_CHANGE...",
this));
observer->PostPositionChangeNotification();
return;
}
// If position change notification is unnecessary anymore, just cancel it.
if (!observer->NeedsPositionChangeNotification()) {
MOZ_LOG(sIMECOLog, LogLevel::Verbose,
("0x%p IMENotificationSender::SendPositionChange(), Warning, "
"canceling sending NOTIFY_IME_OF_POSITION_CHANGE",
this));
observer->CancelNotifyingIMEOfPositionChange();
return;
}
MOZ_LOG(sIMECOLog, LogLevel::Info,
("0x%p IMENotificationSender::SendPositionChange(), sending "
"NOTIFY_IME_OF_POSITION_CHANGE...",
this));
MOZ_RELEASE_ASSERT(observer->mSendingNotification == NOTIFY_IME_OF_NOTHING);
observer->mSendingNotification = NOTIFY_IME_OF_POSITION_CHANGE;
IMEStateManager::NotifyIME(IMENotification(NOTIFY_IME_OF_POSITION_CHANGE),
observer->mWidget);
observer->mSendingNotification = NOTIFY_IME_OF_NOTHING;
MOZ_LOG(sIMECOLog, LogLevel::Debug,
("0x%p IMENotificationSender::SendPositionChange(), sent "
"NOTIFY_IME_OF_POSITION_CHANGE",
this));
}
void IMEContentObserver::IMENotificationSender::SendCompositionEventHandled() {
RefPtr observer = GetObserver();
if (!observer) {
return;
}
if (!CanNotifyIME(eChangeEventType_CompositionEventHandled)) {
MOZ_LOG(sIMECOLog, LogLevel::Warning,
("0x%p IMENotificationSender::SendCompositionEventHandled(), "
"Warning, does not send notification due to impossible to notify "
"IME of composition event handled",
this));
return;
}
if (!IsSafeToNotifyIME(eChangeEventType_CompositionEventHandled)) {
MOZ_LOG(sIMECOLog, LogLevel::Warning,
("0x%p IMENotificationSender::SendCompositionEventHandled(), "
"Warning, does not send notification due to unsafe, retrying to "
"send NOTIFY_IME_OF_POSITION_CHANGE...",
this));
observer->PostCompositionEventHandledNotification();
return;
}
MOZ_LOG(sIMECOLog, LogLevel::Info,
("0x%p IMENotificationSender::SendCompositionEventHandled(), sending "
"NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED...",
this));
MOZ_RELEASE_ASSERT(observer->mSendingNotification == NOTIFY_IME_OF_NOTHING);
observer->mSendingNotification = NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED;
IMEStateManager::NotifyIME(
IMENotification(NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED),
observer->mWidget);
observer->mSendingNotification = NOTIFY_IME_OF_NOTHING;
MOZ_LOG(sIMECOLog, LogLevel::Debug,
("0x%p IMENotificationSender::SendCompositionEventHandled(), sent "
"NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED",
this));
}
/******************************************************************************
* mozilla::IMEContentObserver::DocumentObservingHelper
******************************************************************************/
NS_IMPL_CYCLE_COLLECTION_CLASS(IMEContentObserver::DocumentObserver)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(IMEContentObserver::DocumentObserver)
// StopObserving() releases mIMEContentObserver and mDocument.
tmp->StopObserving();
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(IMEContentObserver::DocumentObserver)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIMEContentObserver)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IMEContentObserver::DocumentObserver)
NS_INTERFACE_MAP_ENTRY(nsIDocumentObserver)
NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(IMEContentObserver::DocumentObserver)
NS_IMPL_CYCLE_COLLECTING_RELEASE(IMEContentObserver::DocumentObserver)
void IMEContentObserver::DocumentObserver::Observe(Document* aDocument) {
MOZ_ASSERT(aDocument);
// Guarantee that aDocument won't be destroyed during a call of
// StopObserving().
RefPtr newDocument = aDocument;
StopObserving();
mDocument = std::move(newDocument);
mDocument->AddObserver(this);
}
void IMEContentObserver::DocumentObserver::StopObserving() {
if (!IsObserving()) {
return;
}
// Grab IMEContentObserver which could be destroyed during method calls.
RefPtr observer = std::move(mIMEContentObserver);
// Stop observing the document first.
RefPtr document = std::move(mDocument);
document->RemoveObserver(this);
// Notify IMEContentObserver of ending of document updates if this already
// notified it of beginning of document updates.
for (; IsUpdating(); --mDocumentUpdating) {
// FYI: IsUpdating() returns true until mDocumentUpdating becomes 0.
// However, IsObserving() returns false now because mDocument was
// already cleared above. Therefore, this method won't be called
// recursively.
observer->EndDocumentUpdate();
}
}
void IMEContentObserver::DocumentObserver::Destroy() {
StopObserving();
mIMEContentObserver = nullptr;
}
void IMEContentObserver::DocumentObserver::BeginUpdate(Document* aDocument) {
if (NS_WARN_IF(Destroyed()) || NS_WARN_IF(!IsObserving())) {
return;
}
mIMEContentObserver->BeginDocumentUpdate();
mDocumentUpdating++;
}
void IMEContentObserver::DocumentObserver::EndUpdate(Document* aDocument) {
if (NS_WARN_IF(Destroyed()) || NS_WARN_IF(!IsObserving()) ||
NS_WARN_IF(!IsUpdating())) {
return;
}
mDocumentUpdating--;
mIMEContentObserver->EndDocumentUpdate();
}
/******************************************************************************
* mozilla::IMEContentObserver::FlatTextCache
******************************************************************************/
void IMEContentObserver::FlatTextCache::Clear(const char* aCallerName) {
if (!HasCache()) {
return;
}
MOZ_LOG(sCacheLog, LogLevel::Info,
("%s.Clear: called by %s", mInstanceName, aCallerName));
mContainerNode = nullptr;
mContent = nullptr;
mFlatTextLength = 0;
}
nsresult IMEContentObserver::FlatTextCache::
ComputeAndCacheFlatTextLengthBeforeEndOfContent(
const char* aCallerName, const nsIContent& aContent,
const Element* aRootElement) {
MOZ_ASSERT(aRootElement);
MOZ_ASSERT(aContent.GetParentNode());
uint32_t length = 0;
nsresult rv = ContentEventHandler::GetFlatTextLengthInRange(
RawNodePosition::BeforeFirstContentOf(*aRootElement),
RawNodePosition::After(aContent), aRootElement, &length,
LineBreakType::LINE_BREAK_TYPE_NATIVE);
if (NS_FAILED(rv)) {
Clear(aCallerName);
return rv;
}
CacheFlatTextLengthBeforeEndOfContent(aCallerName, aContent, length,
aRootElement);
return NS_OK;
}
void IMEContentObserver::FlatTextCache::CacheFlatTextLengthBeforeEndOfContent(
const char* aCallerName, const nsIContent& aContent,
uint32_t aFlatTextLength, const dom::Element* aRootElement) {
mContainerNode = aContent.GetParentNode();
mContent = const_cast(&aContent);
mFlatTextLength = aFlatTextLength;
MOZ_ASSERT(IsCachingToEndOfContent());
MOZ_LOG(sCacheLog, LogLevel::Info,
("%s.%s: called by %s -> %s", mInstanceName, __FUNCTION__,
aCallerName, ToString(*this).c_str()));
AssertValidCache(aRootElement);
}
nsresult IMEContentObserver::FlatTextCache::
ComputeAndCacheFlatTextLengthBeforeFirstContent(
const char* aCallerName, const nsINode& aContainer,
const Element* aRootElement) {
MOZ_ASSERT(aRootElement);
const Result
lengthIncludingLineBreakCausedByOpenTagOfContainer =
FlatTextCache::ComputeTextLengthBeforeFirstContentOf(aContainer,
aRootElement);
if (MOZ_UNLIKELY(
lengthIncludingLineBreakCausedByOpenTagOfContainer.isErr())) {
Clear(__FUNCTION__);
return lengthIncludingLineBreakCausedByOpenTagOfContainer.inspectErr();
}
CacheFlatTextLengthBeforeFirstContent(
aCallerName, aContainer,
lengthIncludingLineBreakCausedByOpenTagOfContainer.inspect(),
aRootElement);
return NS_OK;
}
void IMEContentObserver::FlatTextCache::CacheFlatTextLengthBeforeFirstContent(
const char* aCallerName, const nsINode& aContainer,
uint32_t aFlatTextLength, const dom::Element* aRootElement) {
mContainerNode = const_cast(&aContainer);
mContent = nullptr;
mFlatTextLength = aFlatTextLength;
MOZ_ASSERT(IsCachingToStartOfContainer());
MOZ_LOG(sCacheLog, LogLevel::Info,
("%s.%s: called by %s -> %s", mInstanceName, __FUNCTION__,
aCallerName, ToString(*this).c_str()));
AssertValidCache(aRootElement);
}
Maybe
IMEContentObserver::FlatTextCache::GetFlatTextLengthBeforeContent(
const nsIContent& aContent, const dom::Element* aRootElement,
ForRemoval aForRemoval) const {
MOZ_ASSERT(aRootElement);
if (!mContainerNode) {
return Nothing();
}
nsIContent* const prevSibling = aContent.GetPreviousSibling();
if (IsCachingToStartOfContainer()) {
MOZ_ASSERT(!mContent);
// If aContent is the first child of mContainerNode and we're caching text
// length before first child of mContainerNode, we're caching the result
// as-is..
if (!prevSibling && mContainerNode == aContent.GetParentNode()) {
return Some(mFlatTextLength);
}
return Nothing();
}
MOZ_ASSERT(IsCachingToEndOfContent());
MOZ_ASSERT(mContent);
// If we're caching text length before end of previous sibling of aContent,
// the cached length is the result of this call.
if (mContent == prevSibling) {
return Some(mFlatTextLength);
}
// If we're caching text length before end of aContent, aContent siblings
// may be being removed backward because aContent is the previous sibling of
// previously removed node. We should return the length with computing the
// text length of aContent because it's much faster than computing the length
// starting from the root element especially when there are a lot of preceding
// content.
if (mContent == &aContent) {
const Result textLength =
FlatTextCache::ComputeTextLengthOfContent(aContent, aRootElement,
aForRemoval);
if (NS_WARN_IF(textLength.isErr()) ||
NS_WARN_IF(mFlatTextLength < textLength.inspect())) {
return Nothing();
}
return Some(mFlatTextLength - textLength.inspect());
}
return Nothing();
}
Maybe IMEContentObserver::FlatTextCache::GetFlatTextOffsetOnInsertion(
const nsIContent& aFirstContent, const nsIContent& aLastContent,
const dom::Element* aRootElement) const {
MOZ_ASSERT(aRootElement);
MOZ_ASSERT(aFirstContent.GetParentNode() == aLastContent.GetParentNode());
MOZ_ASSERT(!aFirstContent.IsBeingRemoved());
MOZ_ASSERT(!aLastContent.IsBeingRemoved());
if (!mContainerNode || mContainerNode != aFirstContent.GetParentNode()) {
return Nothing();
}
if (IsCachingToStartOfContainer()) {
MOZ_ASSERT(!mContent);
// If aFirstContent is the first child of mContainerNode, we're caching the
// result as-is.
if (mContainerNode->GetFirstChild() == &aFirstContent) {
return Some(mFlatTextLength);
}
return Nothing();
}
MOZ_ASSERT(IsCachingToEndOfContent());
MOZ_ASSERT(mContent);
MOZ_ASSERT(mContent != &aFirstContent);
MOZ_ASSERT(mContent != &aLastContent);
// When the content nodes are inserted forward, we may cache text length
// before end of last inserted content. If so, mContent should be the
// previous sibling of aFirstContent. Then, we can return the cached length
// simply.
if (mContent == aFirstContent.GetPreviousSibling()) {
return Some(mFlatTextLength);
}
// When the content nodes inserted backward, we may cache text length before
// the end of the last inserted content which is next or latter sibling of
// aLastContent. In this case, we can compute the length with the cache with
// computing text length starting from the next sibling of aLastContent to
// mContent which were previously inserted. That must be faster than
// computing the length starting from the root element.
if (mContent == aLastContent.GetNextSibling() ||
aLastContent.ComputeIndexInParentNode().valueOr(UINT32_MAX) <
mContent->ComputeIndexInParentNode().valueOr(0u)) {
Result previouslyInsertedTextLengthOrError =
FlatTextCache::ComputeTextLengthStartOfContentToEndOfContent(
*aLastContent.GetNextSibling(), *mContent, aRootElement);
if (NS_WARN_IF(previouslyInsertedTextLengthOrError.isErr()) ||
NS_WARN_IF(mFlatTextLength <
previouslyInsertedTextLengthOrError.inspect())) {
return Nothing();
}
// mFlatTextLength contains the last inserted text length, but it does not
// contain text length starting from aFirstContent to aLastContent.
// Therefore, subtracting the last inserted text length from mFlatTextLength
// equals the text length before aFirstContent.
return Some(mFlatTextLength - previouslyInsertedTextLengthOrError.unwrap());
}
return Nothing();
}
/* static */
Result
IMEContentObserver::FlatTextCache::ComputeTextLengthOfContent(
const nsIContent& aContent, const dom::Element* aRootElement,
ForRemoval aForRemoval) {
MOZ_ASSERT(aRootElement);
if (const Text* textNode = Text::FromNode(aContent)) {
return ContentEventHandler::GetNativeTextLength(*textNode);
}
if (aForRemoval == ForRemoval::Yes) {
// When we compute the text length of the removing content node, we need to
// select all children in the removing node because of the same reason
// above. Therefore, if a is being removed, we want to compute
// `{
...}
`. In this case, we want to include the open tag of
// aRemovingContent if it's an element to add the line break if it's caused
// by the open tag. However, we have no way to specify it with
// RawNodePosition, but ContentEventHandler::GetFlatTextLengthInRange()
// treats the range as the start container is selected. Therefore, we
// should use a RawNodePosition setting its container to the removed node.
uint32_t textLength = 0;
RawNodePosition start(const_cast
(&aContent), 0u);
start.mAfterOpenTag = false;
nsresult rv = ContentEventHandler::GetFlatTextLengthInRange(
start, RawNodePosition::AtEndOf(aContent), aRootElement, &textLength,
LineBreakType::LINE_BREAK_TYPE_NATIVE, /* aIsRemovingNode = */ true);
if (NS_FAILED(rv)) {
return Err(rv);
}
return textLength;
}
return ComputeTextLengthStartOfContentToEndOfContent(aContent, aContent,
aRootElement);
}
/* static */
Result
IMEContentObserver::FlatTextCache::ComputeTextLengthBeforeContent(
const nsIContent& aContent, const dom::Element* aRootElement) {
uint32_t textLengthBeforeContent = 0;
nsresult rv = ContentEventHandler::GetFlatTextLengthInRange(
RawNodePosition::BeforeFirstContentOf(*aRootElement),
RawNodePosition::Before(aContent), aRootElement, &textLengthBeforeContent,
LineBreakType::LINE_BREAK_TYPE_NATIVE);
if (NS_FAILED(rv)) {
return Err(rv);
}
return textLengthBeforeContent;
}
/* static */
Result IMEContentObserver::FlatTextCache::
ComputeTextLengthStartOfContentToEndOfContent(
const nsIContent& aStartContent, const nsIContent& aEndContent,
const dom::Element* aRootElement) {
uint32_t textLength = 0;
nsresult rv = ContentEventHandler::GetFlatTextLengthInRange(
RawNodePosition::Before(aStartContent),
RawNodePosition::After(aEndContent), aRootElement, &textLength,
LineBreakType::LINE_BREAK_TYPE_NATIVE);
if (NS_FAILED(rv)) {
return Err(rv);
}
return textLength;
}
/* static */
Result
IMEContentObserver::FlatTextCache::ComputeTextLengthBeforeFirstContentOf(
const nsINode& aContainer, const dom::Element* aRootElement) {
uint32_t lengthIncludingLineBreakCausedByOpenTagOfContent = 0;
nsresult rv = ContentEventHandler::GetFlatTextLengthInRange(
RawNodePosition::BeforeFirstContentOf(*aRootElement),
// Include the line break caused by open tag of aContainer if it's an
// element when we cache text length before first content of aContainer.
RawNodePosition(const_cast(&aContainer), nullptr), aRootElement,
&lengthIncludingLineBreakCausedByOpenTagOfContent,
LineBreakType::LINE_BREAK_TYPE_NATIVE);
if (NS_FAILED(rv)) {
return Err(rv);
}
return lengthIncludingLineBreakCausedByOpenTagOfContent;
}
void IMEContentObserver::FlatTextCache::AssertValidCache(
const Element* aRootElement) const {
#ifdef DEBUG
if (MOZ_LIKELY(
!StaticPrefs::test_ime_content_observer_assert_valid_cache())) {
return;
}
MOZ_ASSERT(aRootElement);
if (!mContainerNode) {
return;
}
MOZ_ASSERT(mContainerNode->IsInclusiveDescendantOf(aRootElement));
MOZ_ASSERT_IF(mContent, mContent->IsInclusiveDescendantOf(aRootElement));
if (IsCachingToEndOfContent()) {
MOZ_ASSERT(mContent);
Result offset =
FlatTextCache::ComputeTextLengthBeforeContent(*mContent, aRootElement);
MOZ_ASSERT(offset.isOk());
Result length =
FlatTextCache::ComputeTextLengthStartOfContentToEndOfContent(
*mContent, *mContent, aRootElement);
MOZ_ASSERT(length.isOk());
if (mFlatTextLength != offset.inspect() + length.inspect()) {
nsAutoString innerHTMLOfEditable;
const_cast(aRootElement)
->GetInnerHTML(innerHTMLOfEditable, IgnoreErrors());
NS_WARNING(
nsPrintfCString(
"mFlatTextLength=%u, offset: %u, length: %u, mContainerNode:%s, "
"mContent=%s (%s)",
mFlatTextLength, offset.inspect(), length.inspect(),
ToString(mContainerNode).c_str(), ToString(*mContent).c_str(),
NS_ConvertUTF16toUTF8(innerHTMLOfEditable).get())
.get());
}
MOZ_ASSERT(mFlatTextLength == offset.inspect() + length.inspect());
return;
}
MOZ_ASSERT(!mContent);
MOZ_ASSERT(mContainerNode->IsContent());
Result offset =
ComputeTextLengthBeforeFirstContentOf(*mContainerNode, aRootElement);
MOZ_ASSERT(offset.isOk());
if (mFlatTextLength != offset.inspect()) {
nsAutoString innerHTMLOfEditable;
const_cast(aRootElement)
->GetInnerHTML(innerHTMLOfEditable, IgnoreErrors());
NS_WARNING(nsPrintfCString(
"mFlatTextLength=%u, offset: %u, mContainerNode:%s (%s)",
mFlatTextLength, offset.inspect(),
ToString(mContainerNode).c_str(),
NS_ConvertUTF16toUTF8(innerHTMLOfEditable).get())
.get());
}
MOZ_ASSERT(mFlatTextLength == offset.inspect());
#endif // #ifdef DEBUG
}
void IMEContentObserver::FlatTextCache::ContentAdded(
const char* aCallerName, const nsIContent& aFirstContent,
const nsIContent& aLastContent, const Maybe& aAddedFlatTextLength,
const Element* aRootElement) {
MOZ_ASSERT(nsContentUtils::ComparePoints(
RawRangeBoundary(aFirstContent.GetParentNode(),
aFirstContent.GetPreviousSibling()),
RawRangeBoundary(aLastContent.GetParentNode(),
aLastContent.GetPreviousSibling()))
.value() <= 0);
if (!mContainerNode) {
return; // No cache.
}
// We can keep cache without anything if the next sibling is the first added
// content.
if (mContent && &aFirstContent == mContent->GetNextSibling()) {
return;
}
if (IsCachingToStartOfContainer()) {
MOZ_ASSERT(!mContent);
// We can keep the cache if added nodes are children of mContainerNode since
// we cache the text length before its first child.
if (mContainerNode == aFirstContent.GetParentNode()) {
AssertValidCache(aRootElement);
return;
}
// Let's clear the cache for avoiding to do anything expensive for a hot
// path only for not frequent cases. Be aware, this is a hot code path
// here. Therefore, expensive computation would make the DOM mutation
// slower.
Clear(aCallerName);
return;
}
MOZ_ASSERT(IsCachingToEndOfContent());
MOZ_ASSERT(mContent);
if (aAddedFlatTextLength.isSome() &&
aLastContent.GetNextSibling() == mContent) {
// If we cache test length before end of next sibling of the last added
// content node, we can update the cached text simply.
CacheFlatTextLengthBeforeEndOfContent(
aCallerName, *mContent, mFlatTextLength + *aAddedFlatTextLength,
aRootElement);
return;
}
// Let's clear the cache for avoiding to do anything expensive for a hot
// path only for not frequent cases. Be aware, this is a hot code path here.
// Therefore, expensive computation would make the DOM mutation slower.
Clear(aCallerName);
}
void IMEContentObserver::FlatTextCache::ContentWillBeRemoved(
const nsIContent& aContent, uint32_t aFlatTextLengthOfContent,
const Element* aRootElement) {
if (!mContainerNode) {
return; // No cache.
}
// We can keep the cache without anything if the next sibling is removed.
if (mContent && mContent == aContent.GetPreviousSibling()) {
return;
}
if (IsCachingToStartOfContainer()) {
MOZ_ASSERT(!mContent);
// We're caching text length before first child of mContainerNode.
// Therefore, if a child of mContainerNode is being removed, we can keep the
// cache.
if (mContainerNode == aContent.GetParentNode()) {
AssertValidCache(aRootElement);
return;
}
// Let's clear the cache for avoiding to do anything expensive for a hot
// path only for not frequent cases. Be aware, this is a hot code path
// here. Therefore, expensive computation would make the DOM mutation
// slower.
Clear("FlatTextCache::ContentRemoved");
return;
}
MOZ_ASSERT(IsCachingToEndOfContent());
if (&aContent == mContent) {
MOZ_ASSERT(mFlatTextLength >= aFlatTextLengthOfContent);
if (NS_WARN_IF(mFlatTextLength < aFlatTextLengthOfContent)) {
Clear("FlatTextCache::ContentRemoved");
return;
}
// We're caching text length before end of aContent. So, if there is a
// previous sibling, we can cache text length before aContent with
// subtracting the text length caused by aContent from the cached value.
if (nsIContent* prevSibling = aContent.GetPreviousSibling()) {
CacheFlatTextLengthBeforeEndOfContent(
"FlatTextCache::ContentRemoved", *prevSibling,
mFlatTextLength - aFlatTextLengthOfContent, aRootElement);
return;
}
// Otherwise, i.e., if aContent is first child of mContainerNode, we can
// cache text length before first content of mContainerNode with subtracting
// the text length caused by aContent from the cached value.
CacheFlatTextLengthBeforeFirstContent(
"FlatTextCache::ContentRemoved", *mContainerNode,
mFlatTextLength - aFlatTextLengthOfContent, aRootElement);
return;
}
// Let's clear the cache for avoiding to do anything expensive for a hot
// path only for not frequent cases. Be aware, this is a hot code path here.
// Therefore, expensive computation would make the DOM mutation slower.
Clear("FlatTextCache::ContentRemoved");
}
/******************************************************************************
* mozilla::IMEContentObserver::AddedContentCache
******************************************************************************/
void IMEContentObserver::AddedContentCache::Clear(const char* aCallerName) {
mFirst = nullptr;
mLast = nullptr;
MOZ_LOG(sCacheLog, LogLevel::Info,
("AddedContentCache::Clear: called by %s", aCallerName));
}
bool IMEContentObserver::AddedContentCache::IsInRange(
const nsIContent& aContent, const dom::Element* aRootElement) const {
MOZ_ASSERT(HasCache());
// First, try to find sibling of mFirst from the ancestor chain of aContent.
const nsIContent* sibling = [&]() -> const nsIContent* {
const nsIContent* maybeSibling = &aContent;
const nsIContent* const container = mFirst->GetParent();
for (const nsIContent* ancestor : aContent.AncestorsOfType()) {
if (ancestor == container) {
return maybeSibling;
}
if (ancestor == aRootElement) {
return nullptr;
}
maybeSibling = ancestor;
}
return nullptr;
}();
if (!sibling) {
return false; // Not in same container node
}
// Let's avoid to compute indices...
if (mFirst == sibling || mLast == sibling ||
(mFirst != mLast && (mFirst->GetNextSibling() == sibling ||
sibling->GetNextSibling() == mLast))) {
return true;
}
if (mFirst == mLast || sibling->GetNextSibling() == mFirst ||
mLast->GetNextSibling() == sibling || !sibling->GetPreviousSibling() ||
!sibling->GetNextSibling()) {
return false;
}
const Maybe index = aContent.ComputeIndexInParentNode();
MOZ_ASSERT(index.isSome());
const Maybe firstIndex = mFirst->ComputeIndexInParentNode();
MOZ_ASSERT(firstIndex.isSome());
const Maybe lastIndex = mLast->ComputeIndexInParentNode();
MOZ_ASSERT(lastIndex.isSome());
return firstIndex.value() < index.value() &&
index.value() < lastIndex.value();
}
bool IMEContentObserver::AddedContentCache::CanMergeWith(
const nsIContent& aFirstContent, const nsIContent& aLastContent,
const dom::Element* aRootElement) const {
MOZ_ASSERT(HasCache());
if (aLastContent.GetNextSibling() == mFirst ||
mLast->GetNextSibling() == &aFirstContent) {
return true;
}
MOZ_DIAGNOSTIC_ASSERT(aFirstContent.GetParentNode() ==
aLastContent.GetParentNode());
if (mFirst->GetParentNode() != aFirstContent.GetParentNode()) {
return false;
}
const Maybe newFirstIndex =
aFirstContent.ComputeIndexInParentNode();
MOZ_RELEASE_ASSERT(newFirstIndex.isSome());
const Maybe newLastIndex =
&aFirstContent == &aLastContent ? newFirstIndex
: aLastContent.ComputeIndexInParentNode();
MOZ_RELEASE_ASSERT(newLastIndex.isSome());
const Maybe currentFirstIndex = mFirst->ComputeIndexInParentNode();
MOZ_RELEASE_ASSERT(currentFirstIndex.isSome());
const Maybe currentLastIndex =
mFirst == mLast ? currentFirstIndex : mLast->ComputeIndexInParentNode();
MOZ_RELEASE_ASSERT(currentLastIndex.isSome());
MOZ_ASSERT(!(newFirstIndex.value() < currentFirstIndex.value() &&
newLastIndex.value() > currentLastIndex.value()),
"New content nodes shouldn't contain mFirst nor mLast");
MOZ_ASSERT(!(newFirstIndex.value() < currentFirstIndex.value() &&
newLastIndex.value() > currentFirstIndex.value()),
"New content nodes shouldn't contain mFirst");
MOZ_ASSERT(!(newFirstIndex.value() < currentLastIndex.value() &&
newLastIndex.value() > currentLastIndex.value()),
"New content nodes shouldn't contain mLast");
return *newFirstIndex > *currentFirstIndex &&
*newLastIndex < *currentLastIndex;
}
bool IMEContentObserver::AddedContentCache::TryToCache(
const nsIContent& aFirstContent, const nsIContent& aLastContent,
const dom::Element* aRootElement) {
if (!HasCache()) {
mFirst = const_cast(&aFirstContent);
mLast = const_cast(&aLastContent);
MOZ_LOG(
sCacheLog, LogLevel::Info,
("AddedContentCache::TryToCache: Starting to cache the range: %s - %s",
ToString(mFirst).c_str(), ToString(mLast).c_str()));
return true;
}
MOZ_ASSERT(mFirst != &aFirstContent);
MOZ_ASSERT(mLast != &aLastContent);
if (aLastContent.GetNextSibling() == mFirst) {
MOZ_ASSERT(CanMergeWith(aFirstContent, aLastContent, aRootElement));
mFirst = const_cast(&aFirstContent);
MOZ_LOG(
sCacheLog, LogLevel::Info,
("AddedContentCache::TryToCache: Extending the range backward (to %s)",
ToString(mFirst).c_str()));
return true;
}
if (mLast->GetNextSibling() == &aFirstContent) {
MOZ_ASSERT(CanMergeWith(aFirstContent, aLastContent, aRootElement));
mLast = const_cast(&aLastContent);
MOZ_LOG(
sCacheLog, LogLevel::Info,
("AddedContentCache::TryToCache: Extending the range forward (to %s)",
ToString(mLast).c_str()));
return true;
}
MOZ_DIAGNOSTIC_ASSERT(aFirstContent.GetParentNode() ==
aLastContent.GetParentNode());
if (mFirst->GetParentNode() != aFirstContent.GetParentNode()) {
MOZ_ASSERT(!CanMergeWith(aFirstContent, aLastContent, aRootElement));
return false;
}
const Maybe newFirstIndex =
aFirstContent.ComputeIndexInParentNode();
MOZ_RELEASE_ASSERT(newFirstIndex.isSome());
const Maybe newLastIndex =
&aFirstContent == &aLastContent ? newFirstIndex
: aLastContent.ComputeIndexInParentNode();
MOZ_RELEASE_ASSERT(newLastIndex.isSome());
const Maybe currentFirstIndex = mFirst->ComputeIndexInParentNode();
MOZ_RELEASE_ASSERT(currentFirstIndex.isSome());
const Maybe currentLastIndex =
mFirst == mLast ? currentFirstIndex : mLast->ComputeIndexInParentNode();
MOZ_RELEASE_ASSERT(currentLastIndex.isSome());
MOZ_ASSERT(!(newFirstIndex.value() < currentFirstIndex.value() &&
newLastIndex.value() > currentLastIndex.value()),
"New content nodes shouldn't contain mFirst nor mLast");
MOZ_ASSERT(!(newFirstIndex.value() < currentFirstIndex.value() &&
newLastIndex.value() > currentFirstIndex.value()),
"New content nodes shouldn't contain mFirst");
MOZ_ASSERT(!(newFirstIndex.value() < currentLastIndex.value() &&
newLastIndex.value() > currentLastIndex.value()),
"New content nodes shouldn't contain mLast");
if (*newFirstIndex > *currentFirstIndex &&
*newLastIndex < *currentLastIndex) {
MOZ_ASSERT(CanMergeWith(aFirstContent, aLastContent, aRootElement));
MOZ_LOG(sCacheLog, LogLevel::Info,
("AddedContentCache::TryToCache: New nodes in the range"));
return true;
}
MOZ_ASSERT(!CanMergeWith(aFirstContent, aLastContent, aRootElement));
return false;
}
Result, nsresult> IMEContentObserver::
AddedContentCache::ComputeFlatTextRangeBeforeInsertingNewContent(
const nsIContent& aNewFirstContent, const nsIContent& aNewLastContent,
const dom::Element* aRootElement,
OffsetAndLengthAdjustments& aDifferences) const {
MOZ_ASSERT(HasCache());
const Maybe newLastContentComparedWithCachedFirstContent =
nsContentUtils::ComparePoints(
RawRangeBoundary(aNewLastContent.GetParentNode(),
aNewLastContent.GetPreviousSibling()),
RawRangeBoundary(mFirst->GetParentNode(),
mFirst->GetPreviousSibling()));
MOZ_RELEASE_ASSERT(newLastContentComparedWithCachedFirstContent.isSome());
MOZ_ASSERT(*newLastContentComparedWithCachedFirstContent != 0);
MOZ_ASSERT((*nsContentUtils::ComparePoints(
RawRangeBoundary(aNewFirstContent.GetParentNode(),
aNewFirstContent.GetPreviousSibling()),
RawRangeBoundary(mFirst->GetParentNode(),
mFirst->GetPreviousSibling())) > 0) ==
(*newLastContentComparedWithCachedFirstContent > 0),
"New nodes shouldn't contain mFirst");
const Maybe newFirstContentComparedWithCachedLastContent =
mLast->GetNextSibling() == &aNewFirstContent
? Some(1)
: nsContentUtils::ComparePoints(
RawRangeBoundary(aNewFirstContent.GetParentNode(),
aNewFirstContent.GetPreviousSibling()),
// aNewFirstContent and aNewLastContent may be descendants of
// mLast. Then, we need to ignore the new length. Therefore,
// we need to compare aNewFirstContent position with next
// sibling of mLast.
RawRangeBoundary(mLast->GetParentNode(), mLast));
MOZ_RELEASE_ASSERT(newFirstContentComparedWithCachedLastContent.isSome());
MOZ_ASSERT(*newFirstContentComparedWithCachedLastContent != 0);
MOZ_ASSERT((*newFirstContentComparedWithCachedLastContent > 0) ==
(*nsContentUtils::ComparePoints(
RawRangeBoundary(aNewLastContent.GetParentNode(),
aNewLastContent.GetPreviousSibling()),
RawRangeBoundary(mLast->GetParentNode(), mLast)) > 0),
"New nodes shouldn't contain mLast");
Result length =
FlatTextCache::ComputeTextLengthStartOfContentToEndOfContent(
*mFirst, *mLast, aRootElement);
if (NS_WARN_IF(length.isErr())) {
return length.propagateErr();
}
Result offset =
FlatTextCache::ComputeTextLengthBeforeContent(*mFirst, aRootElement);
if (NS_WARN_IF(offset.isErr())) {
return offset.propagateErr();
}
// If new content nodes are after the cached range, we can just ignore the
// new content nodes.
if (*newFirstContentComparedWithCachedLastContent == 1u) {
aDifferences = OffsetAndLengthAdjustments{0, 0};
return std::make_pair(offset.inspect(), length.inspect());
}
Result newLength =
FlatTextCache::ComputeTextLengthStartOfContentToEndOfContent(
aNewFirstContent, aNewLastContent, aRootElement);
if (NS_WARN_IF(newLength.isErr())) {
return newLength.propagateErr();
}
// If new content nodes are in the cached range, we need to subtract the new
// content length from cached content length.
if (*newLastContentComparedWithCachedFirstContent == 1u) {
MOZ_RELEASE_ASSERT(length.inspect() >= newLength.inspect());
aDifferences = OffsetAndLengthAdjustments{0, newLength.inspect()};
return std::make_pair(offset.inspect(),
length.inspect() - newLength.inspect());
}
// If new content nodes are before the cached range, we need to subtract the
// new content length from cached offset.
MOZ_RELEASE_ASSERT(offset.inspect() >= newLength.inspect());
aDifferences = OffsetAndLengthAdjustments{newLength.inspect(), 0};
return std::make_pair(offset.inspect() - newLength.inspect(),
length.inspect());
}
} // namespace mozilla