Bug 596501 TextEditRules::WillOutputText() should handle itself if it can return only the text of first text node r=m_kato
authorMasayuki Nakano <masayuki@d-toybox.com>
Fri, 16 Jun 2017 19:08:10 +0900
changeset 416082 36a05246c18c3ed390294ff72135e7b6bfe44be3
parent 416081 e69e9befe6f3bc909146c9eb9478e7ba60c427c9
child 416083 8b06f81e24a8dcc3c59fc8b950f8ad10c8a51bf4
push id1517
push userjlorenzo@mozilla.com
push dateThu, 14 Sep 2017 16:50:54 +0000
treeherdermozilla-release@3b41fd564418 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersm_kato
bugs596501
milestone56.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 596501 TextEditRules::WillOutputText() should handle itself if it can return only the text of first text node r=m_kato TextEditor::OutputToString() uses DocumentEncoder if TextEditRules::WillOutputText() doesn't handle it. However, TextEditRules::WillOutputText() doesn't handle it even if it's really simple case of <input type="text"> and <textarea>. This patch makes TextEditRules::WillOutputText() handle it if DOM tree in the editor root element is expected and there is no special flag which requires complicated handling. MozReview-Commit-ID: 3HvdTAWRpw0
editor/libeditor/TextEditRules.cpp
editor/libeditor/TextEditRules.h
editor/libeditor/TextEditor.cpp
--- a/editor/libeditor/TextEditRules.cpp
+++ b/editor/libeditor/TextEditRules.cpp
@@ -20,16 +20,17 @@
 #include "nsCRT.h"
 #include "nsCRTGlue.h"
 #include "nsComponentManagerUtils.h"
 #include "nsContentUtils.h"
 #include "nsDebug.h"
 #include "nsError.h"
 #include "nsGkAtoms.h"
 #include "nsIContent.h"
+#include "nsIDocumentEncoder.h"
 #include "nsIDOMDocument.h"
 #include "nsIDOMNode.h"
 #include "nsIDOMNodeFilter.h"
 #include "nsNameSpaceManager.h"
 #include "nsINode.h"
 #include "nsIPlaintextEditor.h"
 #include "nsISupportsBase.h"
 #include "nsLiteralString.h"
@@ -291,17 +292,17 @@ TextEditRules::WillDoAction(Selection* a
     case EditAction::redo:
       return WillRedo(aSelection, aCancel, aHandled);
     case EditAction::setTextProperty:
       return WillSetTextProperty(aSelection, aCancel, aHandled);
     case EditAction::removeTextProperty:
       return WillRemoveTextProperty(aSelection, aCancel, aHandled);
     case EditAction::outputText:
       return WillOutputText(aSelection, info->outputFormat, info->outString,
-                            aCancel, aHandled);
+                            info->flags, aCancel, aHandled);
     case EditAction::insertElement:
       // i had thought this would be html rules only.  but we put pre elements
       // into plaintext mail when doing quoting for reply!  doh!
       WillInsert(*aSelection, aCancel);
       return NS_OK;
     default:
       return NS_ERROR_FAILURE;
   }
