diff options
author | rsesek@chromium.org <rsesek@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-02-15 12:50:35 +0000 |
---|---|---|
committer | rsesek@chromium.org <rsesek@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-02-15 12:50:35 +0000 |
commit | f5558b9761d7942f07689ec8c3e38a4459464ccc (patch) | |
tree | 4acc5d5ec2c4f457c5419c5214688fd6e0351f88 /chrome | |
parent | ae9e7e94ccbaef3b1ea7830db10f7fb7d9f32bc3 (diff) | |
download | chromium_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.h | 5 | ||||
-rw-r--r-- | chrome/browser/ui/cocoa/tabs/tab_strip_controller.mm | 11 | ||||
-rw-r--r-- | chrome/browser/ui/cocoa/tracking_area.h | 37 | ||||
-rw-r--r-- | chrome/browser/ui/cocoa/tracking_area.mm | 107 | ||||
-rw-r--r-- | chrome/browser/ui/cocoa/tracking_area_unittest.mm | 86 | ||||
-rw-r--r-- | chrome/chrome_browser.gypi | 2 | ||||
-rw-r--r-- | chrome/chrome_tests.gypi | 1 |
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', |