Bug 1009733 - Add thread safety checks for dealing with touch input blocks. r=botond
authorKartikaya Gupta <kgupta@mozilla.com>
Wed, 16 Jul 2014 12:03:06 -0400
changeset 216321 6432c138d1b7c2f0f91cb707a9d8c0cd28657837
parent 216320 8fd6bf6c57cbed415c08ab7c58c3b18e0bd54277
child 216322 19b8c4576669c84aadcd24b66f02205f91aff9a3
push id515
push userraliiev@mozilla.com
push dateMon, 06 Oct 2014 12:51:51 +0000
treeherdermozilla-release@267c7a481bef [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbotond
bugs1009733
milestone33.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 1009733 - Add thread safety checks for dealing with touch input blocks. r=botond
gfx/layers/apz/src/AsyncPanZoomController.cpp
gfx/layers/apz/src/AsyncPanZoomController.h
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -400,16 +400,36 @@ static uint32_t sAsyncPanZoomControllerC
 static TimeStamp
 GetFrameTime() {
   if (sFrameTime.IsNull()) {
     return TimeStamp::Now();
   }
   return sFrameTime;
 }
 
+static PRThread* sControllerThread;
+
+static void
+AssertOnControllerThread() {
+  if (!AsyncPanZoomController::GetThreadAssertionsEnabled()) {
+    return;
+  }
+
+  static bool sControllerThreadDetermined = false;
+  if (!sControllerThreadDetermined) {
+    // Technically this may not actually pick up the correct controller thread,
+    // if the first call to this method happens from a non-controller thread.
+    // If the assertion below fires, it is possible that it is because
+    // sControllerThread is not actually the controller thread.
+    sControllerThread = PR_GetCurrentThread();
+    sControllerThreadDetermined = true;
+  }
+  MOZ_ASSERT(sControllerThread == PR_GetCurrentThread());
+}
+
 class FlingAnimation: public AsyncPanZoomAnimation {
 public:
   FlingAnimation(AsyncPanZoomController& aApzc,
                  bool aApplyAcceleration,
                  bool aAllowOverscroll)
     : AsyncPanZoomAnimation(TimeDuration::FromMilliseconds(gfxPrefs::APZFlingRepaintInterval()))
     , mApzc(aApzc)
     , mAllowOverscroll(aAllowOverscroll)
@@ -791,16 +811,18 @@ AsyncPanZoomController::GetTouchStartTol
 }
 
 /* static */AsyncPanZoomController::AxisLockMode AsyncPanZoomController::GetAxisLockMode()
 {
   return static_cast<AxisLockMode>(gfxPrefs::APZAxisLockMode());
 }
 
 nsEventStatus AsyncPanZoomController::ReceiveInputEvent(const InputData& aEvent) {
+  AssertOnControllerThread();
+
   if (aEvent.mInputType != MULTITOUCH_INPUT) {
     return HandleInputEvent(aEvent);
   }
 
   TouchBlockState* block = nullptr;
   if (aEvent.AsMultiTouchInput().mType == MultiTouchInput::MULTITOUCH_START) {
     block = StartNewTouchBlock(false);
     APZC_LOG("%p started new touch block %p\n", this, block);
@@ -838,16 +860,18 @@ nsEventStatus AsyncPanZoomController::Re
   }
 
   // Otherwise, add it to the queue for the touch block
   block->AddEvent(aEvent.AsMultiTouchInput());
   return nsEventStatus_eConsumeDoDefault;
 }
 
 nsEventStatus AsyncPanZoomController::HandleInputEvent(const InputData& aEvent) {
+  AssertOnControllerThread();
+
   nsEventStatus rv = nsEventStatus_eIgnore;
 
   switch (aEvent.mInputType) {
   case MULTITOUCH_INPUT: {
     const MultiTouchInput& multiTouchInput = aEvent.AsMultiTouchInput();
 
     nsRefPtr<GestureEventListener> listener = GetGestureEventListener();
     if (listener) {
@@ -884,16 +908,18 @@ nsEventStatus AsyncPanZoomController::Ha
   default: NS_WARNING("Unhandled input event"); break;
   }
 
   return rv;
 }
 
 nsEventStatus AsyncPanZoomController::HandleGestureEvent(const InputData& aEvent)
 {
+  AssertOnControllerThread();
+
   nsEventStatus rv = nsEventStatus_eIgnore;
 
   switch (aEvent.mInputType) {
   case PINCHGESTURE_INPUT: {
     const PinchGestureInput& pinchGestureInput = aEvent.AsPinchGestureInput();
     switch (pinchGestureInput.mType) {
       case PinchGestureInput::PINCHGESTURE_START: rv = OnScaleBegin(pinchGestureInput); break;
       case PinchGestureInput::PINCHGESTURE_SCALE: rv = OnScale(pinchGestureInput); break;
@@ -2465,16 +2491,18 @@ AsyncPanZoomController::ScheduleContentR
   APZC_LOG("%p scheduling content response timeout\n", this);
   PostDelayedTask(
     NewRunnableMethod(this, &AsyncPanZoomController::ContentResponseTimeout),
     gfxPrefs::APZContentResponseTimeout());
 }
 
 void
 AsyncPanZoomController::ContentResponseTimeout() {
+  AssertOnControllerThread();
+
   mTouchBlockBalance++;
   APZC_LOG("%p got a content response timeout; balance %d\n", this, mTouchBlockBalance);
   if (mTouchBlockBalance > 0) {
     // Find the first touch block in the queue that hasn't already received
     // the content response timeout callback, and notify it.
     bool found = false;
     for (size_t i = 0; i < mTouchBlockQueue.Length(); i++) {
       if (mTouchBlockQueue[i]->TimeoutContentResponse()) {
@@ -2487,16 +2515,18 @@ AsyncPanZoomController::ContentResponseT
     } else {
       NS_WARNING("APZC received more ContentResponseTimeout calls than it has unprocessed touch blocks\n");
     }
   }
 }
 
 void
 AsyncPanZoomController::ContentReceivedTouch(bool aPreventDefault) {
+  AssertOnControllerThread();
+
   mTouchBlockBalance--;
   APZC_LOG("%p got a content response; balance %d\n", this, mTouchBlockBalance);
   if (mTouchBlockBalance < 0) {
     // Find the first touch block in the queue that hasn't already received
     // its response from content, and notify it.
     bool found = false;
     for (size_t i = 0; i < mTouchBlockQueue.Length(); i++) {
       if (mTouchBlockQueue[i]->SetContentResponse(aPreventDefault)) {
@@ -2509,32 +2539,36 @@ AsyncPanZoomController::ContentReceivedT
     } else {
       NS_WARNING("APZC received more ContentReceivedTouch calls than it has unprocessed touch blocks\n");
     }
   }
 }
 
 void
 AsyncPanZoomController::SetAllowedTouchBehavior(const nsTArray<TouchBehaviorFlags>& aBehaviors) {
+  AssertOnControllerThread();
+
   bool found = false;
   for (size_t i = 0; i < mTouchBlockQueue.Length(); i++) {
     if (mTouchBlockQueue[i]->SetAllowedTouchBehaviors(aBehaviors)) {
       found = true;
       break;
     }
   }
   if (found) {
     ProcessPendingInputBlocks();
   } else {
     NS_WARNING("APZC received more SetAllowedTouchBehavior calls than it has unprocessed touch blocks\n");
   }
 }
 
 void
 AsyncPanZoomController::ProcessPendingInputBlocks() {
+  AssertOnControllerThread();
+
   while (true) {
     TouchBlockState* curBlock = CurrentTouchBlock();
     if (!curBlock->IsReadyForHandling()) {
       break;
     }
 
     APZC_LOG("%p processing input block %p; preventDefault %d\n",
         this, curBlock, curBlock->IsDefaultPrevented());
@@ -2585,16 +2619,18 @@ AsyncPanZoomController::StartNewTouchBlo
   // Add the new block to the queue.
   mTouchBlockQueue.AppendElement(newBlock);
   return newBlock;
 }
 
 TouchBlockState*
 AsyncPanZoomController::CurrentTouchBlock()
 {
+  AssertOnControllerThread();
+
   MOZ_ASSERT(!mTouchBlockQueue.IsEmpty());
   return mTouchBlockQueue[0].get();
 }
 
 bool
 AsyncPanZoomController::HasReadyTouchBlock()
 {
   return !mTouchBlockQueue.IsEmpty() && mTouchBlockQueue[0]->IsReadyForHandling();
--- a/gfx/layers/apz/src/AsyncPanZoomController.h
+++ b/gfx/layers/apz/src/AsyncPanZoomController.h
@@ -714,16 +714,17 @@ private:
    */
   void ProcessPendingInputBlocks();
   TouchBlockState* StartNewTouchBlock(bool aCopyAllowedTouchBehaviorFromCurrent);
   TouchBlockState* CurrentTouchBlock();
   bool HasReadyTouchBlock();
 
 private:
   // The queue of touch blocks that have not yet been processed by this APZC.
+  // This member must only be accessed on the controller/UI thread.
   nsTArray<UniquePtr<TouchBlockState>> mTouchBlockQueue;
 
   // This variable requires some explanation. Strap yourself in.
   //
   // For each block of events, we do two things: (1) send the events to gecko and expect
   // exactly one call to ContentReceivedTouch in return, and (2) kick off a timeout
   // that triggers in case we don't hear from web content in a timely fashion.
   // Since events are constantly coming in, we need to be able to handle more than one
@@ -762,16 +763,18 @@ private:
   //
   // Note that each touch block internally carries flags that indicate whether or not it
   // has received a content response and/or timeout expiration. However, we cannot rely
   // on that alone to deliver these notifications to the right input block, because
   // once an input block has been processed, it can potentially be removed from the queue.
   // Therefore the information in that block is lost. An alternative approach would
   // be to keep around those blocks until they have received both the content response
   // and timeout expiration, but that involves a higher level of memory usage.
+  //
+  // This member must only be accessed on the controller/UI thread.
   int32_t mTouchBlockBalance;
 
 
   /* ===================================================================
    * The functions and members in this section are used to manage
    * fling animations and handling overscroll during a fling.
    */
 public: