Bug 1533293 - part 2: Rewrite EditorBase::SelectEntireDocument() and its overrides r=m_kato
☠☠ backed out by bfc174c4deb8 ☠ ☠
authorMasayuki Nakano <masayuki@d-toybox.com>
Mon, 18 Mar 2019 01:51:53 +0000
changeset 525611 e536f6e123d8f54d5bf165e5e78da13c71a901af
parent 525610 19cff61f4fed314bb596c9b82483d1bc88a246fd
child 525612 d011dfe8368374923cb69a0ab510d6814cbf3ab1
push id2032
push userffxbld-merge
push dateMon, 13 May 2019 09:36:57 +0000
treeherdermozilla-release@455c1065dcbe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersm_kato
bugs1533293
milestone67.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1533293 - part 2: Rewrite EditorBase::SelectEntireDocument() and its overrides r=m_kato `EditorBase::SelectEntierDocument()` uses `Selection::Extend()` but it's too slow. It should use `Selection::SetStartAndEndInLimiter()` instead. Additionally, `TextEditor::SelectEntierDocument()` shrink the result of `EditorBase::SelectEntierDocument()` with `Selection::Extend()` if there is a `moz-<br>` element. So, `TextEditor::SelectEntinerDocument()` should set its expected selection with a call for saving the runtime cost. Then, we don't need to make `EditorBase::SelectEntierDocument()` as non-pure virtual method. So, this patch makes each its callers call `Selection->SelectAllChildren()` directly. Differential Revision: https://phabricator.services.mozilla.com/D23461
dom/html/nsTextEditorState.cpp
editor/libeditor/EditorBase.cpp
editor/libeditor/EditorBase.h
editor/libeditor/EditorCommands.cpp
editor/libeditor/HTMLEditor.cpp
editor/libeditor/HTMLEditor.h
editor/libeditor/TextEditor.cpp
editor/libeditor/TextEditor.h
editor/nsIEditor.idl
editor/nsIEditorMailSupport.idl
editor/nsIHTMLEditor.idl
editor/spellchecker/nsIInlineSpellChecker.idl
extensions/spellcheck/src/mozInlineSpellChecker.cpp
--- a/dom/html/nsTextEditorState.cpp
+++ b/dom/html/nsTextEditorState.cpp
@@ -2296,17 +2296,19 @@ bool nsTextEditorState::SetValue(const n
 
           if (aFlags & eSetValue_BySetUserInput) {
             // If the caller inserts text as part of user input, for example,
             // autocomplete, we need to replace the text as "insert string"
             // because undo should cancel only this operation (i.e., previous
             // transactions typed by user shouldn't be merged with this).
             // In this case, we need to dispatch "input" event because
             // web apps may need to know the user's operation.
-            DebugOnly<nsresult> rv = textEditor->ReplaceTextAsAction(newValue);
+            RefPtr<nsRange> range;  // See bug 1506439
+            DebugOnly<nsresult> rv =
+                textEditor->ReplaceTextAsAction(newValue, range);
             NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                                  "Failed to set the new value");
           } else if (aFlags & eSetValue_ForXUL) {
             // When setting value of XUL <textbox>, we shouldn't dispatch
             // "input" event.
             suppressInputEventDispatching.Init();
 
             // On XUL <textbox> element, we need to preserve existing undo
--- a/editor/libeditor/EditorBase.cpp
+++ b/editor/libeditor/EditorBase.cpp
@@ -2655,29 +2655,16 @@ nsresult EditorBase::InsertTextIntoTextN
       mComposition->OnTextNodeRemoved();
       static_cast<CompositionTransaction*>(transaction.get())->MarkFixed();
     }
   }
 
   return rv;
 }
 
