Bug 555642 part.1 nsCaret should have a way to override the caret visible state for hiding caret temporarily and nsEditor should hide caret if composition string doesn't have caret information r=roc
authorMasayuki Nakano <masayuki@d-toybox.com>
Mon, 17 Aug 2015 20:58:38 +0900
changeset 258031 f84b3c91aeceeeefd94cdf13cd5955d89342d13d
parent 258030 edd5e534c1fe41a81418f65009e2199e232e2cfe
child 258032 77f71a695592baff88cc3084dfd41ca3f2830bf7
push id29241
push userkwierso@gmail.com
push dateTue, 18 Aug 2015 00:00:46 +0000
treeherdermozilla-central@6ae3e9ff53b2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersroc
bugs555642
milestone43.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 555642 part.1 nsCaret should have a way to override the caret visible state for hiding caret temporarily and nsEditor should hide caret if composition string doesn't have caret information r=roc
editor/libeditor/IMETextTxn.cpp
editor/libeditor/nsEditor.cpp
editor/libeditor/nsEditor.h
layout/base/nsCaret.cpp
layout/base/nsCaret.h
--- a/editor/libeditor/IMETextTxn.cpp
+++ b/editor/libeditor/IMETextTxn.cpp
@@ -218,17 +218,22 @@ IMETextTxn::SetIMESelection(nsEditor& aE
       NS_ASSERTION(!textRange.Length(), "nsEditor doesn't support wide caret");
       int32_t caretOffset = static_cast<int32_t>(
         aOffsetInNode +
           std::min(textRange.mStartOffset, aLengthOfCompositionString));
       MOZ_ASSERT(caretOffset >= 0 &&
                  static_cast<uint32_t>(caretOffset) <= maxOffset);
       rv = selection->Collapse(aTextNode, caretOffset);
       setCaret = setCaret || NS_SUCCEEDED(rv);
-      NS_ASSERTION(setCaret, "Failed to collapse normal selection");
+      if (NS_WARN_IF(!setCaret)) {
+        continue;
+      }
+      // If caret range is specified explicitly, we should show the caret if
+      // it should be so.
+      aEditor.HideCaret(false);
       continue;
     }
 
     // If the clause length is 0, it should be a bug.
     if (!textRange.Length()) {
       NS_WARNING("Any clauses must not be empty");
       continue;
     }
@@ -287,15 +292,17 @@ IMETextTxn::SetIMESelection(nsEditor& aE
   if (!setCaret) {
     int32_t caretOffset =
       static_cast<int32_t>(aOffsetInNode + aLengthOfCompositionString);
     MOZ_ASSERT(caretOffset >= 0 &&
                static_cast<uint32_t>(caretOffset) <= maxOffset);
     rv = selection->Collapse(aTextNode, caretOffset);
     NS_ASSERTION(NS_SUCCEEDED(rv),
                  "Failed to set caret at the end of composition string");
+    // If caret range isn't specified explicitly, we should hide the caret.
+    aEditor.HideCaret(true);
   }
 
   rv = selection->EndBatchChanges();
   NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to end batch changes");
 
   return rv;
 }
--- a/editor/libeditor/nsEditor.cpp
+++ b/editor/libeditor/nsEditor.cpp
@@ -142,27 +142,30 @@ nsEditor::nsEditor()
 ,  mDirection(eNone)
 ,  mDocDirtyState(-1)
 ,  mSpellcheckCheckboxState(eTriUnset)
 ,  mShouldTxnSetSelection(true)
 ,  mDidPreDestroy(false)
 ,  mDidPostCreate(false)
 ,  mDispatchInputEvent(true)
 ,  mIsInEditAction(false)
+,  mHidingCaret(false)
 {
 }
 
 nsEditor::~nsEditor()
 {
   NS_ASSERTION(!mDocWeak || mDidPreDestroy, "Why PreDestroy hasn't been called?");
 
   if (mComposition) {
     mComposition->OnEditorDestroyed();
     mComposition = nullptr;
   }
+  // If this editor is still hiding the caret, we need to restore it.
+  HideCaret(false);
   mTxnMgr = nullptr;
 
   delete mPhonetic;
 }
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(nsEditor)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsEditor)
@@ -459,16 +462,18 @@ nsEditor::PreDestroy(bool aDestroyingFra
   if (mInlineSpellChecker)
     mInlineSpellChecker->Cleanup(aDestroyingFrames);
 
   // tell our listeners that the doc is going away
   NotifyDocumentListeners(eDocumentToBeDestroyed);
 
   // Unregister event listeners
   RemoveEventListeners();
+  // If this editor is still hiding the caret, we need to restore it.
+  HideCaret(false);
   mActionListeners.Clear();
   mEditorObservers.Clear();
   mDocStateListeners.Clear();
   mInlineSpellChecker = nullptr;
   mSpellcheckCheckboxState = eTriUnset;
   mRootElement = nullptr;
 
   mDidPreDestroy = true;
@@ -2060,16 +2065,20 @@ nsEditor::EndIMEComposition()
     nsCOMPtr<nsIAbsorbingTransaction> plcTxn = do_QueryInterface(txn);
     if (plcTxn) {
       DebugOnly<nsresult> rv = plcTxn->Commit();
       NS_ASSERTION(NS_SUCCEEDED(rv),
                    "nsIAbsorbingTransaction::Commit() failed");
     }
   }
 
