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