-nsresult EditorBase::SelectEntireDocument() {
-  MOZ_ASSERT(IsEditActionDataAvailable());
-
-  Element* rootElement = GetRoot();
-  if (!rootElement) {
-    return NS_ERROR_NOT_INITIALIZED;
-  }
-
-  ErrorResult errorResult;
-  SelectionRefPtr()->SelectAllChildren(*rootElement, errorResult);
-  return errorResult.StealNSResult();
-}
-
 nsINode* EditorBase::GetFirstEditableNode(nsINode* aRoot) {
   MOZ_ASSERT(aRoot);
 
   nsIContent* node = GetLeftmostChild(aRoot);
   if (node && !IsEditable(node)) {
     node = GetNextEditableNode(*node);
   }
 
--- a/editor/libeditor/EditorBase.h
+++ b/editor/libeditor/EditorBase.h
@@ -1811,16 +1811,17 @@ class EditorBase : public nsIEditor,
    */
   nsresult GetDocumentCharsetInternal(nsACString& aCharset) const;
 
   /**
    * SelectAllInternal() should be used instead of SelectAll() in editor
    * because SelectAll() creates AutoEditActionSetter but we should avoid
    * to create it as far as possible.
    */
+  MOZ_CAN_RUN_SCRIPT
   virtual nsresult SelectAllInternal();
 
   nsresult DetermineCurrentDirection();
 
   /**
    * FireInputEvent() dispatches an "input" event synchronously or
    * asynchronously if it's not safe to dispatch.
    */
@@ -1855,17 +1856,18 @@ class EditorBase : public nsIEditor,
     eDocumentStateChanged
   };
   nsresult NotifyDocumentListeners(
       TDocumentListenerNotification aNotificationType);
 
   /**
    * Make the given selection span the entire document.
    */
-  virtual nsresult SelectEntireDocument();
+  MOZ_CAN_RUN_SCRIPT
+  virtual nsresult SelectEntireDocument() = 0;
 
   /**
    * Helper method for scrolling the selection into view after
    * an edit operation. aScrollToAnchor should be true if you
    * want to scroll to the point where the selection was started.
    * If false, it attempts to scroll the end of the selection into view.
    *
    * Editor methods *should* call this method instead of the versions
--- a/editor/libeditor/EditorCommands.cpp
+++ b/editor/libeditor/EditorCommands.cpp
@@ -690,17 +690,17 @@ NS_IMETHODIMP
 SelectAllCommand::DoCommand(const char* aCommandName,
                             nsISupports* aCommandRefCon) {
   nsCOMPtr<nsIEditor> editor = do_QueryInterface(aCommandRefCon);
   if (!editor) {
     return NS_ERROR_FAILURE;
   }
   TextEditor* textEditor = editor->AsTextEditor();
   MOZ_ASSERT(textEditor);
-  return textEditor->SelectAll();
+  return MOZ_KnownLive(textEditor)->SelectAll();
 }
 
 NS_IMETHODIMP
 SelectAllCommand::DoCommandParams(const char* aCommandName,
                                   nsICommandParams* aParams,
                                   nsISupports* aCommandRefCon) {
   return DoCommand(aCommandName, aCommandRefCon);
 }
--- a/editor/libeditor/HTMLEditor.cpp
+++ b/editor/libeditor/HTMLEditor.cpp
@@ -3598,33 +3598,41 @@ bool HTMLEditor::IsContainer(nsINode* aN
 
 nsresult HTMLEditor::SelectEntireDocument() {
   MOZ_ASSERT(IsEditActionDataAvailable());
 
   if (!mRules) {
     return NS_ERROR_NULL_POINTER;
   }
 
+  RefPtr<Element> rootElement = GetRoot();
+  if (NS_WARN_IF(!rootElement)) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
   // Protect the edit rules object from dying
   RefPtr<TextEditRules> rules(mRules);
 
-  // is doc empty?
+  // If we're empty, don't select all children because that would select the
+  // bogus node.
   if (rules->DocumentIsEmpty()) {
-    // get editor root node
-    Element* rootElement = GetRoot();
-
-    // if its empty dont select entire doc - that would select the bogus node
     nsresult rv = SelectionRefPtr()->Collapse(rootElement, 0);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-    return NS_OK;
-  }
-
-  return EditorBase::SelectEntireDocument();
+    NS_WARNING_ASSERTION(
+        NS_SUCCEEDED(rv),
+        "Failed to move caret to start of the editor root element");
+    return rv;
+  }
+
+  // Otherwise, select all children.
+  ErrorResult error;
+  SelectionRefPtr()->SelectAllChildren(*rootElement, error);
+  NS_WARNING_ASSERTION(
+      !error.Failed(),
+      "Failed to select all children of the editor root element");
+  return error.StealNSResult();
 }
 
 nsresult HTMLEditor::SelectAllInternal() {
   MOZ_ASSERT(IsEditActionDataAvailable());
 
   CommitComposition();
   if (NS_WARN_IF(Destroyed())) {
     return NS_ERROR_EDITOR_DESTROYED;
--- a/editor/libeditor/HTMLEditor.h
+++ b/editor/libeditor/HTMLEditor.h
@@ -950,16 +950,17 @@ class HTMLEditor final : public TextEdit
   virtual ~HTMLEditor();
 
   /**
    * InsertParagraphSeparatorAsSubAction() inserts a line break if it's
    * HTMLEditor and it's possible.
    */
   nsresult InsertParagraphSeparatorAsSubAction();
 
