Bug 1215959 - (GeckoCaret2) Upgrade Core and AccessibleCaret, r=smaug
authorMark Capella <markcapella@twcny.rr.com>
Tue, 01 Dec 2015 15:25:06 -0500
changeset 309161 716f4df6fad5bc5b6fcbddd4385676467c97bb6c
parent 309160 2d4cfc1c9e97ddab80895ef4e3f5570baea65520
child 309162 15522bc2931a52ec435f22e233f747264b90f647
push id5513
push userraliiev@mozilla.com
push dateMon, 25 Jan 2016 13:55:34 +0000
treeherdermozilla-beta@5ee97dd05b5c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1215959
milestone45.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 1215959 - (GeckoCaret2) Upgrade Core and AccessibleCaret, r=smaug
dom/base/nsISelectionListener.idl
dom/base/nsISelectionPrivate.idl
dom/events/ContentEventHandler.cpp
dom/webidl/CaretStateChangedEvent.webidl
editor/libeditor/IMETextTxn.cpp
editor/libeditor/IMETextTxn.h
layout/base/AccessibleCaretManager.cpp
layout/base/AccessibleCaretManager.h
layout/generic/Selection.h
layout/generic/nsFrameSelection.h
layout/generic/nsSelection.cpp
widget/TextEvents.h
widget/android/nsWindow.cpp
--- a/dom/base/nsISelectionListener.idl
+++ b/dom/base/nsISelectionListener.idl
@@ -3,24 +3,25 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISupports.idl"
 
 interface nsIDOMDocument;
 interface nsISelection;
 
-[scriptable, uuid(280cd784-23c2-468d-8624-354e0b3804bd)]
+[scriptable, uuid(45686299-ae2b-46bc-9502-c56c35691ab9)]
 interface nsISelectionListener : nsISupports
 {
   const short NO_REASON=0;
   const short DRAG_REASON=1;
   const short MOUSEDOWN_REASON=2;/*bitflags*/
   const short MOUSEUP_REASON=4;/*bitflags*/
   const short KEYPRESS_REASON=8;/*bitflags*/
   const short SELECTALL_REASON=16;
   const short COLLAPSETOSTART_REASON=32;
   const short COLLAPSETOEND_REASON=64;
+  const short IME_REASON=128;
 
 	void			notifySelectionChanged(in nsIDOMDocument doc, in nsISelection sel, in short reason);
 };
 
 
--- a/dom/base/nsISelectionPrivate.idl
+++ b/dom/base/nsISelectionPrivate.idl
@@ -23,34 +23,34 @@ template<class T> class nsTArray;
 
 [ptr] native nsIFrame(nsIFrame);
 [ptr] native RangeArray(nsTArray<nsRange*>);
 [ref] native constTextRangeStyleRef(const mozilla::TextRangeStyle);
 [ref] native nsPointRef(nsPoint);
 native nsDirection(nsDirection);
 native ScrollAxis(nsIPresShell::ScrollAxis);
 
