Bug 1943226 - Make nsFrameSelection treat limiters are elements r=jjaschke,dom-core

`nsFrameSelection::GetLimiter()` is not `nullptr` only when it's an instance for
an independent selection of a text control.  In the case, it's set to the editor
root anonymous `<div>` of the text control.  Despite the name, this is already
optimized only for this purpose in `nsFrameSelection::NodeIsInLimiters()`.
Thus, we don't have any problems to make this clearer for the other developers
with renaming some parameter names.

`nsFrameSelection::GetAncestorLimiter()` is also always an `Element`.  So,
we can change this to `Element` too.

Differential Revision: https://phabricator.services.mozilla.com/D235254
This commit is contained in:
Masayuki Nakano
2025-01-24 02:53:07 +00:00
parent 5e9b04cd87
commit 2b503ca56d
12 changed files with 114 additions and 71 deletions

View File

@@ -2179,7 +2179,7 @@ nsresult Selection::GetCachedFrameOffset(nsIFrame* aFrame, int32_t inOffset,
return rv;
}
nsIContent* Selection::GetAncestorLimiter() const {
Element* Selection::GetAncestorLimiter() const {
MOZ_ASSERT(mSelectionType == SelectionType::eNormal);
if (mFrameSelection) {
@@ -2188,7 +2188,7 @@ nsIContent* Selection::GetAncestorLimiter() const {
return nullptr;
}
void Selection::SetAncestorLimiter(nsIContent* aLimiter) {
void Selection::SetAncestorLimiter(Element* aLimiter) {
if (NeedsToLogSelectionAPI(*this)) {
LogSelectionAPI(this, __FUNCTION__, "aLimiter", aLimiter);
LogStackForSelectionAPI();

View File

@@ -783,8 +783,8 @@ class Selection final : public nsSupportsWeakReference,
const TextRangeStyle& aTextRangeStyle);
// Methods to manipulate our mFrameSelection's ancestor limiter.
nsIContent* GetAncestorLimiter() const;
void SetAncestorLimiter(nsIContent* aLimiter);
Element* GetAncestorLimiter() const;
void SetAncestorLimiter(Element* aLimiter);
/*
* Frame Offset cache can be used just during calling

View File

@@ -364,8 +364,7 @@ nsresult ContentEventHandler::InitRootContent(
if (!aNormalSelection.RangeCount()) {
// If there is no selection range, we should compute the selection root
// from ancestor limiter or root content of the document.
mRootElement =
Element::FromNodeOrNull(aNormalSelection.GetAncestorLimiter());
mRootElement = aNormalSelection.GetAncestorLimiter();
if (!mRootElement) {
mRootElement = mDocument->GetRootElement();
if (NS_WARN_IF(!mRootElement)) {

View File

@@ -339,7 +339,8 @@ class TextInputSelectionController final : public nsSupportsWeakReference,
NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(TextInputSelectionController,
nsISelectionController)
TextInputSelectionController(PresShell* aPresShell, nsIContent* aLimiter);
TextInputSelectionController(PresShell* aPresShell,
Element& aEditorRootAnonymousDiv);
void SetScrollContainerFrame(ScrollContainerFrame* aScrollContainerFrame);
nsFrameSelection* GetConstFrameSelection() { return mFrameSelection; }
@@ -401,12 +402,12 @@ NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTION_WEAK(TextInputSelectionController, mFrameSelection)
TextInputSelectionController::TextInputSelectionController(
PresShell* aPresShell, nsIContent* aLimiter) {
PresShell* aPresShell, Element& aEditorRootAnonymousDiv) {
if (aPresShell) {
bool accessibleCaretEnabled =
PresShell::AccessibleCaretEnabled(aLimiter->OwnerDoc()->GetDocShell());
mFrameSelection =
new nsFrameSelection(aPresShell, aLimiter, accessibleCaretEnabled);
const bool accessibleCaretEnabled = PresShell::AccessibleCaretEnabled(
aEditorRootAnonymousDiv.OwnerDoc()->GetDocShell());
mFrameSelection = new nsFrameSelection(aPresShell, accessibleCaretEnabled,
&aEditorRootAnonymousDiv);
mPresShellWeak = do_GetWeakReference(aPresShell);
}
}
@@ -1608,14 +1609,14 @@ nsresult TextControlState::BindToFrame(nsTextControlFrame* aFrame) {
mBoundFrame = aFrame;
Element* rootNode = aFrame->GetRootNode();
MOZ_ASSERT(rootNode);
MOZ_ASSERT(aFrame->GetRootNode());
Element& editorRootAnonymousDiv = *aFrame->GetRootNode();
PresShell* presShell = aFrame->PresContext()->GetPresShell();
MOZ_ASSERT(presShell);
// Create a SelectionController
mSelCon = new TextInputSelectionController(presShell, rootNode);
mSelCon = new TextInputSelectionController(presShell, editorRootAnonymousDiv);
MOZ_ASSERT(!mTextListener, "Should not overwrite the object");
mTextListener = new TextInputListener(mTextCtrlElement);
@@ -1645,9 +1646,11 @@ nsresult TextControlState::BindToFrame(nsTextControlFrame* aFrame) {
// Set the correct direction on the newly created root node
if (mTextEditor->IsRightToLeft()) {
rootNode->SetAttr(kNameSpaceID_None, nsGkAtoms::dir, u"rtl"_ns, false);
editorRootAnonymousDiv.SetAttr(kNameSpaceID_None, nsGkAtoms::dir,
u"rtl"_ns, false);
} else if (mTextEditor->IsLeftToRight()) {
rootNode->SetAttr(kNameSpaceID_None, nsGkAtoms::dir, u"ltr"_ns, false);
editorRootAnonymousDiv.SetAttr(kNameSpaceID_None, nsGkAtoms::dir,
u"ltr"_ns, false);
} else {
// otherwise, inherit the content node's direction
}

View File

@@ -524,14 +524,14 @@ class MOZ_STACK_CLASS AutoClonedSelectionRangeArray final
* Equivalent to nsFrameSelection::GetLimiter().
* NOTE: This should be called only when IsForSelection() returns true.
*/
[[nodiscard]] nsIContent* GetLimiter() const {
[[nodiscard]] dom::Element* GetLimiter() const {
return mLimitersAndCaretData.mLimiter;
}
/**
* Equivalent to nsFrameSelection::GetAncestorLimiter()
* NOTE: This should be called only when IsForSelection() returns true.
*/
[[nodiscard]] nsIContent* GetAncestorLimiter() const {
[[nodiscard]] dom::Element* GetAncestorLimiter() const {
return mLimitersAndCaretData.mAncestorLimiter;
}
/**
@@ -553,12 +553,12 @@ class MOZ_STACK_CLASS AutoClonedSelectionRangeArray final
return mLimitersAndCaretData.mCaretBidiLevel;
}
void SetAncestorLimiter(const nsIContent* aSelectionAncestorLimiter) {
void SetAncestorLimiter(const dom::Element* aSelectionAncestorLimiter) {
if (mLimitersAndCaretData.mAncestorLimiter == aSelectionAncestorLimiter) {
return;
}
mLimitersAndCaretData.mAncestorLimiter =
const_cast<nsIContent*>(aSelectionAncestorLimiter);
const_cast<dom::Element*>(aSelectionAncestorLimiter);
if (NodeIsInLimiters(GetFocusNode())) {
return;
}

View File

@@ -5702,7 +5702,7 @@ Element* EditorBase::FindSelectionRoot(const nsINode& aNode) const {
}
void EditorBase::InitializeSelectionAncestorLimit(
nsIContent& aAncestorLimit) const {
Element& aAncestorLimit) const {
MOZ_ASSERT(IsEditActionDataAvailable());
SelectionRef().SetAncestorLimiter(&aAncestorLimit);
@@ -5712,7 +5712,7 @@ nsresult EditorBase::InitializeSelection(
const nsINode& aOriginalEventTargetNode) {
MOZ_ASSERT(IsEditActionDataAvailable());
nsCOMPtr<nsIContent> selectionRootContent =
const RefPtr<Element> selectionRootContent =
FindSelectionRoot(aOriginalEventTargetNode);
if (!selectionRootContent) {
return NS_OK;

View File

@@ -2517,8 +2517,7 @@ class EditorBase : public nsIEditor,
* has parent node. So, it's always safe to
* call SetAncestorLimit() with this node.
*/
virtual void InitializeSelectionAncestorLimit(
nsIContent& aAncestorLimit) const;
virtual void InitializeSelectionAncestorLimit(Element& aAncestorLimit) const;
/**
* Initializes selection and caret for the editor at getting focus. If

View File

@@ -1063,7 +1063,7 @@ nsresult HTMLEditor::CollapseSelectionToEndOfLastLeafNodeOfDocument() const {
}
void HTMLEditor::InitializeSelectionAncestorLimit(
nsIContent& aAncestorLimit) const {
Element& aAncestorLimit) const {
MOZ_ASSERT(IsEditActionDataAvailable());
// Hack for initializing selection.
@@ -7450,7 +7450,7 @@ void HTMLEditor::NotifyEditingHostMaybeChanged() {
}
// Compute current editing host.
nsIContent* editingHost = ComputeEditingHost();
Element* const editingHost = ComputeEditingHost();
if (NS_WARN_IF(!editingHost)) {
return;
}

View File

@@ -3505,7 +3505,7 @@ class HTMLEditor final : public EditorBase,
SetHTMLBackgroundColorWithTransaction(const nsAString& aColor);
MOZ_CAN_RUN_SCRIPT_BOUNDARY void InitializeSelectionAncestorLimit(
nsIContent& aAncestorLimit) const final;
Element& aAncestorLimit) const final;
/**
* Make the given selection span the entire document.

View File

@@ -939,7 +939,7 @@ void PresShell::Init(nsPresContext* aPresContext, nsViewManager* aViewManager) {
mAccessibleCaretEventHub->Init();
}
mSelection = new nsFrameSelection(this, nullptr, accessibleCaretEnabled);
mSelection = new nsFrameSelection(this, accessibleCaretEnabled);
// Important: this has to happen after the selection has been set up
#ifdef SHOW_CARET
@@ -2462,7 +2462,7 @@ PresShell::CompleteMove(bool aForward, bool aExtend) {
// Beware! This may flush notifications via synchronous
// ScrollSelectionIntoView.
RefPtr<nsFrameSelection> frameSelection = mSelection;
nsIContent* limiter = frameSelection->GetAncestorLimiter();
Element* const limiter = frameSelection->GetAncestorLimiter();
nsIFrame* frame = limiter ? limiter->GetPrimaryFrame()
: FrameConstructor()->GetRootElementFrame();
if (!frame) {

View File

@@ -19,6 +19,7 @@
#include "mozilla/IntegerRange.h"
#include "mozilla/Logging.h"
#include "mozilla/PresShell.h"
#include "mozilla/PseudoStyleType.h"
#include "mozilla/ScrollContainerFrame.h"
#include "mozilla/ScrollTypes.h"
#include "mozilla/StaticAnalysisFunctions.h"
@@ -218,19 +219,35 @@ bool nsFrameSelection::NodeIsInLimiters(const nsINode* aContainerNode) const {
// static
bool nsFrameSelection::NodeIsInLimiters(
const nsINode* aContainerNode, const nsIContent* aSelectionLimiter,
const nsIContent* aSelectionAncestorLimiter) {
const nsINode* aContainerNode, const Element* aSelectionLimiter,
const Element* aSelectionAncestorLimiter) {
if (!aContainerNode) {
return false;
}
if (aSelectionLimiter && aSelectionLimiter != aContainerNode &&
aSelectionLimiter != aContainerNode->GetParent()) {
// if newfocus == the limiter. that's ok. but if not there and not parent
// bad
return false; // not in the right content. tLimiter said so
// If there is a selection limiter, it must be the anonymous <div> of a text
// control. The <div> should have only one Text and/or a <br>. Therefore,
// when it's non-nullptr, selection range containers must be the container or
// the Text in it.
if (aSelectionLimiter) {
MOZ_ASSERT(aSelectionLimiter->GetPseudoElementType() ==
PseudoStyleType::mozTextControlEditingRoot);
MOZ_ASSERT(aSelectionLimiter->IsHTMLElement(nsGkAtoms::div));
if (aSelectionLimiter == aContainerNode) {
return true;
}
if (aSelectionLimiter == aContainerNode->GetParent()) {
NS_WARNING_ASSERTION(aContainerNode->IsText(),
ToString(*aContainerNode).c_str());
MOZ_ASSERT(aContainerNode->IsText());
return true;
}
return false;
}
// XXX We might need to return `false` if aContainerNode is in a native
// anonymous subtree, but doing it will make it impossible to select the
// anonymous subtree text in <details>.
return !aSelectionAncestorLimiter ||
aContainerNode->IsInclusiveDescendantOf(aSelectionAncestorLimiter);
}
@@ -376,8 +393,9 @@ nsFrameSelection::CreateRangeExtendedToSomewhere(
nsDirection aExtendDirection, nsSelectionAmount aAmount,
CaretMovementStyle aMovementStyle);
nsFrameSelection::nsFrameSelection(PresShell* aPresShell, nsIContent* aLimiter,
const bool aAccessibleCaretEnabled) {
nsFrameSelection::nsFrameSelection(
PresShell* aPresShell, const bool aAccessibleCaretEnabled,
Element* aEditorRootAnonymousDiv /* = nullptr */) {
for (size_t i = 0; i < std::size(mDomSelections); i++) {
mDomSelections[i] = new Selection(kPresentSelectionTypes[i], this);
}
@@ -389,7 +407,13 @@ nsFrameSelection::nsFrameSelection(PresShell* aPresShell, nsIContent* aLimiter,
mPresShell = aPresShell;
mDragState = false;
mLimiters.mLimiter = aLimiter;
MOZ_ASSERT_IF(aEditorRootAnonymousDiv,
aEditorRootAnonymousDiv->GetPseudoElementType() ==
PseudoStyleType::mozTextControlEditingRoot);
MOZ_ASSERT_IF(aEditorRootAnonymousDiv,
aEditorRootAnonymousDiv->IsHTMLElement(nsGkAtoms::div));
mLimiters.mLimiter = aEditorRootAnonymousDiv;
// This should only ever be initialized on the main thread, so we are OK here.
MOZ_ASSERT(NS_IsMainThread());
@@ -940,9 +964,8 @@ nsresult nsFrameSelection::MoveCaret(nsDirection aDirection,
// static
Result<PeekOffsetOptions, nsresult>
nsFrameSelection::CreatePeekOffsetOptionsForCaretMove(
const nsIContent* aSelectionLimiter,
ForceEditableRegion aForceEditableRegion, ExtendSelection aExtendSelection,
CaretMovementStyle aMovementStyle) {
const Element* aSelectionLimiter, ForceEditableRegion aForceEditableRegion,
ExtendSelection aExtendSelection, CaretMovementStyle aMovementStyle) {
PeekOffsetOptions options;
// set data using aSelectionLimiter to stop on scroll views. If we have a
// limiter then we stop peeking when we hit scrollable views. If no limiter
@@ -978,7 +1001,7 @@ Result<Element*, nsresult> nsFrameSelection::GetAncestorLimiterForCaretMove(
MOZ_ASSERT(mPresShell->GetDocument() == content->GetComposedDoc());
Element* ancestorLimiter = Element::FromNodeOrNull(GetAncestorLimiter());
Element* ancestorLimiter = GetAncestorLimiter();
if (aSelection->IsEditorSelection()) {
// If the editor has not receive `focus` event, it may have not set ancestor
// limiter. Then, we need to compute it here for the caret move.
@@ -2051,9 +2074,7 @@ nsFrameSelection::CreateRangeExtendedToSomewhere(
: aRange.EndRef().AsRaw(),
aExtendDirection, aLimitersAndCaretData.mCaretAssociationHint,
aLimitersAndCaretData.mCaretBidiLevel, aAmount, options.unwrap(),
// FIXME: mAncestorLimiter should always be an Element, but it's not
// guaranteed at build time for now.
Element::FromNodeOrNull(aLimitersAndCaretData.mAncestorLimiter));
aLimitersAndCaretData.mAncestorLimiter);
if (result.isErr()) {
return result.propagateErr();
}
@@ -3022,7 +3043,7 @@ nsresult CreateAndAddRange(nsINode* aContainer, int32_t aOffset,
// End of Table Selection
void nsFrameSelection::SetAncestorLimiter(nsIContent* aLimiter) {
void nsFrameSelection::SetAncestorLimiter(Element* aLimiter) {
if (mLimiters.mAncestorLimiter != aLimiter) {
mLimiters.mAncestorLimiter = aLimiter;
const Selection& sel = NormalSelection();

View File

@@ -15,6 +15,7 @@
#include "mozilla/CompactPair.h"
#include "mozilla/EnumSet.h"
#include "mozilla/EventForwards.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/Highlight.h"
#include "mozilla/dom/Selection.h"
#include "mozilla/Result.h"
@@ -238,6 +239,7 @@ enum class TableSelectionMode : uint32_t {
class nsFrameSelection final {
public:
using CaretAssociationHint = mozilla::CaretAssociationHint;
using Element = mozilla::dom::Element;
/*interfaces for addref and release and queryinterface*/
@@ -520,8 +522,8 @@ class nsFrameSelection final {
[[nodiscard]] bool NodeIsInLimiters(const nsINode* aContainerNode) const;
[[nodiscard]] static bool NodeIsInLimiters(
const nsINode* aContainerNode, const nsIContent* aSelectionLimiter,
const nsIContent* aSelectionAncestorLimiter);
const nsINode* aContainerNode, const Element* aSelectionLimiter,
const Element* aSelectionAncestorLimiter);
/**
* GetFrameToPageSelect() returns a frame which is ancestor limit of
@@ -802,16 +804,26 @@ class nsFrameSelection final {
}
/**
* Get the content node that limits the selection
*
* When searching up a nodes for parents, as in a text edit field
* in an browser page, we must stop at this node else we reach into the
* parent page, which is very bad!
* GetLimiter() returns the selection limiter element which is currently
* non-nullptr only when this instance is for an independent selection of a
* text control. Then, this returns the editor root anonymous <div> in the
* text control element.
*/
nsIContent* GetLimiter() const { return mLimiters.mLimiter; }
Element* GetLimiter() const { return mLimiters.mLimiter; }
nsIContent* GetAncestorLimiter() const { return mLimiters.mAncestorLimiter; }
MOZ_CAN_RUN_SCRIPT_BOUNDARY void SetAncestorLimiter(nsIContent* aLimiter);
/**
* GetAncestorLimiter() returns the root of current selection ranges. This is
* typically the focused editing host unless it's the root element of the
* document.
*/
Element* GetAncestorLimiter() const { return mLimiters.mAncestorLimiter; }
/**
* Set ancestor limiter. If aLimiter is not nullptr, this adjusts all
* selection ranges into the limiter element. Thus, calling this may run
* the selection listeners.
*/
MOZ_CAN_RUN_SCRIPT_BOUNDARY void SetAncestorLimiter(Element* aLimiter);
/**
* GetPrevNextBidiLevels will return the frames and associated Bidi levels of
@@ -855,13 +867,15 @@ class nsFrameSelection final {
* @param aPresShell is the parameter to be used for most of the other calls
* for callbacks etc
*
* @param aLimiter limits the selection to nodes with aLimiter parents
*
* @param aAccessibleCaretEnabled true if we should enable the accessible
* caret.
*
* @param aEditorRootAnonymousDiv if this instance is for an independent
* selection for a text control, specify this to the anonymous <div> element
* of the text control which contains only an editable Text and/or a <br>.
*/
nsFrameSelection(mozilla::PresShell* aPresShell, nsIContent* aLimiter,
bool aAccessibleCaretEnabled);
nsFrameSelection(mozilla::PresShell* aPresShell, bool aAccessibleCaretEnabled,
Element* aEditorRootAnonymousDiv = nullptr);
/**
* @param aRequesterFuncName function name which wants to start the batch.
@@ -1005,7 +1019,7 @@ class nsFrameSelection final {
enum class ForceEditableRegion : bool { No, Yes };
static mozilla::Result<mozilla::PeekOffsetOptions, nsresult>
CreatePeekOffsetOptionsForCaretMove(const nsIContent* aSelectionLimiter,
CreatePeekOffsetOptionsForCaretMove(const Element* aSelectionLimiter,
ForceEditableRegion aForceEditableRegion,
ExtendSelection aExtendSelection,
CaretMovementStyle aMovementStyle);
@@ -1019,8 +1033,8 @@ class nsFrameSelection final {
* @param aSelection The selection object. Must be non-null
* @return The ancestor limiter, or nullptr.
*/
mozilla::Result<mozilla::dom::Element*, nsresult>
GetAncestorLimiterForCaretMove(mozilla::dom::Selection* aSelection) const;
mozilla::Result<Element*, nsresult> GetAncestorLimiterForCaretMove(
mozilla::dom::Selection* aSelection) const;
/**
* CreateRangeExtendedToSomewhere() is common method to implement
@@ -1190,10 +1204,15 @@ class nsFrameSelection final {
Batching mBatching;
struct Limiters {
// Limit selection navigation to a child of this node.
nsCOMPtr<nsIContent> mLimiter;
// Limit selection navigation to a descendant of this node.
nsCOMPtr<nsIContent> mAncestorLimiter;
// Limit selection navigation to a child of this element.
// This is set only when the nsFrameSelection instance is for the
// independent selection for a text control. If this is set, it's always
// the anonymous <div> of the text control element.
RefPtr<Element> mLimiter;
// Limit selection navigation to a descendant of this element.
// This is typically the focused editing host if set unless it's the root
// element of the document.
RefPtr<Element> mAncestorLimiter;
};
Limiters mLimiters;
@@ -1294,6 +1313,8 @@ namespace mozilla {
* A struct for sharing nsFrameSelection outside of its instance.
*/
struct LimitersAndCaretData {
using Element = dom::Element;
LimitersAndCaretData() = default;
explicit LimitersAndCaretData(const nsFrameSelection& aFrameSelection)
: mLimiter(aFrameSelection.GetLimiter()),
@@ -1312,9 +1333,9 @@ struct LimitersAndCaretData {
}
// nsFrameSelection::GetLimiter
nsCOMPtr<nsIContent> mLimiter;
RefPtr<Element> mLimiter;
// nsFrameSelection::GetAncestorLimiter
nsCOMPtr<nsIContent> mAncestorLimiter;
RefPtr<Element> mAncestorLimiter;
// nsFrameSelection::GetHint
CaretAssociationHint mCaretAssociationHint = CaretAssociationHint::Before;
// nsFrameSelection::GetCaretBidiLevel