@@ -1181,39 +1182,124 @@ TextEditRules::DidRedo(Selection* aSelec
   }
   return NS_OK;
 }
 
 nsresult
 TextEditRules::WillOutputText(Selection* aSelection,
                               const nsAString* aOutputFormat,
                               nsAString* aOutString,
+                              uint32_t aFlags,
                               bool* aCancel,
                               bool* aHandled)
 {
   // null selection ok
-  if (!aOutString || !aOutputFormat || !aCancel || !aHandled) {
+  if (NS_WARN_IF(!aOutString) || NS_WARN_IF(!aOutputFormat) ||
+      NS_WARN_IF(!aCancel) || NS_WARN_IF(!aHandled)) {
     return NS_ERROR_NULL_POINTER;
   }
 
   // initialize out param
   *aCancel = false;
   *aHandled = false;
 
-  if (aOutputFormat->LowerCaseEqualsLiteral("text/plain")) {
-    // Only use these rules for plain text output.
-    if (IsPasswordEditor()) {
-      *aOutString = mPasswordText;
-      *aHandled = true;
-    } else if (mBogusNode) {
-      // This means there's no content, so output null string.
-      aOutString->Truncate();
-      *aHandled = true;
-    }
+  if (!aOutputFormat->LowerCaseEqualsLiteral("text/plain")) {
+    return NS_OK;
+  }
+
+  // XXX Looks like that even if it's password field, we need to use the
+  //     expensive path if the caller requests some complicated handling.
+  //     However, changing the behavior for password field might cause
+  //     security issue.  So, be careful when you touch here.
+  if (IsPasswordEditor()) {
+    *aOutString = mPasswordText;
+    *aHandled = true;
+    return NS_OK;
+  }
+
+  // If there is a bogus node, there's no content.  So output empty string.
+  if (mBogusNode) {
+    aOutString->Truncate();
+    *aHandled = true;
+    return NS_OK;
+  }
+
+  // If it's necessary to check selection range or the editor wraps hard,
+  // we need some complicated handling.  In such case, we need to use the
+  // expensive path.
+  // XXX Anything else what we cannot return plain text simply?
+  if (aFlags & nsIDocumentEncoder::OutputSelectionOnly ||
+      aFlags & nsIDocumentEncoder::OutputWrap) {
+    return NS_OK;
+  }
+
+  // If it's neither <input type="text"> nor <textarea>, e.g., an HTML editor
+  // which is in plaintext mode (e.g., plaintext email composer on Thunderbird),
+  // it should be handled by the expensive path.
+  if (NS_WARN_IF(!mTextEditor) || mTextEditor->AsHTMLEditor()) {
+    return NS_OK;
+  }
+
+  RefPtr<Element> root = mTextEditor->GetRoot();
+  if (!root) { // Don't warn it, this is possible, e.g., 997805.html
+    aOutString->Truncate();
+    *aHandled = true;
+    return NS_OK;
   }
+
+  nsIContent* firstChild = root->GetFirstChild();
+  if (!firstChild) {
+    aOutString->Truncate();
+    *aHandled = true;
+    return NS_OK;
+  }
+
+  // If it's an <input type="text"> element, the DOM tree should be:
+  // <div class="anonymous-div">
+  //   #text
+  // </div>
+  //
+  // If it's a <textarea> element, the DOM tree should be:
+  // <div class="anonymous-div">
+  //   #text (if there is)
+  //   <br type="_moz">
+  //   <scrollbar orient="horizontal">
+  //   ...
+  // </div>
+
+  Text* text = firstChild->GetAsText();
+  nsIContent* firstChildExceptText =
+    text ? firstChild->GetNextSibling() : firstChild;
+  // If the DOM tree is unexpected, fall back to the expensive path.
+  bool isInput = IsSingleLineEditor();
+  bool isTextarea = !isInput;
+  if (NS_WARN_IF(isInput && firstChildExceptText) ||
+      NS_WARN_IF(isTextarea && !firstChildExceptText) ||
+      NS_WARN_IF(isTextarea &&
+                 !TextEditUtils::IsMozBR(firstChildExceptText) &&
+                 !firstChildExceptText->IsXULElement(nsGkAtoms::scrollbar))) {
+    return NS_OK;
+  }
+
+  // If there is no text node in the expected DOM tree, we can say that it's
+  // just empty.
+  if (!text) {
+    aOutString->Truncate();
+    *aHandled = true;
+    return NS_OK;
+  }
+
+  // Otherwise, the text is the value.
+  nsresult rv = text->GetData(*aOutString);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    // Fall back to the expensive path if it fails.
+    return NS_OK;
+  }
+
+  *aHandled = true;
   return NS_OK;
 }
 
 nsresult
 TextEditRules::DidOutputText(Selection* aSelection,
                              nsresult aResult)
 {
   return NS_OK;
--- a/editor/libeditor/TextEditRules.h
+++ b/editor/libeditor/TextEditRules.h
@@ -166,16 +166,17 @@ protected:
    * @param aInFormat  The format requested for the output, a MIME type.
    * @param aOutText   The string to use for output, if aCancel is set to true.
    * @param aOutCancel If set to true, the caller should cancel the operation
    *                   and use aOutText as the result.
    */
   nsresult WillOutputText(Selection* aSelection,
                           const nsAString* aInFormat,
                           nsAString* aOutText,
+                          uint32_t aFlags,
                           bool* aOutCancel,
                           bool* aHandled);
 
   nsresult DidOutputText(Selection* aSelection, nsresult aResult);
 
   /**
    * Check for and replace a redundant trailing break.
    */
@@ -256,40 +257,46 @@ protected:
   nsCOMPtr<nsITimer> mTimer;
   uint32_t mLastStart;
   uint32_t mLastLength;
 
   // friends
   friend class AutoLockRulesSniffing;
 };
 
+// TODO: This class (almost struct, though) is ugly and its size isn't
+//       optimized.  Should be refined later.
 class TextRulesInfo final : public RulesInfo
 {
 public:
   explicit TextRulesInfo(EditAction aAction)
     : RulesInfo(aAction)
     , inString(nullptr)
     , outString(nullptr)
     , outputFormat(nullptr)
     , maxLength(-1)
+    , flags(0)
     , collapsedAction(nsIEditor::eNext)
     , stripWrappers(nsIEditor::eStrip)
     , bOrdered(false)
     , entireList(false)
     , bulletType(nullptr)
     , alignType(nullptr)
     , blockType(nullptr)
   {}
 
   // EditAction::insertText / EditAction::insertIMEText
   const nsAString* inString;
   nsAString* outString;
   const nsAString* outputFormat;
   int32_t maxLength;
 
+  // EditAction::outputText
+  uint32_t flags;
+
   // EditAction::deleteSelection
   nsIEditor::EDirection collapsedAction;
   nsIEditor::EStripWrappers stripWrappers;
 
   // EditAction::removeList
   bool bOrdered;
 
   // EditAction::makeList
--- a/editor/libeditor/TextEditor.cpp
+++ b/editor/libeditor/TextEditor.cpp
@@ -1308,16 +1308,17 @@ TextEditor::OutputToString(const nsAStri
                            nsAString& aOutputString)
 {
   // Protect the edit rules object from dying
   nsCOMPtr<nsIEditRules> rules(mRules);
 
   nsString resultString;
   TextRulesInfo ruleInfo(EditAction::outputText);
   ruleInfo.outString = &resultString;
+  ruleInfo.flags = aFlags;
   // XXX Struct should store a nsAReadable*
   nsAutoString str(aFormatType);
   ruleInfo.outputFormat = &str;
   bool cancel, handled;
   nsresult rv = rules->WillDoAction(nullptr, &ruleInfo, &cancel, &handled);
   if (cancel || NS_FAILED(rv)) {
     return rv;
   }