Commit ac419494 authored by dconnelly's avatar dconnelly Committed by Commit bot

Add ManagePasswordsBubbleManageViewController and unit tests.

BUG=328847

Review URL: https://codereview.chromium.org/462563002

Cr-Commit-Position: refs/heads/master@{#291889}
parent ee69a3bd
...@@ -8,12 +8,15 @@ ...@@ -8,12 +8,15 @@
#import <Cocoa/Cocoa.h> #import <Cocoa/Cocoa.h>
#import "base/mac/scoped_nsobject.h" #import "base/mac/scoped_nsobject.h"
#include "components/autofill/core/common/password_form.h"
#include "components/password_manager/core/common/password_manager_ui.h" #include "components/password_manager/core/common/password_manager_ui.h"
#import "ui/base/cocoa/tracking_area.h"
namespace autofill { namespace autofill {
struct PasswordForm; struct PasswordForm;
} // namespace autofill } // namespace autofill
@class HoverImageButton;
class ManagePasswordsBubbleModel; class ManagePasswordsBubbleModel;
// The state of the password item. // The state of the password item.
...@@ -23,6 +26,44 @@ enum ManagePasswordItemState { ...@@ -23,6 +26,44 @@ enum ManagePasswordItemState {
MANAGE_PASSWORD_ITEM_STATE_DELETED MANAGE_PASSWORD_ITEM_STATE_DELETED
}; };
// Abstract superclass for items that are clickable. Highlights on hover.
@interface ManagePasswordItemClickableView : NSView {
@private
BOOL hovering_;
ui::ScopedCrTrackingArea trackingArea_;
}
@end
// Shows the option to undelete a password.
@interface ManagePasswordItemUndoView : ManagePasswordItemClickableView {
@private
base::scoped_nsobject<NSButton> undoButton_;
}
- (id)initWithTarget:(id)target action:(SEL)action;
@end
@interface ManagePasswordItemUndoView (Testing)
@property(readonly) NSButton* undoButton;
@end
// Shows a username, obscured password, and delete button in a single row.
@interface ManagePasswordItemManageView : ManagePasswordItemClickableView {
@private
base::scoped_nsobject<NSTextField> usernameField_;
base::scoped_nsobject<NSSecureTextField> passwordField_;
base::scoped_nsobject<HoverImageButton> deleteButton_;
}
- (id)initWithForm:(const autofill::PasswordForm&)form
target:(id)target
action:(SEL)action;
@end
@interface ManagePasswordItemManageView (Testing)
@property(readonly) NSTextField* usernameField;
@property(readonly) NSSecureTextField* passwordField;
@property(readonly) NSButton* deleteButton;
@end
// Shows a username and obscured password in a single row. // Shows a username and obscured password in a single row.
@interface ManagePasswordItemPendingView : NSView { @interface ManagePasswordItemPendingView : NSView {
@private @private
...@@ -42,19 +83,20 @@ enum ManagePasswordItemState { ...@@ -42,19 +83,20 @@ enum ManagePasswordItemState {
@interface ManagePasswordItemViewController : NSViewController { @interface ManagePasswordItemViewController : NSViewController {
@private @private
ManagePasswordsBubbleModel* model_; // weak ManagePasswordsBubbleModel* model_; // weak
autofill::PasswordForm passwordForm_;
ManagePasswordItemState state_; ManagePasswordItemState state_;
password_manager::ui::PasswordItemPosition position_; password_manager::ui::PasswordItemPosition position_;
base::scoped_nsobject<NSView> contentView_; base::scoped_nsobject<NSView> contentView_;
CGFloat minWidth_;
} }
- (id)initWithModel:(ManagePasswordsBubbleModel*)model - (id)initWithModel:(ManagePasswordsBubbleModel*)model
position:(password_manager::ui::PasswordItemPosition)position passwordForm:(const autofill::PasswordForm&)passwordForm
minWidth:(CGFloat)minWidth; position:(password_manager::ui::PasswordItemPosition)position;
@end @end
@interface ManagePasswordItemViewController (Testing) @interface ManagePasswordItemViewController (Testing)
@property(readonly) ManagePasswordItemState state; @property(readonly) ManagePasswordItemState state;
@property(readonly) NSView* contentView; @property(readonly) NSView* contentView;
@property(readonly) autofill::PasswordForm passwordForm;
@end @end
#endif // CHROME_BROWSER_UI_COCOA_PASSWORDS_MANAGE_PASSWORD_ITEM_VIEW_CONTROLLER_H_ #endif // CHROME_BROWSER_UI_COCOA_PASSWORDS_MANAGE_PASSWORD_ITEM_VIEW_CONTROLLER_H_
...@@ -7,17 +7,68 @@ ...@@ -7,17 +7,68 @@
#include "base/logging.h" #include "base/logging.h"
#include "base/strings/string16.h" #include "base/strings/string16.h"
#include "base/strings/sys_string_conversions.h" #include "base/strings/sys_string_conversions.h"
#include "chrome/browser/ui/chrome_style.h"
#import "chrome/browser/ui/cocoa/passwords/manage_passwords_bubble_content_view_controller.h" #import "chrome/browser/ui/cocoa/passwords/manage_passwords_bubble_content_view_controller.h"
#include "chrome/browser/ui/passwords/manage_passwords_bubble_model.h" #include "chrome/browser/ui/passwords/manage_passwords_bubble_model.h"
#include "grit/generated_resources.h"
#include "skia/ext/skia_utils_mac.h" #include "skia/ext/skia_utils_mac.h"
#import "ui/base/cocoa/controls/hyperlink_button_cell.h"
#import "ui/base/cocoa/hover_image_button.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/image/image.h"
#include "ui/native_theme/common_theme.h" #include "ui/native_theme/common_theme.h"
#include "ui/resources/grit/ui_resources.h"
#include "ui/views/layout/layout_constants.h" #include "ui/views/layout/layout_constants.h"
using namespace password_manager::mac::ui; using namespace password_manager::mac::ui;
namespace { namespace {
static const CGFloat kBorderWidth = 1; const CGFloat kBorderWidth = 1;
const SkColor kHoverColor = SkColorSetARGBInline(0xFF, 0xEB, 0xEB, 0xEB);
NSColor* HoverColor() {
return gfx::SkColorToCalibratedNSColor(kHoverColor);
}
NSFont* LabelFont() {
return [NSFont systemFontOfSize:[NSFont smallSystemFontSize]];
}
NSSize LabelSize(int resourceID) {
return [l10n_util::GetNSString(resourceID)
sizeWithAttributes:@{NSFontAttributeName : LabelFont()}];
}
CGFloat FirstFieldWidth() {
const CGFloat undoExplanationWidth =
LabelSize(IDS_MANAGE_PASSWORDS_DELETED).width;
const CGFloat kUsernameWidth =
ManagePasswordsBubbleModel::UsernameFieldWidth();
const CGFloat width = std::max(kUsernameWidth, undoExplanationWidth);
return width;
}
CGFloat SecondFieldWidth() {
const CGFloat undoLinkWidth =
LabelSize(IDS_MANAGE_PASSWORDS_UNDO).width;
const CGFloat kPasswordWidth =
ManagePasswordsBubbleModel::PasswordFieldWidth();
const CGFloat width = std::max(kPasswordWidth, undoLinkWidth);
return width;
}
CGFloat ItemWidth() {
const CGFloat width =
kFramePadding +
FirstFieldWidth() +
views::kItemLabelSpacing +
SecondFieldWidth() +
views::kItemLabelSpacing +
chrome_style::GetCloseButtonSize() +
kFramePadding;
return width;
}
void InitLabel(NSTextField* textField, const base::string16& text) { void InitLabel(NSTextField* textField, const base::string16& text) {
[textField setStringValue:base::SysUTF16ToNSString(text)]; [textField setStringValue:base::SysUTF16ToNSString(text)];
...@@ -25,34 +76,156 @@ void InitLabel(NSTextField* textField, const base::string16& text) { ...@@ -25,34 +76,156 @@ void InitLabel(NSTextField* textField, const base::string16& text) {
[textField setSelectable:NO]; [textField setSelectable:NO];
[textField setDrawsBackground:NO]; [textField setDrawsBackground:NO];
[textField setBezeled:NO]; [textField setBezeled:NO];
NSFont* font = [NSFont systemFontOfSize:[NSFont smallSystemFontSize]]; [textField setFont:LabelFont()];
[textField setFont:font];
[textField sizeToFit]; [textField sizeToFit];
// TODO(dconnelly): Handle max width.
} }
NSTextField* UsernameLabel(const base::string16& text) { NSTextField* Label(const base::string16& text) {
base::scoped_nsobject<NSTextField> textField( base::scoped_nsobject<NSTextField> textField(
[[NSTextField alloc] initWithFrame:NSZeroRect]); [[NSTextField alloc] initWithFrame:NSZeroRect]);
InitLabel(textField, text); InitLabel(textField, text);
return textField.autorelease(); return textField.autorelease();
} }
NSTextField* UsernameLabel(const base::string16& text) {
NSTextField* textField = Label(text);
[textField
setFrameSize:NSMakeSize(FirstFieldWidth(), NSHeight([textField frame]))];
return textField;
}
NSSecureTextField* PasswordLabel(const base::string16& text) { NSSecureTextField* PasswordLabel(const base::string16& text) {
base::scoped_nsobject<NSSecureTextField> textField( base::scoped_nsobject<NSSecureTextField> textField(
[[NSSecureTextField alloc] initWithFrame:NSZeroRect]); [[NSSecureTextField alloc] initWithFrame:NSZeroRect]);
InitLabel(textField, text); InitLabel(textField, text);
[textField
setFrameSize:NSMakeSize(SecondFieldWidth(), NSHeight([textField frame]))];
return textField.autorelease(); return textField.autorelease();
} }
} // namespace } // namespace
@implementation ManagePasswordItemUndoView
- (id)initWithTarget:(id)target action:(SEL)action {
if ((self = [super init])) {
// The button should look like a link.
undoButton_.reset([[NSButton alloc] initWithFrame:NSZeroRect]);
base::scoped_nsobject<HyperlinkButtonCell> cell([[HyperlinkButtonCell alloc]
initTextCell:l10n_util::GetNSString(IDS_MANAGE_PASSWORDS_UNDO)]);
[cell setControlSize:NSSmallControlSize];
[cell setShouldUnderline:NO];
[cell setUnderlineOnHover:NO];
[cell setTextColor:gfx::SkColorToCalibratedNSColor(
chrome_style::GetLinkColor())];
[undoButton_ setCell:cell.get()];
[undoButton_ sizeToFit];
[undoButton_ setTarget:target];
[undoButton_ setAction:action];
const CGFloat width = ItemWidth();
CGFloat curX = kFramePadding;
CGFloat curY = views::kRelatedControlVerticalSpacing;
// Add the explanation text.
NSTextField* label =
Label(l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_DELETED));
[label setFrameOrigin:NSMakePoint(curX, curY)];
[self addSubview:label];
// The undo button should be right-aligned.
curX = width - kFramePadding - NSWidth([undoButton_ frame]);
[undoButton_ setFrameOrigin:NSMakePoint(curX, curY)];
[self addSubview:undoButton_ ];
// Move to the top-right of the delete button.
curX = NSMaxX([undoButton_ frame]) + kFramePadding;
curY = NSMaxY([undoButton_ frame]) + views::kRelatedControlVerticalSpacing;
// Update the frame.
DCHECK_EQ(width, curX);
[self setFrameSize:NSMakeSize(curX, curY)];
}
return self;
}
@end
@implementation ManagePasswordItemUndoView (Testing)
- (NSButton*)undoButton {
return undoButton_.get();
}
@end
@implementation ManagePasswordItemManageView
- (id)initWithForm:(const autofill::PasswordForm&)form
target:(id)target
action:(SEL)action {
if ((self = [super init])) {
deleteButton_.reset([[HoverImageButton alloc] initWithFrame:NSZeroRect]);
[deleteButton_ setFrameSize:NSMakeSize(chrome_style::GetCloseButtonSize(),
chrome_style::GetCloseButtonSize())];
[deleteButton_ setBordered:NO];
[[deleteButton_ cell] setHighlightsBy:NSNoCellMask];
ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
[deleteButton_
setDefaultImage:bundle.GetImageNamed(IDR_CLOSE_2).ToNSImage()];
[deleteButton_
setHoverImage:bundle.GetImageNamed(IDR_CLOSE_2_H).ToNSImage()];
[deleteButton_
setPressedImage:bundle.GetImageNamed(IDR_CLOSE_2_P).ToNSImage()];
[deleteButton_ setTarget:target];
[deleteButton_ setAction:action];
const CGFloat width = ItemWidth();
CGFloat curX = kFramePadding;
CGFloat curY = views::kRelatedControlVerticalSpacing;
// Add the username.
usernameField_.reset([UsernameLabel(form.username_value) retain]);
[usernameField_ setFrameOrigin:NSMakePoint(curX, curY)];
[self addSubview:usernameField_];
// Move to the right of the username and add the password.
curX = NSMaxX([usernameField_ frame]) + views::kItemLabelSpacing;
passwordField_.reset([PasswordLabel(form.password_value) retain]);
[passwordField_ setFrameOrigin:NSMakePoint(curX, curY)];
[self addSubview:passwordField_];
// The delete button should be right-aligned.
curX = width - kFramePadding - NSWidth([deleteButton_ frame]);
[deleteButton_ setFrameOrigin:NSMakePoint(curX, curY)];
[self addSubview:deleteButton_];
// Move to the top-right of the delete button.
curX = NSMaxX([deleteButton_ frame]) + kFramePadding;
curY =
NSMaxY([deleteButton_ frame]) + views::kRelatedControlVerticalSpacing;
// Update the frame.
DCHECK_EQ(width, curX);
[self setFrameSize:NSMakeSize(curX, curY)];
}
return self;
}
@end
@implementation ManagePasswordItemManageView (Testing)
- (NSTextField*)usernameField {
return usernameField_.get();
}
- (NSSecureTextField*)passwordField {
return passwordField_.get();
}
- (NSButton*)deleteButton {
return deleteButton_.get();
}
@end
@implementation ManagePasswordItemPendingView @implementation ManagePasswordItemPendingView
- (id)initWithForm:(const autofill::PasswordForm&)form { - (id)initWithForm:(const autofill::PasswordForm&)form {
if ((self = [super initWithFrame:NSZeroRect])) { if ((self = [super initWithFrame:NSZeroRect])) {
CGFloat curX = 0; CGFloat curX = kFramePadding;
CGFloat curY = 0; CGFloat curY = views::kRelatedControlVerticalSpacing;
// Add the username. // Add the username.
usernameField_.reset([UsernameLabel(form.username_value) retain]); usernameField_.reset([UsernameLabel(form.username_value) retain]);
...@@ -60,17 +233,17 @@ NSSecureTextField* PasswordLabel(const base::string16& text) { ...@@ -60,17 +233,17 @@ NSSecureTextField* PasswordLabel(const base::string16& text) {
[self addSubview:usernameField_]; [self addSubview:usernameField_];
// Move to the right of the username and add the password. // Move to the right of the username and add the password.
curX += NSWidth([usernameField_ frame]) + views::kItemLabelSpacing; curX = NSMaxX([usernameField_ frame]) + views::kItemLabelSpacing;
passwordField_.reset([PasswordLabel(form.password_value) retain]); passwordField_.reset([PasswordLabel(form.password_value) retain]);
[passwordField_ setFrameOrigin:NSMakePoint(curX, curY)]; [passwordField_ setFrameOrigin:NSMakePoint(curX, curY)];
[self addSubview:passwordField_]; [self addSubview:passwordField_];
// Move to the top-right of the password. // Move to the top-right of the password.
curX = NSMaxX([passwordField_ frame]); curY =
curY = NSMaxY([passwordField_ frame]); NSMaxY([passwordField_ frame]) + views::kRelatedControlVerticalSpacing;
// Update the frame. // Update the frame.
[self setFrameSize:NSMakeSize(curX, curY)]; [self setFrameSize:NSMakeSize(ItemWidth(), curY)];
} }
return self; return self;
} }
...@@ -89,53 +262,87 @@ NSSecureTextField* PasswordLabel(const base::string16& text) { ...@@ -89,53 +262,87 @@ NSSecureTextField* PasswordLabel(const base::string16& text) {
@end @end
@interface ManagePasswordItemViewController ()
- (void)onDeleteClicked:(id)sender;
- (void)onUndoClicked:(id)sender;
// Find the next content view and repaint.
- (void)refresh;
// Find the next content view.
- (void)updateContent;
// Repaint the content.
- (void)layoutContent;
@end
@implementation ManagePasswordItemViewController @implementation ManagePasswordItemViewController
- (id)initWithModel:(ManagePasswordsBubbleModel*)model - (id)initWithModel:(ManagePasswordsBubbleModel*)model
position:(password_manager::ui::PasswordItemPosition)position passwordForm:(const autofill::PasswordForm&)passwordForm
minWidth:(CGFloat)minWidth { position:(password_manager::ui::PasswordItemPosition)position {
if ((self = [super initWithNibName:nil bundle:nil])) { if ((self = [super initWithNibName:nil bundle:nil])) {
model_ = model; model_ = model;
position_ = position; position_ = position;
minWidth_ = minWidth; passwordForm_ = passwordForm;
state_ = password_manager::ui::IsPendingState(model_->state()) state_ = password_manager::ui::IsPendingState(model_->state())
? MANAGE_PASSWORD_ITEM_STATE_PENDING ? MANAGE_PASSWORD_ITEM_STATE_PENDING
: MANAGE_PASSWORD_ITEM_STATE_MANAGE; : MANAGE_PASSWORD_ITEM_STATE_MANAGE;
[self updateContent];
}
return self;
}
- (void)onDeleteClicked:(id)sender {
DCHECK_EQ(MANAGE_PASSWORD_ITEM_STATE_MANAGE, state_);
state_ = MANAGE_PASSWORD_ITEM_STATE_DELETED;
[self refresh];
model_->OnPasswordAction(passwordForm_,
ManagePasswordsBubbleModel::REMOVE_PASSWORD);
}
- (void)onUndoClicked:(id)sender {
DCHECK_EQ(MANAGE_PASSWORD_ITEM_STATE_DELETED, state_);
state_ = MANAGE_PASSWORD_ITEM_STATE_MANAGE;
[self refresh];
model_->OnPasswordAction(passwordForm_,
ManagePasswordsBubbleModel::ADD_PASSWORD);
}
- (void)refresh {
[self updateContent];
[self layoutContent];
}
- (void)updateContent {
switch (state_) { switch (state_) {
default: default:
NOTREACHED(); NOTREACHED();
case MANAGE_PASSWORD_ITEM_STATE_PENDING: case MANAGE_PASSWORD_ITEM_STATE_PENDING:
contentView_.reset([[ManagePasswordItemPendingView alloc] contentView_.reset(
initWithForm:model_->pending_credentials()]); [[ManagePasswordItemPendingView alloc] initWithForm:passwordForm_]);
break; return;
case MANAGE_PASSWORD_ITEM_STATE_MANAGE: case MANAGE_PASSWORD_ITEM_STATE_MANAGE:
NOTIMPLEMENTED(); contentView_.reset([[ManagePasswordItemManageView alloc]
break; initWithForm:passwordForm_
target:self
action:@selector(onDeleteClicked:)]);
return;
case MANAGE_PASSWORD_ITEM_STATE_DELETED: case MANAGE_PASSWORD_ITEM_STATE_DELETED:
NOTIMPLEMENTED(); contentView_.reset([[ManagePasswordItemUndoView alloc]
break; initWithTarget:self
action:@selector(onUndoClicked:)]);
return;
}; };
}
return self;
} }
- (void)loadView { - (void)layoutContent {
self.view = [[[NSView alloc] initWithFrame:NSZeroRect] autorelease]; // Update the view size according to the content view size.
[self.view addSubview:contentView_];
// Update the view size according to the content view size, expanding if
// necessary to fill the min width.
const NSSize contentSize = [contentView_ frame].size; const NSSize contentSize = [contentView_ frame].size;
const CGFloat width = [self.view setFrameSize:contentSize];
std::max(contentSize.width + 2 * kFramePadding, minWidth_);
const CGFloat height =
contentSize.height + 2 * views::kRelatedControlVerticalSpacing;
[self.view setFrameSize:NSMakeSize(width, height)];
// Position the content view with some padding in the center of the view. // Add the content.
[contentView_ [self.view setSubviews:@[ contentView_ ]];
setFrameOrigin:NSMakePoint(kFramePadding,
views::kRelatedControlVerticalSpacing)];
// Add the borders, which go along the entire view. // Add the borders, which go along the entire view.
SkColor borderSkColor; SkColor borderSkColor;
...@@ -153,18 +360,25 @@ NSSecureTextField* PasswordLabel(const base::string16& text) { ...@@ -153,18 +360,25 @@ NSSecureTextField* PasswordLabel(const base::string16& text) {
if (position_ == password_manager::ui::FIRST_ITEM) { if (position_ == password_manager::ui::FIRST_ITEM) {
base::scoped_nsobject<CALayer> topBorder([[CALayer alloc] init]); base::scoped_nsobject<CALayer> topBorder([[CALayer alloc] init]);
[topBorder setBackgroundColor:borderColor]; [topBorder setBackgroundColor:borderColor];
[topBorder [topBorder setFrame:CGRectMake(0,
setFrame:CGRectMake(0, height - kBorderWidth, width, kBorderWidth)]; contentSize.height - kBorderWidth,
contentSize.width,
kBorderWidth)];
[self.view.layer addSublayer:topBorder]; [self.view.layer addSublayer:topBorder];
} }
// The bottom border is always present. // The bottom border is always present.
base::scoped_nsobject<CALayer> bottomBorder([[CALayer alloc] init]); base::scoped_nsobject<CALayer> bottomBorder([[CALayer alloc] init]);
[bottomBorder setBackgroundColor:borderColor]; [bottomBorder setBackgroundColor:borderColor];
[bottomBorder setFrame:CGRectMake(0, 0, width, kBorderWidth)]; [bottomBorder setFrame:CGRectMake(0, 0, contentSize.width, kBorderWidth)];
[self.view.layer addSublayer:bottomBorder]; [self.view.layer addSublayer:bottomBorder];
} }
- (void)loadView {
self.view = [[[NSView alloc] initWithFrame:NSZeroRect] autorelease];
[self layoutContent];
}
@end @end
@implementation ManagePasswordItemViewController (Testing) @implementation ManagePasswordItemViewController (Testing)
...@@ -177,4 +391,42 @@ NSSecureTextField* PasswordLabel(const base::string16& text) { ...@@ -177,4 +391,42 @@ NSSecureTextField* PasswordLabel(const base::string16& text) {
return contentView_.get(); return contentView_.get();
} }
- (autofill::PasswordForm)passwordForm {
return passwordForm_;
}
@end
@implementation ManagePasswordItemClickableView
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
if (hovering_) {
[HoverColor() setFill];
NSRectFill(dirtyRect);
}
}
- (void)mouseEntered:(NSEvent*)event {
hovering_ = YES;
[self setNeedsDisplay:YES];
}
- (void)mouseExited:(NSEvent*)event {
hovering_ = NO;
[self setNeedsDisplay:YES];
}
- (void)updateTrackingAreas {
[super updateTrackingAreas];
if (trackingArea_.get())
[self removeTrackingArea:trackingArea_.get()];
NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited;
trackingArea_.reset([[CrTrackingArea alloc] initWithRect:[self bounds]
options:options
owner:self
userInfo:nil]);
[self addTrackingArea:trackingArea_.get()];
}
@end @end
...@@ -8,53 +8,142 @@ ...@@ -8,53 +8,142 @@
#include "base/mac/scoped_nsobject.h" #include "base/mac/scoped_nsobject.h"
#include "base/strings/string16.h" #include "base/strings/string16.h"
#include "base/strings/sys_string_conversions.h" #include "base/strings/sys_string_conversions.h"
#include "chrome/browser/password_manager/mock_password_store_service.h"
#include "chrome/browser/password_manager/password_store_factory.h"
#include "chrome/browser/ui/cocoa/cocoa_test_helper.h" #include "chrome/browser/ui/cocoa/cocoa_test_helper.h"
#import "chrome/browser/ui/cocoa/passwords/manage_password_item_view_controller.h" #import "chrome/browser/ui/cocoa/passwords/manage_password_item_view_controller.h"
#include "chrome/browser/ui/cocoa/passwords/manage_passwords_controller_test.h" #include "chrome/browser/ui/cocoa/passwords/manage_passwords_controller_test.h"
#include "chrome/browser/ui/passwords/manage_passwords_bubble_model.h"
#include "chrome/browser/ui/passwords/manage_passwords_ui_controller_mock.h" #include "chrome/browser/ui/passwords/manage_passwords_ui_controller_mock.h"
#include "components/password_manager/core/browser/mock_password_store.h"
#include "components/password_manager/core/browser/password_store.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
#include "testing/gtest_mac.h" #include "testing/gtest_mac.h"
using namespace testing;
namespace { namespace {
static const CGFloat kArbitraryWidth = 500; NSString* const kItemTestUsername = @"foo";
NSString* const kItemTestPassword = @"bar";
} // namespace } // namespace
typedef ManagePasswordsControllerTest ManagePasswordItemViewControllerTest; MATCHER_P(PasswordFormEq, form, "") {
return form.username_value == arg.username_value &&
form.password_value == arg.password_value;
}
class ManagePasswordItemViewControllerTest
: public ManagePasswordsControllerTest {
public:
ManagePasswordItemViewControllerTest() {}
virtual ~ManagePasswordItemViewControllerTest() {}
virtual void SetUp() OVERRIDE {
ManagePasswordsControllerTest::SetUp();
PasswordStoreFactory::GetInstance()->SetTestingFactory(
profile(), MockPasswordStoreService::Build);
ui_controller()->SetPendingCredentials(credentials());
}
ManagePasswordItemViewController* controller() {
if (!controller_) {
controller_.reset([[ManagePasswordItemViewController alloc]
initWithModel:model()
passwordForm:ui_controller()->PendingCredentials()
position:password_manager::ui::FIRST_ITEM]);
}
return controller_.get();
}
autofill::PasswordForm credentials() {
autofill::PasswordForm form;
form.username_value = base::SysNSStringToUTF16(kItemTestUsername);
form.password_value = base::SysNSStringToUTF16(kItemTestPassword);
return form;
}
password_manager::MockPasswordStore* mockStore() {
password_manager::PasswordStore* store =
PasswordStoreFactory::GetForProfile(profile(), Profile::EXPLICIT_ACCESS)
.get();
password_manager::MockPasswordStore* mockStore =
static_cast<password_manager::MockPasswordStore*>(store);
return mockStore;
}
private:
base::scoped_nsobject<ManagePasswordItemViewController> controller_;
DISALLOW_COPY_AND_ASSIGN(ManagePasswordItemViewControllerTest);
};
TEST_F(ManagePasswordItemViewControllerTest, ManageStateShouldHaveManageView) {
model()->set_state(password_manager::ui::MANAGE_STATE);
EXPECT_EQ(MANAGE_PASSWORD_ITEM_STATE_MANAGE, [controller() state]);
EXPECT_NSEQ([ManagePasswordItemManageView class],
[[controller() contentView] class]);
}
TEST_F(ManagePasswordItemViewControllerTest,
ClickingDeleteShouldShowUndoViewAndDeletePassword) {
EXPECT_CALL(*mockStore(), RemoveLogin(PasswordFormEq(credentials())));
model()->set_state(password_manager::ui::MANAGE_STATE);
ManagePasswordItemManageView* manageView =
base::mac::ObjCCast<ManagePasswordItemManageView>(
controller().contentView);
[manageView.deleteButton performClick:nil];
EXPECT_NSEQ([ManagePasswordItemUndoView class],
[controller().contentView class]);
}
TEST_F(ManagePasswordItemViewControllerTest,
ClickingUndoShouldShowManageViewAndAddPassword) {
EXPECT_CALL(*mockStore(), AddLogin(PasswordFormEq(credentials())));
model()->set_state(password_manager::ui::MANAGE_STATE);
ManagePasswordItemManageView* manageView =
base::mac::ObjCCast<ManagePasswordItemManageView>(
controller().contentView);
[manageView.deleteButton performClick:nil];
ManagePasswordItemUndoView* undoView =
base::mac::ObjCCast<ManagePasswordItemUndoView>(controller().contentView);
[undoView.undoButton performClick:nil];
EXPECT_NSEQ([ManagePasswordItemManageView class],
[controller().contentView class]);
}
TEST_F(ManagePasswordItemViewControllerTest,
ManageViewShouldHaveCorrectUsernameAndObscuredPassword) {
model()->set_state(password_manager::ui::MANAGE_STATE);
ManagePasswordItemManageView* manageView =
base::mac::ObjCCast<ManagePasswordItemManageView>(
[controller() contentView]);
// Ensure the fields are populated properly and the password is obscured.
EXPECT_NSEQ(kItemTestUsername, manageView.usernameField.stringValue);
EXPECT_NSEQ(kItemTestPassword, manageView.passwordField.stringValue);
EXPECT_TRUE([[manageView.passwordField cell] echosBullets]);
}
TEST_F(ManagePasswordItemViewControllerTest, TEST_F(ManagePasswordItemViewControllerTest,
PendingStateShouldHavePendingView) { PendingStateShouldHavePendingView) {
base::scoped_nsobject<ManagePasswordItemViewController> controller( EXPECT_EQ(MANAGE_PASSWORD_ITEM_STATE_PENDING, [controller() state]);
[[ManagePasswordItemViewController alloc]
initWithModel:model()
position:password_manager::ui::FIRST_ITEM
minWidth:kArbitraryWidth]);
EXPECT_EQ(MANAGE_PASSWORD_ITEM_STATE_PENDING, [controller state]);
EXPECT_NSEQ([ManagePasswordItemPendingView class], EXPECT_NSEQ([ManagePasswordItemPendingView class],
[[controller contentView] class]); [[controller() contentView] class]);
} }
TEST_F(ManagePasswordItemViewControllerTest, TEST_F(ManagePasswordItemViewControllerTest,
PendingViewShouldHaveCorrectUsernameAndObscuredPassword) { PendingViewShouldHaveCorrectUsernameAndObscuredPassword) {
// Set the pending credentials. model()->set_state(password_manager::ui::PENDING_PASSWORD_STATE);
autofill::PasswordForm form;
NSString* const kUsername = @"foo";
NSString* const kPassword = @"bar";
form.username_value = base::SysNSStringToUTF16(kUsername);
form.password_value = base::SysNSStringToUTF16(kPassword);
ui_controller()->SetPendingCredentials(form);
ui_controller()->SetState(password_manager::ui::PENDING_PASSWORD_STATE);
base::scoped_nsobject<ManagePasswordItemViewController> controller(
[[ManagePasswordItemViewController alloc]
initWithModel:model()
position:password_manager::ui::FIRST_ITEM
minWidth:kArbitraryWidth]);
ManagePasswordItemPendingView* pendingView = ManagePasswordItemPendingView* pendingView =
base::mac::ObjCCast<ManagePasswordItemPendingView>( base::mac::ObjCCast<ManagePasswordItemPendingView>(
[controller contentView]); [controller() contentView]);
// Ensure the fields are populated properly and the password is obscured. // Ensure the fields are populated properly and the password is obscured.
EXPECT_NSEQ(kUsername, pendingView.usernameField.stringValue); EXPECT_NSEQ(kItemTestUsername, pendingView.usernameField.stringValue);
EXPECT_NSEQ(kPassword, pendingView.passwordField.stringValue); EXPECT_NSEQ(kItemTestPassword, pendingView.passwordField.stringValue);
EXPECT_TRUE([[pendingView.passwordField cell] echosBullets]); EXPECT_TRUE([[pendingView.passwordField cell] echosBullets]);
} }
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
#import "chrome/browser/ui/cocoa/browser_window_controller.h" #import "chrome/browser/ui/cocoa/browser_window_controller.h"
#import "chrome/browser/ui/cocoa/info_bubble_view.h" #import "chrome/browser/ui/cocoa/info_bubble_view.h"
#import "chrome/browser/ui/cocoa/info_bubble_window.h" #import "chrome/browser/ui/cocoa/info_bubble_window.h"
#import "chrome/browser/ui/cocoa/passwords/manage_passwords_bubble_manage_view_controller.h"
#include "chrome/browser/ui/cocoa/location_bar/location_bar_view_mac.h" #include "chrome/browser/ui/cocoa/location_bar/location_bar_view_mac.h"
#import "chrome/browser/ui/cocoa/passwords/manage_passwords_bubble_confirmation_view_controller.h" #import "chrome/browser/ui/cocoa/passwords/manage_passwords_bubble_confirmation_view_controller.h"
#include "ui/base/cocoa/window_size_constants.h" #include "ui/base/cocoa/window_size_constants.h"
...@@ -54,6 +55,10 @@ ...@@ -54,6 +55,10 @@
[[ManagePasswordsBubbleConfirmationViewController alloc] [[ManagePasswordsBubbleConfirmationViewController alloc]
initWithModel:model_ initWithModel:model_
delegate:self]); delegate:self]);
} else if (model_->state() == password_manager::ui::MANAGE_STATE) {
currentController_.reset([[ManagePasswordsBubbleManageViewController alloc]
initWithModel:model_
delegate:self]);
} }
[self performLayout]; [self performLayout];
} }
......
...@@ -12,6 +12,8 @@ ...@@ -12,6 +12,8 @@
#include "chrome/browser/ui/browser_window.h" #include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/cocoa/cocoa_test_helper.h" #include "chrome/browser/ui/cocoa/cocoa_test_helper.h"
#import "chrome/browser/ui/cocoa/info_bubble_window.h" #import "chrome/browser/ui/cocoa/info_bubble_window.h"
#import "chrome/browser/ui/cocoa/passwords/manage_passwords_bubble_manage_view_controller.h"
#import "chrome/browser/ui/cocoa/passwords/manage_passwords_bubble_pending_view_controller.h"
#include "chrome/browser/ui/cocoa/passwords/manage_passwords_controller_test.h" #include "chrome/browser/ui/cocoa/passwords/manage_passwords_controller_test.h"
#include "chrome/browser/ui/passwords/manage_passwords_bubble_model.h" #include "chrome/browser/ui/passwords/manage_passwords_bubble_model.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
...@@ -27,7 +29,6 @@ class ManagePasswordsBubbleControllerTest ...@@ -27,7 +29,6 @@ class ManagePasswordsBubbleControllerTest
virtual void SetUp() OVERRIDE { virtual void SetUp() OVERRIDE {
ManagePasswordsControllerTest::SetUp(); ManagePasswordsControllerTest::SetUp();
model()->set_state(password_manager::ui::PENDING_PASSWORD_STATE);
} }
ManagePasswordsBubbleController* controller() { ManagePasswordsBubbleController* controller() {
...@@ -44,12 +45,13 @@ class ManagePasswordsBubbleControllerTest ...@@ -44,12 +45,13 @@ class ManagePasswordsBubbleControllerTest
}; };
TEST_F(ManagePasswordsBubbleControllerTest, PendingStateShouldHavePendingView) { TEST_F(ManagePasswordsBubbleControllerTest, PendingStateShouldHavePendingView) {
// We start in the pending state. model()->set_state(password_manager::ui::PENDING_PASSWORD_STATE);
EXPECT_EQ([ManagePasswordsBubblePendingViewController class], EXPECT_EQ([ManagePasswordsBubblePendingViewController class],
[[controller() currentController] class]); [[controller() currentController] class]);
} }
TEST_F(ManagePasswordsBubbleControllerTest, DismissingShouldCloseWindow) { TEST_F(ManagePasswordsBubbleControllerTest, DismissingShouldCloseWindow) {
model()->set_state(password_manager::ui::PENDING_PASSWORD_STATE);
[controller() showWindow:nil]; [controller() showWindow:nil];
// Turn off animations so that closing happens immediately. // Turn off animations so that closing happens immediately.
...@@ -62,4 +64,10 @@ TEST_F(ManagePasswordsBubbleControllerTest, DismissingShouldCloseWindow) { ...@@ -62,4 +64,10 @@ TEST_F(ManagePasswordsBubbleControllerTest, DismissingShouldCloseWindow) {
EXPECT_FALSE([window isVisible]); EXPECT_FALSE([window isVisible]);
} }
TEST_F(ManagePasswordsBubbleControllerTest, ManageStateShouldHaveManageView) {
model()->set_state(password_manager::ui::MANAGE_STATE);
EXPECT_EQ([ManagePasswordsBubbleManageViewController class],
[[controller() currentController] class]);
}
} // namespace } // namespace
// Copyright 2014 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_PASSWORDS_MANAGE_PASSWORDS_BUBBLE_MANAGE_VIEW_CONTROLLER_H_
#define CHROME_BROWSER_UI_COCOA_PASSWORDS_MANAGE_PASSWORDS_BUBBLE_MANAGE_VIEW_CONTROLLER_H_
#import <Cocoa/Cocoa.h>
#include "base/mac/scoped_nsobject.h"
#import "chrome/browser/ui/cocoa/passwords/manage_passwords_bubble_content_view_controller.h"
class ManagePasswordsBubbleModel;
// Shows a list of passwords saved for the current site..
@interface PasswordItemListView : NSView {
base::scoped_nsobject<NSArray> itemViews_;
}
- (id)initWithModel:(ManagePasswordsBubbleModel*)model;
@end
@interface PasswordItemListView (Testing)
@property(readonly) NSArray* itemViews;
@end
// Informs the user that no passwords are stored for the current site.
@interface NoPasswordsView : NSTextField
- (id)initWithWidth:(CGFloat)width;
@end
// Manages the view that allows users to manage passwords for a site.
@interface ManagePasswordsBubbleManageViewController
: ManagePasswordsBubbleContentViewController {
@private
ManagePasswordsBubbleModel* model_; // weak
id<ManagePasswordsBubbleContentViewDelegate> delegate_; // weak
base::scoped_nsobject<NSButton> doneButton_;
base::scoped_nsobject<NSButton> manageButton_;
base::scoped_nsobject<NSView> contentView_;
}
- (id)initWithModel:(ManagePasswordsBubbleModel*)model
delegate:(id<ManagePasswordsBubbleContentViewDelegate>)delegate;
@end
@interface ManagePasswordsBubbleManageViewController (Testing)
@property(readonly) NSButton* doneButton;
@property(readonly) NSButton* manageButton;
@property(readonly) NSView* contentView;
@end
#endif // CHROME_BROWSER_UI_COCOA_PASSWORDS_MANAGE_PASSWORDS_BUBBLE_MANAGE_VIEW_CONTROLLER_H_
// Copyright 2014 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/passwords/manage_passwords_bubble_manage_view_controller.h"
#include <cmath>
#include "base/strings/sys_string_conversions.h"
#include "chrome/browser/ui/chrome_style.h"
#import "chrome/browser/ui/cocoa/passwords/manage_password_item_view_controller.h"
#include "chrome/browser/ui/passwords/manage_passwords_bubble_model.h"
#include "grit/generated_resources.h"
#include "skia/ext/skia_utils_mac.h"
#import "third_party/google_toolbox_for_mac/src/AppKit/GTMUILocalizerAndLayoutTweaker.h"
#import "ui/base/cocoa/controls/hyperlink_button_cell.h"
#include "ui/base/l10n/l10n_util.h"
using namespace password_manager::mac::ui;
@implementation PasswordItemListView
- (id)initWithModel:(ManagePasswordsBubbleModel*)model {
if ((self = [super initWithFrame:NSZeroRect])) {
base::scoped_nsobject<NSMutableArray> items([[NSMutableArray alloc] init]);
// Create and lay out the items.
const CGFloat curX = 0;
CGFloat maxX = 0;
CGFloat curY = 0;
for (autofill::ConstPasswordFormMap::const_reverse_iterator i =
model->best_matches().rbegin();
i != model->best_matches().rend();
++i) {
autofill::PasswordForm form = *i->second;
password_manager::ui::PasswordItemPosition position =
(&(*i) == &(*model->best_matches().begin()))
? password_manager::ui::FIRST_ITEM
: password_manager::ui::SUBSEQUENT_ITEM;
base::scoped_nsobject<ManagePasswordItemViewController> item(
[[ManagePasswordItemViewController alloc] initWithModel:model
passwordForm:form
position:position]);
[items addObject:item.get()];
NSView* itemView = [item view];
[self addSubview:itemView];
// The items stack up on each other.
[itemView setFrameOrigin:NSMakePoint(curX, curY)];
maxX = NSMaxX([itemView frame]);
curY = NSMaxY([itemView frame]);
}
[self setFrameSize:NSMakeSize(maxX, curY)];
itemViews_.reset(items.release());
}
return self;
}
@end
@implementation PasswordItemListView (Testing)
- (NSArray*)itemViews {
return itemViews_.get();
}
@end
@implementation NoPasswordsView
- (id)initWithWidth:(CGFloat)width {
if ((self = [super initWithFrame:NSZeroRect])) {
[self setEditable:NO];
[self setSelectable:NO];
[self setDrawsBackground:NO];
[self setBezeled:NO];
[self setStringValue:l10n_util::GetNSString(
IDS_MANAGE_PASSWORDS_NO_PASSWORDS)];
[self setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
[[self cell] setWraps:YES];
[self setFrameSize:NSMakeSize(width, MAXFLOAT)];
[GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:self];
}
return self;
}
@end
@interface ManagePasswordsBubbleManageViewController ()
- (void)onDoneClicked:(id)sender;
- (void)onManageClicked:(id)sender;
@end
@implementation ManagePasswordsBubbleManageViewController
- (id)initWithModel:(ManagePasswordsBubbleModel*)model
delegate:(id<ManagePasswordsBubbleContentViewDelegate>)delegate {
if ((self = [super initWithNibName:nil bundle:nil])) {
model_ = model;
delegate_ = delegate;
}
return self;
}
- (void)loadView {
self.view = [[[NSView alloc] initWithFrame:NSZeroRect] autorelease];
// -----------------------------------
// | Title |
// | ----------------------------- | (1 px border)
// | username password x |
// | ----------------------------- | (1 px border)
// | username password x |
// | ----------------------------- | (1 px border)
// | username password x |
// | ----------------------------- | (1 px border)
// | Manage [Done] |
// -----------------------------------
// The bubble should be wide enough to fit the title row, the username and
// password rows, and the buttons row on one line each, but not smaller than
// kDesiredBubbleWidth.
// Create the elements and add them to the view.
NSTextField* titleLabel =
[self addTitleLabel:base::SysUTF16ToNSString(model_->title())];
// Content. If we have a list of passwords to store for the current site, we
// display them to the user for management. Otherwise, we show a "No passwords
// for this site" message.
if (model_->best_matches().empty()) {
const CGFloat noPasswordsWidth = std::max(
kDesiredBubbleWidth - 2 * kFramePadding, NSWidth([titleLabel frame]));
contentView_.reset(
[[NoPasswordsView alloc] initWithWidth:noPasswordsWidth]);
} else {
contentView_.reset(
[[PasswordItemListView alloc] initWithModel:model_]);
}
[self.view addSubview:contentView_];
DCHECK_GE(NSWidth([contentView_ frame]), NSWidth([titleLabel frame]));
// Done button.
doneButton_.reset([[self addButton:l10n_util::GetNSString(IDS_DONE)
target:self
action:@selector(onDoneClicked:)] retain]);
// Manage button.
manageButton_.reset([[NSButton alloc] initWithFrame:NSZeroRect]);
base::scoped_nsobject<HyperlinkButtonCell> cell([[HyperlinkButtonCell alloc]
initTextCell:base::SysUTF16ToNSString(model_->manage_link())]);
[cell setControlSize:NSSmallControlSize];
[cell setShouldUnderline:NO];
[cell setUnderlineOnHover:NO];
[cell setTextColor:gfx::SkColorToCalibratedNSColor(
chrome_style::GetLinkColor())];
[manageButton_ setCell:cell.get()];
[manageButton_ sizeToFit];
[manageButton_ setTarget:self];
[manageButton_ setAction:@selector(onManageClicked:)];
[self.view addSubview:manageButton_];
// Layout the elements, starting at the bottom and moving up.
// The Done button goes in the bottom-right corner.
const CGFloat width = 2 * kFramePadding + NSWidth([contentView_ frame]);
CGFloat curX = width - kFramePadding - NSWidth([doneButton_ frame]);
CGFloat curY = kFramePadding;
[doneButton_ setFrameOrigin:NSMakePoint(curX, curY)];
// The Manage button goes in the bottom-left corner, centered vertically with
// the Done button.
curX = kFramePadding;
const CGFloat diffY = std::ceil(
(NSHeight([doneButton_ frame]) - NSHeight([manageButton_ frame])) / 2.0);
[manageButton_ setFrameOrigin:NSMakePoint(curX, curY + diffY)];
// The content goes above the button row.
curX = kFramePadding;
curY = NSMaxY([doneButton_ frame]) + kUnrelatedControlVerticalPadding;
[contentView_ setFrameOrigin:NSMakePoint(curX, curY)];
// The title goes above the content.
curY = NSMaxY([contentView_ frame]) + kUnrelatedControlVerticalPadding;
[titleLabel setFrameOrigin:NSMakePoint(curX, curY)];
curX = NSMaxX([contentView_ frame]) + kFramePadding;
curY = NSMaxY([titleLabel frame]) + kFramePadding;
DCHECK_EQ(width, curX);
[self.view setFrameSize:NSMakeSize(curX, curY)];
}
- (void)onDoneClicked:(id)sender {
model_->OnDoneClicked();
[delegate_ viewShouldDismiss];
}
- (void)onManageClicked:(id)sender {
model_->OnManageLinkClicked();
[delegate_ viewShouldDismiss];
}
@end
@implementation ManagePasswordsBubbleManageViewController (Testing)
- (NSButton*)doneButton {
return doneButton_.get();
}
- (NSButton*)manageButton {
return manageButton_.get();
}
- (NSView*)contentView {
return contentView_.get();
}
@end
// Copyright 2014 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/passwords/manage_passwords_bubble_manage_view_controller.h"
#include "base/mac/foundation_util.h"
#include "base/strings/utf_string_conversions.h"
#import "chrome/browser/ui/cocoa/passwords/manage_password_item_view_controller.h"
#include "chrome/browser/ui/cocoa/passwords/manage_passwords_controller_test.h"
#include "chrome/browser/ui/passwords/manage_passwords_bubble_model.h"
#include "chrome/browser/ui/passwords/manage_passwords_ui_controller_mock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/gtest_mac.h"
@interface ManagePasswordsBubbleManageViewTestDelegate
: NSObject<ManagePasswordsBubbleContentViewDelegate> {
BOOL dismissed_;
}
@property(readonly) BOOL dismissed;
@end
@implementation ManagePasswordsBubbleManageViewTestDelegate
@synthesize dismissed = dismissed_;
- (void)viewShouldDismiss {
dismissed_ = YES;
}
@end
namespace {
class ManagePasswordsBubbleManageViewControllerTest
: public ManagePasswordsControllerTest {
public:
ManagePasswordsBubbleManageViewControllerTest() : controller_(nil) {}
virtual void SetUp() OVERRIDE {
ManagePasswordsControllerTest::SetUp();
delegate_.reset(
[[ManagePasswordsBubbleManageViewTestDelegate alloc] init]);
ui_controller()->SetState(password_manager::ui::MANAGE_STATE);
}
ManagePasswordsBubbleManageViewTestDelegate* delegate() {
return delegate_.get();
}
ManagePasswordsBubbleManageViewController* controller() {
if (!controller_) {
controller_.reset([[ManagePasswordsBubbleManageViewController alloc]
initWithModel:model()
delegate:delegate()]);
[controller_ loadView];
}
return controller_.get();
}
private:
base::scoped_nsobject<ManagePasswordsBubbleManageViewController> controller_;
base::scoped_nsobject<ManagePasswordsBubbleManageViewTestDelegate> delegate_;
DISALLOW_COPY_AND_ASSIGN(ManagePasswordsBubbleManageViewControllerTest);
};
TEST_F(ManagePasswordsBubbleManageViewControllerTest,
ShouldDismissWhenDoneClicked) {
[controller().doneButton performClick:nil];
EXPECT_TRUE([delegate() dismissed]);
}
TEST_F(ManagePasswordsBubbleManageViewControllerTest,
ShouldOpenPasswordsWhenManageClicked) {
[controller().manageButton performClick:nil];
EXPECT_TRUE([delegate() dismissed]);
EXPECT_TRUE(ui_controller()->navigated_to_settings_page());
}
TEST_F(ManagePasswordsBubbleManageViewControllerTest,
ShouldShowNoPasswordsWhenNoPasswordsExistForSite) {
EXPECT_TRUE(model()->best_matches().empty());
EXPECT_EQ([NoPasswordsView class], [controller().contentView class]);
}
TEST_F(ManagePasswordsBubbleManageViewControllerTest,
ShouldShowAllPasswordItemsWhenPasswordsExistForSite) {
// Add a few password entries.
autofill::ConstPasswordFormMap map;
autofill::PasswordForm form1;
form1.username_value = base::ASCIIToUTF16("username1");
form1.password_value = base::ASCIIToUTF16("password1");
map[base::ASCIIToUTF16("username1")] = &form1;
autofill::PasswordForm form2;
form2.username_value = base::ASCIIToUTF16("username2");
form2.password_value = base::ASCIIToUTF16("password2");
map[base::ASCIIToUTF16("username2")] = &form2;
// Add the entries to the model.
ui_controller()->SetPasswordFormMap(map);
model()->set_state(password_manager::ui::MANAGE_STATE);
// Check the view state.
EXPECT_FALSE(model()->best_matches().empty());
EXPECT_EQ([PasswordItemListView class], [controller().contentView class]);
NSArray* items = base::mac::ObjCCastStrict<PasswordItemListView>(
controller().contentView).itemViews;
EXPECT_EQ(2U, [items count]);
// Check the entry items.
for (ManagePasswordItemViewController* item in items) {
ManagePasswordItemManageView* itemContent =
base::mac::ObjCCastStrict<ManagePasswordItemManageView>(
item.contentView);
NSString* username = [itemContent.usernameField stringValue];
if ([username isEqualToString:@"username1"]) {
EXPECT_NSEQ(@"password1", [itemContent.passwordField stringValue]);
} else if ([username isEqualToString:@"username2"]) {
EXPECT_NSEQ(@"password2", [itemContent.passwordField stringValue]);
} else {
NOTREACHED();
}
}
}
} // namespace
...@@ -78,11 +78,10 @@ using namespace password_manager::mac::ui; ...@@ -78,11 +78,10 @@ using namespace password_manager::mac::ui;
// Password item. // Password item.
// It should be at least as wide as the box without the padding. // It should be at least as wide as the box without the padding.
const CGFloat itemMinWidth = kDesiredBubbleWidth - 2 * kFramePadding;
passwordItem_.reset([[ManagePasswordItemViewController alloc] passwordItem_.reset([[ManagePasswordItemViewController alloc]
initWithModel:model_ initWithModel:model_
position:password_manager::ui::FIRST_ITEM passwordForm:model_->pending_credentials()
minWidth:itemMinWidth]); position:password_manager::ui::FIRST_ITEM]);
NSView* password = [passwordItem_ view]; NSView* password = [passwordItem_ view];
[self.view addSubview:password]; [self.view addSubview:password];
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
#include "components/password_manager/core/common/password_manager_ui.h" #include "components/password_manager/core/common/password_manager_ui.h"
#include "grit/generated_resources.h" #include "grit/generated_resources.h"
#include "ui/base/l10n/l10n_util.h" #include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
using autofill::PasswordFormMap; using autofill::PasswordFormMap;
using content::WebContents; using content::WebContents;
...@@ -21,6 +22,19 @@ namespace metrics_util = password_manager::metrics_util; ...@@ -21,6 +22,19 @@ namespace metrics_util = password_manager::metrics_util;
namespace { namespace {
enum FieldType { USERNAME_FIELD, PASSWORD_FIELD };
const int kUsernameFieldSize = 30;
const int kPasswordFieldSize = 22;
// Returns the width of |type| field.
int GetFieldWidth(FieldType type) {
return ui::ResourceBundle::GetSharedInstance()
.GetFontList(ui::ResourceBundle::SmallFont)
.GetExpectedTextWidth(type == USERNAME_FIELD ? kUsernameFieldSize
: kPasswordFieldSize);
}
void SetupLinkifiedText(const base::string16& string_with_separator, void SetupLinkifiedText(const base::string16& string_with_separator,
base::string16* text, base::string16* text,
gfx::Range* link_range) { gfx::Range* link_range) {
...@@ -179,3 +193,13 @@ void ManagePasswordsBubbleModel::OnPasswordAction( ...@@ -179,3 +193,13 @@ void ManagePasswordsBubbleModel::OnPasswordAction(
else else
password_store->AddLogin(password_form); password_store->AddLogin(password_form);
} }
// static
int ManagePasswordsBubbleModel::UsernameFieldWidth() {
return GetFieldWidth(USERNAME_FIELD);
}
// static
int ManagePasswordsBubbleModel::PasswordFieldWidth() {
return GetFieldWidth(PASSWORD_FIELD);
}
...@@ -102,6 +102,10 @@ class ManagePasswordsBubbleModel : public content::WebContentsObserver { ...@@ -102,6 +102,10 @@ class ManagePasswordsBubbleModel : public content::WebContentsObserver {
void set_state(password_manager::ui::State state) { state_ = state; } void set_state(password_manager::ui::State state) { state_ = state; }
#endif #endif
// Upper limits on the size of the username and password fields.
static int UsernameFieldWidth();
static int PasswordFieldWidth();
private: private:
password_manager::ui::State state_; password_manager::ui::State state_;
base::string16 title_; base::string16 title_;
......
...@@ -18,23 +18,9 @@ ...@@ -18,23 +18,9 @@
namespace { namespace {
enum FieldType { USERNAME_FIELD, PASSWORD_FIELD };
// Upper limit on the size of the username and password fields.
const int kUsernameFieldSize = 30;
const int kPasswordFieldSize = 22;
// Returns the width of |type| field.
int GetFieldWidth(FieldType type) {
return ui::ResourceBundle::GetSharedInstance()
.GetFontList(ui::ResourceBundle::SmallFont)
.GetExpectedTextWidth(type == USERNAME_FIELD ? kUsernameFieldSize
: kPasswordFieldSize);
}
int FirstFieldWidth() { int FirstFieldWidth() {
return std::max( return std::max(
GetFieldWidth(USERNAME_FIELD), ManagePasswordsBubbleModel::UsernameFieldWidth(),
views::Label(l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_DELETED)) views::Label(l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_DELETED))
.GetPreferredSize() .GetPreferredSize()
.width()); .width());
...@@ -42,7 +28,7 @@ int FirstFieldWidth() { ...@@ -42,7 +28,7 @@ int FirstFieldWidth() {
int SecondFieldWidth() { int SecondFieldWidth() {
return std::max( return std::max(
GetFieldWidth(PASSWORD_FIELD), ManagePasswordsBubbleModel::PasswordFieldWidth(),
views::Label(l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_UNDO)) views::Label(l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_UNDO))
.GetPreferredSize() .GetPreferredSize()
.width()); .width());
......
...@@ -637,6 +637,8 @@ ...@@ -637,6 +637,8 @@
'browser/ui/cocoa/passwords/manage_passwords_bubble_content_view_controller.mm', 'browser/ui/cocoa/passwords/manage_passwords_bubble_content_view_controller.mm',
'browser/ui/cocoa/passwords/manage_passwords_bubble_controller.h', 'browser/ui/cocoa/passwords/manage_passwords_bubble_controller.h',
'browser/ui/cocoa/passwords/manage_passwords_bubble_controller.mm', 'browser/ui/cocoa/passwords/manage_passwords_bubble_controller.mm',
'browser/ui/cocoa/passwords/manage_passwords_bubble_manage_view_controller.h',
'browser/ui/cocoa/passwords/manage_passwords_bubble_manage_view_controller.mm',
'browser/ui/cocoa/passwords/manage_passwords_bubble_pending_view_controller.h', 'browser/ui/cocoa/passwords/manage_passwords_bubble_pending_view_controller.h',
'browser/ui/cocoa/passwords/manage_passwords_bubble_pending_view_controller.mm', 'browser/ui/cocoa/passwords/manage_passwords_bubble_pending_view_controller.mm',
'browser/ui/cocoa/pdf_password_dialog.mm', 'browser/ui/cocoa/pdf_password_dialog.mm',
......
...@@ -1627,6 +1627,7 @@ ...@@ -1627,6 +1627,7 @@
'browser/ui/cocoa/passwords/manage_passwords_bubble_cocoa_unittest.mm', 'browser/ui/cocoa/passwords/manage_passwords_bubble_cocoa_unittest.mm',
'browser/ui/cocoa/passwords/manage_passwords_bubble_confirmation_view_controller_unittest.mm', 'browser/ui/cocoa/passwords/manage_passwords_bubble_confirmation_view_controller_unittest.mm',
'browser/ui/cocoa/passwords/manage_passwords_bubble_controller_unittest.mm', 'browser/ui/cocoa/passwords/manage_passwords_bubble_controller_unittest.mm',
'browser/ui/cocoa/passwords/manage_passwords_bubble_manage_view_controller_unittest.mm',
'browser/ui/cocoa/passwords/manage_passwords_bubble_pending_view_controller_unittest.mm', 'browser/ui/cocoa/passwords/manage_passwords_bubble_pending_view_controller_unittest.mm',
'browser/ui/cocoa/passwords/manage_passwords_controller_test.h', 'browser/ui/cocoa/passwords/manage_passwords_controller_test.h',
'browser/ui/cocoa/passwords/manage_passwords_controller_test.mm', 'browser/ui/cocoa/passwords/manage_passwords_controller_test.mm',
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment