Bug 364914 - Do not spell check the contents of a textarea until it's focused for the first time; r=ehsan
authorQuentin Headen <qheaden@phaseshiftsoftware.com>
Sun, 16 Oct 2011 22:06:24 -0400
changeset 79477 1906abbc9caf7a224cfb6db062626d269e3a40da
parent 79476 63ad1495f24f068f17f63d26b8b00023e265767e
child 79478 783daf190579265296e7e87d428dfb48858fd24b
push id506
push userclegnitto@mozilla.com
push dateWed, 09 Nov 2011 02:03:18 +0000
treeherdermozilla-aurora@63587fc7bb93 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersehsan
bugs364914
milestone10.0a1
Bug 364914 - Do not spell check the contents of a textarea until it's focused for the first time; r=ehsan
browser/base/content/test/test_contextmenu.html
content/html/content/src/nsTextEditorState.cpp
editor/composer/test/test_bug338427.html
editor/idl/nsIPlaintextEditor.idl
editor/libeditor/base/nsEditor.h
editor/libeditor/base/nsEditorEventListener.cpp
editor/libeditor/text/tests/test_bug596333.html
editor/libeditor/text/tests/test_bug636465.xul
layout/reftests/editor/reftest.list
layout/reftests/editor/spellcheck-input-nofocus-ref.html
layout/reftests/editor/spellcheck-textarea-focused.html
layout/reftests/editor/spellcheck-textarea-nofocus-ref.html
layout/reftests/editor/spellcheck-textarea-nofocus.html
layout/reftests/editor/spellcheck-textarea-ref.html
--- a/browser/base/content/test/test_contextmenu.html
+++ b/browser/base/content/test/test_contextmenu.html
@@ -18,26 +18,43 @@ Browser context menu tests.
 
 /** Test for Login Manager: multiple login autocomplete. **/
 
 netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 
-function openContextMenuFor(element, shiftkey) {
+function openContextMenuFor(element, shiftkey, shouldWaitForFocus) {
     // Context menu should be closed before we open it again.
     is(contextMenu.state, "closed", "checking if popup is closed");
 
-    if (lastElement)
-      lastElement.blur();
-    element.focus();
-    lastElement = element;
-    var eventDetails = { type : "contextmenu", button : 2, shiftKey : shiftkey };
-    synthesizeMouse(element, 2, 2, eventDetails, element.ownerDocument.defaultView);
+    //Some elements need time to focus and spellcheck before any tests are
+    //run on them.
+    if(shouldWaitForFocus)
+    {
+      if (lastElement)
+        lastElement.blur();
+      element.focus();
+      
+      SimpleTest.executeSoon(function() {
+        lastElement = element;
+        var eventDetails = { type : "contextmenu", button : 2, shiftKey : shiftkey };
+        synthesizeMouse(element, 2, 2, eventDetails, element.ownerDocument.defaultView);
+      });
+    }
+    else
+    {
+      if (lastElement)
+          lastElement.blur();
+      element.focus();
+      lastElement = element;
+      var eventDetails = { type : "contextmenu", button : 2, shiftKey : shiftkey };
+      synthesizeMouse(element, 2, 2, eventDetails, element.ownerDocument.defaultView);
+    }
 }
 
 function closeContextMenu() {
     contextMenu.hidePopup();
 }
 
 function executeCopyCommand(command, expectedValue)
 {
@@ -416,17 +433,17 @@ function runTest(testNum) {
                                "context-viewframesource",   true,
                                "context-viewframeinfo",     true], null,
                           "---",                  null,
                           "context-viewsource",   true,
                           "context-viewinfo",     true,
                           "---",                  null,
                           "context-inspect",      true]);
         closeContextMenu();