+  // Composition string may have hidden the caret.  Therefore, we need to
+  // cancel it here.
+  HideCaret(false);
+
   /* reset the data we need to construct a transaction */
   mIMETextNode = nullptr;
   mIMETextOffset = 0;
   mIMETextLength = 0;
   mComposition->EndHandlingComposition(this);
   mComposition = nullptr;
 
   // notify editor observers of action
@@ -5253,8 +5262,28 @@ nsEditor::GetIMESelectionStartOffsetIn(n
         MOZ_ASSERT(range->EndOffset() >= 0,
                    "start offset shouldn't be negative");
         minOffset = std::min(minOffset, range->EndOffset());
       }
     }
   }
   return minOffset < INT32_MAX ? minOffset : -1;
 }
+
+void
+nsEditor::HideCaret(bool aHide)
+{
+  if (mHidingCaret == aHide) {
+    return;
+  }
+
+  nsCOMPtr<nsIPresShell> presShell = GetPresShell();
+  NS_ENSURE_TRUE_VOID(presShell);
+  nsRefPtr<nsCaret> caret = presShell->GetCaret();
+  NS_ENSURE_TRUE_VOID(caret);
+
+  mHidingCaret = aHide;
+  if (aHide) {
+    caret->AddForceHide();
+  } else {
+    caret->RemoveForceHide();
+  }
+}
--- a/editor/libeditor/nsEditor.h
+++ b/editor/libeditor/nsEditor.h
@@ -819,16 +819,23 @@ public:
 
   /**
    * FindBetterInsertionPoint() tries to look for better insertion point which
    * is typically the nearest text node and offset in it.
    */
   void FindBetterInsertionPoint(nsCOMPtr<nsINode>& aNode,
                                 int32_t& aOffset);
 
+  /**
+   * HideCaret() hides caret with nsCaret::AddForceHide() or may show carent
+   * with nsCaret::RemoveForceHide().  This does NOT set visibility of
+   * nsCaret.  Therefore, this is stateless.
+   */
+  void HideCaret(bool aHide);
+
 protected:
   enum Tristate {
     eTriUnset,
     eTriFalse,
     eTriTrue
   };
   // Spellchecking
   nsCString mContentMIMEType;       // MIME type of the doc we are editing.
@@ -878,16 +885,17 @@ protected:
   int8_t            mDocDirtyState;      // -1 = not initialized
   uint8_t           mSpellcheckCheckboxState; // a Tristate value
 
   bool mShouldTxnSetSelection;  // turn off for conservative selection adjustment by txns
   bool mDidPreDestroy;    // whether PreDestroy has been called
   bool mDidPostCreate;    // whether PostCreate has been called
   bool mDispatchInputEvent;
   bool mIsInEditAction;   // true while the instance is handling an edit action
+  bool mHidingCaret;      // whether caret is hidden forcibly.
 
   friend bool NSCanUnload(nsISupports* serviceMgr);
   friend class nsAutoTxnsConserveSelection;
   friend class nsAutoSelectionReset;
   friend class nsAutoRules;
   friend class nsRangeUpdater;
 };
 
--- a/layout/base/nsCaret.cpp
+++ b/layout/base/nsCaret.cpp
@@ -122,18 +122,19 @@ IsKeyboardRTL()
   if (bidiKeyboard) {
     bidiKeyboard->IsLangRTL(&isKeyboardRTL);
   }
   return isKeyboardRTL;
 }
 
 nsCaret::nsCaret()
 : mOverrideOffset(0)
+, mBlinkCount(-1)
+, mHideCount(0)
 , mIsBlinkOn(false)
-, mBlinkCount(-1)
 , mVisible(false)
 , mReadOnly(false)
 , mShowDuringSelection(false)
 , mIgnoreUserModify(true)
 {
 }
 
 nsCaret::~nsCaret()
