Bug 1316330 - Cancel delayed keypress events if last keydown was canceled. r=smaug
authorJessica Jong <jjong@mozilla.com>
Fri, 02 Dec 2016 15:35:28 +0800
changeset 329320 a60d62c55e27f7f62666d0215e3e818bc7f1430e
parent 329319 83b1e97f352a360bd39442635ca0a963cc415ce0
child 329321 f3ccf0f50160e169389aa26a2d95ec225effcd3b
push id85680
push userryanvm@gmail.com
push dateFri, 13 Jan 2017 17:13:00 +0000
treeherdermozilla-inbound@f3ccf0f50160 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1316330
milestone53.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 1316330 - Cancel delayed keypress events if last keydown was canceled. r=smaug
dom/base/nsGlobalWindow.cpp
dom/tests/browser/browser.ini
dom/tests/browser/browser_cancel_keydown_keypress_event.js
dom/tests/browser/prevent_return_key.html
layout/base/PresShell.cpp
layout/base/PresShell.h
toolkit/components/passwordmgr/test/subtst_master_pass.html
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -8918,17 +8918,17 @@ nsGlobalWindow::EnterModalState()
     nsIPresShell::SetCapturingContent(nullptr, 0);
   }
 
   if (topWin->mModalStateDepth == 0) {
     NS_ASSERTION(!topWin->mSuspendedDoc, "Shouldn't have mSuspendedDoc here!");
 
     topWin->mSuspendedDoc = topDoc;
     if (topDoc) {
-      topDoc->SuppressEventHandling(nsIDocument::eAnimationsOnly);
+      topDoc->SuppressEventHandling(nsIDocument::eEvents);
     }
 
     nsGlobalWindow* inner = topWin->GetCurrentInnerWindowInternal();
     if (inner) {
       topWin->GetCurrentInnerWindowInternal()->Suspend();
     }
   }
   topWin->mModalStateDepth++;
@@ -8955,17 +8955,17 @@ nsGlobalWindow::LeaveModalState()
 
   if (topWin->mModalStateDepth == 0) {
     if (inner) {
       inner->Resume();
     }
 
     if (topWin->mSuspendedDoc) {
       nsCOMPtr<nsIDocument> currentDoc = topWin->GetExtantDoc();
-      topWin->mSuspendedDoc->UnsuppressEventHandlingAndFireEvents(nsIDocument::eAnimationsOnly,
+      topWin->mSuspendedDoc->UnsuppressEventHandlingAndFireEvents(nsIDocument::eEvents,
                                                                   currentDoc == topWin->mSuspendedDoc);
       topWin->mSuspendedDoc = nullptr;
     }
   }
 
   // Remember the time of the last dialog quit.
   if (inner) {
     inner->mLastDialogQuitTime = TimeStamp::Now();
--- a/dom/tests/browser/browser.ini
+++ b/dom/tests/browser/browser.ini
@@ -16,16 +16,19 @@ support-files =
 disabled = Does not reliably pass on 32-bit systems - bug 1314098
 skip-if = !e10s
 [browser_autofocus_background.js]
 [browser_autofocus_preference.js]
 [browser_bug396843.js]
 [browser_bug1004814.js]
 [browser_bug1008941_dismissGeolocationHanger.js]
 [browser_bug1238427.js]
+[browser_cancel_keydown_keypress_event.js]
+support-files =
+  prevent_return_key.html
 [browser_ConsoleAPI_originAttributes.js]
 [browser_ConsoleAPITests.js]
 skip-if = e10s
 [browser_ConsoleStorageAPITests.js]
 [browser_ConsoleStoragePBTest_perwindowpb.js]
 [browser_focus_steal_from_chrome.js]
 [browser_focus_steal_from_chrome_during_mousedown.js]
 [browser_frame_elements.js]
new file mode 100644
--- /dev/null
+++ b/dom/tests/browser/browser_cancel_keydown_keypress_event.js
@@ -0,0 +1,41 @@
+const URL =
+  "https://example.com/browser/dom/tests/browser/prevent_return_key.html";
+
+// Wait for alert dialog and dismiss it immediately.
+function awaitAndCloseAlertDialog() {
+  return new Promise(resolve => {
+    function onDialogShown(node) {
+      Services.obs.removeObserver(onDialogShown, "tabmodal-dialog-loaded");
+      let button = node.ui.button0;
+      button.click();
+      resolve();
+    }
+    Services.obs.addObserver(onDialogShown, "tabmodal-dialog-loaded");
+  });
+}
+
+add_task(function* () {
+  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, URL);
+  let browser = tab.linkedBrowser;
+
+  // Focus and enter random text on input.
+  yield ContentTask.spawn(browser, null, function* () {
+    let input = content.document.getElementById("input");
+    input.focus();
+    input.value = "abcd";
+  });
+
+  // Send return key (cross process) to submit the form implicitly.
+  let dialogShown = awaitAndCloseAlertDialog();
+  EventUtils.synthesizeKey("VK_RETURN", {});
+  yield dialogShown;
+
+  // Check that the form should not have been submitted.
+  yield ContentTask.spawn(browser, null, function* () {
+    let result = content.document.getElementById("result").innerHTML;
+    info("submit result: " + result);
+    is(result, "not submitted", "form should not have submitted");
+  });
+
+  gBrowser.removeCurrentTab();
+});
new file mode 100644
--- /dev/null
+++ b/dom/tests/browser/prevent_return_key.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Prevent return key should not submit form</title>
+  <script type="application/javascript">
+  function init() {
+    let input = document.getElementById("input");
+    input.addEventListener("keydown", function(aEvent) {
+      input.removeEventListener("keydown", arguments.callee);
+      if (aEvent.keyCode == 13) { // return key
+        alert("Hello!");
+        aEvent.preventDefault();
+        return false;
+      }
+    });
+
+    let form = document.getElementById("form");
+    form.addEventListener("submit", function() {
+      form.removeEventListener("submit", arguments.callee);
+      let result = document.getElementById("result");
+      result.innerHTML = "submitted";
+    });
+  }
+  </script>
+</head>
+
+<body onload="init()">
+  <form id="form">
+    <input type="text" id="input">
+    <button type="submit" id="submitBtn">Submit</button>
+  </form>
+  <p id="result">not submitted</p>
+</body>
+</html>
+
--- a/layout/base/PresShell.cpp
+++ b/layout/base/PresShell.cpp
@@ -8039,16 +8039,19 @@ PresShell::HandleEventInternal(WidgetEve
           mIsLastChromeOnlyEscapeKeyConsumed = false;
         } else {
           if (aEvent->mFlags.mOnlyChromeDispatch &&
               aEvent->mFlags.mDefaultPreventedByChrome) {
             mIsLastChromeOnlyEscapeKeyConsumed = true;
           }
         }
       }