-[scriptable, builtinclass, uuid(5a82ee9a-35ce-11e4-8c3e-b7043d68ad70)]
+[scriptable, builtinclass, uuid(0c9f4f74-ee7e-4fe9-be6b-0ba856368178)]
 interface nsISelectionPrivate : nsISelection
  {
     const short ENDOFPRECEDINGLINE=0;
     const short STARTOFNEXTLINE=1;
 
     attribute boolean interlinePosition;
     [noscript] attribute nsIContent ancestorLimiter;
 
     /* startBatchChanges
        match this up with endbatchChanges. will stop ui updates while multiple selection methods are called
     */
-    [noscript] void  startBatchChanges();
+    [noscript] void startBatchChanges();
 
     /* endBatchChanges
        match this up with startBatchChanges
     */
-    [noscript] void  endBatchChanges();
+    [noscript] void endBatchChanges();
 
     DOMString  toStringWithFormat(in string formatType, in unsigned long flags, in int32_t wrapColumn);
     void  addSelectionListener(in nsISelectionListener newListener);
     void  removeSelectionListener(in nsISelectionListener listenerToRemove);
 
     /* Table selection stuff
        We should probably move this and table-related 
        items in nsFrameSelection  to a
--- a/dom/events/ContentEventHandler.cpp
+++ b/dom/events/ContentEventHandler.cpp
@@ -1653,17 +1653,20 @@ ContentEventHandler::OnSelectionEvent(Wi
         (startNode != endNode || startNodeOffset != endNodeOffset)) {
       if (aEvent->mReversed) {
         rv = mSelection->Extend(startNode, startNodeOffset);
       } else {
         rv = mSelection->Extend(endNode, endNodeOffset);
       }
     }
   }
-  mSelection->EndBatchChanges();
+
+  // Pass the eSetSelection events reason along with the BatchChange-end
+  // selection change notifications.
+  mSelection->EndBatchChangesInternal(aEvent->mReason);
   NS_ENSURE_SUCCESS(rv, rv);
 
   mSelection->ScrollIntoViewInternal(
     nsISelectionController::SELECTION_FOCUS_REGION,
     false, nsIPresShell::ScrollAxis(), nsIPresShell::ScrollAxis());
   aEvent->mSucceeded = true;
   return NS_OK;
 }
--- a/dom/webidl/CaretStateChangedEvent.webidl
+++ b/dom/webidl/CaretStateChangedEvent.webidl
@@ -13,24 +13,26 @@ enum CaretChangedReason {
   "releasecaret"
 };
 
 dictionary CaretStateChangedEventInit : EventInit {
   boolean collapsed = true;
   DOMRectReadOnly? boundingClientRect = null;
   CaretChangedReason reason = "visibilitychange";
   boolean caretVisible = false;
+  boolean caretVisuallyVisible = false;
   boolean selectionVisible = false;
   boolean selectionEditable = false;
   DOMString selectedTextContent = "";
 };
 
 [Constructor(DOMString type, optional CaretStateChangedEventInit eventInit),
  ChromeOnly]
 interface CaretStateChangedEvent : Event {
   readonly attribute boolean collapsed;
   readonly attribute DOMRectReadOnly? boundingClientRect;
   readonly attribute CaretChangedReason reason;
   readonly attribute boolean caretVisible;
+  readonly attribute boolean caretVisuallyVisible;
   readonly attribute boolean selectionVisible;
   readonly attribute boolean selectionEditable;
   readonly attribute DOMString selectedTextContent;
 };
--- a/editor/libeditor/IMETextTxn.cpp
+++ b/editor/libeditor/IMETextTxn.cpp
@@ -2,41 +2,52 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "IMETextTxn.h"
 
 #include "mozilla/dom/Selection.h"      // local var
 #include "mozilla/dom/Text.h"           // mTextNode
+#include "mozilla/Preferences.h"        // nsCaret Visibility
 #include "nsAString.h"                  // params
 #include "nsDebug.h"                    // for NS_ASSERTION, etc
 #include "nsEditor.h"                   // mEditor
 #include "nsError.h"                    // for NS_SUCCEEDED, NS_FAILED, etc
 #include "nsIPresShell.h"               // nsISelectionController constants
 #include "nsRange.h"                    // local var
 #include "nsQueryObject.h"              // for do_QueryObject
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
+/*static*/ bool
+IMETextTxn::sCaretsExtendedVisibility = false;
+
+
 IMETextTxn::IMETextTxn(Text& aTextNode, uint32_t aOffset,
                        uint32_t aReplaceLength,
                        TextRangeArray* aTextRangeArray,
                        const nsAString& aStringToInsert,
                        nsEditor& aEditor)
   : EditTxn()
   , mTextNode(&aTextNode)
   , mOffset(aOffset)
   , mReplaceLength(aReplaceLength)
   , mRanges(aTextRangeArray)
   , mStringToInsert(aStringToInsert)
   , mEditor(aEditor)
   , mFixed(false)
 {
+  static bool addedPrefs = false;
+  if (!addedPrefs) {
+    mozilla::Preferences::AddBoolVarCache(&sCaretsExtendedVisibility,
+                                          "layout.accessiblecaret.extendedvisibility");
+    addedPrefs = true;
+  }
 }
 
 IMETextTxn::~IMETextTxn()
 {
 }
 
 NS_IMPL_CYCLE_COLLECTION_INHERITED(IMETextTxn, EditTxn,
                                    mTextNode)
@@ -292,17 +303,22 @@ 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);
+    // Hiding the caret benefits a Windows build (see bug 555642 comment #6),
+    // but causes loss of Fennec AccessibleCaret visibility during Caret drag.
+    if (!sCaretsExtendedVisibility) {
+      aEditor.HideCaret(true);
+    }
   }
 
