summaryrefslogtreecommitdiffstats
path: root/chrome
diff options
context:
space:
mode:
authorrsesek@chromium.org <rsesek@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-02-15 12:50:35 +0000
committerrsesek@chromium.org <rsesek@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-02-15 12:50:35 +0000
commitf5558b9761d7942f07689ec8c3e38a4459464ccc (patch)
tree4acc5d5ec2c4f457c5419c5214688fd6e0351f88 /chrome
parentae9e7e94ccbaef3b1ea7830db10f7fb7d9f32bc3 (diff)
downloadchromium_src-f5558b9761d7942f07689ec8c3e38a4459464ccc.zip
chromium_src-f5558b9761d7942f07689ec8c3e38a4459464ccc.tar.gz
chromium_src-f5558b9761d7942f07689ec8c3e38a4459464ccc.tar.bz2
[Mac] Create CrTrackingArea and use it in TabStripController.
CrTrackingArea can prevent messages from being sent to the tracking area's owner, which is the source of a significant number of zombie crashes. This hopes to prevent that. BUG=48709 TEST=Crash reports go down. Review URL: http://codereview.chromium.org/6486002 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@74940 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
-rw-r--r--chrome/browser/ui/cocoa/tabs/tab_strip_controller.h5
-rw-r--r--chrome/browser/ui/cocoa/tabs/tab_strip_controller.mm11
-rw-r--r--chrome/browser/ui/cocoa/tracking_area.h37
-rw-r--r--chrome/browser/ui/cocoa/tracking_area.mm107
-rw-r--r--chrome/browser/ui/cocoa/tracking_area_unittest.mm86
-rw-r--r--chrome/chrome_browser.gypi2
-rw-r--r--chrome/chrome_tests.gypi1
7 files changed, 244 insertions, 5 deletions
diff --git a/chrome/browser/ui/cocoa/tabs/tab_strip_controller.h b/chrome/browser/ui/cocoa/tabs/tab_strip_controller.h
index 96a8e6b..69ffd41 100644
--- a/chrome/browser/ui/cocoa/tabs/tab_strip_controller.h
+++ b/chrome/browser/ui/cocoa/tabs/tab_strip_controller.h
@@ -15,6 +15,7 @@
#import "chrome/browser/ui/cocoa/url_drop_target.h"
#import "third_party/GTM/AppKit/GTMWindowSheetController.h"
+@class CrTrackingArea;
@class NewTabButton;
@class TabContentsController;
@class TabView;
@@ -71,7 +72,7 @@ class ToolbarModel;
NewTabButton* newTabButton_; // weak, obtained from the nib.
// Tracks the newTabButton_ for rollovers.
- scoped_nsobject<NSTrackingArea> newTabTrackingArea_;
+ scoped_nsobject<CrTrackingArea> newTabTrackingArea_;
scoped_ptr<TabStripModelObserverBridge> bridge_;
Browser* browser_; // weak
TabStripModel* tabStripModel_; // weak
@@ -123,7 +124,7 @@ class ToolbarModel;
float availableResizeWidth_;
// A tracking area that's the size of the tab strip used to be notified
// when the mouse moves in the tab strip
- scoped_nsobject<NSTrackingArea> trackingArea_;
+ scoped_nsobject<CrTrackingArea> trackingArea_;
TabView* hoveredTab_; // weak. Tab that the mouse is hovering over
// Array of subviews which are permanent (and which should never be removed),
diff --git a/chrome/browser/ui/cocoa/tabs/tab_strip_controller.mm b/chrome/browser/ui/cocoa/tabs/tab_strip_controller.mm
index 93048d4..eb49281 100644
--- a/chrome/browser/ui/cocoa/tabs/tab_strip_controller.mm
+++ b/chrome/browser/ui/cocoa/tabs/tab_strip_controller.mm
@@ -38,6 +38,7 @@
#import "chrome/browser/ui/cocoa/tabs/tab_strip_view.h"
#import "chrome/browser/ui/cocoa/tabs/tab_view.h"
#import "chrome/browser/ui/cocoa/tabs/throbber_view.h"
+#import "chrome/browser/ui/cocoa/tracking_area.h"
#include "chrome/browser/ui/find_bar/find_bar.h"
#include "chrome/browser/ui/find_bar/find_bar_controller.h"
#include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
@@ -122,7 +123,6 @@ private:
} // namespace
@interface TabStripController (Private)
-- (void)installTrackingArea;
- (void)addSubviewToPermanentList:(NSView*)aView;
- (void)regenerateSubviewList;
- (NSInteger)indexForContentsView:(NSView*)view;
@@ -296,6 +296,7 @@ private:
bridge_.reset(new TabStripModelObserverBridge(tabStripModel_, self));
tabContentsArray_.reset([[NSMutableArray alloc] init]);
tabArray_.reset([[NSMutableArray alloc] init]);
+ NSWindow* browserWindow = [view window];
// Important note: any non-tab subviews not added to |permanentSubviews_|
// (see |-addSubviewToPermanentList:|) will be wiped out.
@@ -321,11 +322,13 @@ private:
app::mac::GetCachedImageWithName(kNewTabPressedImage)];
newTabButtonShowingHoverImage_ = NO;
newTabTrackingArea_.reset(
- [[NSTrackingArea alloc] initWithRect:[newTabButton_ bounds]
+ [[CrTrackingArea alloc] initWithRect:[newTabButton_ bounds]
options:(NSTrackingMouseEnteredAndExited |
NSTrackingActiveAlways)
owner:self
userInfo:nil]);
+ if (browserWindow) // Nil for Browsers without a tab strip (e.g. popups).
+ [newTabTrackingArea_ clearOwnerWhenWindowWillClose:browserWindow];
[newTabButton_ addTrackingArea:newTabTrackingArea_.get()];
targetFrames_.reset([[NSMutableDictionary alloc] init]);
@@ -350,7 +353,7 @@ private:
name:NSViewFrameDidChangeNotification
object:tabStripView_];
- trackingArea_.reset([[NSTrackingArea alloc]
+ trackingArea_.reset([[CrTrackingArea alloc]
initWithRect:NSZeroRect // Ignored by NSTrackingInVisibleRect
options:NSTrackingMouseEnteredAndExited |
NSTrackingMouseMoved |
@@ -358,6 +361,8 @@ private:
NSTrackingInVisibleRect
owner:self
userInfo:nil]);
+ if (browserWindow) // Nil for Browsers without a tab strip (e.g. popups).
+ [trackingArea_ clearOwnerWhenWindowWillClose:browserWindow];
[tabStripView_ addTrackingArea:trackingArea_.get()];
// Check to see if the mouse is currently in our bounds so we can
diff --git a/chrome/browser/ui/cocoa/tracking_area.h b/chrome/browser/ui/cocoa/tracking_area.h
new file mode 100644
index 0000000..26cb2ef
--- /dev/null
+++ b/chrome/browser/ui/cocoa/tracking_area.h
@@ -0,0 +1,37 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_COCOA_TRACKING_AREA_H_
+#define CHROME_BROWSER_UI_COCOA_TRACKING_AREA_H_
+
+#import <AppKit/AppKit.h>
+
+#include "base/scoped_nsobject.h"
+
+@class CrTrackingAreaOwnerProxy;
+
+// The CrTrackingArea can be used in place of an NSTrackingArea to shut off
+// messaging to the |owner| at a specific point in time.
+@interface CrTrackingArea : NSTrackingArea {
+ @private
+ scoped_nsobject<CrTrackingAreaOwnerProxy> ownerProxy_;
+}
+
+// Designated initializer. Forwards all arguments to the superclass, but wraps
+// |owner| in a proxy object.
+- (id)initWithRect:(NSRect)rect
+ options:(NSTrackingAreaOptions)options
+ owner:(id)owner
+ userInfo:(NSDictionary*)userInfo;
+
+// Prevents any future messages from being delivered to the |owner|.
+- (void)clearOwner;
+
+// Watches |window| for its NSWindowWillCloseNotification and calls
+// |-clearOwner| when the notification is observed.
+- (void)clearOwnerWhenWindowWillClose:(NSWindow*)window;
+
+@end
+
+#endif // CHROME_BROWSER_UI_COCOA_TRACKING_AREA_H_
diff --git a/chrome/browser/ui/cocoa/tracking_area.mm b/chrome/browser/ui/cocoa/tracking_area.mm
new file mode 100644
index 0000000..692515f
--- /dev/null
+++ b/chrome/browser/ui/cocoa/tracking_area.mm
@@ -0,0 +1,107 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "chrome/browser/ui/cocoa/tracking_area.h"
+
+#include "base/logging.h"
+
+// NSTrackingArea does not retain its |owner| so CrTrackingArea wraps the real
+// owner in this proxy, which can stop forwarding messages to the owner when
+// it is no longer |alive_|.
+@interface CrTrackingAreaOwnerProxy : NSObject {
+ @private
+ // Whether or not the owner is "alive" and should forward calls to the real
+ // owner object.
+ BOOL alive_;
+
+ // The real object for which this is a proxy. Weak.
+ id owner_;
+
+ // The Class of |owner_|. When the actual object is no longer alive (and could
+ // be zombie), this allows for introspection.
+ Class ownerClass_;
+}
+@property(nonatomic, assign) BOOL alive;
+- (id)initWithOwner:(id)owner;
+@end
+
+@implementation CrTrackingAreaOwnerProxy
+
+@synthesize alive = alive_;
+
+- (id)initWithOwner:(id)owner {
+ if ((self = [super init])) {
+ alive_ = YES;
+ owner_ = owner;
+ ownerClass_ = [owner class];
+ }
+ return self;
+}
+
+- (void)forwardInvocation:(NSInvocation*)invocation {
+ if (!alive_)
+ return;
+ [invocation invokeWithTarget:owner_];
+}
+
+- (NSMethodSignature*)methodSignatureForSelector:(SEL)sel {
+ // This can be called if |owner_| is not |alive_|, so use the Class to
+ // generate the signature. |-forwardInvocation:| will block the actual call.
+ return [ownerClass_ instanceMethodSignatureForSelector:sel];
+}
+
+- (BOOL)respondsToSelector:(SEL)aSelector {
+ return [ownerClass_ instancesRespondToSelector:aSelector];
+}
+
+@end
+
+// Private Interface ///////////////////////////////////////////////////////////
+
+@interface CrTrackingArea (Private)
+- (void)windowWillClose:(NSNotification*)notif;
+@end
+
+////////////////////////////////////////////////////////////////////////////////
+
+@implementation CrTrackingArea
+
+- (id)initWithRect:(NSRect)rect
+ options:(NSTrackingAreaOptions)options
+ owner:(id)owner
+ userInfo:(NSDictionary*)userInfo {
+ scoped_nsobject<CrTrackingAreaOwnerProxy> ownerProxy(
+ [[CrTrackingAreaOwnerProxy alloc] initWithOwner:owner]);
+ if ((self = static_cast<id>([super initWithRect:rect
+ options:options
+ owner:ownerProxy.get()
+ userInfo:userInfo]))) {
+ ownerProxy_.swap(ownerProxy);
+ }
+ return self;
+}
+
+- (void)dealloc {
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+ [super dealloc];
+}
+
+- (void)clearOwner {
+ [ownerProxy_ setAlive:NO];
+}
+
+- (void)clearOwnerWhenWindowWillClose:(NSWindow*)window {
+ DCHECK(window);
+ NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
+ [center addObserver:self
+ selector:@selector(windowWillClose:)
+ name:NSWindowWillCloseNotification
+ object:window];
+}
+
+- (void)windowWillClose:(NSNotification*)notif {
+ [self clearOwner];
+}
+
+@end
diff --git a/chrome/browser/ui/cocoa/tracking_area_unittest.mm b/chrome/browser/ui/cocoa/tracking_area_unittest.mm
new file mode 100644
index 0000000..151d033
--- /dev/null
+++ b/chrome/browser/ui/cocoa/tracking_area_unittest.mm
@@ -0,0 +1,86 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/scoped_nsobject.h"
+#include "chrome/browser/ui/cocoa/cocoa_test_helper.h"
+#include "chrome/browser/ui/cocoa/objc_zombie.h"
+#import "chrome/browser/ui/cocoa/tracking_area.h"
+
+// A test object that counts the number of times a message is sent to it.
+@interface TestTrackingAreaOwner : NSObject {
+ @private
+ NSUInteger messageCount_;
+}
+@property(nonatomic, assign) NSUInteger messageCount;
+- (void)performMessage;
+@end
+
+@implementation TestTrackingAreaOwner
+@synthesize messageCount = messageCount_;
+- (void)performMessage {
+ ++messageCount_;
+}
+@end
+
+class CrTrackingAreaTest : public CocoaTest {
+ public:
+ CrTrackingAreaTest()
+ : owner_([[TestTrackingAreaOwner alloc] init]),
+ trackingArea_([[CrTrackingArea alloc]
+ initWithRect:NSMakeRect(0, 0, 100, 100)
+ options:NSTrackingMouseMoved | NSTrackingActiveInKeyWindow
+ owner:owner_.get()
+ userInfo:nil]) {
+ }
+
+ scoped_nsobject<TestTrackingAreaOwner> owner_;
+ scoped_nsobject<CrTrackingArea> trackingArea_;
+};
+
+TEST_F(CrTrackingAreaTest, OwnerForwards) {
+ [[trackingArea_ owner] performMessage];
+ EXPECT_EQ(1U, [owner_ messageCount]);
+
+ [[trackingArea_ owner] performMessage];
+ EXPECT_EQ(2U, [owner_ messageCount]);
+}
+
+TEST_F(CrTrackingAreaTest, OwnerStopsForwarding) {
+ [[trackingArea_ owner] performMessage];
+ EXPECT_EQ(1U, [owner_ messageCount]);
+
+ [trackingArea_ clearOwner];
+
+ [[trackingArea_ owner] performMessage];
+ EXPECT_EQ(1U, [owner_ messageCount]);
+}
+
+TEST_F(CrTrackingAreaTest, OwnerAutomaticallyStopsForwardingOnClose) {
+ [test_window() orderFront:nil];
+ [trackingArea_ clearOwnerWhenWindowWillClose:test_window()];
+
+ [[trackingArea_ owner] performMessage];
+ EXPECT_EQ(1U, [owner_ messageCount]);
+
+ [test_window() close];
+
+ [[trackingArea_ owner] performMessage];
+ EXPECT_EQ(1U, [owner_ messageCount]);
+}
+
+TEST_F(CrTrackingAreaTest, ZombieOwner) {
+ EXPECT_TRUE(ObjcEvilDoers::ZombieEnable(NO, 20));
+
+ [[trackingArea_ owner] performMessage];
+ EXPECT_EQ(1U, [owner_ messageCount]);
+
+ [owner_ shouldBecomeCrZombie];
+ owner_.reset();
+ [trackingArea_ clearOwner];
+
+ [[trackingArea_ owner] performMessage];
+ // Don't crash!
+
+ ObjcEvilDoers::ZombieDisable();
+}
diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi
index 3a96bfa..d4eac92 100644
--- a/chrome/chrome_browser.gypi
+++ b/chrome/chrome_browser.gypi
@@ -2834,6 +2834,8 @@
'browser/ui/cocoa/toolbar/toolbar_controller.mm',
'browser/ui/cocoa/toolbar/toolbar_view.h',
'browser/ui/cocoa/toolbar/toolbar_view.mm',
+ 'browser/ui/cocoa/tracking_area.h',
+ 'browser/ui/cocoa/tracking_area.mm',
'browser/ui/cocoa/translate/after_translate_infobar_controller.h',
'browser/ui/cocoa/translate/after_translate_infobar_controller.mm',
'browser/ui/cocoa/translate/before_translate_infobar_controller.h',
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index f0c67ba..f024eb9 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -1696,6 +1696,7 @@
'browser/ui/cocoa/toolbar/reload_button_unittest.mm',
'browser/ui/cocoa/toolbar/toolbar_controller_unittest.mm',
'browser/ui/cocoa/toolbar/toolbar_view_unittest.mm',
+ 'browser/ui/cocoa/tracking_area_unittest.mm',
'browser/ui/cocoa/translate/translate_infobar_unittest.mm',
'browser/ui/cocoa/vertical_gradient_view_unittest.mm',
'browser/ui/cocoa/view_resizer_pong.h',