Bug 492394 part.1 NS_QUERY_CHARACTER_AT_POINT should also return tentative caret offset for the point r=smaug, sr=smaug
authorMasayuki Nakano <masayuki@d-toybox.com>
Tue, 14 Apr 2015 14:27:37 +0900
changeset 257876 3cf74485ea6a93ab7b492e21723c9326fd977c37
parent 257875 7cb645a5d38ebb414447ff477827832e74cf7705
child 257877 b617cfc2df875ae85147b6e956fffffe60dc1b24
push id8007
push userraliiev@mozilla.com
push dateMon, 11 May 2015 19:23:16 +0000
treeherdermozilla-aurora@e2ce1aac996e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug, smaug
bugs492394
milestone40.0a1
Bug 492394 part.1 NS_QUERY_CHARACTER_AT_POINT should also return tentative caret offset for the point r=smaug, sr=smaug
dom/base/nsQueryContentEventResult.cpp
dom/base/nsQueryContentEventResult.h
dom/events/ContentEventHandler.cpp
dom/interfaces/base/nsIQueryContentEventResult.idl
widget/BasicEvents.h
widget/TextEvents.h
widget/nsGUIEventIPC.h
widget/tests/window_composition_text_querycontent.xul
--- a/dom/base/nsQueryContentEventResult.cpp
+++ b/dom/base/nsQueryContentEventResult.cpp
@@ -33,16 +33,31 @@ nsQueryContentEventResult::GetOffset(uin
   bool notFound;
   nsresult rv = GetNotFound(&notFound);
   NS_ENSURE_SUCCESS(rv, rv);
   NS_ENSURE_TRUE(!notFound, NS_ERROR_NOT_AVAILABLE);
   *aOffset = mOffset;
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsQueryContentEventResult::GetTentativeCaretOffset(uint32_t* aOffset)
+{
+  bool notFound;
+  nsresult rv = GetTentativeCaretOffsetNotFound(&notFound);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+  if (NS_WARN_IF(notFound)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+  *aOffset = mTentativeCaretOffset;
+  return NS_OK;
+}
+
 static bool IsRectEnabled(uint32_t aEventID)
 {
   return aEventID == NS_QUERY_CARET_RECT ||
          aEventID == NS_QUERY_TEXT_RECT ||
          aEventID == NS_QUERY_EDITOR_RECT ||
          aEventID == NS_QUERY_CHARACTER_AT_POINT;
 }
 
@@ -121,25 +136,39 @@ nsQueryContentEventResult::GetNotFound(b
   NS_ENSURE_TRUE(mSucceeded, NS_ERROR_NOT_AVAILABLE);
   NS_ENSURE_TRUE(mEventID == NS_QUERY_SELECTED_TEXT ||
                  mEventID == NS_QUERY_CHARACTER_AT_POINT,
                  NS_ERROR_NOT_AVAILABLE);
   *aNotFound = (mOffset == WidgetQueryContentEvent::NOT_FOUND);
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsQueryContentEventResult::GetTentativeCaretOffsetNotFound(bool* aNotFound)
+{
+  if (NS_WARN_IF(!mSucceeded)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+  if (NS_WARN_IF(mEventID != NS_QUERY_CHARACTER_AT_POINT)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+  *aNotFound = (mTentativeCaretOffset == WidgetQueryContentEvent::NOT_FOUND);
+  return NS_OK;
+}
+
 void
 nsQueryContentEventResult::SetEventResult(nsIWidget* aWidget,
                                           const WidgetQueryContentEvent &aEvent)
 {
   mEventID = aEvent.message;
   mSucceeded = aEvent.mSucceeded;
   mReversed = aEvent.mReply.mReversed;
   mRect = aEvent.mReply.mRect;
   mOffset = aEvent.mReply.mOffset;
+  mTentativeCaretOffset = aEvent.mReply.mTentativeCaretOffset;
   mString = aEvent.mReply.mString;
 
   if (!IsRectEnabled(mEventID) || !aWidget || !mSucceeded) {
     return;
   }
 
   nsIWidget* topWidget = aWidget->GetTopLevelWidget();
   if (!topWidget || topWidget == aWidget) {
--- a/dom/base/nsQueryContentEventResult.h
+++ b/dom/base/nsQueryContentEventResult.h
@@ -25,16 +25,17 @@ public:
                       const mozilla::WidgetQueryContentEvent &aEvent);
 
 protected:
   ~nsQueryContentEventResult();
 
   uint32_t mEventID;
 
   uint32_t mOffset;
+  uint32_t mTentativeCaretOffset;
   nsString mString;
   mozilla::LayoutDeviceIntRect mRect;
 
   bool mSucceeded;
   bool mReversed;
 };
 
 #endif // mozilla_dom_nsQueryContentEventResult_h
--- a/dom/events/ContentEventHandler.cpp
+++ b/dom/events/ContentEventHandler.cpp
@@ -1130,16 +1130,19 @@ ContentEventHandler::OnQuerySelectionAsT
 nsresult
 ContentEventHandler::OnQueryCharacterAtPoint(WidgetQueryContentEvent* aEvent)
 {
   nsresult rv = Init(aEvent);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
+  aEvent->mReply.mOffset = aEvent->mReply.mTentativeCaretOffset =
+    WidgetQueryContentEvent::NOT_FOUND;
+
   nsIFrame* rootFrame = mPresShell->GetRootFrame();
   NS_ENSURE_TRUE(rootFrame, NS_ERROR_FAILURE);
   nsIWidget* rootWidget = rootFrame->GetNearestWidget();
   NS_ENSURE_TRUE(rootWidget, NS_ERROR_FAILURE);
 
   // The root frame's widget might be different, e.g., the event was fired on
   // a popup but the rootFrame is the document root.
   if (rootWidget != aEvent->widget) {
@@ -1159,30 +1162,57 @@ ContentEventHandler::OnQueryCharacterAtP
   if (rootWidget != aEvent->widget) {
     eventOnRoot.refPoint += aEvent->widget->WidgetToScreenOffset() -
       rootWidget->WidgetToScreenOffset();
   }
   nsPoint ptInRoot =
     nsLayoutUtils::GetEventCoordinatesRelativeTo(&eventOnRoot, rootFrame);
 
   nsIFrame* targetFrame = nsLayoutUtils::GetFrameForPoint(rootFrame, ptInRoot);
-  if (!targetFrame || targetFrame->GetType() != nsGkAtoms::textFrame ||
-      !targetFrame->GetContent() ||
+  if (!targetFrame || !targetFrame->GetContent() ||
       !nsContentUtils::ContentIsDescendantOf(targetFrame->GetContent(),
                                              mRootContent)) {
-    // there is no character at the point.
-    aEvent->mReply.mOffset = WidgetQueryContentEvent::NOT_FOUND;
+    // There is no character at the point.
     aEvent->mSucceeded = true;
     return NS_OK;
   }
   nsPoint ptInTarget = ptInRoot + rootFrame->GetOffsetToCrossDoc(targetFrame);
   int32_t rootAPD = rootFrame->PresContext()->AppUnitsPerDevPixel();
   int32_t targetAPD = targetFrame->PresContext()->AppUnitsPerDevPixel();
   ptInTarget = ptInTarget.ScaleToOtherAppUnits(rootAPD, targetAPD);
 
+  nsIFrame::ContentOffsets tentativeCaretOffsets =
+    targetFrame->GetContentOffsetsFromPoint(ptInTarget);
+  if (!tentativeCaretOffsets.content ||
+      !nsContentUtils::ContentIsDescendantOf(tentativeCaretOffsets.content,
+                                             mRootContent)) {
+    // There is no character nor tentative caret point at the point.
+    aEvent->mSucceeded = true;
+    return NS_OK;
+  }
+
+  rv = GetFlatTextOffsetOfRange(mRootContent, tentativeCaretOffsets.content,
+                                tentativeCaretOffsets.offset,
+                                &aEvent->mReply.mTentativeCaretOffset,
+                                GetLineBreakType(aEvent));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  if (targetFrame->GetType() != nsGkAtoms::textFrame) {
+    // There is no character at the point but there is tentative caret point.
+    aEvent->mSucceeded = true;
+    return NS_OK;
+  }
+
+  MOZ_ASSERT(
+    aEvent->mReply.mTentativeCaretOffset != WidgetQueryContentEvent::NOT_FOUND,
+    "The point is inside a character bounding box.  Why tentative caret point "
+    "hasn't been found?");
+
   nsTextFrame* textframe = static_cast<nsTextFrame*>(targetFrame);
   nsIFrame::ContentOffsets contentOffsets =
     textframe->GetCharacterOffsetAtFramePoint(ptInTarget);
   NS_ENSURE_TRUE(contentOffsets.content, NS_ERROR_FAILURE);
   uint32_t offset;
   rv = GetFlatTextOffsetOfRange(mRootContent, contentOffsets.content,
                                 contentOffsets.offset, &offset,
                                 GetLineBreakType(aEvent));
--- a/dom/interfaces/base/nsIQueryContentEventResult.idl
+++ b/dom/interfaces/base/nsIQueryContentEventResult.idl
@@ -6,23 +6,25 @@
 #include "nsISupports.idl"
 
 /**
  * The result of query content events.  succeeded propery can be used always.
  * Whether other properties can be used or not depends on the event.
  * See nsIDOMWindowUtils.idl, which properites can be used was documented.
  */
 
-[scriptable, uuid(4b4ba266-b51e-4f0f-8d0e-9f13cb2a0056)]
+[scriptable, uuid(e2c39e0e-345f-451a-a7b2-e0230d555847)]
 interface nsIQueryContentEventResult : nsISupports
 {
   readonly attribute unsigned long offset;
+  readonly attribute unsigned long tentativeCaretOffset;
   readonly attribute boolean reversed;
 
   readonly attribute long left;
   readonly attribute long top;
   readonly attribute long width;
   readonly attribute long height;
   readonly attribute AString text;
 
   readonly attribute boolean succeeded;
   readonly attribute boolean notFound;
+  readonly attribute boolean tentativeCaretOffsetNotFound;
 };
--- a/widget/BasicEvents.h
+++ b/widget/BasicEvents.h
@@ -253,18 +253,19 @@
 // Query for the bounding rect of the current focused frame. Result is relative
 // to top level widget coordinates
 #define NS_QUERY_EDITOR_RECT            (NS_QUERY_CONTENT_EVENT_START + 5)
 // Query for the current state of the content. The particular members of
 // mReply that are set for each query content event will be valid on success.
 #define NS_QUERY_CONTENT_STATE          (NS_QUERY_CONTENT_EVENT_START + 6)
 // Query for the selection in the form of a nsITransferable.
 #define NS_QUERY_SELECTION_AS_TRANSFERABLE (NS_QUERY_CONTENT_EVENT_START + 7)
-// Query for character at a point.  This returns the character offset and its
-// rect.  The point is specified by Event::refPoint.
+// Query for character at a point.  This returns the character offset, its
+// rect and also tentative caret point if the point is clicked.  The point is
+// specified by Event::refPoint.
 #define NS_QUERY_CHARACTER_AT_POINT     (NS_QUERY_CONTENT_EVENT_START + 8)
 // Query if the DOM element under Event::refPoint belongs to our widget
 // or not.
 #define NS_QUERY_DOM_WIDGET_HITTEST     (NS_QUERY_CONTENT_EVENT_START + 9)
 
 // Video events
 #define NS_MEDIA_EVENT_START            3300
 #define NS_LOADSTART           (NS_MEDIA_EVENT_START)
--- a/widget/TextEvents.h
+++ b/widget/TextEvents.h
@@ -549,16 +549,19 @@ public:
   {
     uint32_t mOffset;
     uint32_t mLength;
   } mInput;
   struct
   {
     void* mContentsRoot;
     uint32_t mOffset;
+    // mTentativeCaretOffset is used by only NS_QUERY_CHARACTER_AT_POINT.
+    // This is the offset where caret would be if user clicked at the refPoint.
+    uint32_t mTentativeCaretOffset;
     nsString mString;
     // Finally, the coordinates is system coordinates.
     mozilla::LayoutDeviceIntRect mRect;
     // The return widget has the caret. This is set at all query events.
     nsIWidget* mFocusedWidget;
     // true if selection is reversed (end < start)
     bool mReversed;
     // true if the selection exists
--- a/widget/nsGUIEventIPC.h
+++ b/widget/nsGUIEventIPC.h
@@ -582,16 +582,17 @@ struct ParamTraits<mozilla::WidgetQueryC
   {
     WriteParam(aMsg, static_cast<mozilla::WidgetGUIEvent>(aParam));
     WriteParam(aMsg, aParam.mSucceeded);
     WriteParam(aMsg, aParam.mUseNativeLineBreak);
     WriteParam(aMsg, aParam.mWithFontRanges);
     WriteParam(aMsg, aParam.mInput.mOffset);
     WriteParam(aMsg, aParam.mInput.mLength);
     WriteParam(aMsg, aParam.mReply.mOffset);
+    WriteParam(aMsg, aParam.mReply.mTentativeCaretOffset);
     WriteParam(aMsg, aParam.mReply.mString);
     WriteParam(aMsg, aParam.mReply.mRect);
     WriteParam(aMsg, aParam.mReply.mReversed);
     WriteParam(aMsg, aParam.mReply.mHasSelection);
     WriteParam(aMsg, aParam.mReply.mWidgetIsHit);
     WriteParam(aMsg, aParam.mReply.mFontRanges);
   }
 
@@ -601,16 +602,17 @@ struct ParamTraits<mozilla::WidgetQueryC
     return ReadParam(aMsg, aIter,
                      static_cast<mozilla::WidgetGUIEvent*>(aResult)) &&
            ReadParam(aMsg, aIter, &aResult->mSucceeded) &&
            ReadParam(aMsg, aIter, &aResult->mUseNativeLineBreak) &&
            ReadParam(aMsg, aIter, &aResult->mWithFontRanges) &&
            ReadParam(aMsg, aIter, &aResult->mInput.mOffset) &&
            ReadParam(aMsg, aIter, &aResult->mInput.mLength) &&
            ReadParam(aMsg, aIter, &aResult->mReply.mOffset) &&
+           ReadParam(aMsg, aIter, &aResult->mReply.mTentativeCaretOffset) &&
            ReadParam(aMsg, aIter, &aResult->mReply.mString) &&
            ReadParam(aMsg, aIter, &aResult->mReply.mRect) &&
            ReadParam(aMsg, aIter, &aResult->mReply.mReversed) &&
            ReadParam(aMsg, aIter, &aResult->mReply.mHasSelection) &&
            ReadParam(aMsg, aIter, &aResult->mReply.mWidgetIsHit) &&
            ReadParam(aMsg, aIter, &aResult->mReply.mFontRanges);
   }
 };
--- a/widget/tests/window_composition_text_querycontent.xul
+++ b/widget/tests/window_composition_text_querycontent.xul
@@ -14,23 +14,23 @@
   <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/ChromeUtils.js"></script>
 
   <panel id="panel" hidden="true"
          orient="vertical"
          onpopupshown="onPanelShown(event);"
          onpopuphidden="onPanelHidden(event);">
     <vbox id="vbox">
       <textbox id="textbox" onfocus="onFocusPanelTextbox(event);"
-               multiline="true" cols="20" rows="4"/>
+               multiline="true" cols="20" rows="4" style="font-size: 36px;"/>
     </vbox>
   </panel>
 
 <body  xmlns="http://www.w3.org/1999/xhtml">
 <p id="display">
-<div id="div" style="margin: 0; padding: 0; font-size: 24px;">Here is a text frame.</div>
+<div id="div" style="margin: 0; padding: 0; font-size: 36px;">Here is a text frame.</div>
 <textarea style="margin: 0;" id="textarea" cols="20" rows="4"></textarea><br/>
 <iframe id="iframe" width="300" height="150"
         src="data:text/html,&lt;textarea id='textarea' cols='20' rows='4'&gt;&lt;/textarea&gt;"></iframe><br/>
 <iframe id="iframe2" width="300" height="150"
         src="data:text/html,&lt;body onload='document.designMode=%22on%22'&gt;body content&lt;/body&gt;"></iframe><br/>
 <iframe id="iframe3" width="300" height="150"
         src="data:text/html,&lt;body onload='document.designMode=%22on%22'&gt;body content&lt;/body&gt;"></iframe><br/>
 <input id="input" type="text"/><br/>
@@ -2164,19 +2164,21 @@ function runCharAtPointTest(aFocusedEdit
 {
   aFocusedEditor.value = "This is a test of the\nContent Events";
                        // 012345678901234567890  12345678901234
                        // 0         1         2           3    
 
   aFocusedEditor.focus();
 
   const kNone = -1;
-  const kTestingOffset   = [     0, 10,    20, 21 + kLFLen, 34 + kLFLen];
-  const kLeftSideOffset  = [ kNone,  9,    19,       kNone, 33 + kLFLen];
-  const kRightSideOffset = [     1, 11, kNone, 22 + kLFLen,       kNone];
+  const kTestingOffset             = [     0, 10,    20, 21 + kLFLen, 34 + kLFLen];
+  const kLeftSideOffset            = [ kNone,  9,    19,       kNone, 33 + kLFLen];
+  const kRightSideOffset           = [     1, 11, kNone, 22 + kLFLen,       kNone];
+  const kLeftTentativeCaretOffset  = [     0, 10,    20, 21 + kLFLen, 34 + kLFLen];
+  const kRightTentativeCaretOffset = [     1, 11,    21, 22 + kLFLen, 35 + kLFLen];
 
   var editorRect = synthesizeQueryEditorRect();
   if (!checkQueryContentResult(editorRect,
         "runCharAtPointTest (" + aTargetName + "): editorRect")) {
     return;
   }
 
   for (var i = 0; i < kTestingOffset.length; i++) {
@@ -2198,61 +2200,91 @@ function runCharAtPointTest(aFocusedEdit
       ok(!charAtPt1.notFound,
          "runCharAtPointTest (" + aTargetName + "): charAtPt1 isn't found: i=" + i);
       if (!charAtPt1.notFound) {
         is(charAtPt1.offset, kTestingOffset[i],
            "runCharAtPointTest (" + aTargetName + "): charAtPt1 offset is wrong: i=" + i);
         checkRect(charAtPt1, textRect, "runCharAtPointTest (" + aTargetName +
                   "): charAtPt1 left is wrong: i=" + i);
       }
+      ok(!charAtPt1.tentativeCaretOffsetNotFound,
+         "runCharAtPointTest (" + aTargetName + "): tentative caret offset for charAtPt1 isn't found: i=" + i);
+      if (!charAtPt1.tentativeCaretOffsetNotFound) {
+        is(charAtPt1.tentativeCaretOffset, kLeftTentativeCaretOffset[i],
+           "runCharAtPointTest (" + aTargetName + "): tentative caret offset for charAtPt1 is wrong: i=" + i);
+      }
     }
 
     // Test #2, getting same character rect by the point near the bottom-right.
     var charAtPt2 = synthesizeCharAtPoint(textRect.left + textRect.width - 2,
                                           textRect.top + textRect.height - 2);
     if (checkQueryContentResult(charAtPt2,
           "runCharAtPointTest (" + aTargetName + "): charAtPt2: i=" + i)) {
       ok(!charAtPt2.notFound,
          "runCharAtPointTest (" + aTargetName + "): charAtPt2 isn't found: i=" + i);
       if (!charAtPt2.notFound) {
         is(charAtPt2.offset, kTestingOffset[i],
            "runCharAtPointTest (" + aTargetName + "): charAtPt2 offset is wrong: i=" + i);
         checkRect(charAtPt2, textRect, "runCharAtPointTest (" + aTargetName +
                   "): charAtPt1 left is wrong: i=" + i);
       }
+      ok(!charAtPt2.tentativeCaretOffsetNotFound,
+         "runCharAtPointTest (" + aTargetName + "): tentative caret offset for charAtPt2 isn't found: i=" + i);
+      if (!charAtPt2.tentativeCaretOffsetNotFound) {
+        is(charAtPt2.tentativeCaretOffset, kRightTentativeCaretOffset[i],
+           "runCharAtPointTest (" + aTargetName + "): tentative caret offset for charAtPt2 is wrong: i=" + i);
+      }
     }
 
     // Test #3, getting left character offset.
     var charAtPt3 = synthesizeCharAtPoint(textRect.left - 2,
                                           textRect.top + 1);
     if (checkQueryContentResult(charAtPt3,
           "runCharAtPointTest (" + aTargetName + "): charAtPt3: i=" + i)) {
       is(charAtPt3.notFound, kLeftSideOffset[i] == kNone,
          kLeftSideOffset[i] == kNone ?
            "runCharAtPointTest (" + aTargetName + "): charAtPt3 is found: i=" + i :
            "runCharAtPointTest (" + aTargetName + "): charAtPt3 isn't found: i=" + i);
       if (!charAtPt3.notFound) {
         is(charAtPt3.offset, kLeftSideOffset[i],
            "runCharAtPointTest (" + aTargetName + "): charAtPt3 offset is wrong: i=" + i);
       }
+      if (kLeftSideOffset[i] == kNone) {
+        // There may be no enough padding-left (depends on platform)
+        todo(false,
+             "runCharAtPointTest (" + aTargetName + "): tentative caret offset for charAtPt3 isn't tested: i=" + i);
+      } else {
+        ok(!charAtPt3.tentativeCaretOffsetNotFound,
+           "runCharAtPointTest (" + aTargetName + "): tentative caret offset for charAtPt3 isn't found: i=" + i);
+        if (!charAtPt3.tentativeCaretOffsetNotFound) {
+          is(charAtPt3.tentativeCaretOffset, kLeftTentativeCaretOffset[i],
+             "runCharAtPointTest (" + aTargetName + "): tentative caret offset for charAtPt3 is wrong: i=" + i);
+        }
+      }
     }
 
     // Test #4, getting right character offset.
     var charAtPt4 = synthesizeCharAtPoint(textRect.left + textRect.width + 1,
                                           textRect.top + textRect.height - 2);
     if (checkQueryContentResult(charAtPt4,
           "runCharAtPointTest (" + aTargetName + "): charAtPt4: i=" + i)) {
       is(charAtPt4.notFound, kRightSideOffset[i] == kNone,
          kRightSideOffset[i] == kNone ?
            "runCharAtPointTest (" + aTargetName + "): charAtPt4 is found: i=" + i :
            "runCharAtPointTest (" + aTargetName + "): charAtPt4 isn't found: i=" + i);
       if (!charAtPt4.notFound) {
         is(charAtPt4.offset, kRightSideOffset[i],
            "runCharAtPointTest (" + aTargetName + "): charAtPt4 offset is wrong: i=" + i);
       }
+      ok(!charAtPt4.tentativeCaretOffsetNotFound,
+         "runCharAtPointTest (" + aTargetName + "): tentative caret offset for charAtPt4 isn't found: i=" + i);
+      if (!charAtPt4.tentativeCaretOffsetNotFound) {
+        is(charAtPt4.tentativeCaretOffset, kRightTentativeCaretOffset[i],
+           "runCharAtPointTest (" + aTargetName + "): tentative caret offset for charAtPt4 is wrong: i=" + i);
+      }
     }
   }
 }
 
 function runCharAtPointAtOutsideTest()
 {
   textarea.focus();
   textarea.value = "some text";
@@ -2263,16 +2295,18 @@ function runCharAtPointAtOutsideTest()
   }
   // Check on a text node which is at the outside of editor.
   var charAtPt = synthesizeCharAtPoint(editorRect.left + 20,
                                        editorRect.top - 10);
   if (checkQueryContentResult(charAtPt,
         "runCharAtPointAtOutsideTest: charAtPt")) {
     ok(charAtPt.notFound,
        "runCharAtPointAtOutsideTest: charAtPt is found on outside of editor");
+    ok(charAtPt.tentativeCaretOffsetNotFound,
+       "runCharAtPointAtOutsideTest: tentative caret offset for charAtPt is found on outside of editor");
   }
 }
 
 function runBug722639Test()
 {
   textarea.focus();
   textarea.value = "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n";
   textarea.value += textarea.value;