diff --git a/extensions/spellcheck/src/mozInlineSpellChecker.cpp b/extensions/spellcheck/src/mozInlineSpellChecker.cpp index 398059fc9b..a50d390037 100644 --- a/extensions/spellcheck/src/mozInlineSpellChecker.cpp +++ b/extensions/spellcheck/src/mozInlineSpellChecker.cpp @@ -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. diff --git a/extensions/spellcheck/src/mozInlineSpellWordUtil.cpp b/extensions/spellcheck/src/mozInlineSpellWordUtil.cpp index 460ac46b85..13e621c870 100644 --- a/extensions/spellcheck/src/mozInlineSpellWordUtil.cpp +++ b/extensions/spellcheck/src/mozInlineSpellWordUtil.cpp @@ -23,6 +23,8 @@ #include "nsIFrame.h" #include #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 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; } diff --git a/extensions/spellcheck/src/mozInlineSpellWordUtil.h b/extensions/spellcheck/src/mozInlineSpellWordUtil.h index b28d24ae5f..213fa52a16 100644 --- a/extensions/spellcheck/src/mozInlineSpellWordUtil.h +++ b/extensions/spellcheck/src/mozInlineSpellWordUtil.h @@ -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 mDOMDocument; - nsCOMPtr mDocument; + nsCOMPtr mDocument; + bool mIsContentEditableOrDesignMode; // range to check, see SetPosition and SetEnd nsINode* mRootNode;