-        openContextMenuFor(textarea); // Invoke context menu for next test.
+        openContextMenuFor(textarea, false, true); // Invoke context menu for next test, but wait for the spellcheck.
         break;
 
     case 12:
         // Context menu for textarea
         checkContextMenu(["*chubbiness",         true, // spelling suggestion
                           "spell-add-to-dictionary", true,
                           "---",                 null,
                           "context-undo",        false,
--- a/content/html/content/src/nsTextEditorState.cpp
+++ b/content/html/content/src/nsTextEditorState.cpp
@@ -1158,16 +1158,20 @@ nsTextEditorState::PrepareEditor(const n
     editorFlags |= nsIPlaintextEditor::eEditorPasswordMask;
 
   // All nsTextControlFrames are widgets
   editorFlags |= nsIPlaintextEditor::eEditorWidgetMask;
 
   // Use async reflow and painting for text widgets to improve
   // performance.
   editorFlags |= nsIPlaintextEditor::eEditorUseAsyncUpdatesMask;
+  
+  // Spell check is diabled at creation time. It is enabled once
+  // the editor comes into focus.
+  editorFlags |= nsIPlaintextEditor::eEditorSkipSpellCheck;
 
   bool shouldInitializeEditor = false;
   nsCOMPtr<nsIEditor> newEditor; // the editor that we might create
   nsresult rv;
   if (!mEditor) {
     shouldInitializeEditor = PR_TRUE;
 
     // Create an editor
--- a/editor/composer/test/test_bug338427.html
+++ b/editor/composer/test/test_bug338427.html
@@ -7,17 +7,17 @@ https://bugzilla.mozilla.org/show_bug.cg
   <title>Test for Bug 338427</title>
   <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=338427">Mozilla Bug 338427</a>
 <p id="display"></p>
 <div id="content">
-<textarea id="editor" lang="testing-XX"></textarea>
+<textarea id="editor" lang="testing-XX" spellcheck="true"></textarea>
 
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 /** Test for Bug 338427 **/
 function init() {
     var textarea = document.getElementById("editor");
--- a/editor/idl/nsIPlaintextEditor.idl
+++ b/editor/idl/nsIPlaintextEditor.idl
@@ -72,16 +72,18 @@ interface nsIPlaintextEditor : nsISuppor
   // see bug 530367 for the detail.
   const long eEditorDontEchoPassword    = 0x1000;
   // when this flag is set, the internal direction of the editor is RTL.
   // if neither of the direction flags are set, the direction is determined
   // from the text control's content node.
   const long eEditorRightToLeft         = 0x2000;
   // when this flag is set, the internal direction of the editor is LTR.
   const long eEditorLeftToRight         = 0x4000;
+  // when this flag is set, the editor's text content is not spell checked.
+  const long eEditorSkipSpellCheck      = 0x8000;
 
   /*
    * The valid values for newlines handling.
    * Can't change the values unless we remove
    * use of the pref.
    */
   const long eNewlinesPasteIntact                = 0;
   const long eNewlinesPasteToFirst               = 1;
--- a/editor/libeditor/base/nsEditor.h
+++ b/editor/libeditor/base/nsEditor.h
@@ -391,18 +391,18 @@ protected:
    */
   bool GetDesiredSpellCheckState();
 
   nsKeyEvent* GetNativeKeyEvent(nsIDOMKeyEvent* aDOMKeyEvent);
 
   bool CanEnableSpellCheck()
   {
     // Check for password/readonly/disabled, which are not spellchecked
-    // regardless of DOM
-    return !IsPasswordEditor() && !IsReadonly() && !IsDisabled();
+    // regardless of DOM. Also, check to see if spell check should be skipped or not.
+    return !IsPasswordEditor() && !IsReadonly() && !IsDisabled() && !ShouldSkipSpellCheck();
   }
 
 public:
 
   /** All editor operations which alter the doc should be prefaced
    *  with a call to StartOperation, naming the action and direction */
   NS_IMETHOD StartOperation(PRInt32 opID, nsIEditor::EDirection aDirection);
 
@@ -690,16 +690,21 @@ public:
   {
     return (mFlags & nsIPlaintextEditor::eEditorAllowInteraction) != 0;
   }
 
   bool DontEchoPassword() const
   {
     return (mFlags & nsIPlaintextEditor::eEditorDontEchoPassword) != 0;
   }
+  
+  PRBool ShouldSkipSpellCheck() const
+  {
+    return (mFlags & nsIPlaintextEditor::eEditorSkipSpellCheck) != 0;
+  }
 
   bool IsTabbable() const
   {
     return IsSingleLineEditor() || IsPasswordEditor() || IsFormWidget() ||
            IsInteractionAllowed();
   }
 
   // Get the focused content, if we're focused.  Returns null otherwise.
--- a/editor/libeditor/base/nsEditorEventListener.cpp
+++ b/editor/libeditor/base/nsEditorEventListener.cpp
@@ -900,16 +900,27 @@ nsEditorEventListener::Focus(nsIDOMEvent
 {
   NS_ENSURE_TRUE(mEditor, NS_ERROR_NOT_AVAILABLE);
   NS_ENSURE_ARG(aEvent);
 
   // Don't turn on selection and caret when the editor is disabled.
   if (mEditor->IsDisabled()) {
     return NS_OK;
   }
+  
+  // If the spell check skip flag is still enabled from creation time,
+  // disable it because focused editors are allowed to spell check.
+  PRUint32 currentFlags = 0;
+  mEditor->GetFlags(&currentFlags);
+  if(currentFlags & nsIPlaintextEditor::eEditorSkipSpellCheck)
+  {
+    currentFlags ^= nsIPlaintextEditor::eEditorSkipSpellCheck;
+    mEditor->SetFlags(currentFlags);
+  }
+  
 
   nsCOMPtr<nsIDOMEventTarget> target;
   aEvent->GetTarget(getter_AddRefs(target));
   nsCOMPtr<nsINode> node = do_QueryInterface(target);
   NS_ENSURE_TRUE(node, NS_ERROR_UNEXPECTED);
 
   // If the traget is a document node but it's not editable, we should ignore
   // it because actual focused element's event is going to come.
--- a/editor/libeditor/text/tests/test_bug596333.html
+++ b/editor/libeditor/text/tests/test_bug596333.html
@@ -59,57 +59,67 @@ function paste(str) {
   var trans = Cc["@mozilla.org/widget/transferable;1"].createInstance(Ci.nsITransferable);
   var s = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString);
   s.data = str;
   trans.setTransferData("text/unicode", s, str.length * 2);
 
   getEditor().pasteTransferable(trans);
 }
 
-function runTest() {
-  gMisspeltWords = ["haz", "cheezburger"];
-  is(isSpellingCheckOk(), true, "All misspellings before editing are accounted for.");
-
+function runOnFocus() {
   var edit = document.getElementById("edit");
-  append(" becaz I'm a lolcat!");
+  edit.removeEventListener("focus", runOnFocus, false);
+  
   SimpleTest.executeSoon(function() {
-    gMisspeltWords.push("becaz");
-    gMisspeltWords.push("lolcat");
-    is(isSpellingCheckOk(), true, "All misspellings after typing are accounted for.");
+    gMisspeltWords = ["haz", "cheezburger"];
+    is(isSpellingCheckOk(), true, "All misspellings before editing are accounted for.");
+    append(" becaz I'm a lolcat!");
+    SimpleTest.executeSoon(function() {
+      gMisspeltWords.push("becaz");
+      gMisspeltWords.push("lolcat");
+      is(isSpellingCheckOk(), true, "All misspellings after typing are accounted for.");
 
-    // Now, type an invalid word, and instead of hitting "space" at the end, just blur
-    // the textarea and see if the spell check after the blur event catches it.
-    append(" workd");
-    edit.blur();
-    SimpleTest.executeSoon(function() {
-      gMisspeltWords.push("workd");
-      is(isSpellingCheckOk(), true, "All misspellings after blur are accounted for.");
+      // Now, type an invalid word, and instead of hitting "space" at the end, just blur
+      // the textarea and see if the spell check after the blur event catches it.
+      append(" workd");
+      edit.blur();
+      SimpleTest.executeSoon(function() {
+        gMisspeltWords.push("workd");
+        is(isSpellingCheckOk(), true, "All misspellings after blur are accounted for.");
 
-      // Also, test the case when we're entering the first word in a textarea
-      gMisspeltWords = ["workd"];
-      edit.value = "";
-      append("workd ");
-      SimpleTest.executeSoon(function() {
-        is(isSpellingCheckOk(), true, "Misspelling in the first entered word is accounted for.");
-
-        // Make sure that pasting would also trigger spell checking for the previous word
+        // Also, test the case when we're entering the first word in a textarea
         gMisspeltWords = ["workd"];
         edit.value = "";
-        append("workd");
-        paste("           x");
+        append("workd ");
         SimpleTest.executeSoon(function() {
-          is(isSpellingCheckOk(), true, "Misspelling is accounted for after pasting.");
+          is(isSpellingCheckOk(), true, "Misspelling in the first entered word is accounted for.");
 
-          SimpleTest.finish();
+          // Make sure that pasting would also trigger spell checking for the previous word
+          gMisspeltWords = ["workd"];
+          edit.value = "";
+          append("workd");
+          paste("           x");
+          SimpleTest.executeSoon(function() {
+            is(isSpellingCheckOk(), true, "Misspelling is accounted for after pasting.");
+
+            SimpleTest.finish();
+          });
         });
       });
     });
   });
 }
 
+function runTest()
+{
+  var edit = document.getElementById("edit");
+  edit.addEventListener("focus", runOnFocus, false);
+  edit.focus();
+}
+
 function isSpellingCheckOk() {
   netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
 
   var sel = getSpellCheckSelection();
   var numWords = sel.rangeCount;
 
   is(numWords, gMisspeltWords.length, "Correct number of misspellings and words.");
 
--- a/editor/libeditor/text/tests/test_bug636465.xul
+++ b/editor/libeditor/text/tests/test_bug636465.xul
@@ -25,26 +25,30 @@ https://bugzilla.mozilla.org/show_bug.cg
   </body>
   <textbox id="x" value="foobarbaz" spellcheck="true"/>
   <script class="testbody" type="application/javascript">
   <![CDATA[
 
   SimpleTest.waitForExplicitFinish();
 
   function runTest() {
+    var x = document.getElementById("x");
+    x.focus();
     setTimeout(function(){
+      x.blur();
       setTimeout(function(){
-        var x = document.getElementById("x");
         var spellCheckTrue = snapshotWindow(window);
         x.setAttribute("spellcheck", "false");
         setTimeout(function(){
           setTimeout(function(){
             var spellCheckFalse = snapshotWindow(window);
             x.setAttribute("spellcheck", "true");
+            x.focus();
             setTimeout(function(){
+              x.blur();
               setTimeout(function(){
                 var spellCheckTrueAgain = snapshotWindow(window);
                 x.removeAttribute("spellcheck");
                 setTimeout(function(){
                   setTimeout(function(){
                     var spellCheckNone = snapshotWindow(window);
                     var after = snapshotWindow(window);
                     ok(compareSnapshots(spellCheckTrue, spellCheckFalse, false)[0],
--- a/layout/reftests/editor/reftest.list
+++ b/layout/reftests/editor/reftest.list
@@ -13,28 +13,42 @@ include xul/reftest.list
 == passwd-1.html passwd-ref.html
 != passwd-2.html passwd-ref.html
 == passwd-3.html passwd-ref.html
 needs-focus == passwd-4.html passwd-ref.html
 == emptypasswd-1.html emptypasswd-ref.html
 == emptypasswd-2.html emptypasswd-ref.html
 == caret_on_positioned.html caret_on_positioned-ref.html
 fails-if(Android) != spellcheck-input-disabled.html spellcheck-input-ref.html
-== spellcheck-input-attr-before.html spellcheck-input-ref.html
-== spellcheck-input-attr-after.html spellcheck-input-ref.html
-== spellcheck-input-attr-inherit.html spellcheck-input-ref.html
-== spellcheck-input-attr-dynamic.html spellcheck-input-ref.html
-== spellcheck-input-attr-dynamic-inherit.html spellcheck-input-ref.html
-== spellcheck-input-property-dynamic.html spellcheck-input-ref.html
-== spellcheck-input-property-dynamic-inherit.html spellcheck-input-ref.html
-== spellcheck-input-attr-dynamic-override.html spellcheck-input-ref.html
-== spellcheck-input-attr-dynamic-override-inherit.html spellcheck-input-ref.html
-== spellcheck-input-property-dynamic-override.html spellcheck-input-ref.html
-== spellcheck-input-property-dynamic-override-inherit.html spellcheck-input-ref.html
-== spellcheck-textarea-attr.html spellcheck-textarea-ref.html
+== spellcheck-input-attr-before.html spellcheck-input-nofocus-ref.html
+!= spellcheck-input-attr-before.html spellcheck-input-ref.html
+== spellcheck-input-attr-after.html spellcheck-input-nofocus-ref.html
+!= spellcheck-input-attr-after.html spellcheck-input-ref.html
+== spellcheck-input-attr-inherit.html spellcheck-input-nofocus-ref.html
+!= spellcheck-input-attr-inherit.html spellcheck-input-ref.html
+== spellcheck-input-attr-dynamic.html spellcheck-input-nofocus-ref.html
+!= spellcheck-input-attr-dynamic.html spellcheck-input-ref.html
+== spellcheck-input-attr-dynamic-inherit.html spellcheck-input-nofocus-ref.html
+!= spellcheck-input-attr-dynamic-inherit.html spellcheck-input-ref.html
+== spellcheck-input-property-dynamic.html spellcheck-input-nofocus-ref.html
+!= spellcheck-input-property-dynamic.html spellcheck-input-ref.html
+== spellcheck-input-property-dynamic-inherit.html spellcheck-input-nofocus-ref.html
+!= spellcheck-input-property-dynamic-inherit.html spellcheck-input-ref.html
+== spellcheck-input-attr-dynamic-override.html spellcheck-input-nofocus-ref.html
+!= spellcheck-input-attr-dynamic-override.html spellcheck-input-ref.html
+== spellcheck-input-attr-dynamic-override-inherit.html spellcheck-input-nofocus-ref.html
+!= spellcheck-input-attr-dynamic-override-inherit.html spellcheck-input-ref.html
+== spellcheck-input-property-dynamic-override.html spellcheck-input-nofocus-ref.html
+!= spellcheck-input-property-dynamic-override.html spellcheck-input-ref.html
+== spellcheck-input-property-dynamic-override-inherit.html spellcheck-input-nofocus-ref.html
+!= spellcheck-input-property-dynamic-override-inherit.html spellcheck-input-ref.html
+== spellcheck-textarea-attr.html spellcheck-textarea-nofocus-ref.html
+!= spellcheck-textarea-attr.html spellcheck-textarea-ref.html
+== spellcheck-textarea-focused.html spellcheck-textarea-ref.html
+!= spellcheck-textarea-nofocus.html spellcheck-textarea-ref.html
 fails-if(Android) != spellcheck-textarea-disabled.html spellcheck-textarea-ref.html
 fails-if(Android) != spellcheck-textarea-attr-inherit.html spellcheck-textarea-ref.html
 fails-if(Android) != spellcheck-textarea-attr-dynamic.html spellcheck-textarea-ref.html
 fails-if(Android) != spellcheck-textarea-attr-dynamic-inherit.html spellcheck-textarea-ref.html
 fails-if(Android) != spellcheck-textarea-property-dynamic.html spellcheck-textarea-ref.html
 fails-if(Android) != spellcheck-textarea-property-dynamic-inherit.html spellcheck-textarea-ref.html
 fails-if(Android) != spellcheck-textarea-attr-dynamic-override.html spellcheck-textarea-ref.html
 fails-if(Android) != spellcheck-textarea-attr-dynamic-override-inherit.html spellcheck-textarea-ref.html
new file mode 100644
--- /dev/null
+++ b/layout/reftests/editor/spellcheck-input-nofocus-ref.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body>
+    <input type="text" value="blahblahblah" spellcheck="true">
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/editor/spellcheck-textarea-focused.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<body>
+
+  <textarea id="testBox">blahblahblah</textarea>
+  <script type="text/javascript">
+    //Adding focus to the textbox should trigger a spellcheck
+    document.getElementById("testBox").focus();
+  </script>
+  
+</body>
+</html>
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/layout/reftests/editor/spellcheck-textarea-nofocus-ref.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body>
+  <textarea spellcheck="true">blahblahblah</textarea>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/editor/spellcheck-textarea-nofocus.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body>
+  <textarea>blahblahblah</textarea>
+</body>
+</html>
\ No newline at end of file
--- a/layout/reftests/editor/spellcheck-textarea-ref.html
+++ b/layout/reftests/editor/spellcheck-textarea-ref.html
@@ -1,6 +1,10 @@
 <!DOCTYPE html>
 <html>
 <body>
   <textarea spellcheck="true">blahblahblah</textarea>
+  <script type="text/javascript">
+    var box = document.getElementsByTagName("textarea")[0];
+    box.focus(); //Bring the textbox into focus, triggering a spellcheck
+  </script>
 </body>
 </html>