+      if (aEvent->mMessage == eKeyDown) {
+        mIsLastKeyDownCanceled = aEvent->mFlags.mDefaultPrevented;
+      }
       break;
     }
     case eMouseUp:
       // reset the capturing content now that the mouse button is up
       SetCapturingContent(nullptr, 0);
       break;
     case eMouseMove:
       nsIPresShell::AllowMouseCapture(false);
@@ -8828,16 +8831,19 @@ PresShell::FireOrClearDelayedEvents(bool
   }
 
   if (mDocument) {
     nsCOMPtr<nsIDocument> doc = mDocument;
     while (!mIsDestroying && mDelayedEvents.Length() &&
            !doc->EventHandlingSuppressed()) {
       nsAutoPtr<DelayedEvent> ev(mDelayedEvents[0].forget());
       mDelayedEvents.RemoveElementAt(0);
+      if (ev->IsKeyPressEvent() && mIsLastKeyDownCanceled) {
+        continue;
+      }
       ev->Dispatch();
     }
     if (!doc->EventHandlingSuppressed()) {
       mDelayedEvents.Clear();
     }
   }
 }
 
@@ -9633,16 +9639,22 @@ PresShell::DelayedKeyEvent::DelayedKeyEv
                             aEvent->mMessage,
                             aEvent->mWidget);
   keyEvent->AssignKeyEventData(*aEvent, false);
   keyEvent->mFlags.mIsSynthesizedForTests = aEvent->mFlags.mIsSynthesizedForTests;
   keyEvent->mFlags.mIsSuppressedOrDelayed = true;
   mEvent = keyEvent;
 }
 
+bool
+PresShell::DelayedKeyEvent::IsKeyPressEvent()
+{
+  return mEvent->mMessage == eKeyPress;
+}
+
 // Start of DEBUG only code
 
 #ifdef DEBUG
 
 static void
 LogVerifyMessage(nsIFrame* k1, nsIFrame* k2, const char* aMsg)
 {
   nsAutoString n1, n2;
--- a/layout/base/PresShell.h
+++ b/layout/base/PresShell.h
@@ -595,16 +595,17 @@ protected:
     return rv;
   }
 
   class DelayedEvent
   {
   public:
     virtual ~DelayedEvent() { }
     virtual void Dispatch() { }
+    virtual bool IsKeyPressEvent() { return false; }
   };
 
   class DelayedInputEvent : public DelayedEvent
   {
   public:
     virtual void Dispatch() override;
 
   protected:
@@ -619,16 +620,17 @@ protected:
   public:
     explicit DelayedMouseEvent(mozilla::WidgetMouseEvent* aEvent);
   };
 
   class DelayedKeyEvent : public DelayedInputEvent
   {
   public:
     explicit DelayedKeyEvent(mozilla::WidgetKeyboardEvent* aEvent);
+    virtual bool IsKeyPressEvent() override;
   };
 
   // Check if aEvent is a mouse event and record the mouse location for later
   // synth mouse moves.
   void RecordMouseLocation(mozilla::WidgetGUIEvent* aEvent);
   class nsSynthMouseMoveEvent final : public nsARefreshObserver {
   public:
     nsSynthMouseMoveEvent(PresShell* aPresShell, bool aFromScroll)
@@ -898,14 +900,16 @@ protected:
   bool                      mScaleToResolution : 1;
 
   // Whether the last chrome-only escape key event is consumed.
   bool                      mIsLastChromeOnlyEscapeKeyConsumed : 1;
 
   // Whether the widget has received a paint message yet.
   bool                      mHasReceivedPaintMessage : 1;
 
+  bool                      mIsLastKeyDownCanceled : 1;
+
   static bool               sDisableNonTestMouseEvents;
 };
 
 } // namespace mozilla
 
 #endif // mozilla_PresShell_h
--- a/toolkit/components/passwordmgr/test/subtst_master_pass.html
+++ b/toolkit/components/passwordmgr/test/subtst_master_pass.html
@@ -1,12 +1,7 @@
 <h2>MP subtest</h2>
 This form triggers a MP and gets filled in.<br>
 <form>
 Username: <input type="text"     id="userfield" name="u"><br>
-Password: <input type="password" id="passfield" name="p"><br>
-<script>
-    // Only notify when we fill in the password field.
-    document.getElementById("passfield").addEventListener("input", function() {
-        parent.postMessage("filled", "*");
-    });
-</script>
+Password: <input type="password" id="passfield" name="p"
+                 oninput="parent.postMessage('filled', '*');"><br>
 </form>