+  MOZ_CAN_RUN_SCRIPT
   virtual nsresult SelectAllInternal() override;
 
   /**
    * SelectContentInternal() sets Selection to aContentToSelect to
    * aContentToSelect + 1 in parent of aContentToSelect.
    *
    * @param aContentToSelect    The content which should be selected.
    */
@@ -1516,16 +1517,17 @@ class HTMLEditor final : public TextEdit
   nsresult SetHTMLBackgroundColorWithTransaction(const nsAString& aColor);
 
   virtual void InitializeSelectionAncestorLimit(
       nsIContent& aAncestorLimit) override;
 
   /**
    * Make the given selection span the entire document.
    */
+  MOZ_CAN_RUN_SCRIPT
   virtual nsresult SelectEntireDocument() override;
 
   /**
    * Use this to assure that selection is set after attribute nodes when
    * trying to collapse selection at begining of a block node
    * e.g., when setting at beginning of a table cell
    * This will stop at a table, however, since we don't want to
    * "drill down" into nested tables.
--- a/editor/libeditor/TextEditor.cpp
+++ b/editor/libeditor/TextEditor.cpp
@@ -1193,28 +1193,35 @@ nsresult TextEditor::SetTextAsSubAction(
     return NS_OK;
   }
   if (!handled) {
     // Note that do not notify selectionchange caused by selecting all text
     // because it's preparation of our delete implementation so web apps
     // shouldn't receive such selectionchange before the first mutation.
     AutoUpdateViewBatch preventSelectionChangeEvent(*this);
 
+    Element* rootElement = GetRoot();
+    if (NS_WARN_IF(!rootElement)) {
+      return NS_ERROR_FAILURE;
+    }
+
     // We want to select trailing BR node to remove all nodes to replace all,
     // but TextEditor::SelectEntireDocument doesn't select that BR node.
     if (rules->DocumentIsEmpty()) {
-      // if it's empty, don't select entire doc - that would select
-      // the bogus node
-      Element* rootElement = GetRoot();
-      if (NS_WARN_IF(!rootElement)) {
-        return NS_ERROR_FAILURE;
-      }
       rv = SelectionRefPtr()->Collapse(rootElement, 0);
+      NS_WARNING_ASSERTION(
+          NS_SUCCEEDED(rv),
+          "Failed to move caret to start of the editor root element");
     } else {
-      rv = EditorBase::SelectEntireDocument();
+      ErrorResult error;
+      SelectionRefPtr()->SelectAllChildren(*rootElement, error);
+      NS_WARNING_ASSERTION(
+          !error.Failed(),
+          "Failed to select all children of the editor root element");
+      rv = error.StealNSResult();
     }
     if (NS_SUCCEEDED(rv)) {
       rv = ReplaceSelectionAsSubAction(aString);
       NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                            "Failed to replace selection with new string");
     }
   }
   // post-process
@@ -2138,56 +2145,62 @@ void TextEditor::OnEndHandlingTopLevelEd
 
 nsresult TextEditor::SelectEntireDocument() {
   MOZ_ASSERT(IsEditActionDataAvailable());
 
   if (!mRules) {
     return NS_ERROR_NULL_POINTER;
   }
 
+  Element* rootElement = GetRoot();
+  if (NS_WARN_IF(!rootElement)) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
   // Protect the edit rules object from dying
   RefPtr<TextEditRules> rules(mRules);
 
-  // is doc empty?
+  // If we're empty, don't select all children because that would select the
+  // bogus node.
   if (rules->DocumentIsEmpty()) {
-    // get root node
-    Element* rootElement = GetRoot();
-    if (NS_WARN_IF(!rootElement)) {
-      return NS_ERROR_FAILURE;
-    }
-
-    // if it's empty don't select entire doc - that would select the bogus node
-    return SelectionRefPtr()->Collapse(rootElement, 0);
-  }
-
-  SelectionBatcher selectionBatcher(SelectionRefPtr());
-  nsresult rv = EditorBase::SelectEntireDocument();
-  if (NS_WARN_IF(NS_FAILED(rv))) {
+    nsresult rv = SelectionRefPtr()->Collapse(rootElement, 0);
+    NS_WARNING_ASSERTION(
+        NS_SUCCEEDED(rv),
+        "Failed to move caret to start of the editor root element");
     return rv;
   }
 
   // Don't select the trailing BR node if we have one
   nsCOMPtr<nsIContent> childNode;
-  rv = EditorBase::GetEndChildNode(*SelectionRefPtr(),
-                                   getter_AddRefs(childNode));
+  nsresult rv = EditorBase::GetEndChildNode(*SelectionRefPtr(),
+                                            getter_AddRefs(childNode));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
   if (childNode) {
     childNode = childNode->GetPreviousSibling();
   }
 
   if (childNode && TextEditUtils::IsMozBR(childNode)) {
-    int32_t parentOffset;
-    nsINode* parentNode = GetNodeLocation(childNode, &parentOffset);
-
-    return SelectionRefPtr()->Extend(parentNode, parentOffset);
+    ErrorResult error;
+    MOZ_KnownLive(SelectionRefPtr())
+        ->SetStartAndEndInLimiter(RawRangeBoundary(rootElement, 0),
+                                  EditorRawDOMPoint(childNode), error);
+    NS_WARNING_ASSERTION(!error.Failed(),
+                         "Failed to select all children of the editor root "
+                         "element except the moz-<br> element");
+    return error.StealNSResult();
   }
 
-  return NS_OK;
+  ErrorResult error;
+  SelectionRefPtr()->SelectAllChildren(*rootElement, error);
+  NS_WARNING_ASSERTION(
+      !error.Failed(),
+      "Failed to select all children of the editor root element");
+  return error.StealNSResult();
 }
 
 EventTarget* TextEditor::GetDOMEventTarget() { return mEventTarget; }
 
 nsresult TextEditor::SetAttributeOrEquivalent(Element* aElement,
                                               nsAtom* aAttribute,
                                               const nsAString& aValue,
                                               bool aSuppressTransaction) {
--- a/editor/libeditor/TextEditor.h
+++ b/editor/libeditor/TextEditor.h
@@ -168,26 +168,28 @@ class TextEditor : public EditorBase, pu
   void SetMaxTextLength(int32_t aLength) { mMaxTextLength = aLength; }
 
   /**
    * Replace existed string with a string.
    * This is fast path to replace all string when using single line control.
    *
    * @ param aString   the string to be set
    */
