// Copyright (c) 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #import "chrome/browser/ui/cocoa/autofill/autofill_section_container.h" #include <stddef.h> #include <algorithm> #include "base/mac/foundation_util.h" #include "base/mac/sdk_forward_declarations.h" #include "base/strings/sys_string_conversions.h" #include "base/strings/utf_string_conversions.h" #include "chrome/browser/ui/autofill/autofill_dialog_view_delegate.h" #include "chrome/browser/ui/chrome_style.h" #import "chrome/browser/ui/cocoa/autofill/autofill_pop_up_button.h" #import "chrome/browser/ui/cocoa/autofill/autofill_section_view.h" #import "chrome/browser/ui/cocoa/autofill/autofill_suggestion_container.h" #import "chrome/browser/ui/cocoa/autofill/autofill_textfield.h" #import "chrome/browser/ui/cocoa/autofill/autofill_tooltip_controller.h" #import "chrome/browser/ui/cocoa/autofill/layout_view.h" #include "chrome/browser/ui/cocoa/autofill/simple_grid_layout.h" #import "chrome/browser/ui/cocoa/image_button_cell.h" #import "chrome/browser/ui/cocoa/menu_button.h" #include "components/autofill/core/browser/autofill_type.h" #include "content/public/browser/native_web_keyboard_event.h" #include "grit/components_scaled_resources.h" #include "grit/theme_resources.h" #import "ui/base/cocoa/menu_controller.h" #include "ui/base/l10n/l10n_util_mac.h" #include "ui/base/models/combobox_model.h" #include "ui/base/resource/resource_bundle.h" namespace { // Constants used for layouting controls. These variables are copied from // "ui/views/layout/layout_constants.h". // Horizontal spacing between controls that are logically related. const int kRelatedControlHorizontalSpacing = 8; // Vertical spacing between controls that are logically related. const int kRelatedControlVerticalSpacing = 8; // TODO(estade): pull out these constants, and figure out better values // for them. Note: These are duplicated from Views code. // Fixed width for the details section. const int kDetailsWidth = 440; // Top/bottom inset for contents of a detail section. const size_t kDetailSectionInset = 10; // Vertical padding around the section header. const CGFloat kVerticalHeaderPadding = 6; // If the Autofill data comes from a credit card, make sure to overwrite the // CC comboboxes (even if they already have something in them). If the // Autofill data comes from an AutofillProfile, leave the comboboxes alone. // TODO(groby): This kind of logic should _really_ live on the delegate. bool ShouldOverwriteComboboxes(autofill::DialogSection section, autofill::ServerFieldType type) { if (autofill::AutofillType(type).group() != autofill::CREDIT_CARD) { return false; } return section == autofill::SECTION_CC; } } // namespace @interface AutofillSectionContainer () // An input field has been edited or activated - inform the delegate and // possibly reset the validity of the input (if it's a textfield). - (void)fieldEditedOrActivated:(NSControl<AutofillInputField>*)field edited:(BOOL)edited; // Convenience method to retrieve a field type via the control's tag. - (autofill::ServerFieldType)fieldTypeForControl:(NSControl*)control; // Find the DetailInput* associated with a field type. - (const autofill::DetailInput*)detailInputForType: (autofill::ServerFieldType)type; // Takes an NSArray of controls and builds a FieldValueMap from them. // Translates between Cocoa code and delegate, essentially. // All controls must inherit from NSControl and conform to AutofillInputView. - (void)fillDetailOutputs:(autofill::FieldValueMap*)outputs fromControls:(NSArray*)controls; // Updates input fields based on delegate status. If |shouldClobber| is YES, // will clobber existing data and reset fields to the initial values. - (void)updateAndClobber:(BOOL)shouldClobber; // Return YES if this is a section that contains CC info. (And, more // importantly, a potential CVV field) - (BOOL)isCreditCardSection; // Create properly styled label for section. Autoreleased. - (NSTextField*)makeDetailSectionLabel:(NSString*)labelText; // Create a button offering input suggestions. - (MenuButton*)makeSuggestionButton; // Create a view with all inputs requested by |delegate_| and resets |input_|. - (void)makeInputControls; // Refresh all field icons based on |delegate_| status. - (void)updateFieldIcons; @end @implementation AutofillSectionContainer @synthesize section = section_; @synthesize validationDelegate = validationDelegate_; - (id)initWithDelegate:(autofill::AutofillDialogViewDelegate*)delegate forSection:(autofill::DialogSection)section { if (self = [super init]) { section_ = section; delegate_ = delegate; } return self; } - (void)getInputs:(autofill::FieldValueMap*)output { [self fillDetailOutputs:output fromControls:[inputs_ subviews]]; } // Note: This corresponds to Views' "UpdateDetailsGroupState". - (void)modelChanged { ui::MenuModel* suggestionModel = delegate_->MenuModelForSection(section_); menuController_.reset([[MenuController alloc] initWithModel:suggestionModel useWithPopUpButtonCell:YES]); NSMenu* menu = [menuController_ menu]; const BOOL hasSuggestions = [menu numberOfItems] > 0; [suggestButton_ setHidden:!hasSuggestions]; [suggestButton_ setAttachedMenu:menu]; [self updateSuggestionState]; if (![[self view] isHidden]) [self validateFor:autofill::VALIDATE_EDIT]; // Always request re-layout on state change. [self requestRelayout]; } - (void)requestRelayout { id delegate = [[view_ window] windowController]; if ([delegate respondsToSelector:@selector(requestRelayout)]) [delegate performSelector:@selector(requestRelayout)]; } - (void)loadView { [self makeInputControls]; base::string16 labelText = delegate_->LabelForSection(section_); label_.reset( [[self makeDetailSectionLabel:base::SysUTF16ToNSString(labelText)] retain]); suggestButton_.reset([[self makeSuggestionButton] retain]); suggestContainer_.reset([[AutofillSuggestionContainer alloc] init]); view_.reset([[AutofillSectionView alloc] initWithFrame:NSZeroRect]); [self setView:view_]; [view_ setSubviews: @[label_, inputs_, [suggestContainer_ view], suggestButton_]]; if (tooltipController_) { [view_ addSubview:[tooltipController_ view] positioned:NSWindowAbove relativeTo:inputs_]; } if ([self isCreditCardSection]) { // Credit card sections *MUST* have a CREDIT_CARD_VERIFICATION_CODE input. DCHECK([self detailInputForType:autofill::CREDIT_CARD_VERIFICATION_CODE]); [[suggestContainer_ inputField] setTag: autofill::CREDIT_CARD_VERIFICATION_CODE]; [[suggestContainer_ inputField] setInputDelegate:self]; } [self modelChanged]; } - (NSSize)preferredSize { if ([view_ isHidden]) return NSZeroSize; NSSize labelSize = [label_ frame].size; // Assumes sizeToFit was called. CGFloat controlHeight = [inputs_ preferredHeightForWidth:kDetailsWidth]; if (showSuggestions_) controlHeight = [suggestContainer_ preferredSize].height; return NSMakeSize(kDetailsWidth + 2 * chrome_style::kHorizontalPadding, labelSize.height + kVerticalHeaderPadding + controlHeight + 2 * kDetailSectionInset); } - (void)performLayout { if ([view_ isHidden]) return; NSSize buttonSize = [suggestButton_ frame].size; // Assume sizeToFit. NSSize labelSize = [label_ frame].size; // Assumes sizeToFit was called. CGFloat controlHeight = [inputs_ preferredHeightForWidth:kDetailsWidth]; if (showSuggestions_) controlHeight = [suggestContainer_ preferredSize].height; NSRect viewFrame = NSZeroRect; viewFrame.size = [self preferredSize]; NSRect contentFrame = NSInsetRect(viewFrame, chrome_style::kHorizontalPadding, kDetailSectionInset); NSRect controlFrame, labelFrame, buttonFrame; // Label is top left, suggestion button is top right, controls are below that. NSDivideRect(contentFrame, &labelFrame, &controlFrame, kVerticalHeaderPadding + labelSize.height, NSMaxYEdge); NSDivideRect(labelFrame, &buttonFrame, &labelFrame, buttonSize.width, NSMaxXEdge); labelFrame = NSOffsetRect(labelFrame, 0, kVerticalHeaderPadding); labelFrame.size = labelSize; buttonFrame = NSOffsetRect(buttonFrame, 0, 5); buttonFrame.size = buttonSize; if (showSuggestions_) { [[suggestContainer_ view] setFrame:controlFrame]; [suggestContainer_ performLayout]; } else { [inputs_ setFrame:controlFrame]; } [label_ setFrame:labelFrame]; [suggestButton_ setFrame:buttonFrame]; [inputs_ setHidden:showSuggestions_]; [[suggestContainer_ view] setHidden:!showSuggestions_]; [view_ setFrameSize:viewFrame.size]; if (tooltipController_) { [[tooltipController_ view] setHidden:showSuggestions_]; NSRect tooltipIconFrame = [tooltipField_ decorationFrame]; tooltipIconFrame.origin = [[self view] convertPoint:tooltipIconFrame.origin fromView:[tooltipField_ superview]]; [[tooltipController_ view] setFrame:tooltipIconFrame]; } } - (KeyEventHandled)keyEvent:(NSEvent*)event forInput:(id)sender { content::NativeWebKeyboardEvent webEvent(event); // Only handle keyDown, to handle key repeats without duplicates. if (webEvent.type != content::NativeWebKeyboardEvent::RawKeyDown) return kKeyEventNotHandled; // Allow the delegate to intercept key messages. if (delegate_->HandleKeyPressEventInInput(webEvent)) return kKeyEventHandled; return kKeyEventNotHandled; } - (void)onMouseDown:(NSControl<AutofillInputField>*)field { [self fieldEditedOrActivated:field edited:NO]; [validationDelegate_ updateMessageForField:field]; } - (void)fieldBecameFirstResponder:(NSControl<AutofillInputField>*)field { [validationDelegate_ updateMessageForField:field]; } - (void)didChange:(id)sender { [self fieldEditedOrActivated:sender edited:YES]; } - (void)didEndEditing:(id)sender { delegate_->FocusMoved(); [validationDelegate_ hideErrorBubble]; [self validateFor:autofill::VALIDATE_EDIT]; } - (void)updateSuggestionState { const autofill::SuggestionState& suggestionState = delegate_->SuggestionStateForSection(section_); showSuggestions_ = suggestionState.visible; if (!suggestionState.extra_text.empty()) { NSString* extraText = base::SysUTF16ToNSString(suggestionState.extra_text); NSImage* extraIcon = suggestionState.extra_icon.AsNSImage(); [suggestContainer_ showInputField:extraText withIcon:extraIcon]; } // NOTE: It's important to set the input field, if there is one, _before_ // setting the suggestion text, since the suggestion container needs to // account for the input field's width when deciding which of the two string // representations to use. NSString* verticallyCompactText = base::SysUTF16ToNSString(suggestionState.vertically_compact_text); NSString* horizontallyCompactText = base::SysUTF16ToNSString(suggestionState.horizontally_compact_text); [suggestContainer_ setSuggestionWithVerticallyCompactText:verticallyCompactText horizontallyCompactText:horizontallyCompactText icon:suggestionState.icon.AsNSImage() maxWidth:kDetailsWidth]; [view_ setShouldHighlightOnHover:showSuggestions_]; if (showSuggestions_) [view_ setClickTarget:suggestButton_]; else [view_ setClickTarget:nil]; [view_ setHidden:!delegate_->SectionIsActive(section_)]; } - (void)update { [self updateAndClobber:YES]; [view_ updateHoverState]; } - (void)fillForType:(const autofill::ServerFieldType)type { // Make sure to overwrite the originating input if it is a text field. AutofillTextField* field = base::mac::ObjCCast<AutofillTextField>([inputs_ viewWithTag:type]); [field setFieldValue:@""]; if (ShouldOverwriteComboboxes(section_, type)) { for (NSControl* control in [inputs_ subviews]) { AutofillPopUpButton* popup = base::mac::ObjCCast<AutofillPopUpButton>(control); if (popup) { autofill::ServerFieldType fieldType = [self fieldTypeForControl:popup]; if (autofill::AutofillType(fieldType).group() == autofill::CREDIT_CARD) { ui::ComboboxModel* model = delegate_->ComboboxModelForAutofillType(fieldType); DCHECK(model); [popup selectItemAtIndex:model->GetDefaultIndex()]; } } } } [self updateAndClobber:NO]; } - (BOOL)validateFor:(autofill::ValidationType)validationType { NSArray* fields = nil; if (!showSuggestions_) { fields = [inputs_ subviews]; } else if ([self isCreditCardSection]) { if (![[suggestContainer_ inputField] isHidden]) fields = @[ [suggestContainer_ inputField] ]; } // Ensure only editable fields are validated. fields = [fields filteredArrayUsingPredicate: [NSPredicate predicateWithBlock: ^BOOL(NSControl<AutofillInputField>* field, NSDictionary* bindings) { return [field isEnabled]; }]]; autofill::FieldValueMap detailOutputs; [self fillDetailOutputs:&detailOutputs fromControls:fields]; autofill::ValidityMessages messages = delegate_->InputsAreValid( section_, detailOutputs); for (NSControl<AutofillInputField>* input in fields) { const autofill::ValidityMessage& message = messages.GetMessageOrDefault([self fieldTypeForControl:input]); if (validationType != autofill::VALIDATE_FINAL && !message.sure) continue; [input setValidityMessage:base::SysUTF16ToNSString(message.text)]; [validationDelegate_ updateMessageForField:input]; } return !messages.HasErrors(); } - (NSString*)suggestionText { return showSuggestions_ ? [[suggestContainer_ inputField] stringValue] : nil; } - (void)addInputsToArray:(NSMutableArray*)array { [array addObjectsFromArray:[inputs_ subviews]]; // Only credit card sections can have a suggestion input. if ([self isCreditCardSection]) [array addObject:[suggestContainer_ inputField]]; } #pragma mark Internal API for AutofillSectionContainer. - (void)fieldEditedOrActivated:(NSControl<AutofillInputField>*)field edited:(BOOL)edited { autofill::ServerFieldType type = [self fieldTypeForControl:field]; base::string16 fieldValue = base::SysNSStringToUTF16([field fieldValue]); // Get the frame rectangle for the designated field, in screen coordinates. NSRect textFrameInScreen = [field convertRect:[field bounds] toView:nil]; textFrameInScreen = [[field window] convertRectToScreen:textFrameInScreen]; // And adjust for gfx::Rect being flipped compared to OSX coordinates. NSScreen* screen = [[NSScreen screens] firstObject]; textFrameInScreen.origin.y = NSMaxY([screen frame]) - NSMaxY(textFrameInScreen); gfx::Rect textFrameRect(NSRectToCGRect(textFrameInScreen)); delegate_->UserEditedOrActivatedInput(section_, type, [self view], textFrameRect, fieldValue, edited); AutofillTextField* textfield = base::mac::ObjCCast<AutofillTextField>(field); if (!textfield) return; // If the field is marked as invalid, check if the text is now valid. Many // fields (i.e. CC#) are invalid for most of the duration of editing, so // flagging them as invalid prematurely is not helpful. However, correcting a // minor mistake (i.e. a wrong CC digit) should immediately result in // validation - positive user feedback. if ([textfield invalid] && edited) { base::string16 message = delegate_->InputValidityMessage(section_, type, fieldValue); [textfield setValidityMessage:base::SysUTF16ToNSString(message)]; // If the field transitioned from invalid to valid, re-validate the group, // since inter-field checks become meaningful with valid fields. if (![textfield invalid]) [self validateFor:autofill::VALIDATE_EDIT]; // The validity message has potentially changed - notify the error bubble. [validationDelegate_ updateMessageForField:textfield]; } // Update the icon if necessary. if (delegate_->FieldControlsIcons(type)) [self updateFieldIcons]; } - (autofill::ServerFieldType)fieldTypeForControl:(NSControl*)control { DCHECK([control tag]); return static_cast<autofill::ServerFieldType>([control tag]); } - (const autofill::DetailInput*)detailInputForType: (autofill::ServerFieldType)type { for (size_t i = 0; i < detailInputs_.size(); ++i) { if (detailInputs_[i]->type == type) return detailInputs_[i]; } // TODO(groby): Needs to be NOTREACHED. Can't, due to the fact that tests // blindly call setFieldValue:forType:, even for non-existing inputs. return NULL; } - (void)fillDetailOutputs:(autofill::FieldValueMap*)outputs fromControls:(NSArray*)controls { for (NSControl<AutofillInputField>* input in controls) { DCHECK([input isKindOfClass:[NSControl class]]); DCHECK([input conformsToProtocol:@protocol(AutofillInputField)]); outputs->insert(std::make_pair( [self fieldTypeForControl:input], base::SysNSStringToUTF16([input fieldValue]))); } } - (NSTextField*)makeDetailSectionLabel:(NSString*)labelText { base::scoped_nsobject<NSTextField> label([[NSTextField alloc] init]); [label setFont: [[NSFontManager sharedFontManager] convertFont:[label font] toHaveTrait:NSBoldFontMask]]; [label setStringValue:labelText]; [label setEditable:NO]; [label setBordered:NO]; [label setDrawsBackground:NO]; [label sizeToFit]; return label.autorelease(); } - (void)updateAndClobber:(BOOL)shouldClobber { if (shouldClobber) { // Remember which one of the inputs was first responder so focus can be // restored after the inputs are rebuilt. NSView* firstResponderView = base::mac::ObjCCast<NSView>([[inputs_ window] firstResponder]); autofill::ServerFieldType type = autofill::UNKNOWN_TYPE; for (NSControl* field in [inputs_ subviews]) { if ([firstResponderView isDescendantOf:field]) { type = [self fieldTypeForControl:field]; break; } } [self makeInputControls]; if (type != autofill::UNKNOWN_TYPE) { NSView* view = [inputs_ viewWithTag:type]; if (view) [[inputs_ window] makeFirstResponder:view]; } } else { const autofill::DetailInputs& updatedInputs = delegate_->RequestedFieldsForSection(section_); for (autofill::DetailInputs::const_iterator iter = updatedInputs.begin(); iter != updatedInputs.end(); ++iter) { NSControl<AutofillInputField>* field = [inputs_ viewWithTag:iter->type]; DCHECK(field); if ([field isDefault]) [field setFieldValue:base::SysUTF16ToNSString(iter->initial_value)]; } [self updateFieldIcons]; } [self modelChanged]; } - (BOOL)isCreditCardSection { return section_ == autofill::SECTION_CC; } - (MenuButton*)makeSuggestionButton { base::scoped_nsobject<MenuButton> button([[MenuButton alloc] init]); [button setOpenMenuOnClick:YES]; [button setBordered:NO]; [button setShowsBorderOnlyWhileMouseInside:YES]; ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); NSImage* image = rb.GetNativeImageNamed(IDR_AUTOFILL_DIALOG_MENU_BUTTON).ToNSImage(); [[button cell] setImage:image forButtonState:image_button_cell::kDefaultState]; image = rb.GetNativeImageNamed(IDR_AUTOFILL_DIALOG_MENU_BUTTON_H). ToNSImage(); [[button cell] setImage:image forButtonState:image_button_cell::kHoverState]; image = rb.GetNativeImageNamed(IDR_AUTOFILL_DIALOG_MENU_BUTTON_P). ToNSImage(); [[button cell] setImage:image forButtonState:image_button_cell::kPressedState]; image = rb.GetNativeImageNamed(IDR_AUTOFILL_DIALOG_MENU_BUTTON_D). ToNSImage(); [[button cell] setImage:image forButtonState:image_button_cell::kDisabledState]; // ImageButtonCell's cellSize is not working. (http://crbug.com/298501) [button setFrameSize:[image size]]; return button.autorelease(); } // TODO(estade): we should be using Chrome-style constrained window padding // values. - (void)makeInputControls { if (inputs_) { // When |inputs_| is replaced in response to a country change, there's a // didEndEditing dispatched that segfaults or DCHECKS() as it's operating on // stale input fields. Nil out the input delegate so this doesn't happen. for (NSControl<AutofillInputField>* input in [inputs_ subviews]) { [input setInputDelegate:nil]; } } detailInputs_.clear(); // Keep a list of weak pointers to DetailInputs. const autofill::DetailInputs& inputs = delegate_->RequestedFieldsForSection(section_); // Reverse the order of all the inputs. for (int i = inputs.size() - 1; i >= 0; --i) { detailInputs_.push_back(&(inputs[i])); } // Then right the reversal in each row. std::vector<const autofill::DetailInput*>::iterator it; for (it = detailInputs_.begin(); it < detailInputs_.end(); ++it) { std::vector<const autofill::DetailInput*>::iterator start = it; while (it != detailInputs_.end() && (*it)->length != autofill::DetailInput::LONG) { ++it; } std::reverse(start, it); } base::scoped_nsobject<LayoutView> view([[LayoutView alloc] init]); [view setLayoutManager: scoped_ptr<SimpleGridLayout>(new SimpleGridLayout(view))]; SimpleGridLayout* layout = [view layoutManager]; int column_set_id = 0; for (size_t i = 0; i < detailInputs_.size(); ++i) { const autofill::DetailInput& input = *detailInputs_[i]; if (input.length == autofill::DetailInput::LONG) ++column_set_id; int kColumnSetId = input.length == autofill::DetailInput::NONE ? -1 : column_set_id; ColumnSet* columnSet = layout->GetColumnSet(kColumnSetId); if (!columnSet) { // Create a new column set and row. columnSet = layout->AddColumnSet(kColumnSetId); if (i != 0 && kColumnSetId != -1) layout->AddPaddingRow(kRelatedControlVerticalSpacing); layout->StartRow(0, kColumnSetId); } else { // Add a new column to existing row. columnSet->AddPaddingColumn(kRelatedControlHorizontalSpacing); // Must explicitly skip the padding column since we've already started // adding views. layout->SkipColumns(1); } columnSet->AddColumn(input.expand_weight ? input.expand_weight : 1.0f); ui::ComboboxModel* inputModel = delegate_->ComboboxModelForAutofillType(input.type); base::scoped_nsprotocol<NSControl<AutofillInputField>*> control; if (inputModel) { base::scoped_nsobject<AutofillPopUpButton> popup( [[AutofillPopUpButton alloc] initWithFrame:NSZeroRect pullsDown:NO]); for (int i = 0; i < inputModel->GetItemCount(); ++i) { if (!inputModel->IsItemSeparatorAt(i)) { // Currently, the first item in |inputModel| is duplicated later in // the list. The second item is a separator. Because NSPopUpButton // de-duplicates, the menu's just left with a separator on the top of // the list (with nothing it's separating). For that reason, // separators are ignored on Mac for now. http://crbug.com/347653 [popup addItemWithTitle: base::SysUTF16ToNSString(inputModel->GetItemAt(i))]; } } [popup setDefaultValue:base::SysUTF16ToNSString( inputModel->GetItemAt(inputModel->GetDefaultIndex()))]; control.reset(popup.release()); } else { base::scoped_nsobject<AutofillTextField> field( [[AutofillTextField alloc] init]); [field setIsMultiline:input.IsMultiline()]; [[field cell] setLineBreakMode:NSLineBreakByClipping]; [[field cell] setScrollable:YES]; [[field cell] setPlaceholderString: l10n_util::FixUpWindowsStyleLabel(input.placeholder_text)]; NSString* tooltipText = base::SysUTF16ToNSString(delegate_->TooltipForField(input.type)); // VoiceOver onlys seems to pick up the help message on [field cell] // (rather than just field). BOOL success = [[field cell] accessibilitySetOverrideValue:tooltipText forAttribute:NSAccessibilityHelpAttribute]; DCHECK(success); if ([tooltipText length] > 0) { if (!tooltipController_) { tooltipController_.reset( [[AutofillTooltipController alloc] initWithArrowLocation:info_bubble::kTopRight]); } tooltipField_ = field.get(); NSImage* icon = ui::ResourceBundle::GetSharedInstance().GetNativeImageNamed( IDR_AUTOFILL_TOOLTIP_ICON).ToNSImage(); [tooltipController_ setImage:icon]; [tooltipController_ setMessage:tooltipText]; [[field cell] setDecorationSize:[icon size]]; } [field setDefaultValue:@""]; control.reset(field.release()); } [control setTag:input.type]; [control setFieldValue:base::SysUTF16ToNSString(input.initial_value)]; [control sizeToFit]; [control setFrame:NSIntegralRect([control frame])]; [control setInputDelegate:self]; // Hide away fields that cannot be edited. if (kColumnSetId == -1) { [control setFrame:NSZeroRect]; [control setHidden:YES]; } layout->AddView(control); if (input.length == autofill::DetailInput::LONG || input.length == autofill::DetailInput::SHORT_EOL) { ++column_set_id; } } if (inputs_) { [[self view] replaceSubview:inputs_ with:view]; [self requestRelayout]; } inputs_ = view; [self updateFieldIcons]; } - (void)updateFieldIcons { autofill::FieldValueMap fieldValues; for (NSControl<AutofillInputField>* input in [inputs_ subviews]) { DCHECK([input isKindOfClass:[NSControl class]]); DCHECK([input conformsToProtocol:@protocol(AutofillInputField)]); autofill::ServerFieldType fieldType = [self fieldTypeForControl:input]; NSString* value = [input fieldValue]; fieldValues[fieldType] = base::SysNSStringToUTF16(value); } autofill::FieldIconMap fieldIcons = delegate_->IconsForFields(fieldValues); for (autofill::FieldIconMap::const_iterator iter = fieldIcons.begin(); iter!= fieldIcons.end(); ++iter) { AutofillTextField* textfield = base::mac::ObjCCastStrict<AutofillTextField>( [inputs_ viewWithTag:iter->first]); [[textfield cell] setIcon:iter->second.ToNSImage()]; } } @end @implementation AutofillSectionContainer (ForTesting) - (NSControl*)getField:(autofill::ServerFieldType)type { return [inputs_ viewWithTag:type]; } - (void)setFieldValue:(NSString*)text forType:(autofill::ServerFieldType)type { NSControl<AutofillInputField>* field = [inputs_ viewWithTag:type]; if (field) [field setFieldValue:text]; } - (void)setSuggestionFieldValue:(NSString*)text { [[suggestContainer_ inputField] setFieldValue:text]; } - (void)activateFieldForType:(autofill::ServerFieldType)type { NSControl<AutofillInputField>* field = [inputs_ viewWithTag:type]; if (field) { [[field window] makeFirstResponder:field]; [self fieldEditedOrActivated:field edited:NO]; } } @end