autofill_dialog_views.cc revision a02191e04bc25c4935f804f2c080ae28663d096d
1// Copyright (c) 2012 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.
4
5#include "chrome/browser/ui/views/autofill/autofill_dialog_views.h"
6
7#include <utility>
8
9#include "base/bind.h"
10#include "base/location.h"
11#include "base/strings/utf_string_conversions.h"
12#include "chrome/browser/profiles/profile.h"
13#include "chrome/browser/ui/autofill/autofill_dialog_sign_in_delegate.h"
14#include "chrome/browser/ui/autofill/autofill_dialog_view_delegate.h"
15#include "chrome/browser/ui/autofill/loading_animation.h"
16#include "chrome/browser/ui/views/autofill/expanding_textfield.h"
17#include "chrome/browser/ui/views/autofill/info_bubble.h"
18#include "chrome/browser/ui/views/autofill/tooltip_icon.h"
19#include "chrome/browser/ui/views/constrained_window_views.h"
20#include "components/autofill/content/browser/wallet/wallet_service_url.h"
21#include "components/autofill/core/browser/autofill_type.h"
22#include "components/web_modal/web_contents_modal_dialog_host.h"
23#include "components/web_modal/web_contents_modal_dialog_manager.h"
24#include "components/web_modal/web_contents_modal_dialog_manager_delegate.h"
25#include "content/public/browser/native_web_keyboard_event.h"
26#include "content/public/browser/navigation_controller.h"
27#include "content/public/browser/web_contents.h"
28#include "content/public/browser/web_contents_view.h"
29#include "grit/theme_resources.h"
30#include "grit/ui_resources.h"
31#include "third_party/skia/include/core/SkColor.h"
32#include "ui/base/l10n/l10n_util.h"
33#include "ui/base/models/combobox_model.h"
34#include "ui/base/models/menu_model.h"
35#include "ui/base/resource/resource_bundle.h"
36#include "ui/events/event_handler.h"
37#include "ui/gfx/animation/animation_delegate.h"
38#include "ui/gfx/canvas.h"
39#include "ui/gfx/color_utils.h"
40#include "ui/gfx/font_list.h"
41#include "ui/gfx/path.h"
42#include "ui/gfx/point.h"
43#include "ui/gfx/skia_util.h"
44#include "ui/views/background.h"
45#include "ui/views/border.h"
46#include "ui/views/bubble/bubble_border.h"
47#include "ui/views/bubble/bubble_frame_view.h"
48#include "ui/views/controls/button/blue_button.h"
49#include "ui/views/controls/button/checkbox.h"
50#include "ui/views/controls/button/label_button.h"
51#include "ui/views/controls/button/label_button_border.h"
52#include "ui/views/controls/button/menu_button.h"
53#include "ui/views/controls/combobox/combobox.h"
54#include "ui/views/controls/image_view.h"
55#include "ui/views/controls/label.h"
56#include "ui/views/controls/link.h"
57#include "ui/views/controls/menu/menu_runner.h"
58#include "ui/views/controls/separator.h"
59#include "ui/views/controls/styled_label.h"
60#include "ui/views/controls/textfield/textfield.h"
61#include "ui/views/controls/webview/webview.h"
62#include "ui/views/layout/box_layout.h"
63#include "ui/views/layout/fill_layout.h"
64#include "ui/views/layout/grid_layout.h"
65#include "ui/views/layout/layout_constants.h"
66#include "ui/views/painter.h"
67#include "ui/views/widget/widget.h"
68#include "ui/views/window/dialog_client_view.h"
69#include "ui/views/window/non_client_view.h"
70
71using web_modal::WebContentsModalDialogManager;
72using web_modal::WebContentsModalDialogManagerDelegate;
73
74namespace autofill {
75
76namespace {
77
78// The width for the section container.
79const int kSectionContainerWidth = 440;
80
81// The minimum useful height of the contents area of the dialog.
82const int kMinimumContentsHeight = 101;
83
84// The default height of the loading shield, also its minimum size.
85const int kInitialLoadingShieldHeight = 150;
86
87// Horizontal padding between text and other elements (in pixels).
88const int kAroundTextPadding = 4;
89
90// The space between the edges of a notification bar and the text within (in
91// pixels).
92const int kNotificationPadding = 17;
93
94// Vertical padding above and below each detail section (in pixels).
95const int kDetailSectionVerticalPadding = 10;
96
97const int kArrowHeight = 7;
98const int kArrowWidth = 2 * kArrowHeight;
99
100// The padding inside the edges of the dialog, in pixels.
101const int kDialogEdgePadding = 20;
102
103// The vertical padding between rows of manual inputs (in pixels).
104const int kManualInputRowPadding = 10;
105
106// Slight shading for mouse hover and legal document background.
107SkColor kShadingColor = SkColorSetARGB(7, 0, 0, 0);
108
109// A border color for the legal document view.
110SkColor kSubtleBorderColor = SkColorSetARGB(10, 0, 0, 0);
111
112// The top and bottom padding, in pixels, for the suggestions menu dropdown
113// arrows.
114const int kMenuButtonTopInset = 3;
115const int kMenuButtonBottomInset = 6;
116
117// The height in pixels of the padding above and below the overlay message view.
118const int kOverlayMessageVerticalPadding = 34;
119
120// Spacing below image and above text messages in overlay view.
121const int kOverlayImageBottomMargin = 100;
122
123const char kNotificationAreaClassName[] = "autofill/NotificationArea";
124const char kOverlayViewClassName[] = "autofill/OverlayView";
125const char kSectionContainerClassName[] = "autofill/SectionContainer";
126const char kSuggestedButtonClassName[] = "autofill/SuggestedButton";
127
128// Draws an arrow at the top of |canvas| pointing to |tip_x|.
129void DrawArrow(gfx::Canvas* canvas,
130               int tip_x,
131               const SkColor& fill_color,
132               const SkColor& stroke_color) {
133  const int arrow_half_width = kArrowWidth / 2.0f;
134
135  SkPath arrow;
136  arrow.moveTo(tip_x - arrow_half_width, kArrowHeight);
137  arrow.lineTo(tip_x, 0);
138  arrow.lineTo(tip_x + arrow_half_width, kArrowHeight);
139
140  SkPaint fill_paint;
141  fill_paint.setColor(fill_color);
142  canvas->DrawPath(arrow, fill_paint);
143
144  if (stroke_color != SK_ColorTRANSPARENT) {
145    SkPaint stroke_paint;
146    stroke_paint.setColor(stroke_color);
147    stroke_paint.setStyle(SkPaint::kStroke_Style);
148    canvas->DrawPath(arrow, stroke_paint);
149  }
150}
151
152void SelectComboboxValueOrSetToDefault(views::Combobox* combobox,
153                                       const base::string16& value) {
154  if (!combobox->SelectValue(value))
155    combobox->SetSelectedIndex(combobox->model()->GetDefaultIndex());
156}
157
158// This class handles layout for the first row of a SuggestionView.
159// It exists to circumvent shortcomings of GridLayout and BoxLayout (namely that
160// the former doesn't fully respect child visibility, and that the latter won't
161// expand a single child).
162class SectionRowView : public views::View {
163 public:
164  SectionRowView() { SetBorder(views::Border::CreateEmptyBorder(10, 0, 0, 0)); }
165
166  virtual ~SectionRowView() {}
167
168  // views::View implementation:
169  virtual gfx::Size GetPreferredSize() OVERRIDE {
170    int height = 0;
171    int width = 0;
172    for (int i = 0; i < child_count(); ++i) {
173      if (child_at(i)->visible()) {
174        if (width > 0)
175          width += kAroundTextPadding;
176
177        gfx::Size size = child_at(i)->GetPreferredSize();
178        height = std::max(height, size.height());
179        width += size.width();
180      }
181    }
182
183    gfx::Insets insets = GetInsets();
184    return gfx::Size(width + insets.width(), height + insets.height());
185  }
186
187  virtual void Layout() OVERRIDE {
188    const gfx::Rect bounds = GetContentsBounds();
189
190    // Icon is left aligned.
191    int start_x = bounds.x();
192    views::View* icon = child_at(0);
193    if (icon->visible()) {
194      icon->SizeToPreferredSize();
195      icon->SetX(start_x);
196      icon->SetY(bounds.y() +
197          (bounds.height() - icon->bounds().height()) / 2);
198      start_x += icon->bounds().width() + kAroundTextPadding;
199    }
200
201    // Textfield is right aligned.
202    int end_x = bounds.width();
203    views::View* textfield = child_at(2);
204    if (textfield->visible()) {
205      const int preferred_width = textfield->GetPreferredSize().width();
206      textfield->SetBounds(bounds.width() - preferred_width, bounds.y(),
207                           preferred_width, bounds.height());
208      end_x = textfield->bounds().x() - kAroundTextPadding;
209    }
210
211    // Label takes up all the space in between.
212    views::View* label = child_at(1);
213    if (label->visible())
214      label->SetBounds(start_x, bounds.y(), end_x - start_x, bounds.height());
215
216    views::View::Layout();
217  }
218
219 private:
220  DISALLOW_COPY_AND_ASSIGN(SectionRowView);
221};
222
223// A view that propagates visibility and preferred size changes.
224class LayoutPropagationView : public views::View {
225 public:
226  LayoutPropagationView() {}
227  virtual ~LayoutPropagationView() {}
228
229 protected:
230  virtual void ChildVisibilityChanged(views::View* child) OVERRIDE {
231    PreferredSizeChanged();
232  }
233  virtual void ChildPreferredSizeChanged(views::View* child) OVERRIDE {
234    PreferredSizeChanged();
235  }
236
237 private:
238  DISALLOW_COPY_AND_ASSIGN(LayoutPropagationView);
239};
240
241// A View for a single notification banner.
242class NotificationView : public views::View,
243                         public views::ButtonListener,
244                         public views::StyledLabelListener {
245 public:
246  NotificationView(const DialogNotification& data,
247                   AutofillDialogViewDelegate* delegate)
248      : data_(data),
249        delegate_(delegate),
250        checkbox_(NULL) {
251    scoped_ptr<views::View> label_view;
252    if (data.HasCheckbox()) {
253      scoped_ptr<views::Checkbox> checkbox(
254          new views::Checkbox(base::string16()));
255      checkbox->SetText(data.display_text());
256      checkbox->SetTextMultiLine(true);
257      checkbox->SetHorizontalAlignment(gfx::ALIGN_LEFT);
258      checkbox->SetTextColor(views::Button::STATE_NORMAL,
259                             data.GetTextColor());
260      checkbox->SetTextColor(views::Button::STATE_HOVERED,
261                             data.GetTextColor());
262      checkbox->SetChecked(data.checked());
263      checkbox->set_listener(this);
264      checkbox_ = checkbox.get();
265      label_view.reset(checkbox.release());
266    } else {
267      scoped_ptr<views::StyledLabel> label(new views::StyledLabel(
268          data.display_text(), this));
269      label->set_auto_color_readability_enabled(false);
270
271      views::StyledLabel::RangeStyleInfo text_style;
272      text_style.color = data.GetTextColor();
273
274      if (data.link_range().is_empty()) {
275        label->AddStyleRange(gfx::Range(0, data.display_text().size()),
276                             text_style);
277      } else {
278        gfx::Range prefix_range(0, data.link_range().start());
279        if (!prefix_range.is_empty())
280          label->AddStyleRange(prefix_range, text_style);
281
282        label->AddStyleRange(
283            data.link_range(),
284            views::StyledLabel::RangeStyleInfo::CreateForLink());
285
286        gfx::Range suffix_range(data.link_range().end(),
287                                data.display_text().size());
288        if (!suffix_range.is_empty())
289          label->AddStyleRange(suffix_range, text_style);
290      }
291
292      label_view.reset(label.release());
293    }
294
295    AddChildView(label_view.release());
296
297    if (!data.tooltip_text().empty())
298      AddChildView(new TooltipIcon(data.tooltip_text()));
299
300    set_background(
301       views::Background::CreateSolidBackground(data.GetBackgroundColor()));
302    SetBorder(views::Border::CreateSolidSidedBorder(
303        1, 0, 1, 0, data.GetBorderColor()));
304  }
305
306  virtual ~NotificationView() {}
307
308  views::Checkbox* checkbox() {
309    return checkbox_;
310  }
311
312  // views::View implementation.
313  virtual gfx::Insets GetInsets() const OVERRIDE {
314    int vertical_padding = kNotificationPadding;
315    if (checkbox_)
316      vertical_padding -= 3;
317    return gfx::Insets(vertical_padding, kDialogEdgePadding,
318                       vertical_padding, kDialogEdgePadding);
319  }
320
321  virtual int GetHeightForWidth(int width) OVERRIDE {
322    int label_width = width - GetInsets().width();
323    if (child_count() > 1) {
324      views::View* tooltip_icon = child_at(1);
325      label_width -= tooltip_icon->GetPreferredSize().width() +
326          kDialogEdgePadding;
327    }
328
329    return child_at(0)->GetHeightForWidth(label_width) + GetInsets().height();
330  }
331
332  virtual void Layout() OVERRIDE {
333    // Surprisingly, GetContentsBounds() doesn't consult GetInsets().
334    gfx::Rect bounds = GetLocalBounds();
335    bounds.Inset(GetInsets());
336    int right_bound = bounds.right();
337
338    if (child_count() > 1) {
339      // The icon takes up the entire vertical space and an extra 20px on
340      // each side. This increases the hover target for the tooltip.
341      views::View* tooltip_icon = child_at(1);
342      gfx::Size icon_size = tooltip_icon->GetPreferredSize();
343      int icon_width = icon_size.width() + kDialogEdgePadding;
344      right_bound -= icon_width;
345      tooltip_icon->SetBounds(
346          right_bound, 0,
347          icon_width + kDialogEdgePadding, GetLocalBounds().height());
348    }
349
350    child_at(0)->SetBounds(bounds.x(), bounds.y(),
351                           right_bound - bounds.x(), bounds.height());
352  }
353
354  // views::ButtonListener implementation.
355  virtual void ButtonPressed(views::Button* sender,
356                             const ui::Event& event) OVERRIDE {
357    DCHECK_EQ(sender, checkbox_);
358    delegate_->NotificationCheckboxStateChanged(data_.type(),
359                                                checkbox_->checked());
360  }
361
362  // views::StyledLabelListener implementation.
363  virtual void StyledLabelLinkClicked(const gfx::Range& range, int event_flags)
364      OVERRIDE {
365    delegate_->LinkClicked(data_.link_url());
366  }
367
368 private:
369  // The model data for this notification.
370  DialogNotification data_;
371
372  // The delegate that handles interaction with |this|.
373  AutofillDialogViewDelegate* delegate_;
374
375  // The checkbox associated with this notification, or NULL if there is none.
376  views::Checkbox* checkbox_;
377
378  DISALLOW_COPY_AND_ASSIGN(NotificationView);
379};
380
381// A view that displays a loading message with some dancing dots.
382class LoadingAnimationView : public views::View,
383                             public gfx::AnimationDelegate {
384 public:
385  explicit LoadingAnimationView(const base::string16& text) :
386      container_(new views::View()) {
387    AddChildView(container_);
388    container_->SetLayoutManager(
389        new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 0));
390
391    const gfx::FontList& font_list =
392        ui::ResourceBundle::GetSharedInstance().GetFontList(
393            ui::ResourceBundle::LargeFont);
394    animation_.reset(new LoadingAnimation(this, font_list.GetHeight()));
395
396    container_->AddChildView(new views::Label(text, font_list));
397
398    for (size_t i = 0; i < 3; ++i) {
399      container_->AddChildView(
400          new views::Label(base::ASCIIToUTF16("."), font_list));
401    }
402
403    OnNativeThemeChanged(GetNativeTheme());
404  }
405
406  virtual ~LoadingAnimationView() {}
407
408  // views::View implementation.
409  virtual void SetVisible(bool visible) OVERRIDE {
410    if (visible)
411      animation_->Start();
412    else
413      animation_->Reset();
414
415    views::View::SetVisible(visible);
416  }
417
418  virtual void Layout() OVERRIDE {
419    gfx::Size container_size = container_->GetPreferredSize();
420    gfx::Rect container_bounds((width() - container_size.width()) / 2,
421                               (height() - container_size.height()) / 2,
422                               container_size.width(),
423                               container_size.height());
424    container_->SetBoundsRect(container_bounds);
425    container_->Layout();
426
427    for (size_t i = 0; i < 3; ++i) {
428      views::View* dot = container_->child_at(i + 1);
429      dot->SetY(dot->y() + animation_->GetCurrentValueForDot(i));
430    }
431  }
432
433  virtual void OnNativeThemeChanged(const ui::NativeTheme* theme) OVERRIDE {
434    set_background(views::Background::CreateSolidBackground(
435        theme->GetSystemColor(ui::NativeTheme::kColorId_DialogBackground)));
436  }
437
438  // gfx::AnimationDelegate implementation.
439  virtual void AnimationProgressed(const gfx::Animation* animation) OVERRIDE {
440    DCHECK_EQ(animation, animation_.get());
441    Layout();
442  }
443
444 private:
445  // Contains the "Loading" label and the dots.
446  views::View* container_;
447
448  scoped_ptr<LoadingAnimation> animation_;
449
450  DISALLOW_COPY_AND_ASSIGN(LoadingAnimationView);
451};
452
453// Gets either the Combobox or ExpandingTextfield that is an ancestor (including
454// self) of |view|.
455views::View* GetAncestralInputView(views::View* view) {
456  if (view->GetClassName() == views::Combobox::kViewClassName)
457    return view;
458
459  return view->GetAncestorWithClassName(ExpandingTextfield::kViewClassName);
460}
461
462// A class that informs |delegate_| when an unhandled mouse press occurs.
463class MousePressedHandler : public ui::EventHandler {
464 public:
465  explicit MousePressedHandler(AutofillDialogViewDelegate* delegate)
466      : delegate_(delegate) {}
467
468  // ui::EventHandler implementation.
469  virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE {
470    if (event->type() == ui::ET_MOUSE_PRESSED && !event->handled())
471      delegate_->FocusMoved();
472  }
473
474 private:
475  AutofillDialogViewDelegate* const delegate_;
476
477  DISALLOW_COPY_AND_ASSIGN(MousePressedHandler);
478};
479
480}  // namespace
481
482// AutofillDialogViews::AccountChooser -----------------------------------------
483
484AutofillDialogViews::AccountChooser::AccountChooser(
485    AutofillDialogViewDelegate* delegate)
486    : image_(new views::ImageView()),
487      menu_button_(new views::MenuButton(NULL, base::string16(), this, true)),
488      link_(new views::Link()),
489      delegate_(delegate) {
490  SetBorder(views::Border::CreateEmptyBorder(0, 0, 0, 10));
491  SetLayoutManager(
492      new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0,
493                           kAroundTextPadding));
494  AddChildView(image_);
495
496  menu_button_->set_background(NULL);
497  menu_button_->SetBorder(views::Border::NullBorder());
498  gfx::Insets insets = GetInsets();
499  menu_button_->SetFocusPainter(
500      views::Painter::CreateDashedFocusPainterWithInsets(insets));
501  menu_button_->SetFocusable(true);
502  AddChildView(menu_button_);
503
504  link_->set_listener(this);
505  AddChildView(link_);
506}
507
508AutofillDialogViews::AccountChooser::~AccountChooser() {}
509
510void AutofillDialogViews::AccountChooser::Update() {
511  SetVisible(delegate_->ShouldShowAccountChooser());
512
513  gfx::Image icon = delegate_->AccountChooserImage();
514  image_->SetImage(icon.AsImageSkia());
515  menu_button_->SetText(delegate_->AccountChooserText());
516  // This allows the button to shrink if the new text is smaller.
517  menu_button_->ClearMaxTextSize();
518
519  bool show_link = !delegate_->MenuModelForAccountChooser();
520  menu_button_->SetVisible(!show_link);
521  link_->SetText(delegate_->SignInLinkText());
522  link_->SetVisible(show_link);
523
524  menu_runner_.reset();
525
526  PreferredSizeChanged();
527}
528
529void AutofillDialogViews::AccountChooser::OnMenuButtonClicked(
530    views::View* source,
531    const gfx::Point& point) {
532  DCHECK_EQ(menu_button_, source);
533
534  ui::MenuModel* model = delegate_->MenuModelForAccountChooser();
535  if (!model)
536    return;
537
538  menu_runner_.reset(new views::MenuRunner(model));
539  if (menu_runner_->RunMenuAt(source->GetWidget(),
540                              NULL,
541                              source->GetBoundsInScreen(),
542                              views::MenuItemView::TOPRIGHT,
543                              ui::MENU_SOURCE_NONE,
544                              0) == views::MenuRunner::MENU_DELETED) {
545    return;
546  }
547}
548
549views::View* AutofillDialogViews::GetLoadingShieldForTesting() {
550  return loading_shield_;
551}
552
553views::WebView* AutofillDialogViews::GetSignInWebViewForTesting() {
554  return sign_in_web_view_;
555}
556
557views::View* AutofillDialogViews::GetNotificationAreaForTesting() {
558  return notification_area_;
559}
560
561views::View* AutofillDialogViews::GetScrollableAreaForTesting() {
562  return scrollable_area_;
563}
564
565void AutofillDialogViews::AccountChooser::LinkClicked(views::Link* source,
566                                                      int event_flags) {
567  delegate_->SignInLinkClicked();
568}
569
570// AutofillDialogViews::OverlayView --------------------------------------------
571
572AutofillDialogViews::OverlayView::OverlayView(
573    AutofillDialogViewDelegate* delegate)
574    : delegate_(delegate),
575      image_view_(new views::ImageView()),
576      message_view_(new views::Label()) {
577  message_view_->SetAutoColorReadabilityEnabled(false);
578  message_view_->SetMultiLine(true);
579
580  AddChildView(image_view_);
581  AddChildView(message_view_);
582
583  OnNativeThemeChanged(GetNativeTheme());
584}
585
586AutofillDialogViews::OverlayView::~OverlayView() {}
587
588int AutofillDialogViews::OverlayView::GetHeightForContentsForWidth(int width) {
589  // In this case, 0 means "no preference".
590  if (!message_view_->visible())
591    return 0;
592
593  return kOverlayImageBottomMargin +
594      views::kButtonVEdgeMarginNew +
595      message_view_->GetHeightForWidth(width) +
596      image_view_->GetHeightForWidth(width);
597}
598
599void AutofillDialogViews::OverlayView::UpdateState() {
600  const DialogOverlayState& state = delegate_->GetDialogOverlay();
601
602  if (state.image.IsEmpty()) {
603    SetVisible(false);
604    return;
605  }
606
607  image_view_->SetImage(state.image.ToImageSkia());
608
609  message_view_->SetVisible(!state.string.text.empty());
610  message_view_->SetText(state.string.text);
611  message_view_->SetFontList(state.string.font_list);
612  message_view_->SetEnabledColor(GetNativeTheme()->GetSystemColor(
613      ui::NativeTheme::kColorId_TextfieldReadOnlyColor));
614
615  message_view_->SetBorder(
616      views::Border::CreateEmptyBorder(kOverlayMessageVerticalPadding,
617                                       kDialogEdgePadding,
618                                       kOverlayMessageVerticalPadding,
619                                       kDialogEdgePadding));
620
621  SetVisible(true);
622}
623
624gfx::Insets AutofillDialogViews::OverlayView::GetInsets() const {
625  return gfx::Insets(12, 12, 12, 12);
626}
627
628void AutofillDialogViews::OverlayView::Layout() {
629  gfx::Rect bounds = ContentBoundsSansBubbleBorder();
630  if (!message_view_->visible()) {
631    image_view_->SetBoundsRect(bounds);
632    return;
633  }
634
635  int message_height = message_view_->GetHeightForWidth(bounds.width());
636  int y = bounds.bottom() - message_height;
637  message_view_->SetBounds(bounds.x(), y, bounds.width(), message_height);
638
639  gfx::Size image_size = image_view_->GetPreferredSize();
640  y -= image_size.height() + kOverlayImageBottomMargin;
641  image_view_->SetBounds(bounds.x(), y, bounds.width(), image_size.height());
642}
643
644const char* AutofillDialogViews::OverlayView::GetClassName() const {
645  return kOverlayViewClassName;
646}
647
648void AutofillDialogViews::OverlayView::OnPaint(gfx::Canvas* canvas) {
649  // BubbleFrameView doesn't mask the window, it just draws the border via
650  // image assets. Match that rounding here.
651  gfx::Rect rect = ContentBoundsSansBubbleBorder();
652  const SkScalar kCornerRadius = SkIntToScalar(
653      GetBubbleBorder() ? GetBubbleBorder()->GetBorderCornerRadius() : 2);
654  gfx::Path window_mask;
655  window_mask.addRoundRect(gfx::RectToSkRect(rect),
656                           kCornerRadius, kCornerRadius);
657  canvas->ClipPath(window_mask);
658
659  OnPaintBackground(canvas);
660
661  // Draw the arrow, border, and fill for the bottom area.
662  if (message_view_->visible()) {
663    const int arrow_half_width = kArrowWidth / 2.0f;
664    SkPath arrow;
665    int y = message_view_->y() - 1;
666    // Note that we purposely draw slightly outside of |rect| so that the
667    // stroke is hidden on the sides.
668    arrow.moveTo(rect.x() - 1, y);
669    arrow.rLineTo(rect.width() / 2 - arrow_half_width, 0);
670    arrow.rLineTo(arrow_half_width, -kArrowHeight);
671    arrow.rLineTo(arrow_half_width, kArrowHeight);
672    arrow.lineTo(rect.right() + 1, y);
673    arrow.lineTo(rect.right() + 1, rect.bottom() + 1);
674    arrow.lineTo(rect.x() - 1, rect.bottom() + 1);
675    arrow.close();
676
677    // The mocked alpha blends were 7 for background & 10 for the border against
678    // a very bright background. The eye perceives luminance differences of
679    // darker colors much less than lighter colors, so increase the alpha blend
680    // amount the darker the color (lower the luminance).
681    SkPaint paint;
682    SkColor background_color = background()->get_color();
683    int background_luminance =
684        color_utils::GetLuminanceForColor(background_color);
685    int background_alpha = static_cast<int>(
686        7 + 15 * (255 - background_luminance) / 255);
687    int subtle_border_alpha = static_cast<int>(
688        10 + 20 * (255 - background_luminance) / 255);
689
690    paint.setColor(color_utils::BlendTowardOppositeLuminance(
691        background_color, background_alpha));
692    paint.setStyle(SkPaint::kFill_Style);
693    canvas->DrawPath(arrow, paint);
694    paint.setColor(color_utils::BlendTowardOppositeLuminance(
695        background_color, subtle_border_alpha));
696    paint.setStyle(SkPaint::kStroke_Style);
697    canvas->DrawPath(arrow, paint);
698  }
699
700  PaintChildren(canvas);
701}
702
703void AutofillDialogViews::OverlayView::OnNativeThemeChanged(
704    const ui::NativeTheme* theme) {
705  set_background(views::Background::CreateSolidBackground(
706      theme->GetSystemColor(ui::NativeTheme::kColorId_DialogBackground)));
707}
708
709views::BubbleBorder* AutofillDialogViews::OverlayView::GetBubbleBorder() {
710  views::View* frame = GetWidget()->non_client_view()->frame_view();
711  std::string bubble_frame_view_name(views::BubbleFrameView::kViewClassName);
712  if (frame->GetClassName() == bubble_frame_view_name)
713    return static_cast<views::BubbleFrameView*>(frame)->bubble_border();
714  NOTREACHED();
715  return NULL;
716}
717
718gfx::Rect AutofillDialogViews::OverlayView::ContentBoundsSansBubbleBorder() {
719  gfx::Rect bounds = GetContentsBounds();
720  int bubble_width = 5;
721  if (GetBubbleBorder())
722    bubble_width = GetBubbleBorder()->GetBorderThickness();
723  bounds.Inset(bubble_width, bubble_width, bubble_width, bubble_width);
724  return bounds;
725}
726
727// AutofillDialogViews::NotificationArea ---------------------------------------
728
729AutofillDialogViews::NotificationArea::NotificationArea(
730    AutofillDialogViewDelegate* delegate)
731    : delegate_(delegate) {
732  // Reserve vertical space for the arrow (regardless of whether one exists).
733  // The -1 accounts for the border.
734  SetBorder(views::Border::CreateEmptyBorder(kArrowHeight - 1, 0, 0, 0));
735
736  views::BoxLayout* box_layout =
737      new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0);
738  SetLayoutManager(box_layout);
739}
740
741AutofillDialogViews::NotificationArea::~NotificationArea() {}
742
743void AutofillDialogViews::NotificationArea::SetNotifications(
744    const std::vector<DialogNotification>& notifications) {
745  notifications_ = notifications;
746
747  RemoveAllChildViews(true);
748
749  if (notifications_.empty())
750    return;
751
752  for (size_t i = 0; i < notifications_.size(); ++i) {
753    const DialogNotification& notification = notifications_[i];
754    scoped_ptr<NotificationView> view(new NotificationView(notification,
755                                                           delegate_));
756
757    AddChildView(view.release());
758  }
759
760  PreferredSizeChanged();
761}
762
763gfx::Size AutofillDialogViews::NotificationArea::GetPreferredSize() {
764  gfx::Size size = views::View::GetPreferredSize();
765  // Ensure that long notifications wrap and don't enlarge the dialog.
766  size.set_width(1);
767  return size;
768}
769
770const char* AutofillDialogViews::NotificationArea::GetClassName() const {
771  return kNotificationAreaClassName;
772}
773
774void AutofillDialogViews::NotificationArea::PaintChildren(
775    gfx::Canvas* canvas) {}
776
777void AutofillDialogViews::NotificationArea::OnPaint(gfx::Canvas* canvas) {
778  views::View::OnPaint(canvas);
779  views::View::PaintChildren(canvas);
780
781  if (HasArrow()) {
782    DrawArrow(
783        canvas,
784        GetMirroredXInView(width() - arrow_centering_anchor_->width() / 2.0f),
785        notifications_[0].GetBackgroundColor(),
786        notifications_[0].GetBorderColor());
787  }
788}
789
790void AutofillDialogViews::OnWidgetDestroying(views::Widget* widget) {
791  if (widget == window_)
792    window_->GetRootView()->RemovePostTargetHandler(event_handler_.get());
793}
794
795void AutofillDialogViews::OnWidgetClosing(views::Widget* widget) {
796  observer_.Remove(widget);
797  if (error_bubble_ && error_bubble_->GetWidget() == widget)
798    error_bubble_ = NULL;
799}
800
801void AutofillDialogViews::OnWidgetBoundsChanged(views::Widget* widget,
802                                                const gfx::Rect& new_bounds) {
803  if (error_bubble_ && error_bubble_->GetWidget() == widget)
804    return;
805
806  // Notify the web contents of its new auto-resize limits.
807  if (sign_in_delegate_ && sign_in_web_view_->visible()) {
808    sign_in_delegate_->UpdateLimitsAndEnableAutoResize(
809        GetMinimumSignInViewSize(), GetMaximumSignInViewSize());
810  }
811  HideErrorBubble();
812}
813
814bool AutofillDialogViews::NotificationArea::HasArrow() {
815  return !notifications_.empty() && notifications_[0].HasArrow() &&
816      arrow_centering_anchor_.get();
817}
818
819// AutofillDialogViews::SectionContainer ---------------------------------------
820
821AutofillDialogViews::SectionContainer::SectionContainer(
822    const base::string16& label,
823    views::View* controls,
824    views::Button* proxy_button)
825    : proxy_button_(proxy_button),
826      forward_mouse_events_(false) {
827  set_notify_enter_exit_on_child(true);
828
829  SetBorder(views::Border::CreateEmptyBorder(kDetailSectionVerticalPadding,
830                                             kDialogEdgePadding,
831                                             kDetailSectionVerticalPadding,
832                                             kDialogEdgePadding));
833
834  ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
835  views::Label* label_view = new views::Label(
836      label, rb.GetFontList(ui::ResourceBundle::BoldFont));
837  label_view->SetHorizontalAlignment(gfx::ALIGN_LEFT);
838
839  views::View* label_bar = new views::View();
840  views::GridLayout* label_bar_layout = new views::GridLayout(label_bar);
841  label_bar->SetLayoutManager(label_bar_layout);
842  const int kColumnSetId = 0;
843  views::ColumnSet* columns = label_bar_layout->AddColumnSet(kColumnSetId);
844  columns->AddColumn(
845      views::GridLayout::LEADING,
846      views::GridLayout::LEADING,
847      0,
848      views::GridLayout::FIXED,
849      kSectionContainerWidth - proxy_button->GetPreferredSize().width(),
850      0);
851  columns->AddColumn(views::GridLayout::LEADING,
852                     views::GridLayout::LEADING,
853                     0,
854                     views::GridLayout::USE_PREF,
855                     0,
856                     0);
857  label_bar_layout->StartRow(0, kColumnSetId);
858  label_bar_layout->AddView(label_view);
859  label_bar_layout->AddView(proxy_button);
860
861  SetLayoutManager(new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0));
862  AddChildView(label_bar);
863  AddChildView(controls);
864}
865
866AutofillDialogViews::SectionContainer::~SectionContainer() {}
867
868void AutofillDialogViews::SectionContainer::SetActive(bool active) {
869  bool is_active = active && proxy_button_->visible();
870  if (is_active == !!background())
871    return;
872
873  set_background(is_active ?
874      views::Background::CreateSolidBackground(kShadingColor) :
875      NULL);
876  SchedulePaint();
877}
878
879void AutofillDialogViews::SectionContainer::SetForwardMouseEvents(
880    bool forward) {
881  forward_mouse_events_ = forward;
882  if (!forward)
883    set_background(NULL);
884}
885
886const char* AutofillDialogViews::SectionContainer::GetClassName() const {
887  return kSectionContainerClassName;
888}
889
890void AutofillDialogViews::SectionContainer::OnMouseMoved(
891    const ui::MouseEvent& event) {
892  SetActive(ShouldForwardEvent(event));
893}
894
895void AutofillDialogViews::SectionContainer::OnMouseEntered(
896    const ui::MouseEvent& event) {
897  if (!ShouldForwardEvent(event))
898    return;
899
900  SetActive(true);
901  proxy_button_->OnMouseEntered(ProxyEvent(event));
902  SchedulePaint();
903}
904
905void AutofillDialogViews::SectionContainer::OnMouseExited(
906    const ui::MouseEvent& event) {
907  SetActive(false);
908  if (!ShouldForwardEvent(event))
909    return;
910
911  proxy_button_->OnMouseExited(ProxyEvent(event));
912  SchedulePaint();
913}
914
915bool AutofillDialogViews::SectionContainer::OnMousePressed(
916    const ui::MouseEvent& event) {
917  if (!ShouldForwardEvent(event))
918    return false;
919
920  return proxy_button_->OnMousePressed(ProxyEvent(event));
921}
922
923void AutofillDialogViews::SectionContainer::OnMouseReleased(
924    const ui::MouseEvent& event) {
925  if (!ShouldForwardEvent(event))
926    return;
927
928  proxy_button_->OnMouseReleased(ProxyEvent(event));
929}
930
931void AutofillDialogViews::SectionContainer::OnGestureEvent(
932    ui::GestureEvent* event) {
933  if (!ShouldForwardEvent(*event))
934    return;
935
936  proxy_button_->OnGestureEvent(event);
937}
938
939views::View* AutofillDialogViews::SectionContainer::GetEventHandlerForRect(
940    const gfx::Rect& rect) {
941  // TODO(tdanderson): Modify this function to support rect-based event
942  // targeting.
943
944  views::View* handler = views::View::GetEventHandlerForRect(rect);
945  // If the event is not in the label bar and there's no background to be
946  // cleared, let normal event handling take place.
947  if (!background() &&
948      rect.CenterPoint().y() > child_at(0)->bounds().bottom()) {
949    return handler;
950  }
951
952  // Special case for (CVC) inputs in the suggestion view.
953  if (forward_mouse_events_ &&
954      handler->GetAncestorWithClassName(ExpandingTextfield::kViewClassName)) {
955    return handler;
956  }
957
958  // Special case for the proxy button itself.
959  if (handler == proxy_button_)
960    return handler;
961
962  return this;
963}
964
965// static
966ui::MouseEvent AutofillDialogViews::SectionContainer::ProxyEvent(
967    const ui::MouseEvent& event) {
968  ui::MouseEvent event_copy = event;
969  event_copy.set_location(gfx::Point());
970  return event_copy;
971}
972
973bool AutofillDialogViews::SectionContainer::ShouldForwardEvent(
974    const ui::LocatedEvent& event) {
975  // Always forward events on the label bar.
976  return forward_mouse_events_ || event.y() <= child_at(0)->bounds().bottom();
977}
978
979// AutofillDialogViews::SuggestedButton ----------------------------------------
980
981AutofillDialogViews::SuggestedButton::SuggestedButton(
982    views::MenuButtonListener* listener)
983    : views::MenuButton(NULL, base::string16(), listener, false) {
984  const int kFocusBorderWidth = 1;
985  SetBorder(views::Border::CreateEmptyBorder(kMenuButtonTopInset,
986                                             kFocusBorderWidth,
987                                             kMenuButtonBottomInset,
988                                             kFocusBorderWidth));
989  gfx::Insets insets = GetInsets();
990  insets += gfx::Insets(-kFocusBorderWidth, -kFocusBorderWidth,
991                        -kFocusBorderWidth, -kFocusBorderWidth);
992  SetFocusPainter(
993      views::Painter::CreateDashedFocusPainterWithInsets(insets));
994  SetFocusable(true);
995}
996
997AutofillDialogViews::SuggestedButton::~SuggestedButton() {}
998
999gfx::Size AutofillDialogViews::SuggestedButton::GetPreferredSize() {
1000  ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
1001  gfx::Size size = rb.GetImageNamed(ResourceIDForState()).Size();
1002  const gfx::Insets insets = GetInsets();
1003  size.Enlarge(insets.width(), insets.height());
1004  return size;
1005}
1006
1007const char* AutofillDialogViews::SuggestedButton::GetClassName() const {
1008  return kSuggestedButtonClassName;
1009}
1010
1011void AutofillDialogViews::SuggestedButton::PaintChildren(gfx::Canvas* canvas) {}
1012
1013void AutofillDialogViews::SuggestedButton::OnPaint(gfx::Canvas* canvas) {
1014  ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
1015  const gfx::Insets insets = GetInsets();
1016  canvas->DrawImageInt(*rb.GetImageSkiaNamed(ResourceIDForState()),
1017                       insets.left(), insets.top());
1018  views::Painter::PaintFocusPainter(this, canvas, focus_painter());
1019}
1020
1021int AutofillDialogViews::SuggestedButton::ResourceIDForState() const {
1022  views::Button::ButtonState button_state = state();
1023  if (button_state == views::Button::STATE_PRESSED)
1024    return IDR_AUTOFILL_DIALOG_MENU_BUTTON_P;
1025  else if (button_state == views::Button::STATE_HOVERED)
1026    return IDR_AUTOFILL_DIALOG_MENU_BUTTON_H;
1027  else if (button_state == views::Button::STATE_DISABLED)
1028    return IDR_AUTOFILL_DIALOG_MENU_BUTTON_D;
1029  DCHECK_EQ(views::Button::STATE_NORMAL, button_state);
1030  return IDR_AUTOFILL_DIALOG_MENU_BUTTON;
1031}
1032
1033// AutofillDialogViews::DetailsContainerView -----------------------------------
1034
1035AutofillDialogViews::DetailsContainerView::DetailsContainerView(
1036    const base::Closure& callback)
1037    : bounds_changed_callback_(callback),
1038      ignore_layouts_(false) {}
1039
1040AutofillDialogViews::DetailsContainerView::~DetailsContainerView() {}
1041
1042void AutofillDialogViews::DetailsContainerView::OnBoundsChanged(
1043    const gfx::Rect& previous_bounds) {
1044  bounds_changed_callback_.Run();
1045}
1046
1047void AutofillDialogViews::DetailsContainerView::Layout() {
1048  if (!ignore_layouts_)
1049    views::View::Layout();
1050}
1051
1052// AutofillDialogViews::SuggestionView -----------------------------------------
1053
1054AutofillDialogViews::SuggestionView::SuggestionView(
1055    AutofillDialogViews* autofill_dialog)
1056    : label_(new views::Label()),
1057      label_line_2_(new views::Label()),
1058      icon_(new views::ImageView()),
1059      textfield_(
1060          new ExpandingTextfield(base::string16(),
1061                                 base::string16(),
1062                                 false,
1063                                 autofill_dialog)) {
1064  // TODO(estade): Make this the correct color.
1065  SetBorder(views::Border::CreateSolidSidedBorder(1, 0, 0, 0, SK_ColorLTGRAY));
1066
1067  SectionRowView* label_container = new SectionRowView();
1068  AddChildView(label_container);
1069
1070  // Label and icon.
1071  label_container->AddChildView(icon_);
1072  label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
1073  label_container->AddChildView(label_);
1074
1075  // TODO(estade): get the sizing and spacing right on this textfield.
1076  textfield_->SetVisible(false);
1077  textfield_->SetDefaultWidthInCharacters(15);
1078  label_container->AddChildView(textfield_);
1079
1080  label_line_2_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
1081  label_line_2_->SetVisible(false);
1082  label_line_2_->SetLineHeight(22);
1083  label_line_2_->SetMultiLine(true);
1084  AddChildView(label_line_2_);
1085
1086  SetLayoutManager(new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 7));
1087}
1088
1089AutofillDialogViews::SuggestionView::~SuggestionView() {}
1090
1091gfx::Size AutofillDialogViews::SuggestionView::GetPreferredSize() {
1092  // There's no preferred width. The parent's layout should get the preferred
1093  // height from GetHeightForWidth().
1094  return gfx::Size();
1095}
1096
1097int AutofillDialogViews::SuggestionView::GetHeightForWidth(int width) {
1098  int height = 0;
1099  CanUseVerticallyCompactText(width, &height);
1100  return height;
1101}
1102
1103bool AutofillDialogViews::SuggestionView::CanUseVerticallyCompactText(
1104    int available_width,
1105    int* resulting_height) {
1106  // This calculation may be costly, avoid doing it more than once per width.
1107  if (!calculated_heights_.count(available_width)) {
1108    // Changing the state of |this| now will lead to extra layouts and
1109    // paints we don't want, so create another SuggestionView to calculate
1110    // which label we have room to show.
1111    SuggestionView sizing_view(NULL);
1112    sizing_view.SetLabelText(state_.vertically_compact_text);
1113    sizing_view.SetIcon(state_.icon);
1114    sizing_view.SetTextfield(state_.extra_text, state_.extra_icon);
1115    sizing_view.label_->SetSize(gfx::Size(available_width, 0));
1116    sizing_view.label_line_2_->SetSize(gfx::Size(available_width, 0));
1117
1118    // Shortcut |sizing_view|'s GetHeightForWidth() to avoid an infinite loop.
1119    // Its BoxLayout must do these calculations for us.
1120    views::LayoutManager* layout = sizing_view.GetLayoutManager();
1121    if (layout->GetPreferredSize(&sizing_view).width() <= available_width) {
1122      calculated_heights_[available_width] = std::make_pair(
1123          true,
1124          layout->GetPreferredHeightForWidth(&sizing_view, available_width));
1125    } else {
1126      sizing_view.SetLabelText(state_.horizontally_compact_text);
1127      calculated_heights_[available_width] = std::make_pair(
1128          false,
1129          layout->GetPreferredHeightForWidth(&sizing_view, available_width));
1130    }
1131  }
1132
1133  const std::pair<bool, int>& values = calculated_heights_[available_width];
1134  *resulting_height = values.second;
1135  return values.first;
1136}
1137
1138void AutofillDialogViews::SuggestionView::OnBoundsChanged(
1139    const gfx::Rect& previous_bounds) {
1140  UpdateLabelText();
1141}
1142
1143void AutofillDialogViews::SuggestionView::SetState(
1144    const SuggestionState& state) {
1145  calculated_heights_.clear();
1146  state_ = state;
1147  SetVisible(state_.visible);
1148  UpdateLabelText();
1149  SetIcon(state_.icon);
1150  SetTextfield(state_.extra_text, state_.extra_icon);
1151  PreferredSizeChanged();
1152}
1153
1154void AutofillDialogViews::SuggestionView::SetLabelText(
1155    const base::string16& text) {
1156  // TODO(estade): does this localize well?
1157  base::string16 line_return(base::ASCIIToUTF16("\n"));
1158  size_t position = text.find(line_return);
1159  if (position == base::string16::npos) {
1160    label_->SetText(text);
1161    label_line_2_->SetVisible(false);
1162  } else {
1163    label_->SetText(text.substr(0, position));
1164    label_line_2_->SetText(text.substr(position + line_return.length()));
1165    label_line_2_->SetVisible(true);
1166  }
1167}
1168
1169void AutofillDialogViews::SuggestionView::SetIcon(
1170    const gfx::Image& image) {
1171  icon_->SetVisible(!image.IsEmpty());
1172  icon_->SetImage(image.AsImageSkia());
1173}
1174
1175void AutofillDialogViews::SuggestionView::SetTextfield(
1176    const base::string16& placeholder_text,
1177    const gfx::Image& icon) {
1178  textfield_->SetPlaceholderText(placeholder_text);
1179  textfield_->SetIcon(icon);
1180  textfield_->SetVisible(!placeholder_text.empty());
1181}
1182
1183void AutofillDialogViews::SuggestionView::UpdateLabelText() {
1184  int unused;
1185  SetLabelText(CanUseVerticallyCompactText(width(), &unused) ?
1186      state_.vertically_compact_text :
1187      state_.horizontally_compact_text);
1188}
1189
1190// AutofillDialogView ----------------------------------------------------------
1191
1192// static
1193AutofillDialogView* AutofillDialogView::Create(
1194    AutofillDialogViewDelegate* delegate) {
1195  return new AutofillDialogViews(delegate);
1196}
1197
1198// AutofillDialogViews ---------------------------------------------------------
1199
1200AutofillDialogViews::AutofillDialogViews(AutofillDialogViewDelegate* delegate)
1201    : delegate_(delegate),
1202      updates_scope_(0),
1203      needs_update_(false),
1204      window_(NULL),
1205      notification_area_(NULL),
1206      account_chooser_(NULL),
1207      sign_in_web_view_(NULL),
1208      scrollable_area_(NULL),
1209      details_container_(NULL),
1210      loading_shield_(NULL),
1211      loading_shield_height_(0),
1212      overlay_view_(NULL),
1213      button_strip_extra_view_(NULL),
1214      save_in_chrome_checkbox_(NULL),
1215      save_in_chrome_checkbox_container_(NULL),
1216      button_strip_image_(NULL),
1217      footnote_view_(NULL),
1218      legal_document_view_(NULL),
1219      focus_manager_(NULL),
1220      error_bubble_(NULL),
1221      observer_(this) {
1222  DCHECK(delegate);
1223  detail_groups_.insert(std::make_pair(SECTION_CC,
1224                                       DetailsGroup(SECTION_CC)));
1225  detail_groups_.insert(std::make_pair(SECTION_BILLING,
1226                                       DetailsGroup(SECTION_BILLING)));
1227  detail_groups_.insert(std::make_pair(SECTION_CC_BILLING,
1228                                       DetailsGroup(SECTION_CC_BILLING)));
1229  detail_groups_.insert(std::make_pair(SECTION_SHIPPING,
1230                                       DetailsGroup(SECTION_SHIPPING)));
1231}
1232
1233AutofillDialogViews::~AutofillDialogViews() {
1234  HideErrorBubble();
1235  DCHECK(!window_);
1236}
1237
1238void AutofillDialogViews::Show() {
1239  InitChildViews();
1240  UpdateAccountChooser();
1241  UpdateNotificationArea();
1242  UpdateButtonStripExtraView();
1243
1244  // Ownership of |contents_| is handed off by this call. The widget will take
1245  // care of deleting itself after calling DeleteDelegate().
1246  WebContentsModalDialogManager* web_contents_modal_dialog_manager =
1247      WebContentsModalDialogManager::FromWebContents(
1248          delegate_->GetWebContents());
1249  WebContentsModalDialogManagerDelegate* modal_delegate =
1250      web_contents_modal_dialog_manager->delegate();
1251  DCHECK(modal_delegate);
1252  window_ = views::Widget::CreateWindowAsFramelessChild(
1253      this, modal_delegate->GetWebContentsModalDialogHost()->GetHostView());
1254  web_contents_modal_dialog_manager->ShowModalDialog(
1255      window_->GetNativeView());
1256  focus_manager_ = window_->GetFocusManager();
1257  focus_manager_->AddFocusChangeListener(this);
1258
1259  ShowDialogInMode(DETAIL_INPUT);
1260
1261  // Listen for size changes on the browser.
1262  views::Widget* browser_widget =
1263      views::Widget::GetTopLevelWidgetForNativeView(
1264          delegate_->GetWebContents()->GetView()->GetNativeView());
1265  observer_.Add(browser_widget);
1266
1267  // Listen for unhandled mouse presses on the non-client view.
1268  event_handler_.reset(new MousePressedHandler(delegate_));
1269  window_->GetRootView()->AddPostTargetHandler(event_handler_.get());
1270  observer_.Add(window_);
1271}
1272
1273void AutofillDialogViews::Hide() {
1274  if (window_)
1275    window_->Close();
1276}
1277
1278void AutofillDialogViews::UpdatesStarted() {
1279  updates_scope_++;
1280}
1281
1282void AutofillDialogViews::UpdatesFinished() {
1283  updates_scope_--;
1284  DCHECK_GE(updates_scope_, 0);
1285  if (updates_scope_ == 0 && needs_update_) {
1286    needs_update_ = false;
1287    ContentsPreferredSizeChanged();
1288  }
1289}
1290
1291void AutofillDialogViews::UpdateAccountChooser() {
1292  account_chooser_->Update();
1293
1294  bool show_loading = delegate_->ShouldShowSpinner();
1295  if (show_loading != loading_shield_->visible()) {
1296    if (show_loading) {
1297      loading_shield_height_ = std::max(kInitialLoadingShieldHeight,
1298                                        GetContentsBounds().height());
1299      ShowDialogInMode(LOADING);
1300    } else {
1301      bool show_sign_in = delegate_->ShouldShowSignInWebView();
1302      ShowDialogInMode(show_sign_in ? SIGN_IN : DETAIL_INPUT);
1303    }
1304
1305    InvalidateLayout();
1306    ContentsPreferredSizeChanged();
1307  }
1308
1309  // Update legal documents for the account.
1310  if (footnote_view_) {
1311    const base::string16 text = delegate_->LegalDocumentsText();
1312    legal_document_view_->SetText(text);
1313
1314    if (!text.empty()) {
1315      const std::vector<gfx::Range>& link_ranges =
1316          delegate_->LegalDocumentLinks();
1317      for (size_t i = 0; i < link_ranges.size(); ++i) {
1318        views::StyledLabel::RangeStyleInfo link_range_info =
1319            views::StyledLabel::RangeStyleInfo::CreateForLink();
1320        link_range_info.disable_line_wrapping = false;
1321        legal_document_view_->AddStyleRange(link_ranges[i], link_range_info);
1322      }
1323    }
1324
1325    footnote_view_->SetVisible(!text.empty());
1326    ContentsPreferredSizeChanged();
1327  }
1328
1329  if (GetWidget())
1330    GetWidget()->UpdateWindowTitle();
1331}
1332
1333void AutofillDialogViews::UpdateButtonStrip() {
1334  button_strip_extra_view_->SetVisible(
1335      GetDialogButtons() != ui::DIALOG_BUTTON_NONE);
1336  UpdateButtonStripExtraView();
1337  GetDialogClientView()->UpdateDialogButtons();
1338
1339  ContentsPreferredSizeChanged();
1340}
1341
1342void AutofillDialogViews::UpdateOverlay() {
1343  overlay_view_->UpdateState();
1344  ContentsPreferredSizeChanged();
1345}
1346
1347void AutofillDialogViews::UpdateDetailArea() {
1348  scrollable_area_->SetVisible(true);
1349  ContentsPreferredSizeChanged();
1350}
1351
1352void AutofillDialogViews::UpdateForErrors() {
1353  ValidateForm();
1354}
1355
1356void AutofillDialogViews::UpdateNotificationArea() {
1357  DCHECK(notification_area_);
1358  notification_area_->SetNotifications(delegate_->CurrentNotifications());
1359  ContentsPreferredSizeChanged();
1360}
1361
1362void AutofillDialogViews::UpdateSection(DialogSection section) {
1363  UpdateSectionImpl(section, true);
1364}
1365
1366void AutofillDialogViews::UpdateErrorBubble() {
1367  if (!delegate_->ShouldShowErrorBubble())
1368    HideErrorBubble();
1369}
1370
1371void AutofillDialogViews::FillSection(DialogSection section,
1372                                      ServerFieldType originating_type) {
1373  DetailsGroup* group = GroupForSection(section);
1374  // Make sure to overwrite the originating input if it exists.
1375  TextfieldMap::iterator text_mapping =
1376      group->textfields.find(originating_type);
1377  if (text_mapping != group->textfields.end())
1378    text_mapping->second->SetText(base::string16());
1379
1380  // If the Autofill data comes from a credit card, make sure to overwrite the
1381  // CC comboboxes (even if they already have something in them). If the
1382  // Autofill data comes from an AutofillProfile, leave the comboboxes alone.
1383  if (section == GetCreditCardSection() &&
1384      AutofillType(originating_type).group() == CREDIT_CARD) {
1385    for (ComboboxMap::const_iterator it = group->comboboxes.begin();
1386         it != group->comboboxes.end(); ++it) {
1387      if (AutofillType(it->first).group() == CREDIT_CARD)
1388        it->second->SetSelectedIndex(it->second->model()->GetDefaultIndex());
1389    }
1390  }
1391
1392  UpdateSectionImpl(section, false);
1393}
1394
1395void AutofillDialogViews::GetUserInput(DialogSection section,
1396                                       FieldValueMap* output) {
1397  DetailsGroup* group = GroupForSection(section);
1398  for (TextfieldMap::const_iterator it = group->textfields.begin();
1399       it != group->textfields.end(); ++it) {
1400    output->insert(std::make_pair(it->first, it->second->GetText()));
1401  }
1402  for (ComboboxMap::const_iterator it = group->comboboxes.begin();
1403       it != group->comboboxes.end(); ++it) {
1404    output->insert(std::make_pair(it->first,
1405        it->second->model()->GetItemAt(it->second->selected_index())));
1406  }
1407}
1408
1409base::string16 AutofillDialogViews::GetCvc() {
1410  return GroupForSection(GetCreditCardSection())->suggested_info->
1411      textfield()->GetText();
1412}
1413
1414bool AutofillDialogViews::SaveDetailsLocally() {
1415  DCHECK(save_in_chrome_checkbox_->visible());
1416  return save_in_chrome_checkbox_->checked();
1417}
1418
1419const content::NavigationController* AutofillDialogViews::ShowSignIn() {
1420  // The initial minimum width and height are set such that the dialog
1421  // won't change size before the page is loaded.
1422  int min_width = GetContentsBounds().width();
1423  // The height has to include the button strip.
1424  int min_height = GetDialogClientView()->GetContentsBounds().height();
1425
1426  // TODO(abodenha): We should be able to use the WebContents of the WebView
1427  // to navigate instead of LoadInitialURL. Figure out why it doesn't work.
1428  sign_in_delegate_.reset(
1429      new AutofillDialogSignInDelegate(
1430          this,
1431          sign_in_web_view_->GetWebContents(),
1432          delegate_->GetWebContents(),
1433          gfx::Size(min_width, min_height), GetMaximumSignInViewSize()));
1434  sign_in_web_view_->LoadInitialURL(delegate_->SignInUrl());
1435
1436  ShowDialogInMode(SIGN_IN);
1437
1438  ContentsPreferredSizeChanged();
1439
1440  return &sign_in_web_view_->web_contents()->GetController();
1441}
1442
1443void AutofillDialogViews::HideSignIn() {
1444  sign_in_web_view_->SetWebContents(NULL);
1445
1446  if (delegate_->ShouldShowSpinner()) {
1447    UpdateAccountChooser();
1448  } else {
1449    ShowDialogInMode(DETAIL_INPUT);
1450    InvalidateLayout();
1451  }
1452  DCHECK(!sign_in_web_view_->visible());
1453
1454  ContentsPreferredSizeChanged();
1455}
1456
1457void AutofillDialogViews::ModelChanged() {
1458  menu_runner_.reset();
1459
1460  for (DetailGroupMap::const_iterator iter = detail_groups_.begin();
1461       iter != detail_groups_.end(); ++iter) {
1462    UpdateDetailsGroupState(iter->second);
1463  }
1464}
1465
1466void AutofillDialogViews::OnSignInResize(const gfx::Size& pref_size) {
1467  sign_in_web_view_->SetPreferredSize(pref_size);
1468  ContentsPreferredSizeChanged();
1469}
1470
1471void AutofillDialogViews::ValidateSection(DialogSection section) {
1472  ValidateGroup(*GroupForSection(section), VALIDATE_EDIT);
1473}
1474
1475gfx::Size AutofillDialogViews::GetPreferredSize() {
1476  if (preferred_size_.IsEmpty())
1477    preferred_size_ = CalculatePreferredSize(false);
1478
1479  return preferred_size_;
1480}
1481
1482gfx::Size AutofillDialogViews::GetMinimumSize() {
1483  return CalculatePreferredSize(true);
1484}
1485
1486void AutofillDialogViews::Layout() {
1487  const gfx::Rect content_bounds = GetContentsBounds();
1488  if (sign_in_web_view_->visible()) {
1489    sign_in_web_view_->SetBoundsRect(content_bounds);
1490    return;
1491  }
1492
1493  if (loading_shield_->visible()) {
1494    loading_shield_->SetBoundsRect(bounds());
1495    return;
1496  }
1497
1498  const int x = content_bounds.x();
1499  const int y = content_bounds.y();
1500  const int width = content_bounds.width();
1501  // Layout notification area at top of dialog.
1502  int notification_height = notification_area_->GetHeightForWidth(width);
1503  notification_area_->SetBounds(x, y, width, notification_height);
1504
1505  // The rest (the |scrollable_area_|) takes up whatever's left.
1506  if (scrollable_area_->visible()) {
1507    int scroll_y = y;
1508    if (notification_height > notification_area_->GetInsets().height())
1509      scroll_y += notification_height + views::kRelatedControlVerticalSpacing;
1510
1511    int scroll_bottom = content_bounds.bottom();
1512    DCHECK_EQ(scrollable_area_->contents(), details_container_);
1513    details_container_->SizeToPreferredSize();
1514    details_container_->Layout();
1515    // TODO(estade): remove this hack. See crbug.com/285996
1516    details_container_->set_ignore_layouts(true);
1517    scrollable_area_->SetBounds(x, scroll_y, width, scroll_bottom - scroll_y);
1518    details_container_->set_ignore_layouts(false);
1519  }
1520
1521  if (error_bubble_)
1522    error_bubble_->UpdatePosition();
1523}
1524
1525void AutofillDialogViews::OnNativeThemeChanged(
1526    const ui::NativeTheme* theme) {
1527  if (!legal_document_view_)
1528    return;
1529
1530  // NOTE: This color may change because of |auto_color_readability|, set on
1531  // |legal_document_view_|.
1532  views::StyledLabel::RangeStyleInfo default_style;
1533  default_style.color =
1534      theme->GetSystemColor(ui::NativeTheme::kColorId_LabelDisabledColor);
1535
1536  legal_document_view_->SetDefaultStyle(default_style);
1537}
1538
1539base::string16 AutofillDialogViews::GetWindowTitle() const {
1540  base::string16 title = delegate_->DialogTitle();
1541  // Hack alert: we don't want the dialog to jiggle when a title is added or
1542  // removed. Setting a non-empty string here keeps the dialog's title bar the
1543  // same size.
1544  return title.empty() ? base::ASCIIToUTF16(" ") : title;
1545}
1546
1547void AutofillDialogViews::WindowClosing() {
1548  focus_manager_->RemoveFocusChangeListener(this);
1549}
1550
1551void AutofillDialogViews::DeleteDelegate() {
1552  window_ = NULL;
1553  // |this| belongs to the controller (|delegate_|).
1554  delegate_->ViewClosed();
1555}
1556
1557int AutofillDialogViews::GetDialogButtons() const {
1558  return delegate_->GetDialogButtons();
1559}
1560
1561int AutofillDialogViews::GetDefaultDialogButton() const {
1562  if (GetDialogButtons() & ui::DIALOG_BUTTON_OK)
1563    return ui::DIALOG_BUTTON_OK;
1564
1565  return ui::DIALOG_BUTTON_NONE;
1566}
1567
1568base::string16 AutofillDialogViews::GetDialogButtonLabel(
1569    ui::DialogButton button) const {
1570  return button == ui::DIALOG_BUTTON_OK ?
1571      delegate_->ConfirmButtonText() : delegate_->CancelButtonText();
1572}
1573
1574bool AutofillDialogViews::ShouldDefaultButtonBeBlue() const {
1575  return true;
1576}
1577
1578bool AutofillDialogViews::IsDialogButtonEnabled(ui::DialogButton button) const {
1579  return delegate_->IsDialogButtonEnabled(button);
1580}
1581
1582views::View* AutofillDialogViews::GetInitiallyFocusedView() {
1583  if (!window_ || !focus_manager_)
1584    return NULL;
1585
1586  if (sign_in_web_view_->visible())
1587    return sign_in_web_view_;
1588
1589  if (loading_shield_->visible())
1590    return views::DialogDelegateView::GetInitiallyFocusedView();
1591
1592  DCHECK(scrollable_area_->visible());
1593
1594  views::FocusManager* manager = focus_manager_;
1595  for (views::View* next = scrollable_area_;
1596       next;
1597       next = manager->GetNextFocusableView(next, window_, false, true)) {
1598    views::View* input_view = GetAncestralInputView(next);
1599    if (!input_view)
1600      continue;
1601
1602    // If there are no invalid inputs, return the first input found. Otherwise,
1603    // return the first invalid input found.
1604    if (validity_map_.empty() ||
1605        validity_map_.find(input_view) != validity_map_.end()) {
1606      return next;
1607    }
1608  }
1609
1610  return views::DialogDelegateView::GetInitiallyFocusedView();
1611}
1612
1613views::View* AutofillDialogViews::CreateExtraView() {
1614  return button_strip_extra_view_;
1615}
1616
1617views::View* AutofillDialogViews::CreateTitlebarExtraView() {
1618  return account_chooser_;
1619}
1620
1621views::View* AutofillDialogViews::CreateFootnoteView() {
1622  footnote_view_ = new LayoutPropagationView();
1623  footnote_view_->SetLayoutManager(
1624      new views::BoxLayout(views::BoxLayout::kVertical,
1625                           kDialogEdgePadding,
1626                           kDialogEdgePadding,
1627                           0));
1628  footnote_view_->SetBorder(
1629      views::Border::CreateSolidSidedBorder(1, 0, 0, 0, kSubtleBorderColor));
1630  footnote_view_->set_background(
1631      views::Background::CreateSolidBackground(kShadingColor));
1632
1633  legal_document_view_ = new views::StyledLabel(base::string16(), this);
1634  OnNativeThemeChanged(GetNativeTheme());
1635
1636  footnote_view_->AddChildView(legal_document_view_);
1637  footnote_view_->SetVisible(false);
1638
1639  return footnote_view_;
1640}
1641
1642views::View* AutofillDialogViews::CreateOverlayView() {
1643  return overlay_view_;
1644}
1645
1646bool AutofillDialogViews::Cancel() {
1647  return delegate_->OnCancel();
1648}
1649
1650bool AutofillDialogViews::Accept() {
1651  if (ValidateForm())
1652    return delegate_->OnAccept();
1653
1654  // |ValidateForm()| failed; there should be invalid views in |validity_map_|.
1655  DCHECK(!validity_map_.empty());
1656  FocusInitialView();
1657
1658  return false;
1659}
1660
1661void AutofillDialogViews::ContentsChanged(views::Textfield* sender,
1662                                          const base::string16& new_contents) {
1663  InputEditedOrActivated(TypeForTextfield(sender),
1664                         sender->GetBoundsInScreen(),
1665                         true);
1666
1667  const ExpandingTextfield* expanding = static_cast<ExpandingTextfield*>(
1668      sender->GetAncestorWithClassName(ExpandingTextfield::kViewClassName));
1669  if (expanding && expanding->needs_layout())
1670    ContentsPreferredSizeChanged();
1671}
1672
1673bool AutofillDialogViews::HandleKeyEvent(views::Textfield* sender,
1674                                         const ui::KeyEvent& key_event) {
1675  ui::KeyEvent copy(key_event);
1676  content::NativeWebKeyboardEvent event(&copy);
1677  return delegate_->HandleKeyPressEventInInput(event);
1678}
1679
1680bool AutofillDialogViews::HandleMouseEvent(views::Textfield* sender,
1681                                           const ui::MouseEvent& mouse_event) {
1682  if (mouse_event.IsLeftMouseButton() && sender->HasFocus()) {
1683    InputEditedOrActivated(TypeForTextfield(sender),
1684                           sender->GetBoundsInScreen(),
1685                           false);
1686    // Show an error bubble if a user clicks on an input that's already focused
1687    // (and invalid).
1688    ShowErrorBubbleForViewIfNecessary(sender);
1689  }
1690
1691  return false;
1692}
1693
1694void AutofillDialogViews::OnWillChangeFocus(
1695    views::View* focused_before,
1696    views::View* focused_now) {
1697  delegate_->FocusMoved();
1698  HideErrorBubble();
1699}
1700
1701void AutofillDialogViews::OnDidChangeFocus(
1702    views::View* focused_before,
1703    views::View* focused_now) {
1704  // If user leaves an edit-field, revalidate the group it belongs to.
1705  if (focused_before) {
1706    DetailsGroup* group = GroupForView(focused_before);
1707    if (group && group->container->visible())
1708      ValidateGroup(*group, VALIDATE_EDIT);
1709  }
1710
1711  // Show an error bubble when the user focuses the input.
1712  if (focused_now) {
1713    focused_now->ScrollRectToVisible(focused_now->GetLocalBounds());
1714    ShowErrorBubbleForViewIfNecessary(focused_now);
1715  }
1716}
1717
1718void AutofillDialogViews::OnPerformAction(views::Combobox* combobox) {
1719  DialogSection section = GroupForView(combobox)->section;
1720  InputEditedOrActivated(TypeForCombobox(combobox), gfx::Rect(), true);
1721  // NOTE: |combobox| may have been deleted.
1722  ValidateGroup(*GroupForSection(section), VALIDATE_EDIT);
1723  SetEditabilityForSection(section);
1724}
1725
1726void AutofillDialogViews::StyledLabelLinkClicked(const gfx::Range& range,
1727                                                 int event_flags) {
1728  delegate_->LegalDocumentLinkClicked(range);
1729}
1730
1731void AutofillDialogViews::OnMenuButtonClicked(views::View* source,
1732                                              const gfx::Point& point) {
1733  DCHECK_EQ(kSuggestedButtonClassName, source->GetClassName());
1734
1735  DetailsGroup* group = NULL;
1736  for (DetailGroupMap::iterator iter = detail_groups_.begin();
1737       iter != detail_groups_.end(); ++iter) {
1738    if (source == iter->second.suggested_button) {
1739      group = &iter->second;
1740      break;
1741    }
1742  }
1743  DCHECK(group);
1744
1745  if (!group->suggested_button->visible())
1746    return;
1747
1748  menu_runner_.reset(new views::MenuRunner(
1749                         delegate_->MenuModelForSection(group->section)));
1750
1751  group->container->SetActive(true);
1752  views::Button::ButtonState state = group->suggested_button->state();
1753  group->suggested_button->SetState(views::Button::STATE_PRESSED);
1754
1755  gfx::Rect screen_bounds = source->GetBoundsInScreen();
1756  screen_bounds.Inset(source->GetInsets());
1757  if (menu_runner_->RunMenuAt(source->GetWidget(),
1758                              NULL,
1759                              screen_bounds,
1760                              views::MenuItemView::TOPRIGHT,
1761                              ui::MENU_SOURCE_NONE,
1762                              0) == views::MenuRunner::MENU_DELETED) {
1763    return;
1764  }
1765
1766  group->container->SetActive(false);
1767  group->suggested_button->SetState(state);
1768}
1769
1770gfx::Size AutofillDialogViews::CalculatePreferredSize(bool get_minimum_size) {
1771  gfx::Insets insets = GetInsets();
1772  gfx::Size scroll_size = scrollable_area_->contents()->GetPreferredSize();
1773  // The width is always set by the scroll area.
1774  const int width = scroll_size.width();
1775
1776  if (sign_in_web_view_->visible()) {
1777    const gfx::Size size = static_cast<views::View*>(sign_in_web_view_)->
1778        GetPreferredSize();
1779    return gfx::Size(width + insets.width(), size.height() + insets.height());
1780  }
1781
1782  if (overlay_view_->visible()) {
1783    const int height = overlay_view_->GetHeightForContentsForWidth(width);
1784    if (height != 0)
1785      return gfx::Size(width + insets.width(), height + insets.height());
1786  }
1787
1788  if (loading_shield_->visible()) {
1789    return gfx::Size(width + insets.width(),
1790                     loading_shield_height_ + insets.height());
1791  }
1792
1793  int height = 0;
1794  const int notification_height = notification_area_->GetHeightForWidth(width);
1795  if (notification_height > notification_area_->GetInsets().height())
1796    height += notification_height + views::kRelatedControlVerticalSpacing;
1797
1798  if (scrollable_area_->visible())
1799    height += get_minimum_size ? kMinimumContentsHeight : scroll_size.height();
1800
1801  return gfx::Size(width + insets.width(), height + insets.height());
1802}
1803
1804gfx::Size AutofillDialogViews::GetMinimumSignInViewSize() const {
1805  return gfx::Size(GetDialogClientView()->size().width() - GetInsets().width(),
1806                   kMinimumContentsHeight);
1807}
1808
1809gfx::Size AutofillDialogViews::GetMaximumSignInViewSize() const {
1810  web_modal::WebContentsModalDialogHost* dialog_host =
1811      WebContentsModalDialogManager::FromWebContents(
1812          delegate_->GetWebContents())->delegate()->
1813              GetWebContentsModalDialogHost();
1814
1815  // Inset the maximum dialog height to get the maximum content height.
1816  int height = dialog_host->GetMaximumDialogSize().height();
1817  const int non_client_height = GetWidget()->non_client_view()->height();
1818  const int client_height = GetWidget()->client_view()->height();
1819  // TODO(msw): Resolve the 12 pixel discrepancy; is that the bubble border?
1820  height -= non_client_height - client_height - 12;
1821  height = std::max(height, kMinimumContentsHeight);
1822
1823  // The dialog's width never changes.
1824  const int width = GetDialogClientView()->size().width() - GetInsets().width();
1825  return gfx::Size(width, height);
1826}
1827
1828DialogSection AutofillDialogViews::GetCreditCardSection() const {
1829  if (delegate_->SectionIsActive(SECTION_CC))
1830    return SECTION_CC;
1831
1832  DCHECK(delegate_->SectionIsActive(SECTION_CC_BILLING));
1833  return SECTION_CC_BILLING;
1834}
1835
1836void AutofillDialogViews::InitChildViews() {
1837  button_strip_extra_view_ = new LayoutPropagationView();
1838  button_strip_extra_view_->SetLayoutManager(
1839      new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 0));
1840
1841  save_in_chrome_checkbox_container_ = new views::View();
1842  save_in_chrome_checkbox_container_->SetLayoutManager(
1843      new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 7));
1844  button_strip_extra_view_->AddChildView(save_in_chrome_checkbox_container_);
1845
1846  save_in_chrome_checkbox_ =
1847      new views::Checkbox(delegate_->SaveLocallyText());
1848  save_in_chrome_checkbox_->SetChecked(delegate_->ShouldSaveInChrome());
1849  save_in_chrome_checkbox_container_->AddChildView(save_in_chrome_checkbox_);
1850
1851  save_in_chrome_checkbox_container_->AddChildView(
1852      new TooltipIcon(delegate_->SaveLocallyTooltip()));
1853
1854  button_strip_image_ = new views::ImageView();
1855  button_strip_extra_view_->AddChildView(button_strip_image_);
1856
1857  account_chooser_ = new AccountChooser(delegate_);
1858  notification_area_ = new NotificationArea(delegate_);
1859  notification_area_->set_arrow_centering_anchor(account_chooser_->AsWeakPtr());
1860  AddChildView(notification_area_);
1861
1862  scrollable_area_ = new views::ScrollView();
1863  scrollable_area_->set_hide_horizontal_scrollbar(true);
1864  scrollable_area_->SetContents(CreateDetailsContainer());
1865  AddChildView(scrollable_area_);
1866
1867  loading_shield_ = new LoadingAnimationView(delegate_->SpinnerText());
1868  AddChildView(loading_shield_);
1869
1870  sign_in_web_view_ = new views::WebView(delegate_->profile());
1871  AddChildView(sign_in_web_view_);
1872
1873  overlay_view_ = new OverlayView(delegate_);
1874  overlay_view_->SetVisible(false);
1875}
1876
1877views::View* AutofillDialogViews::CreateDetailsContainer() {
1878  details_container_ = new DetailsContainerView(
1879      base::Bind(&AutofillDialogViews::DetailsContainerBoundsChanged,
1880                 base::Unretained(this)));
1881
1882  // A box layout is used because it respects widget visibility.
1883  details_container_->SetLayoutManager(
1884      new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0));
1885  for (DetailGroupMap::iterator iter = detail_groups_.begin();
1886       iter != detail_groups_.end(); ++iter) {
1887    CreateDetailsSection(iter->second.section);
1888    details_container_->AddChildView(iter->second.container);
1889  }
1890
1891  return details_container_;
1892}
1893
1894void AutofillDialogViews::CreateDetailsSection(DialogSection section) {
1895  // Inputs container (manual inputs + combobox).
1896  views::View* inputs_container = CreateInputsContainer(section);
1897
1898  DetailsGroup* group = GroupForSection(section);
1899  // Container (holds label + inputs).
1900  group->container = new SectionContainer(delegate_->LabelForSection(section),
1901                                          inputs_container,
1902                                          group->suggested_button);
1903  DCHECK(group->suggested_button->parent());
1904  UpdateDetailsGroupState(*group);
1905}
1906
1907views::View* AutofillDialogViews::CreateInputsContainer(DialogSection section) {
1908  // The |info_view| holds |manual_inputs| and |suggested_info|, allowing the
1909  // dialog to toggle which is shown.
1910  views::View* info_view = new views::View();
1911  info_view->SetLayoutManager(
1912      new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0));
1913
1914  DetailsGroup* group = GroupForSection(section);
1915  group->manual_input = new views::View();
1916  InitInputsView(section);
1917  info_view->AddChildView(group->manual_input);
1918
1919  group->suggested_info = new SuggestionView(this);
1920  info_view->AddChildView(group->suggested_info);
1921
1922  // TODO(estade): It might be slightly more OO if this button were created
1923  // and listened to by the section container.
1924  group->suggested_button = new SuggestedButton(this);
1925
1926  return info_view;
1927}
1928
1929// TODO(estade): we should be using Chrome-style constrained window padding
1930// values.
1931void AutofillDialogViews::InitInputsView(DialogSection section) {
1932  DetailsGroup* group = GroupForSection(section);
1933  EraseInvalidViewsInGroup(group);
1934
1935  TextfieldMap* textfields = &group->textfields;
1936  textfields->clear();
1937
1938  ComboboxMap* comboboxes = &group->comboboxes;
1939  comboboxes->clear();
1940
1941  views::View* view = group->manual_input;
1942  view->RemoveAllChildViews(true);
1943
1944  views::GridLayout* layout = new views::GridLayout(view);
1945  view->SetLayoutManager(layout);
1946
1947  int column_set_id = 0;
1948  const DetailInputs& inputs = delegate_->RequestedFieldsForSection(section);
1949  for (DetailInputs::const_iterator it = inputs.begin();
1950       it != inputs.end(); ++it) {
1951    const DetailInput& input = *it;
1952
1953    ui::ComboboxModel* input_model =
1954        delegate_->ComboboxModelForAutofillType(input.type);
1955    scoped_ptr<views::View> view_to_add;
1956    if (input_model) {
1957      views::Combobox* combobox = new views::Combobox(input_model);
1958      combobox->set_listener(this);
1959      comboboxes->insert(std::make_pair(input.type, combobox));
1960      SelectComboboxValueOrSetToDefault(combobox, input.initial_value);
1961      view_to_add.reset(combobox);
1962    } else {
1963      ExpandingTextfield* field = new ExpandingTextfield(input.initial_value,
1964                                                         input.placeholder_text,
1965                                                         input.IsMultiline(),
1966                                                         this);
1967      textfields->insert(std::make_pair(input.type, field));
1968      view_to_add.reset(field);
1969    }
1970
1971    if (input.length == DetailInput::NONE) {
1972      other_owned_views_.push_back(view_to_add.release());
1973      continue;
1974    }
1975
1976    if (input.length == DetailInput::LONG)
1977      ++column_set_id;
1978
1979    views::ColumnSet* column_set = layout->GetColumnSet(column_set_id);
1980    if (!column_set) {
1981      // Create a new column set and row.
1982      column_set = layout->AddColumnSet(column_set_id);
1983      if (it != inputs.begin())
1984        layout->AddPaddingRow(0, kManualInputRowPadding);
1985      layout->StartRow(0, column_set_id);
1986    } else {
1987      // Add a new column to existing row.
1988      column_set->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing);
1989      // Must explicitly skip the padding column since we've already started
1990      // adding views.
1991      layout->SkipColumns(1);
1992    }
1993
1994    float expand = input.expand_weight;
1995    column_set->AddColumn(views::GridLayout::FILL,
1996                          views::GridLayout::FILL,
1997                          expand ? expand : 1.0,
1998                          views::GridLayout::USE_PREF,
1999                          0,
2000                          0);
2001
2002    // This is the same as AddView(view_to_add), except that 1 is used for the
2003    // view's preferred width. Thus the width of the column completely depends
2004    // on |expand|.
2005    layout->AddView(view_to_add.release(), 1, 1,
2006                    views::GridLayout::FILL, views::GridLayout::FILL,
2007                    1, 0);
2008
2009    if (input.length == DetailInput::LONG ||
2010        input.length == DetailInput::SHORT_EOL) {
2011      ++column_set_id;
2012    }
2013  }
2014
2015  SetIconsForSection(section);
2016}
2017
2018void AutofillDialogViews::ShowDialogInMode(DialogMode dialog_mode) {
2019  loading_shield_->SetVisible(dialog_mode == LOADING);
2020  sign_in_web_view_->SetVisible(dialog_mode == SIGN_IN);
2021  notification_area_->SetVisible(dialog_mode == DETAIL_INPUT);
2022  scrollable_area_->SetVisible(dialog_mode == DETAIL_INPUT);
2023  FocusInitialView();
2024}
2025
2026void AutofillDialogViews::UpdateSectionImpl(
2027    DialogSection section,
2028    bool clobber_inputs) {
2029  DetailsGroup* group = GroupForSection(section);
2030
2031  if (clobber_inputs) {
2032    ServerFieldType type = UNKNOWN_TYPE;
2033    views::View* focused = GetFocusManager()->GetFocusedView();
2034    if (focused && group->container->Contains(focused)) {
2035      // Remember which view was focused before the inputs are clobbered.
2036      if (focused->GetClassName() == ExpandingTextfield::kViewClassName)
2037        type = TypeForTextfield(focused);
2038      else if (focused->GetClassName() == views::Combobox::kViewClassName)
2039        type = TypeForCombobox(static_cast<views::Combobox*>(focused));
2040    }
2041
2042    InitInputsView(section);
2043
2044    if (type != UNKNOWN_TYPE) {
2045      // Restore the focus to the input with the previous type (e.g. country).
2046      views::View* to_focus = TextfieldForType(type);
2047      if (!to_focus) to_focus = ComboboxForType(type);
2048      if (to_focus)
2049        to_focus->RequestFocus();
2050    }
2051  } else {
2052    const DetailInputs& updated_inputs =
2053        delegate_->RequestedFieldsForSection(section);
2054
2055    for (DetailInputs::const_iterator iter = updated_inputs.begin();
2056         iter != updated_inputs.end(); ++iter) {
2057      const DetailInput& input = *iter;
2058
2059      TextfieldMap::iterator text_mapping = group->textfields.find(input.type);
2060      if (text_mapping != group->textfields.end()) {
2061        ExpandingTextfield* textfield = text_mapping->second;
2062        if (textfield->GetText().empty())
2063          textfield->SetText(input.initial_value);
2064      }
2065
2066      ComboboxMap::iterator combo_mapping = group->comboboxes.find(input.type);
2067      if (combo_mapping != group->comboboxes.end()) {
2068        views::Combobox* combobox = combo_mapping->second;
2069        if (combobox->selected_index() == combobox->model()->GetDefaultIndex())
2070          SelectComboboxValueOrSetToDefault(combobox, input.initial_value);
2071      }
2072    }
2073
2074    SetIconsForSection(section);
2075  }
2076
2077  SetEditabilityForSection(section);
2078  UpdateDetailsGroupState(*group);
2079}
2080
2081void AutofillDialogViews::UpdateDetailsGroupState(const DetailsGroup& group) {
2082  const SuggestionState& suggestion_state =
2083      delegate_->SuggestionStateForSection(group.section);
2084  group.suggested_info->SetState(suggestion_state);
2085  group.manual_input->SetVisible(!suggestion_state.visible);
2086
2087  UpdateButtonStripExtraView();
2088
2089  const bool has_menu = !!delegate_->MenuModelForSection(group.section);
2090
2091  if (group.suggested_button)
2092    group.suggested_button->SetVisible(has_menu);
2093
2094  if (group.container) {
2095    group.container->SetForwardMouseEvents(
2096        has_menu && suggestion_state.visible);
2097    group.container->SetVisible(delegate_->SectionIsActive(group.section));
2098    if (group.container->visible())
2099      ValidateGroup(group, VALIDATE_EDIT);
2100  }
2101
2102  ContentsPreferredSizeChanged();
2103}
2104
2105void AutofillDialogViews::FocusInitialView() {
2106  views::View* to_focus = GetInitiallyFocusedView();
2107  if (to_focus && !to_focus->HasFocus())
2108    to_focus->RequestFocus();
2109}
2110
2111template<class T>
2112void AutofillDialogViews::SetValidityForInput(
2113    T* input,
2114    const base::string16& message) {
2115  bool invalid = !message.empty();
2116  input->SetInvalid(invalid);
2117
2118  if (invalid) {
2119    validity_map_[input] = message;
2120  } else {
2121    validity_map_.erase(input);
2122
2123    if (error_bubble_ &&
2124        error_bubble_->anchor()->GetAncestorWithClassName(
2125            input->GetClassName()) == input) {
2126      validity_map_.erase(input);
2127      HideErrorBubble();
2128    }
2129  }
2130}
2131
2132void AutofillDialogViews::ShowErrorBubbleForViewIfNecessary(views::View* view) {
2133  if (!view->GetWidget())
2134    return;
2135
2136  if (!delegate_->ShouldShowErrorBubble()) {
2137    DCHECK(!error_bubble_);
2138    return;
2139  }
2140
2141  if (view->GetClassName() == DecoratedTextfield::kViewClassName &&
2142      !static_cast<DecoratedTextfield*>(view)->invalid()) {
2143    return;
2144  }
2145
2146  views::View* input_view = GetAncestralInputView(view);
2147  std::map<views::View*, base::string16>::iterator error_message =
2148      validity_map_.find(input_view);
2149  if (error_message != validity_map_.end()) {
2150    input_view->ScrollRectToVisible(input_view->GetLocalBounds());
2151
2152    if (!error_bubble_ || error_bubble_->anchor() != view) {
2153      HideErrorBubble();
2154      error_bubble_ = new InfoBubble(view, error_message->second);
2155      error_bubble_->set_align_to_anchor_edge(true);
2156      error_bubble_->set_preferred_width(
2157          (kSectionContainerWidth - views::kRelatedControlVerticalSpacing) / 2);
2158      bool show_above = view->GetClassName() == views::Combobox::kViewClassName;
2159      error_bubble_->set_show_above_anchor(show_above);
2160      error_bubble_->Show();
2161      observer_.Add(error_bubble_->GetWidget());
2162    }
2163  }
2164}
2165
2166void AutofillDialogViews::HideErrorBubble() {
2167  if (error_bubble_)
2168    error_bubble_->Hide();
2169}
2170
2171void AutofillDialogViews::MarkInputsInvalid(
2172    DialogSection section,
2173    const ValidityMessages& messages,
2174    bool overwrite_unsure) {
2175  DetailsGroup* group = GroupForSection(section);
2176  DCHECK(group->container->visible());
2177
2178  if (group->manual_input->visible()) {
2179    for (TextfieldMap::const_iterator iter = group->textfields.begin();
2180         iter != group->textfields.end(); ++iter) {
2181      const ValidityMessage& message =
2182          messages.GetMessageOrDefault(iter->first);
2183      if (overwrite_unsure || message.sure)
2184        SetValidityForInput(iter->second, message.text);
2185    }
2186    for (ComboboxMap::const_iterator iter = group->comboboxes.begin();
2187         iter != group->comboboxes.end(); ++iter) {
2188      const ValidityMessage& message =
2189          messages.GetMessageOrDefault(iter->first);
2190      if (overwrite_unsure || message.sure)
2191        SetValidityForInput(iter->second, message.text);
2192    }
2193  } else {
2194    EraseInvalidViewsInGroup(group);
2195
2196    if (section == GetCreditCardSection()) {
2197      // Special case CVC as it's not part of |group->manual_input|.
2198      const ValidityMessage& message =
2199          messages.GetMessageOrDefault(CREDIT_CARD_VERIFICATION_CODE);
2200      if (overwrite_unsure || message.sure) {
2201        SetValidityForInput(group->suggested_info->textfield(), message.text);
2202      }
2203    }
2204  }
2205}
2206
2207bool AutofillDialogViews::ValidateGroup(const DetailsGroup& group,
2208                                        ValidationType validation_type) {
2209  DCHECK(group.container->visible());
2210
2211  FieldValueMap detail_outputs;
2212
2213  if (group.manual_input->visible()) {
2214    for (TextfieldMap::const_iterator iter = group.textfields.begin();
2215         iter != group.textfields.end(); ++iter) {
2216      if (!iter->second->editable())
2217        continue;
2218
2219      detail_outputs[iter->first] = iter->second->GetText();
2220    }
2221    for (ComboboxMap::const_iterator iter = group.comboboxes.begin();
2222         iter != group.comboboxes.end(); ++iter) {
2223      if (!iter->second->enabled())
2224        continue;
2225
2226      views::Combobox* combobox = iter->second;
2227      base::string16 item =
2228          combobox->model()->GetItemAt(combobox->selected_index());
2229      detail_outputs[iter->first] = item;
2230    }
2231  } else if (group.section == GetCreditCardSection()) {
2232    ExpandingTextfield* cvc = group.suggested_info->textfield();
2233    if (cvc->visible())
2234      detail_outputs[CREDIT_CARD_VERIFICATION_CODE] = cvc->GetText();
2235  }
2236
2237  ValidityMessages validity = delegate_->InputsAreValid(group.section,
2238                                                        detail_outputs);
2239  MarkInputsInvalid(group.section, validity, validation_type == VALIDATE_FINAL);
2240
2241  // If there are any validation errors, sure or unsure, the group is invalid.
2242  return !validity.HasErrors();
2243}
2244
2245bool AutofillDialogViews::ValidateForm() {
2246  bool all_valid = true;
2247  validity_map_.clear();
2248
2249  for (DetailGroupMap::iterator iter = detail_groups_.begin();
2250       iter != detail_groups_.end(); ++iter) {
2251    const DetailsGroup& group = iter->second;
2252    if (!group.container->visible())
2253      continue;
2254
2255    if (!ValidateGroup(group, VALIDATE_FINAL))
2256      all_valid = false;
2257  }
2258
2259  return all_valid;
2260}
2261
2262void AutofillDialogViews::InputEditedOrActivated(ServerFieldType type,
2263                                                 const gfx::Rect& bounds,
2264                                                 bool was_edit) {
2265  DCHECK_NE(UNKNOWN_TYPE, type);
2266
2267  ExpandingTextfield* textfield = TextfieldForType(type);
2268  views::Combobox* combobox = ComboboxForType(type);
2269
2270  // Both views may be NULL if the event comes from an inactive section, which
2271  // may occur when using an IME.
2272  if (!combobox && !textfield)
2273    return;
2274
2275  DCHECK_NE(!!combobox, !!textfield);
2276  DetailsGroup* group = textfield ? GroupForView(textfield) :
2277                                    GroupForView(combobox);
2278  base::string16 text = textfield ?
2279      textfield->GetText() :
2280      combobox->model()->GetItemAt(combobox->selected_index());
2281  DCHECK(group);
2282
2283  delegate_->UserEditedOrActivatedInput(group->section,
2284                                        type,
2285                                        GetWidget()->GetNativeView(),
2286                                        bounds,
2287                                        text,
2288                                        was_edit);
2289
2290  // If the field is a textfield and is invalid, check if the text is now valid.
2291  // Many fields (i.e. CC#) are invalid for most of the duration of editing,
2292  // so flagging them as invalid prematurely is not helpful. However,
2293  // correcting a minor mistake (i.e. a wrong CC digit) should immediately
2294  // result in validation - positive user feedback.
2295  if (textfield && textfield->invalid() && was_edit) {
2296    SetValidityForInput(
2297        textfield,
2298        delegate_->InputValidityMessage(
2299            group->section, type, textfield->GetText()));
2300
2301    // If the field transitioned from invalid to valid, re-validate the group,
2302    // since inter-field checks become meaningful with valid fields.
2303    if (!textfield->invalid())
2304      ValidateGroup(*group, VALIDATE_EDIT);
2305  }
2306
2307  if (delegate_->FieldControlsIcons(type))
2308    SetIconsForSection(group->section);
2309
2310  SetEditabilityForSection(group->section);
2311}
2312
2313void AutofillDialogViews::UpdateButtonStripExtraView() {
2314  save_in_chrome_checkbox_container_->SetVisible(
2315      delegate_->ShouldOfferToSaveInChrome());
2316
2317  gfx::Image image = delegate_->ButtonStripImage();
2318  button_strip_image_->SetVisible(!image.IsEmpty());
2319  button_strip_image_->SetImage(image.AsImageSkia());
2320}
2321
2322void AutofillDialogViews::ContentsPreferredSizeChanged() {
2323  if (updates_scope_ != 0) {
2324    needs_update_ = true;
2325    return;
2326  }
2327
2328  preferred_size_ = gfx::Size();
2329
2330  if (GetWidget() && delegate_ && delegate_->GetWebContents()) {
2331    UpdateWebContentsModalDialogPosition(
2332        GetWidget(),
2333        WebContentsModalDialogManager::FromWebContents(
2334            delegate_->GetWebContents())->delegate()->
2335                GetWebContentsModalDialogHost());
2336    SetBoundsRect(bounds());
2337  }
2338}
2339
2340AutofillDialogViews::DetailsGroup* AutofillDialogViews::GroupForSection(
2341    DialogSection section) {
2342  return &detail_groups_.find(section)->second;
2343}
2344
2345AutofillDialogViews::DetailsGroup* AutofillDialogViews::GroupForView(
2346    views::View* view) {
2347  DCHECK(view);
2348
2349  views::View* input_view = GetAncestralInputView(view);
2350  if (!input_view)
2351    return NULL;
2352
2353  for (DetailGroupMap::iterator iter = detail_groups_.begin();
2354       iter != detail_groups_.end(); ++iter) {
2355    DetailsGroup* group = &iter->second;
2356    if (input_view->parent() == group->manual_input)
2357      return group;
2358
2359    // Textfields need to check a second case, since they can be suggested
2360    // inputs instead of directly editable inputs. Those are accessed via
2361    // |suggested_info|.
2362    if (input_view == group->suggested_info->textfield()) {
2363      return group;
2364    }
2365  }
2366
2367  return NULL;
2368}
2369
2370void AutofillDialogViews::EraseInvalidViewsInGroup(const DetailsGroup* group) {
2371  std::map<views::View*, base::string16>::iterator it = validity_map_.begin();
2372  while (it != validity_map_.end()) {
2373    if (GroupForView(it->first) == group)
2374      validity_map_.erase(it++);
2375    else
2376      ++it;
2377  }
2378}
2379
2380ExpandingTextfield* AutofillDialogViews::TextfieldForType(
2381    ServerFieldType type) {
2382  if (type == CREDIT_CARD_VERIFICATION_CODE) {
2383    DetailsGroup* group = GroupForSection(GetCreditCardSection());
2384    if (!group->manual_input->visible())
2385      return group->suggested_info->textfield();
2386  }
2387
2388  for (DetailGroupMap::iterator iter = detail_groups_.begin();
2389       iter != detail_groups_.end(); ++iter) {
2390    const DetailsGroup& group = iter->second;
2391    if (!delegate_->SectionIsActive(group.section))
2392      continue;
2393
2394    TextfieldMap::const_iterator text_mapping = group.textfields.find(type);
2395    if (text_mapping != group.textfields.end())
2396      return text_mapping->second;
2397  }
2398
2399  return NULL;
2400}
2401
2402ServerFieldType AutofillDialogViews::TypeForTextfield(
2403    const views::View* textfield) {
2404  const views::View* expanding =
2405      textfield->GetAncestorWithClassName(ExpandingTextfield::kViewClassName);
2406
2407  DetailsGroup* cc_group = GroupForSection(GetCreditCardSection());
2408  if (expanding == cc_group->suggested_info->textfield())
2409    return CREDIT_CARD_VERIFICATION_CODE;
2410
2411  for (DetailGroupMap::const_iterator it = detail_groups_.begin();
2412       it != detail_groups_.end(); ++it) {
2413    if (!delegate_->SectionIsActive(it->second.section))
2414      continue;
2415
2416    for (TextfieldMap::const_iterator text_it = it->second.textfields.begin();
2417         text_it != it->second.textfields.end(); ++text_it) {
2418      if (expanding == text_it->second)
2419        return text_it->first;
2420    }
2421  }
2422
2423  return UNKNOWN_TYPE;
2424}
2425
2426views::Combobox* AutofillDialogViews::ComboboxForType(
2427    ServerFieldType type) {
2428  for (DetailGroupMap::iterator iter = detail_groups_.begin();
2429       iter != detail_groups_.end(); ++iter) {
2430    const DetailsGroup& group = iter->second;
2431    if (!delegate_->SectionIsActive(group.section))
2432      continue;
2433
2434    ComboboxMap::const_iterator combo_mapping = group.comboboxes.find(type);
2435    if (combo_mapping != group.comboboxes.end())
2436      return combo_mapping->second;
2437  }
2438
2439  return NULL;
2440}
2441
2442ServerFieldType AutofillDialogViews::TypeForCombobox(
2443    const views::Combobox* combobox) const {
2444  for (DetailGroupMap::const_iterator it = detail_groups_.begin();
2445       it != detail_groups_.end(); ++it) {
2446    const DetailsGroup& group = it->second;
2447    if (!delegate_->SectionIsActive(group.section))
2448      continue;
2449
2450    for (ComboboxMap::const_iterator combo_it = group.comboboxes.begin();
2451         combo_it != group.comboboxes.end(); ++combo_it) {
2452      if (combo_it->second == combobox)
2453        return combo_it->first;
2454    }
2455  }
2456
2457  return UNKNOWN_TYPE;
2458}
2459
2460void AutofillDialogViews::DetailsContainerBoundsChanged() {
2461  if (error_bubble_)
2462    error_bubble_->UpdatePosition();
2463}
2464
2465void AutofillDialogViews::SetIconsForSection(DialogSection section) {
2466  FieldValueMap user_input;
2467  GetUserInput(section, &user_input);
2468  FieldIconMap field_icons = delegate_->IconsForFields(user_input);
2469  TextfieldMap* textfields = &GroupForSection(section)->textfields;
2470  for (TextfieldMap::const_iterator textfield_it = textfields->begin();
2471       textfield_it != textfields->end();
2472       ++textfield_it) {
2473    ServerFieldType field_type = textfield_it->first;
2474    FieldIconMap::const_iterator field_icon_it = field_icons.find(field_type);
2475    ExpandingTextfield* textfield = textfield_it->second;
2476    if (field_icon_it != field_icons.end())
2477      textfield->SetIcon(field_icon_it->second);
2478    else
2479      textfield->SetTooltipIcon(delegate_->TooltipForField(field_type));
2480  }
2481}
2482
2483void AutofillDialogViews::SetEditabilityForSection(DialogSection section) {
2484  const DetailInputs& inputs =
2485      delegate_->RequestedFieldsForSection(section);
2486  DetailsGroup* group = GroupForSection(section);
2487
2488  for (DetailInputs::const_iterator iter = inputs.begin();
2489       iter != inputs.end(); ++iter) {
2490    const DetailInput& input = *iter;
2491    bool editable = delegate_->InputIsEditable(input, section);
2492
2493    TextfieldMap::iterator text_mapping = group->textfields.find(input.type);
2494    if (text_mapping != group->textfields.end()) {
2495      ExpandingTextfield* textfield= text_mapping->second;
2496      textfield->SetEditable(editable);
2497      continue;
2498    }
2499
2500    ComboboxMap::iterator combo_mapping = group->comboboxes.find(input.type);
2501    if (combo_mapping != group->comboboxes.end()) {
2502      views::Combobox* combobox = combo_mapping->second;
2503      combobox->SetEnabled(editable);
2504    }
2505  }
2506}
2507
2508void AutofillDialogViews::NonClientMousePressed() {
2509  delegate_->FocusMoved();
2510}
2511
2512AutofillDialogViews::DetailsGroup::DetailsGroup(DialogSection section)
2513    : section(section),
2514      container(NULL),
2515      manual_input(NULL),
2516      suggested_info(NULL),
2517      suggested_button(NULL) {}
2518
2519AutofillDialogViews::DetailsGroup::~DetailsGroup() {}
2520
2521}  // namespace autofill
2522