Bug 1318312 part.2 Mark Selection as "called by JS" when every Selection API which may cause changing selection is called by JS r?smaug draft
authorMasayuki Nakano <masayuki@d-toybox.com>
Fri, 10 Mar 2017 16:55:12 +0900
changeset 498030 9aa2777f807bda8a8d85f723123d9014d7a787c0
parent 498029 607bf31da6e8f5b9056668be028dcce958432d33
child 498031 1cac55df84971a6b33301d577b3e3067e7836805
push id49084
push usermasayuki@d-toybox.com
push dateTue, 14 Mar 2017 05:28:54 +0000
reviewerssmaug
bugs1318312
milestone55.0a1
Bug 1318312 part.2 Mark Selection as "called by JS" when every Selection API which may cause changing selection is called by JS r?smaug Selection needs to be able to distinguish if every selection change is caused by JS (i.e., via Selection API) or the others. This patch maps some methods of Range and Selection to *JS(). Each of them marks its instance as "used by JS" and calls corresponding method. With this change, Selection::NotifySelectionListeners() can move focus only when it's caused by Selection API. MozReview-Commit-ID: 1GoLHiIJ10Y
dom/base/nsRange.cpp
dom/base/nsRange.h
dom/webidl/Range.webidl
dom/webidl/Selection.webidl
layout/generic/Selection.h
layout/generic/nsSelection.cpp
--- a/dom/base/nsRange.cpp
+++ b/dom/base/nsRange.cpp
@@ -250,16 +250,17 @@ nsRange::nsRange(nsINode* aNode)
   , mStartOffset(0)
   , mEndOffset(0)
   , mIsPositioned(false)
   , mMaySpanAnonymousSubtrees(false)
   , mIsGenerated(false)
   , mStartOffsetWasIncremented(false)
   , mEndOffsetWasIncremented(false)
   , mEnableGravitationOnElementRemoval(true)
+  , mCalledByJS(false)
 #ifdef DEBUG
   , mAssertNextInsertOrAppendIndex(-1)
   , mAssertNextInsertOrAppendNode(nullptr)
 #endif
 {
   MOZ_ASSERT(aNode, "range isn't in a document!");
   mOwner = aNode->OwnerDoc();
 }
@@ -962,17 +963,17 @@ nsRange::DoSetRange(nsINode* aStartN, in
 
   // This needs to be the last thing this function does, other than notifying
   // selection listeners. See comment in ParentChainChanged.
   mRoot = aRoot;
 
   // Notify any selection listeners. This has to occur last because otherwise the world
   // could be observed by a selection listener while the range was in an invalid state.
   if (mSelection) {
-    mSelection->NotifySelectionListeners();
+    mSelection->NotifySelectionListeners(mCalledByJS);
   }
 }
 
 static int32_t
 IndexOf(nsINode* aChild)
 {
   nsINode* parent = aChild->GetParentNode();
 
@@ -1181,16 +1182,23 @@ nsRange::IsValidBoundary(nsINode* aNode)
   NS_ASSERTION(!root->IsNodeOfType(nsINode::eDOCUMENT),
                "GetUncomposedDoc should have returned a doc");
 
   // We allow this because of backward compatibility.
   return root;
 }
 
 void
+nsRange::SetStartJS(nsINode& aNode, uint32_t aOffset, ErrorResult& aErr)
+{
+  AutoCalledByJSSetter markAsCalledByJS(*this);
+  SetStart(aNode, aOffset, aErr);
+}
+
+void
 nsRange::SetStart(nsINode& aNode, uint32_t aOffset, ErrorResult& aRv)
 {
  if (!nsContentUtils::LegacyIsCallerNativeCode() &&
      !nsContentUtils::CanCallerAccess(&aNode)) {
     aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
     return;
   }
 
@@ -1234,16 +1242,23 @@ nsRange::SetStart(nsINode* aParent, int3
   }
 
   DoSetRange(aParent, aOffset, mEndParent, mEndOffset, mRoot);
 
   return NS_OK;
 }
 
 void