@@ -261,17 +262,17 @@ void nsCaret::SetVisible(bool inMakeVisi
   mVisible = inMakeVisible;
   mIgnoreUserModify = mVisible;
   ResetBlinking();
   SchedulePaint();
 }
 
 bool nsCaret::IsVisible()
 {
-  if (!mVisible) {
+  if (!mVisible || mHideCount) {
     return false;
   }
 
   if (!mShowDuringSelection &&
       !(sSelectionCaretEnabled && sSelectionCaretsAffectCaret)) {
     Selection* selection = GetSelectionInternal();
     if (!selection) {
       return false;
@@ -298,16 +299,35 @@ bool nsCaret::IsVisible()
 
   if (IsMenuPopupHidingCaret()) {
     return false;
   }
 
   return true;
 }
 
+void nsCaret::AddForceHide()
+{
+  MOZ_ASSERT(mHideCount < UINT32_MAX);
+  if (++mHideCount > 1) {
+    return;
+  }
+  ResetBlinking();
+  SchedulePaint();
+}
+
+void nsCaret::RemoveForceHide()
+{
+  if (!mHideCount || --mHideCount) {
+    return;
+  }
+  ResetBlinking();
+  SchedulePaint();
+}
+
 void nsCaret::SetCaretReadOnly(bool inMakeReadonly)
 {
   mReadOnly = inMakeReadonly;
   ResetBlinking();
   SchedulePaint();
 }
 
 /* static */ nsRect
@@ -617,17 +637,17 @@ nsCaret::NotifySelectionChanged(nsIDOMDo
 
   return NS_OK;
 }
 
 void nsCaret::ResetBlinking()
 {
   mIsBlinkOn = true;
 
-  if (mReadOnly || !mVisible) {
+  if (mReadOnly || !mVisible || mHideCount) {
     StopBlinking();
     return;
   }
 
   if (mBlinkTimer) {
     mBlinkTimer->Cancel();
   } else {
     nsresult  err;
--- a/layout/base/nsCaret.h
+++ b/layout/base/nsCaret.h
@@ -73,16 +73,31 @@ class nsCaret final : public nsISelectio
     /** IsVisible will get the visibility of the caret.
      *  This returns false if the caret is hidden because it was set
      *  to not be visible, or because the selection is not collapsed, or
      *  because an open popup is hiding the caret.
      *  It does not take account of blinking or the caret being hidden
      *  because we're in non-editable/disabled content.
      */
     bool IsVisible();
+    /**
+     * AddForceHide() increases mHideCount and hide the caret even if
+     * SetVisible(true) has been or will be called.  This is useful when the
+     * caller wants to hide caret temporarily and it needs to cancel later.
+     * Especially, in the latter case, it's too difficult to decide if the
+     * caret should be actually visible or not because caret visible state
+     * is set from a lot of event handlers.  So, it's very stateful.
+     */
+    void AddForceHide();
+    /**
+     * RemoveForceHide() decreases mHideCount if it's over 0.
+     * If the value becomes 0, this may show the caret if SetVisible(true)
+     * has been called.
+     */
+    void RemoveForceHide();
     /** SetCaretReadOnly set the appearance of the caret
      *  @param inMakeReadonly true to show the caret in a 'read only' state,
      *         false to show the caret in normal, editing state
      */
     void SetCaretReadOnly(bool inMakeReadonly);
     /**
      * @param aVisibility true if the caret should be visible even when the
      * selection is not collapsed.
@@ -196,28 +211,33 @@ protected:
      * focus node instead.
      */
     nsCOMPtr<nsINode>     mOverrideContent;
     /**
      * The character offset to draw the caret at.
      * Ignored if mOverrideContent is null.
      */
     int32_t               mOverrideOffset;
-
-    /**
-     * mIsBlinkOn is true when we're in a blink cycle where the caret is on.
-     */
-    bool                  mIsBlinkOn;
     /**
      * mBlinkCount is used to control the number of times to blink the caret
      * before stopping the blink. This is reset each time we reset the
      * blinking.
      */
     int32_t               mBlinkCount;
     /**
+     * mHideCount is not 0, it means that somebody doesn't want the caret
+     * to be visible.  See AddForceHide() and RemoveForceHide().
+     */
+    uint32_t              mHideCount;
+
+    /**
+     * mIsBlinkOn is true when we're in a blink cycle where the caret is on.
+     */
+    bool                  mIsBlinkOn;
+    /**
      * mIsVisible is true when SetVisible was last called with 'true'.
      */
     bool                  mVisible;
     /**
      * mReadOnly is true when the caret is set to "read only" mode (i.e.,
      * it doesn't blink).
      */
     bool                  mReadOnly;