+  MOZ_CAN_RUN_SCRIPT
   nsresult SetText(const nsAString& aString);
 
   /**
    * Replace text in aReplaceRange or all text in this editor with aString and
    * treat the change as inserting the string.
    *
    * @param aString             The string to set.
    * @param aReplaceRange       The range to be replaced.
    *                            If nullptr, all contents will be replaced.
    */
+  MOZ_CAN_RUN_SCRIPT
   nsresult ReplaceTextAsAction(const nsAString& aString,
                                nsRange* aReplaceRange = nullptr);
 
   /**
    * InsertLineBreakAsAction() is called when user inputs a line break with
    * Enter or something.
    */
   virtual nsresult InsertLineBreakAsAction();
@@ -360,16 +362,17 @@ class TextEditor : public EditorBase, pu
  protected:  // Shouldn't be used by friend classes
   virtual ~TextEditor();
 
   int32_t WrapWidth() const { return mWrapColumn; }
 
   /**
    * Make the given selection span the entire document.
    */
+  MOZ_CAN_RUN_SCRIPT
   virtual nsresult SelectEntireDocument() override;
 
   /**
    * OnInputText() is called when user inputs text with keyboard or something.
    *
    * @param aStringToInsert     The string to insert.
    */
   nsresult OnInputText(const nsAString& aStringToInsert);
