Bug 1497480 - Part 1: Set the root for spelling checker to shadow root if the contenteditable nodes are in the shadow DOM; r=smaug a=jcristau
authorEdgar Chen <echen@mozilla.com>
Fri, 02 Nov 2018 00:07:30 +0000
changeset 501041 fa8dfa3aef26a452b800502fa4e6ebf956e64c5e
parent 501040 2b2c323a635295870bfc3d4e5bf511185857a36e
child 501042 ad24ecd2dd3979ec8e13ab4ba79d370c4423fbbd
push id1864
push userffxbld-merge
push dateMon, 03 Dec 2018 15:51:40 +0000
treeherdermozilla-release@f040763d99ad [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug, jcristau
bugs1497480
milestone64.0
Bug 1497480 - Part 1: Set the root for spelling checker to shadow root if the contenteditable nodes are in the shadow DOM; r=smaug a=jcristau Differential Revision: https://phabricator.services.mozilla.com/D9542
extensions/spellcheck/src/mozInlineSpellChecker.cpp
extensions/spellcheck/src/mozInlineSpellWordUtil.cpp
extensions/spellcheck/src/mozInlineSpellWordUtil.h
--- a/extensions/spellcheck/src/mozInlineSpellChecker.cpp
+++ b/extensions/spellcheck/src/mozInlineSpellChecker.cpp
@@ -1365,18 +1365,22 @@ nsresult mozInlineSpellChecker::DoSpellC
     nsINode* rootNode = aWordUtil.GetRootNode();
     if (!beginNode->IsInComposedDoc() || !endNode->IsInComposedDoc() ||
         !nsContentUtils::ContentIsShadowIncludingDescendantOf(beginNode, rootNode) ||
         !nsContentUtils::ContentIsShadowIncludingDescendantOf(endNode, rootNode)) {
       // Just bail out and don't try to spell-check this
       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.
   if (!mTextEditor) {
     return NS_ERROR_FAILURE;
   }
 
   int32_t wordsChecked = 0;
--- a/extensions/spellcheck/src/mozInlineSpellWordUtil.cpp
+++ b/extensions/spellcheck/src/mozInlineSpellWordUtil.cpp
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozInlineSpellWordUtil.h"
 
 #include "mozilla/BinarySearch.h"
+#include "mozilla/HTMLEditor.h"
 #include "mozilla/TextEditor.h"
 #include "mozilla/dom/Element.h"
 
 #include "nsDebug.h"
 #include "nsAtom.h"
 #include "nsComponentManagerUtils.h"
 #include "nsIEditor.h"
 #include "nsUnicodeProperties.h"
@@ -55,18 +56,20 @@ mozInlineSpellWordUtil::Init(TextEditor*
     return NS_ERROR_FAILURE;
   }
 
   mDocument = aTextEditor->GetDocument();
   if (NS_WARN_IF(!mDocument)) {
     return NS_ERROR_FAILURE;
   }
 
-  // Find the root node for the editor. For contenteditable we'll need something
-  // cleverer here.
+  mIsContentEditableOrDesignMode = !!aTextEditor->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.
   mRootNode = aTextEditor->GetRoot();
   if (NS_WARN_IF(!mRootNode)) {
     return NS_ERROR_FAILURE;
   }
   return NS_OK;
 }
 
 static inline bool
@@ -140,17 +143,17 @@ FindNextTextNode(nsINode* aNode, int32_t
   }
 
   while (checkNode && !IsSpellCheckingTextNode(checkNode)) {
     checkNode = checkNode->GetNextNode(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
 //    by the caller of this class by calling this function. If this function is
 //    not called, the soft boundary is the same as the hard boundary.
 //
 //    When we reach the soft boundary (mSoftEnd), we keep
 //    going until we reach the end of a word. This allows the caller to set the
@@ -158,53 +161,65 @@ FindNextTextNode(nsINode* aNode, int32_t
 //    words. When we reach the hard boundary we stop no matter what.
 //
 //    There is no beginning soft boundary. This is because we only go to the
 //    previous node once, when finding the previous word boundary in
 //    SetPosition(). You might think of the soft boundary as being this initial
 //    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?");
   MOZ_ASSERT(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.
+  if (mIsContentEditableOrDesignMode) {
+    nsINode* rootNode = aPositionNode->SubtreeRoot();
+    if (rootNode != aEndNode->SubtreeRoot()) {
+      return NS_ERROR_FAILURE;
+    }
+
+    if (ShadowRoot::FromNode(rootNode)) {
+      mRootNode = rootNode;
+    }
+  }
+
   InvalidateWords();
 
+  if (!IsSpellCheckingTextNode(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 (!IsSpellCheckingTextNode(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 (!IsSpellCheckingTextNode(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)) {
     return rv;
   }
 
   int32_t textOffset = MapDOMPositionToSoftTextOffset(mSoftBegin);
-  if (textOffset < 0)
+  if (textOffset < 0) {
     return NS_OK;
+  }
+
   mNextWordIndex = FindRealWordContaining(textOffset, HINT_END, true);
   return NS_OK;
 }
 
 nsresult
 mozInlineSpellWordUtil::EnsureWords()
 {
   if (mSoftTextValid)
--- a/extensions/spellcheck/src/mozInlineSpellWordUtil.h
+++ b/extensions/spellcheck/src/mozInlineSpellWordUtil.h
@@ -75,39 +75,37 @@ public:
  *    complex rules; for example substrings that look like URLs or
  *    email addresses are treated as single words, but otherwise many kinds of
  *    punctuation are treated as word separators. GetNextWord provides a way
  *    to iterate over these "real words".
  *
  *    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 MOZ_STACK_CLASS mozInlineSpellWordUtil
 {
 public:
   mozInlineSpellWordUtil()
-    : mRootNode(nullptr),
+    : mIsContentEditableOrDesignMode(false), mRootNode(nullptr),
       mSoftBegin(nullptr, 0), mSoftEnd(nullptr, 0),
       mNextWordIndex(-1), mSoftTextValid(false) {}
 
   nsresult Init(mozilla::TextEditor* aTextEditor);
 
-  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);
+  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
   // position will be at the end of the word. This will find the previous
   // word if the current position is space, so if you care that the point is
   // inside the word, you should check the range.
   //
   // THIS CHANGES THE CURRENT POSITION AND RANGE. It is designed to be called
@@ -133,16 +131,17 @@ public:
 
   nsIDocument* GetDocument() const { return mDocument; }
   nsINode* GetRootNode() { return mRootNode; }
 
 private:
 
   // cached stuff for the editor, set by Init
   nsCOMPtr<nsIDocument>         mDocument;
+  bool mIsContentEditableOrDesignMode;
 
   // range to check, see SetPosition and SetEnd
   nsINode*    mRootNode;
   NodeOffset  mSoftBegin;
   NodeOffset  mSoftEnd;
 
   // DOM text covering the soft range, with newlines added at block boundaries
   nsString mSoftText;