123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846 |
- // Copyright 2018 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 "components/password_manager/ios/shared_password_controller.h"
- #include <stddef.h>
- #include <algorithm>
- #include <map>
- #include <memory>
- #include <string>
- #include <utility>
- #include <vector>
- #include "base/bind.h"
- #include "base/mac/foundation_util.h"
- #include "base/metrics/histogram_macros.h"
- #include "base/strings/sys_string_conversions.h"
- #include "base/strings/utf_string_conversions.h"
- #include "base/values.h"
- #include "components/autofill/core/browser/ui/popup_item_ids.h"
- #include "components/autofill/core/common/autofill_features.h"
- #include "components/autofill/core/common/form_data.h"
- #include "components/autofill/core/common/password_form_fill_data.h"
- #include "components/autofill/core/common/password_form_generation_data.h"
- #include "components/autofill/core/common/password_generation_util.h"
- #include "components/autofill/core/common/signatures.h"
- #include "components/autofill/core/common/unique_ids.h"
- #include "components/autofill/ios/browser/autofill_util.h"
- #import "components/autofill/ios/browser/form_suggestion_provider_query.h"
- #import "components/autofill/ios/form_util/form_activity_observer_bridge.h"
- #include "components/autofill/ios/form_util/form_activity_params.h"
- #include "components/autofill/ios/form_util/unique_id_data_tab_helper.h"
- #include "components/password_manager/core/browser/password_bubble_experiment.h"
- #include "components/password_manager/core/browser/password_generation_frame_helper.h"
- #include "components/password_manager/core/browser/password_manager_client.h"
- #include "components/password_manager/core/browser/password_manager_driver.h"
- #include "components/password_manager/ios/account_select_fill_data.h"
- #include "components/password_manager/ios/password_manager_ios_util.h"
- #include "components/strings/grit/components_strings.h"
- #include "ios/web/common/url_scheme_util.h"
- #include "ios/web/public/js_messaging/web_frame.h"
- #include "ios/web/public/js_messaging/web_frame_util.h"
- #include "ios/web/public/navigation/navigation_context.h"
- #import "ios/web/public/web_state.h"
- #include "services/network/public/cpp/shared_url_loader_factory.h"
- #include "ui/base/l10n/l10n_util_mac.h"
- #include "url/gurl.h"
- #if !defined(__has_feature) || !__has_feature(objc_arc)
- #error "This file requires ARC support."
- #endif
- using autofill::FieldDataManager;
- using autofill::FieldRendererId;
- using autofill::FormActivityObserverBridge;
- using autofill::FormData;
- using autofill::FormRendererId;
- using autofill::PasswordFormGenerationData;
- using autofill::password_generation::LogPasswordGenerationEvent;
- using autofill::password_generation::PasswordGenerationType;
- using base::SysNSStringToUTF16;
- using base::SysNSStringToUTF8;
- using base::SysUTF16ToNSString;
- using base::SysUTF8ToNSString;
- using l10n_util::GetNSString;
- using l10n_util::GetNSStringF;
- using password_manager::AccountSelectFillData;
- using password_manager::FillData;
- using password_manager::GetPageURLAndCheckTrustLevel;
- using password_manager::JsonStringToFormData;
- using password_manager::PasswordFormManagerForUI;
- using password_manager::PasswordGenerationFrameHelper;
- using password_manager::PasswordManagerClient;
- using password_manager::PasswordManagerDriver;
- using password_manager::PasswordManagerInterface;
- using password_manager::metrics_util::LogPasswordDropdownShown;
- using password_manager::metrics_util::PasswordDropdownState;
- namespace {
- // Password is considered not generated when user edits it below 4 characters.
- constexpr int kMinimumLengthForEditedPassword = 4;
- // The string ' •••' appended to the username in the suggestion.
- NSString* const kSuggestionSuffix = @" ••••••••";
- } // namespace
- @interface SharedPasswordController ()
- // Helper contains common password suggestion logic.
- @property(nonatomic, readonly) PasswordSuggestionHelper* suggestionHelper;
- // Tracks if current password is generated.
- @property(nonatomic, assign) BOOL isPasswordGenerated;
- // Tracks field when current password was generated.
- @property(nonatomic) FieldRendererId passwordGeneratedIdentifier;
- // Tracks current potential generated password until accepted or rejected.
- @property(nonatomic, copy) NSString* generatedPotentialPassword;
- - (BOOL)isIncognito;
- @end
- @implementation SharedPasswordController {
- PasswordManagerInterface* _passwordManager;
- // The WebState this instance is observing. Will be null after
- // -webStateDestroyed: has been called.
- web::WebState* _webState;
- // Bridge to observe WebState from Objective-C.
- std::unique_ptr<web::WebStateObserverBridge> _webStateObserverBridge;
- // Bridge to observe form activity in |_webState|.
- std::unique_ptr<FormActivityObserverBridge> _formActivityObserverBridge;
- // Form data for password generation on this page.
- std::map<FormRendererId, PasswordFormGenerationData> _formGenerationData;
- // Identifier of the field that was last typed into.
- FieldRendererId _lastTypedfieldIdentifier;
- // The value that was last typed by the user.
- NSString* _lastTypedValue;
- // Identifier of the last focused form.
- FormRendererId _lastFocusedFormIdentifier;
- // Identifier of the last focused field.
- FieldRendererId _lastFocusedFieldIdentifier;
- // Identifier of the last focused frame.
- web::WebFrame* _lastFocusedFrame;
- }
- - (instancetype)initWithWebState:(web::WebState*)webState
- manager:(password_manager::PasswordManagerInterface*)
- passwordManager
- formHelper:(PasswordFormHelper*)formHelper
- suggestionHelper:(PasswordSuggestionHelper*)suggestionHelper {
- self = [super init];
- if (self) {
- DCHECK(webState);
- _webState = webState;
- _webStateObserverBridge =
- std::make_unique<web::WebStateObserverBridge>(self);
- _webState->AddObserver(_webStateObserverBridge.get());
- _formActivityObserverBridge =
- std::make_unique<FormActivityObserverBridge>(_webState, self);
- _formHelper = formHelper;
- _formHelper.delegate = self;
- _suggestionHelper = suggestionHelper;
- _suggestionHelper.delegate = self;
- _passwordManager = passwordManager;
- }
- return self;
- }
- - (void)dealloc {
- if (_webState) {
- _webState->RemoveObserver(_webStateObserverBridge.get());
- }
- }
- - (BOOL)isIncognito {
- DCHECK(_delegate.passwordManagerClient);
- return _delegate.passwordManagerClient->IsIncognito();
- }
- #pragma mark - PasswordGenerationProvider
- - (void)triggerPasswordGeneration {
- if (!_lastFocusedFieldIdentifier) {
- return;
- }
- LogPasswordGenerationEvent(
- autofill::password_generation::PASSWORD_GENERATION_CONTEXT_MENU_PRESSED);
- [self generatePasswordForFormId:_lastFocusedFormIdentifier
- fieldIdentifier:_lastFocusedFieldIdentifier
- inFrame:_lastFocusedFrame
- isManuallyTriggered:YES];
- }
- #pragma mark - CRWWebStateObserver
- - (void)webState:(web::WebState*)webState
- didFinishNavigation:(web::NavigationContext*)navigation {
- DCHECK_EQ(_webState, webState);
- if (!navigation->HasCommitted() || navigation->IsSameDocument()) {
- return;
- }
- if (!GetPageURLAndCheckTrustLevel(webState, nullptr)) {
- return;
- }
- // Clear per-page state.
- [self.suggestionHelper resetForNewPage];
- auto fieldDataManager =
- UniqueIDDataTabHelper::FromWebState(_webState)->GetFieldDataManager();
- _passwordManager->PropagateFieldDataManagerInfo(
- *fieldDataManager, _delegate.passwordManagerDriver);
- // On non-iOS platforms navigations initiated by link click are excluded from
- // navigations which might be form submssions. On iOS there is no easy way to
- // check that the navigation is link initiated, so it is skipped. It should
- // not be so important since it is unlikely that the user clicks on a link
- // after filling password form w/o submitting it.
- _passwordManager->DidNavigateMainFrame(
- /*form_may_be_submitted=*/navigation->IsRendererInitiated());
- fieldDataManager->ClearData();
- }
- - (void)webState:(web::WebState*)webState didLoadPageWithSuccess:(BOOL)success {
- DCHECK_EQ(_webState, webState);
- // Retrieve the identity of the page. In case the page might be malicous,
- // returns early.
- GURL pageURL;
- if (!GetPageURLAndCheckTrustLevel(webState, &pageURL)) {
- return;
- }
- if (!web::UrlHasWebScheme(pageURL)) {
- return;
- }
- if (!webState->ContentIsHTML()) {
- // If the current page is not HTML, it does not contain any HTML forms.
- UniqueIDDataTabHelper* uniqueIDDataTabHelper =
- UniqueIDDataTabHelper::FromWebState(_webState);
- uint32_t maxUniqueID = uniqueIDDataTabHelper->GetNextAvailableRendererID();
- [self didFinishPasswordFormExtraction:std::vector<FormData>()
- withMaxUniqueID:maxUniqueID
- triggeredByFormChange:false];
- }
- }
- - (void)webState:(web::WebState*)webState
- frameDidBecomeAvailable:(web::WebFrame*)web_frame {
- DCHECK_EQ(_webState, webState);
- DCHECK(web_frame);
- if (!web_frame->CanCallJavaScriptFunction()) {
- return;
- }
- UniqueIDDataTabHelper* uniqueIDDataTabHelper =
- UniqueIDDataTabHelper::FromWebState(_webState);
- uint32_t nextAvailableRendererID =
- uniqueIDDataTabHelper->GetNextAvailableRendererID();
- [self.formHelper setUpForUniqueIDsWithInitialState:nextAvailableRendererID
- inFrame:web_frame];
- // Form parsing is run via the main frame for all same origin iframes.
- if (web_frame->IsMainFrame() && webState->ContentIsHTML()) {
- [self findPasswordFormsAndSendToPasswordStoreForFormChange:false];
- }
- }
- // Track detaching iframes.
- - (void)webState:(web::WebState*)webState
- frameWillBecomeUnavailable:(web::WebFrame*)web_frame {
- // No need to try to detect submissions when the webState is being destroyed.
- if (webState->IsBeingDestroyed()) {
- return;
- }
- if (web_frame->IsMainFrame() || !web_frame->CanCallJavaScriptFunction()) {
- return;
- }
- _passwordManager->OnIframeDetach(web_frame->GetFrameId(),
- _delegate.passwordManagerDriver,
- *self.formHelper.fieldDataManager);
- }
- - (void)webStateDestroyed:(web::WebState*)webState {
- DCHECK_EQ(_webState, webState);
- if (_webState) {
- _webState->RemoveObserver(_webStateObserverBridge.get());
- _webStateObserverBridge.reset();
- _formActivityObserverBridge.reset();
- _webState = nullptr;
- }
- _formGenerationData.clear();
- _isPasswordGenerated = NO;
- _lastTypedfieldIdentifier = FieldRendererId();
- _lastTypedValue = nil;
- _lastFocusedFormIdentifier = FormRendererId();
- _lastFocusedFieldIdentifier = FieldRendererId();
- _lastFocusedFrame = nullptr;
- _passwordManager = nullptr;
- }
- #pragma mark - FormSuggestionProvider
- - (void)checkIfSuggestionsAvailableForForm:
- (FormSuggestionProviderQuery*)formQuery
- isMainFrame:(BOOL)isMainFrame
- hasUserGesture:(BOOL)hasUserGesture
- webState:(web::WebState*)webState
- completionHandler:
- (SuggestionsAvailableCompletion)completion {
- if (!GetPageURLAndCheckTrustLevel(webState, nullptr)) {
- return;
- }
- [self.suggestionHelper
- checkIfSuggestionsAvailableForForm:formQuery
- isMainFrame:isMainFrame
- webState:webState
- completionHandler:^(BOOL suggestionsAvailable) {
- // Always display "Show All..." for password fields.
- completion([formQuery isOnPasswordField] ||
- suggestionsAvailable);
- }];
- if (self.isPasswordGenerated &&
- ([formQuery.type isEqual:@"input"] ||
- [formQuery.type isEqual:@"keyup"]) &&
- formQuery.uniqueFieldID == self.passwordGeneratedIdentifier) {
- // On other platforms, when the user clicks on generation field, we show
- // password in clear text. And the user has the possibility to edit it. On
- // iOS, it's harder to do (it's probably bad idea to change field type from
- // password to text). The decision was to give everything to the automatic
- // flow and avoid the manual flow, for a cleaner and simpler UI.
- if (formQuery.typedValue.length < kMinimumLengthForEditedPassword) {
- self.isPasswordGenerated = NO;
- LogPasswordGenerationEvent(
- autofill::password_generation::PASSWORD_DELETED);
- self.passwordGeneratedIdentifier = FieldRendererId();
- _passwordManager->OnPasswordNoLongerGenerated(
- _delegate.passwordManagerDriver);
- } else {
- web::WebFrame* frame = web::GetWebFrameWithId(
- _webState, SysNSStringToUTF8(formQuery.frameID));
- // Inject updated value to possibly update confirmation field.
- [self injectGeneratedPasswordForFormId:formQuery.uniqueFormID
- inFrame:frame
- generatedPassword:formQuery.typedValue
- completionHandler:nil];
- }
- }
- if (formQuery.uniqueFieldID != _lastTypedfieldIdentifier ||
- ![formQuery.typedValue isEqual:_lastTypedValue]) {
- // This method is called multiple times for the same user keystroke. Inform
- // only once the keystroke.
- _lastTypedfieldIdentifier = formQuery.uniqueFieldID;
- _lastTypedValue = formQuery.typedValue;
- if ([formQuery.type isEqual:@"input"] ||
- [formQuery.type isEqual:@"keyup"]) {
- [self.formHelper updateFieldDataOnUserInput:formQuery.uniqueFieldID
- inputValue:formQuery.typedValue];
- _passwordManager->UpdateStateOnUserInput(
- _delegate.passwordManagerDriver, formQuery.uniqueFormID,
- formQuery.uniqueFieldID, SysNSStringToUTF16(formQuery.typedValue));
- }
- }
- }
- - (void)retrieveSuggestionsForForm:(FormSuggestionProviderQuery*)formQuery
- webState:(web::WebState*)webState
- completionHandler:(SuggestionsReadyCompletion)completion {
- if (!GetPageURLAndCheckTrustLevel(webState, nullptr)) {
- return;
- }
- NSArray<FormSuggestion*>* rawSuggestions = [self.suggestionHelper
- retrieveSuggestionsWithFormID:formQuery.uniqueFormID
- fieldIdentifier:formQuery.uniqueFieldID
- fieldType:formQuery.fieldType];
- NSMutableArray<FormSuggestion*>* suggestions = [NSMutableArray array];
- bool isPasswordField = [formQuery isOnPasswordField];
- for (FormSuggestion* rawSuggestion in rawSuggestions) {
- // 1) If this is a focus event or the field is empty show all suggestions.
- // Otherwise:
- // 2) If this is a username field then show only credentials with matching
- // prefixes.
- // 3) If this is a password field then show suggestions only if
- // the field is empty.
- if (![formQuery hasFocusType] && formQuery.typedValue.length > 0 &&
- (isPasswordField ||
- ![rawSuggestion.value hasPrefix:formQuery.typedValue])) {
- continue;
- }
- DCHECK(self.delegate.passwordManagerClient);
- NSString* value =
- [rawSuggestion.value stringByAppendingString:kSuggestionSuffix];
- FormSuggestion* suggestion =
- [FormSuggestion suggestionWithValue:value
- displayDescription:rawSuggestion.displayDescription
- icon:nil
- identifier:0
- requiresReauth:YES];
- [suggestions addObject:suggestion];
- }
- absl::optional<PasswordDropdownState> suggestionState;
- if (suggestions.count) {
- suggestionState = PasswordDropdownState::kStandard;
- }
- if ([self canGeneratePasswordForForm:formQuery.uniqueFormID
- fieldIdentifier:formQuery.uniqueFieldID
- fieldType:formQuery.fieldType]) {
- // Add "Suggest Password...".
- NSString* suggestPassword = GetNSString(IDS_IOS_SUGGEST_PASSWORD);
- FormSuggestion* suggestion = [FormSuggestion
- suggestionWithValue:suggestPassword
- displayDescription:nil
- icon:nil
- identifier:autofill::POPUP_ITEM_ID_GENERATE_PASSWORD_ENTRY
- requiresReauth:NO];
- [suggestions addObject:suggestion];
- suggestionState = PasswordDropdownState::kStandardGenerate;
- }
- if (suggestionState) {
- LogPasswordDropdownShown(*suggestionState, [self isIncognito]);
- }
- completion([suggestions copy], self);
- }
- - (void)didSelectSuggestion:(FormSuggestion*)suggestion
- form:(NSString*)formName
- uniqueFormID:(FormRendererId)uniqueFormID
- fieldIdentifier:(NSString*)fieldIdentifier
- uniqueFieldID:(FieldRendererId)uniqueFieldID
- frameID:(NSString*)frameID
- completionHandler:(SuggestionHandledCompletion)completion {
- web::WebFrame* frame =
- web::GetWebFrameWithId(_webState, SysNSStringToUTF8(frameID));
- switch (suggestion.identifier) {
- case autofill::POPUP_ITEM_ID_ALL_SAVED_PASSWORDS_ENTRY: {
- completion();
- password_manager::metrics_util::LogPasswordDropdownItemSelected(
- password_manager::metrics_util::PasswordDropdownSelectedOption::
- kShowAll,
- [self isIncognito]);
- return;
- }
- case autofill::POPUP_ITEM_ID_GENERATE_PASSWORD_ENTRY: {
- // Don't call completion because current suggestion state should remain
- // whether user injects a generated password or cancels.
- [self generatePasswordForFormId:uniqueFormID
- fieldIdentifier:uniqueFieldID
- inFrame:frame
- isManuallyTriggered:NO];
- password_manager::metrics_util::LogPasswordDropdownItemSelected(
- password_manager::metrics_util::PasswordDropdownSelectedOption::
- kGenerate,
- [self isIncognito]);
- return;
- }
- default: {
- password_manager::metrics_util::LogPasswordDropdownItemSelected(
- password_manager::metrics_util::PasswordDropdownSelectedOption::
- kPassword,
- [self isIncognito]);
- DCHECK([suggestion.value hasSuffix:kSuggestionSuffix]);
- NSString* username = [suggestion.value
- substringToIndex:suggestion.value.length - kSuggestionSuffix.length];
- std::unique_ptr<password_manager::FillData> fillData =
- [self.suggestionHelper passwordFillDataForUsername:username];
- if (!fillData) {
- completion();
- return;
- }
- [self.formHelper fillPasswordFormWithFillData:*fillData
- inFrame:frame
- triggeredOnField:uniqueFieldID
- completionHandler:^(BOOL success) {
- completion();
- }];
- break;
- }
- }
- [_delegate sharedPasswordController:self didAcceptSuggestion:suggestion];
- }
- - (SuggestionProviderType)type {
- return SuggestionProviderTypePassword;
- }
- #pragma mark - PasswordManagerDriverDelegate
- - (const GURL&)lastCommittedURL {
- return _webState ? _webState->GetLastCommittedURL() : GURL::EmptyGURL();
- }
- - (void)fillPasswordForm:(const autofill::PasswordFormFillData&)formData
- completionHandler:(void (^)(BOOL))completionHandler {
- [self.suggestionHelper processWithPasswordFormFillData:formData];
- [self.formHelper fillPasswordForm:formData
- inFrame:web::GetMainFrame(_webState)
- completionHandler:completionHandler];
- }
- - (void)onNoSavedCredentials {
- [self.suggestionHelper processWithNoSavedCredentials];
- }
- - (PasswordGenerationFrameHelper*)passwordGenerationHelper {
- return _delegate.passwordManagerDriver->GetPasswordGenerationHelper();
- }
- - (void)formEligibleForGenerationFound:(const PasswordFormGenerationData&)form {
- _formGenerationData[form.form_renderer_id] = form;
- }
- #pragma mark - PasswordFormHelperDelegate
- - (void)formHelper:(PasswordFormHelper*)formHelper
- didSubmitForm:(const FormData&)form
- inMainFrame:(BOOL)inMainFrame {
- if (inMainFrame) {
- _passwordManager->OnPasswordFormSubmitted(_delegate.passwordManagerDriver,
- form);
- } else {
- // Show a save prompt immediately because for iframes it is very hard to
- // figure out correctness of password forms submission.
- _passwordManager->OnSubframeFormSubmission(_delegate.passwordManagerDriver,
- form);
- }
- }
- #pragma mark - PasswordSuggestionHelperDelegate
- - (void)suggestionHelperShouldTriggerFormExtraction:
- (PasswordSuggestionHelper*)suggestionHelper {
- [self findPasswordFormsAndSendToPasswordStoreForFormChange:false];
- }
- #pragma mark - Private methods
- - (void)didFinishPasswordFormExtraction:(const std::vector<FormData>&)forms
- withMaxUniqueID:(uint32_t)maxID
- triggeredByFormChange:(BOOL)triggeredByFormChange {
- // Do nothing if |self| has been detached.
- if (!_passwordManager) {
- return;
- }
- if (!forms.empty()) {
- [self.suggestionHelper updateStateOnPasswordFormExtracted];
- UniqueIDDataTabHelper* uniqueIDDataTabHelper =
- UniqueIDDataTabHelper::FromWebState(_webState);
- // Update NextAvailableRendererId if a bigger value was extracted.
- if (uniqueIDDataTabHelper->GetNextAvailableRendererID() < maxID)
- uniqueIDDataTabHelper->SetNextAvailableRendererID(++maxID);
- // Invoke the password manager callback to autofill password forms
- // on the loaded page.
- _passwordManager->OnPasswordFormsParsed(_delegate.passwordManagerDriver,
- forms);
- } else {
- [self onNoSavedCredentials];
- }
- // Invoke the password manager callback to check if password was
- // accepted or rejected. If accepted, infobar is presented. If
- // rejected, the provisionally saved password is deleted. On Chrome
- // w/ a renderer, it is the renderer who calls OnPasswordFormsParsed()
- // and OnPasswordFormsRendered(). Bling has to improvised a bit on the
- // ordering of these two calls.
- // Only check for form submissions if forms are not being parsed due to
- // added elements to the form.
- if (!triggeredByFormChange) {
- _passwordManager->OnPasswordFormsRendered(_delegate.passwordManagerDriver,
- forms);
- }
- }
- - (void)findPasswordFormsAndSendToPasswordStoreForFormChange:
- (BOOL)triggeredByFormChange {
- // Read all password forms from the page and send them to the password
- // manager.
- __weak SharedPasswordController* weakSelf = self;
- [self.formHelper findPasswordFormsWithCompletionHandler:^(
- const std::vector<FormData>& forms, uint32_t maxID) {
- [weakSelf didFinishPasswordFormExtraction:forms
- withMaxUniqueID:maxID
- triggeredByFormChange:triggeredByFormChange];
- }];
- }
- - (BOOL)canGeneratePasswordForForm:(FormRendererId)formIdentifier
- fieldIdentifier:(FieldRendererId)fieldIdentifier
- fieldType:(NSString*)fieldType {
- if (!self.passwordGenerationHelper->IsGenerationEnabled(
- /*log_debug_data*/ true)) {
- return NO;
- }
- if (![fieldType isEqual:kPasswordFieldType]) {
- return NO;
- }
- const PasswordFormGenerationData* generationData =
- [self formForGenerationFromFormID:formIdentifier];
- if (!generationData) {
- return NO;
- }
- FieldRendererId newPasswordIdentifier =
- generationData->new_password_renderer_id;
- if (fieldIdentifier == newPasswordIdentifier) {
- return YES;
- }
- // Don't show password generation if the field is 'confirm password'.
- return NO;
- }
- - (const PasswordFormGenerationData*)formForGenerationFromFormID:
- (FormRendererId)formIdentifier {
- if (_formGenerationData.find(formIdentifier) != _formGenerationData.end()) {
- return &_formGenerationData[formIdentifier];
- }
- return nullptr;
- }
- - (void)generatePasswordForFormId:(FormRendererId)formIdentifier
- fieldIdentifier:(FieldRendererId)fieldIdentifier
- inFrame:(web::WebFrame*)frame
- isManuallyTriggered:(BOOL)isManuallyTriggered {
- const autofill::PasswordFormGenerationData* generationData =
- [self formForGenerationFromFormID:formIdentifier];
- if (!isManuallyTriggered && !generationData) {
- return;
- }
- BOOL shouldUpdateGenerationData =
- !generationData ||
- generationData->new_password_renderer_id != fieldIdentifier;
- if (isManuallyTriggered && shouldUpdateGenerationData) {
- PasswordFormGenerationData generationData = {
- .form_renderer_id = formIdentifier,
- .new_password_renderer_id = fieldIdentifier,
- };
- [self formEligibleForGenerationFound:generationData];
- }
- __weak SharedPasswordController* weakSelf = self;
- auto formDataCompletion = ^(BOOL found, const autofill::FormData& form) {
- autofill::FormSignature formSignature =
- found ? CalculateFormSignature(form) : autofill::FormSignature(0);
- autofill::FieldSignature fieldSignature = autofill::FieldSignature(0);
- int maxLength = 0;
- if (found) {
- for (const autofill::FormFieldData& field : form.fields) {
- if (field.unique_renderer_id == fieldIdentifier) {
- fieldSignature = CalculateFieldSignatureForField(field);
- maxLength = field.max_length;
- break;
- }
- }
- }
- std::u16string generatedPassword =
- weakSelf.passwordGenerationHelper->GeneratePassword(
- [weakSelf lastCommittedURL], formSignature, fieldSignature,
- maxLength);
- weakSelf.generatedPotentialPassword = SysUTF16ToNSString(generatedPassword);
- auto clearPotentialPassword = ^{
- weakSelf.generatedPotentialPassword = nil;
- };
- [weakSelf.delegate
- sharedPasswordController:weakSelf
- showGeneratedPotentialPassword:weakSelf.generatedPotentialPassword
- decisionHandler:^(BOOL accept) {
- if (accept) {
- LogPasswordGenerationEvent(
- autofill::password_generation::
- PASSWORD_ACCEPTED);
- [weakSelf
- injectGeneratedPasswordForFormId:formIdentifier
- inFrame:frame
- generatedPassword:
- weakSelf
- .generatedPotentialPassword
- completionHandler:
- clearPotentialPassword];
- } else {
- clearPotentialPassword();
- }
- }];
- };
- [self.formHelper extractPasswordFormData:formIdentifier
- completionHandler:formDataCompletion];
- _passwordManager->SetGenerationElementAndTypeForForm(
- _delegate.passwordManagerDriver, formIdentifier, fieldIdentifier,
- isManuallyTriggered ? PasswordGenerationType::kManual
- : PasswordGenerationType::kAutomatic);
- }
- - (void)injectGeneratedPasswordForFormId:(FormRendererId)formIdentifier
- inFrame:(web::WebFrame*)frame
- generatedPassword:(NSString*)generatedPassword
- completionHandler:(void (^)())completionHandler {
- const autofill::PasswordFormGenerationData* generationData =
- [self formForGenerationFromFormID:formIdentifier];
- if (!generationData) {
- return;
- }
- FieldRendererId newPasswordUniqueId =
- generationData->new_password_renderer_id;
- FieldRendererId confirmPasswordUniqueId =
- generationData->confirmation_password_renderer_id;
- __weak SharedPasswordController* weakSelf = self;
- auto generatedPasswordInjected = ^(BOOL success) {
- if (success) {
- [weakSelf onFilledPasswordForm:formIdentifier
- withGeneratedPassword:generatedPassword
- passwordUniqueId:newPasswordUniqueId];
- }
- if (completionHandler) {
- completionHandler();
- }
- };
- [self.formHelper fillPasswordForm:formIdentifier
- inFrame:frame
- newPasswordIdentifier:newPasswordUniqueId
- confirmPasswordIdentifier:confirmPasswordUniqueId
- generatedPassword:generatedPassword
- completionHandler:generatedPasswordInjected];
- }
- - (void)onFilledPasswordForm:(FormRendererId)formIdentifier
- withGeneratedPassword:(NSString*)generatedPassword
- passwordUniqueId:(FieldRendererId)newPasswordUniqueId {
- __weak SharedPasswordController* weakSelf = self;
- auto passwordPresaved = ^(BOOL found, const autofill::FormData& form) {
- // If the form isn't found, it disappeared between the call to
- // [self.formHelper fillPasswordForm:newPasswordIdentifier:...]
- // and here. There isn't much that can be done.
- if (!found)
- return;
- [weakSelf presaveGeneratedPassword:generatedPassword
- passwordUniqueId:newPasswordUniqueId
- formData:form];
- };
- [self.formHelper extractPasswordFormData:formIdentifier
- completionHandler:passwordPresaved];
- self.isPasswordGenerated = YES;
- self.passwordGeneratedIdentifier = newPasswordUniqueId;
- }
- - (void)presaveGeneratedPassword:(NSString*)generatedPassword
- passwordUniqueId:(FieldRendererId)newPasswordUniqueId
- formData:(const autofill::FormData&)formData {
- if (!_passwordManager)
- return;
- _passwordManager->PresaveGeneratedPassword(
- _delegate.passwordManagerDriver, formData,
- SysNSStringToUTF16(generatedPassword), newPasswordUniqueId);
- }
- // Checks that all fields with |fieldIds| have user input recorded by
- // the FieldDataManager.
- - (BOOL)allFieldsContainUserInput:
- (const std::vector<FieldRendererId>&)fieldIds {
- for (auto fieldId : fieldIds) {
- if (!self.formHelper.fieldDataManager->HasFieldData(fieldId)) {
- return NO;
- }
- }
- return YES;
- }
- #pragma mark - FormActivityObserver
- - (void)webState:(web::WebState*)webState
- didRegisterFormActivity:(const autofill::FormActivityParams&)params
- inFrame:(web::WebFrame*)frame {
- DCHECK_EQ(_webState, webState);
- GURL pageURL;
- if (!GetPageURLAndCheckTrustLevel(webState, &pageURL) || !frame ||
- !frame->CanCallJavaScriptFunction() || params.input_missing) {
- _lastFocusedFormIdentifier = FormRendererId();
- _lastFocusedFieldIdentifier = FieldRendererId();
- _lastFocusedFrame = nullptr;
- return;
- }
- if (params.type == "focus") {
- _lastFocusedFormIdentifier = params.unique_form_id;
- _lastFocusedFieldIdentifier = params.unique_field_id;
- _lastFocusedFrame = frame;
- }
- // If there's a change in password forms on a page, they should be parsed
- // again.
- if (params.type == "form_changed") {
- [self findPasswordFormsAndSendToPasswordStoreForFormChange:true];
- }
- // If the form was cleared PasswordManager should be informed to decide
- // whether it's a change password form that was submitted.
- if (params.type == "password_form_cleared") {
- FormData formData;
- if (!JsonStringToFormData(SysUTF8ToNSString(params.value), &formData,
- pageURL)) {
- return;
- }
- _passwordManager->OnPasswordFormCleared(_delegate.passwordManagerDriver,
- formData);
- }
- }
- // If the form was removed, PasswordManager should be informed to decide
- // whether the form was submitted.
- - (void)webState:(web::WebState*)webState
- didRegisterFormRemoval:(const autofill::FormRemovalParams&)params
- inFrame:(web::WebFrame*)frame {
- if (!params.unique_form_id) {
- // If formless password fields were removed, check that all of them had
- // user input.
- if (![self allFieldsContainUserInput:params.removed_unowned_fields]) {
- return;
- }
- }
- _passwordManager->OnPasswordFormRemoved(_delegate.passwordManagerDriver,
- *self.formHelper.fieldDataManager,
- params.unique_form_id);
- }
- @end
|