diff --git a/dom/base/nsRange.cpp b/dom/base/nsRange.cpp index 150bf7d628..a40ba04a07 100644 --- a/dom/base/nsRange.cpp +++ b/dom/base/nsRange.cpp @@ -269,6 +269,7 @@ nsRange::nsRange(nsINode* aNode) , mStartOffsetWasIncremented(false) , mEndOffsetWasIncremented(false) , mEnableGravitationOnElementRemoval(true) + , mCalledByJS(false) #ifdef DEBUG , mAssertNextInsertOrAppendIndex(-1) , mAssertNextInsertOrAppendNode(nullptr) @@ -997,7 +998,7 @@ nsRange::DoSetRange(nsINode* aStartN, uint32_t aStartOffset, // 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); } } @@ -1224,6 +1225,13 @@ nsRange::IsValidBoundary(nsINode* aNode) 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) { @@ -1279,6 +1287,13 @@ nsRange::SetStart(nsINode* aParent, uint32_t aOffset) return NS_OK; } +void +nsRange::SetStartBeforeJS(nsINode& aNode, ErrorResult& aErr) +{ + AutoCalledByJSSetter markAsCalledByJS(*this); + SetStartBefore(aNode, aErr); +} + void nsRange::SetStartBefore(nsINode& aNode, ErrorResult& aRv) { @@ -1310,6 +1325,13 @@ nsRange::SetStartBefore(nsIDOMNode* aSibling) return rv.StealNSResult(); } +void +nsRange::SetStartAfterJS(nsINode& aNode, ErrorResult& aErr) +{ + AutoCalledByJSSetter markAsCalledByJS(*this); + SetStartAfter(aNode, aErr); +} + void nsRange::SetStartAfter(nsINode& aNode, ErrorResult& aRv) { @@ -1341,6 +1363,13 @@ nsRange::SetStartAfter(nsIDOMNode* aSibling) 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) { @@ -1455,6 +1484,13 @@ nsRange::SetStartAndEnd(nsINode* aStartParent, uint32_t aStartOffset, return NS_OK; } +void +nsRange::SetEndBeforeJS(nsINode& aNode, ErrorResult& aErr) +{ + AutoCalledByJSSetter markAsCalledByJS(*this); + SetEndBefore(aNode, aErr); +} + void nsRange::SetEndBefore(nsINode& aNode, ErrorResult& aRv) { @@ -1486,6 +1522,13 @@ nsRange::SetEndBefore(nsIDOMNode* aSibling) return rv.StealNSResult(); } +void +nsRange::SetEndAfterJS(nsINode& aNode, ErrorResult& aErr) +{ + AutoCalledByJSSetter markAsCalledByJS(*this); + SetEndAfter(aNode, aErr); +} + void nsRange::SetEndAfter(nsINode& aNode, ErrorResult& aRv) { @@ -1532,6 +1575,13 @@ nsRange::Collapse(bool aToStart) return NS_OK; } +void +nsRange::CollapseJS(bool aToStart) +{ + AutoCalledByJSSetter markAsCalledByJS(*this); + Unused << Collapse(aToStart); +} + NS_IMETHODIMP nsRange::SelectNode(nsIDOMNode* aN) { @@ -1543,6 +1593,13 @@ nsRange::SelectNode(nsIDOMNode* aN) return rv.StealNSResult(); } +void +nsRange::SelectNodeJS(nsINode& aNode, ErrorResult& aErr) +{ + AutoCalledByJSSetter markAsCalledByJS(*this); + SelectNode(aNode, aErr); +} + void nsRange::SelectNode(nsINode& aNode, ErrorResult& aRv) { @@ -1582,6 +1639,13 @@ nsRange::SelectNodeContents(nsIDOMNode* aN) return rv.StealNSResult(); } +void +nsRange::SelectNodeContentsJS(nsINode& aNode, ErrorResult& aErr) +{ + AutoCalledByJSSetter markAsCalledByJS(*this); + SelectNodeContents(aNode, aErr); +} + void nsRange::SelectNodeContents(nsINode& aNode, ErrorResult& aRv) { diff --git a/dom/base/nsRange.h b/dom/base/nsRange.h index 7cc3049ded..f96bdd4c4d 100644 --- a/dom/base/nsRange.h +++ b/dom/base/nsRange.h @@ -20,6 +20,7 @@ #include "nsStubMutationObserver.h" #include "nsWrapperCache.h" #include "mozilla/Attributes.h" +#include "mozilla/GuardObjects.h" namespace mozilla { class ErrorResult; @@ -256,14 +257,20 @@ public: void InsertNode(nsINode& aNode, ErrorResult& aErr); bool IntersectsNode(nsINode& aNode, ErrorResult& aRv); bool IsPointInRange(nsINode& aParent, uint32_t aOffset, ErrorResult& aErr); - 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); + + // *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 GetBoundingClientRect(bool aClampToEdge = true, bool aFlushLayout = true); @@ -272,6 +279,17 @@ public: 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); + static void GetInnerTextNoFlush(mozilla::dom::DOMString& aValue, mozilla::ErrorResult& aError, nsIContent* aStartParent, @@ -392,6 +410,31 @@ protected: 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) @@ -428,6 +471,7 @@ protected: bool mStartOffsetWasIncremented : 1; bool mEndOffsetWasIncremented : 1; bool mEnableGravitationOnElementRemoval : 1; + bool mCalledByJS : 1; #ifdef DEBUG int32_t mAssertNextInsertOrAppendIndex; nsINode* mAssertNextInsertOrAppendNode; diff --git a/dom/webidl/Range.webidl b/dom/webidl/Range.webidl index 6b8be970ba..a7bd5f95d6 100644 --- a/dom/webidl/Range.webidl +++ b/dom/webidl/Range.webidl @@ -26,22 +26,23 @@ interface Range { [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; diff --git a/dom/webidl/Selection.webidl b/dom/webidl/Selection.webidl index 2b85b87f90..b5da4a5eb4 100644 --- a/dom/webidl/Selection.webidl +++ b/dom/webidl/Selection.webidl @@ -17,17 +17,17 @@ interface Selection { 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(); @@ -36,7 +36,7 @@ interface Selection { readonly attribute DOMString type; [Throws] Range getRangeAt(unsigned long index); - [Throws] + [Throws, BinaryName="addRangeJS"] void addRange(Range range); [Throws] void removeRange(Range range); @@ -46,7 +46,7 @@ interface Selection { [Throws] boolean containsNode(Node node, boolean allowPartialContainment); - [Throws] + [Throws, BinaryName="setBaseAndExtentJS"] void setBaseAndExtent(Node anchorNode, unsigned long anchorOffset, Node focusNode, diff --git a/layout/generic/Selection.h b/layout/generic/Selection.h index 83a2e2c688..5470acecdd 100644 --- a/layout/generic/Selection.h +++ b/layout/generic/Selection.h @@ -170,13 +170,19 @@ public: 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); + // *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 Extend(nsINode& aNode, uint32_t aOffset, mozilla::ErrorResult& aRv); + void ExtendJS(nsINode& aNode, uint32_t aOffset, + mozilla::ErrorResult& aRv); + + void SelectAllChildrenJS(nsINode& aNode, mozilla::ErrorResult& aRv); - void SelectAllChildren(nsINode& aNode, mozilla::ErrorResult& aRv); void DeleteFromDocument(mozilla::ErrorResult& aRv); uint32_t RangeCount() const @@ -187,7 +193,7 @@ public: void GetType(nsAString& aOutType) const; 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); @@ -207,9 +213,9 @@ public: 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); @@ -243,6 +249,17 @@ public: 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; @@ -265,7 +282,8 @@ public: mSelectionType = aSelectionType; } - nsresult NotifySelectionListeners(); + nsresult NotifySelectionListeners(bool aCalledByJS); + nsresult NotifySelectionListeners(); friend struct AutoUserInitiated; struct MOZ_RAII AutoUserInitiated @@ -371,6 +389,12 @@ private: */ 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; diff --git a/layout/generic/nsSelection.cpp b/layout/generic/nsSelection.cpp index b9ee20d1fc..fd67440599 100644 --- a/layout/generic/nsSelection.cpp +++ b/layout/generic/nsSelection.cpp @@ -10,6 +10,7 @@ #include "mozilla/dom/Selection.h" #include "mozilla/Attributes.h" +#include "mozilla/AutoRestore.h" #include "mozilla/EventStates.h" #include "nsCOMPtr.h" @@ -3473,6 +3474,7 @@ Selection::Selection() , mDirection(eDirNext) , mSelectionType(SelectionType::eNormal) , mUserInitiated(false) + , mCalledByJS(false) , mSelectionChangeBlockerCount(0) { } @@ -3483,6 +3485,7 @@ Selection::Selection(nsFrameSelection* aList) , mDirection(eDirNext) , mSelectionType(SelectionType::eNormal) , mUserInitiated(false) + , mCalledByJS(false) , mSelectionChangeBlockerCount(0) { } @@ -4966,6 +4969,14 @@ Selection::AddRange(nsIDOMRange* aDOMRange) return result.StealNSResult(); } +void +Selection::AddRangeJS(nsRange& aRange, ErrorResult& aRv) +{ + AutoRestore calledFromJSRestorer(mCalledByJS); + mCalledByJS = true; + AddRange(aRange, aRv); +} + void Selection::AddRange(nsRange& aRange, ErrorResult& aRv) { @@ -5140,6 +5151,14 @@ Selection::CollapseNative(nsINode* aParentNode, int32_t aOffset) return Collapse(aParentNode, aOffset); } +void +Selection::CollapseJS(nsINode& aNode, uint32_t aOffset, ErrorResult& aRv) +{ + AutoRestore calledFromJSRestorer(mCalledByJS); + mCalledByJS = true; + Collapse(aNode, aOffset, aRv); +} + nsresult Selection::Collapse(nsINode* aParentNode, int32_t aOffset) { @@ -5240,6 +5259,14 @@ Selection::CollapseToStart() return result.StealNSResult(); } +void +Selection::CollapseToStartJS(ErrorResult& aRv) +{ + AutoRestore calledFromJSRestorer(mCalledByJS); + mCalledByJS = true; + CollapseToStart(aRv); +} + void Selection::CollapseToStart(ErrorResult& aRv) { @@ -5281,6 +5308,14 @@ Selection::CollapseToEnd() return result.StealNSResult(); } +void +Selection::CollapseToEndJS(ErrorResult& aRv) +{ + AutoRestore calledFromJSRestorer(mCalledByJS); + mCalledByJS = true; + CollapseToEnd(aRv); +} + void Selection::CollapseToEnd(ErrorResult& aRv) { @@ -5495,6 +5530,14 @@ Selection::ExtendNative(nsINode* aParentNode, int32_t aOffset) return Extend(aParentNode, aOffset); } +void +Selection::ExtendJS(nsINode& aNode, uint32_t aOffset, ErrorResult& aRv) +{ + AutoRestore calledFromJSRestorer(mCalledByJS); + mCalledByJS = true; + Extend(aNode, aOffset, aRv); +} + nsresult Selection::Extend(nsINode* aParentNode, int32_t aOffset) { @@ -5790,6 +5833,14 @@ Selection::SelectAllChildren(nsIDOMNode* aParentNode) return result.StealNSResult(); } +void +Selection::SelectAllChildrenJS(nsINode& aNode, ErrorResult& aRv) +{ + AutoRestore calledFromJSRestorer(mCalledByJS); + mCalledByJS = true; + SelectAllChildren(aNode, aRv); +} + void Selection::SelectAllChildren(nsINode& aNode, ErrorResult& aRv) { @@ -6239,6 +6290,14 @@ Selection::RemoveSelectionListener(nsISelectionListener* aListenerToRemove, } } +nsresult +Selection::NotifySelectionListeners(bool aCalledByJS) +{ + AutoRestore calledFromJSRestorer(mCalledByJS); + mCalledByJS = aCalledByJS; + return NotifySelectionListeners(); +} + nsresult Selection::NotifySelectionListeners() { @@ -6454,6 +6513,19 @@ Selection::Modify(const nsAString& aAlter, const nsAString& aDirection, } } +void +Selection::SetBaseAndExtentJS(nsINode& aAnchorNode, + uint32_t aAnchorOffset, + nsINode& aFocusNode, + uint32_t aFocusOffset, + ErrorResult& aRv) +{ + AutoRestore calledFromJSRestorer(mCalledByJS); + mCalledByJS = true; + SetBaseAndExtent(aAnchorNode, aAnchorOffset, + aFocusNode, aFocusOffset, aRv); +} + void Selection::SetBaseAndExtent(nsINode& aAnchorNode, uint32_t aAnchorOffset, nsINode& aFocusNode, uint32_t aFocusOffset,