Bug 1316330 - Cancel delayed keypress events if last keydown was canceled. r=smaug, a=jcristau
☠☠ backed out by 4a274293090d ☠ ☠
authorJessica Jong <jjong@mozilla.com>
Fri, 02 Dec 2016 15:35:28 +0800
changeset 353634 fd642e0cca778eb13702e616e36d9940217624f9
parent 353633 40fcae3eee184f7338cf52d2c887f0261f093697
child 353635 d49507cffc8ac291ec912ace8240894b9cd53988
push id6795
push userjlund@mozilla.com
push dateMon, 23 Jan 2017 14:19:46 +0000
treeherdermozilla-esr52@76101b503191 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug, jcristau
bugs1316330
milestone52.0a2
Bug 1316330 - Cancel delayed keypress events if last keydown was canceled. r=smaug, a=jcristau
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/nsPresShell.cpp
layout/base/nsPresShell.h
toolkit/components/passwordmgr/test/subtst_master_pass.html
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -9070,17 +9070,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++;
@@ -9107,17 +9107,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/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -8276,16 +8276,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);
@@ -9066,16 +9069,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();
     }
   }
 }
 
@@ -9874,16 +9880,22 @@ PresShell::DelayedKeyEvent::DelayedKeyEv
     new WidgetKeyboardEvent(aEvent->IsTrusted(),
                             aEvent->mMessage,
                             aEvent->mWidget);
   keyEvent->AssignKeyEventData(*aEvent, false);
   keyEvent->mFlags.mIsSynthesizedForTests = aEvent->mFlags.mIsSynthesizedForTests;
   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/nsPresShell.h
+++ b/layout/base/nsPresShell.h
@@ -612,16 +612,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:
@@ -636,16 +637,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)
@@ -955,12 +957,14 @@ 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;
 };
 
 #endif /* !defined(nsPresShell_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>