Bug 625452 - Make sure all plaintext editor commands generate trusted input events; r=roc a=blocking-final+
authorEhsan Akhgari <ehsan@mozilla.com>
Sat, 15 Jan 2011 23:16:45 -0500
changeset 60699 9698210ea3c66a935750a597ce6b48e1736fc9ca
parent 60698 8c1766aa38d3bec214cd9b356e5bf0a2c7395800
child 60700 7c9e4303c3c7b545e02cb405a0894dd4f1d7701e
push idunknown
push userunknown
push dateunknown
reviewersroc, blocking-final
bugs625452
milestone2.0b10pre
Bug 625452 - Make sure all plaintext editor commands generate trusted input events; r=roc a=blocking-final+
editor/libeditor/base/nsEditor.h
editor/libeditor/text/nsPlaintextDataTransfer.cpp
editor/libeditor/text/nsPlaintextEditor.cpp
editor/libeditor/text/tests/Makefile.in
editor/libeditor/text/tests/test_bug625452.html
--- a/editor/libeditor/base/nsEditor.h
+++ b/editor/libeditor/base/nsEditor.h
@@ -199,19 +199,39 @@ public:
   nsresult CreateHTMLContent(const nsAString& aTag, nsIContent** aContent);
 
   // IME event handlers
   virtual nsresult BeginIMEComposition();
   virtual nsresult UpdateIMEComposition(const nsAString &aCompositionString,
                                         nsIPrivateTextRangeList *aTextRange)=0;
   nsresult EndIMEComposition();
 
+  void BeginKeypressHandling() { mLastKeypressEventWasTrusted = eTriTrue; }
   void BeginKeypressHandling(nsIDOMNSEvent* aEvent);
   void EndKeypressHandling() { mLastKeypressEventWasTrusted = eTriUnset; }
 
