Check USB device path access when prompting users to select a device.
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / autofill / card_unmask_prompt_view_bridge.mm
blob7b8b8f236fbfe64478045253f05cf461b34255c9
1 // Copyright (c) 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "base/bind.h"
6 #include "base/message_loop/message_loop.h"
7 #include "base/strings/sys_string_conversions.h"
8 #include "chrome/browser/ui/autofill/autofill_dialog_models.h"
9 #include "chrome/browser/ui/autofill/autofill_dialog_types.h"
10 #include "chrome/browser/ui/autofill/card_unmask_prompt_controller.h"
11 #include "chrome/browser/ui/chrome_style.h"
12 #import "chrome/browser/ui/cocoa/autofill/autofill_pop_up_button.h"
13 #import "chrome/browser/ui/cocoa/autofill/autofill_textfield.h"
14 #import "chrome/browser/ui/cocoa/autofill/autofill_tooltip_controller.h"
15 #include "chrome/browser/ui/cocoa/autofill/card_unmask_prompt_view_bridge.h"
16 #import "chrome/browser/ui/cocoa/constrained_window/constrained_window_button.h"
17 #import "chrome/browser/ui/cocoa/constrained_window/constrained_window_control_utils.h"
18 #import "chrome/browser/ui/cocoa/constrained_window/constrained_window_custom_sheet.h"
19 #import "chrome/browser/ui/cocoa/constrained_window/constrained_window_custom_window.h"
20 #import "chrome/browser/ui/cocoa/key_equivalent_constants.h"
21 #import "chrome/browser/ui/cocoa/l10n_util.h"
22 #import "chrome/browser/ui/cocoa/spinner_view.h"
23 #include "grit/generated_resources.h"
24 #include "grit/theme_resources.h"
25 #include "skia/ext/skia_utils_mac.h"
26 #include "ui/base/cocoa/window_size_constants.h"
27 #include "ui/base/l10n/l10n_util.h"
29 namespace {
31 const CGFloat kButtonGap = 6.0f;
32 const CGFloat kButtonsToRetriableErrorGap = 12.0f;
33 const CGFloat kCvcInputWidth = 64.0f;
34 const CGFloat kCvcInputToImageGap = 8.0f;
35 const CGFloat kDialogContentMinWidth = 210.0f;
36 const CGFloat kInputRowToInstructionsGap = 16.0f;
37 const CGFloat kInstructionsToTitleGap = 8.0f;
38 const CGFloat kPermanentErrorExteriorPadding = 12.0f;
39 const CGFloat kPermanentErrorHorizontalPadding = 16.0f;
40 const CGFloat kPermanentErrorVerticalPadding = 12.0f;
41 const CGFloat kProgressToInstructionsGap = 24.0f;
42 const CGFloat kRetriableErrorToInputRowGap = 4.0f;
43 const CGFloat kSeparatorHeight = 1.0f;
44 const CGFloat kSpinnerSize = 16.0f;
45 const CGFloat kSpinnerToProgressTextGap = 8.0f;
46 const CGFloat kYearToCvcGap = 12.0f;
48 const SkColor kPermanentErrorTextColor = SK_ColorWHITE;
49 // Material blue. TODO(bondd): share with Views version.
50 const SkColor kProgressTextColor = SkColorSetRGB(0x42, 0x85, 0xf4);
51 // TODO(bondd): Unify colors with Views version and AutofillMessageView.
52 const SkColor kShadingColor = SkColorSetRGB(0xf2, 0xf2, 0xf2);
53 const SkColor kSubtleBorderColor = SkColorSetRGB(0xdf, 0xdf, 0xdf);
55 }  // namespace
57 namespace autofill {
59 // static
60 CardUnmaskPromptView* CardUnmaskPromptView::CreateAndShow(
61     CardUnmaskPromptController* controller) {
62   return new CardUnmaskPromptViewBridge(controller);
65 #pragma mark CardUnmaskPromptViewBridge
67 CardUnmaskPromptViewBridge::CardUnmaskPromptViewBridge(
68     CardUnmaskPromptController* controller)
69     : controller_(controller), weak_ptr_factory_(this) {
70   view_controller_.reset(
71       [[CardUnmaskPromptViewCocoa alloc] initWithBridge:this]);
73   // Setup the constrained window that will show the view.
74   base::scoped_nsobject<NSWindow> window([[ConstrainedWindowCustomWindow alloc]
75       initWithContentRect:[[view_controller_ view] bounds]]);
76   [window setContentView:[view_controller_ view]];
77   base::scoped_nsobject<CustomConstrainedWindowSheet> sheet(
78       [[CustomConstrainedWindowSheet alloc] initWithCustomWindow:window]);
79   constrained_window_.reset(
80       new ConstrainedWindowMac(this, controller_->GetWebContents(), sheet));
83 CardUnmaskPromptViewBridge::~CardUnmaskPromptViewBridge() {
86 void CardUnmaskPromptViewBridge::ControllerGone() {
87   controller_ = nullptr;
88   PerformClose();
91 void CardUnmaskPromptViewBridge::DisableAndWaitForVerification() {
92   [view_controller_ setProgressOverlayText:
93                         l10n_util::GetStringUTF16(
94                             IDS_AUTOFILL_CARD_UNMASK_VERIFICATION_IN_PROGRESS)
95                                showSpinner:YES];
98 void CardUnmaskPromptViewBridge::GotVerificationResult(
99     const base::string16& error_message,
100     bool allow_retry) {
101   if (error_message.empty()) {
102     [view_controller_ setProgressOverlayText:
103                           l10n_util::GetStringUTF16(
104                               IDS_AUTOFILL_CARD_UNMASK_VERIFICATION_SUCCESS)
105                                  showSpinner:NO];
107     base::MessageLoop::current()->PostDelayedTask(
108         FROM_HERE, base::Bind(&CardUnmaskPromptViewBridge::PerformClose,
109                               weak_ptr_factory_.GetWeakPtr()),
110         base::TimeDelta::FromSeconds(1));
111   } else {
112     [view_controller_ setProgressOverlayText:base::string16() showSpinner:NO];
114     if (allow_retry) {
115       // TODO(bondd): Views version never hides |errorLabel_|. When Views
116       // decides when to hide it then do the same thing here.
117       [view_controller_ setRetriableErrorMessage:error_message];
118     } else {
119       [view_controller_ setPermanentErrorMessage:error_message];
120     }
121   }
124 void CardUnmaskPromptViewBridge::OnConstrainedWindowClosed(
125     ConstrainedWindowMac* window) {
126   if (controller_)
127     controller_->OnUnmaskDialogClosed();
128   base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
131 CardUnmaskPromptController* CardUnmaskPromptViewBridge::GetController() {
132   return controller_;
135 void CardUnmaskPromptViewBridge::PerformClose() {
136   constrained_window_->CloseWebContentsModalDialog();
139 }  // autofill
141 #pragma mark CardUnmaskPromptViewCocoa
143 @implementation CardUnmaskPromptViewCocoa {
144   base::scoped_nsobject<NSBox> permanentErrorBox_;
145   base::scoped_nsobject<NSView> inputRowView_;
146   base::scoped_nsobject<NSView> progressOverlayView_;
147   base::scoped_nsobject<NSView> storageView_;
149   base::scoped_nsobject<NSTextField> titleLabel_;
150   base::scoped_nsobject<NSTextField> permanentErrorLabel_;
151   base::scoped_nsobject<NSTextField> instructionsLabel_;
152   base::scoped_nsobject<AutofillTextField> cvcInput_;
153   base::scoped_nsobject<AutofillPopUpButton> monthPopup_;
154   base::scoped_nsobject<AutofillPopUpButton> yearPopup_;
155   base::scoped_nsobject<NSButton> cancelButton_;
156   base::scoped_nsobject<NSButton> verifyButton_;
157   base::scoped_nsobject<NSButton> storageCheckbox_;
158   base::scoped_nsobject<AutofillTooltipController> storageTooltip_;
159   base::scoped_nsobject<NSTextField> errorLabel_;
160   base::scoped_nsobject<NSTextField> progressOverlayLabel_;
161   base::scoped_nsobject<SpinnerView> progressOverlaySpinner_;
163   int monthPopupDefaultIndex_;
164   int yearPopupDefaultIndex_;
166   // Owns |self|.
167   autofill::CardUnmaskPromptViewBridge* bridge_;
170 + (AutofillPopUpButton*)buildDatePopupWithModel:(ui::ComboboxModel&)model {
171   AutofillPopUpButton* popup =
172       [[AutofillPopUpButton alloc] initWithFrame:NSZeroRect pullsDown:NO];
174   for (int i = 0; i < model.GetItemCount(); ++i) {
175     [popup addItemWithTitle:base::SysUTF16ToNSString(model.GetItemAt(i))];
176   }
177   [popup sizeToFit];
178   return popup;
181 + (base::scoped_nsobject<NSBox>)createPlainBox {
182   base::scoped_nsobject<NSBox> box([[NSBox alloc] initWithFrame:NSZeroRect]);
183   [box setBoxType:NSBoxCustom];
184   [box setBorderType:NSNoBorder];
185   [box setTitlePosition:NSNoTitle];
186   return box;
189 // Set |view|'s frame to the minimum dimensions required to contain all of its
190 // subviews.
191 + (void)sizeToFitView:(NSView*)view {
192   NSRect frame = NSZeroRect;
193   for (NSView* child in [view subviews]) {
194     frame = NSUnionRect(frame, [child frame]);
195   }
196   [view setFrame:frame];
199 + (void)verticallyCenterSubviewsInView:(NSView*)view {
200   CGFloat height = NSHeight([view frame]);
201   for (NSView* child in [view subviews]) {
202     [child setFrameOrigin:NSMakePoint(
203                               NSMinX([child frame]),
204                               ceil((height - NSHeight([child frame])) * 0.5))];
205   }
208 - (id)initWithBridge:(autofill::CardUnmaskPromptViewBridge*)bridge {
209   DCHECK(bridge);
211   if ((self = [super initWithNibName:nil bundle:nil]))
212     bridge_ = bridge;
214   return self;
217 - (void)updateProgressOverlayOrigin {
218   // Center progressOverlayView_ horizontally in the dialog, and position it a
219   // fixed distance below instructionsLabel_.
220   CGFloat viewMinY = NSMinY([instructionsLabel_ frame]) -
221                      kProgressToInstructionsGap -
222                      NSHeight([progressOverlayView_ frame]);
223   [progressOverlayView_
224       setFrameOrigin:NSMakePoint(
225                          NSMidX([[self view] frame]) -
226                              NSWidth([progressOverlayView_ frame]) / 2.0,
227                          viewMinY)];
230 - (void)setProgressOverlayText:(const base::string16&)text
231                    showSpinner:(BOOL)showSpinner {
232   if (!text.empty()) {
233     NSAttributedString* attributedString =
234         constrained_window::GetAttributedLabelString(
235             SysUTF16ToNSString(text), chrome_style::kTextFontStyle,
236             NSNaturalTextAlignment, NSLineBreakByWordWrapping);
237     [progressOverlayLabel_ setAttributedStringValue:attributedString];
238     [progressOverlayLabel_ sizeToFit];
239     CGFloat labelMinX = showSpinner
240                             ? NSMaxX([progressOverlaySpinner_ frame]) +
241                                   kSpinnerToProgressTextGap
242                             : 0;
243     [progressOverlayLabel_ setFrameOrigin:NSMakePoint(labelMinX, 0)];
245     [CardUnmaskPromptViewCocoa sizeToFitView:progressOverlayView_];
246     [self updateProgressOverlayOrigin];
247   }
249   [progressOverlayView_ setHidden:text.empty()];
250   [progressOverlaySpinner_ setHidden:!showSpinner];
251   [inputRowView_ setHidden:!text.empty()];
252   [errorLabel_ setHidden:!text.empty()];
253   [storageCheckbox_ setHidden:!text.empty()];
254   [[storageTooltip_ view] setHidden:!text.empty()];
255   [self updateVerifyButtonEnabled];
258 - (void)setInputsEnabled:(BOOL)enabled {
259   [cvcInput_ setEnabled:enabled];
260   [monthPopup_ setEnabled:enabled];
261   [yearPopup_ setEnabled:enabled];
262   [storageCheckbox_ setEnabled:enabled];
265 - (void)setRetriableErrorMessage:(const base::string16&)text {
266   NSAttributedString* attributedString =
267       constrained_window::GetAttributedLabelString(
268           SysUTF16ToNSString(text), chrome_style::kTextFontStyle,
269           NSNaturalTextAlignment, NSLineBreakByWordWrapping);
270   [errorLabel_ setAttributedStringValue:attributedString];
272   // If there is more than one input showing, don't mark anything as
273   // invalid since we don't know the location of the problem.
274   if (!text.empty() && !bridge_->GetController()->ShouldRequestExpirationDate())
275     [cvcInput_ setValidityMessage:@"invalid"];
277   [self performLayoutAndDisplay:YES];
280 - (void)setPermanentErrorMessage:(const base::string16&)text {
281   if (!text.empty()) {
282     if (!permanentErrorBox_) {
283       permanentErrorBox_ = [CardUnmaskPromptViewCocoa createPlainBox];
284       [permanentErrorBox_ setFillColor:gfx::SkColorToCalibratedNSColor(
285                                            autofill::kWarningColor)];
286       [permanentErrorBox_
287           setContentViewMargins:NSMakeSize(kPermanentErrorHorizontalPadding,
288                                            kPermanentErrorVerticalPadding)];
290       permanentErrorLabel_.reset([constrained_window::CreateLabel() retain]);
291       [permanentErrorLabel_ setAutoresizingMask:NSViewWidthSizable];
292       [permanentErrorLabel_ setTextColor:gfx::SkColorToCalibratedNSColor(
293                                              kPermanentErrorTextColor)];
295       [permanentErrorBox_ addSubview:permanentErrorLabel_];
296       [[self view] addSubview:permanentErrorBox_];
297     }
299     NSAttributedString* attributedString =
300         constrained_window::GetAttributedLabelString(
301             SysUTF16ToNSString(text), chrome_style::kTextFontStyle,
302             NSNaturalTextAlignment, NSLineBreakByWordWrapping);
303     [permanentErrorLabel_ setAttributedStringValue:attributedString];
304   }
306   [permanentErrorBox_ setHidden:text.empty()];
307   [self setInputsEnabled:NO];
308   [self updateVerifyButtonEnabled];
309   [self setRetriableErrorMessage:base::string16()];
312 - (void)updateVerifyButtonEnabled {
313   BOOL enable = ![inputRowView_ isHidden] &&
314                 ![[permanentErrorLabel_ stringValue] length] &&
315                 bridge_->GetController()->InputCvcIsValid(
316                     base::SysNSStringToUTF16([cvcInput_ stringValue])) &&
317                 [self expirationDateIsValid];
319   [verifyButton_ setEnabled:enable];
322 - (void)onVerify:(id)sender {
323   bridge_->GetController()->OnUnmaskResponse(
324       base::SysNSStringToUTF16([cvcInput_ stringValue]),
325       base::SysNSStringToUTF16([monthPopup_ titleOfSelectedItem]),
326       base::SysNSStringToUTF16([yearPopup_ titleOfSelectedItem]),
327       [storageCheckbox_ state] == NSOnState);
330 - (void)onCancel:(id)sender {
331   bridge_->PerformClose();
334 - (BOOL)expirationDateIsValid {
335   if (!bridge_->GetController()->ShouldRequestExpirationDate())
336     return true;
338   return bridge_->GetController()->InputExpirationIsValid(
339       base::SysNSStringToUTF16([monthPopup_ titleOfSelectedItem]),
340       base::SysNSStringToUTF16([yearPopup_ titleOfSelectedItem]));
343 - (void)onExpirationDateChanged:(id)sender {
344   if ([self expirationDateIsValid]) {
345     if ([monthPopup_ invalid]) {
346       [monthPopup_ setValidityMessage:@""];
347       [yearPopup_ setValidityMessage:@""];
348       [self setRetriableErrorMessage:base::string16()];
349     }
350   } else if ([monthPopup_ indexOfSelectedItem] != monthPopupDefaultIndex_ &&
351              [yearPopup_ indexOfSelectedItem] != yearPopupDefaultIndex_) {
352     [monthPopup_ setValidityMessage:@"invalid"];
353     [yearPopup_ setValidityMessage:@"invalid"];
354     [self setRetriableErrorMessage:
355               l10n_util::GetStringUTF16(
356                   IDS_AUTOFILL_CARD_UNMASK_INVALID_EXPIRATION_DATE)];
357   }
359   [self updateVerifyButtonEnabled];
362 // Called when text in CVC input field changes.
363 - (void)controlTextDidChange:(NSNotification*)notification {
364   if (bridge_->GetController()->InputCvcIsValid(
365           base::SysNSStringToUTF16([cvcInput_ stringValue])))
366     [cvcInput_ setValidityMessage:@""];
368   [self updateVerifyButtonEnabled];
371 - (base::scoped_nsobject<NSView>)createStorageViewWithController:
372         (autofill::CardUnmaskPromptController*)controller {
373   base::scoped_nsobject<NSView> view([[NSView alloc] initWithFrame:NSZeroRect]);
374   [view setAutoresizingMask:NSViewWidthSizable];
376   base::scoped_nsobject<NSBox> box = [CardUnmaskPromptViewCocoa createPlainBox];
377   [box setAutoresizingMask:NSViewWidthSizable];
378   [box setFillColor:gfx::SkColorToCalibratedNSColor(kShadingColor)];
379   [box setContentViewMargins:NSMakeSize(chrome_style::kHorizontalPadding,
380                                         chrome_style::kClientBottomPadding)];
381   [view addSubview:box];
383   // Add "Store card on this device" checkbox.
384   storageCheckbox_.reset([[NSButton alloc] initWithFrame:NSZeroRect]);
385   [storageCheckbox_ setButtonType:NSSwitchButton];
386   [storageCheckbox_
387       setTitle:base::SysUTF16ToNSString(l10n_util::GetStringUTF16(
388                    IDS_AUTOFILL_CARD_UNMASK_PROMPT_STORAGE_CHECKBOX))];
389   [storageCheckbox_
390       setState:(controller->GetStoreLocallyStartState() ? NSOnState
391                                                         : NSOffState)];
392   [storageCheckbox_ sizeToFit];
393   [box addSubview:storageCheckbox_];
395   // Add "?" icon with tooltip.
396   storageTooltip_.reset([[AutofillTooltipController alloc]
397       initWithArrowLocation:info_bubble::kTopRight]);
398   [storageTooltip_ setImage:ui::ResourceBundle::GetSharedInstance()
399                                 .GetNativeImageNamed(IDR_AUTOFILL_TOOLTIP_ICON)
400                                 .ToNSImage()];
401   [storageTooltip_
402       setMessage:base::SysUTF16ToNSString(l10n_util::GetStringUTF16(
403                      IDS_AUTOFILL_CARD_UNMASK_PROMPT_STORAGE_TOOLTIP))];
404   [box addSubview:[storageTooltip_ view]];
405   [[storageTooltip_ view] setFrameOrigin:
406       NSMakePoint(NSMaxX([storageCheckbox_ frame]) + kButtonGap, 0)];
408   // Add horizontal separator.
409   base::scoped_nsobject<NSBox> separator =
410       [CardUnmaskPromptViewCocoa createPlainBox];
411   [separator setAutoresizingMask:NSViewWidthSizable];
412   [separator setFillColor:gfx::SkColorToCalibratedNSColor(kSubtleBorderColor)];
413   [view addSubview:separator];
415   [box sizeToFit];
416   [separator setFrame:NSMakeRect(0, NSMaxY([box frame]), NSWidth([box frame]),
417                                  kSeparatorHeight)];
418   [CardUnmaskPromptViewCocoa sizeToFitView:view];
419   return view;
422 // +---------------------------------------------------------------------------+
423 // | titleLabel_        (Single line.)                                         |
424 // |---------------------------------------------------------------------------|
425 // | permanentErrorBox_ (Multiline, may be hidden.)                            |
426 // |---------------------------------------------------------------------------|
427 // | instructionsLabel_ (Multiline.)                                           |
428 // |---------------------------------------------------------------------------|
429 // | monthPopup_ yearPopup_ cvcInput_ cvcImage                                 |
430 // |     (All enclosed in inputRowView_. Month and year may be hidden.)        |
431 // |---------------------------------------------------------------------------|
432 // | errorLabel_ (Multiline. Always takes up space for one line even if        |
433 // |                 empty. Hidden when progressOverlayView_ is displayed.)    |
434 // |---------------------------------------------------------------------------|
435 // |                                                         [Cancel] [Verify] |
436 // |---------------------------------------------------------------------------|
437 // | separator (NSBox.)                                                        |
438 // | storageCheckbox_ storageTooltip_ (Both enclosed in another NSBox.         |
439 // |                                   Checkbox and tooltip may be hidden.)    |
440 // |     (Both NSBoxes are enclosed in storageView_. Will all be nil if        |
441 // |          !CanStoreLocally()).                                             |
442 // +---------------------------------------------------------------------------+
444 // progressOverlayView_:
445 //     (Displayed below instructionsLabel_, may be hidden.
446 //         progressOverlaySpinner_ may be hidden while progressOverlayLabel_
447 //         is shown.)
448 // +---------------------------------------------------------------------------+
449 // | progressOverlaySpinner_ progressOverlayLabel_                             |
450 // +---------------------------------------------------------------------------+
451 - (void)performLayoutAndDisplay:(BOOL)display {
452   // Calculate dialog content width.
453   CGFloat contentWidth =
454       std::max(NSWidth([titleLabel_ frame]), NSWidth([inputRowView_ frame]));
455   contentWidth = std::max(contentWidth,
456                           NSWidth(NSUnionRect([storageCheckbox_ frame],
457                                               [[storageTooltip_ view] frame])));
458   contentWidth = std::max(contentWidth, kDialogContentMinWidth);
460   CGFloat contentMinX = chrome_style::kHorizontalPadding;
461   CGFloat contentMaxX = contentMinX + contentWidth;
462   CGFloat dialogWidth = contentMaxX + chrome_style::kHorizontalPadding;
464   CGFloat verifyMinY =
465       storageView_ ? NSMaxY([storageView_ frame]) + chrome_style::kRowPadding
466                    : chrome_style::kClientBottomPadding;
467   [verifyButton_
468       setFrameOrigin:NSMakePoint(contentMaxX - NSWidth([verifyButton_ frame]),
469                                  verifyMinY)];
471   [cancelButton_
472       setFrameOrigin:NSMakePoint(NSMinX([verifyButton_ frame]) - kButtonGap -
473                                      NSWidth([cancelButton_ frame]),
474                                  NSMinY([verifyButton_ frame]))];
476   [errorLabel_ setFrame:NSMakeRect(contentMinX, NSMaxY([cancelButton_ frame]) +
477                                                     kButtonsToRetriableErrorGap,
478                                    contentWidth, 0)];
479   cocoa_l10n_util::WrapOrSizeToFit(errorLabel_);
481   [inputRowView_ setFrameOrigin:NSMakePoint(contentMinX,
482                                             NSMaxY([errorLabel_ frame]) +
483                                                 kRetriableErrorToInputRowGap)];
485   [instructionsLabel_
486       setFrame:NSMakeRect(contentMinX, NSMaxY([inputRowView_ frame]) +
487                                            kInputRowToInstructionsGap,
488                           contentWidth, 0)];
489   cocoa_l10n_util::WrapOrSizeToFit(instructionsLabel_);
491   // Layout permanent error box.
492   CGFloat titleMinY;
493   if (permanentErrorBox_ && ![permanentErrorBox_ isHidden]) {
494     [permanentErrorBox_
495         setFrame:NSMakeRect(0, NSMaxY([instructionsLabel_ frame]) +
496                                    kPermanentErrorExteriorPadding,
497                             dialogWidth, 0)];
498     cocoa_l10n_util::WrapOrSizeToFit(permanentErrorLabel_);
499     [permanentErrorBox_ sizeToFit];
500     titleMinY =
501         NSMaxY([permanentErrorBox_ frame]) + kPermanentErrorExteriorPadding;
502   } else {
503     titleMinY = NSMaxY([instructionsLabel_ frame]) + kInstructionsToTitleGap;
504   }
506   [titleLabel_ setFrameOrigin:NSMakePoint(contentMinX, titleMinY)];
508   // Set dialog size.
509   [[self view]
510       setFrameSize:NSMakeSize(dialogWidth, NSMaxY([titleLabel_ frame]) +
511                                                chrome_style::kTitleTopPadding)];
513   [self updateProgressOverlayOrigin];
515   NSRect frameRect =
516       [[[self view] window] frameRectForContentRect:[[self view] frame]];
517   [[[self view] window] setFrame:frameRect display:display];
520 - (void)loadView {
521   autofill::CardUnmaskPromptController* controller = bridge_->GetController();
522   DCHECK(controller);
524   base::scoped_nsobject<NSView> mainView(
525       [[NSView alloc] initWithFrame:NSZeroRect]);
527   inputRowView_.reset([[NSView alloc] initWithFrame:NSZeroRect]);
528   [mainView addSubview:inputRowView_];
530   if (controller->CanStoreLocally()) {
531     storageView_ = [self createStorageViewWithController:controller];
532     [mainView addSubview:storageView_];
533   }
535   // Add progress overlay.
536   progressOverlayView_.reset([[NSView alloc] initWithFrame:NSZeroRect]);
537   [progressOverlayView_ setHidden:YES];
538   [mainView addSubview:progressOverlayView_];
540   progressOverlayLabel_.reset([constrained_window::CreateLabel() retain]);
541   [progressOverlayLabel_
542       setTextColor:gfx::SkColorToCalibratedNSColor(kProgressTextColor)];
543   [progressOverlayView_ addSubview:progressOverlayLabel_];
545   progressOverlaySpinner_.reset([[SpinnerView alloc]
546       initWithFrame:NSMakeRect(0, 0, kSpinnerSize, kSpinnerSize)]);
547   [progressOverlayView_ addSubview:progressOverlaySpinner_];
549   // Add title label.
550   titleLabel_.reset([constrained_window::CreateLabel() retain]);
551   NSAttributedString* titleString =
552       constrained_window::GetAttributedLabelString(
553           SysUTF16ToNSString(controller->GetWindowTitle()),
554           chrome_style::kTitleFontStyle, NSNaturalTextAlignment,
555           NSLineBreakByWordWrapping);
556   [titleLabel_ setAttributedStringValue:titleString];
557   [titleLabel_ sizeToFit];
558   [mainView addSubview:titleLabel_];
560   // Add instructions label.
561   instructionsLabel_.reset([constrained_window::CreateLabel() retain]);
562   NSAttributedString* instructionsString =
563       constrained_window::GetAttributedLabelString(
564           SysUTF16ToNSString(controller->GetInstructionsMessage()),
565           chrome_style::kTextFontStyle, NSNaturalTextAlignment,
566           NSLineBreakByWordWrapping);
567   [instructionsLabel_ setAttributedStringValue:instructionsString];
568   [mainView addSubview:instructionsLabel_];
570   // Add expiration date.
571   base::scoped_nsobject<NSView> expirationView;
572   if (controller->ShouldRequestExpirationDate()) {
573     expirationView.reset([[NSView alloc] initWithFrame:NSZeroRect]);
575     // Add expiration month.
576     autofill::MonthComboboxModel monthModel;
577     monthPopupDefaultIndex_ = monthModel.GetDefaultIndex();
578     monthPopup_.reset(
579         [CardUnmaskPromptViewCocoa buildDatePopupWithModel:monthModel]);
580     [monthPopup_ setTarget:self];
581     [monthPopup_ setAction:@selector(onExpirationDateChanged:)];
582     [expirationView addSubview:monthPopup_];
584     // Add separator between month and year.
585     base::scoped_nsobject<NSTextField> separatorLabel(
586         [constrained_window::CreateLabel() retain]);
587     NSAttributedString* separatorString =
588         constrained_window::GetAttributedLabelString(
589             SysUTF16ToNSString(l10n_util::GetStringUTF16(
590                 IDS_AUTOFILL_CARD_UNMASK_EXPIRATION_DATE_SEPARATOR)),
591             chrome_style::kTextFontStyle, NSNaturalTextAlignment,
592             NSLineBreakByWordWrapping);
593     [separatorLabel setAttributedStringValue:separatorString];
594     [separatorLabel sizeToFit];
595     [expirationView addSubview:separatorLabel];
597     // Add expiration year.
598     autofill::YearComboboxModel yearModel;
599     yearPopupDefaultIndex_ = yearModel.GetDefaultIndex();
600     yearPopup_.reset(
601         [CardUnmaskPromptViewCocoa buildDatePopupWithModel:yearModel]);
602     [yearPopup_ setTarget:self];
603     [yearPopup_ setAction:@selector(onExpirationDateChanged:)];
604     [expirationView addSubview:yearPopup_];
606     // Layout month, separator, and year within expirationView.
607     [separatorLabel setFrameOrigin:NSMakePoint(NSMaxX([monthPopup_ frame]), 0)];
608     [yearPopup_ setFrameOrigin:NSMakePoint(NSMaxX([separatorLabel frame]), 0)];
609     NSRect expirationFrame =
610         NSUnionRect([monthPopup_ frame], [yearPopup_ frame]);
611     expirationFrame.size.width += kYearToCvcGap;
612     [expirationView setFrame:expirationFrame];
613     [CardUnmaskPromptViewCocoa verticallyCenterSubviewsInView:expirationView];
614     [inputRowView_ addSubview:expirationView];
615   }
617   // Add CVC text input.
618   cvcInput_.reset([[AutofillTextField alloc] initWithFrame:NSZeroRect]);
619   [[cvcInput_ cell]
620       setPlaceholderString:l10n_util::GetNSString(
621                                IDS_AUTOFILL_DIALOG_PLACEHOLDER_CVC)];
622   [[cvcInput_ cell] setScrollable:YES];
623   [cvcInput_ setDelegate:self];
624   [cvcInput_ sizeToFit];
625   [cvcInput_ setFrame:NSMakeRect(NSMaxX([expirationView frame]), 0,
626                                  kCvcInputWidth, NSHeight([cvcInput_ frame]))];
627   [inputRowView_ addSubview:cvcInput_];
629   // Add CVC image.
630   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
631   NSImage* cvcImage =
632       rb.GetNativeImageNamed(controller->GetCvcImageRid()).ToNSImage();
633   base::scoped_nsobject<NSImageView> cvcImageView(
634       [[NSImageView alloc] initWithFrame:NSZeroRect]);
635   [cvcImageView setImage:cvcImage];
636   [cvcImageView setFrameSize:[cvcImage size]];
637   [cvcImageView
638       setFrameOrigin:NSMakePoint(
639                          NSMaxX([cvcInput_ frame]) + kCvcInputToImageGap, 0)];
640   [inputRowView_ addSubview:cvcImageView];
642   // Add error message label.
643   errorLabel_.reset([constrained_window::CreateLabel() retain]);
644   [errorLabel_
645       setTextColor:gfx::SkColorToCalibratedNSColor(autofill::kWarningColor)];
646   [mainView addSubview:errorLabel_];
648   // Add cancel button.
649   cancelButton_.reset(
650       [[ConstrainedWindowButton alloc] initWithFrame:NSZeroRect]);
651   [cancelButton_ setTitle:l10n_util::GetNSStringWithFixup(IDS_CANCEL)];
652   [cancelButton_ setKeyEquivalent:kKeyEquivalentEscape];
653   [cancelButton_ setTarget:self];
654   [cancelButton_ setAction:@selector(onCancel:)];
655   [cancelButton_ sizeToFit];
656   [mainView addSubview:cancelButton_];
658   // Add verify button.
659   verifyButton_.reset(
660       [[ConstrainedWindowButton alloc] initWithFrame:NSZeroRect]);
661   [verifyButton_ setTitle:l10n_util::GetNSStringWithFixup(
662                               IDS_AUTOFILL_CARD_UNMASK_CONFIRM_BUTTON)];
663   [verifyButton_ setKeyEquivalent:kKeyEquivalentReturn];
664   [verifyButton_ setTarget:self];
665   [verifyButton_ setAction:@selector(onVerify:)];
666   [verifyButton_ sizeToFit];
667   [self updateVerifyButtonEnabled];
668   [mainView addSubview:verifyButton_];
670   // Layout inputRowView_.
671   [CardUnmaskPromptViewCocoa sizeToFitView:inputRowView_];
672   [CardUnmaskPromptViewCocoa verticallyCenterSubviewsInView:inputRowView_];
674   [self setView:mainView];
675   [self performLayoutAndDisplay:NO];
678 @end