Bug 492394 part.2 Implement ITextStoreACP::GetACPFromPoint() r=emk
authorMasayuki Nakano <masayuki@d-toybox.com>
Tue, 14 Apr 2015 14:27:37 +0900
changeset 270334 b617cfc2df875ae85147b6e956fffffe60dc1b24
parent 270333 3cf74485ea6a93ab7b492e21723c9326fd977c37
child 270335 e17f5910da7539c9193518cab9d139559d7f2774
push id863
push userraliiev@mozilla.com
push dateMon, 03 Aug 2015 13:22:43 +0000
treeherdermozilla-release@f6321b14228d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersemk
bugs492394
milestone40.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 492394 part.2 Implement ITextStoreACP::GetACPFromPoint() r=emk
widget/windows/nsTextStore.cpp
--- a/widget/windows/nsTextStore.cpp
+++ b/widget/windows/nsTextStore.cpp
@@ -99,16 +99,44 @@ GetFindFlagName(DWORD aFindFlag)
   if (description.IsEmpty()) {
     description.AppendLiteral("Unknown (");
     description.AppendInt(static_cast<uint32_t>(aFindFlag));
     description.Append(')');
   }
   return description;
 }
 
+class GetACPFromPointFlagName : public nsAutoCString
+{
+public:
+  GetACPFromPointFlagName(DWORD aFlags)
+  {
+    if (!aFlags) {
+      AppendLiteral("no flags (0)");
+      return;
+    }
+    if (aFlags & GXFPF_ROUND_NEAREST) {
+      AppendLiteral("GXFPF_ROUND_NEAREST");
+      aFlags &= ~GXFPF_ROUND_NEAREST;
+    }
+    if (aFlags & GXFPF_NEAREST) {
+      HandleSeparator(*this);
+      AppendLiteral("GXFPF_NEAREST");
+      aFlags &= ~GXFPF_NEAREST;
+    }
+    if (aFlags) {
+      HandleSeparator(*this);
+      AppendLiteral("Unknown(");
+      AppendInt(static_cast<uint32_t>(aFlags));
+      Append(')');
+    }
+  }
+  virtual ~GetACPFromPointFlagName() {}
+};
+
 static const char*
 GetIMEEnabledName(IMEState::Enabled aIMEEnabled)
 {
   switch (aIMEEnabled) {
     case IMEState::DISABLED:
       return "DISABLED";
     case IMEState::ENABLED:
       return "ENABLED";
@@ -3112,45 +3140,158 @@ nsTextStore::GetActiveView(TsViewCookie 
 }
 
 STDMETHODIMP
 nsTextStore::GetACPFromPoint(TsViewCookie vcView,
                              const POINT *pt,
                              DWORD dwFlags,
                              LONG *pacp)
 {
+  PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
+         ("TSF: 0x%p nsTextStore::GetACPFromPoint(pvcView=%d, pt=%p (x=%d, "
+          "y=%d), dwFlags=%s, pacp=%p",
+          this, vcView, pt, pt ? pt->x : 0, pt ? pt->y : 0,
+          GetACPFromPointFlagName(dwFlags).get(), pacp));
+
   if (!IsReadLocked()) {
     PR_LOG(sTextStoreLog, PR_LOG_ERROR,
            ("TSF: 0x%p   nsTextStore::GetACPFromPoint() FAILED due to "
             "not locked (read)", this));
     return TS_E_NOLOCK;
   }
 
   if (vcView != TEXTSTORE_DEFAULT_VIEW) {
     PR_LOG(sTextStoreLog, PR_LOG_ERROR,
            ("TSF: 0x%p   nsTextStore::GetACPFromPoint() FAILED due to "
             "called with invalid view", this));
     return E_INVALIDARG;
   }
 
+  if (!pt) {
+    PR_LOG(sTextStoreLog, PR_LOG_ERROR,
+           ("TSF: 0x%p   nsTextStore::GetACPFromPoint() FAILED due to "
+            "null pt", this));
+    return E_INVALIDARG;
+  }
+
+  if (!pacp) {
+    PR_LOG(sTextStoreLog, PR_LOG_ERROR,
+           ("TSF: 0x%p   nsTextStore::GetACPFromPoint() FAILED due to "
+            "null pacp", this));
+    return E_INVALIDARG;
+  }
+
   if (mLockedContent.IsLayoutChanged()) {
     PR_LOG(sTextStoreLog, PR_LOG_ERROR,
            ("TSF: 0x%p   nsTextStore::GetACPFromPoint() FAILED due to "
             "layout not recomputed", this));
     mPendingOnLayoutChange = true;
     return TS_E_NOLAYOUT;
   }
 
+  nsIntPoint ourPt(pt->x, pt->y);
+  // Convert to widget relative coordinates from screen's.
+  ourPt -= mWidget->WidgetToScreenOffsetUntyped();
+
+  // NOTE: Don't check if the point is in the widget since the point can be
+  //       outside of the widget if focused editor is in a XUL <panel>.
+
+  WidgetQueryContentEvent charAtPt(true, NS_QUERY_CHARACTER_AT_POINT, mWidget);
+  mWidget->InitEvent(charAtPt, &ourPt);
+
+  // FYI: WidgetQueryContentEvent may cause flushing pending layout and it
+  //      may cause focus change or something.
+  nsRefPtr<nsTextStore> kungFuDeathGrip(this);
+  mWidget->DispatchWindowEvent(&charAtPt);
+  if (!mWidget || mWidget->Destroyed()) {
+    PR_LOG(sTextStoreLog, PR_LOG_ERROR,
+           ("TSF: 0x%p   nsTextStore::GetACPFromPoint() FAILED due to "
+            "mWidget was destroyed during NS_QUERY_CHARACTER_AT_POINT", this));
+    return E_FAIL;
+  }
+
+  PR_LOG(sTextStoreLog, PR_LOG_DEBUG,
+         ("TSF: 0x%p   nsTextStore::GetACPFromPoint(), charAtPt={ "
+          "mSucceeded=%s, mReply={ mOffset=%u, mTentativeCaretOffset=%u }}",
+          this, GetBoolName(charAtPt.mSucceeded), charAtPt.mReply.mOffset,
+          charAtPt.mReply.mTentativeCaretOffset));
+
+  if (NS_WARN_IF(!charAtPt.mSucceeded)) {
+    PR_LOG(sTextStoreLog, PR_LOG_ERROR,
+           ("TSF: 0x%p   nsTextStore::GetACPFromPoint() FAILED due to "
+            "NS_QUERY_CHARACTER_AT_POINT failure", this));
+    return E_FAIL;
+  }
+
+  // If dwFlags isn't set and the point isn't in any character's bounding box,
+  // we should return TS_E_INVALIDPOINT.
+  if (!(dwFlags & GXFPF_NEAREST) &&
+      charAtPt.mReply.mOffset == WidgetQueryContentEvent::NOT_FOUND) {
+    PR_LOG(sTextStoreLog, PR_LOG_ERROR,
+           ("TSF: 0x%p   nsTextStore::GetACPFromPoint() FAILED due to the "
+            "point contained by no bounding box", this));
+    return TS_E_INVALIDPOINT;
+  }
+
+  // Although, we're not sure if mTentativeCaretOffset becomes NOT_FOUND,
+  // let's assume that there is no content in such case.
+  if (NS_WARN_IF(charAtPt.mReply.mTentativeCaretOffset ==
+                   WidgetQueryContentEvent::NOT_FOUND)) {
+    charAtPt.mReply.mTentativeCaretOffset = 0;
+  }
+
+  uint32_t offset;
+
+  // If dwFlags includes GXFPF_ROUND_NEAREST, we should return tentative
+  // caret offset (MSDN calls it "range position").
+  if (dwFlags & GXFPF_ROUND_NEAREST) {
+    offset = charAtPt.mReply.mTentativeCaretOffset;
+  } else if (charAtPt.mReply.mOffset != WidgetQueryContentEvent::NOT_FOUND) {
+    // Otherwise, we should return character offset whose bounding box contains
+    // the point.
+    offset = charAtPt.mReply.mOffset;
+  } else {
+    // If the point isn't in any character's bounding box but we need to return
+    // the nearest character from the point, we should *guess* the character
+    // offset since there is no inexpensive API to check it strictly.
+    // XXX If we retrieve 2 bounding boxes, one is before the offset and
+    //     the other is after the offset, we could resolve the offset.
+    //     However, dispatching 2 NS_QUERY_TEXT_RECT may be expensive.
+
+    // So, use tentative offset for now.
+    offset = charAtPt.mReply.mTentativeCaretOffset;
+
+    // However, if it's after the last character, we need to decrement the
+    // offset.
+    Content& lockedContent = LockedContent();
+    if (!lockedContent.IsInitialized()) {
+      PR_LOG(sTextStoreLog, PR_LOG_ERROR,
+             ("TSF: 0x%p   nsTextStore::GetACPFromPoint() FAILED due to "
+              "LockedContent() failure", this));
+      return E_FAIL;
+    }
+    if (lockedContent.Text().Length() <= offset) {
+      // If the tentative caret is after the last character, let's return
+      // the last character's offset.
+      offset = lockedContent.Text().Length() - 1;
+    }
+  }
+
+  if (NS_WARN_IF(offset > LONG_MAX)) {
+    PR_LOG(sTextStoreLog, PR_LOG_ERROR,
+           ("TSF: 0x%p   nsTextStore::GetACPFromPoint() FAILED due to out of "
+            "range of the result", this));
+    return TS_E_INVALIDPOINT;
+  }
+
+  *pacp = static_cast<LONG>(offset);
   PR_LOG(sTextStoreLog, PR_LOG_ALWAYS,
-         ("TSF: 0x%p nsTextStore::GetACPFromPoint(vcView=%ld, "
-          "pt(0x%p)={ x=%ld, y=%ld }, dwFlags=%s, pacp=0x%p) called "
-          "but not supported (E_NOTIMPL)", this));
-
-  // not supported for now
-  return E_NOTIMPL;
+         ("TSF: 0x%p   nsTextStore::GetACPFromPoint() succeeded: *pacp=%d",
+          this, *pacp));
+  return S_OK;
 }
 
 STDMETHODIMP
 nsTextStore::GetTextExt(TsViewCookie vcView,
                         LONG acpStart,
                         LONG acpEnd,
                         RECT *prc,
                         BOOL *pfClipped)