+nsRange::SetStartBeforeJS(nsINode& aNode, ErrorResult& aErr)
+{
+  AutoCalledByJSSetter markAsCalledByJS(*this);
+  SetStartBefore(aNode, aErr);
+}
+
+void
 nsRange::SetStartBefore(nsINode& aNode, ErrorResult& aRv)
 {
   if (!nsContentUtils::LegacyIsCallerNativeCode() &&
       !nsContentUtils::CanCallerAccess(&aNode)) {
     aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
     return;
   }
 
@@ -1260,16 +1275,23 @@ nsRange::SetStartBefore(nsIDOMNode* aSib
   }
 
   ErrorResult rv;
   SetStartBefore(*sibling, rv);
   return rv.StealNSResult();
 }
 
 void
+nsRange::SetStartAfterJS(nsINode& aNode, ErrorResult& aErr)
+{
+  AutoCalledByJSSetter markAsCalledByJS(*this);
+  SetStartAfter(aNode, aErr);
+}
+
+void
 nsRange::SetStartAfter(nsINode& aNode, ErrorResult& aRv)
 {
   if (!nsContentUtils::LegacyIsCallerNativeCode() &&
       !nsContentUtils::CanCallerAccess(&aNode)) {
     aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
     return;
   }
 
@@ -1286,16 +1308,23 @@ nsRange::SetStartAfter(nsIDOMNode* aSibl
   }
 
   ErrorResult rv;
   SetStartAfter(*sibling, rv);
   return rv.StealNSResult();
 }
 
 void
+nsRange::SetEndJS(nsINode& aNode, uint32_t aOffset, ErrorResult& aErr)
+{
+  AutoCalledByJSSetter markAsCalledByJS(*this);
+  SetEnd(aNode, aOffset, aErr);
+}
+
+void
 nsRange::SetEnd(nsINode& aNode, uint32_t aOffset, ErrorResult& aRv)
 {
  if (!nsContentUtils::LegacyIsCallerNativeCode() &&
      !nsContentUtils::CanCallerAccess(&aNode)) {
     aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
     return;
   }
   AutoInvalidateSelection atEndOfBlock(this);
@@ -1338,16 +1367,23 @@ nsRange::SetEnd(nsINode* aParent, int32_
   }
 
   DoSetRange(mStartParent, mStartOffset, aParent, aOffset, mRoot);
 
   return NS_OK;
 }
 
 void
+nsRange::SetEndBeforeJS(nsINode& aNode, ErrorResult& aErr)
+{
+  AutoCalledByJSSetter markAsCalledByJS(*this);
+  SetEndBefore(aNode, aErr);
+}
+
+void
 nsRange::SetEndBefore(nsINode& aNode, ErrorResult& aRv)
 {
   if (!nsContentUtils::LegacyIsCallerNativeCode() &&
       !nsContentUtils::CanCallerAccess(&aNode)) {
     aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
     return;
   }
 
@@ -1364,16 +1400,23 @@ nsRange::SetEndBefore(nsIDOMNode* aSibli
   }
 
   ErrorResult rv;
   SetEndBefore(*sibling, rv);
   return rv.StealNSResult();
 }
 
 void
+nsRange::SetEndAfterJS(nsINode& aNode, ErrorResult& aErr)
+{
+  AutoCalledByJSSetter markAsCalledByJS(*this);
+  SetEndAfter(aNode, aErr);
+}
+
+void
 nsRange::SetEndAfter(nsINode& aNode, ErrorResult& aRv)
 {
   if (!nsContentUtils::LegacyIsCallerNativeCode() &&
       !nsContentUtils::CanCallerAccess(&aNode)) {
     aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
     return;
   }
 
@@ -1404,28 +1447,42 @@ nsRange::Collapse(bool aToStart)
   if (aToStart)
     DoSetRange(mStartParent, mStartOffset, mStartParent, mStartOffset, mRoot);
   else
     DoSetRange(mEndParent, mEndOffset, mEndParent, mEndOffset, mRoot);
 
   return NS_OK;
 }
 
+void
+nsRange::CollapseJS(bool aToStart)
+{
+  AutoCalledByJSSetter markAsCalledByJS(*this);
+  Unused << Collapse(aToStart);
+}
+
 NS_IMETHODIMP
 nsRange::SelectNode(nsIDOMNode* aN)
 {
   nsCOMPtr<nsINode> node = do_QueryInterface(aN);
   NS_ENSURE_TRUE(node, NS_ERROR_DOM_INVALID_NODE_TYPE_ERR);
 
   ErrorResult rv;
   SelectNode(*node, rv);
   return rv.StealNSResult();
 }
 
 void