-  rv = selection->EndBatchChanges();
+  rv = selection->EndBatchChangesInternal();
   NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to end batch changes");
 
   return rv;
 }
--- a/editor/libeditor/IMETextTxn.h
+++ b/editor/libeditor/IMETextTxn.h
@@ -78,16 +78,23 @@ private:
 
   /** The text to insert into mTextNode at mOffset */
   nsString mStringToInsert;
 
   /** The editor, which is used to get the selection controller */
   nsEditor& mEditor;
 
   bool mFixed;
+
+  /*
+   * AccessibleCaret visibility preference. Used to avoid hiding caret during
+   * handle drag, caused by dynamic eCompositionCommit events generated by
+   * the keyboard IME for autoSuggest/autoCorrect support.
+   */
+  static bool sCaretsExtendedVisibility;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(IMETextTxn, NS_IMETEXTTXN_IID)
 
 } // namespace dom
 } // namespace mozilla
 
 #endif
--- a/layout/base/AccessibleCaretManager.cpp
+++ b/layout/base/AccessibleCaretManager.cpp
@@ -56,45 +56,82 @@ std::ostream& operator<<(std::ostream& a
   switch (aHint) {
     AC_PROCESS_ENUM_TO_STREAM(UpdateCaretsHint::Default);
     AC_PROCESS_ENUM_TO_STREAM(UpdateCaretsHint::RespectOldAppearance);
   }
   return aStream;
 }
 #undef AC_PROCESS_ENUM_TO_STREAM
 
