Bug 1164693 - Part 1: Directional caret should point in caret direction in bidi paragraphs. r=smontagu
authorTed Clancy <tclancy@mozilla.com>
Fri, 05 Jun 2015 21:44:54 -0400
changeset 249993 b355b20f61235e6a06cacca56161558ef8cf4390
parent 249992 ad46ffd4c50094b705573d37c01034504ef731de
child 249994 2a383ebb99d5a76b2fdd13b4248368feec57e639
push id61403
push usercbook@mozilla.com
push dateTue, 23 Jun 2015 08:21:57 +0000
treeherdermozilla-inbound@b232c2a5af6d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmontagu
bugs1164693
milestone41.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 1164693 - Part 1: Directional caret should point in caret direction in bidi paragraphs. r=smontagu
layout/base/nsCaret.cpp
layout/base/nsCaret.h
layout/base/tests/bug646382-1-ref.html
layout/base/tests/bug646382-2-ref.html
layout/generic/nsFrameSelection.h
layout/generic/nsSelection.cpp
--- a/layout/base/nsCaret.cpp
+++ b/layout/base/nsCaret.cpp
@@ -110,19 +110,24 @@ AdjustCaretFrameForLineEnd(nsIFrame** aF
       NS_ASSERTION(r->GetType() == nsGkAtoms::textFrame, "Expected text frame");
       *aOffset = (static_cast<nsTextFrame*>(r))->GetContentEnd();
       return;
     }
   }
 }
 
 static bool
-IsBidiUI()
+IsKeyboardRTL()
 {
-  return Preferences::GetBool("bidi.browser.ui");
+  bool isKeyboardRTL = false;
+  nsIBidiKeyboard* bidiKeyboard = nsContentUtils::GetBidiKeyboard();
+  if (bidiKeyboard) {
+    bidiKeyboard->IsLangRTL(&isKeyboardRTL);
+  }
+  return isKeyboardRTL;
 }
 
 nsCaret::nsCaret()
 : mOverrideOffset(0)
 , mIsBlinkOn(false)
 , mBlinkCount(-1)
 , mVisible(false)
 , mReadOnly(false)
