1
0
mirror of https://github.com/roytam1/UXP.git synced 2026-05-26 13:58:49 +00:00

Issue #3011 - Part 2: Switch spellchecker root to Shadow DOM.

Set the root for spelling checker to shadow root if the contenteditable
nodes are in the shadow DOM. Bail if the position can't be set properly.
This commit is contained in:
Moonchild
2026-03-20 14:45:11 +01:00
committed by roytam1
parent 2f20100059
commit e701dad7ef
3 changed files with 60 additions and 36 deletions
@@ -1490,8 +1490,11 @@ nsresult mozInlineSpellChecker::DoSpellCheck(mozInlineSpellWordUtil& aWordUtil,
return NS_OK;
}
aWordUtil.SetEnd(endNode, endOffset);
aWordUtil.SetPosition(beginNode, beginOffset);
nsresult rv = aWordUtil.SetPositionAndEnd(beginNode, beginOffset, endNode, endOffset);
if (NS_FAILED(rv)) {
// Just bail out and don't try to spell-check this
return NS_OK;
}
}
// aWordUtil.SetPosition flushes pending notifications, check editor again.
@@ -23,6 +23,8 @@
#include "nsIFrame.h"
#include <algorithm>
#include "mozilla/BinarySearch.h"
#include "mozilla/HTMLEditor.h"
#include "mozilla/dom/ShadowRoot.h"
using namespace mozilla;
@@ -69,8 +71,10 @@ mozInlineSpellWordUtil::Init(nsWeakPtr aWeakEditor)
mDOMDocument = domDoc;
mDocument = do_QueryInterface(domDoc);
// Find the root node for the editor. For contenteditable we'll need something
// cleverer here.
mIsContentEditableOrDesignMode = !!editor->AsHTMLEditor();
// Find the root node for the editor. For contenteditable the mRootNode could
// change to shadow root if the begin and end are inside the shadowDOM.
nsCOMPtr<nsIDOMElement> rootElt;
rv = editor->GetRootElement(getter_AddRefs(rootElt));
NS_ENSURE_SUCCESS(rv, rv);
@@ -154,7 +158,7 @@ FindNextTextNode(nsINode* aNode, int32_t aOffset, nsINode* aRoot)
return checkNode;
}
// mozInlineSpellWordUtil::SetEnd
// mozInlineSpellWordUtil::SetPositionAndEnd
//
// We have two ranges "hard" and "soft". The hard boundary is simply
// the scope of the root node. The soft boundary is that which is set
@@ -172,34 +176,44 @@ FindNextTextNode(nsINode* aNode, int32_t aOffset, nsINode* aRoot)
// position.
nsresult
mozInlineSpellWordUtil::SetEnd(nsINode* aEndNode, int32_t aEndOffset)
mozInlineSpellWordUtil::SetPositionAndEnd(nsINode* aPositionNode,
int32_t aPositionOffset,
nsINode* aEndNode,
int32_t aEndOffset)
{
MOZ_ASSERT(aPositionNode, "Null begin node?");
NS_PRECONDITION(aEndNode, "Null end node?");
NS_ASSERTION(mRootNode, "Not initialized");
// Find a appropriate root if we are dealing with contenteditable nodes which
// are in the shadow DOM. See UXP Issue #3011
if (mIsContentEditableOrDesignMode) {
nsINode* rootNode = aPositionNode->SubtreeRoot();
if (rootNode != aEndNode->SubtreeRoot()) {
return NS_ERROR_FAILURE;
}
if (mozilla::dom::ShadowRoot::FromNode(rootNode)) {
mRootNode = rootNode;
}
}
InvalidateWords();
if (!IsTextNode(aPositionNode)) {
// Start at the start of the first text node after aNode/aOffset.
aPositionNode = FindNextTextNode(aPositionNode, aPositionOffset, mRootNode);
aPositionOffset = 0;
}
mSoftBegin = NodeOffset(aPositionNode, aPositionOffset);
if (!IsTextNode(aEndNode)) {
// End at the start of the first text node after aEndNode/aEndOffset.
aEndNode = FindNextTextNode(aEndNode, aEndOffset, mRootNode);
aEndOffset = 0;
}
mSoftEnd = NodeOffset(aEndNode, aEndOffset);
return NS_OK;
}
nsresult
mozInlineSpellWordUtil::SetPosition(nsINode* aNode, int32_t aOffset)
{
InvalidateWords();
if (!IsTextNode(aNode)) {
// Start at the start of the first text node after aNode/aOffset.
aNode = FindNextTextNode(aNode, aOffset, mRootNode);
aOffset = 0;
}
mSoftBegin = NodeOffset(aNode, aOffset);
nsresult rv = EnsureWords();
if (NS_FAILED(rv)) {
@@ -207,8 +221,10 @@ mozInlineSpellWordUtil::SetPosition(nsINode* aNode, int32_t aOffset)
}
int32_t textOffset = MapDOMPositionToSoftTextOffset(mSoftBegin);
if (textOffset < 0)
if (textOffset < 0) {
return NS_OK;
}
mNextWordIndex = FindRealWordContaining(textOffset, HINT_END, true);
return NS_OK;
}
@@ -29,12 +29,11 @@ class nsINode;
* The basic operation is:
*
* 1. Call Init with the weak pointer to the editor that you're using.
* 2. Call SetEnd to set where you want to stop spellchecking. We'll stop
* at the word boundary after that. If SetEnd is not called, we'll stop
* at the end of the document's root element.
* 3. Call SetPosition to initialize the current position inside the
* previously given range.
* 4. Call GetNextWord over and over until it returns false.
* 2. Call SetPositionAndEnd to to initialize the current position inside the
* previously given range and set where you want to stop spellchecking.
* We'll stop at the word boundary after that. If SetEnd is not called,
* we'll stop at the end of the root element.
* 3. Call GetNextWord over and over until it returns false.
*/
class mozInlineSpellWordUtil
@@ -57,17 +56,22 @@ public:
};
mozInlineSpellWordUtil()
: mRootNode(nullptr),
mSoftBegin(nullptr, 0), mSoftEnd(nullptr, 0),
mNextWordIndex(-1), mSoftTextValid(false) {}
: mIsContentEditableOrDesignMode(false)
, mRootNode(nullptr)
, mSoftBegin(nullptr, 0)
, mSoftEnd(nullptr, 0)
, mNextWordIndex(-1)
, mSoftTextValid(false)
{}
nsresult Init(nsWeakPtr aWeakEditor);
nsresult SetEnd(nsINode* aEndNode, int32_t aEndOffset);
// sets the current position, this should be inside the range. If we are in
// the middle of a word, we'll move to its start.
nsresult SetPosition(nsINode* aNode, int32_t aOffset);
// Sets the current position and end. This should be inside the range.
// If we are in the middle of a word, we'll move to its start.
nsresult SetPositionAndEnd(nsINode* aPositionNode,
int32_t aPositionOffset,
nsINode* aEndNode,
int32_t aEndOffset);
// Given a point inside or immediately following a word, this returns the
// DOM range that exactly encloses that word's characters. The current
@@ -100,7 +104,8 @@ private:
// cached stuff for the editor, set by Init
nsCOMPtr<nsIDOMDocument> mDOMDocument;
nsCOMPtr<nsIDocument> mDocument;
nsCOMPtr<nsIDocument> mDocument;
bool mIsContentEditableOrDesignMode;
// range to check, see SetPosition and SetEnd
nsINode* mRootNode;