Bug 688208 - Guard against accessing destroyed object [r=mfinkle]
authorKartikaya Gupta <kgupta@mozilla.com>
Tue, 22 Nov 2011 15:31:49 -0500
changeset 83837 d10af1d1d98583a021ee89bac1ecf526c22e451a
parent 83836 d34c48393e3b8c14cd4835b9b3ba77ef52105c01
child 83838 1a66c313f23f9d9bf69257775ab6f09df27da1dc
push id519
push userakeybl@mozilla.com
push dateWed, 01 Feb 2012 00:38:35 +0000
treeherdermozilla-beta@788ea1ef610b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmfinkle
bugs688208
milestone11.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 688208 - Guard against accessing destroyed object [r=mfinkle] The nsWindow for Android often dispatches events but doesn't check for window destruction afterwards. Ensure that necessary destruction checks and kungFuDeathGrips are in place to avoid accessing a destroyed object.
widget/src/android/nsWindow.cpp
--- a/widget/src/android/nsWindow.cpp
+++ b/widget/src/android/nsWindow.cpp
@@ -268,16 +268,18 @@ nsWindow::Create(nsIWidget *aParent,
     mSwipeMinDistance = SWIPE_MIN_DISTANCE_INCHES * dpi;
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsWindow::Destroy(void)
 {
+    nsBaseWidget::mOnDestroyCalled = true;
+
     for (PRUint32 i = 0; i < mChildren.Length(); ++i) {
         // why do we still have children?
         ALOG("### Warning: Destroying window %p and reparenting child %p to null!", (void*)this, (void*)mChildren[i]);
         mChildren[i]->SetParent(nsnull);
     }
 
     if (IsTopLevel())
         gTopLevelWindows.RemoveElement(this);
@@ -634,33 +636,45 @@ nsWindow::BringToFront()
     if (existingTopWindow && FindTopLevel() == TopWindow())
         return;
 
     if (!IsTopLevel()) {
         FindTopLevel()->BringToFront();
         return;
     }
 
+    nsRefPtr<nsWindow> kungFuDeathGrip(this);
+
     nsWindow *oldTop = nsnull;
+    nsWindow *newTop = this;
     if (!gTopLevelWindows.IsEmpty())
         oldTop = gTopLevelWindows[0];
 
     gTopLevelWindows.RemoveElement(this);
     gTopLevelWindows.InsertElementAt(0, this);
 
     if (oldTop) {
         nsGUIEvent event(true, NS_DEACTIVATE, oldTop);
         DispatchEvent(&event);
     }
 
-    nsGUIEvent event(true, NS_ACTIVATE, this);
+    if (Destroyed()) {
+        // somehow the deactivate event handler destroyed this window.
+        // try to recover by grabbing the next window in line and activating
+        // that instead
+        if (gTopLevelWindows.IsEmpty())
+            return;
+        newTop = gTopLevelWindows[0];
+    }
+
+    nsGUIEvent event(true, NS_ACTIVATE, newTop);
     DispatchEvent(&event);
 
     // force a window resize
-    nsAppShell::gAppShell->ResendLastResizeEvent(this);
+    nsAppShell::gAppShell->ResendLastResizeEvent(newTop);
     RedrawAll();
 }
 
 NS_IMETHODIMP
 nsWindow::GetScreenBounds(nsIntRect &aRect)
 {
     nsIntPoint p = WidgetToScreenOffset();
 
@@ -980,16 +994,17 @@ nsWindow::DrawTo(gfxASurface *targetSurf
 }
 
 bool
 nsWindow::DrawTo(gfxASurface *targetSurface, const nsIntRect &invalidRect)
 {
     if (!mIsVisible)
         return false;
 
+    nsRefPtr<nsWindow> kungFuDeathGrip(this);
     nsEventStatus status;
     nsIntRect boundsRect(0, 0, mBounds.width, mBounds.height);
 
     // Figure out if any of our children cover this widget completely
     PRInt32 coveringChildIndex = -1;
     for (PRUint32 i = 0; i < mChildren.Length(); ++i) {
         if (mChildren[i]->mBounds.IsEmpty())
             continue;
@@ -1242,16 +1257,17 @@ nsWindow::OnDraw(AndroidGeckoEvent *ae)
 void
 nsWindow::OnSizeChanged(const gfxIntSize& aSize)
 {
     int w = aSize.width;
     int h = aSize.height;
 
     ALOG("nsWindow: %p OnSizeChanged [%d %d]", (void*)this, w, h);
 
+    nsRefPtr<nsWindow> kungFuDeathGrip(this);
     nsSizeEvent event(true, NS_SIZE, this);
     InitEvent(event);
 
     nsIntRect wsz(0, 0, w, h);
     event.windowSize = &wsz;
     event.mWinWidth = w;
     event.mWinHeight = h;
 
@@ -1316,16 +1332,17 @@ nsWindow::OnMotionEvent(AndroidGeckoEven
         case AndroidMotionEvent::ACTION_CANCEL:
             msg = NS_MOUSE_BUTTON_UP;
             break;
 
         default:
             return;
     }
 
+    nsRefPtr<nsWindow> kungFuDeathGrip(this);
     nsIntPoint pt(ae->P0());
     nsIntPoint offset = WidgetToScreenOffset();
 
     //ALOG("#### motion pt: %d %d offset: %d %d", pt.x, pt.y, offset.x, offset.y);
 
     pt.x -= offset.x;
     pt.y -= offset.y;
 
@@ -1348,16 +1365,18 @@ send_again:
     event.button = nsMouseEvent::eLeftButton;
 
     if (msg != NS_MOUSE_MOVE)
         event.clickCount = 1;
 
     // XXX add the double-click handling logic here
 
     DispatchEvent(&event);
+    if (Destroyed())
+        return;
 
     if (msg == NS_MOUSE_BUTTON_DOWN) {
         msg = NS_MOUSE_MOVE;
         goto send_again;
     }
 }
 
 static double
@@ -1397,17 +1416,20 @@ void nsWindow::OnMultitouchEvent(Android
             pinchDelta = pinchDist - mStartDist;
             mStartPoint = nsnull;
             break;
         default:
             return;
     }
 
     if (!mGestureFinished) {
+        nsRefPtr<nsWindow> kungFuDeathGrip(this);
         DispatchGestureEvent(msg, 0, pinchDelta, refPoint, ae->Time());
+        if (Destroyed())
+            return;
 
         // If the cumulative pinch delta goes past the threshold, treat this
         // as a pinch only, and not a swipe.
         if (fabs(pinchDist - mStartDist) > mSwipeMaxPinchDelta)
             mStartPoint = nsnull;
 
         // If we have traveled more than SWIPE_MIN_DISTANCE from the start
         // point, stop the pinch gesture and fire a swipe event.
@@ -1424,16 +1446,18 @@ void nsWindow::OnMultitouchEvent(Android
                 if (motion.y < -swipeDistance/2)
                     direction |= nsIDOMSimpleGestureEvent::DIRECTION_UP;
                 if (motion.y > swipeDistance/2)
                     direction |= nsIDOMSimpleGestureEvent::DIRECTION_DOWN;
 
                 // Finish the pinch gesture, then fire the swipe event:
                 msg = NS_SIMPLE_GESTURE_MAGNIFY;
                 DispatchGestureEvent(msg, 0, pinchDist - mStartDist, refPoint, ae->Time());
+                if (Destroyed())
+                    return;
                 msg = NS_SIMPLE_GESTURE_SWIPE;
                 DispatchGestureEvent(msg, direction, 0, refPoint, ae->Time());
 
                 // Don't generate any more gesture events for this touch.
                 mGestureFinished = true;
             }
         }
     }
@@ -1626,16 +1650,17 @@ nsWindow::InitKeyEvent(nsKeyEvent& event
 
     if (gMenu)
         gMenuConsumed = true;
 }
 
 void
 nsWindow::HandleSpecialKey(AndroidGeckoEvent *ae)
 {
+    nsRefPtr<nsWindow> kungFuDeathGrip(this);
     nsCOMPtr<nsIAtom> command;
     bool isDown = ae->Action() == AndroidKeyEvent::ACTION_DOWN;
     bool isLongPress = !!(ae->Flags() & AndroidKeyEvent::FLAG_LONG_PRESS);
     bool doCommand = false;
     PRUint32 keyCode = ae->KeyCode();
 
     if (isDown) {
         switch (keyCode) {
@@ -1687,16 +1712,17 @@ nsWindow::HandleSpecialKey(AndroidGeckoE
         InitEvent(event);
         DispatchEvent(&event);
     }
 }
 
 void
 nsWindow::OnKeyEvent(AndroidGeckoEvent *ae)
 {
+    nsRefPtr<nsWindow> kungFuDeathGrip(this);
     PRUint32 msg;
     switch (ae->Action()) {
     case AndroidKeyEvent::ACTION_DOWN:
         msg = NS_KEY_DOWN;
         break;
     case AndroidKeyEvent::ACTION_UP:
         msg = NS_KEY_UP;
         break;
@@ -1729,16 +1755,18 @@ nsWindow::OnKeyEvent(AndroidGeckoEvent *
         return;
     }
 
     nsEventStatus status;
     nsKeyEvent event(true, msg, this);
     InitKeyEvent(event, *ae);
     DispatchEvent(&event, status);
 
+    if (Destroyed())
+        return;
     if (!firePress)
         return;
 
     nsKeyEvent pressEvent(true, NS_KEY_PRESS, this);
     InitKeyEvent(pressEvent, *ae);
     if (status == nsEventStatus_eConsumeNoDefault) {
         pressEvent.flags |= NS_EVENT_FLAG_NO_DEFAULT;
     }
@@ -1776,16 +1804,17 @@ nsWindow::OnIMEAddRange(AndroidGeckoEven
         ((ae->RangeBackColor() >> 24) & 0xff));
     mIMERanges.AppendElement(range);
     return;
 }
 
 void
 nsWindow::OnIMEEvent(AndroidGeckoEvent *ae)
 {
+    nsRefPtr<nsWindow> kungFuDeathGrip(this);
     switch (ae->Action()) {
     case AndroidGeckoEvent::IME_COMPOSITION_END:
         {
             ALOGIME("IME: IME_COMPOSITION_END");
             nsCompositionEvent event(true, NS_COMPOSITION_END, this);
             InitEvent(event, nsnull);
             event.data = mIMELastDispatchedComposingText;
             mIMELastDispatchedComposingText.Truncate();
@@ -1821,19 +1850,18 @@ nsWindow::OnIMEEvent(AndroidGeckoEvent *
                 event.theText != mIMELastDispatchedComposingText) {
                 nsCompositionEvent compositionUpdate(true,
                                                      NS_COMPOSITION_UPDATE,
                                                      this);
                 InitEvent(compositionUpdate, nsnull);
                 compositionUpdate.data = event.theText;
                 mIMELastDispatchedComposingText = event.theText;
                 DispatchEvent(&compositionUpdate);
-                // XXX We must check whether this widget is destroyed or not
-                //     before dispatching next event.  However, Android's
-                //     nsWindow has never checked it...
+                if (Destroyed())
+                    return;
             }
 
             ALOGIME("IME: IME_SET_TEXT: l=%u, r=%u",
                 event.theText.Length(), mIMERanges.Length());
 
             DispatchEvent(&event);
             mIMERanges.Clear();
         }
@@ -1944,16 +1972,18 @@ nsWindow::UserActivity()
 
 NS_IMETHODIMP
 nsWindow::ResetInputState()
 {
     //ALOGIME("IME: ResetInputState: s=%d", aState);
 
     // Cancel composition on Gecko side
     if (mIMEComposing) {
+        nsRefPtr<nsWindow> kungFuDeathGrip(this);
+
         nsTextEvent textEvent(true, NS_TEXT_TEXT, this);
         InitEvent(textEvent, nsnull);
         textEvent.theText = mIMEComposingText;
         DispatchEvent(&textEvent);
         mIMEComposingText.Truncate(0);
 
         nsCompositionEvent event(true, NS_COMPOSITION_END, this);
         InitEvent(event, nsnull);
@@ -1998,16 +2028,18 @@ nsWindow::GetInputContext()
 
 NS_IMETHODIMP
 nsWindow::CancelIMEComposition()
 {
     ALOGIME("IME: CancelIMEComposition");
 
     // Cancel composition on Gecko side
     if (mIMEComposing) {
+        nsRefPtr<nsWindow> kungFuDeathGrip(this);
+
         nsTextEvent textEvent(true, NS_TEXT_TEXT, this);
         InitEvent(textEvent, nsnull);
         DispatchEvent(&textEvent);
         mIMEComposingText.Truncate(0);
 
         nsCompositionEvent compEvent(true, NS_COMPOSITION_END, this);
         InitEvent(compEvent, nsnull);
         DispatchEvent(&compEvent);
@@ -2038,16 +2070,17 @@ nsWindow::OnIMETextChange(PRUint32 aStar
 {
     ALOGIME("IME: OnIMETextChange: s=%d, oe=%d, ne=%d",
             aStart, aOldEnd, aNewEnd);
 
     // A quirk in Android makes it necessary to pass the whole text.
     // The more efficient way would have been passing the substring from index
     // aStart to index aNewEnd
 
+    nsRefPtr<nsWindow> kungFuDeathGrip(this);
     nsQueryContentEvent event(true, NS_QUERY_TEXT_CONTENT, this);
     InitEvent(event, nsnull);
     event.InitForQueryTextContent(0, PR_UINT32_MAX);
 
     DispatchEvent(&event);
     if (!event.mSucceeded)
         return NS_OK;
 
@@ -2058,16 +2091,17 @@ nsWindow::OnIMETextChange(PRUint32 aStar
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsWindow::OnIMESelectionChange(void)
 {
     ALOGIME("IME: OnIMESelectionChange");
 
+    nsRefPtr<nsWindow> kungFuDeathGrip(this);
     nsQueryContentEvent event(true, NS_QUERY_SELECTED_TEXT, this);
     InitEvent(event, nsnull);
 
     DispatchEvent(&event);
     if (!event.mSucceeded)
         return NS_OK;
 
     AndroidBridge::NotifyIMEChange(nsnull, 0, int(event.mReply.mOffset),