Bug 1273635: Enable alertable waits in content process main thread; r?jimm draft
authorAaron Klotz <aklotz@mozilla.com>
Wed, 03 Aug 2016 11:54:35 -0600
changeset 397386 d5e239faf4279b68d450852d5ed436af8a9d6b28
parent 397385 192132a4cbc78fa948ad1e37c204f537043fc37e
child 397439 10b2b6c32219f432f5837fb9547cbdcf4e9a70fc
child 400193 626353cd7692f759e76935d9f4222ad4cd9c7800
push id25280
push useraklotz@mozilla.com
push dateFri, 05 Aug 2016 18:51:27 +0000
reviewersjimm
bugs1273635
milestone51.0a1
Bug 1273635: Enable alertable waits in content process main thread; r?jimm MozReview-Commit-ID: 2qGdGj41M0n
dom/ipc/ContentChild.cpp
ipc/glue/MessageChannel.h
ipc/glue/WindowsMessageLoop.cpp
widget/windows/WinUtils.cpp
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -583,16 +583,19 @@ ContentChild::Init(MessageLoop* aIOLoop,
   if (!Open(aChannel, aParentPid, aIOLoop)) {
     return false;
   }
   sSingleton = this;
 
   // If communications with the parent have broken down, take the process
   // down so it's not hanging around.
   GetIPCChannel()->SetAbortOnError(true);
+#if defined(XP_WIN) && defined(ACCESSIBILITY)
+  GetIPCChannel()->SetChannelFlags(MessageChannel::REQUIRE_A11Y_REENTRY);
+#endif
 
 #ifdef MOZ_X11
   // Send the parent our X socket to act as a proxy reference for our X
   // resources.
   int xSocketFd = ConnectionNumber(DefaultXDisplay());
   SendBackUpXResources(FileDescriptor(xSocketFd));
 #endif
 
--- a/ipc/glue/MessageChannel.h
+++ b/ipc/glue/MessageChannel.h
@@ -122,17 +122,22 @@ class MessageChannel : HasResultCodes
     // Misc. behavioral traits consumers can request for this channel
     enum ChannelFlags {
       REQUIRE_DEFAULT                         = 0,
       // Windows: if this channel operates on the UI thread, indicates
       // WindowsMessageLoop code should enable deferred native message
       // handling to prevent deadlocks. Should only be used for protocols
       // that manage child processes which might create native UI, like
       // plugins.
-      REQUIRE_DEFERRED_MESSAGE_PROTECTION     = 1 << 0
+      REQUIRE_DEFERRED_MESSAGE_PROTECTION     = 1 << 0,
+      // Windows: When this flag is specified, any wait that occurs during
+      // synchronous IPC will be alertable, thus allowing a11y code in the
+      // chrome process to reenter content while content is waiting on a
+      // synchronous call.
+      REQUIRE_A11Y_REENTRY                    = 1 << 1,
     };
     void SetChannelFlags(ChannelFlags aFlags) { mFlags = aFlags; }
     ChannelFlags GetChannelFlags() { return mFlags; }
 
     // Asynchronously send a message to the other side of the channel
     bool Send(Message* aMsg);
 
     // Asynchronously deliver a message back to this side of the
@@ -238,17 +243,20 @@ class MessageChannel : HasResultCodes
     static SyncStackFrame* sStaticTopFrame;
 
   public:
     void ProcessNativeEventsInInterruptCall();
     static void NotifyGeckoEventDispatch();
 
   private:
     void SpinInternalEventLoop();
-#endif
+#if defined(ACCESSIBILITY)
+    bool WaitForSyncNotifyWithA11yReentry();
+#endif // defined(ACCESSIBILITY)
+#endif // defined(OS_WIN)
 
   private:
     void CommonThreadOpenInit(MessageChannel *aTargetChan, Side aSide);
     void OnOpenAsSlave(MessageChannel *aTargetChan, Side aSide);
 
     void PostErrorNotifyTask();
     void OnNotifyMaybeChannelError();
     void ReportConnectionError(const char* aChannelName, Message* aMsg = nullptr) const;
--- a/ipc/glue/WindowsMessageLoop.cpp
+++ b/ipc/glue/WindowsMessageLoop.cpp
@@ -14,16 +14,17 @@
 #include "nsServiceManagerUtils.h"
 #include "nsString.h"
 #include "nsIXULAppInfo.h"
 #include "nsWindowsDllInterceptor.h"
 #include "WinUtils.h"
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/PaintTracker.h"
+#include "mozilla/WindowsVersion.h"
 
 using namespace mozilla;
 using namespace mozilla::ipc;
 using namespace mozilla::ipc::windows;
 
 /**
  * The Windows-only code below exists to solve a general problem with deadlocks
  * that we experience when sending synchronous IPC messages to processes that
@@ -1051,23 +1052,82 @@ SuppressedNeuteringRegion::~SuppressedNe
   if (mReenable) {
     MOZ_ASSERT(sSuppressNeutering);
     sSuppressNeutering = false;
   }
 }
 
 bool SuppressedNeuteringRegion::sSuppressNeutering = false;
 
+#if defined(ACCESSIBILITY)
+bool
+MessageChannel::WaitForSyncNotifyWithA11yReentry()
+{
+  mMonitor->AssertCurrentThreadOwns();
+  MonitorAutoUnlock unlock(*mMonitor);
+
+  const DWORD waitStart = ::GetTickCount();
+  DWORD elapsed = 0;
+  DWORD timeout = mTimeoutMs == kNoTimeout ? INFINITE :
+                  static_cast<DWORD>(mTimeoutMs);
+  bool timedOut = false;
+
+  while (true) {
+    { // Scope for lock
+      MonitorAutoLock lock(*mMonitor);
+      if (!Connected()) {
+        break;
+      }
+    }
+    if (timeout != static_cast<DWORD>(kNoTimeout)) {
+      elapsed = ::GetTickCount() - waitStart;
+    }
+    if (elapsed >= timeout) {
+      timedOut = true;
+      break;
+    }
+    DWORD waitResult = 0;
+    ::SetLastError(ERROR_SUCCESS);
+    HRESULT hr = ::CoWaitForMultipleHandles(COWAIT_ALERTABLE,
+                                            timeout - elapsed,
+                                            1, &mEvent, &waitResult);
+    if (hr == RPC_S_CALLPENDING) {
+      timedOut = true;
+      break;
+    }
+    if (hr == S_OK) {
+      if (waitResult == 0) {
+        // mEvent is signaled
+        break;
+      }
+      if (waitResult == WAIT_IO_COMPLETION) {
+        // APC fired, keep waiting
+        continue;
+      }
+    }
+    NS_WARN_IF_FALSE(SUCCEEDED(hr), "CoWaitForMultipleHandles failed");
+  }
+
+  return WaitResponse(timedOut);
+}
+#endif
+
 bool
 MessageChannel::WaitForSyncNotify(bool aHandleWindowsMessages)
 {
   mMonitor->AssertCurrentThreadOwns();
 
   MOZ_ASSERT(gUIThreadId, "InitUIThread was not called!");
 
+#if defined(ACCESSIBILITY)
+  if (IsVistaOrLater() && (mFlags & REQUIRE_A11Y_REENTRY)) {
+    return WaitForSyncNotifyWithA11yReentry();
+  }
+#endif
+
   // Use a blocking wait if this channel does not require
   // Windows message deferral behavior.
   if (!(mFlags & REQUIRE_DEFERRED_MESSAGE_PROTECTION) || !aHandleWindowsMessages) {
     PRIntervalTime timeout = (kNoTimeout == mTimeoutMs) ?
                              PR_INTERVAL_NO_TIMEOUT :
                              PR_MillisecondsToInterval(mTimeoutMs);
     PRIntervalTime waitStart = 0;
 
--- a/widget/windows/WinUtils.cpp
+++ b/widget/windows/WinUtils.cpp
@@ -721,36 +721,56 @@ WinUtils::GetMessage(LPMSG aMsg, HWND aW
                                       &ret);
     NS_ENSURE_TRUE(SUCCEEDED(hr), false);
     return ret;
   }
 #endif // #ifdef NS_ENABLE_TSF
   return ::GetMessageW(aMsg, aWnd, aFirstMessage, aLastMessage);
 }
 
+#if defined(ACCESSIBILITY)
+static DWORD
+GetWaitFlags()
+{
+  DWORD result = MWMO_INPUTAVAILABLE;
+  if (IsVistaOrLater() && XRE_IsContentProcess()) {
+    result |= MWMO_ALERTABLE;
+  }
+  return result;
+}
+#endif
+
 /* static */
 void
 WinUtils::WaitForMessage(DWORD aTimeoutMs)
 {
+#if defined(ACCESSIBILITY)
+  static const DWORD waitFlags = GetWaitFlags();
+#else
+  const DWORD waitFlags = MWMO_INPUTAVAILABLE;
+#endif
+
   const DWORD waitStart = ::GetTickCount();
   DWORD elapsed = 0;
   while (true) {
     if (aTimeoutMs != INFINITE) {
       elapsed = ::GetTickCount() - waitStart;
     }
     if (elapsed >= aTimeoutMs) {
       break;
     }
     DWORD result = ::MsgWaitForMultipleObjectsEx(0, NULL, aTimeoutMs - elapsed,
-                                                 MOZ_QS_ALLEVENT,
-                                                 MWMO_INPUTAVAILABLE);
+                                                 MOZ_QS_ALLEVENT, waitFlags);
     NS_WARN_IF_FALSE(result != WAIT_FAILED, "Wait failed");
     if (result == WAIT_TIMEOUT) {
       break;
     }
+    if (result == WAIT_IO_COMPLETION) {
+      continue;
+    }
 
     // Sent messages (via SendMessage and friends) are processed differently
     // than queued messages (via PostMessage); the destination window procedure
     // of the sent message is called during (Get|Peek)Message. Since PeekMessage
     // does not tell us whether it processed any sent messages, we need to query
     // this ahead of time.
     bool haveSentMessagesPending =
       (HIWORD(::GetQueueStatus(QS_SENDMESSAGE)) & QS_SENDMESSAGE) != 0;