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 462245 68aff122042fd472f4dcc05f259929a8b7efe894
parent 462244 586bc089769d01abc91343cb8900bfa7a6923c88
child 462246 99ea5dbd29164d1d0a9bc3397b9ae5e1e57290be
push id41683
push userbmo:steffen.wilberg@web.de
push dateMon, 16 Jan 2017 21:50:32 +0000
reviewersmasayuki
bugs1330252
milestone53.0a1
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();
+});