+/*static*/ bool
+AccessibleCaretManager::sCaretsExtendedVisibility = false;
+
+
 AccessibleCaretManager::AccessibleCaretManager(nsIPresShell* aPresShell)
   : mPresShell(aPresShell)
 {
-  if (mPresShell) {
-    mFirstCaret = MakeUnique<AccessibleCaret>(mPresShell);
-    mSecondCaret = MakeUnique<AccessibleCaret>(mPresShell);
+  if (!mPresShell) {
+    return;
+  }
+
+  mFirstCaret = MakeUnique<AccessibleCaret>(mPresShell);
+  mSecondCaret = MakeUnique<AccessibleCaret>(mPresShell);
 
-    mCaretTimeoutTimer = do_CreateInstance("@mozilla.org/timer;1");
+  mCaretTimeoutTimer = do_CreateInstance("@mozilla.org/timer;1");
+
+  static bool addedPrefs = false;
+  if (!addedPrefs) {
+    Preferences::AddBoolVarCache(&sCaretsExtendedVisibility,
+                                 "layout.accessiblecaret.extendedvisibility");
+    addedPrefs = true;
   }
 }
 
 AccessibleCaretManager::~AccessibleCaretManager()
 {
   CancelCaretTimeoutTimer();
 }
 
 nsresult
 AccessibleCaretManager::OnSelectionChanged(nsIDOMDocument* aDoc,
                                            nsISelection* aSel, int16_t aReason)
 {
+  Selection* selection = GetSelection();
   AC_LOG("%s: aSel: %p, GetSelection(): %p, aReason: %d", __FUNCTION__,
-         aSel, GetSelection(), aReason);
-
-  if (aSel != GetSelection()) {
+         aSel, selection, aReason);
+  if (aSel != selection) {
     return NS_OK;
   }
 
-  // Move the cursor by Javascript.
+  // eSetSelection events from the Fennec widget IME can be generated
+  // by autoSuggest, autoCorrect, and nsCaret position changes.
+  if (aReason & nsISelectionListener::IME_REASON) {
+    if (GetCaretMode() == CaretMode::Cursor) {
+      // Caret position changes need us to open/update,
+      // or hide the AccessibleCaret.
+      FlushLayout();
+      UpdateCarets();
+    } else {
+      // Ignore transient autoSuggest selection styling,
+      // or autoCorrect text updates.
+    }
+    return NS_OK;
+  }
+
+  // Move the cursor by Javascript / or unknown internal.
   if (aReason == nsISelectionListener::NO_REASON) {
+    // Extended visibility won't make hidden carets visible. Visible carets will
+    // be updated or hidden as appropriate.
+    if (sCaretsExtendedVisibility &&
+        (mFirstCaret->IsLogicallyVisible() || mSecondCaret->IsLogicallyVisible())) {
+        FlushLayout();
+        UpdateCarets();
+        return NS_OK;
+    }
+    // Default for NO_REASON is to make hidden.
     HideCarets();
     return NS_OK;
   }
 
   // Move cursor by keyboard.
   if (aReason & nsISelectionListener::KEYPRESS_REASON) {
     HideCarets();
     return NS_OK;
@@ -126,16 +163,28 @@ AccessibleCaretManager::HideCarets()
     mFirstCaret->SetAppearance(Appearance::None);
     mSecondCaret->SetAppearance(Appearance::None);
     DispatchCaretStateChangedEvent(CaretChangedReason::Visibilitychange);
     CancelCaretTimeoutTimer();
   }
 }
 
 void
+AccessibleCaretManager::DoNotShowCarets()
+{
+  if (mFirstCaret->IsLogicallyVisible() || mSecondCaret->IsLogicallyVisible()) {
+    AC_LOG("%s", __FUNCTION__);
+    mFirstCaret->SetAppearance(Appearance::NormalNotShown);
+    mSecondCaret->SetAppearance(Appearance::NormalNotShown);
+    DispatchCaretStateChangedEvent(CaretChangedReason::Visibilitychange);
+    CancelCaretTimeoutTimer();
+  }
+}
+
+void
 AccessibleCaretManager::UpdateCarets(UpdateCaretsHint aHint)
 {
   mLastUpdateCaretMode = GetCaretMode();
 
   switch (mLastUpdateCaretMode) {
   case CaretMode::None:
     HideCarets();
     break;
@@ -454,17 +503,22 @@ void
 AccessibleCaretManager::OnScrollStart()
 {
   AC_LOG("%s", __FUNCTION__);
 
   if (GetCaretMode() == CaretMode::Cursor) {
     mFirstCaretAppearanceOnScrollStart = mFirstCaret->GetAppearance();
   }
 
-  HideCarets();
+  // Hide the carets. (Extended visibility makes them "NormalNotShown").
+  if (sCaretsExtendedVisibility) {
+    DoNotShowCarets();
+  } else {
+    HideCarets();
+  }
 }
 
 void
 AccessibleCaretManager::OnScrollEnd()
 {
   if (mLastUpdateCaretMode != GetCaretMode()) {
     return;
   }
@@ -1086,16 +1140,18 @@ AccessibleCaretManager::DispatchCaretSta
   init.mSelectionEditable = commonAncestorFrame &&
     GetEditingHostForFrame(commonAncestorFrame);
 
   init.mBoundingClientRect = domRect;
   init.mReason = aReason;
   init.mCollapsed = sel->IsCollapsed();
   init.mCaretVisible = mFirstCaret->IsLogicallyVisible() ||
                        mSecondCaret->IsLogicallyVisible();
+  init.mCaretVisuallyVisible = mFirstCaret->IsVisuallyVisible() ||
+                                mSecondCaret->IsVisuallyVisible();
   sel->Stringify(init.mSelectedTextContent);
 
   RefPtr<CaretStateChangedEvent> event =
     CaretStateChangedEvent::Constructor(doc, NS_LITERAL_STRING("mozcaretstatechanged"), init);
 
   event->SetTrusted(true);
   event->GetInternalNSEvent()->mFlags.mOnlyChromeDispatch = true;
 
--- a/layout/base/AccessibleCaretManager.h
+++ b/layout/base/AccessibleCaretManager.h
@@ -125,16 +125,20 @@ protected:
                                   const UpdateCaretsHint& aResult);
 
   // Update carets based on current selection status.
   void UpdateCarets(UpdateCaretsHint aHint = UpdateCaretsHint::Default);
 
   // Force hiding all carets regardless of the current selection status.
   void HideCarets();
 
+  // Force carets to be "present" logically, but not visible. Allows ActionBar
+  // to stay open when carets visibility is supressed during scroll.
+  void DoNotShowCarets();
+
   void UpdateCaretsForCursorMode(UpdateCaretsHint aHint);
   void UpdateCaretsForSelectionMode(UpdateCaretsHint aHint);
 
   // Get the nearest enclosing focusable frame of aFrame.
   // @return focusable frame if there is any; nullptr otherwise.
   nsIFrame* GetFocusableFrame(nsIFrame* aFrame) const;
 
   // Change focus to aFrame if it isn't nullptr. Otherwise, clear the old focus
@@ -234,16 +238,21 @@ protected:
 
   static const int32_t kAutoScrollTimerDelay = 30;
 
   // Clicking on the boundary of input or textarea will move the caret to the
   // front or end of the content. To avoid this, we need to deflate the content
   // boundary by 61 app units, which is 1 pixel + 1 app unit as defined in
   // AppUnit.h.
   static const int32_t kBoundaryAppUnits = 61;
+
+  // AccessibleCaret visibility preference. Used to avoid hiding caret during
+  // (NO_REASON) selection change notifications generated by keyboard IME, and to
+  // maintain a visible ActionBar while carets NotShown during scroll.
+  static bool sCaretsExtendedVisibility;
 };
 
 std::ostream& operator<<(std::ostream& aStream,
                          const AccessibleCaretManager::CaretMode& aCaretMode);
 
 std::ostream& operator<<(std::ostream& aStream,
                          const AccessibleCaretManager::UpdateCaretsHint& aResult);
 
--- a/layout/generic/Selection.h
+++ b/layout/generic/Selection.h
@@ -8,16 +8,17 @@
 #define mozilla_Selection_h__
 
 #include "nsIWeakReference.h"
 
 #include "mozilla/AutoRestore.h"
 #include "mozilla/TextRange.h"
 #include "nsISelection.h"
 #include "nsISelectionController.h"
+#include "nsISelectionListener.h"
 #include "nsISelectionPrivate.h"
 #include "nsRange.h"
 #include "nsThreadUtils.h"
 #include "nsWrapperCache.h"
 
 struct CachedOffsetForFrame;
 class nsAutoScrollTimer;
 class nsIContentIterator;
@@ -59,16 +60,18 @@ public:
   Selection();
   explicit Selection(nsFrameSelection *aList);
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(Selection, nsISelectionPrivate)
   NS_DECL_NSISELECTION
   NS_DECL_NSISELECTIONPRIVATE
 
+  nsresult EndBatchChangesInternal(int16_t aReason = nsISelectionListener::NO_REASON);
+
   nsIDocument* GetParentObject() const;
 
   // utility methods for scrolling the selection into view
   nsPresContext* GetPresContext() const;
   nsIPresShell* GetPresShell() const;
   nsFrameSelection* GetFrameSelection() const { return mFrameSelection; }
   // Returns a rect containing the selection region, and frame that that
   // position is relative to. For SELECTION_ANCHOR_REGION or
@@ -340,17 +343,17 @@ public:
     if (mSelection) {
       mSelection->StartBatchChanges();
     }
   }
 
   ~SelectionBatcher()
   {
     if (mSelection) {
-      mSelection->EndBatchChanges();
+      mSelection->EndBatchChangesInternal();
     }
   }
 };
 
 class MOZ_STACK_CLASS AutoHideSelectionChanges final
 {
 private:
   RefPtr<Selection> mSelection;
--- a/layout/generic/nsFrameSelection.h
+++ b/layout/generic/nsFrameSelection.h
@@ -592,17 +592,18 @@ public:
   nsresult ConstrainFrameAndPointToAnchorSubtree(nsIFrame *aFrame,
                                                  nsPoint& aPoint,
                                                  nsIFrame **aRetFrame,
                                                  nsPoint& aRetPoint);
 
   nsFrameSelection();
 
   void StartBatchChanges();
-  void EndBatchChanges();
+  void EndBatchChanges(int16_t aReason = nsISelectionListener::NO_REASON);
+
   /*unsafe*/
   nsresult DeleteFromDocument();
 
   nsIPresShell *GetShell()const  { return mShell; }
 
   void DisconnectFromPresShell();
   nsresult ClearNormalSelection();
 
--- a/layout/generic/nsSelection.cpp
+++ b/layout/generic/nsSelection.cpp
@@ -50,17 +50,16 @@ static NS_DEFINE_CID(kFrameTraversalCID,
 
 #include "nsPresContext.h"
 #include "nsIPresShell.h"
 #include "nsCaret.h"
 #include "TouchCaret.h"
 #include "SelectionCarets.h"
 
 #include "AccessibleCaretEventHub.h"
-#include "AccessibleCaretManager.h"
 
 #include "mozilla/MouseEvents.h"
 #include "mozilla/TextEvents.h"
 
 #include "nsITimer.h"
 #include "nsFrameManager.h"
 // notifications
 #include "nsIDOMDocument.h"
@@ -2238,21 +2237,24 @@ nsFrameSelection::SelectAll()
 
 void
 nsFrameSelection::StartBatchChanges()
 {
   mBatching++;
 }
 
 void
-nsFrameSelection::EndBatchChanges()
+nsFrameSelection::EndBatchChanges(int16_t aReason)
 {
   mBatching--;
   NS_ASSERTION(mBatching >=0,"Bad mBatching");
-  if (mBatching == 0 && mChangesDuringBatching){
+
+  if (mBatching == 0 && mChangesDuringBatching) {
+    int16_t postReason = PopReason() | aReason;
+    PostReason(postReason);
     mChangesDuringBatching = false;
     NotifySelectionListeners(nsISelectionController::SELECTION_NORMAL);
   }
 }
 
 
 nsresult
 nsFrameSelection::NotifySelectionListeners(SelectionType aType)
@@ -5990,19 +5992,25 @@ Selection::StartBatchChanges()
   return NS_OK;
 }
 
 
 
 NS_IMETHODIMP
 Selection::EndBatchChanges()
 {
-  if (mFrameSelection)
-    mFrameSelection->EndBatchChanges();
-
+  return EndBatchChangesInternal();
+}
+
+nsresult
+Selection::EndBatchChangesInternal(int16_t aReason)
+{
+  if (mFrameSelection) {
+    mFrameSelection->EndBatchChanges(aReason);
+  }
   return NS_OK;
 }
 
 void
 Selection::AddSelectionChangeBlocker()
 {
   mSelectionChangeBlockerCount++;
 }
--- a/widget/TextEvents.h
+++ b/widget/TextEvents.h
@@ -11,16 +11,17 @@
 #include "mozilla/Assertions.h"
 #include "mozilla/BasicEvents.h"
 #include "mozilla/CheckedInt.h"
 #include "mozilla/EventForwards.h" // for KeyNameIndex, temporarily
 #include "mozilla/TextRange.h"
 #include "mozilla/FontRange.h"
 #include "nsCOMPtr.h"
 #include "nsIDOMKeyEvent.h"
+#include "nsISelectionListener.h"
 #include "nsITransferable.h"
 #include "nsRect.h"
 #include "nsStringGlue.h"
 #include "nsTArray.h"
 #include "WritingModes.h"
 
 class nsStringHashKey;
 template<class, class> class nsDataHashtable;
@@ -657,16 +658,17 @@ public:
                        nsIWidget* aWidget)
     : WidgetGUIEvent(aIsTrusted, aMessage, aWidget, eSelectionEventClass)
     , mOffset(0)
     , mLength(0)
     , mReversed(false)
     , mExpandToClusterBoundary(true)
     , mSucceeded(false)
     , mUseNativeLineBreak(true)
+    , mReason(nsISelectionListener::NO_REASON)
   {
   }
 
   virtual WidgetEvent* Duplicate() const override
   {
     // This event isn't an internal event of any DOM event.
     NS_ASSERTION(!IsAllowedToDispatchDOMEvent(),
       "WidgetSelectionEvent needs to support Duplicate()");
@@ -681,16 +683,19 @@ public:
   // Selection "anchor" should be in front
   bool mReversed;
   // Cluster-based or character-based
   bool mExpandToClusterBoundary;
   // true if setting selection succeeded.
   bool mSucceeded;
   // true if native line breaks are used for mOffset and mLength
   bool mUseNativeLineBreak;
+  // Fennec provides eSetSelection reason codes for downstream
+  // use in AccessibleCaret visibility logic.
+  int16_t mReason;
 };
 
 /******************************************************************************
  * mozilla::InternalEditorInputEvent
  ******************************************************************************/
 
 class InternalEditorInputEvent : public InternalUIEvent
 {
--- a/widget/android/nsWindow.cpp
+++ b/widget/android/nsWindow.cpp
@@ -2312,16 +2312,17 @@ nsWindow::Natives::OnImeReplaceText(int3
         window.RemoveIMEComposition();
 
         {
             WidgetSelectionEvent event(true, eSetSelection, &window);
             window.InitEvent(event, nullptr);
             event.mOffset = uint32_t(aStart);
             event.mLength = uint32_t(aEnd - aStart);
             event.mExpandToClusterBoundary = false;
+            event.mReason = nsISelectionListener::IME_REASON;
             window.DispatchEvent(&event);
         }
 
         if (!mIMEKeyEvents.IsEmpty()) {
             nsEventStatus status;
             for (uint32_t i = 0; i < mIMEKeyEvents.Length(); i++) {
                 const auto event = static_cast<WidgetGUIEvent*>(
                         mIMEKeyEvents[i].get());
@@ -2440,16 +2441,17 @@ nsWindow::Natives::OnImeUpdateCompositio
 
         WidgetSelectionEvent selEvent(true, eSetSelection, &window);
         window.InitEvent(selEvent, nullptr);
 
         selEvent.mOffset = std::min(aStart, aEnd);
         selEvent.mLength = std::max(aStart, aEnd) - selEvent.mOffset;
         selEvent.mReversed = aStart > aEnd;
         selEvent.mExpandToClusterBoundary = false;
+        selEvent.mReason = nsISelectionListener::IME_REASON;
 
         window.DispatchEvent(&selEvent);
         return;
     }
 
     /*
         Update the composition from aStart to aEnd using
           information from added ranges. This is only used for
@@ -2478,16 +2480,17 @@ nsWindow::Natives::OnImeUpdateCompositio
         window.RemoveIMEComposition();
 
         {
             WidgetSelectionEvent event(true, eSetSelection, &window);
             window.InitEvent(event, nullptr);
             event.mOffset = uint32_t(aStart);
             event.mLength = uint32_t(aEnd - aStart);
             event.mExpandToClusterBoundary = false;
+            event.mReason = nsISelectionListener::IME_REASON;
             window.DispatchEvent(&event);
         }
 
         {
             WidgetQueryContentEvent queryEvent(true, eQuerySelectedText,
                                                &window);
             window.InitEvent(queryEvent, nullptr);
             window.DispatchEvent(&queryEvent);