@@ -473,28 +478,40 @@ nsCaret::SetCaretPosition(nsIDOMNode* aN
 {
   mOverrideContent = do_QueryInterface(aNode);
   mOverrideOffset = aOffset;
 
   ResetBlinking();
   SchedulePaint();
 }
 
+bool
+nsCaret::IsBidiUI()
+{
+  nsIFrame* frame = nullptr;
+
+  if(Selection* selection = GetSelectionInternal()) {
+    int32_t contentOffset;
+    frame = GetFrameAndOffset(selection, mOverrideContent, mOverrideOffset,
+                              &contentOffset);
+  }
+
+  return (frame && frame->GetStateBits() & NS_FRAME_IS_BIDI) ||
+         Preferences::GetBool("bidi.browser.ui");
+}
+
 void
 nsCaret::CheckSelectionLanguageChange()
 {
   if (!IsBidiUI()) {
     return;
   }
 
-  bool isKeyboardRTL = false;
-  nsIBidiKeyboard* bidiKeyboard = nsContentUtils::GetBidiKeyboard();
-  if (bidiKeyboard) {
-    bidiKeyboard->IsLangRTL(&isKeyboardRTL);
-  }
+  bool isKeyboardRTL = IsKeyboardRTL();
+
   // Call SelectionLanguageChange on every paint. Mostly it will be a noop
   // but it should be fast anyway. This guarantees we never paint the caret
   // at the wrong place.
   Selection* selection = GetSelectionInternal();
   if (selection) {
     selection->SelectionLanguageChange(isKeyboardRTL);
   }
 }
@@ -673,18 +690,19 @@ nsCaret::GetCaretFrameForNodeOffset(nsFr
   //
   // Direction Style from visibility->mDirection
   // ------------------
   // NS_STYLE_DIRECTION_LTR : LTR or Default
   // NS_STYLE_DIRECTION_RTL
   if (theFrame->PresContext()->BidiEnabled())
   {
     // If there has been a reflow, take the caret Bidi level to be the level of the current frame
-    if (aBidiLevel & BIDI_LEVEL_UNDEFINED)
+    if (aBidiLevel & BIDI_LEVEL_UNDEFINED) {
       aBidiLevel = NS_GET_EMBEDDING_LEVEL(theFrame);
+    }
 
     int32_t start;
     int32_t end;
     nsIFrame* frameBefore;
     nsIFrame* frameAfter;
     nsBidiLevel levelBefore; // Bidi level of the character before the caret
     nsBidiLevel levelAfter;  // Bidi level of the character after the caret
 
@@ -897,31 +915,32 @@ nsCaret::ComputeCaretRects(nsIFrame* aFr
   if (NS_STYLE_DIRECTION_RTL == vis->mDirection) {
     if (isVertical) {
       aCaretRect->y -= aCaretRect->height;
     } else {
       aCaretRect->x -= aCaretRect->width;
     }
   }
 
-  // Simon -- make a hook to draw to the left or right of the caret to show keyboard language direction
   aHookRect->SetEmpty();
-  if (!IsBidiUI()) {
+
+  Selection* selection = GetSelectionInternal();
+  if (!selection || !selection->GetFrameSelection()) {
     return;
   }
 
-  bool isCaretRTL;
-  nsIBidiKeyboard* bidiKeyboard = nsContentUtils::GetBidiKeyboard();
-  // if bidiKeyboard->IsLangRTL() fails, there is no way to tell the
-  // keyboard direction, or the user has no right-to-left keyboard
-  // installed, so we never draw the hook.
-  if (bidiKeyboard && NS_SUCCEEDED(bidiKeyboard->IsLangRTL(&isCaretRTL))) {
-    // If keyboard language is RTL, draw the hook on the left; if LTR, to the right
+  if (IsBidiUI() || IsKeyboardRTL()) {
+    // If caret level is RTL, draw the hook on the left; if LTR, to the right
     // The height of the hook rectangle is the same as the width of the caret
     // rectangle.
+    int caretBidiLevel = selection->GetFrameSelection()->GetCaretBidiLevel();
+    if (caretBidiLevel & BIDI_LEVEL_UNDEFINED) {
+      caretBidiLevel = NS_GET_EMBEDDING_LEVEL(aFrame);
+    }
+    bool isCaretRTL = caretBidiLevel % 2;
     if (isVertical) {
       aHookRect->SetRect(aCaretRect->XMost() - bidiIndicatorSize,
                          aCaretRect->y + (isCaretRTL ? bidiIndicatorSize * -1 :
                                                        aCaretRect->height),
                          aCaretRect->height,
                          bidiIndicatorSize);
     } else {
       aHookRect->SetRect(aCaretRect->x + (isCaretRTL ? bidiIndicatorSize * -1 :
--- a/layout/base/nsCaret.h
+++ b/layout/base/nsCaret.h
@@ -153,16 +153,17 @@ class nsCaret final : public nsISelectio
                                       int32_t   aFrameOffset,
                                       nscoord*  aBidiIndicatorSize);
 
     size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
 
 protected:
     static void   CaretBlinkCallback(nsITimer *aTimer, void *aClosure);
 
+    bool          IsBidiUI();
     void          CheckSelectionLanguageChange();
 
     void          ResetBlinking();
     void          StopBlinking();
 
     mozilla::dom::Selection* GetSelectionInternal();
 
     struct Metrics {
--- a/layout/base/tests/bug646382-1-ref.html
+++ b/layout/base/tests/bug646382-1-ref.html
@@ -1,17 +1,18 @@
 <html class="reftest-wait">
   <head>
     <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
   </head>
   <body onload="start()">
-    <textarea onfocus="done()" style="-moz-appearance: none">س</textarea>
+    <textarea onfocus="done()" style="-moz-appearance: none">س&lrm;</textarea>
     <script>
       var textarea = document.querySelector("textarea");
       function start() {
         textarea.focus();
       }
       function done() {
+        textarea.selectionStart = textarea.selectionEnd = 2;
         document.documentElement.removeAttribute("class");
       }
     </script>
   </body>
 </html>
--- a/layout/base/tests/bug646382-2-ref.html
+++ b/layout/base/tests/bug646382-2-ref.html
@@ -1,14 +1,15 @@
 <html class="reftest-wait">
   <body onload="start()">
-    <textarea dir="rtl" onfocus="done()" style="-moz-appearance: none">s</textarea>
+    <textarea dir="rtl" onfocus="done()" style="-moz-appearance: none">s&rlm;</textarea>
     <script>
       var textarea = document.querySelector("textarea");
       function start() {
         textarea.focus();
       }
       function done() {
+        textarea.selectionStart = textarea.selectionEnd = 2;
         document.documentElement.removeAttribute("class");
       }
     </script>
   </body>
 </html>
--- a/layout/generic/nsFrameSelection.h
+++ b/layout/generic/nsFrameSelection.h
@@ -616,17 +616,19 @@ private:
                      bool aContinueSelection,
                      bool aMultipleSelection);
 
   void BidiLevelFromMove(nsIPresShell* aPresShell,
                          nsIContent *aNode,
                          uint32_t aContentOffset,
                          nsSelectionAmount aAmount,
                          CaretAssociateHint aHint);
-  void BidiLevelFromClick(nsIContent *aNewFocus, uint32_t aContentOffset);
+  void BidiLevelFromClick(nsIContent *aNewFocus,
+                          uint32_t aContentOffset,
+                          CaretAssociateHint aHint);
   nsPrevNextBidiLevels GetPrevNextBidiLevels(nsIContent *aNode,
                                              uint32_t aContentOffset,
                                              CaretAssociateHint aHint,
                                              bool aJumpLines) const;
 
   bool AdjustForMaintainedSelection(nsIContent *aContent, int32_t aOffset);
 
 // post and pop reasons for notifications. we may stack these later
--- a/layout/generic/nsSelection.cpp
+++ b/layout/generic/nsSelection.cpp
@@ -1426,22 +1426,23 @@ void nsFrameSelection::BidiLevelFromMove
 
 /**
  * BidiLevelFromClick is called when the caret is repositioned by clicking the mouse
  *
  * @param aNode is the content node
  * @param aContentOffset is the new caret position, as an offset into aNode
  */
 void nsFrameSelection::BidiLevelFromClick(nsIContent *aNode,
-                                          uint32_t    aContentOffset)
+                                          uint32_t    aContentOffset,
+                                          CaretAssociateHint aHint)
 {
   nsIFrame* clickInFrame=nullptr;
   int32_t OffsetNotUsed;
 
-  clickInFrame = GetFrameForNodeOffset(aNode, aContentOffset, mHint, &OffsetNotUsed);
+  clickInFrame = GetFrameForNodeOffset(aNode, aContentOffset, aHint, &OffsetNotUsed);
   if (!clickInFrame)
     return;
 
   SetCaretBidiLevel(NS_GET_EMBEDDING_LEVEL(clickInFrame));
 }
 
 
 bool
@@ -1510,17 +1511,17 @@ nsFrameSelection::HandleClick(nsIContent
     if (!IsValidSelectionPoint(this, aNewFocus)) {
       mAncestorLimiter = nullptr;
     }
   }
 
   // Don't take focus when dragging off of a table
   if (!mDragSelectingCells)
   {
-    BidiLevelFromClick(aNewFocus, aContentOffset);
+    BidiLevelFromClick(aNewFocus, aContentOffset, aHint);
     PostReason(nsISelectionListener::MOUSEDOWN_REASON + nsISelectionListener::DRAG_REASON);
     if (aContinueSelection &&
         AdjustForMaintainedSelection(aNewFocus, aContentOffset))
       return NS_OK; //shift clicked to maintained selection. rejected.
 
     int8_t index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
     AutoPrepareFocusRange prep(mDomSelections[index], aContinueSelection, aMultipleSelection);
     return TakeFocus(aNewFocus, aContentOffset, aContentEndOffset, aHint,