+nsRange::SelectNodeJS(nsINode& aNode, ErrorResult& aErr)
+{
+  AutoCalledByJSSetter markAsCalledByJS(*this);
+  SelectNode(aNode, aErr);
+}
+
+void
 nsRange::SelectNode(nsINode& aNode, ErrorResult& aRv)
 {
   if (!nsContentUtils::LegacyIsCallerNativeCode() &&
       !nsContentUtils::CanCallerAccess(&aNode)) {
     aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
     return;
   }
 
@@ -1453,16 +1510,23 @@ nsRange::SelectNodeContents(nsIDOMNode* 
   NS_ENSURE_TRUE(node, NS_ERROR_DOM_INVALID_NODE_TYPE_ERR);
 
   ErrorResult rv;
   SelectNodeContents(*node, rv);
   return rv.StealNSResult();
 }
 
 void
+nsRange::SelectNodeContentsJS(nsINode& aNode, ErrorResult& aErr)
+{
+  AutoCalledByJSSetter markAsCalledByJS(*this);
+  SelectNodeContents(aNode, aErr);
+}
+
+void
 nsRange::SelectNodeContents(nsINode& aNode, ErrorResult& aRv)
 {
   if (!nsContentUtils::LegacyIsCallerNativeCode() &&
       !nsContentUtils::CanCallerAccess(&aNode)) {
     aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
     return;
   }
 
--- a/dom/base/nsRange.h
+++ b/dom/base/nsRange.h
@@ -16,16 +16,17 @@
 #include "nsINode.h"
 #include "nsIDocument.h"
 #include "nsIDOMNode.h"
 #include "nsLayoutUtils.h"
 #include "prmon.h"
 #include "nsStubMutationObserver.h"
 #include "nsWrapperCache.h"
 #include "mozilla/Attributes.h"
+#include "mozilla/GuardObjects.h"
 
 namespace mozilla {
 class ErrorResult;
 namespace dom {
 struct ClientRectsAndTexts;
 class DocumentFragment;
 class DOMRect;
 class DOMRectList;
@@ -195,32 +196,49 @@ public:
   nsINode* GetCommonAncestorContainer(ErrorResult& aRv) const;
   nsINode* GetStartContainer(ErrorResult& aRv) const;
   uint32_t GetStartOffset(ErrorResult& aRv) const;
   nsINode* GetEndContainer(ErrorResult& aRv) const;
   uint32_t GetEndOffset(ErrorResult& aRv) const;
   void InsertNode(nsINode& aNode, ErrorResult& aErr);
   bool IntersectsNode(nsINode& aNode, ErrorResult& aRv);
   bool IsPointInRange(nsINode& aParent, uint32_t aOffset, ErrorResult& aErr);
+
+  // *JS() methods are mapped to Range.*() of DOM.
+  // They may move focus only when the range represents normal selection.
+  // These methods shouldn't be used from internal.
+  void CollapseJS(bool aToStart);
+  void SelectNodeJS(nsINode& aNode, ErrorResult& aErr);
+  void SelectNodeContentsJS(nsINode& aNode, ErrorResult& aErr);
+  void SetEndJS(nsINode& aNode, uint32_t aOffset, ErrorResult& aErr);
+  void SetEndAfterJS(nsINode& aNode, ErrorResult& aErr);
+  void SetEndBeforeJS(nsINode& aNode, ErrorResult& aErr);
+  void SetStartJS(nsINode& aNode, uint32_t aOffset, ErrorResult& aErr);
+  void SetStartAfterJS(nsINode& aNode, ErrorResult& aErr);
+  void SetStartBeforeJS(nsINode& aNode, ErrorResult& aErr);
+
+  void SurroundContents(nsINode& aNode, ErrorResult& aErr);
+  already_AddRefed<DOMRect> GetBoundingClientRect(bool aClampToEdge = true,
+                                                  bool aFlushLayout = true);
+  already_AddRefed<DOMRectList> GetClientRects(bool aClampToEdge = true,
+                                               bool aFlushLayout = true);
+  void GetClientRectsAndTexts(
+    mozilla::dom::ClientRectsAndTexts& aResult,
+    ErrorResult& aErr);
+
+  // Following methods should be used for internal use instead of *JS().
   void SelectNode(nsINode& aNode, ErrorResult& aErr);
   void SelectNodeContents(nsINode& aNode, ErrorResult& aErr);
   void SetEnd(nsINode& aNode, uint32_t aOffset, ErrorResult& aErr);
   void SetEndAfter(nsINode& aNode, ErrorResult& aErr);
   void SetEndBefore(nsINode& aNode, ErrorResult& aErr);
   void SetStart(nsINode& aNode, uint32_t aOffset, ErrorResult& aErr);
   void SetStartAfter(nsINode& aNode, ErrorResult& aErr);
   void SetStartBefore(nsINode& aNode, ErrorResult& aErr);
-  void SurroundContents(nsINode& aNode, ErrorResult& aErr);
-  already_AddRefed<DOMRect> GetBoundingClientRect(bool aClampToEdge = true,
-                                                  bool aFlushLayout = true);
-  already_AddRefed<DOMRectList> GetClientRects(bool aClampToEdge = true,
-                                               bool aFlushLayout = true);
-  void GetClientRectsAndTexts(
-    mozilla::dom::ClientRectsAndTexts& aResult,
-    ErrorResult& aErr);
+
   static void GetInnerTextNoFlush(mozilla::dom::DOMString& aValue,
                                   mozilla::ErrorResult& aError,
                                   nsIContent* aStartParent,
                                   uint32_t aStartOffset,
                                   nsIContent* aEndParent,
                                   uint32_t aEndOffset);
 
   nsINode* GetParentObject() const { return mOwner; }
@@ -318,16 +336,41 @@ protected:
   // Helper to IsNodeSelected.
   static bool IsNodeInSortedRanges(nsINode* aNode,
                                    uint32_t aStartOffset,
                                    uint32_t aEndOffset,
                                    const nsTArray<const nsRange*>& aRanges,
                                    size_t aRangeStart,
                                    size_t aRangeEnd);
 
+  // Assume that this is guaranteed that this is held by the caller when
+  // this is used.  (Note that we cannot use AutoRestore for mCalledByJS
+  // due to a bit field.)
+  class MOZ_RAII AutoCalledByJSSetter final
+  {
+  private:
+    nsRange& mRange;
+    bool mOldValue;
+    MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
+
+  public:
+    explicit AutoCalledByJSSetter(nsRange& aRange
+                                  MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
+      : mRange(aRange)
+      , mOldValue(aRange.mCalledByJS)
+    {
+      MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+      mRange.mCalledByJS = true;
+    }
+    ~AutoCalledByJSSetter()
+    {
+      mRange.mCalledByJS = mOldValue;
+    }
+  };
+
   struct MOZ_STACK_CLASS AutoInvalidateSelection
   {
     explicit AutoInvalidateSelection(nsRange* aRange) : mRange(aRange)
     {
 #ifdef DEBUG
       mWasInSelection = mRange->IsInSelection();
 #endif
       if (!mRange->IsInSelection() || mIsNested) {
@@ -354,16 +397,17 @@ protected:
   int32_t mEndOffset;
 
   bool mIsPositioned : 1;
   bool mMaySpanAnonymousSubtrees : 1;
   bool mIsGenerated : 1;
   bool mStartOffsetWasIncremented : 1;
   bool mEndOffsetWasIncremented : 1;
   bool mEnableGravitationOnElementRemoval : 1;
+  bool mCalledByJS : 1;
 #ifdef DEBUG
   int32_t  mAssertNextInsertOrAppendIndex;
   nsINode* mAssertNextInsertOrAppendNode;
 #endif
 };
 
 inline nsISupports*
 ToCanonicalSupports(nsRange* aRange)
--- a/dom/webidl/Range.webidl
+++ b/dom/webidl/Range.webidl
@@ -21,32 +21,33 @@ interface Range {
   [Throws]
   readonly attribute Node endContainer;
   [Throws]
   readonly attribute unsigned long endOffset;
   readonly attribute boolean collapsed;
   [Throws]
   readonly attribute Node commonAncestorContainer;
 
-  [Throws]
+  [Throws, BinaryName="setStartJS"]
   void setStart(Node refNode, unsigned long offset);
-  [Throws]
+  [Throws, BinaryName="setEndJS"]
   void setEnd(Node refNode, unsigned long offset);
-  [Throws]
+  [Throws, BinaryName="setStartBeforeJS"]
   void setStartBefore(Node refNode);
-  [Throws]
+  [Throws, BinaryName="setStartAfterJS"]
   void setStartAfter(Node refNode);
-  [Throws]
+  [Throws, BinaryName="setEndBeforeJS"]
   void setEndBefore(Node refNode);
-  [Throws]
+  [Throws, BinaryName="setEndAfterJS"]
   void setEndAfter(Node refNode);
+  [BinaryName="collapseJS"]
   void collapse(optional boolean toStart = false);
-  [Throws]
+  [Throws, BinaryName="selectNodeJS"]
   void selectNode(Node refNode);
-  [Throws]
+  [Throws, BinaryName="selectNodeContentsJS"]
   void selectNodeContents(Node refNode);
 
   const unsigned short START_TO_START = 0;
   const unsigned short START_TO_END = 1;
   const unsigned short END_TO_END = 2;
   const unsigned short END_TO_START = 3;
   [Throws]
   short compareBoundaryPoints(unsigned short how, Range sourceRange);
--- a/dom/webidl/Selection.webidl
+++ b/dom/webidl/Selection.webidl
@@ -12,45 +12,45 @@
 
 interface Selection {
   readonly attribute Node? anchorNode;
   readonly attribute unsigned long anchorOffset;
   readonly attribute Node? focusNode;
   readonly attribute unsigned long focusOffset;
 
   readonly attribute boolean isCollapsed;
-  [Throws]
+  [Throws, BinaryName="collapseJS"]
   void               collapse(Node node, unsigned long offset);
-  [Throws]
+  [Throws, BinaryName="collapseToStartJS"]
   void               collapseToStart();
-  [Throws]
+  [Throws, BinaryName="collapseToEndJS"]
   void               collapseToEnd();
 
-  [Throws]
+  [Throws, BinaryName="extendJS"]
   void               extend(Node node, unsigned long offset);
 
-  [Throws]
+  [Throws, BinaryName="selectAllChildrenJS"]
   void               selectAllChildren(Node node);
   [Throws]
   void               deleteFromDocument();
 
   readonly attribute unsigned long rangeCount;
   [Throws]
   Range              getRangeAt(unsigned long index);
-  [Throws]
+  [Throws, BinaryName="addRangeJS"]
   void               addRange(Range range);
   [Throws]
   void               removeRange(Range range);
   [Throws]
   void               removeAllRanges();
 
   [Throws]
   boolean            containsNode(Node node, boolean allowPartialContainment);
 
-  [Throws]
+  [Throws, BinaryName="setBaseAndExtentJS"]
   void               setBaseAndExtent(Node anchorNode,
                                       unsigned long anchorOffset,
                                       Node focusNode,
                                       unsigned long focusOffset);
 
   stringifier;
 };
 
--- a/layout/generic/Selection.h
+++ b/layout/generic/Selection.h
@@ -162,31 +162,38 @@ public:
 
   // WebIDL methods
   nsINode*     GetAnchorNode();
   uint32_t     AnchorOffset();
   nsINode*     GetFocusNode();
   uint32_t     FocusOffset();
 
   bool IsCollapsed() const;
-  void Collapse(nsINode& aNode, uint32_t aOffset, mozilla::ErrorResult& aRv);
-  void CollapseToStart(mozilla::ErrorResult& aRv);
-  void CollapseToEnd(mozilla::ErrorResult& aRv);
 
-  void Extend(nsINode& aNode, uint32_t aOffset, mozilla::ErrorResult& aRv);
+  // *JS() methods are mapped to Selection.*().
+  // They may move focus only when the range represents normal selection.
+  // These methods shouldn't be used by non-JS callers.
+  void CollapseJS(nsINode& aNode, uint32_t aOffset,
+                  mozilla::ErrorResult& aRv);
+  void CollapseToStartJS(mozilla::ErrorResult& aRv);
+  void CollapseToEndJS(mozilla::ErrorResult& aRv);
 
-  void SelectAllChildren(nsINode& aNode, mozilla::ErrorResult& aRv);
+  void ExtendJS(nsINode& aNode, uint32_t aOffset,
+                mozilla::ErrorResult& aRv);
+
+  void SelectAllChildrenJS(nsINode& aNode, mozilla::ErrorResult& aRv);
+
   void DeleteFromDocument(mozilla::ErrorResult& aRv);
 
   uint32_t RangeCount() const
   {
     return mRanges.Length();
   }
   nsRange* GetRangeAt(uint32_t aIndex, mozilla::ErrorResult& aRv);
-  void AddRange(nsRange& aRange, mozilla::ErrorResult& aRv);
+  void AddRangeJS(nsRange& aRange, mozilla::ErrorResult& aRv);
   void RemoveRange(nsRange& aRange, mozilla::ErrorResult& aRv);
   void RemoveAllRanges(mozilla::ErrorResult& aRv);
 
   void Stringify(nsAString& aResult);
 
   bool ContainsNode(nsINode& aNode, bool aPartlyContained, mozilla::ErrorResult& aRv);
 
   /**
@@ -196,19 +203,19 @@ public:
    * in any one of them.
    * @param aPoint The point to check, relative to the root frame.
    */
   bool ContainsPoint(const nsPoint& aPoint);
 
   void Modify(const nsAString& aAlter, const nsAString& aDirection,
               const nsAString& aGranularity, mozilla::ErrorResult& aRv);
 
-  void SetBaseAndExtent(nsINode& aAnchorNode, uint32_t aAnchorOffset,
-                        nsINode& aFocusNode, uint32_t aFocusOffset,
-                        mozilla::ErrorResult& aRv);
+  void SetBaseAndExtentJS(nsINode& aAnchorNode, uint32_t aAnchorOffset,
+                          nsINode& aFocusNode, uint32_t aFocusOffset,
+                          mozilla::ErrorResult& aRv);
 
   bool GetInterlinePosition(mozilla::ErrorResult& aRv);
   void SetInterlinePosition(bool aValue, mozilla::ErrorResult& aRv);
 
   Nullable<int16_t> GetCaretBidiLevel(mozilla::ErrorResult& aRv) const;
   void SetCaretBidiLevel(const Nullable<int16_t>& aCaretBidiLevel, mozilla::ErrorResult& aRv);
 
   void ToStringWithFormat(const nsAString& aFormatType,
@@ -232,16 +239,27 @@ public:
                             bool aAllowAdjacent,
                             nsTArray<RefPtr<nsRange>>& aReturn,
                             mozilla::ErrorResult& aRv);
 
   void ScrollIntoView(int16_t aRegion, bool aIsSynchronous,
                       int16_t aVPercent, int16_t aHPercent,
                       mozilla::ErrorResult& aRv);
 
+  // Non-JS callers should use the following methods.
+  void Collapse(nsINode& aNode, uint32_t aOffset, mozilla::ErrorResult& aRv);
+  void CollapseToStart(mozilla::ErrorResult& aRv);
+  void CollapseToEnd(mozilla::ErrorResult& aRv);
+  void Extend(nsINode& aNode, uint32_t aOffset, mozilla::ErrorResult& aRv);
+  void AddRange(nsRange& aRange, mozilla::ErrorResult& aRv);
+  void SelectAllChildren(nsINode& aNode, mozilla::ErrorResult& aRv);
+  void SetBaseAndExtent(nsINode& aAnchorNode, uint32_t aAnchorOffset,
+                        nsINode& aFocusNode, uint32_t aFocusOffset,
+                        mozilla::ErrorResult& aRv);
+
   void AddSelectionChangeBlocker();
   void RemoveSelectionChangeBlocker();
   bool IsBlockingSelectionChangeEvents() const;
 private:
   friend class ::nsAutoScrollTimer;
 
   // Note: DoAutoScroll might destroy arbitrary frames etc.
   nsresult DoAutoScroll(nsIFrame *aFrame, nsPoint& aPoint);
@@ -254,17 +272,18 @@ private:
 
 public:
   SelectionType GetType() const { return mSelectionType; }
   void SetType(SelectionType aSelectionType)
   {
     mSelectionType = aSelectionType;
   }
 
-  nsresult     NotifySelectionListeners();
+  nsresult NotifySelectionListeners(bool aCalledByJS);
+  nsresult NotifySelectionListeners();
 
   friend struct AutoUserInitiated;
   struct MOZ_RAII AutoUserInitiated
   {
     explicit AutoUserInitiated(Selection* aSelection
                                MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
       : mSavedValue(aSelection->mUserInitiated)
     {
@@ -360,16 +379,22 @@ private:
   SelectionType mSelectionType;
   /**
    * True if the current selection operation was initiated by user action.
    * It determines whether we exclude -moz-user-select:none nodes or not,
    * as well as whether selectstart events will be fired.
    */
   bool mUserInitiated;
 
+  /**
+   * When the selection change is caused by a call of Selection API,
+   * mCalledByJS is true.  Otherwise, false.
+   */
+  bool mCalledByJS;
+
   // Non-zero if we don't want any changes we make to the selection to be
   // visible to content. If non-zero, content won't be notified about changes.
   uint32_t mSelectionChangeBlockerCount;
 };
 
 // Stack-class to turn on/off selection batching.
 class MOZ_STACK_CLASS SelectionBatcher final
 {
--- a/layout/generic/nsSelection.cpp
+++ b/layout/generic/nsSelection.cpp
@@ -6,16 +6,17 @@
 
 /*
  * Implementation of selection: nsISelection,nsISelectionPrivate and nsFrameSelection
  */
 
 #include "mozilla/dom/Selection.h"
 
 #include "mozilla/Attributes.h"
+#include "mozilla/AutoRestore.h"
 #include "mozilla/EventStates.h"
 
 #include "nsCOMPtr.h"
 #include "nsString.h"
 #include "nsFrameSelection.h"
 #include "nsISelectionListener.h"
 #include "nsContentCID.h"
 #include "nsDeviceContext.h"
@@ -3474,26 +3475,28 @@ nsFrameSelection::DisconnectFromPresShel
 
 // note: this can return a nil anchor node
 
 Selection::Selection()
   : mCachedOffsetForFrame(nullptr)
   , mDirection(eDirNext)
   , mSelectionType(SelectionType::eNormal)
   , mUserInitiated(false)
+  , mCalledByJS(false)
   , mSelectionChangeBlockerCount(0)
 {
 }
 
 Selection::Selection(nsFrameSelection* aList)
   : mFrameSelection(aList)
   , mCachedOffsetForFrame(nullptr)
   , mDirection(eDirNext)
   , mSelectionType(SelectionType::eNormal)
   , mUserInitiated(false)
+  , mCalledByJS(false)
   , mSelectionChangeBlockerCount(0)
 {
 }
 
 Selection::~Selection()
 {
   setAnchorFocusRange(-1);
 
@@ -4957,16 +4960,24 @@ Selection::AddRange(nsIDOMRange* aDOMRan
   }
   nsRange* range = static_cast<nsRange*>(aDOMRange);
   ErrorResult result;
   AddRange(*range, result);
   return result.StealNSResult();
 }
 
 void
+Selection::AddRangeJS(nsRange& aRange, ErrorResult& aRv)
+{
+  AutoRestore<bool> calledFromJSRestorer(mCalledByJS);
+  mCalledByJS = true;
+  AddRange(aRange, aRv);
+}
+
+void
 Selection::AddRange(nsRange& aRange, ErrorResult& aRv)
 {
   return AddRangeInternal(aRange, GetParentObject(), aRv);
 }
 
 void
 Selection::AddRangeInternal(nsRange& aRange, nsIDocument* aDocument,
                             ErrorResult& aRv)
@@ -5128,16 +5139,24 @@ Selection::Collapse(nsIDOMNode* aParentN
 }
 
 NS_IMETHODIMP
 Selection::CollapseNative(nsINode* aParentNode, int32_t aOffset)
 {
   return Collapse(aParentNode, aOffset);
 }
 
+void
+Selection::CollapseJS(nsINode& aNode, uint32_t aOffset, ErrorResult& aRv)
+{
+  AutoRestore<bool> calledFromJSRestorer(mCalledByJS);
+  mCalledByJS = true;
+  Collapse(aNode, aOffset, aRv);
+}
+
 nsresult
 Selection::Collapse(nsINode* aParentNode, int32_t aOffset)
 {
   if (!aParentNode)
     return NS_ERROR_INVALID_ARG;
 
   ErrorResult result;
   Collapse(*aParentNode, static_cast<uint32_t>(aOffset), result);
@@ -5233,16 +5252,24 @@ NS_IMETHODIMP
 Selection::CollapseToStart()
 {
   ErrorResult result;
   CollapseToStart(result);
   return result.StealNSResult();
 }
 
 void
+Selection::CollapseToStartJS(ErrorResult& aRv)
+{
+  AutoRestore<bool> calledFromJSRestorer(mCalledByJS);
+  mCalledByJS = true;
+  CollapseToStart(aRv);
+}
+
+void
 Selection::CollapseToStart(ErrorResult& aRv)
 {
   int32_t cnt;
   nsresult rv = GetRangeCount(&cnt);
   if (NS_FAILED(rv) || cnt <= 0) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
@@ -5274,16 +5301,24 @@ NS_IMETHODIMP
 Selection::CollapseToEnd()
 {
   ErrorResult result;
   CollapseToEnd(result);
   return result.StealNSResult();
 }
 
 void
+Selection::CollapseToEndJS(ErrorResult& aRv)
+{
+  AutoRestore<bool> calledFromJSRestorer(mCalledByJS);
+  mCalledByJS = true;
+  CollapseToEnd(aRv);
+}
+
+void
 Selection::CollapseToEnd(ErrorResult& aRv)
 {
   int32_t cnt;
   nsresult rv = GetRangeCount(&cnt);
   if (NS_FAILED(rv) || cnt <= 0) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
@@ -5475,16 +5510,24 @@ Selection::Extend(nsIDOMNode* aParentNod
 }
 
 NS_IMETHODIMP
 Selection::ExtendNative(nsINode* aParentNode, int32_t aOffset)
 {
   return Extend(aParentNode, aOffset);
 }
 
+void
+Selection::ExtendJS(nsINode& aNode, uint32_t aOffset, ErrorResult& aRv)
+{
+  AutoRestore<bool> calledFromJSRestorer(mCalledByJS);
+  mCalledByJS = true;
+  Extend(aNode, aOffset, aRv);
+}
+
 nsresult
 Selection::Extend(nsINode* aParentNode, int32_t aOffset)
 {
   if (!aParentNode)
     return NS_ERROR_INVALID_ARG;
 
   ErrorResult result;
   Extend(*aParentNode, static_cast<uint32_t>(aOffset), result);
@@ -5787,16 +5830,24 @@ Selection::SelectAllChildren(nsIDOMNode*
   ErrorResult result;
   nsCOMPtr<nsINode> node = do_QueryInterface(aParentNode);
   NS_ENSURE_TRUE(node, NS_ERROR_INVALID_ARG);
   SelectAllChildren(*node, result);
   return result.StealNSResult();
 }
 
 void
+Selection::SelectAllChildrenJS(nsINode& aNode, ErrorResult& aRv)
+{
+  AutoRestore<bool> calledFromJSRestorer(mCalledByJS);
+  mCalledByJS = true;
+  SelectAllChildren(aNode, aRv);
+}
+
+void
 Selection::SelectAllChildren(nsINode& aNode, ErrorResult& aRv)
 {
   if (mFrameSelection) {
     mFrameSelection->PostReason(nsISelectionListener::SELECTALL_REASON);
   }
   SelectionBatcher batch(this);
 
   Collapse(aNode, 0, aRv);
@@ -6239,16 +6290,24 @@ Selection::RemoveSelectionListener(nsISe
 {
   bool result = mSelectionListeners.RemoveObject(aListenerToRemove); // Releases
   if (!result) {
     aRv.Throw(NS_ERROR_FAILURE);
   }
 }
 
 nsresult
+Selection::NotifySelectionListeners(bool aCalledByJS)
+{
+  AutoRestore<bool> calledFromJSRestorer(mCalledByJS);
+  mCalledByJS = aCalledByJS;
+  return NotifySelectionListeners();
+}
+
+nsresult
 Selection::NotifySelectionListeners()
 {
   if (!mFrameSelection)
     return NS_OK;//nothing to do
  
   if (mFrameSelection->GetBatching()) {
     mFrameSelection->SetDirty();
     return NS_OK;
@@ -6449,16 +6508,29 @@ Selection::Modify(const nsAString& aAlte
       do_QueryInterface(mFrameSelection->GetShell());
     if (!shell)
       return;
     shell->CompleteMove(forward, extend);
   }
 }
 
 void
+Selection::SetBaseAndExtentJS(nsINode& aAnchorNode,
+                              uint32_t aAnchorOffset,
+                              nsINode& aFocusNode,
+                              uint32_t aFocusOffset,
+                              ErrorResult& aRv)
+{
+  AutoRestore<bool> calledFromJSRestorer(mCalledByJS);
+  mCalledByJS = true;
+  SetBaseAndExtent(aAnchorNode, aAnchorOffset,
+                   aFocusNode, aFocusOffset, aRv);
+}
+
+void
 Selection::SetBaseAndExtent(nsINode& aAnchorNode, uint32_t aAnchorOffset,
                             nsINode& aFocusNode, uint32_t aFocusOffset,
                             ErrorResult& aRv)
 {
   if (!mFrameSelection) {
     return;
   }