+  class FireTrustedInputEvent {
+  public:
+    explicit FireTrustedInputEvent(nsEditor* aSelf, PRBool aActive = PR_TRUE)
+      : mEditor(aSelf)
+      , mShouldAct(aActive && mEditor->mLastKeypressEventWasTrusted == eTriUnset) {
+      if (mShouldAct) {
+        mEditor->BeginKeypressHandling();
+      }
+    }
+    ~FireTrustedInputEvent() {
+      if (mShouldAct) {
+        mEditor->EndKeypressHandling();
+      }
+    }
+  private:
+    nsEditor* mEditor;
+    PRBool mShouldAct;
+  };
+
 protected:
   nsCString mContentMIMEType;       // MIME type of the doc we are editing.
 
   /** create a transaction for setting aAttribute to aValue on aElement
     */
   NS_IMETHOD CreateTxnForSetAttribute(nsIDOMElement *aElement, 
                                       const nsAString &  aAttribute, 
                                       const nsAString &  aValue,
--- a/editor/libeditor/text/nsPlaintextDataTransfer.cpp
+++ b/editor/libeditor/text/nsPlaintextDataTransfer.cpp
@@ -114,16 +114,18 @@ nsresult nsPlaintextEditor::InsertTextAt
   return InsertText(aStringToInsert);
 }
 
 NS_IMETHODIMP nsPlaintextEditor::InsertTextFromTransferable(nsITransferable *aTransferable,
                                                             nsIDOMNode *aDestinationNode,
                                                             PRInt32 aDestOffset,
                                                             PRBool aDoDeleteSelection)
 {
+  FireTrustedInputEvent trusted(this);
+
   nsresult rv = NS_OK;
   char* bestFlavor = nsnull;
   nsCOMPtr<nsISupports> genericDataObj;
   PRUint32 len = 0;
   if (NS_SUCCEEDED(aTransferable->GetAnyTransferData(&bestFlavor, getter_AddRefs(genericDataObj), &len))
       && bestFlavor && (0 == nsCRT::strcmp(bestFlavor, kUnicodeMime) ||
                         0 == nsCRT::strcmp(bestFlavor, kMozTextInternal)))
   {
@@ -425,25 +427,19 @@ NS_IMETHODIMP nsPlaintextEditor::Paste(P
     if (NS_SUCCEEDED(clipboard->GetData(trans, aSelectionType)) && IsModifiable())
     {
       // handle transferable hooks
       nsCOMPtr<nsIDOMDocument> domdoc;
       GetDocument(getter_AddRefs(domdoc));
       if (!nsEditorHookUtils::DoInsertionHook(domdoc, nsnull, trans))
         return NS_OK;
 
-      // Make sure to issue an input event for paste operations
-      NS_ASSERTION(mLastKeypressEventWasTrusted == eTriUnset, "How come our status is not clear?");
-      mLastKeypressEventWasTrusted = eTriTrue;
-
       // Beware! This may flush notifications via synchronous
       // ScrollSelectionIntoView.
       rv = InsertTextFromTransferable(trans, nsnull, nsnull, PR_TRUE);
-
-      mLastKeypressEventWasTrusted = eTriUnset;
     }
   }
 
   return rv;
 }
 
 NS_IMETHODIMP nsPlaintextEditor::PasteTransferable(nsITransferable *aTransferable)
 {
--- a/editor/libeditor/text/nsPlaintextEditor.cpp
+++ b/editor/libeditor/text/nsPlaintextEditor.cpp
@@ -742,16 +742,18 @@ NS_IMETHODIMP nsPlaintextEditor::DeleteS
   nsCOMPtr<nsIEditRules> kungFuDeathGrip(mRules);
 
   nsresult result;
 
   // delete placeholder txns merge.
   nsAutoPlaceHolderBatch batch(this, nsGkAtoms::DeleteTxnName);
   nsAutoRules beginRulesSniffing(this, kOpDeleteSelection, aAction);
 
+  FireTrustedInputEvent trusted(this, aAction != eNone);
+
   // pre-process
   nsCOMPtr<nsISelection> selection;
   result = GetSelection(getter_AddRefs(selection));
   NS_ENSURE_SUCCESS(result, result);
   NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
 
   // If there is an existing selection when an extended delete is requested,
   //  platforms that use "caret-style" caret positioning collapse the
@@ -1210,16 +1212,18 @@ nsPlaintextEditor::SetNewlineHandling(PR
 NS_IMETHODIMP 
 nsPlaintextEditor::Undo(PRUint32 aCount)
 {
   // Protect the edit rules object from dying
   nsCOMPtr<nsIEditRules> kungFuDeathGrip(mRules);
 
   nsAutoUpdateViewBatch beginViewBatching(this);
 
+  FireTrustedInputEvent trusted(this);
+
   ForceCompositionEnd();
 
   nsAutoRules beginRulesSniffing(this, kOpUndo, nsIEditor::eNone);
 
   nsTextRulesInfo ruleInfo(nsTextEditRules::kUndo);
   nsCOMPtr<nsISelection> selection;
   GetSelection(getter_AddRefs(selection));
   PRBool cancel, handled;
@@ -1237,16 +1241,18 @@ nsPlaintextEditor::Undo(PRUint32 aCount)
 NS_IMETHODIMP 
 nsPlaintextEditor::Redo(PRUint32 aCount)
 {
   // Protect the edit rules object from dying
   nsCOMPtr<nsIEditRules> kungFuDeathGrip(mRules);
 
   nsAutoUpdateViewBatch beginViewBatching(this);
 
+  FireTrustedInputEvent trusted(this);
+
   ForceCompositionEnd();
 
   nsAutoRules beginRulesSniffing(this, kOpRedo, nsIEditor::eNone);
 
   nsTextRulesInfo ruleInfo(nsTextEditRules::kRedo);
   nsCOMPtr<nsISelection> selection;
   GetSelection(getter_AddRefs(selection));
   PRBool cancel, handled;
@@ -1291,16 +1297,18 @@ nsPlaintextEditor::FireClipboardEvent(PR
 
   // If the event handler caused the editor to be destroyed, return false.
   // Otherwise return true to indicate that the event was not cancelled.
   return !mDidPreDestroy;
 }
 
 NS_IMETHODIMP nsPlaintextEditor::Cut()
 {
+  FireTrustedInputEvent trusted(this);
+
   if (FireClipboardEvent(NS_CUT))
     return DeleteSelection(eNone);
   return NS_OK;
 }
 
 NS_IMETHODIMP nsPlaintextEditor::CanCut(PRBool *aCanCut)
 {
   NS_ENSURE_ARG_POINTER(aCanCut);
--- a/editor/libeditor/text/tests/Makefile.in
+++ b/editor/libeditor/text/tests/Makefile.in
@@ -52,16 +52,17 @@ include $(topsrcdir)/config/rules.mk
 		test_bug596001.html \
 		test_bug596333.html \
 		test_bug596506.html \
 		test_bug597331.html \
 		test_bug600570.html \
 		test_bug602130.html \
 		test_bug603556.html \
 		test_bug604532.html \
+		test_bug625452.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_bug625452.html
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=625452
+-->
+<head>
+  <title>Test for Bug 625452</title>
+  <script type="application/javascript" src="/MochiKit/packed.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=625452">Mozilla Bug 625452</a>
+<p id="display"></p>
+<div id="content">
+<input>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 625452 **/
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+  var i = document.querySelector("input");
+  var inputCount = 0;
+  i.addEventListener("input", function() inputCount++, false);
+
+  // test cut
+  i.focus();
+  i.value = "foo bar";
+  i.selectionStart = 0;
+  i.selectionEnd = 4;
+  synthesizeKey("X", {accelKey: true});
+  is(i.value, "bar", "Cut should work correctly");
+  is(inputCount, 1, "input event should be raised correctly");
+
+  // test undo
+  synthesizeKey("Z", {accelKey: true});
+  is(i.value, "foo bar", "Undo should work correctly");
+  is(inputCount, 2, "input event should be raised correctly");
+
+  // test redo
+  synthesizeKey("Z", {accelKey: true, shiftKey: true});
+  is(i.value, "bar", "Redo should work correctly");
+  is(inputCount, 3, "input event should be raised correctly");
+
+  // test delete
+  i.selectionStart = 0;
+  i.selectionEnd = 2;
+  synthesizeKey("VK_DELETE", {});
+  is(i.value, "r", "Delete should work correctly");
+  is(inputCount, 4, "input event should be raised correctly");
+
+  // test DeleteSelection(eNone)
+  i.value = "retest"; // the "r" common prefix is crucial here
+  is(inputCount, 4, "input event should not have been raised");
+
+  // paste is tested in test_bug596001.html
+
+  SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>