Bug 1330252, try to avoid flooding child process with repeated key events, r=masayuki
authorOlli Pettay <Olli.Pettay@helsinki.fi>
Mon, 16 Jan 2017 11:29:37 +0200
changeset 374594 68aff122042fd472f4dcc05f259929a8b7efe894
parent 374593 586bc089769d01abc91343cb8900bfa7a6923c88
child 374595 99ea5dbd29164d1d0a9bc3397b9ae5e1e57290be
push id6996
push userjlorenzo@mozilla.com
push dateMon, 06 Mar 2017 20:48:21 +0000
treeherdermozilla-beta@d89512dab048 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmasayuki
bugs1330252
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 1330252, try to avoid flooding child process with repeated key events, r=masayuki
dom/ipc/TabChild.cpp
dom/ipc/TabChild.h
dom/tests/browser/browser.ini
dom/tests/browser/browser_bug1316330.js
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -378,16 +378,17 @@ TabChild::TabChild(nsIContentChild* aMan
   , mRounding(0)
   , mDefaultScale(0)
   , mIsTransparent(false)
   , mIPCOpen(false)
   , mParentIsActive(false)
   , mDidSetRealShowInfo(false)
   , mDidLoadURLInit(false)
   , mIsFreshProcess(false)
+  , mSkipKeyPress(false)
   , mLayerObserverEpoch(0)
 #if defined(XP_WIN) && defined(ACCESSIBILITY)
   , mNativeWindowHandle(0)
 #endif
 {
   nsWeakPtr weakPtrThis(do_GetWeakReference(static_cast<nsITabChild*>(this)));  // for capture by the lambda
   mSetAllowedTouchBehaviorCallback = [weakPtrThis](uint64_t aInputBlockId,
                                                    const nsTArray<TouchBehaviorFlags>& aFlags)
@@ -1773,20 +1774,62 @@ TabChild::RequestNativeKeyBindings(AutoC
 mozilla::ipc::IPCResult
 TabChild::RecvNativeSynthesisResponse(const uint64_t& aObserverId,
                                       const nsCString& aResponse)
 {
   mozilla::widget::AutoObserverNotifier::NotifySavedObserver(aObserverId, aResponse.get());
   return IPC_OK();
 }
 
+// In case handling repeated keys takes much time, we skip firing new ones.
+bool
+TabChild::SkipRepeatedKeyEvent(const WidgetKeyboardEvent& aEvent)
+{
+  if (mRepeatedKeyEventTime.IsNull() ||
+      !aEvent.mIsRepeat ||
+      (aEvent.mMessage != eKeyDown && aEvent.mMessage != eKeyPress)) {
+    mRepeatedKeyEventTime = TimeStamp();
+    mSkipKeyPress = false;
+    return false;
+  }
+
+  if ((aEvent.mMessage == eKeyDown &&
+       (mRepeatedKeyEventTime > aEvent.mTimeStamp)) ||
+      (mSkipKeyPress && (aEvent.mMessage == eKeyPress))) {
+    // If we skip a keydown event, also the following keypress events should be
+    // skipped.
+    mSkipKeyPress |= aEvent.mMessage == eKeyDown;
+    return true;
+  }
+
+  if (aEvent.mMessage == eKeyDown) {
+    // If keydown wasn't skipped, nor should the possible following keypress.
+    mRepeatedKeyEventTime = TimeStamp();
+    mSkipKeyPress = false;
+  }
+  return false;
+}
+
+void
+TabChild::UpdateRepeatedKeyEventEndTime(const WidgetKeyboardEvent& aEvent)
+{
+  if (aEvent.mIsRepeat &&
+      (aEvent.mMessage == eKeyDown || aEvent.mMessage == eKeyPress)) {
+    mRepeatedKeyEventTime = TimeStamp::Now();
+  }
+}
+
 mozilla::ipc::IPCResult
 TabChild::RecvRealKeyEvent(const WidgetKeyboardEvent& event,
                            const MaybeNativeKeyBinding& aBindings)
 {
+  if (SkipRepeatedKeyEvent(event)) {
+    return IPC_OK();
+  }
+
   AutoCacheNativeKeyCommands autoCache(mPuppetWidget);
 
   if (event.mMessage == eKeyPress) {
     // If content code called preventDefault() on a keydown event, then we don't
     // want to process any following keypress events.
     if (mIgnoreKeyPressEvent) {
       return IPC_OK();
     }
@@ -1799,16 +1842,20 @@ TabChild::RecvRealKeyEvent(const WidgetK
       autoCache.CacheNoCommands();
     }
   }
 
   WidgetKeyboardEvent localEvent(event);
   localEvent.mWidget = mPuppetWidget;
   nsEventStatus status = APZCCallbackHelper::DispatchWidgetEvent(localEvent);
 
+  // Update the end time of the possible repeated event so that we can skip
+  // some incoming events in case event handling took long time.
+  UpdateRepeatedKeyEventEndTime(localEvent);
+
   if (event.mMessage == eKeyDown) {
     mIgnoreKeyPressEvent = status == nsEventStatus_eConsumeNoDefault;
   }
 
   if (localEvent.mFlags.mIsSuppressedOrDelayed) {
     localEvent.PreventDefault();
   }
 
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -742,16 +742,20 @@ private:
 
   ScreenIntRect GetOuterRect();
 
   void SetUnscaledInnerSize(const CSSSize& aSize)
   {
     mUnscaledInnerSize = aSize;
   }
 
+  bool SkipRepeatedKeyEvent(const WidgetKeyboardEvent& aEvent);
+
+  void UpdateRepeatedKeyEventEndTime(const WidgetKeyboardEvent& aEvent);
+
   class DelayedDeleteRunnable;
 
   TextureFactoryIdentifier mTextureFactoryIdentifier;
   nsCOMPtr<nsIWebNavigation> mWebNav;
   RefPtr<PuppetWidget> mPuppetWidget;
   nsCOMPtr<nsIURI> mLastURI;
   RenderFrameChild* mRemoteFrame;
   RefPtr<nsIContentChild> mManager;
@@ -790,16 +794,23 @@ private:
 
   bool mIPCOpen;
   bool mParentIsActive;
   CSSSize mUnscaledInnerSize;
   bool mDidSetRealShowInfo;
   bool mDidLoadURLInit;
   bool mIsFreshProcess;
 
+  bool mSkipKeyPress;
+
+  // Store the end time of the handling of the last repeated keydown/keypress
+  // event so that in case event handling takes time, some repeated events can
+  // be skipped to not flood child process.
+  mozilla::TimeStamp mRepeatedKeyEventTime;
+
   AutoTArray<bool, NUMBER_OF_AUDIO_CHANNELS> mAudioChannelsActive;
 
   RefPtr<layers::IAPZCTreeManager> mApzcTreeManager;
 
   // The most recently seen layer observer epoch in RecvSetDocShellIsActive.
   uint64_t mLayerObserverEpoch;
 
 #if defined(XP_WIN) && defined(ACCESSIBILITY)
--- a/dom/tests/browser/browser.ini
+++ b/dom/tests/browser/browser.ini
@@ -16,16 +16,18 @@ 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_bug1316330.js]
+skip-if = !e10s
 [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]
new file mode 100644
--- /dev/null
+++ b/dom/tests/browser/browser_bug1316330.js
@@ -0,0 +1,54 @@
+const URL =
+  "data:text/html,<script>" +
+  "window.focus();" + 
+  "var down = 0; var press = 0;" +
+  "onkeydown = function(e) {" +
+  "  document.body.setAttribute('data-down', ++down);" +
+  "  if (e.keyCode == 'D') while (Date.now() - startTime < 500) {}" +
+  "};" +
+  "onkeypress = function(e) {" +
+  "  document.body.setAttribute('data-press', ++press);" +
+  "  if (e.charCode == 'P') while (Date.now() - startTime < 500) {}" +
+  "};" +
+  "</script>";
+
+function createKeyEvent(type, id, repeated) {
+  var code = id.charCodeAt(0);
+  return new KeyboardEvent(type, { keyCode: code, charCode: code, bubbles: true, repeat: repeated });
+}
+
+add_task(function* () {
+  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, URL);
+  let browser = tab.linkedBrowser;
+
+  // Need to dispatch a DOM Event explicitly via PresShell to get KeyEvent with .repeat = true to 
+  // be handled by EventStateManager, which then forwards the event to the child process.
+  var utils = EventUtils._getDOMWindowUtils(window);
+  utils.dispatchDOMEventViaPresShell(browser, createKeyEvent("keydown", "D", false), true);
+  utils.dispatchDOMEventViaPresShell(browser, createKeyEvent("keypress", "D", false), true);
+  utils.dispatchDOMEventViaPresShell(browser, createKeyEvent("keydown", "D", true), true);
+  utils.dispatchDOMEventViaPresShell(browser, createKeyEvent("keypress", "D", true), true);
+  utils.dispatchDOMEventViaPresShell(browser, createKeyEvent("keydown", "D", true), true);
+  utils.dispatchDOMEventViaPresShell(browser, createKeyEvent("keypress", "D", true), true);
+  utils.dispatchDOMEventViaPresShell(browser, createKeyEvent("keyup", "D", true), true);
+
+  yield ContentTask.spawn(browser, null, function* () {
+    is(content.document.body.getAttribute("data-down"), "2", "Correct number of events");
+    is(content.document.body.getAttribute("data-press"), "2", "Correct number of events");
+  });
+
+  utils.dispatchDOMEventViaPresShell(browser, createKeyEvent("keydown", "P", false), true);
+  utils.dispatchDOMEventViaPresShell(browser, createKeyEvent("keypress", "P", false), true);
+  utils.dispatchDOMEventViaPresShell(browser, createKeyEvent("keydown", "P", true), true);
+  utils.dispatchDOMEventViaPresShell(browser, createKeyEvent("keypress", "P", true), true);
+  utils.dispatchDOMEventViaPresShell(browser, createKeyEvent("keydown", "P", true), true);
+  utils.dispatchDOMEventViaPresShell(browser, createKeyEvent("keypress", "P", true), true);
+  utils.dispatchDOMEventViaPresShell(browser, createKeyEvent("keyup", "P", true), true);
+
+  yield ContentTask.spawn(browser, null, function* () {
+    is(content.document.body.getAttribute("data-down"), "4", "Correct number of events");
+    is(content.document.body.getAttribute("data-press"), "4", "Correct number of events");
+  });
+
+  gBrowser.removeCurrentTab();
+});