diff options
author | kuan@chromium.org <kuan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-12-15 19:52:37 +0000 |
---|---|---|
committer | kuan@chromium.org <kuan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-12-15 19:52:37 +0000 |
commit | 27286e7a5905dcde71f834430b62235a40a2d020 (patch) | |
tree | e8153a6374f0dc66d369bf9be1eb83b164948f59 /chrome/browser | |
parent | 7d505dd8c384bafe4412c9625c358347bfc5770a (diff) | |
download | chromium_src-27286e7a5905dcde71f834430b62235a40a2d020.zip chromium_src-27286e7a5905dcde71f834430b62235a40a2d020.tar.gz chromium_src-27286e7a5905dcde71f834430b62235a40a2d020.tar.bz2 |
mac: totally revamp "Aw Snap" page, implement new "Learn more" link, add unittests.
- totally revamp "Aw Snap" page to use xib resource, to reduce initialization code
- new SadTabController:
- controls SadTabView, via xib
- uses TabContents in init, so as to launch url for link; used to use BrowserList::GetLastActive but that's null in chrome-frame release
- if TabContents is nil, remove link in view
- encapsulates the setting up and removing of its SadTabView within and shield them from TabContentsView, which only access the controller
- SadTabView repositions and resizes subviews when browser window is resized, including (un)wrapping of message.
- action for link is decoupled from target to facilitate unittesting
- new SadTabControllerTest that tests initing controller with and without TabContents, and clicking on link
BUG=27212
TEST=Verify that "Aw Snap" page shows up correctly, with the new "Learn more" link centered beneath the message. Contents should be centered in window when the latter resizes. If necessary, message should be wrapped, or unwrapped if previously wrapped and new width can accommodate.
Review URL: http://codereview.chromium.org/432015
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@34585 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser')
-rw-r--r-- | chrome/browser/cocoa/browser_window_controller.mm | 17 | ||||
-rw-r--r-- | chrome/browser/cocoa/sad_tab_controller.h | 32 | ||||
-rw-r--r-- | chrome/browser/cocoa/sad_tab_controller.mm | 48 | ||||
-rw-r--r-- | chrome/browser/cocoa/sad_tab_controller_unittest.mm | 101 | ||||
-rw-r--r-- | chrome/browser/cocoa/sad_tab_view.h | 15 | ||||
-rw-r--r-- | chrome/browser/cocoa/sad_tab_view.mm | 168 | ||||
-rw-r--r-- | chrome/browser/tab_contents/tab_contents_view_mac.h | 4 | ||||
-rw-r--r-- | chrome/browser/tab_contents/tab_contents_view_mac.mm | 22 |
8 files changed, 330 insertions, 77 deletions
diff --git a/chrome/browser/cocoa/browser_window_controller.mm b/chrome/browser/cocoa/browser_window_controller.mm index 43416bd..3af0816 100644 --- a/chrome/browser/cocoa/browser_window_controller.mm +++ b/chrome/browser/cocoa/browser_window_controller.mm @@ -36,6 +36,7 @@ #include "chrome/browser/cocoa/find_bar_bridge.h" #import "chrome/browser/cocoa/fullscreen_window.h" #import "chrome/browser/cocoa/infobar_container_controller.h" +#import "chrome/browser/cocoa/sad_tab_controller.h" #import "chrome/browser/cocoa/status_bubble_mac.h" #import "chrome/browser/cocoa/tab_strip_model_observer_bridge.h" #import "chrome/browser/cocoa/tab_strip_view.h" @@ -48,6 +49,7 @@ #include "chrome/common/pref_names.h" #include "chrome/common/pref_service.h" #include "grit/generated_resources.h" +#include "grit/locale_settings.h" #include "grit/theme_resources.h" #import "third_party/GTM/AppKit/GTMTheme.h" @@ -1337,6 +1339,21 @@ willPositionSheet:(NSWindow*)sheet } } +// Handle the openLearnMoreAboutCrashLink: action from SadTabController when +// "Learn more" link in "Aw snap" page (i.e. crash page or sad tab) is +// clicked. Decoupling the action from its target makes unitestting possible. +- (void)openLearnMoreAboutCrashLink:(id)sender { + if ([sender isKindOfClass:[SadTabController class]]) { + SadTabController* sad_tab = static_cast<SadTabController*>(sender); + TabContents* tab_contents = [sad_tab tabContents]; + if (tab_contents) { + std::string linkUrl = l10n_util::GetStringUTF8(IDS_CRASH_REASON_URL); + tab_contents->OpenURL(GURL(linkUrl), GURL(), CURRENT_TAB, + PageTransition::LINK); + } + } +} + // Delegate method called when window did move. (See below for why we don't use // |-windowWillMove:|, which is called less frequently than |-windowDidMove| // instead.) diff --git a/chrome/browser/cocoa/sad_tab_controller.h b/chrome/browser/cocoa/sad_tab_controller.h new file mode 100644 index 0000000..148729a --- /dev/null +++ b/chrome/browser/cocoa/sad_tab_controller.h @@ -0,0 +1,32 @@ +// Copyright (c) 2009 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_COCOA_SAD_TAB_CONTROLLER_H_ +#define CHROME_BROWSER_COCOA_SAD_TAB_CONTROLLER_H_ + +#import <Cocoa/Cocoa.h> + +class TabContents; + +// A controller class that manages the SadTabView (aka "Aw Snap" or crash page). +@interface SadTabController : NSViewController { + @private + TabContents* tabContents_; // Weak reference. +} + +// Designated initializer is initWithTabContents. +- (id)initWithTabContents:(TabContents*)someTabContents + superview:(NSView*)superview; + +// This action just calls the NSApp sendAction to get it into the standard +// Cocoa action processing. +- (IBAction)openLearnMoreAboutCrashLink:(id)sender; + +// Returns a weak reference to the TabContents whose TabContentsView created +// this SadTabController. +- (TabContents*)tabContents; + +@end + +#endif // CHROME_BROWSER_COCOA_SAD_TAB_CONTROLLER_H_ diff --git a/chrome/browser/cocoa/sad_tab_controller.mm b/chrome/browser/cocoa/sad_tab_controller.mm new file mode 100644 index 0000000..c0c832a --- /dev/null +++ b/chrome/browser/cocoa/sad_tab_controller.mm @@ -0,0 +1,48 @@ +// Copyright (c) 2009 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 "chrome/browser/cocoa/sad_tab_controller.h" + +#include "base/mac_util.h" +#import "chrome/browser/cocoa/sad_tab_view.h" + +@implementation SadTabController + +- (id)initWithTabContents:(TabContents*)someTabContents + superview:(NSView*)superview { + if ((self = [super initWithNibName:@"SadTab" + bundle:mac_util::MainAppBundle()])) { + tabContents_ = someTabContents; + + NSView* view = [self view]; + [superview addSubview:view]; + [view setFrame:[superview bounds]]; + } + + return self; +} + +- (void)awakeFromNib { + // If tab_contents_ is nil, ask view to remove link. + if (!tabContents_) { + SadTabView* sad_view = static_cast<SadTabView*>([self view]); + [sad_view removeLinkButton]; + } +} + +- (void)dealloc { + [[self view] removeFromSuperview]; + [super dealloc]; +} + +- (TabContents*)tabContents { + return tabContents_; +} + +- (void)openLearnMoreAboutCrashLink:(id)sender { + // Send the action up through the responder chain. + [NSApp sendAction:@selector(openLearnMoreAboutCrashLink:) to:nil from:self]; +} + +@end diff --git a/chrome/browser/cocoa/sad_tab_controller_unittest.mm b/chrome/browser/cocoa/sad_tab_controller_unittest.mm new file mode 100644 index 0000000..9395cbb --- /dev/null +++ b/chrome/browser/cocoa/sad_tab_controller_unittest.mm @@ -0,0 +1,101 @@ +// Copyright (c) 2009 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/cocoa/browser_test_helper.h" +#import "chrome/browser/cocoa/cocoa_test_helper.h" +#import "chrome/browser/cocoa/sad_tab_controller.h" +#import "chrome/browser/cocoa/sad_tab_view.h" +#include "chrome/browser/renderer_host/site_instance.h" +#include "chrome/browser/tab_contents/tab_contents.h" + +@interface SadTabView (ExposedForTesting) +// Implementation is below. +- (NSButton*)linkButton; +@end + +@implementation SadTabView (ExposedForTesting) +- (NSButton*)linkButton { + return linkButton_; +} +@end + +namespace { + +class SadTabControllerTest : public CocoaTest { + public: + SadTabControllerTest() { + link_clicked_ = false; + } + + TabContents* CreateTabContents() { + SiteInstance* instance = + SiteInstance::CreateSiteInstance(browser_helper_.profile()); + TabContents* tab_contents = new TabContents(browser_helper_.profile(), + instance, MSG_ROUTING_NONE, NULL); + return tab_contents; + } + + // Creates the controller and adds its view to contents, caller has ownership. + SadTabController* CreateController(TabContents* tab_contents) { + NSView* contentView = [test_window() contentView]; + SadTabController* controller = + [[SadTabController alloc] initWithTabContents:tab_contents + superview:contentView]; + EXPECT_TRUE(controller); + NSView* view = [controller view]; + EXPECT_TRUE(view); + + return controller; + } + + NSButton* GetLinkButton(SadTabController* controller) { + SadTabView* view = static_cast<SadTabView*>([controller view]); + return ([view linkButton]); + } + + BrowserTestHelper browser_helper_; + static bool link_clicked_; +}; + +TEST_F(SadTabControllerTest, TestWithTabContents) { + scoped_ptr<TabContents> tab_contents(CreateTabContents()); + scoped_nsobject<SadTabController> + controller(CreateController(tab_contents.get())); + EXPECT_TRUE(controller); + NSButton* link = GetLinkButton(controller); + EXPECT_TRUE(link); +} + +TEST_F(SadTabControllerTest, TestWithoutTabContents) { + scoped_nsobject<SadTabController> controller(CreateController(NULL)); + EXPECT_TRUE(controller); + NSButton* link = GetLinkButton(controller); + EXPECT_FALSE(link); +} + +TEST_F(SadTabControllerTest, TestClickOnLink) { + scoped_ptr<TabContents> tab_contents(CreateTabContents()); + scoped_nsobject<SadTabController> + controller(CreateController(tab_contents.get())); + NSButton* link = GetLinkButton(controller); + EXPECT_TRUE(link); + EXPECT_FALSE(link_clicked_); + [link performClick:link]; + EXPECT_TRUE(link_clicked_); +} + +} // namespace + +@implementation NSApplication (SadTabControllerUnitTest) +// Add handler for the openLearnMoreAboutCrashLink: action to NSApp for testing +// purposes. Normally this would be sent up the responder tree correctly, but +// since tests run in the background, key window and main window are never set +// on NSApplication. Adding it to NSApplication directly removes the need for +// worrying about what the current window with focus is. +- (void)openLearnMoreAboutCrashLink:(id)sender { + SadTabControllerTest::link_clicked_ = true; +} + +@end diff --git a/chrome/browser/cocoa/sad_tab_view.h b/chrome/browser/cocoa/sad_tab_view.h index d003536..f89e679 100644 --- a/chrome/browser/cocoa/sad_tab_view.h +++ b/chrome/browser/cocoa/sad_tab_view.h @@ -5,18 +5,31 @@ #ifndef CHROME_BROWSER_COCOA_SAD_TAB_VIEW_H_ #define CHROME_BROWSER_COCOA_SAD_TAB_VIEW_H_ +#include "base/scoped_nsobject.h" #include "chrome/browser/cocoa/base_view.h" #import <Cocoa/Cocoa.h> -// A view that displays the "sad tab". +@class HyperlinkButtonCell; +// A view that displays the "sad tab" (aka crash page). @interface SadTabView : BaseView { @private + IBOutlet NSImageView* image_; + IBOutlet NSTextField* title_; + IBOutlet NSTextField* message_; + IBOutlet NSButton* linkButton_; + IBOutlet HyperlinkButtonCell* linkCell_; + + scoped_nsobject<NSColor> backgroundColor_; + NSSize messageSize_; } // Designated initializer is -initWithFrame: . +// Called by SadTabController to remove link button. +- (void)removeLinkButton; + @end #endif // CHROME_BROWSER_COCOA_SAD_TAB_VIEW_H_ diff --git a/chrome/browser/cocoa/sad_tab_view.mm b/chrome/browser/cocoa/sad_tab_view.mm index 1143025..ae018f5 100644 --- a/chrome/browser/cocoa/sad_tab_view.mm +++ b/chrome/browser/cocoa/sad_tab_view.mm @@ -4,78 +4,122 @@ #include "chrome/browser/cocoa/sad_tab_view.h" -#include "app/l10n_util_mac.h" #include "app/resource_bundle.h" -#include "base/sys_string_conversions.h" -#include "grit/generated_resources.h" +#include "base/logging.h" +#import "chrome/browser/cocoa/hyperlink_button_cell.h" #include "grit/theme_resources.h" - -static const int kSadTabOffset = -64; -static const int kIconTitleSpacing = 20; -static const int kTitleMessageSpacing = 15; +#import "third_party/GTM/AppKit/GTMUILocalizerAndLayoutTweaker.h" + +// Offset above vertical middle of page where contents of page start. +static const CGFloat kSadTabOffset = -64; +// Padding between icon and title. +static const CGFloat kIconTitleSpacing = 20; +// Padding between title and message. +static const CGFloat kTitleMessageSpacing = 15; +// Padding between message and link. +static const CGFloat kMessageLinkSpacing = 15; +// Paddings on left and right of page. +static const CGFloat kTabHorzMargin = 13; @implementation SadTabView -- (void)drawRect:(NSRect)dirtyRect { +- (void)awakeFromNib { + // Load resource for image and set it. ResourceBundle& rb = ResourceBundle::GetSharedInstance(); - NSImage* sadTabImage = rb.GetNSImageNamed(IDR_SAD_TAB); - DCHECK(sadTabImage); - NSString* title = l10n_util::GetNSStringWithFixup(IDS_SAD_TAB_TITLE); - NSString* message = l10n_util::GetNSStringWithFixup(IDS_SAD_TAB_MESSAGE); - - NSColor* textColor = [NSColor whiteColor]; - NSColor* backgroundColor = [NSColor colorWithCalibratedRed:(35.0f/255.0f) - green:(48.0f/255.0f) - blue:(64.0f/255.0f) - alpha:1.0]; - - // Layout + NSImage* image = rb.GetNSImageNamed(IDR_SAD_TAB); + DCHECK(image); + [image_ setImage:image]; + + // Set font for title. NSFont* titleFont = [NSFont boldSystemFontOfSize:[NSFont systemFontSize]]; + [title_ setFont:titleFont]; + + // Set font for message. NSFont* messageFont = [NSFont systemFontOfSize:[NSFont smallSystemFontSize]]; + [message_ setFont:messageFont]; + + // If necessary, set font and color for link. + if (linkButton_) { + [linkButton_ setFont:messageFont]; + [linkCell_ setTextColor:[NSColor whiteColor]]; + } + + // Initialize background color. + NSColor* backgroundColor = [[NSColor colorWithCalibratedRed:(35.0f/255.0f) + green:(48.0f/255.0f) + blue:(64.0f/255.0f) + alpha:1.0] retain]; + backgroundColor_.reset(backgroundColor); +} - NSDictionary* titleAttrs = [NSDictionary dictionaryWithObjectsAndKeys: - titleFont, NSFontAttributeName, - textColor, NSForegroundColorAttributeName, - nil]; - NSDictionary* messageAttrs = [NSDictionary dictionaryWithObjectsAndKeys: - messageFont, NSFontAttributeName, - textColor, NSForegroundColorAttributeName, - nil]; - - NSAttributedString* titleString = - [[[NSAttributedString alloc] initWithString:title - attributes:titleAttrs] autorelease]; - NSAttributedString* messageString = - [[[NSAttributedString alloc] initWithString:message - attributes:messageAttrs] autorelease]; - - NSRect viewBounds = [self bounds]; - - NSSize sadTabImageSize = [sadTabImage size]; - CGFloat iconWidth = sadTabImageSize.width; - CGFloat iconHeight = sadTabImageSize.height; - CGFloat iconX = (viewBounds.size.width - iconWidth) / 2; +- (void)drawRect:(NSRect)dirtyRect { + // Paint background. + [backgroundColor_ set]; + NSRectFill(dirtyRect); +} + +- (void)resizeSubviewsWithOldSize:(NSSize)oldSize { + NSRect newBounds = [self bounds]; + CGFloat maxWidth = NSWidth(newBounds) - (kTabHorzMargin * 2); + BOOL callSizeToFit = (messageSize_.width == 0); + + // Set new frame origin for image. + NSRect iconFrame = [image_ frame]; + CGFloat iconX = (maxWidth - NSWidth(iconFrame)) / 2; CGFloat iconY = - ((viewBounds.size.height - iconHeight) / 2) - kSadTabOffset; - - NSSize titleSize = [titleString size]; - CGFloat titleX = (viewBounds.size.width - titleSize.width) / 2; - CGFloat titleY = iconY - kIconTitleSpacing - titleSize.height; - - NSSize messageSize = [messageString size]; - CGFloat messageX = (viewBounds.size.width - messageSize.width) / 2; - CGFloat messageY = titleY - kTitleMessageSpacing - messageSize.height; - - // Paint - [backgroundColor set]; - NSRectFill(viewBounds); - - [sadTabImage drawAtPoint:NSMakePoint(iconX, iconY) - fromRect:NSZeroRect - operation:NSCompositeSourceOver - fraction:1.0f]; - [titleString drawAtPoint:NSMakePoint(titleX, titleY)]; - [messageString drawAtPoint:NSMakePoint(messageX, messageY)]; + MIN(((NSHeight(newBounds) - NSHeight(iconFrame)) / 2) - kSadTabOffset, + NSHeight(newBounds) - NSHeight(iconFrame)); + [image_ setFrameOrigin:NSMakePoint(iconX, iconY)]; + + // Set new frame origin for title. + if (callSizeToFit) + [title_ sizeToFit]; + NSRect titleFrame = [title_ frame]; + CGFloat titleX = (maxWidth - NSWidth(titleFrame)) / 2; + CGFloat titleY = iconY - kIconTitleSpacing - NSHeight(titleFrame); + [title_ setFrameOrigin:NSMakePoint(titleX, titleY)]; + + // Set new frame for message, wrapping or unwrapping the text if necessary. + if (callSizeToFit) { + [message_ sizeToFit]; + messageSize_ = [message_ frame].size; + } + NSRect messageFrame = [message_ frame]; + if (messageSize_.width > maxWidth) { // Need to wrap message. + [message_ setFrameSize:NSMakeSize(maxWidth, messageSize_.height)]; + CGFloat heightChange = + [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:message_]; + messageFrame.size.width = maxWidth; + messageFrame.size.height = messageSize_.height + heightChange; + messageFrame.origin.x = kTabHorzMargin; + } else { + if (!callSizeToFit) { + [message_ sizeToFit]; + messageFrame = [message_ frame]; + } + messageFrame.origin.x = (maxWidth - NSWidth(messageFrame)) / 2; + } + messageFrame.origin.y = + titleY - kTitleMessageSpacing - NSHeight(messageFrame); + [message_ setFrame:messageFrame]; + + if (linkButton_) { + if (callSizeToFit) + [linkButton_ sizeToFit]; + // Set new frame origin for link. + NSRect linkFrame = [linkButton_ frame]; + CGFloat linkX = (maxWidth - NSWidth(linkFrame)) / 2; + CGFloat linkY = + NSMinY(messageFrame) - kMessageLinkSpacing - NSHeight(linkFrame); + [linkButton_ setFrameOrigin:NSMakePoint(linkX, linkY)]; + } +} + +- (void)removeLinkButton { + if (linkButton_) { + [linkButton_ removeFromSuperview]; + linkButton_ = nil; + } } @end diff --git a/chrome/browser/tab_contents/tab_contents_view_mac.h b/chrome/browser/tab_contents/tab_contents_view_mac.h index c3801a7..1e8e174b 100644 --- a/chrome/browser/tab_contents/tab_contents_view_mac.h +++ b/chrome/browser/tab_contents/tab_contents_view_mac.h @@ -19,7 +19,7 @@ class FilePath; class FindBarMac; @class FocusTracker; -@class SadTabView; +@class SadTabController; class TabContentsViewMac; @class WebDragSource; @class WebDropTarget; @@ -101,7 +101,7 @@ class TabContentsViewMac : public TabContentsView, // Used to render the sad tab. This will be non-NULL only when the sad tab is // visible. - scoped_nsobject<SadTabView> sad_tab_; + scoped_nsobject<SadTabController> sad_tab_; // The page content's intrinsic width. int preferred_width_; diff --git a/chrome/browser/tab_contents/tab_contents_view_mac.mm b/chrome/browser/tab_contents/tab_contents_view_mac.mm index 14b16c6..8c1f3d6 100644 --- a/chrome/browser/tab_contents/tab_contents_view_mac.mm +++ b/chrome/browser/tab_contents/tab_contents_view_mac.mm @@ -14,7 +14,7 @@ #import "chrome/browser/cocoa/chrome_browser_window.h" #import "chrome/browser/cocoa/browser_window_controller.h" #include "chrome/browser/global_keyboard_shortcuts_mac.h" -#include "chrome/browser/cocoa/sad_tab_view.h" +#include "chrome/browser/cocoa/sad_tab_controller.h" #import "chrome/browser/cocoa/web_drag_source.h" #import "chrome/browser/cocoa/web_drop_target.h" #include "chrome/browser/renderer_host/render_view_host_factory.h" @@ -158,13 +158,14 @@ void TabContentsViewMac::SetPageTitle(const std::wstring& title) { void TabContentsViewMac::OnTabCrashed() { if (!sad_tab_.get()) { - SadTabView* view = [[SadTabView alloc] initWithFrame:NSZeroRect]; - sad_tab_.reset(view); - - // Set as the dominant child. - [cocoa_view_.get() addSubview:view]; - [view setFrame:[cocoa_view_.get() bounds]]; - [view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; + TabContents* contents = tab_contents(); + DCHECK(contents); + if (contents) { + SadTabController* sad_tab = + [[SadTabController alloc] initWithTabContents:contents + superview:cocoa_view_]; + sad_tab_.reset(sad_tab); + } } } @@ -295,10 +296,7 @@ void TabContentsViewMac::Observe(NotificationType type, const NotificationDetails& details) { switch (type.value) { case NotificationType::TAB_CONTENTS_CONNECTED: { - if (sad_tab_.get()) { - [sad_tab_.get() removeFromSuperview]; - sad_tab_.reset(); - } + sad_tab_.reset(); break; } default: |