Bug 1286157 TSFTextStore should use insertion point relative offset query when cached contents for TSF and actual content (or content cache) are different r=m_kato
MozReview-Commit-ID: 3Q9T3XVvyCj
--- a/widget/windows/TSFTextStore.cpp
+++ b/widget/windows/TSFTextStore.cpp
@@ -2093,16 +2093,39 @@ TSFTextStore::ContentForTSFRef()
GetEscapedUTF8String(mContentForTSF.LastCompositionString()).get(),
mContentForTSF.LastCompositionString().Length(),
mContentForTSF.MinTextModifiedOffset()));
return mContentForTSF;
}
bool
+TSFTextStore::CanAccessActualContentDirectly() const
+{
+ if (!mContentForTSF.IsInitialized() || mSelectionForTSF.IsDirty()) {
+ return true;
+ }
+
+ // If the cached content has been changed by something except composition,
+ // the content cache may be different from actual content.
+ if (mPendingTextChangeData.IsValid() &&
+ !mPendingTextChangeData.mCausedOnlyByComposition) {
+ return false;
+ }
+
+ // If the cached selection isn't changed, cached content and actual content
+ // should be same.
+ if (!mPendingSelectionChangeData.IsValid()) {
+ return true;
+ }
+
+ return mSelectionForTSF.EqualsExceptDirection(mPendingSelectionChangeData);
+}
+
+bool
TSFTextStore::GetCurrentText(nsAString& aTextContent)
{
if (mContentForTSF.IsInitialized()) {
aTextContent = mContentForTSF.Text();
return true;
}
MOZ_ASSERT(!mDestroyed);
@@ -3702,16 +3725,23 @@ TSFTextStore::GetTextExt(TsViewCookie vc
int64_t startOffset = acpStart;
if (mComposition.IsComposing()) {
// If there is a composition, TSF must want character rects related to
// the composition. Therefore, we should use insertion point relative
// query because the composition might be at different position from
// the position where TSFTextStore believes it at.
options.mRelativeToInsertionPoint = true;
startOffset -= mComposition.mStart;
+ } else if (!CanAccessActualContentDirectly()) {
+ // If TSF/TIP cannot access actual content directly, there may be pending
+ // text and/or selection changes which have not been notified TSF yet.
+ // Therefore, we should use relative to insertion point query since
+ // TSF/TIP computes the offset from the cached selection.
+ options.mRelativeToInsertionPoint = true;
+ startOffset -= mSelectionForTSF.StartOffset();
}
event.InitForQueryTextRect(startOffset, acpEnd - acpStart, options);
DispatchEvent(event);
if (NS_WARN_IF(!event.mSucceeded)) {
MOZ_LOG(sTextStoreLog, LogLevel::Error,
("0x%p TSFTextStore::GetTextExt() FAILED due to "
"eQueryTextRect failure", this));
@@ -5165,16 +5195,23 @@ TSFTextStore::CreateNativeCaret()
// collapsed, is it OK?
int64_t caretOffset = selectionForTSF.MaxOffset();
if (mComposition.IsComposing()) {
// If there is a composition, use insertion point relative query for
// deciding caret position because composition might be at different
// position where TSFTextStore believes it at.
options.mRelativeToInsertionPoint = true;
caretOffset -= mComposition.mStart;
+ } else if (!CanAccessActualContentDirectly()) {
+ // If TSF/TIP cannot access actual content directly, there may be pending
+ // text and/or selection changes which have not been notified TSF yet.
+ // Therefore, we should use relative to insertion point query since
+ // TSF/TIP computes the offset from the cached selection.
+ options.mRelativeToInsertionPoint = true;
+ caretOffset -= mSelectionForTSF.StartOffset();
}
queryCaretRect.InitForQueryCaretRect(caretOffset, options);
DispatchEvent(queryCaretRect);
if (NS_WARN_IF(!queryCaretRect.mSucceeded)) {
MOZ_LOG(sTextStoreLog, LogLevel::Error,
("0x%p TSFTextStore::CreateNativeCaret() FAILED due to "
"eQueryCaretRect failure (offset=%d)", this, caretOffset));
--- a/widget/windows/TSFTextStore.h
+++ b/widget/windows/TSFTextStore.h
@@ -559,16 +559,25 @@ protected:
if (mACP.style.ase == aACP.style.ase) {
return mACP.acpStart == aACP.acpStart &&
mACP.acpEnd == aACP.acpEnd;
}
return mACP.acpStart == aACP.acpEnd &&
mACP.acpEnd == aACP.acpStart;
}
+ bool EqualsExceptDirection(
+ const SelectionChangeDataBase& aChangedSelection) const
+ {
+ MOZ_ASSERT(!mDirty);
+ MOZ_ASSERT(aChangedSelection.IsValid());
+ return aChangedSelection.Length() == static_cast<uint32_t>(Length()) &&
+ aChangedSelection.mOffset == static_cast<uint32_t>(StartOffset());
+ }
+
private:
TS_SELECTION_ACP mACP;
WritingMode mWritingMode;
bool mDirty;
};
// Don't access mSelection directly except at calling MarkDirty().
// Use SelectionForTSFRef() instead. This is modified immediately when
// TSF requests to set selection and not updated by selection change in
@@ -832,16 +841,22 @@ protected:
// - When there is a composition, all dispatched events are handled by
// the focused editor which may be in a remote process.
// So, if two compositions are created very quickly, this cache may not be
// cleared between eCompositionCommit(AsIs) and eCompositionStart.
Content mContentForTSF;
Content& ContentForTSFRef();
+ // CanAccessActualContentDirectly() returns true when TSF/TIP can access
+ // actual content directly. In other words, mContentForTSF and/or
+ // mSelectionForTSF doesn't cache content or they matches with actual
+ // contents due to no pending text/selection change notifications.
+ bool CanAccessActualContentDirectly() const;
+
// While mContentForTSF is valid, this returns the text stored by it.
// Otherwise, return the current text content retrieved by eQueryTextContent.
bool GetCurrentText(nsAString& aTextContent);
class MouseTracker final
{
public:
static const DWORD kInvalidCookie = static_cast<DWORD>(-1);