Issue #2388 - Part 1: Mark Selection as "called by JS" when caused by a JS call to the selection API

This commit is contained in:
Moonchild 2024-01-16 19:28:24 +01:00 committed by roytam1
commit a83e4b6a34
6 changed files with 239 additions and 34 deletions

View file

@ -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)
{

View file

@ -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<DOMRect> 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;

View file

@ -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;

View file

@ -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,

View file

@ -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;

View file

@ -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<bool> 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<bool> 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<bool> 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<bool> 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<bool> 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<bool> 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<bool> 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<bool> calledFromJSRestorer(mCalledByJS);
mCalledByJS = true;
SetBaseAndExtent(aAnchorNode, aAnchorOffset,
aFocusNode, aFocusOffset, aRv);
}
void
Selection::SetBaseAndExtent(nsINode& aAnchorNode, uint32_t aAnchorOffset,
nsINode& aFocusNode, uint32_t aFocusOffset,