Bug 590554 - maxlength in textarea does not prevent newline characters from being inserted; r,a=roc
authorEhsan Akhgari <ehsan@mozilla.com>
Fri, 27 Aug 2010 01:36:09 -0400
changeset 53737 cceac3c8908652f4f1ef63e18b4b8a08502ec1e9
parent 53736 055edd12d6091d93e8100e7017b16a54be1e8540
child 53738 7b2fbb60e12ab524893ff12e1e2d9087ff6b89af
push idunknown
push userunknown
push dateunknown
reviewersroc
bugs590554
milestone2.0b6pre
Bug 590554 - maxlength in textarea does not prevent newline characters from being inserted; r,a=roc
editor/libeditor/text/nsPlaintextEditor.cpp
editor/libeditor/text/nsTextEditRules.cpp
editor/libeditor/text/nsTextEditRules.h
editor/libeditor/text/tests/Makefile.in
editor/libeditor/text/tests/test_bug590554.html
--- a/editor/libeditor/text/nsPlaintextEditor.cpp
+++ b/editor/libeditor/text/nsPlaintextEditor.cpp
@@ -618,17 +618,22 @@ nsPlaintextEditor::GetTextSelectionOffse
       }
       if (editable) {
         PRUint32 length;
         textNode->GetLength(&length);
         totalLength += length;
       }
     }
 #ifdef NS_DEBUG