--- a/editor/nsIEditor.idl
+++ b/editor/nsIEditor.idl
@@ -300,16 +300,17 @@ interface nsIEditor  : nsISupports
   /** Can we paste? True if the doc is modifiable, and we have
     * pasteable data in the clipboard.
     */
   boolean canPaste(in long aSelectionType);
 
   /* ------------ Selection methods -------------- */
 
   /** sets the document selection to the entire contents of the document */
+  [can_run_script]
   void selectAll();
 
   /**
    * Collapses selection at start of the document.  If it's an HTML editor,
    * collapses selection at start of current editing host (<body> element if
    * it's in designMode) instead.  If there is a non-editable node before any
    * editable text nodes or inline elements which can have text nodes as their
    * children, collapses selection at start of the editing host.  If there is
--- a/editor/nsIEditorMailSupport.idl
+++ b/editor/nsIEditorMailSupport.idl
@@ -25,11 +25,12 @@ interface nsIEditorMailSupport : nsISupp
   Node insertAsCitedQuotation(in AString aQuotedText,
                               in AString aCitation,
                               in boolean aInsertHTML);
 
   /**
    * Rewrap the selected part of the document, re-quoting if necessary.
    * @param aRespectNewlines  Try to maintain newlines in the original?
    */
+  [can_run_script]
   void rewrap(in boolean aRespectNewlines);
 };
 
--- a/editor/nsIHTMLEditor.idl
+++ b/editor/nsIHTMLEditor.idl
@@ -142,16 +142,17 @@ interface nsIHTMLEditor : nsISupports
   void pasteNoFormatting(in long aSelectionType);
 
   /**
    *  Rebuild the entire document from source HTML
    *  Needed to be able to edit HEAD and other outside-of-BODY content
    *
    *  @param aSourceString   HTML source string of the entire new document
    */
+  [can_run_script]
   void rebuildDocumentFromSource(in AString aSourceString);
 
   /**
     * Insert an element, which may have child nodes, at the selection
     * Used primarily to insert a new element for various insert element dialogs,
     *   but it enforces the HTML 4.0 DTD "CanContain" rules, so it should
     *   be useful for other elements.
     *
--- a/editor/spellchecker/nsIInlineSpellChecker.idl
+++ b/editor/spellchecker/nsIInlineSpellChecker.idl
@@ -20,16 +20,17 @@ interface nsIInlineSpellChecker : nsISup
   void init(in nsIEditor aEditor);
   void cleanup(in boolean aDestroyingFrames);
 
   attribute boolean enableRealTimeSpell;
 
   void spellCheckRange(in Range aSelection);
 
   Range getMisspelledWord(in Node aNode, in long aOffset);
+  [can_run_script]
   void replaceWord(in Node aNode, in long aOffset, in AString aNewword);
   void addWordToDictionary(in AString aWord);
   void removeWordFromDictionary(in AString aWord);
 
   void ignoreWord(in AString aWord);
   void ignoreWords([array, size_is(aCount)] in wstring aWordsToIgnore, in unsigned long aCount);
   void updateCurrentDictionary();
 
--- a/extensions/spellcheck/src/mozInlineSpellChecker.cpp
+++ b/extensions/spellcheck/src/mozInlineSpellChecker.cpp
@@ -864,17 +864,18 @@ mozInlineSpellChecker::ReplaceWord(nsINo
   RefPtr<nsRange> range;
   nsresult res = GetMisspelledWord(aNode, aOffset, getter_AddRefs(range));
   NS_ENSURE_SUCCESS(res, res);
 
   if (!range) {
     return NS_OK;
   }
 
-  DebugOnly<nsresult> rv = mTextEditor->ReplaceTextAsAction(newword, range);
+  RefPtr<TextEditor> textEditor(mTextEditor);
+  DebugOnly<nsresult> rv = textEditor->ReplaceTextAsAction(newword, range);
   NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to insert the new word");
   return NS_OK;
 }
 
 // mozInlineSpellChecker::AddWordToDictionary
 
 NS_IMETHODIMP
 mozInlineSpellChecker::AddWordToDictionary(const nsAString& word) {