element.
// TextEditor assumes this structure when it handles editing commands.
// Therefore, it's safe to assume same things here.
MOZ_ASSERT(mRootElement->GetFirstChild() == aContent);
if (aInfo.mChangeStart) {
offset = ContentEventHandler::GetNativeTextLength(*aContent->AsText(), 0,
aInfo.mChangeStart);
}
} else {
nsresult rv = ContentEventHandler::GetFlatTextLengthInRange(
RawNodePosition(mRootElement, 0u),
RawNodePosition(aContent, aInfo.mChangeStart), mRootElement, &offset,
LINE_BREAK_TYPE_NATIVE);
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
}
uint32_t newLength = ContentEventHandler::GetNativeTextLength(
*aContent->AsText(), aInfo.mChangeStart,
aInfo.mChangeStart + aInfo.mReplaceLength);
uint32_t oldEnd = offset + static_cast
(removedLength);
uint32_t newEnd = offset + newLength;
TextChangeData data(offset, oldEnd, newEnd,
IsEditorHandlingEventForComposition(),
IsEditorComposing());
MaybeNotifyIMEOfTextChange(data);
}
void IMEContentObserver::NotifyContentAdded(nsINode* aContainer,
nsIContent* aFirstContent,
nsIContent* aLastContent) {
if (!NeedsTextChangeNotification() ||
!nsContentUtils::IsInSameAnonymousTree(mRootElement, aFirstContent)) {
return;
}
MOZ_ASSERT_IF(aFirstContent, aFirstContent->GetParentNode() == aContainer);
MOZ_ASSERT_IF(aLastContent, aLastContent->GetParentNode() == aContainer);
mStartOfRemovingTextRangeCache.Clear();
// If it's in a document change, nodes are added consecutively. Therefore,
// if we cache the first node and the last node, we need to compute the
// range once.
// FYI: This is not true if the change caused by an operation in the editor.
if (IsInDocumentChange()) {
// Now, mEndOfAddedTextCache may be invalid if node is added before
// the last node in mEndOfAddedTextCache. Clear it.
mEndOfAddedTextCache.Clear();
if (!HasAddedNodesDuringDocumentChange()) {
mFirstAddedContainer = mLastAddedContainer = aContainer;
mFirstAddedContent = aFirstContent;
mLastAddedContent = aLastContent;
MOZ_LOG(sIMECOLog, LogLevel::Debug,
("0x%p NotifyContentAdded(), starts to store consecutive added "
"nodes",
this));
return;
}
// If first node being added is not next node of the last node,
// notify IME of the previous range first, then, restart to cache the
// range.
if (NS_WARN_IF(!IsNextNodeOfLastAddedNode(aContainer, aFirstContent))) {
// Flush the old range first.
MaybeNotifyIMEOfAddedTextDuringDocumentChange();
mFirstAddedContainer = aContainer;
mFirstAddedContent = aFirstContent;
MOZ_LOG(sIMECOLog, LogLevel::Debug,
("0x%p NotifyContentAdded(), starts to store consecutive added "
"nodes",
this));
}
mLastAddedContainer = aContainer;
mLastAddedContent = aLastContent;
return;
}
MOZ_ASSERT(!HasAddedNodesDuringDocumentChange(),
"The cache should be cleared when document change finished");
uint32_t offset = 0;
nsresult rv = NS_OK;
if (!mEndOfAddedTextCache.Match(aContainer,
aFirstContent->GetPreviousSibling())) {
mEndOfAddedTextCache.Clear();
rv = ContentEventHandler::GetFlatTextLengthInRange(
RawNodePosition(mRootElement, 0u),
RawNodePositionBefore(aContainer,
PointBefore(aContainer, aFirstContent)),
mRootElement, &offset, LINE_BREAK_TYPE_NATIVE);
if (NS_WARN_IF(NS_FAILED((rv)))) {
return;
}
} else {
offset = mEndOfAddedTextCache.mFlatTextLength;
}
// get offset at the end of the last added node
uint32_t addingLength = 0;
rv = ContentEventHandler::GetFlatTextLengthInRange(
RawNodePositionBefore(aContainer, PointBefore(aContainer, aFirstContent)),
RawNodePosition(aContainer, aLastContent), mRootElement, &addingLength,
LINE_BREAK_TYPE_NATIVE);
if (NS_WARN_IF(NS_FAILED((rv)))) {
mEndOfAddedTextCache.Clear();
return;
}
// If multiple lines are being inserted in an HTML editor, next call of
// NotifyContentAdded() is for adding next node. Therefore, caching the text
// length can skip to compute the text length before the adding node and
// before of it.
mEndOfAddedTextCache.Cache(aContainer, aLastContent, offset + addingLength);
if (!addingLength) {
return;
}
TextChangeData data(offset, offset, offset + addingLength,
IsEditorHandlingEventForComposition(),
IsEditorComposing());
MaybeNotifyIMEOfTextChange(data);
}
void IMEContentObserver::ContentAppended(nsIContent* aFirstNewContent) {
nsIContent* parent = aFirstNewContent->GetParent();
MOZ_ASSERT(parent);
NotifyContentAdded(parent, aFirstNewContent, parent->GetLastChild());
}
void IMEContentObserver::ContentInserted(nsIContent* aChild) {
MOZ_ASSERT(aChild);
NotifyContentAdded(aChild->GetParentNode(), aChild, aChild);
}
void IMEContentObserver::ContentRemoved(nsIContent* aChild,
nsIContent* aPreviousSibling) {
if (!NeedsTextChangeNotification() ||
!nsContentUtils::IsInSameAnonymousTree(mRootElement, aChild)) {
return;
}
mEndOfAddedTextCache.Clear();
MaybeNotifyIMEOfAddedTextDuringDocumentChange();
nsINode* containerNode = aChild->GetParentNode();
MOZ_ASSERT(containerNode);
uint32_t offset = 0;
nsresult rv = NS_OK;
if (!mStartOfRemovingTextRangeCache.Match(containerNode, aPreviousSibling)) {
// At removing a child node of aContainer, we need the line break caused
// by open tag of aContainer. Be careful when aPreviousSibling is nullptr.
rv = ContentEventHandler::GetFlatTextLengthInRange(
RawNodePosition(mRootElement, 0u),
RawNodePosition(containerNode, aPreviousSibling), mRootElement, &offset,
LINE_BREAK_TYPE_NATIVE);
if (NS_WARN_IF(NS_FAILED(rv))) {
mStartOfRemovingTextRangeCache.Clear();
return;
}
mStartOfRemovingTextRangeCache.Cache(containerNode, aPreviousSibling,
offset);
} else {
offset = mStartOfRemovingTextRangeCache.mFlatTextLength;
}
// get offset at the end of the deleted node
uint32_t textLength = 0;
if (const Text* textNode = Text::FromNode(aChild)) {
textLength = ContentEventHandler::GetNativeTextLength(*textNode);
} else {
nsresult rv = ContentEventHandler::GetFlatTextLengthInRange(
RawNodePositionBefore(aChild, 0u),
RawNodePosition(aChild, aChild->GetChildCount()), mRootElement,
&textLength, LINE_BREAK_TYPE_NATIVE, true);
if (NS_WARN_IF(NS_FAILED(rv))) {
mStartOfRemovingTextRangeCache.Clear();
return;
}
}
if (!textLength) {
return;
}
TextChangeData data(offset, offset + textLength, offset,
IsEditorHandlingEventForComposition(),
IsEditorComposing());
MaybeNotifyIMEOfTextChange(data);
}
void IMEContentObserver::ClearAddedNodesDuringDocumentChange() {
mFirstAddedContainer = mLastAddedContainer = nullptr;
mFirstAddedContent = mLastAddedContent = nullptr;
MOZ_LOG(sIMECOLog, LogLevel::Debug,
("0x%p ClearAddedNodesDuringDocumentChange(), finished storing "
"consecutive nodes",
this));
}
bool IMEContentObserver::IsNextNodeOfLastAddedNode(nsINode* aParent,
nsIContent* aChild) const {
MOZ_ASSERT(aParent);
MOZ_ASSERT(aChild && aChild->GetParentNode() == aParent);
MOZ_ASSERT(mRootElement);
MOZ_ASSERT(HasAddedNodesDuringDocumentChange());
// If the parent node isn't changed, we can check that mLastAddedContent has
// aChild as its next sibling.
if (aParent == mLastAddedContainer) {
return !NS_WARN_IF(mLastAddedContent->GetNextSibling() != aChild);
}
// If the parent node is changed, that means that the recorded last added node
// shouldn't have a sibling.
if (NS_WARN_IF(mLastAddedContent->GetNextSibling())) {
return false;
}
// If the node is aParent is a descendant of mLastAddedContainer,
// aChild should be the first child in the new container.
if (mLastAddedContainer == aParent->GetParent()) {
return !NS_WARN_IF(aChild->GetPreviousSibling());
}
// Otherwise, we need to check it even with slow path.
nsIContent* nextContentOfLastAddedContent =
mLastAddedContent->GetNextNode(mRootElement->GetParentNode());
if (NS_WARN_IF(!nextContentOfLastAddedContent)) {
return false;
}
if (NS_WARN_IF(nextContentOfLastAddedContent != aChild)) {
return false;
}
return true;
}
void IMEContentObserver::MaybeNotifyIMEOfAddedTextDuringDocumentChange() {
if (!HasAddedNodesDuringDocumentChange()) {
return;
}
MOZ_LOG(sIMECOLog, LogLevel::Debug,
("0x%p "
"IMEContentObserver::MaybeNotifyIMEOfAddedTextDuringDocumentChange()"
", flushing stored consecutive nodes",
this));
// Notify IME of text change which is caused by added nodes now.
// First, compute offset of start of first added node from start of the
// editor.
uint32_t offset;
nsresult rv = ContentEventHandler::GetFlatTextLengthInRange(
RawNodePosition(mRootElement, 0u),
RawNodePosition(mFirstAddedContainer,
PointBefore(mFirstAddedContainer, mFirstAddedContent)),
mRootElement, &offset, LINE_BREAK_TYPE_NATIVE);
if (NS_WARN_IF(NS_FAILED(rv))) {
ClearAddedNodesDuringDocumentChange();
return;
}
// Next, compute the text length of added nodes.
uint32_t length;
rv = ContentEventHandler::GetFlatTextLengthInRange(
RawNodePosition(mFirstAddedContainer,
PointBefore(mFirstAddedContainer, mFirstAddedContent)),
RawNodePosition(mLastAddedContainer, mLastAddedContent), mRootElement,
&length, LINE_BREAK_TYPE_NATIVE);
if (NS_WARN_IF(NS_FAILED(rv))) {
ClearAddedNodesDuringDocumentChange();
return;
}
// Finally, try to notify IME of the range.
TextChangeData data(offset, offset, offset + length,
IsEditorHandlingEventForComposition(),
IsEditorComposing());
MaybeNotifyIMEOfTextChange(data);
ClearAddedNodesDuringDocumentChange();
}
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(), HasAddedNodesDuringDocumentChange()=%s",
this, ToChar(HasAddedNodesDuringDocumentChange())));
// If we're not in a nested document update, this will return early,
// otherwise, it will handle flusing any changes currently pending before
// entering a nested document update.
MaybeNotifyIMEOfAddedTextDuringDocumentChange();
}
void IMEContentObserver::EndDocumentUpdate() {
MOZ_LOG(sIMECOLog, LogLevel::Debug,
("0x%p EndDocumentUpdate(), HasAddedNodesDuringDocumentChange()=%s",
this, ToChar(HasAddedNodesDuringDocumentChange())));
MaybeNotifyIMEOfAddedTextDuringDocumentChange();
}
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 EditAction()", this));
mEndOfAddedTextCache.Clear();
mStartOfRemovingTextRangeCache.Clear();
FlushMergeableNotifications();
}
void IMEContentObserver::BeforeEditAction() {
MOZ_LOG(sIMECOLog, LogLevel::Debug, ("0x%p BeforeEditAction()", this));
mEndOfAddedTextCache.Clear();
mStartOfRemovingTextRangeCache.Clear();
}
void IMEContentObserver::CancelEditAction() {
MOZ_LOG(sIMECOLog, LogLevel::Debug, ("0x%p CancelEditAction()", this));
mEndOfAddedTextCache.Clear();
mStartOfRemovingTextRangeCache.Clear();
FlushMergeableNotifications();
}
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 (mEditorBase && mEditorBase->IsInEditSubAction()) {
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.
#ifdef XP_MACOSX
// We need to flush layout only on macOS because character coordinates are
// cached by cocoa with this call, but we don't have a way to update them
// after that. Therefore, we need the latest layout information right now.
observer->UpdateSelectionCache(true);
#else
// We avoid flushing for focus in the general case.
observer->UpdateSelectionCache(false);
#endif // #ifdef XP_MACOSX #else
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;
}
mDocumentUpdating++;
mIMEContentObserver->BeginDocumentUpdate();
}
void IMEContentObserver::DocumentObserver::EndUpdate(Document* aDocument) {
if (NS_WARN_IF(Destroyed()) || NS_WARN_IF(!IsObserving()) ||
NS_WARN_IF(!IsUpdating())) {
return;
}
mDocumentUpdating--;
mIMEContentObserver->EndDocumentUpdate();
}
} // namespace mozilla