-    ++nodeCount;
+    // The post content iterator might return the parent node (which is the
+    // editor's root node) as the last item.  Don't count the root node itself
+    // as one of its children!
+    if (!SameCOMIdentity(currentNode, rootNode)) {
+      ++nodeCount;
+    }
 #endif
   }
 
   if (endOffset == -1) {
     NS_ASSERTION(endNode == rootNode, "failed to find the end node");
     NS_ASSERTION(endNodeOffset == nodeCount-1 || endNodeOffset == 0,
                  "invalid end node offset");
     endOffset = endNodeOffset == 0 ? 0 : totalLength;
@@ -857,16 +862,17 @@ NS_IMETHODIMP nsPlaintextEditor::InsertL
   // Batching the selection and moving nodes out from under the caret causes
   // caret turds. Ask the shell to invalidate the caret now to avoid the turds.
   nsCOMPtr<nsIPresShell> shell;
   res = GetPresShell(getter_AddRefs(shell));
   NS_ENSURE_SUCCESS(res, res);
   shell->MaybeInvalidateCaretPosition();
 
   nsTextRulesInfo ruleInfo(nsTextEditRules::kInsertBreak);
+  ruleInfo.maxLength = mMaxTextLength;
   PRBool cancel, handled;
   res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
   NS_ENSURE_SUCCESS(res, res);
   if (!cancel && !handled)
   {
     // get the (collapsed) selection location
     nsCOMPtr<nsIDOMNode> selNode;
     PRInt32 selOffset;
--- a/editor/libeditor/text/nsTextEditRules.cpp
+++ b/editor/libeditor/text/nsTextEditRules.cpp
@@ -307,17 +307,17 @@ nsTextEditRules::WillDoAction(nsISelecti
   *aHandled = PR_FALSE;
 
   // my kingdom for dynamic cast
   nsTextRulesInfo *info = static_cast<nsTextRulesInfo*>(aInfo);
     
   switch (info->action)
   {
     case kInsertBreak:
-      return WillInsertBreak(aSelection, aCancel, aHandled);
+      return WillInsertBreak(aSelection, aCancel, aHandled, info->maxLength);
     case kInsertText:
     case kInsertTextIME:
       return WillInsertText(info->action,
                             aSelection, 
                             aCancel,
                             aHandled, 
                             info->inString,
                             info->outString,
@@ -419,31 +419,47 @@ nsTextEditRules::WillInsert(nsISelection
 
 nsresult
 nsTextEditRules::DidInsert(nsISelection *aSelection, nsresult aResult)
 {
   return NS_OK;
 }
 
 nsresult
-nsTextEditRules::WillInsertBreak(nsISelection *aSelection, PRBool *aCancel, PRBool *aHandled)
+nsTextEditRules::WillInsertBreak(nsISelection *aSelection,
+                                 PRBool *aCancel,
+                                 PRBool *aHandled,
+                                 PRInt32 aMaxLength)
 {
   if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
   CANCEL_OPERATION_IF_READONLY_OR_DISABLED
   *aHandled = PR_FALSE;
   if (IsSingleLineEditor()) {
     *aCancel = PR_TRUE;
   }
   else 
   {
+    // handle docs with a max length
+    // NOTE, this function copies inString into outString for us.
+    NS_NAMED_LITERAL_STRING(inString, "\n");
+    nsAutoString outString;
+    PRBool didTruncate;
+    nsresult res = TruncateInsertionIfNeeded(aSelection, &inString, &outString,
+                                             aMaxLength, &didTruncate);
+    NS_ENSURE_SUCCESS(res, res);
+    if (didTruncate) {
+      *aCancel = PR_TRUE;
+      return NS_OK;
+    }
+
     *aCancel = PR_FALSE;
 
     // if the selection isn't collapsed, delete it.
     PRBool bCollapsed;
-    nsresult res = aSelection->GetIsCollapsed(&bCollapsed);
+    res = aSelection->GetIsCollapsed(&bCollapsed);
     NS_ENSURE_SUCCESS(res, res);
     if (!bCollapsed)
     {
       res = mEditor->DeleteSelection(nsIEditor::eNone);
       NS_ENSURE_SUCCESS(res, res);
     }
 
     res = WillInsert(aSelection, aCancel);
@@ -637,17 +653,17 @@ nsTextEditRules::WillInsertText(PRInt32 
   }
   
   // initialize out param
   *aCancel = PR_FALSE;
   *aHandled = PR_TRUE;
 
   // handle docs with a max length
   // NOTE, this function copies inString into outString for us.
-  nsresult res = TruncateInsertionIfNeeded(aSelection, inString, outString, aMaxLength);
+  nsresult res = TruncateInsertionIfNeeded(aSelection, inString, outString, aMaxLength, nsnull);
   NS_ENSURE_SUCCESS(res, res);
   
   PRUint32 start = 0;
   PRUint32 end = 0;  
 
   // handle password field docs
   if (IsPasswordEditor())
   {
@@ -1251,22 +1267,26 @@ nsTextEditRules::CreateBogusNodeIfNeeded
   return res;
 }
 
 
 nsresult
 nsTextEditRules::TruncateInsertionIfNeeded(nsISelection *aSelection, 
                                            const nsAString  *aInString,
                                            nsAString  *aOutString,
-                                           PRInt32          aMaxLength)
+                                           PRInt32          aMaxLength,
+                                           PRBool *aTruncated)
 {
   if (!aSelection || !aInString || !aOutString) {return NS_ERROR_NULL_POINTER;}
   
   nsresult res = NS_OK;
   *aOutString = *aInString;
+  if (aTruncated) {
+    *aTruncated = PR_FALSE;
+  }
   
   if ((-1 != aMaxLength) && IsPlaintextEditor() && !mEditor->IsIMEComposing() )
   {
     // Get the current text length.
     // Get the length of inString.
     // Get the length of the selection.
     //   If selection is collapsed, it is length 0.
     //   Subtract the length of the selection from the len(doc) 
@@ -1289,23 +1309,29 @@ nsTextEditRules::TruncateInsertionIfNeed
     res = mEditor->GetIMEBufferLength(&oldCompStrLength);
     if (NS_FAILED(res)) { return res; }
 
     const PRInt32 selectionLength = end - start;
     const PRInt32 resultingDocLength = docLength - selectionLength - oldCompStrLength;
     if (resultingDocLength >= aMaxLength)
     {
       aOutString->Truncate();
+      if (aTruncated) {
+        *aTruncated = PR_TRUE;
+      }
     }
     else
     {
       PRInt32 inCount = aOutString->Length();
       if (inCount + resultingDocLength > aMaxLength)
       {
         aOutString->Truncate(aMaxLength - resultingDocLength);
+        if (aTruncated) {
+          *aTruncated = PR_TRUE;
+        }
       }
     }
   }
   return res;
 }
 
 nsresult
 nsTextEditRules::ResetIMETextPWBuf()
--- a/editor/libeditor/text/nsTextEditRules.h
+++ b/editor/libeditor/text/nsTextEditRules.h
@@ -155,17 +155,18 @@ protected:
                             PRBool          *aCancel,
                             PRBool          *aHandled,
                             const nsAString *inString,
                             nsAString       *outString,
                             PRInt32          aMaxLength);
   nsresult DidInsertText(nsISelection *aSelection, nsresult aResult);
   nsresult GetTopEnclosingPre(nsIDOMNode *aNode, nsIDOMNode** aOutPreNode);
 
-  nsresult WillInsertBreak(nsISelection *aSelection, PRBool *aCancel, PRBool *aHandled);
+  nsresult WillInsertBreak(nsISelection *aSelection, PRBool *aCancel,
+                           PRBool *aHandled, PRInt32 aMaxLength);
   nsresult DidInsertBreak(nsISelection *aSelection, nsresult aResult);
 
   nsresult WillInsert(nsISelection *aSelection, PRBool *aCancel);
   nsresult DidInsert(nsISelection *aSelection, nsresult aResult);
 
   nsresult WillDeleteSelection(nsISelection *aSelection, 
                                nsIEditor::EDirection aCollapsedAction, 
                                PRBool *aCancel,
@@ -213,17 +214,18 @@ protected:
  /** creates a bogus text node if the document has no editable content */
   nsresult CreateBogusNodeIfNeeded(nsISelection *aSelection);
 
   /** returns a truncated insertion string if insertion would place us
       over aMaxLength */
   nsresult TruncateInsertionIfNeeded(nsISelection             *aSelection, 
                                      const nsAString          *aInString,
                                      nsAString                *aOutString,
-                                     PRInt32                   aMaxLength);
+                                     PRInt32                   aMaxLength,
+                                     PRBool                   *aTruncated);
 
   /** Remove IME composition text from password buffer */
   nsresult RemoveIMETextFromPWBuf(PRUint32 &aStart, nsAString *aIMEString);
 
   nsresult CreateMozBR(nsIDOMNode *inParent, PRInt32 inOffset, nsCOMPtr<nsIDOMNode> *outBRNode);
 
   nsresult CheckBidiLevelForDeletion(nsISelection         *aSelection,
                                      nsIDOMNode           *aSelNode, 
--- a/editor/libeditor/text/tests/Makefile.in
+++ b/editor/libeditor/text/tests/Makefile.in
@@ -42,16 +42,17 @@ VPATH		= @srcdir@
 relativesrcdir  = editor/libeditor/text/tests
 
 include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/config/rules.mk
 
 _TEST_FILES = \
 		test_bug471722.html \
 		test_bug569988.html \
+		test_bug590554.html \
 		$(NULL)
 
 # disables the key handling test on gtk2 because gtk2 overrides some key events
 # on our editor, and the combinations depend on the system.
 ifneq ($(MOZ_WIDGET_TOOLKIT),gtk2)
 _TEST_FILES += \
 		test_texteditor_keyevent_handling.html \
 		$(NULL)
new file mode 100644
--- /dev/null
+++ b/editor/libeditor/text/tests/test_bug590554.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=590554
+-->
+
+<head>
+  <title>Test for Bug 590554</title>
+  <script type="application/javascript" src="/MochiKit/packed.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>  
+</head>
+
+<body>
+
+  <script type="application/javascript">
+
+    /** Test for Bug 590554 **/
+
+    SimpleTest.waitForExplicitFinish();
+
+    SimpleTest.waitForFocus(function() {
+      var t = document.querySelector("textarea");
+      t.focus();
+      synthesizeKey("VK_ENTER", {});
+      is(t.value, "\n", "Pressing enter should work the first time");
+      synthesizeKey("VK_ENTER", {});
+      is(t.value, "\n", "Pressing enter should not work the second time");
+      SimpleTest.finish();
+    });
+
+  </script>
+
+  <textarea maxlength="1"></textarea>
+</body>
+</html>