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