Home | History | Annotate | Download | only in autofill
      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/autofill/autofill_popup_controller_impl.h"
      6 
      7 #include <algorithm>
      8 #include <utility>
      9 
     10 #include "base/logging.h"
     11 #include "base/strings/utf_string_conversions.h"
     12 #include "chrome/browser/ui/autofill/autofill_popup_view.h"
     13 #include "chrome/browser/ui/autofill/popup_constants.h"
     14 #include "components/autofill/core/browser/autofill_popup_delegate.h"
     15 #include "components/autofill/core/browser/popup_item_ids.h"
     16 #include "content/public/browser/native_web_keyboard_event.h"
     17 #include "grit/components_scaled_resources.h"
     18 #include "ui/base/resource/resource_bundle.h"
     19 #include "ui/events/event.h"
     20 #include "ui/gfx/rect_conversions.h"
     21 #include "ui/gfx/screen.h"
     22 #include "ui/gfx/text_elider.h"
     23 #include "ui/gfx/text_utils.h"
     24 #include "ui/gfx/vector2d.h"
     25 
     26 using base::WeakPtr;
     27 
     28 namespace autofill {
     29 namespace {
     30 
     31 // Used to indicate that no line is currently selected by the user.
     32 const int kNoSelection = -1;
     33 
     34 // The vertical height of each row in pixels.
     35 const size_t kRowHeight = 24;
     36 
     37 // The vertical height of a separator in pixels.
     38 const size_t kSeparatorHeight = 1;
     39 
     40 #if !defined(OS_ANDROID)
     41 // Size difference between name and subtext in pixels.
     42 const int kLabelFontSizeDelta = -2;
     43 
     44 const size_t kNamePadding = AutofillPopupView::kNamePadding;
     45 const size_t kIconPadding = AutofillPopupView::kIconPadding;
     46 const size_t kEndPadding = AutofillPopupView::kEndPadding;
     47 #endif
     48 
     49 struct DataResource {
     50   const char* name;
     51   int id;
     52 };
     53 
     54 const DataResource kDataResources[] = {
     55   { "americanExpressCC", IDR_AUTOFILL_CC_AMEX },
     56   { "dinersCC", IDR_AUTOFILL_CC_DINERS },
     57   { "discoverCC", IDR_AUTOFILL_CC_DISCOVER },
     58   { "genericCC", IDR_AUTOFILL_CC_GENERIC },
     59   { "jcbCC", IDR_AUTOFILL_CC_JCB },
     60   { "masterCardCC", IDR_AUTOFILL_CC_MASTERCARD },
     61   { "visaCC", IDR_AUTOFILL_CC_VISA },
     62 #if defined(OS_MACOSX) && !defined(OS_IOS)
     63   { "macContactsIcon", IDR_AUTOFILL_MAC_CONTACTS_ICON },
     64 #endif  // defined(OS_MACOSX) && !defined(OS_IOS)
     65 };
     66 
     67 }  // namespace
     68 
     69 // static
     70 WeakPtr<AutofillPopupControllerImpl> AutofillPopupControllerImpl::GetOrCreate(
     71     WeakPtr<AutofillPopupControllerImpl> previous,
     72     WeakPtr<AutofillPopupDelegate> delegate,
     73     content::WebContents* web_contents,
     74     gfx::NativeView container_view,
     75     const gfx::RectF& element_bounds,
     76     base::i18n::TextDirection text_direction) {
     77   DCHECK(!previous.get() || previous->delegate_.get() == delegate.get());
     78 
     79   if (previous.get() && previous->web_contents() == web_contents &&
     80       previous->container_view() == container_view &&
     81       previous->element_bounds() == element_bounds) {
     82     previous->ClearState();
     83     return previous;
     84   }
     85 
     86   if (previous.get())
     87     previous->Hide();
     88 
     89   AutofillPopupControllerImpl* controller =
     90       new AutofillPopupControllerImpl(
     91           delegate, web_contents, container_view, element_bounds,
     92           text_direction);
     93   return controller->GetWeakPtr();
     94 }
     95 
     96 AutofillPopupControllerImpl::AutofillPopupControllerImpl(
     97     base::WeakPtr<AutofillPopupDelegate> delegate,
     98     content::WebContents* web_contents,
     99     gfx::NativeView container_view,
    100     const gfx::RectF& element_bounds,
    101     base::i18n::TextDirection text_direction)
    102     : controller_common_(new PopupControllerCommon(element_bounds,
    103                                                    container_view,
    104                                                    web_contents)),
    105       view_(NULL),
    106       delegate_(delegate),
    107       text_direction_(text_direction),
    108       weak_ptr_factory_(this) {
    109   ClearState();
    110   controller_common_->SetKeyPressCallback(
    111       base::Bind(&AutofillPopupControllerImpl::HandleKeyPressEvent,
    112                  base::Unretained(this)));
    113 #if !defined(OS_ANDROID)
    114   subtext_font_list_ = name_font_list_.DeriveWithSizeDelta(kLabelFontSizeDelta);
    115 #if defined(OS_MACOSX)
    116   // There is no italic version of the system font.
    117   warning_font_list_ = name_font_list_;
    118 #else
    119   warning_font_list_ = name_font_list_.DeriveWithStyle(gfx::Font::ITALIC);
    120 #endif
    121 #endif
    122 }
    123 
    124 AutofillPopupControllerImpl::~AutofillPopupControllerImpl() {}
    125 
    126 void AutofillPopupControllerImpl::Show(
    127     const std::vector<base::string16>& names,
    128     const std::vector<base::string16>& subtexts,
    129     const std::vector<base::string16>& icons,
    130     const std::vector<int>& identifiers) {
    131   SetValues(names, subtexts, icons, identifiers);
    132 
    133 #if !defined(OS_ANDROID)
    134   // Android displays the long text with ellipsis using the view attributes.
    135 
    136   UpdatePopupBounds();
    137   int popup_width = popup_bounds().width();
    138 
    139   // Elide the name and subtext strings so that the popup fits in the available
    140   // space.
    141   for (size_t i = 0; i < names_.size(); ++i) {
    142     int name_width = gfx::GetStringWidth(names_[i], GetNameFontListForRow(i));
    143     int subtext_width = gfx::GetStringWidth(subtexts_[i], subtext_font_list());
    144     int total_text_length = name_width + subtext_width;
    145 
    146     // The line can have no strings if it represents a UI element, such as
    147     // a separator line.
    148     if (total_text_length == 0)
    149       continue;
    150 
    151     int available_width = popup_width - RowWidthWithoutText(i);
    152 
    153     // Each field receives space in proportion to its length.
    154     int name_size = available_width * name_width / total_text_length;
    155     names_[i] = gfx::ElideText(names_[i], GetNameFontListForRow(i),
    156                                name_size, gfx::ELIDE_TAIL);
    157 
    158     int subtext_size = available_width * subtext_width / total_text_length;
    159     subtexts_[i] = gfx::ElideText(subtexts_[i], subtext_font_list(),
    160                                   subtext_size, gfx::ELIDE_TAIL);
    161   }
    162 #endif
    163 
    164   if (!view_) {
    165     view_ = AutofillPopupView::Create(this);
    166 
    167     // It is possible to fail to create the popup, in this case
    168     // treat the popup as hiding right away.
    169     if (!view_) {
    170       Hide();
    171       return;
    172     }
    173 
    174     ShowView();
    175   } else {
    176     UpdateBoundsAndRedrawPopup();
    177   }
    178 
    179   controller_common_->RegisterKeyPressCallback();
    180   delegate_->OnPopupShown();
    181 }
    182 
    183 void AutofillPopupControllerImpl::UpdateDataListValues(
    184     const std::vector<base::string16>& values,
    185     const std::vector<base::string16>& labels) {
    186   // Remove all the old data list values, which should always be at the top of
    187   // the list if they are present.
    188   while (!identifiers_.empty() &&
    189          identifiers_[0] == POPUP_ITEM_ID_DATALIST_ENTRY) {
    190     names_.erase(names_.begin());
    191     subtexts_.erase(subtexts_.begin());
    192     icons_.erase(icons_.begin());
    193     identifiers_.erase(identifiers_.begin());
    194   }
    195 
    196   // If there are no new data list values, exit (clearing the separator if there
    197   // is one).
    198   if (values.empty()) {
    199     if (!identifiers_.empty() && identifiers_[0] == POPUP_ITEM_ID_SEPARATOR) {
    200       names_.erase(names_.begin());
    201       subtexts_.erase(subtexts_.begin());
    202       icons_.erase(icons_.begin());
    203       identifiers_.erase(identifiers_.begin());
    204     }
    205 
    206      // The popup contents have changed, so either update the bounds or hide it.
    207     if (HasSuggestions())
    208       UpdateBoundsAndRedrawPopup();
    209     else
    210       Hide();
    211 
    212     return;
    213   }
    214 
    215   // Add a separator if there are any other values.
    216   if (!identifiers_.empty() && identifiers_[0] != POPUP_ITEM_ID_SEPARATOR) {
    217     names_.insert(names_.begin(), base::string16());
    218     subtexts_.insert(subtexts_.begin(), base::string16());
    219     icons_.insert(icons_.begin(), base::string16());
    220     identifiers_.insert(identifiers_.begin(), POPUP_ITEM_ID_SEPARATOR);
    221   }
    222 
    223 
    224   names_.insert(names_.begin(), values.begin(), values.end());
    225   subtexts_.insert(subtexts_.begin(), labels.begin(), labels.end());
    226 
    227   // Add the values that are the same for all data list elements.
    228   icons_.insert(icons_.begin(), values.size(), base::string16());
    229   identifiers_.insert(
    230       identifiers_.begin(), values.size(), POPUP_ITEM_ID_DATALIST_ENTRY);
    231 
    232   UpdateBoundsAndRedrawPopup();
    233 }
    234 
    235 void AutofillPopupControllerImpl::Hide() {
    236   controller_common_->RemoveKeyPressCallback();
    237   if (delegate_)
    238     delegate_->OnPopupHidden();
    239 
    240   if (view_)
    241     view_->Hide();
    242 
    243   delete this;
    244 }
    245 
    246 void AutofillPopupControllerImpl::ViewDestroyed() {
    247   // The view has already been destroyed so clear the reference to it.
    248   view_ = NULL;
    249 
    250   Hide();
    251 }
    252 
    253 bool AutofillPopupControllerImpl::HandleKeyPressEvent(
    254     const content::NativeWebKeyboardEvent& event) {
    255   switch (event.windowsKeyCode) {
    256     case ui::VKEY_UP:
    257       SelectPreviousLine();
    258       return true;
    259     case ui::VKEY_DOWN:
    260       SelectNextLine();
    261       return true;
    262     case ui::VKEY_PRIOR:  // Page up.
    263       SetSelectedLine(0);
    264       return true;
    265     case ui::VKEY_NEXT:  // Page down.
    266       SetSelectedLine(names().size() - 1);
    267       return true;
    268     case ui::VKEY_ESCAPE:
    269       Hide();
    270       return true;
    271     case ui::VKEY_DELETE:
    272       return (event.modifiers & content::NativeWebKeyboardEvent::ShiftKey) &&
    273              RemoveSelectedLine();
    274     case ui::VKEY_TAB:
    275       // A tab press should cause the selected line to be accepted, but still
    276       // return false so the tab key press propagates and changes the cursor
    277       // location.
    278       AcceptSelectedLine();
    279       return false;
    280     case ui::VKEY_RETURN:
    281       return AcceptSelectedLine();
    282     default:
    283       return false;
    284   }
    285 }
    286 
    287 void AutofillPopupControllerImpl::UpdateBoundsAndRedrawPopup() {
    288 #if !defined(OS_ANDROID)
    289   // TODO(csharp): Since UpdatePopupBounds can change the position of the popup,
    290   // the popup could end up jumping from above the element to below it.
    291   // It is unclear if it is better to keep the popup where it was, or if it
    292   // should try and move to its desired position.
    293   UpdatePopupBounds();
    294 #endif
    295 
    296   view_->UpdateBoundsAndRedrawPopup();
    297 }
    298 
    299 void AutofillPopupControllerImpl::SetSelectionAtPoint(const gfx::Point& point) {
    300   SetSelectedLine(LineFromY(point.y()));
    301 }
    302 
    303 bool AutofillPopupControllerImpl::AcceptSelectedLine() {
    304   if (selected_line_ == kNoSelection)
    305     return false;
    306 
    307   DCHECK_GE(selected_line_, 0);
    308   DCHECK_LT(selected_line_, static_cast<int>(names_.size()));
    309 
    310   if (!CanAccept(identifiers_[selected_line_]))
    311     return false;
    312 
    313   AcceptSuggestion(selected_line_);
    314   return true;
    315 }
    316 
    317 void AutofillPopupControllerImpl::SelectionCleared() {
    318   SetSelectedLine(kNoSelection);
    319 }
    320 
    321 void AutofillPopupControllerImpl::AcceptSuggestion(size_t index) {
    322   delegate_->DidAcceptSuggestion(full_names_[index], identifiers_[index]);
    323 }
    324 
    325 int AutofillPopupControllerImpl::GetIconResourceID(
    326     const base::string16& resource_name) const {
    327   for (size_t i = 0; i < arraysize(kDataResources); ++i) {
    328     if (resource_name == base::ASCIIToUTF16(kDataResources[i].name))
    329       return kDataResources[i].id;
    330   }
    331 
    332   return -1;
    333 }
    334 
    335 bool AutofillPopupControllerImpl::CanDelete(size_t index) const {
    336   // TODO(isherman): Native AddressBook suggestions on Mac and Android should
    337   // not be considered to be deleteable.
    338   int id = identifiers_[index];
    339   return id > 0 || id == POPUP_ITEM_ID_AUTOCOMPLETE_ENTRY ||
    340          id == POPUP_ITEM_ID_PASSWORD_ENTRY;
    341 }
    342 
    343 bool AutofillPopupControllerImpl::IsWarning(size_t index) const {
    344   return identifiers_[index] == POPUP_ITEM_ID_WARNING_MESSAGE;
    345 }
    346 
    347 gfx::Rect AutofillPopupControllerImpl::GetRowBounds(size_t index) {
    348   int top = kPopupBorderThickness;
    349   for (size_t i = 0; i < index; ++i) {
    350     top += GetRowHeightFromId(identifiers()[i]);
    351   }
    352 
    353   return gfx::Rect(
    354       kPopupBorderThickness,
    355       top,
    356       popup_bounds_.width() - 2 * kPopupBorderThickness,
    357       GetRowHeightFromId(identifiers()[index]));
    358 }
    359 
    360 void AutofillPopupControllerImpl::SetPopupBounds(const gfx::Rect& bounds) {
    361   popup_bounds_ = bounds;
    362   UpdateBoundsAndRedrawPopup();
    363 }
    364 
    365 const gfx::Rect& AutofillPopupControllerImpl::popup_bounds() const {
    366   return popup_bounds_;
    367 }
    368 
    369 content::WebContents* AutofillPopupControllerImpl::web_contents() {
    370   return controller_common_->web_contents();
    371 }
    372 
    373 gfx::NativeView AutofillPopupControllerImpl::container_view() {
    374   return controller_common_->container_view();
    375 }
    376 
    377 const gfx::RectF& AutofillPopupControllerImpl::element_bounds() const {
    378   return controller_common_->element_bounds();
    379 }
    380 
    381 bool AutofillPopupControllerImpl::IsRTL() const {
    382   return text_direction_ == base::i18n::RIGHT_TO_LEFT;
    383 }
    384 
    385 const std::vector<base::string16>& AutofillPopupControllerImpl::names() const {
    386   return names_;
    387 }
    388 
    389 const std::vector<base::string16>& AutofillPopupControllerImpl::subtexts()
    390     const {
    391   return subtexts_;
    392 }
    393 
    394 const std::vector<base::string16>& AutofillPopupControllerImpl::icons() const {
    395   return icons_;
    396 }
    397 
    398 const std::vector<int>& AutofillPopupControllerImpl::identifiers() const {
    399   return identifiers_;
    400 }
    401 
    402 #if !defined(OS_ANDROID)
    403 const gfx::FontList& AutofillPopupControllerImpl::GetNameFontListForRow(
    404     size_t index) const {
    405   if (identifiers_[index] == POPUP_ITEM_ID_WARNING_MESSAGE)
    406     return warning_font_list_;
    407 
    408   return name_font_list_;
    409 }
    410 
    411 const gfx::FontList& AutofillPopupControllerImpl::subtext_font_list() const {
    412   return subtext_font_list_;
    413 }
    414 #endif
    415 
    416 int AutofillPopupControllerImpl::selected_line() const {
    417   return selected_line_;
    418 }
    419 
    420 void AutofillPopupControllerImpl::SetSelectedLine(int selected_line) {
    421   if (selected_line_ == selected_line)
    422     return;
    423 
    424   if (selected_line_ != kNoSelection &&
    425       static_cast<size_t>(selected_line_) < identifiers_.size())
    426     InvalidateRow(selected_line_);
    427 
    428   if (selected_line != kNoSelection)
    429     InvalidateRow(selected_line);
    430 
    431   selected_line_ = selected_line;
    432 
    433   if (selected_line_ != kNoSelection) {
    434     delegate_->DidSelectSuggestion(names_[selected_line_],
    435                                    identifiers_[selected_line_]);
    436   } else {
    437     delegate_->ClearPreviewedForm();
    438   }
    439 }
    440 
    441 void AutofillPopupControllerImpl::SelectNextLine() {
    442   int new_selected_line = selected_line_ + 1;
    443 
    444   // Skip over any lines that can't be selected.
    445   while (static_cast<size_t>(new_selected_line) < names_.size() &&
    446          !CanAccept(identifiers()[new_selected_line])) {
    447     ++new_selected_line;
    448   }
    449 
    450   if (new_selected_line >= static_cast<int>(names_.size()))
    451     new_selected_line = 0;
    452 
    453   SetSelectedLine(new_selected_line);
    454 }
    455 
    456 void AutofillPopupControllerImpl::SelectPreviousLine() {
    457   int new_selected_line = selected_line_ - 1;
    458 
    459   // Skip over any lines that can't be selected.
    460   while (new_selected_line > kNoSelection &&
    461          !CanAccept(identifiers()[new_selected_line])) {
    462     --new_selected_line;
    463   }
    464 
    465   if (new_selected_line <= kNoSelection)
    466     new_selected_line = names_.size() - 1;
    467 
    468   SetSelectedLine(new_selected_line);
    469 }
    470 
    471 bool AutofillPopupControllerImpl::RemoveSelectedLine() {
    472   if (selected_line_ == kNoSelection)
    473     return false;
    474 
    475   DCHECK_GE(selected_line_, 0);
    476   DCHECK_LT(selected_line_, static_cast<int>(names_.size()));
    477 
    478   if (!CanDelete(selected_line_))
    479     return false;
    480 
    481   delegate_->RemoveSuggestion(full_names_[selected_line_],
    482                               identifiers_[selected_line_]);
    483 
    484   // Remove the deleted element.
    485   names_.erase(names_.begin() + selected_line_);
    486   full_names_.erase(full_names_.begin() + selected_line_);
    487   subtexts_.erase(subtexts_.begin() + selected_line_);
    488   icons_.erase(icons_.begin() + selected_line_);
    489   identifiers_.erase(identifiers_.begin() + selected_line_);
    490 
    491   SetSelectedLine(kNoSelection);
    492 
    493   if (HasSuggestions()) {
    494     delegate_->ClearPreviewedForm();
    495     UpdateBoundsAndRedrawPopup();
    496   } else {
    497     Hide();
    498   }
    499 
    500   return true;
    501 }
    502 
    503 int AutofillPopupControllerImpl::LineFromY(int y) {
    504   int current_height = kPopupBorderThickness;
    505 
    506   for (size_t i = 0; i < identifiers().size(); ++i) {
    507     current_height += GetRowHeightFromId(identifiers()[i]);
    508 
    509     if (y <= current_height)
    510       return i;
    511   }
    512 
    513   // The y value goes beyond the popup so stop the selection at the last line.
    514   return identifiers().size() - 1;
    515 }
    516 
    517 int AutofillPopupControllerImpl::GetRowHeightFromId(int identifier) const {
    518   if (identifier == POPUP_ITEM_ID_SEPARATOR)
    519     return kSeparatorHeight;
    520 
    521   return kRowHeight;
    522 }
    523 
    524 bool AutofillPopupControllerImpl::CanAccept(int id) {
    525   return id != POPUP_ITEM_ID_SEPARATOR && id != POPUP_ITEM_ID_WARNING_MESSAGE;
    526 }
    527 
    528 bool AutofillPopupControllerImpl::HasSuggestions() {
    529   return identifiers_.size() != 0 &&
    530          (identifiers_[0] > 0 ||
    531           identifiers_[0] == POPUP_ITEM_ID_AUTOCOMPLETE_ENTRY ||
    532           identifiers_[0] == POPUP_ITEM_ID_PASSWORD_ENTRY ||
    533           identifiers_[0] == POPUP_ITEM_ID_DATALIST_ENTRY ||
    534           identifiers_[0] == POPUP_ITEM_ID_MAC_ACCESS_CONTACTS);
    535 }
    536 
    537 void AutofillPopupControllerImpl::SetValues(
    538     const std::vector<base::string16>& names,
    539     const std::vector<base::string16>& subtexts,
    540     const std::vector<base::string16>& icons,
    541     const std::vector<int>& identifiers) {
    542   names_ = names;
    543   full_names_ = names;
    544   subtexts_ = subtexts;
    545   icons_ = icons;
    546   identifiers_ = identifiers;
    547 }
    548 
    549 void AutofillPopupControllerImpl::ShowView() {
    550   view_->Show();
    551 }
    552 
    553 void AutofillPopupControllerImpl::InvalidateRow(size_t row) {
    554   DCHECK(0 <= row);
    555   DCHECK(row < identifiers_.size());
    556   view_->InvalidateRow(row);
    557 }
    558 
    559 #if !defined(OS_ANDROID)
    560 int AutofillPopupControllerImpl::GetDesiredPopupWidth() const {
    561   int popup_width = controller_common_->RoundedElementBounds().width();
    562   DCHECK_EQ(names().size(), subtexts().size());
    563   for (size_t i = 0; i < names().size(); ++i) {
    564     int row_size =
    565         gfx::GetStringWidth(names()[i], name_font_list_) +
    566         gfx::GetStringWidth(subtexts()[i], subtext_font_list_) +
    567         RowWidthWithoutText(i);
    568 
    569     popup_width = std::max(popup_width, row_size);
    570   }
    571 
    572   return popup_width;
    573 }
    574 
    575 int AutofillPopupControllerImpl::GetDesiredPopupHeight() const {
    576   int popup_height = 2 * kPopupBorderThickness;
    577 
    578   for (size_t i = 0; i < identifiers().size(); ++i) {
    579     popup_height += GetRowHeightFromId(identifiers()[i]);
    580   }
    581 
    582   return popup_height;
    583 }
    584 
    585 int AutofillPopupControllerImpl::RowWidthWithoutText(int row) const {
    586   int row_size = kEndPadding;
    587 
    588   if (!subtexts_[row].empty())
    589     row_size += kNamePadding;
    590 
    591   // Add the Autofill icon size, if required.
    592   if (!icons_[row].empty()) {
    593     int icon_width = ui::ResourceBundle::GetSharedInstance().GetImageNamed(
    594         GetIconResourceID(icons_[row])).Width();
    595     row_size += icon_width + kIconPadding;
    596   }
    597 
    598   // Add the padding at the end.
    599   row_size += kEndPadding;
    600 
    601   // Add room for the popup border.
    602   row_size += 2 * kPopupBorderThickness;
    603 
    604   return row_size;
    605 }
    606 
    607 void AutofillPopupControllerImpl::UpdatePopupBounds() {
    608   int popup_width = GetDesiredPopupWidth();
    609   int popup_height = GetDesiredPopupHeight();
    610 
    611   popup_bounds_ = controller_common_->GetPopupBounds(popup_width,
    612                                                      popup_height);
    613 }
    614 #endif  // !defined(OS_ANDROID)
    615 
    616 WeakPtr<AutofillPopupControllerImpl> AutofillPopupControllerImpl::GetWeakPtr() {
    617   return weak_ptr_factory_.GetWeakPtr();
    618 }
    619 
    620 void AutofillPopupControllerImpl::ClearState() {
    621   // Don't clear view_, because otherwise the popup will have to get regenerated
    622   // and this will cause flickering.
    623 
    624   popup_bounds_ = gfx::Rect();
    625 
    626   names_.clear();
    627   subtexts_.clear();
    628   icons_.clear();
    629   identifiers_.clear();
    630   full_names_.clear();
    631 
    632   selected_line_ = kNoSelection;
    633 }
    634 
    635 }  // namespace autofill
    636