diff options
author | rsesek@chromium.org <rsesek@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-09-30 17:32:28 +0000 |
---|---|---|
committer | rsesek@chromium.org <rsesek@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-09-30 17:32:28 +0000 |
commit | 440cb536972b33d49eb996b9d45aa4a29c65168a (patch) | |
tree | 722dfb9eaa966494c4a94f223826e233b5ecb43e /chrome | |
parent | 237a2f1d4378c294f6a66c95ecf37093fbaccc11 (diff) | |
download | chromium_src-440cb536972b33d49eb996b9d45aa4a29c65168a.zip chromium_src-440cb536972b33d49eb996b9d45aa4a29c65168a.tar.gz chromium_src-440cb536972b33d49eb996b9d45aa4a29c65168a.tar.bz2 |
[Mac] Convert the page info window to a bubble.
BUG=52430,52916
TEST=Enable Page Info Bubble in about:labs and click on the lock icon.
Review URL: http://codereview.chromium.org/3461016
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@61077 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
-rw-r--r-- | chrome/browser/cocoa/base_bubble_controller.h | 10 | ||||
-rw-r--r-- | chrome/browser/cocoa/base_bubble_controller.mm | 28 | ||||
-rw-r--r-- | chrome/browser/cocoa/location_bar/location_bar_view_mac.h | 3 | ||||
-rw-r--r-- | chrome/browser/cocoa/location_bar/location_bar_view_mac.mm | 8 | ||||
-rw-r--r-- | chrome/browser/cocoa/location_bar/location_icon_decoration.h | 4 | ||||
-rw-r--r-- | chrome/browser/cocoa/location_bar/location_icon_decoration.mm | 12 | ||||
-rw-r--r-- | chrome/browser/cocoa/page_info_bubble_controller.h | 50 | ||||
-rw-r--r-- | chrome/browser/cocoa/page_info_bubble_controller.mm | 383 | ||||
-rw-r--r-- | chrome/browser/cocoa/page_info_bubble_controller_unittest.mm | 191 | ||||
-rw-r--r-- | chrome/browser/cocoa/page_info_window_mac.mm | 8 | ||||
-rw-r--r-- | chrome/browser/labs.cc | 2 | ||||
-rw-r--r-- | chrome/browser/page_info_model.h | 5 | ||||
-rw-r--r-- | chrome/chrome_browser.gypi | 2 | ||||
-rw-r--r-- | chrome/chrome_tests.gypi | 1 |
14 files changed, 694 insertions, 13 deletions
diff --git a/chrome/browser/cocoa/base_bubble_controller.h b/chrome/browser/cocoa/base_bubble_controller.h index 51cfa58..b00e49c 100644 --- a/chrome/browser/cocoa/base_bubble_controller.h +++ b/chrome/browser/cocoa/base_bubble_controller.h @@ -26,6 +26,9 @@ IBOutlet InfoBubbleView* bubble_; // to set arrow position } +@property (nonatomic, readonly) NSWindow* parentWindow; +@property (nonatomic, readonly) InfoBubbleView* bubble; + // Creates a bubble. |nibPath| is just the basename, e.g. @"FirstRunBubble". // |anchoredAt| is in screen space. You need to call -showWindow: to make the // bubble visible. It will autorelease itself when the user dismisses the @@ -46,6 +49,11 @@ offset:(NSPoint)offset; -@property (nonatomic, readonly) InfoBubbleView* bubble; +// For subclasses that do not load from a XIB, this will simply set the instance +// variables appropriately. This will also replace the |-[self window]|'s +// contentView with an instance of InfoBubbleView. +- (id)initWithWindow:(NSWindow*)theWindow + parentWindow:(NSWindow*)parentWindow + anchoredAt:(NSPoint)anchoredAt; @end diff --git a/chrome/browser/cocoa/base_bubble_controller.mm b/chrome/browser/cocoa/base_bubble_controller.mm index b155a4f..9cebc61 100644 --- a/chrome/browser/cocoa/base_bubble_controller.mm +++ b/chrome/browser/cocoa/base_bubble_controller.mm @@ -7,12 +7,14 @@ #include "app/l10n_util.h" #include "base/logging.h" #include "base/mac_util.h" +#include "base/scoped_nsobject.h" #include "base/string_util.h" #import "chrome/browser/cocoa/info_bubble_view.h" #include "grit/generated_resources.h" @implementation BaseBubbleController +@synthesize parentWindow = parentWindow_; @synthesize bubble = bubble_; - (id)initWithWindowNibPath:(NSString*)nibPath @@ -48,6 +50,32 @@ anchoredAt:anchor]; } +- (id)initWithWindow:(NSWindow*)theWindow + parentWindow:(NSWindow*)parentWindow + anchoredAt:(NSPoint)anchoredAt { + DCHECK(theWindow); + if ((self = [super initWithWindow:theWindow])) { + parentWindow_ = parentWindow; + anchor_ = anchoredAt; + DCHECK(![[self window] delegate]); + [theWindow setDelegate:self]; + + scoped_nsobject<InfoBubbleView> contentView( + [[InfoBubbleView alloc] initWithFrame:NSMakeRect(0, 0, 0, 0)]); + [theWindow setContentView:contentView.get()]; + bubble_ = contentView.get(); + + // Watch to see if the parent window closes, and if so, close this one. + NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; + [center addObserver:self + selector:@selector(parentWindowWillClose:) + name:NSWindowWillCloseNotification + object:parentWindow_]; + + [self awakeFromNib]; + } + return self; +} - (void)awakeFromNib { // Check all connections have been made in Interface Builder. diff --git a/chrome/browser/cocoa/location_bar/location_bar_view_mac.h b/chrome/browser/cocoa/location_bar/location_bar_view_mac.h index 0ec9058..7ff8306 100644 --- a/chrome/browser/cocoa/location_bar/location_bar_view_mac.h +++ b/chrome/browser/cocoa/location_bar/location_bar_view_mac.h @@ -91,6 +91,9 @@ class LocationBarViewMac : public AutocompleteEditController, // Get the point on the star for the bookmark bubble to aim at. NSPoint GetBookmarkBubblePoint() const; + // Get the point in the security icon at which the page info bubble aims. + NSPoint GetPageInfoBubblePoint() const; + // Updates the location bar. Resets the bar's permanent text and // security style, and if |should_restore_state| is true, restores // saved state from the tab (for tab switching). diff --git a/chrome/browser/cocoa/location_bar/location_bar_view_mac.mm b/chrome/browser/cocoa/location_bar/location_bar_view_mac.mm index d8b8b80..8863841 100644 --- a/chrome/browser/cocoa/location_bar/location_bar_view_mac.mm +++ b/chrome/browser/cocoa/location_bar/location_bar_view_mac.mm @@ -409,6 +409,14 @@ NSPoint LocationBarViewMac::GetBookmarkBubblePoint() const { return [field_ convertPoint:point toView:nil]; } +NSPoint LocationBarViewMac::GetPageInfoBubblePoint() const { + AutocompleteTextFieldCell* cell = [field_ cell]; + const NSRect frame = [cell frameForDecoration:location_icon_decoration_.get() + inFrame:[field_ bounds]]; + const NSPoint point = location_icon_decoration_->GetBubblePointInFrame(frame); + return [field_ convertPoint:point toView:nil]; +} + NSImage* LocationBarViewMac::GetKeywordImage(const std::wstring& keyword) { const TemplateURL* template_url = profile_->GetTemplateURLModel()->GetTemplateURLForKeyword(keyword); diff --git a/chrome/browser/cocoa/location_bar/location_icon_decoration.h b/chrome/browser/cocoa/location_bar/location_icon_decoration.h index 028a0ab..4cba72f 100644 --- a/chrome/browser/cocoa/location_bar/location_icon_decoration.h +++ b/chrome/browser/cocoa/location_bar/location_icon_decoration.h @@ -28,6 +28,10 @@ class LocationIconDecoration : public ImageDecoration { return GetDrawRectInFrame(frame); } + // Get the point where the page info bubble should point within the + // decoration's frame, in the |owner_|'s coordinates. + NSPoint GetBubblePointInFrame(NSRect frame); + // Show the page info panel on click. virtual bool OnMousePressed(NSRect frame); virtual bool AcceptsMousePress() { return true; } diff --git a/chrome/browser/cocoa/location_bar/location_icon_decoration.mm b/chrome/browser/cocoa/location_bar/location_icon_decoration.mm index 9ec9794..f967af2 100644 --- a/chrome/browser/cocoa/location_bar/location_icon_decoration.mm +++ b/chrome/browser/cocoa/location_bar/location_icon_decoration.mm @@ -11,6 +11,12 @@ #include "chrome/browser/tab_contents/tab_contents.h" #import "third_party/mozilla/NSPasteboard+Utils.h" +// The info-bubble point should look like it points to the point +// between the star's lower tips. The popup should be where the +// Omnibox popup ends up (2px below field). Determined via Pixie.app +// magnification. +const CGFloat kBubblePointYOffset = 2.0; + LocationIconDecoration::LocationIconDecoration(LocationBarViewMac* owner) : owner_(owner) { } @@ -45,6 +51,12 @@ NSPasteboard* LocationIconDecoration::GetDragPasteboard() { return pboard; } +NSPoint LocationIconDecoration::GetBubblePointInFrame(NSRect frame) { + const NSRect draw_frame = GetDrawRectInFrame(frame); + return NSMakePoint(NSMidX(draw_frame), + NSMaxY(draw_frame) - kBubblePointYOffset); +} + bool LocationIconDecoration::OnMousePressed(NSRect frame) { // Do not show page info if the user has been editing the location // bar, or the location bar is at the NTP. diff --git a/chrome/browser/cocoa/page_info_bubble_controller.h b/chrome/browser/cocoa/page_info_bubble_controller.h new file mode 100644 index 0000000..7d3d9a3 --- /dev/null +++ b/chrome/browser/cocoa/page_info_bubble_controller.h @@ -0,0 +1,50 @@ +// Copyright (c) 2010 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 <Cocoa/Cocoa.h> + +#include "base/scoped_nsobject.h" +#include "base/scoped_ptr.h" +#import "chrome/browser/cocoa/base_bubble_controller.h" +#include "chrome/browser/page_info_model.h" + +// This NSWindowController subclass manages the InfoBubbleWindow and view that +// are displayed when the user clicks the security lock icon. +@interface PageInfoBubbleController : BaseBubbleController { + @private + // The model that generates the content displayed by the controller. + scoped_ptr<PageInfoModel> model_; + + // Thin bridge that pushes model-changed notifications from C++ to Cocoa. + scoped_ptr<PageInfoModel::PageInfoModelObserver> bridge_; + + // The certificate ID for the page, 0 if the page is not over HTTPS. + int certID_; + + // Reference to the images that are placed within the UI. + scoped_nsobject<NSImage> okImage_; + scoped_nsobject<NSImage> warningMinorImage_; + scoped_nsobject<NSImage> warningMajorImage_; + scoped_nsobject<NSImage> errorImage_; +} + +@property (nonatomic, assign) int certID; + +// Designated initializer. The new instance will take ownership of |model| and +// |bridge|. There should be a 1:1 mapping of models to bridges. The +// controller will release itself when the bubble is closed. +- (id)initWithPageInfoModel:(PageInfoModel*)model + modelObserver:(PageInfoModel::PageInfoModelObserver*)bridge + parentWindow:(NSWindow*)parentWindow; + +// Shows the certificate display window. Note that this will implicitly close +// the bubble because the certificate window will become key. The certificate +// information attaches itself as a sheet to the |parentWindow|. +- (IBAction)showCertWindow:(id)sender; + +@end + +@interface PageInfoBubbleController (ExposedForUnitTesting) +- (void)performLayout; +@end diff --git a/chrome/browser/cocoa/page_info_bubble_controller.mm b/chrome/browser/cocoa/page_info_bubble_controller.mm new file mode 100644 index 0000000..708d0e6 --- /dev/null +++ b/chrome/browser/cocoa/page_info_bubble_controller.mm @@ -0,0 +1,383 @@ +// Copyright (c) 2010 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/cocoa/page_info_bubble_controller.h" + +#include "app/l10n_util_mac.h" +#include "app/resource_bundle.h" +#include "base/sys_string_conversions.h" +#include "chrome/browser/cert_store.h" +#include "chrome/browser/certificate_viewer.h" +#import "chrome/browser/cocoa/browser_window_controller.h" +#import "chrome/browser/cocoa/location_bar/location_bar_view_mac.h" +#import "chrome/browser/cocoa/info_bubble_view.h" +#import "chrome/browser/cocoa/info_bubble_window.h" +#include "chrome/browser/profile.h" +#include "grit/generated_resources.h" +#include "grit/theme_resources.h" +#include "net/base/cert_status_flags.h" +#include "net/base/x509_certificate.h" +#import "third_party/GTM/AppKit/GTMUILocalizerAndLayoutTweaker.h" + +@interface PageInfoBubbleController (Private) +- (PageInfoModel*)model; +- (NSButton*)certificateButtonWithFrame:(NSRect)frame; +- (NSImage*)statusIconForState:(PageInfoModel::SectionInfoState)state; +- (void)configureTextFieldAsLabel:(NSTextField*)textField; +- (CGFloat)addTitleViewForInfo:(const PageInfoModel::SectionInfo&)info + toSubviews:(NSMutableArray*)subviews + atPoint:(NSPoint)point; +- (CGFloat)addDescriptionViewForInfo:(const PageInfoModel::SectionInfo&)info + toSubviews:(NSMutableArray*)subviews + atPoint:(NSPoint)point; +- (CGFloat)addCertificateButtonToSubviews:(NSMutableArray*)subviews + atOffset:(CGFloat)offset; +- (void)addImageViewForInfo:(const PageInfoModel::SectionInfo&)info + toSubviews:(NSMutableArray*)subviews + atOffset:(CGFloat)offset; +@end + +namespace { + +// The width of the window, in view coordinates. The height will be determined +// by the content. +const NSInteger kWindowWidth = 380; + +// Spacing in between sections. +const NSInteger kVerticalSpacing = 10; + +// Padding along on the X-axis between the window frame and content. +const NSInteger kFramePadding = 20; + +// Spacing between the title and description text views. +const NSInteger kTitleSpacing = 2; + +// Spacing between the image and the text. +const NSInteger kImageSpacing = 10; + +// Square size of the image. +const CGFloat kImageSize = 30; + +// The X position of the text fields. Variants for with and without an image. +const CGFloat kTextXPositionNoImage = kFramePadding; +const CGFloat kTextXPosition = kTextXPositionNoImage + kImageSize + + kImageSpacing; + +// Width of the text fields. +const CGFloat kTextWidth = kWindowWidth - (kImageSize + kImageSpacing + + kFramePadding * 2); + +// Takes in the bubble's height and the parent window, which should be a +// BrowserWindow, and gets the proper anchor point for the bubble. The point is +// in screen coordinates. +NSPoint AnchorPointFromParentWindow(NSWindow* parent, CGFloat bubbleHeight) { + BrowserWindowController* controller = [parent windowController]; + NSPoint origin = NSZeroPoint; + if ([controller isKindOfClass:[BrowserWindowController class]]) { + LocationBarViewMac* location_bar = [controller locationBarBridge]; + if (location_bar) { + NSPoint arrowTip = location_bar->GetPageInfoBubblePoint(); + origin = [parent convertBaseToScreen:arrowTip]; + origin.y -= bubbleHeight; + } + } + return origin; +} + +// Bridge that listens for change notifications from the model. +class PageInfoModelBubbleBridge : public PageInfoModel::PageInfoModelObserver { + public: + PageInfoModelBubbleBridge() : controller_(nil) {} + + // PageInfoModelObserver implementation. + virtual void ModelChanged() { + [controller_ performLayout]; + } + + // Sets the controller. + void set_controller(PageInfoBubbleController* controller) { + controller_ = controller; + } + + private: + PageInfoBubbleController* controller_; // weak +}; + +} // namespace + +namespace browser { + +void ShowPageInfoBubble(gfx::NativeWindow parent, + Profile* profile, + const GURL& url, + const NavigationEntry::SSLStatus& ssl, + bool show_history) { + PageInfoModelBubbleBridge* bridge = new PageInfoModelBubbleBridge(); + PageInfoModel* model = + new PageInfoModel(profile, url, ssl, show_history, bridge); + PageInfoBubbleController* controller = + [[PageInfoBubbleController alloc] initWithPageInfoModel:model + modelObserver:bridge + parentWindow:parent]; + bridge->set_controller(controller); + [controller setCertID:ssl.cert_id()]; + [controller showWindow:nil]; +} + +} // namespace browser + +@implementation PageInfoBubbleController + +@synthesize certID = certID_; + +- (id)initWithPageInfoModel:(PageInfoModel*)model + modelObserver:(PageInfoModel::PageInfoModelObserver*)bridge + parentWindow:(NSWindow*)parentWindow { + // Use an arbitrary height because it will be changed by the bridge. + NSRect contentRect = NSMakeRect(0, 0, kWindowWidth, 50); + // Create an empty window into which content is placed. + scoped_nsobject<InfoBubbleWindow> window( + [[InfoBubbleWindow alloc] initWithContentRect:contentRect + styleMask:NSBorderlessWindowMask + backing:NSBackingStoreBuffered + defer:NO]); + + NSPoint anchorPoint = AnchorPointFromParentWindow(parentWindow, 0); + if ((self = [super initWithWindow:window.get() + parentWindow:parentWindow + anchoredAt:anchorPoint])) { + model_.reset(model); + bridge_.reset(bridge); + + // Load the image refs. + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + okImage_.reset([rb.GetNSImageNamed(IDR_PAGEINFO_GOOD) retain]); + DCHECK_GE(kImageSize, [okImage_ size].width); + DCHECK_GE(kImageSize, [okImage_ size].height); + warningMinorImage_.reset( + [rb.GetNSImageNamed(IDR_PAGEINFO_WARNING_MINOR) retain]); + DCHECK_GE(kImageSize, [warningMinorImage_ size].width); + DCHECK_GE(kImageSize, [warningMinorImage_ size].height); + warningMajorImage_.reset( + [rb.GetNSImageNamed(IDR_PAGEINFO_WARNING_MAJOR) retain]); + DCHECK_GE(kImageSize, [warningMajorImage_ size].width); + DCHECK_GE(kImageSize, [warningMajorImage_ size].height); + errorImage_.reset([rb.GetNSImageNamed(IDR_PAGEINFO_BAD) retain]); + DCHECK_GE(kImageSize, [errorImage_ size].width); + DCHECK_GE(kImageSize, [errorImage_ size].height); + + [[self bubble] setArrowLocation:info_bubble::kTopLeft]; + + [self performLayout]; + } + return self; +} + +- (PageInfoModel*)model { + return model_.get(); +} + +- (IBAction)showCertWindow:(id)sender { + DCHECK(certID_ != 0); + ShowCertificateViewerByID([self parentWindow], certID_); +} + +// This will create the subviews for the page info window. The general layout +// is 2 or 3 boxed and titled sections, each of which has a status image to +// provide visual feedback and a description that explains it. The description +// text is usually only 1 or 2 lines, but can be much longer. At the bottom of +// the window is a button to view the SSL certificate, which is disabled if +// not using HTTPS. +- (void)performLayout { + // |offset| is the Y position that should be drawn at next. + CGFloat offset = kVerticalSpacing; + + // Keep the new subviews in an array that gets replaced at the end. + NSMutableArray* subviews = [NSMutableArray array]; + + // Build the window from bottom-up because Cocoa's coordinate origin is the + // lower left. + for (int i = model_->GetSectionCount() - 1; i >= 0; --i) { + PageInfoModel::SectionInfo info = model_->GetSectionInfo(i); + + // Only certain sections have images. This affects the X position. + BOOL hasImage = info.type == PageInfoModel::SECTION_INFO_IDENTITY || + info.type == PageInfoModel::SECTION_INFO_CONNECTION; + CGFloat xPosition = (hasImage ? kTextXPosition : kTextXPositionNoImage); + + if (info.type == PageInfoModel::SECTION_INFO_IDENTITY) { + offset += [self addCertificateButtonToSubviews:subviews + atOffset:offset]; + } + + // Create the description of the state. + offset += [self addDescriptionViewForInfo:info + toSubviews:subviews + atPoint:NSMakePoint(xPosition, offset)]; + + // Add the title. + offset += kTitleSpacing; + offset += [self addTitleViewForInfo:info + toSubviews:subviews + atPoint:NSMakePoint(xPosition, offset)]; + + // Insert the image subview for sections that are appropriate. + if (hasImage) { + [self addImageViewForInfo:info toSubviews:subviews atOffset:offset]; + } + + // Add the separators. + offset += kVerticalSpacing; + if (i != 0) { + scoped_nsobject<NSBox> spacer( + [[NSBox alloc] initWithFrame:NSMakeRect(0, offset, kWindowWidth, 1)]); + [spacer setBoxType:NSBoxSeparator]; + [spacer setBorderType:NSLineBorder]; + [subviews addObject:spacer.get()]; + offset += kVerticalSpacing; + } + } + + // Replace the window's content. + [[[self window] contentView] setSubviews:subviews]; + + offset += kFramePadding; + + // TODO(rsesek): Remove constant value to account for http://crbug.com/57306. + NSRect windowFrame = NSMakeRect(0, 0, kWindowWidth, 50); + windowFrame.size.height += offset; + windowFrame.size = [[[self window] contentView] convertSize:windowFrame.size + toView:nil]; + // Just setting |size| will cause the window to grow upwards. Shift the + // origin up by grow amount, which causes the window to grow downwards. + windowFrame.origin = AnchorPointFromParentWindow([self parentWindow], + NSHeight(windowFrame)); + + // Resize the window. Only animate if the window is visible, otherwise it + // could be "growing" while it's opening, looking awkward. + [[self window] setFrame:windowFrame + display:YES + animate:[[self window] isVisible]]; +} + +// Creates the button with a given |frame| that, when clicked, will show the +// SSL certificate information. +- (NSButton*)certificateButtonWithFrame:(NSRect)frame { + NSButton* certButton = [[[NSButton alloc] initWithFrame:frame] autorelease]; + [certButton setTitle: + l10n_util::GetNSStringWithFixup(IDS_PAGEINFO_CERT_INFO_BUTTON)]; + [certButton setButtonType:NSMomentaryPushInButton]; + [certButton setBezelStyle:NSRoundRectBezelStyle]; + [certButton setTarget:self]; + [certButton setAction:@selector(showCertWindow:)]; + return certButton; +} + +// Returns a weak reference to the NSImage instance to used, or nil if none, for +// the specified info |state|. +- (NSImage*)statusIconForState:(PageInfoModel::SectionInfoState)state { + switch (state) { + case PageInfoModel::SECTION_STATE_OK: + return okImage_.get(); + case PageInfoModel::SECTION_STATE_WARNING_MINOR: + return warningMinorImage_.get(); + case PageInfoModel::SECTION_STATE_WARNING_MAJOR: + return warningMajorImage_.get(); + case PageInfoModel::SECTION_STATE_ERROR: + return errorImage_.get(); + default: + return nil; + } +} + +// Sets proprties on the given |field| to act as the title or description labels +// in the bubble. +- (void)configureTextFieldAsLabel:(NSTextField*)textField { + [textField setEditable:NO]; + [textField setDrawsBackground:NO]; + [textField setBezeled:NO]; +} + +// Adds the title text field at the given x,y position, and returns the y +// position for the next element. +- (CGFloat)addTitleViewForInfo:(const PageInfoModel::SectionInfo&)info + toSubviews:(NSMutableArray*)subviews + atPoint:(NSPoint)point { + NSRect frame = NSMakeRect(point.x, point.y, kTextWidth, kImageSpacing); + scoped_nsobject<NSTextField> titleField( + [[NSTextField alloc] initWithFrame:frame]); + [self configureTextFieldAsLabel:titleField.get()]; + [titleField setStringValue:base::SysUTF16ToNSString(info.title)]; + [titleField setFont:[NSFont boldSystemFontOfSize:11]]; + frame.size.height += + [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField: + titleField]; + [titleField setFrame:frame]; + [subviews addObject:titleField.get()]; + return NSHeight(frame); +} + +// Adds the description text field at the given x,y position, and returns the y +// position for the next element. +- (CGFloat)addDescriptionViewForInfo:(const PageInfoModel::SectionInfo&)info + toSubviews:(NSMutableArray*)subviews + atPoint:(NSPoint)point { + NSRect frame = NSMakeRect(point.x, point.y, kTextWidth, kImageSize); + scoped_nsobject<NSTextField> textField( + [[NSTextField alloc] initWithFrame:frame]); + [self configureTextFieldAsLabel:textField.get()]; + [textField setStringValue:base::SysUTF16ToNSString(info.description)]; + [textField setFont:[NSFont labelFontOfSize:11]]; + + // If the text is oversized, resize the text field. + frame.size.height += + [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField: + textField]; + [subviews addObject:textField.get()]; + return NSHeight(frame); +} + +// Adds the certificate button at a pre-determined x position and the given y. +// Returns the y position for the next element. +- (CGFloat)addCertificateButtonToSubviews:(NSMutableArray*)subviews + atOffset:(CGFloat)offset { + // Create the certificate button. The frame will be fixed up by GTM, so + // use arbitrary values. + NSRect frame = NSMakeRect(kTextXPosition, offset, 100, 20); + NSButton* certButton = [self certificateButtonWithFrame:frame]; + [subviews addObject:certButton]; + [GTMUILocalizerAndLayoutTweaker sizeToFitView:certButton]; + + // By default, assume that we don't have certificate information to show. + [certButton setEnabled:NO]; + if (certID_) { + scoped_refptr<net::X509Certificate> cert; + CertStore::GetSharedInstance()->RetrieveCert(certID_, &cert); + + // Don't bother showing certificates if there isn't one. Gears runs + // with no OS root certificate. + if (cert.get() && cert->os_cert_handle()) { + [certButton setEnabled:YES]; + } + } + + return NSHeight(frame) + kVerticalSpacing; +} + +// Adds the state image at a pre-determined x position and the given y. This +// does not affect the next Y position because the image is placed next to +// a text field that is larger and accounts for the image's size. +- (void)addImageViewForInfo:(const PageInfoModel::SectionInfo&)info + toSubviews:(NSMutableArray*)subviews + atOffset:(CGFloat)offset { + NSRect frame = NSMakeRect(kFramePadding, offset - kImageSize, kImageSize, + kImageSize); + scoped_nsobject<NSImageView> imageView( + [[NSImageView alloc] initWithFrame:frame]); + [imageView setImageFrameStyle:NSImageFrameNone]; + [imageView setImage:[self statusIconForState:info.state]]; + [subviews addObject:imageView.get()]; +} + +@end diff --git a/chrome/browser/cocoa/page_info_bubble_controller_unittest.mm b/chrome/browser/cocoa/page_info_bubble_controller_unittest.mm new file mode 100644 index 0000000..b7a51ad --- /dev/null +++ b/chrome/browser/cocoa/page_info_bubble_controller_unittest.mm @@ -0,0 +1,191 @@ +// Copyright (c) 2010 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 "app/l10n_util.h" +#include "base/scoped_nsobject.h" +#include "base/string_util.h" +#include "base/string_number_conversions.h" +#include "base/sys_string_conversions.h" +#include "base/utf_string_conversions.h" +#import "chrome/browser/cocoa/page_info_bubble_controller.h" +#include "chrome/browser/cocoa/browser_test_helper.h" +#import "chrome/browser/cocoa/cocoa_test_helper.h" +#include "chrome/browser/page_info_model.h" +#include "grit/generated_resources.h" + +namespace { + +class FakeModel : public PageInfoModel { + public: + void AddSection(SectionInfoState state, + const string16& title, + const string16& description, + SectionInfoType type) { + sections_.push_back(SectionInfo( + state, title, string16(), description, type)); + } +}; + +class FakeBridge : public PageInfoModel::PageInfoModelObserver { + public: + void ModelChanged() {} +}; + +class PageInfoBubbleControllerTest : public CocoaTest { + public: + PageInfoBubbleControllerTest() { + controller_ = nil; + model_ = new FakeModel(); + } + + virtual void TearDown() { + [controller_ close]; + CocoaTest::TearDown(); + } + + void CreateBubble() { + // The controller cleans up after itself when the window closes. + controller_ = + [[PageInfoBubbleController alloc] initWithPageInfoModel:model_ + modelObserver:NULL + parentWindow:test_window()]; + window_ = [controller_ window]; + [controller_ showWindow:nil]; + } + + // Checks the controller's window for the requisite subviews in the given + // numbers. + void CheckWindow(int text_count, + int image_count, + int spacer_count, + int button_count) { + for (NSView* view in [[window_ contentView] subviews]) { + if ([view isKindOfClass:[NSTextField class]]) { + --text_count; + } else if ([view isKindOfClass:[NSImageView class]]) { + --image_count; + } else if ([view isKindOfClass:[NSBox class]]) { + --spacer_count; + } else if ([view isKindOfClass:[NSButton class]]) { + --button_count; + CheckButton(static_cast<NSButton*>(view)); + } else { + ADD_FAILURE() << "Unknown subview: " << [[view description] UTF8String]; + } + } + EXPECT_EQ(0, text_count); + EXPECT_EQ(0, image_count); + EXPECT_EQ(0, spacer_count); + EXPECT_EQ(0, button_count); + EXPECT_EQ([window_ delegate], controller_); + } + + // Checks that a button is hooked up correctly. + void CheckButton(NSButton* button) { + EXPECT_EQ(@selector(showCertWindow:), [button action]); + EXPECT_EQ(controller_, [button target]); + EXPECT_TRUE([button stringValue]); + } + + BrowserTestHelper helper_; + + PageInfoBubbleController* controller_; // Weak, owns self. + FakeModel* model_; // Weak, owned by controller. + NSWindow* window_; // Weak, owned by controller. +}; + + +TEST_F(PageInfoBubbleControllerTest, NoHistoryNoSecurity) { + model_->AddSection(PageInfoModel::SECTION_STATE_ERROR, + l10n_util::GetStringUTF16(IDS_PAGE_INFO_SECURITY_TAB_IDENTITY_TITLE), + l10n_util::GetStringFUTF16( + IDS_PAGE_INFO_SECURITY_TAB_UNKNOWN_PARTY, + ASCIIToUTF16("google.com")), + PageInfoModel::SECTION_INFO_IDENTITY); + model_->AddSection(PageInfoModel::SECTION_STATE_ERROR, + l10n_util::GetStringUTF16(IDS_PAGE_INFO_SECURITY_TAB_CONNECTION_TITLE), + l10n_util::GetStringFUTF16( + IDS_PAGE_INFO_SECURITY_TAB_NOT_ENCRYPTED_CONNECTION_TEXT, + ASCIIToUTF16("google.com")), + PageInfoModel::SECTION_INFO_CONNECTION); + + CreateBubble(); + CheckWindow(/*text=*/4, /*image=*/2, /*spacer=*/1, /*button=*/1); +} + + +TEST_F(PageInfoBubbleControllerTest, HistoryNoSecurity) { + model_->AddSection(PageInfoModel::SECTION_STATE_ERROR, + l10n_util::GetStringUTF16(IDS_PAGE_INFO_SECURITY_TAB_IDENTITY_TITLE), + l10n_util::GetStringFUTF16( + IDS_PAGE_INFO_SECURITY_TAB_UNKNOWN_PARTY, + ASCIIToUTF16("google.com")), + PageInfoModel::SECTION_INFO_IDENTITY); + model_->AddSection(PageInfoModel::SECTION_STATE_ERROR, + l10n_util::GetStringUTF16(IDS_PAGE_INFO_SECURITY_TAB_CONNECTION_TITLE), + l10n_util::GetStringFUTF16( + IDS_PAGE_INFO_SECURITY_TAB_NOT_ENCRYPTED_CONNECTION_TEXT, + ASCIIToUTF16("google.com")), + PageInfoModel::SECTION_INFO_CONNECTION); + + // In practice, the history information comes later because it's queried + // asynchronously, so replicate the double-build here. + CreateBubble(); + + model_->AddSection(PageInfoModel::SECTION_STATE_ERROR, + l10n_util::GetStringUTF16( + IDS_PAGE_INFO_SECURITY_TAB_PERSONAL_HISTORY_TITLE), + l10n_util::GetStringUTF16( + IDS_PAGE_INFO_SECURITY_TAB_FIRST_VISITED_TODAY), + PageInfoModel::SECTION_INFO_FIRST_VISIT); + + [controller_ performLayout]; + + CheckWindow(/*text=*/6, /*image=*/2, /*spacer=*/2, /*button=*/1); +} + + +TEST_F(PageInfoBubbleControllerTest, NoHistoryMixedSecurity) { + model_->AddSection(PageInfoModel::SECTION_STATE_OK, + l10n_util::GetStringUTF16(IDS_PAGE_INFO_SECURITY_TAB_IDENTITY_TITLE), + l10n_util::GetStringFUTF16( + IDS_PAGE_INFO_SECURITY_TAB_SECURE_IDENTITY, + ASCIIToUTF16("Goat Security Systems")), + PageInfoModel::SECTION_INFO_IDENTITY); + + // This string is super long and the text should overflow the default clip + // region (kImageSize). + string16 description = l10n_util::GetStringFUTF16( + IDS_PAGE_INFO_SECURITY_TAB_ENCRYPTED_SENTENCE_LINK, + l10n_util::GetStringFUTF16( + IDS_PAGE_INFO_SECURITY_TAB_ENCRYPTED_CONNECTION_TEXT, + ASCIIToUTF16("chrome.google.com"), + base::IntToString16(1024)), + l10n_util::GetStringUTF16( + IDS_PAGE_INFO_SECURITY_TAB_ENCRYPTED_INSECURE_CONTENT_WARNING)); + + model_->AddSection(PageInfoModel::SECTION_STATE_OK, + l10n_util::GetStringUTF16(IDS_PAGE_INFO_SECURITY_TAB_CONNECTION_TITLE), + description, + PageInfoModel::SECTION_INFO_CONNECTION); + + CreateBubble(); + + NSArray* subviews = [[window_ contentView] subviews]; + CheckWindow(/*text=*/4, /*image=*/2, /*spacer=*/1, /*button=*/1); + + // Look for the over-sized box. + NSString* targetDesc = base::SysUTF16ToNSString(description); + for (NSView* subview in subviews) { + if ([subview isKindOfClass:[NSTextField class]]) { + NSTextField* desc = static_cast<NSTextField*>(subview); + if ([[desc stringValue] isEqualToString:targetDesc]) { + // Typical box frame is ~55px, make sure this is extra large. + EXPECT_LT(75, NSHeight([desc frame])); + } + } + } +} + +} // namespace diff --git a/chrome/browser/cocoa/page_info_window_mac.mm b/chrome/browser/cocoa/page_info_window_mac.mm index c98e357..8ca4c1b 100644 --- a/chrome/browser/cocoa/page_info_window_mac.mm +++ b/chrome/browser/cocoa/page_info_window_mac.mm @@ -57,14 +57,6 @@ void ShowPageInfo(gfx::NativeWindow parent, PageInfoWindowMac::ShowPageInfo(parent, profile, url, ssl, show_history); } -void ShowPageInfoBubble(gfx::NativeWindow parent, - Profile* profile, - const GURL& url, - const NavigationEntry::SSLStatus& ssl, - bool show_history) { - NOTIMPLEMENTED(); -} - } // namespace browser PageInfoWindowMac::PageInfoWindowMac(PageInfoWindowController* controller, diff --git a/chrome/browser/labs.cc b/chrome/browser/labs.cc index 874ba2f..c299b82 100644 --- a/chrome/browser/labs.cc +++ b/chrome/browser/labs.cc @@ -101,7 +101,7 @@ const Experiment kExperiments[] = { "page-info-bubble", // Do not change; see above. IDS_LABS_PAGE_INFO_BUBBLE_NAME, IDS_LABS_PAGE_INFO_BUBBLE_DESCRIPTION, - kOsWin | kOsLinux, + kOsAll, switches::kEnableNewPageInfoBubble }, { diff --git a/chrome/browser/page_info_model.h b/chrome/browser/page_info_model.h index 1f9942f..77ba614 100644 --- a/chrome/browser/page_info_model.h +++ b/chrome/browser/page_info_model.h @@ -23,10 +23,9 @@ class PageInfoModel { public: class PageInfoModelObserver { public: - virtual void ModelChanged() = 0; - - protected: virtual ~PageInfoModelObserver() {} + + virtual void ModelChanged() = 0; }; enum SectionInfoType { diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index 57e64bf..5db948f 100644 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -1056,6 +1056,8 @@ 'browser/cocoa/objc_method_swizzle.mm', 'browser/cocoa/objc_zombie.h', 'browser/cocoa/objc_zombie.mm', + 'browser/cocoa/page_info_bubble_controller.h', + 'browser/cocoa/page_info_bubble_controller.mm', 'browser/cocoa/page_info_window_controller.h', 'browser/cocoa/page_info_window_controller.mm', 'browser/cocoa/page_info_window_mac.h', diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index abcf1fa..48fc7f4 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -1010,6 +1010,7 @@ 'browser/cocoa/nsimage_cache_unittest.mm', 'browser/cocoa/nsmenuitem_additions_unittest.mm', 'browser/cocoa/objc_method_swizzle_unittest.mm', + 'browser/cocoa/page_info_bubble_controller_unittest.mm', 'browser/cocoa/page_info_window_mac_unittest.mm', 'browser/cocoa/preferences_window_controller_unittest.mm', 'browser/cocoa/reload_button_unittest.mm', |